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%20Introduction%22%0A%23%20description%20%3D%20%22LP%2C%20MIP%2C%20knapsack%20and%20price%20breaks%22%0A%23%20image%20%3D%20%22__marimo__%2Fthumbnail-moselpy-intro.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%3A%20Introduction**%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_intro.py***%20-%20Getting%20started%20with%20MoselPy%20using%20introductory%20Xpress%20application%20book%20examples.%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%20%23%23%20Getting%20started%20with%20MoselPy%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%5BMoselPy%5D(https%3A%2F%2Fpypi.org%2Fproject%2Fmoselpy%2F)%20lets%20you%20compile%20and%20run%20FICO%26reg%3B%20Xpress%20Mosel%20models%20from%20Python%2C%20and%20exchange%20data%20bidirectionally.%20It%20enables%20you%20to%20combine%20Mosel's%20algebraic%20modeling%20language%20with%20Python's%20data%20and%20visualization%20ecosystem.%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_intro)%0A%0A%20%20%20%20%23%23%23%20When%20to%20use%20MoselPy%0A%0A%20%20%20%20%7C%20Scenario%20%7C%20Use%20%7C%0A%20%20%20%20%7C----------%7C-----%7C%0A%20%20%20%20%7C%20You%20have%20existing%20Mosel%20models%20and%20want%20to%20execute%20them%20from%20Python%20%7C%20**MoselPy**%20%7C%0A%20%20%20%20%7C%20You%20prefer%20Mosel's%20algebraic%20syntax%20for%20building%20models%20%7C%20**MoselPy**%20%7C%0A%20%20%20%20%7C%20You%20want%20to%20distribute%20compiled%20%60.bim%60%20files%20for%20IP%20protection%20%7C%20**MoselPy**%20%7C%0A%20%20%20%20%7C%20You%20are%20building%20a%20new%20model%20directly%20in%20Python%20%7C%20%5BXpress%20Python%20API%5D(https%3A%2F%2Fwww.fico.com%2Ffico-xpress-optimization%2Fdocs%2Flatest%2Fsolver%2Foptimizer%2Fpython%2FHTML%2FGUID-616C323F-05D8-3460-B0D7-80F77DA7D046.html)%20%7C%0A%0A%20%20%20%20%23%23%23%20Running%20Mosel%20models%20from%20Python%0A%0A%20%20%20%20MoselPy%20offers%20two%20workflows%20for%20running%20a%20Mosel%20model%3A%0A%0A%20%20%20%20**One-step%3A**%20compile%20and%20run%20in%20a%20single%20call%3B%20returns%20%60(model%2C%20output_dict)%60%3A%0A%0A%20%20%20%20%60%60%60python%0A%20%20%20%20model%2C%20output%20%3D%20mp.execute_model(%22model.mos%22%2C%20exec_params%3D%7B%22OUTFILE%22%3A%20%22moselpy%3A%22%7D)%0A%20%20%20%20%60%60%60%0A%0A%20%20%20%20**Three-step%3A**%20compile%20once%2C%20load%20the%20binary%2C%20then%20run%20as%20many%20times%20as%20needed%3A%0A%0A%20%20%20%20%60%60%60python%0A%20%20%20%20%23%201.%20Compile%3A%20Mosel%20source%20(.mos)%20-%3E%20binary%20model%20(.bim)%0A%20%20%20%20mp.compile_model(%22model.mos%22%2C%20%22model.bim%22)%0A%0A%20%20%20%20%23%202.%20Load%20the%20compiled%20model%0A%20%20%20%20model%20%3D%20mp.load_model(%22model.bim%22)%0A%0A%20%20%20%20%23%203.%20Run%20with%20optional%20parameters%3B%20results%20come%20back%20as%20a%20dict%0A%20%20%20%20output%20%3D%20model.run(exec_params%3D%7B%22OUTFILE%22%3A%20%22moselpy%3A%22%7D)%0A%20%20%20%20%60%60%60%0A%0A%20%20%20%20In%20both%20cases%2C%20results%20exported%20via%20%60initializations%20to%20%22moselpy%3A%22%60%20are%20returned%20in%20%60output%60.%20The%20three-step%20workflow%20is%20preferred%20when%20the%20same%20model%20needs%20to%20run%20multiple%20times%20with%20different%20data%20(load%20once%2C%20run%20many%20times)%2C%20and%20produces%20a%20distributable%20%60.bim%60%20binary.%20Note%20that%20%60execute_model%60%20does%20not%20write%20a%20%60.bim%60%20file%20to%20disk.%0A%0A%20%20%20%20%23%23%23%20Adapting%20a%20Mosel%20model%20for%20MoselPy%0A%0A%20%20%20%20The%20models%20in%20this%20notebook%20are%20adapted%20versions%20of%20the%20original%20%5BXpress%20application%20book%20examples%5D(https%3A%2F%2Fexamples.xpress.fico.com%2Fexample.pl%3Fid%3Dmosel_app_intro).%20The%20four%20standard%20adaptations%20are%3A%0A%0A%20%20%20%201.%20**Add%20%60DATASOURCE%60%20for%20data%20file%20models%3A**%20Replace%20hardcoded%20filenames%20in%20%60initializations%20from%60%20with%20a%20%60DATASOURCE%60%20parameter.%20Override%20it%20with%20%60exec_params%3D%7B%22DATASOURCE%22%3A%20%22moselpy%3A%22%7D%60%20to%20inject%20data%20from%20Python%20via%20%60input_data%3D%7B...%7D%60.%0A%0A%20%20%20%202.%20**Replace%20%60writeln%60%20output%20with%20%60initializations%20to%20OUTFILE%60%3A**%20Original%20models%20use%20%60writeln()%60%20to%20print%20results.%20Adapted%20models%20add%20%60OUTFILE%20%3D%20%22%5C%22%60%20to%20a%20%60parameters%60%20block%20and%20write%20results%20with%20%60initializations%20to%20OUTFILE%60.%20Empty%20string%20writes%20to%20stdout%20when%20run%20standalone%3B%20%60%22moselpy%3A%22%60%20returns%20data%20to%20Python%20as%20a%20dict%20via%20%60exec_params%3D%7B%22OUTFILE%22%3A%20%22moselpy%3A%22%7D%60.%0A%0A%20%20%20%203.%20**Use%20%60evaluation%20of%60%20for%20scalar%20results%3A**%20Scalar%20expressions%20such%20as%20%60getobjval%60%20or%20%60getsol(xs)%60%20can%20be%20exported%20without%20an%20intermediate%20variable%3A%20%60evaluation%20of%20getobjval%20as%20'obj_val'%60.%0A%0A%20%20%20%204.%20**Array%20results%20require%20a%20helper%20variable%3A**%20For%20arrays%20of%20decision%20variables%2C%20declare%20a%20%60real%60%20array%20(e.g.%2C%20%60sol_x%3A%20array(ITEMS)%20of%20real%60)%2C%20populate%20it%20after%20the%20solve%20with%20%60forall(i%20in%20ITEMS)%20sol_x(i)%20%3A%3D%20getsol(x(i))%60%2C%20then%20include%20%60sol_x%60%20in%20the%20%60initializations%20to%60%20block.%0A%0A%20%20%20%20%23%23%23%20Models%20and%20data%20files%0A%0A%20%20%20%20The%20Mosel%20source%20files%20(%60.mos%60)%20and%20compiled%20binary%20model%20files%20(%60.bim%60)%20for%20this%20notebook%20live%20in%20%5B%60models%2Fintro%2F%60%5D(https%3A%2F%2Fgithub.com%2Ffico-xpress%2Fpython-notebooks%2Ftree%2Fmain%2Fmarimo%2Fpublic%2Fmoselpy%2Fmodels%2Fintro).%20Data%20files%20used%20by%20some%20models%20are%20in%20%5B%60data%2Fintro%2F%60%5D(https%3A%2F%2Fgithub.com%2Ffico-xpress%2Fpython-notebooks%2Ftree%2Fmain%2Fmarimo%2Fpublic%2Fmoselpy%2Fdata%2Fintro).%20The%20%60.bim%60%20files%20are%20generated%20by%20the%20first%20%60compile_model()%60%20call%3B%20they%20are%20platform-independent%20and%20can%20be%20distributed%20without%20the%20source.%0A%0A%20%20%20%20%23%23%23%20About%20this%20notebook%0A%0A%20%20%20%20This%20notebook%20groups%20models%20that%20explain%20the%20basics%20of%20LP%20and%20MIP%20model%20development%20with%20Mosel%2C%20including%20data%20input%2Foutput%20patterns.%20The%20examples%20progress%20from%20a%20simple%20LP%20(chess%20sets)%20through%20integer%20programming%20and%20knapsack%20variants%2C%20to%20special%20ordered%20sets%20(SOS1%2FSOS2)%20and%20piecewise%20linear%20functions.%0A%0A%20%20%20%20%23%23%23%20Suppressing%20solver%20output%0A%0A%20%20%20%20The%20setup%20cell%20below%20calls%20%60mp.config.set_default_stream(mp.StreamType.OUTPUT%2C%20%22null%3A%22)%60%20before%20any%20model%20is%20loaded%2C%20silencing%20solver%20output%20globally%20for%20this%20notebook.%20This%20must%20be%20done%20before%20the%20first%20%60load_model()%60%20call%3B%20per-model%20%60model.set_default_stream()%60%20is%20available%20as%20an%20alternative%20for%20finer%20control.%20To%20retrieve%20solver%20logging%20output%2C%20enable%20it%20by%20setting%20the%20%60XPRS_VERBOSE%60%20control.%20The%20error%20stream%20(%60mp.StreamType.ERROR%60)%20should%20not%20be%20silenced%20-%20redirect%20it%20to%20Python%20if%20you%20need%20to%20capture%20runtime%20errors.%0A%0A%20%20%20%20%23%23%23%20MoselPy%20features%20demonstrated%20in%20this%20notebook%0A%0A%20%20%20%20-%20**Single%20execution%20command%20vs%20compile-load-run%20sequence**%3A%20the%20Chess%20sets%20LP%20demonstrates%20both%20workflows%20side%20by%20side%20-%20%60mp.execute_model()%60%20for%20a%20one-shot%20run%20and%20the%20three-step%20sequence%20for%20reuse.%0A%20%20%20%20-%20**%60exec_params%60%20for%20output%20routing**%3A%20all%20models%20return%20results%20via%20%60OUTFILE%3D%22moselpy%3A%22%60%20in%20%60exec_params%60%3B%20knapsack%20models%20also%20set%20%60DATASOURCE%3D%22moselpy%3A%22%60%20to%20inject%20input%20data.%0A%20%20%20%20-%20**%60input_data%60%20with%201-indexed%20dicts**%3A%20the%20Knapsack%20with%20external%20data%20file%20passes%20item%20values%20and%20weights%20as%20Python%20dicts%20directly%20to%20%60input_data%60%2C%20replacing%20the%20%60.dat%60%20file%20for%20the%20notebook%20run.%0A%20%20%20%20-%20**%60pd.DataFrame%60%20as%20%60input_data%60**%3A%20the%20Knapsack%20with%20DataFrame%20input%20passes%20item%20data%20as%20a%20%60pd.DataFrame%60%3B%20MoselPy%20maps%20the%20index%20to%20the%20Mosel%20array%20index%20automatically.%0A%20%20%20%20-%20**%60model.find_identifier(use_pandas%3DTrue)%60**%3A%20the%20Function%20approximation%20(SOS2%2Fpiecewise%20linear)%20example%20retrieves%20the%20solution%20array%20as%20a%20pandas%20Series%20after%20the%20solve.%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%23%20Setup%0A%0A%20%20%20%20The%20cell%20below%20imports%20MoselPy%2C%20matplotlib%2C%20NumPy%2C%20and%20pandas%2C%20and%20to%20set%20%60MODELS%60%20and%20%60DATA%60%2C%20the%20path%20variables%20pointing%20to%20the%20Mosel%20source%20files%20and%20data%20files%20used%20throughout%20this%20notebook.%20Other%20notebooks%20in%20this%20series%20place%20an%20equivalent%20setup%20cell%20right%20after%20the%20package%20installation%20cell%3B%20here%20it%20appears%20alongside%20the%20workflow%20description%20above.%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%22intro%22)%0A%20%20%20%20DATA%20%20%20%3D%20str(_root%20%2F%20%22data%22%20%2F%20%22intro%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%20ExecStatus%2C%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%20Chess%20sets%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*Introductory%20example%3A%20Chapter%201%20-%20Linear%20Programming*%0A%0A%20%20%20%20%3E%20**Mosel%20model%3A**%20%5B%60chess2_p.mos%60%5D(https%3A%2F%2Fgithub.com%2Ffico-xpress%2Fpython-notebooks%2Fblob%2Fmain%2Fmarimo%2Fpublic%2Fmoselpy%2Fmodels%2Fintro%2Fchess2_p.mos)%0A%0A%20%20%20%20A%20company%20makes%20two%20chess%20set%20sizes.%20Each%20**small%20set**%20requires%203%20lathe-hours%20and%201%20kg%20boxwood%3B%20each%20**large%20set**%20requires%202%20lathe-hours%20and%203%20kg%20boxwood.%20Available%20per%20week%3A%20160%20lathe-hours%20and%20200%20kg%20boxwood.%20Profit%3A%20%5C%245%20per%20small%20set%2C%20%5C%2420%20per%20large%20set.%0A%0A%20%20%20%20**Decision%20variables%3A**%20%24x_s%2C%20x_l%20%5Cgeq%200%24%20(sets%20produced%20per%20week).%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%205%5C%2Cx_s%20%2B%2020%5C%2Cx_l%20%5C%5C%0A%20%20%20%20%5Ctext%7Bsubject%20to%7D%20%5Cquad%20%26%20x_s%20%2B%203%5C%2Cx_l%20%5Cleq%20200%20%5Cquad%20%5Ctext%7B(boxwood%2C%20kg)%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%203%5C%2Cx_s%20%2B%202%5C%2Cx_l%20%5Cleq%20160%20%5Cquad%20%5Ctext%7B(lathe-hours)%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%20x_s%2C%5C%2C%20x_l%20%5Cgeq%200%0A%20%20%20%20%5Cend%7Balign*%7D%0A%20%20%20%20%24%24%0A%0A%20%20%20%20Beyond%20the%20optimal%20solution%2C%20post-solution%20LP%20queries%20reveal%20constraint%20and%20variable%20information%3A%0A%0A%20%20%20%20-%20**Constraint%20activity**%20(%60getact%60)%3A%20how%20much%20of%20each%20resource%20is%20actually%20used%20at%20the%20optimal%20solution%0A%20%20%20%20-%20**Dual%20value%20%2F%20shadow%20price**%20(%60getdual%60)%3A%20how%20much%20the%20objective%20would%20improve%20if%20the%20right-hand%20side%20of%20a%20binding%20constraint%20were%20relaxed%20by%20one%20unit%2C%20e.g.%20a%20lathe%20shadow%20price%20of%206.67%20means%20one%20extra%20lathe-hour%20is%20worth%20%5C%246.67%20of%20profit%0A%20%20%20%20-%20**Reduced%20cost**%20(%60getrcost%60)%3A%20how%20much%20the%20objective%20function%20coefficient%20of%20a%20non-basic%20variable%20must%20change%20before%20that%20variable%20enters%20the%20basis%3B%20zero%20for%20basic%20variables%0A%0A%20%20%20%20**Adaptation%20from%20original%3A**%20%60chess2.mos%60%20uses%20%60writeln()%60%20for%20all%20output.%20In%20%60chess2_p.mos%60%2C%20every%20%60writeln%60%20call%20is%20replaced%20by%20an%20%60evaluation%20of%60%20expression%20inside%20%60initializations%20to%20OUTFILE%60%2C%20one%20line%20per%20result.%20This%20is%20the%20simplest%20adaptation%3A%20no%20helper%20variables%20needed%20when%20all%20outputs%20are%20scalars.%0A%0A%20%20%20%20The%20cell%20below%20uses%20%60mp.execute_model()%60%2C%20which%20compiles%20and%20runs%20in%20a%20single%20call%20and%20returns%20a%20%60(model%2C%20output)%60%20tuple%3A%20%60model%60%20is%20the%20%60Model%60%20object%20(useful%20for%20inspecting%20%60model.prob_status%60%2C%20accessing%20identifiers%2C%20etc.)%20and%20%60output%60%20is%20a%20dict%20containing%20everything%20exported%20via%20%60initializations%20to%20%22moselpy%3A%22%60.%20This%20is%20the%20one-step%20alternative%20to%20the%20compile%2Fload%2Frun%20three-step%20workflow%20-%20convenient%20for%20a%20single%20run%20when%20no%20%60.bim%60%20file%20needs%20to%20be%20written%20to%20disk.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(MODELS%2C%20mp)%3A%0A%20%20%20%20model%2C%20output%20%3D%20mp.execute_model(f%22%7BMODELS%7D%2Fchess2_p.mos%22%2C%20exec_params%3D%7B%22OUTFILE%22%3A%20%22moselpy%3A%22%7D)%0A%0A%20%20%20%20if%20model.prob_status.is_solution_available%3A%0A%20%20%20%20%20%20%20%20print(f%22Optimal%20profit%3A%20%24%7Boutput%5B'obj_val'%5D%3A.2f%7D%22)%0A%20%20%20%20%20%20%20%20print(f%22Small%20sets%3A%20%7Boutput%5B'sol_xs'%5D%3A.0f%7D%20%20%20Large%20sets%3A%20%7Boutput%5B'sol_xl'%5D%3A.0f%7D%22)%0A%20%20%20%20%20%20%20%20print()%0A%20%20%20%20%20%20%20%20print(f%22%7B'Constraint'%3A%3C12%7D%20%7B'Activity'%3A%3E10%7D%20%7B'Shadow%20price'%3A%3E14%7D%22)%0A%20%20%20%20%20%20%20%20print(f%22%7B'Lathe'%3A%3C12%7D%20%7Boutput%5B'act_lathe'%5D%3A%3E10.2f%7D%20%7Boutput%5B'dual_lathe'%5D%3A%3E14.4f%7D%22)%0A%20%20%20%20%20%20%20%20print(f%22%7B'Boxwood'%3A%3C12%7D%20%7Boutput%5B'act_boxwood'%5D%3A%3E10.2f%7D%20%7Boutput%5B'dual_boxwood'%5D%3A%3E14.4f%7D%22)%0A%20%20%20%20%20%20%20%20print()%0A%20%20%20%20%20%20%20%20print(f%22%7B'Variable'%3A%3C12%7D%20%7B'Reduced%20cost'%3A%3E14%7D%22)%0A%20%20%20%20%20%20%20%20print(f%22%7B'xs'%3A%3C12%7D%20%7Boutput%5B'rc_xs'%5D%3A%3E14.4f%7D%22)%0A%20%20%20%20%20%20%20%20print(f%22%7B'xl'%3A%3C12%7D%20%7Boutput%5B'rc_xl'%5D%3A%3E14.4f%7D%22)%0A%20%20%20%20%20%20%20%20print()%0A%20%20%20%20%20%20%20%20print(f%22Model%20name%20%3A%20%7Bmodel.name%7D%22)%0A%20%20%20%20%20%20%20%20print(f%22Exec%20status%3A%20%7Bmodel.exec_status%7D%22)%0A%20%20%20%20%20%20%20%20print(f%22Optimal%20%20%20%20%3A%20%7Bmodel.prob_status.is_solution_optimal%7D%22)%0A%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20print(f%22No%20solution%20found%20(%7Bmodel.prob_status%7D)%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%20The%20knapsack%20problem%20(Burglar)%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*Introductory%20examples%3A%20Chapters%202-3%20-%20Integer%20Programming*%0A%0A%20%20%20%20%3E%20**Mosel%20model%3A**%20%5B%60burglari_p.mos%60%5D(https%3A%2F%2Fgithub.com%2Ffico-xpress%2Fpython-notebooks%2Fblob%2Fmain%2Fmarimo%2Fpublic%2Fmoselpy%2Fmodels%2Fintro%2Fburglari_p.mos)%0A%0A%20%20%20%20A%20burglar%20can%20carry%20at%20most%20%24%5Ctext%7BWTMAX%7D%20%3D%20102%24%20kg.%20Eight%20items%20have%20values%20%24%5Ctext%7BVALUE%7D_i%24%20and%20weights%20%24%5Ctext%7BWEIGHT%7D_i%24.%20Which%20items%20to%20take%3F%0A%0A%20%20%20%20%7C%20Item%20%7C%20camera%20%7C%20necklace%20%7C%20vase%20%7C%20picture%20%7C%20tv%20%7C%20video%20%7C%20chest%20%7C%20brick%20%7C%0A%20%20%20%20%7C------%7C--------%7C----------%7C------%7C---------%7C----%7C----%7C------%7C-------%7C%0A%20%20%20%20%7C%20VALUE%20%7C%2015%20%7C%20100%20%7C%2090%20%7C%2060%20%7C%2040%20%7C%2015%20%7C%2010%20%7C%201%20%7C%0A%20%20%20%20%7C%20WEIGHT%20%7C%202%20%7C%2020%20%7C%2020%20%7C%2030%20%7C%2040%20%7C%2030%20%7C%2060%20%7C%2010%20%7C%0A%0A%20%20%20%20**Decision%20variables%3A**%20%24%5Cmathit%7Btake%7D_i%20%5Cin%20%5C%7B0%2C1%5C%7D%24%20(1%20if%20item%20%24i%24%20is%20taken%2C%200%20otherwise).%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_i%20%5Ctext%7BVALUE%7D_i%5C%2C%5Cmathit%7Btake%7D_i%20%5C%5C%0A%20%20%20%20%5Ctext%7Bsubject%20to%7D%20%5Cquad%20%26%20%5Csum_i%20%5Ctext%7BWEIGHT%7D_i%5C%2C%5Cmathit%7Btake%7D_i%20%5Cleq%20%5Ctext%7BWTMAX%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%7Btake%7D_i%20%5Cin%20%5C%7B0%2C1%5C%7D%20%5Cquad%20%5Cforall%5C%2C%20i%0A%20%20%20%20%5Cend%7Balign*%7D%0A%20%20%20%20%24%24%0A%0A%20%20%20%20%60burglari_p.mos%60%20uses%20named%20string%20indices%20-%20a%20common%20pattern%20in%20real-world%20Mosel%20models.%0A%0A%20%20%20%20**Workflow%3A**%20This%20section%20uses%20the%20three-step%20workflow%20(%60compile_model%60%20-%3E%20%60load_model%60%20-%3E%20%60run%60)%20to%20demonstrate%20the%20pattern%20explicitly.%20The%20compiled%20%60.bim%60%20file%20is%20reused%20by%20subsequent%20cells%20(the%20pandas%20data%20exchange%20and%20sensitivity%20analysis%20below)%2C%20so%20loading%20once%20and%20running%20multiple%20times%20is%20the%20natural%20fit%20here.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(MODELS%2C%20mp)%3A%0A%20%20%20%20mp.compile_model(f'%7BMODELS%7D%2Fburglari_p.mos'%2C%20f'%7BMODELS%7D%2Fburglari_p.bim')%0A%20%20%20%20model_1%20%3D%20mp.load_model(f'%7BMODELS%7D%2Fburglari_p.bim')%0A%20%20%20%20model_1.set_default_stream(mp.StreamType.OUTPUT%2C%20'null%3A')%0A%20%20%20%20output_1%20%3D%20model_1.run(exec_params%3D%7B'OUTFILE'%3A%20'moselpy%3A'%7D)%0A%20%20%20%20print(f'Total%20value%3A%20%24%7Boutput_1%5B'obj_val'%5D%3A.0f%7D')%0A%20%20%20%20taken%20%3D%20%7Bitem%3A%20v%20for%20item%2C%20v%20in%20output_1%5B'sol_take'%5D.items()%20if%20v%20%3E%200.5%7D%0A%20%20%20%20print(f'Items%20taken%3A%20%7Bsorted(taken.keys())%7D')%0A%20%20%20%20return%20model_1%2C%20output_1%2C%20taken%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%20Checking%20solution%20status%0A%0A%20%20%20%20After%20any%20%60model.run()%60%20call%2C%20check%20two%20properties%20before%20using%20results%3A%0A%0A%20%20%20%20-%20**%60model.exec_status%60**%20(%60ExecStatus.OK%60%20%2F%20%60ExecStatus.ERROR%60)%3A%20whether%20Mosel%20itself%20ran%20successfully%20(compiled%2C%20loaded%2C%20no%20runtime%20error).%20Check%20this%20first.%0A%20%20%20%20-%20**%60model.prob_status%60**%3A%20the%20optimizer's%20verdict%20on%20the%20mathematical%20problem.%20Key%20boolean%20flags%3A%20%60is_solution_optimal%60%2C%20%60is_solution_feasible%60%2C%20%60is_problem_infeasible%60%2C%20%60is_problem_unbounded%60.%0A%20%20%20%20-%20**%60model.objective_value%60**%3A%20the%20objective%20function%20value%20at%20the%20best%20solution%20found.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(ExecStatus%2C%20model_1)%3A%0A%20%20%20%20%23%20Check%20the%20solution%20status%20(good%20practice%20after%20any%20solve)%0A%20%20%20%20if%20model_1.exec_status%20%3D%3D%20ExecStatus.OK%3A%0A%20%20%20%20%20%20%20%20ps%20%3D%20model_1.prob_status%0A%20%20%20%20%20%20%20%20if%20ps.is_solution_optimal%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20status%20%3D%20'optimal'%0A%20%20%20%20%20%20%20%20elif%20ps.is_solution_feasible%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20status%20%3D%20'feasible%20(possibly%20not%20optimal)'%0A%20%20%20%20%20%20%20%20elif%20ps.is_problem_infeasible%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20status%20%3D%20'infeasible'%0A%20%20%20%20%20%20%20%20elif%20ps.is_problem_unbounded%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20status%20%3D%20'unbounded'%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20status%20%3D%20'unknown'%0A%20%20%20%20%20%20%20%20print(f'exec_status%20%20%3A%20%7Bmodel_1.exec_status%7D')%0A%20%20%20%20%20%20%20%20print(f'prob_status%20%20%3A%20%7Bstatus%7D')%0A%20%20%20%20%20%20%20%20print(f'objective%20%20%20%20%3A%20%7Bmodel_1.objective_value%3A.0f%7D')%0A%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20print(f'Execution%20error%3A%20%7Bmodel_1.exec_status%7D')%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(mo%2C%20mpatches%2C%20output_1%2C%20plt%2C%20taken)%3A%0A%20%20%20%20_items%20%3D%20%5B'camera'%2C%20'necklace'%2C%20'vase'%2C%20'picture'%2C%20'tv'%2C%20'video'%2C%20'chest'%2C%20'brick'%5D%0A%20%20%20%20values%20%3D%20%5B15%2C%20100%2C%2090%2C%2060%2C%2040%2C%2015%2C%2010%2C%201%5D%0A%20%20%20%20_weights%20%3D%20%5B2%2C%2020%2C%2020%2C%2030%2C%2040%2C%2030%2C%2060%2C%2010%5D%0A%20%20%20%20selected%20%3D%20set(taken.keys())%0A%20%20%20%20colors_v%20%3D%20%5B'%232196F3'%20if%20it%20in%20selected%20else%20'%23BBDEFB'%20for%20it%20in%20_items%5D%0A%20%20%20%20colors_w%20%3D%20%5B'%23FF5722'%20if%20it%20in%20selected%20else%20'%23FFCCBC'%20for%20it%20in%20_items%5D%0A%20%20%20%20_fig%2C%20(ax1%2C%20ax2)%20%3D%20plt.subplots(1%2C%202%2C%20figsize%3D(10%2C%204))%0A%20%20%20%20y%20%3D%20range(len(_items))%0A%20%20%20%20ax1.barh(list(y)%2C%20values%2C%20color%3Dcolors_v)%0A%20%20%20%20ax1.set_yticks(list(y))%0A%20%20%20%20ax1.set_yticklabels(_items)%0A%20%20%20%20ax1.set_xlabel('Value%20(%24)')%0A%20%20%20%20ax1.set_title('Item%20values')%0A%20%20%20%20ax1.invert_yaxis()%0A%20%20%20%20ax2.barh(list(y)%2C%20_weights%2C%20color%3Dcolors_w)%0A%20%20%20%20ax2.set_yticks(list(y))%0A%20%20%20%20ax2.set_yticklabels(_items)%0A%20%20%20%20ax2.set_xlabel('Weight%20(kg)')%0A%20%20%20%20ax2.set_title(f'Item%20weights%20%20(capacity%3A%20102%20kg)')%0A%20%20%20%20ax2.invert_yaxis()%0A%20%20%20%20legend%20%3D%20%5Bmpatches.Patch(color%3D'%232196F3'%2C%20label%3D'Selected')%2C%20mpatches.Patch(color%3D'%23BBDEFB'%2C%20label%3D'Not%20taken')%5D%0A%20%20%20%20ax1.legend(handles%3Dlegend%2C%20loc%3D'lower%20right')%0A%20%20%20%20legend2%20%3D%20%5Bmpatches.Patch(color%3D'%23FF5722'%2C%20label%3D'Selected')%2C%20mpatches.Patch(color%3D'%23FFCCBC'%2C%20label%3D'Not%20taken')%5D%0A%20%20%20%20ax2.legend(handles%3Dlegend2%2C%20loc%3D'lower%20right')%0A%20%20%20%20plt.suptitle(f'Knapsack%20solution%20-total%20value%20%24%7Boutput_1%5B'obj_val'%5D%3A.0f%7D'%2C%20fontweight%3D'bold')%0A%20%20%20%20plt.tight_layout()%0A%20%20%20%20mo.output.append(plt.gcf())%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%23%20Two%20ways%20to%20read%20results%20from%20Python%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%60burglari_p.mos%60%20has%20both%20%60public%60%20declarations%20and%20an%20%60initializations%20to%20OUTFILE%60%20block%2C%20making%20it%20a%20good%20place%20to%20compare%20the%20two%20standard%20approaches%20to%20reading%20results%20in%20MoselPy%3A%0A%0A%20%20%20%20**Approach%201%20-%20%60initializations%20to%20OUTFILE%60**%20(shown%20above%3B%20used%20in%20all%20subsequent%20examples)%0A%20%20%20%20The%20model%20exports%20results%20via%20%60initializations%20to%20OUTFILE%60%3B%20Python%20receives%20them%20as%20the%20dict%20returned%20by%20%60model.run(exec_params%3D%7B%22OUTFILE%22%3A%20%22moselpy%3A%22%7D)%60.%20The%20model%20also%20runs%20correctly%20standalone%2C%20writing%20results%20to%20a%20file%20by%20default.%0A%0A%20%20%20%20**Approach%202%20-%20%60public%60%20%2B%20%60find_identifier()%60**%0A%20%20%20%20Declarations%20marked%20%60public%60%20in%20the%20model%20remain%20accessible%20from%20Python%20after%20the%20model%20runs.%20Retrieve%20them%20with%20%60model.find_identifier(name)%60%2C%20optionally%20as%20a%20pandas%20%60Series%60%20by%20passing%20%60use_pandas%3DTrue%60.%0A%0A%20%20%20%20All%20subsequent%20examples%20use%20**Approach%201%20only**.%20It%20is%20preferred%20because%20the%20model%20produces%20output%20when%20run%20standalone%2C%20without%20any%20Python%20wrapper.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(model_1)%3A%0A%20%20%20%20%23%20Approach%202%3A%20find_identifier()%20accesses%20'public'%20declarations%20directly%20from%20Python.%0A%20%20%20%20%23%20use_pandas%3DTrue%20returns%20a%20pandas%20Series%2C%20convenient%20for%20further%20analysis.%0A%20%20%20%20sol_take_pd%20%3D%20model_1.find_identifier('sol_take'%2C%20use_pandas%3DTrue)%0A%20%20%20%20print('Item%20selection%20(pandas%20Series)%3A')%0A%20%20%20%20print(sol_take_pd.sort_values(ascending%3DFalse).rename('selected').to_frame().to_string())%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%20Knapsack%20with%20an%20external%20data%20file%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%3E%20**Mosel%20model%3A**%20%5B%60burglar2_p.mos%60%5D(https%3A%2F%2Fgithub.com%2Ffico-xpress%2Fpython-notebooks%2Fblob%2Fmain%2Fmarimo%2Fpublic%2Fmoselpy%2Fmodels%2Fintro%2Fburglar2_p.mos)%0A%0A%20%20%20%20%60burglar2_p.mos%60%20reads%20item%20data%20using%20%60initializations%20from%20DATASOURCE%60.%20Here%20the%20item%20data%20is%20defined%20directly%20in%20Python%20and%20injected%20at%20runtime%20via%20%60input_data%60.%20The%20%60.dat%60%20file%20(%60burglar_p.dat%60)%20is%20kept%20alongside%20the%20model%20for%20standalone%20execution.%0A%0A%20%20%20%20%60exec_params%60%20overrides%20Mosel%20%60parameters%60%20block%20values%20before%20the%20model%20starts%3B%20use%20it%20for%20scalars%20such%20as%20file%20paths%20or%20mode%20flags.%20%60input_data%60%20is%20passed%20via%20%60initializations%20from%20%22moselpy%3A%22%60%20at%20runtime%20and%20can%20transfer%20scalars%2C%20arrays%2C%20sets%2C%20and%20pandas%20objects.%20Setting%20%60DATASOURCE%3D%22moselpy%3A%22%60%20in%20%60exec_params%60%20redirects%20the%20%60initializations%20from%60%20block%20to%20read%20from%20Python%20instead%20of%20a%20file.%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%20VALUE%20%20%3D%20%7B%22camera%22%3A%2015%2C%20%22necklace%22%3A%20100%2C%20%22vase%22%3A%2090%2C%20%22picture%22%3A%2060%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22tv%22%3A%2040%2C%20%22video%22%3A%2015%2C%20%22chest%22%3A%2010%2C%20%22brick%22%3A%201%7D%0A%20%20%20%20WEIGHT%20%3D%20%7B%22camera%22%3A%202%2C%20%22necklace%22%3A%2020%2C%20%22vase%22%3A%2020%2C%20%22picture%22%3A%2030%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22tv%22%3A%2040%2C%20%22video%22%3A%2030%2C%20%22chest%22%3A%2060%2C%20%22brick%22%3A%2010%7D%0A%20%20%20%20return%20VALUE%2C%20WEIGHT%0A%0A%0A%40app.cell%0Adef%20_(MODELS%2C%20VALUE%2C%20WEIGHT%2C%20mp)%3A%0A%20%20%20%20mp.compile_model(f'%7BMODELS%7D%2Fburglar2_p.mos'%2C%20f'%7BMODELS%7D%2Fburglar2_p.bim')%0A%20%20%20%20model_2%20%3D%20mp.load_model(f'%7BMODELS%7D%2Fburglar2_p.bim')%0A%20%20%20%20model_2.set_default_stream(mp.StreamType.OUTPUT%2C%20'null%3A')%0A%20%20%20%20output_2%20%3D%20model_2.run(exec_params%3D%7B'DATASOURCE'%3A%20'moselpy%3A'%2C%20'OUTFILE'%3A%20'moselpy%3A'%7D%2C%20input_data%3D%7B'VALUE'%3A%20VALUE%2C%20'WEIGHT'%3A%20WEIGHT%7D)%0A%20%20%20%20if%20model_2.prob_status.is_solution_available%3A%0A%20%20%20%20%20%20%20%20print(f'Total%20value%3A%20%24%7Boutput_2%5B'obj_val'%5D%3A.0f%7D')%0A%20%20%20%20%20%20%20%20taken_1%20%3D%20%7Bitem%3A%20v%20for%20item%2C%20v%20in%20output_2%5B'sol_take'%5D.items()%20if%20v%20%3E%200.5%7D%0A%20%20%20%20%20%20%20%20print(f'Items%20taken%3A%20%7Bsorted(taken_1.keys())%7D')%0A%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20print(f'No%20solution%20found%20(%7Bmodel_2.prob_status%7D)')%0A%20%20%20%20return%20(model_2%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%20Injecting%20data%20from%20Python%20with%20%60exec_params%60%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20Setting%20%60exec_params%3D%7B%22DATASOURCE%22%3A%20%22moselpy%3A%22%7D%60%20(used%20for%20scalars%20such%20as%20file%20paths)%20redirects%20the%20%60initializations%20from%60%20block%20to%20read%20data%20from%20Python%20instead%20of%20a%20file.%20Combined%20with%20%60input_data%60%2C%20this%20lets%20you%20pass%20any%20Python%20dict%20or%20pandas%20object%20directly%20to%20the%20model.%0A%0A%20%20%20%20A%20key%20advantage%3A%20the%20model%20is%20loaded%20once%20and%20can%20be%20run%20as%20many%20times%20as%20needed%20with%20different%20data%3B%20no%20recompile%20or%20reload%20between%20runs.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(model_2)%3A%0A%20%20%20%20_items%20%3D%20%7B'camera'%3A%2015%2C%20'necklace'%3A%20100%2C%20'vase'%3A%2090%2C%20'picture'%3A%2060%2C%20'tv'%3A%2040%2C%20'video'%3A%2015%2C%20'chest'%3A%2010%2C%20'brick'%3A%201%7D%0A%20%20%20%20_weights%20%3D%20%7B'camera'%3A%202%2C%20'necklace'%3A%2020%2C%20'vase'%3A%2020%2C%20'picture'%3A%2030%2C%20'tv'%3A%2040%2C%20'video'%3A%2030%2C%20'chest'%3A%2060%2C%20'brick'%3A%2010%7D%0A%20%20%20%20print(f'%7B'vase%20(kg)'%3A%3E9%7D%20%20%20%7B'value'%3A%3E6%7D%20%20%20items%20taken')%0A%20%20%20%20for%20vase_weight%20in%20%5B20%2C%2040%2C%2060%2C%2080%5D%3A%0A%20%20%20%20%20%20%20%20_out%20%3D%20model_2.run(exec_params%3D%7B'DATASOURCE'%3A%20'moselpy%3A'%2C%20'OUTFILE'%3A%20'moselpy%3A'%7D%2C%20input_data%3D%7B'VALUE'%3A%20_items%2C%20'WEIGHT'%3A%20dict(_weights%2C%20vase%3Dvase_weight)%7D)%0A%20%20%20%20%20%20%20%20taken_2%20%3D%20sorted((k%20for%20k%2C%20v%20in%20_out%5B'sol_take'%5D.items()%20if%20v%20%3E%200.5))%0A%20%20%20%20%20%20%20%20print(f'%7Bvase_weight%3A%3E6%7D%20kg%20%20%20%7B_out%5B'obj_val'%5D%3A%3E6.0f%7D%20%20%20%7Btaken_2%7D')%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%20Knapsack%20with%20DataFrame%20input%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%3E%20**Mosel%20model%3A**%20%5B%60burglar_df_p.mos%60%5D(https%3A%2F%2Fgithub.com%2Ffico-xpress%2Fpython-notebooks%2Fblob%2Fmain%2Fmarimo%2Fpublic%2Fmoselpy%2Fmodels%2Fintro%2Fburglar_df_p.mos)%0A%0A%20%20%20%20%60burglar_df_p.mos%60%20stores%20item%20data%20in%20a%202D%20Mosel%20array%20%60I(FIELDS%2C%20ITEMS)%60%20where%20%60FIELDS%20%3D%20%7B'value'%2C%20'weight'%7D%60%20and%20%60ITEMS%60%20is%20the%20set%20of%20item%20names.%20This%20layout%20maps%20directly%20to%20a%20%60pd.DataFrame%60%20--%20rows%20are%20fields%2C%20columns%20are%20items.%20Passing%20the%20DataFrame%20via%20%60input_data%60%20is%20the%20most%20natural%20way%20to%20inject%20tabular%20item%20data%20from%20Python%20into%20Mosel.%0A%0A%20%20%20%20Both%20%60FIELDS%60%20and%20%60ITEMS%60%20are%20%60set%20of%20string%60%20in%20the%20model%20and%20auto-populate%20from%20the%20DataFrame%20row%20and%20column%20labels%20when%20the%20data%20is%20passed.%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%20item_df%20%3D%20pd.DataFrame(%0A%20%20%20%20%20%20%20%20%7B%22camera%22%3A%20%20%20%20%5B15%2C%20%202%5D%2C%0A%20%20%20%20%20%20%20%20%20%22necklace%22%3A%20%20%5B100%2C%2020%5D%2C%0A%20%20%20%20%20%20%20%20%20%22vase%22%3A%20%20%20%20%20%20%5B90%2C%20%2020%5D%2C%0A%20%20%20%20%20%20%20%20%20%22picture%22%3A%20%20%20%5B60%2C%20%2030%5D%2C%0A%20%20%20%20%20%20%20%20%20%22tv%22%3A%20%20%20%20%20%20%20%20%5B40%2C%20%2040%5D%2C%0A%20%20%20%20%20%20%20%20%20%22video%22%3A%20%20%20%20%20%5B15%2C%20%2030%5D%2C%0A%20%20%20%20%20%20%20%20%20%22chest%22%3A%20%20%20%20%20%5B10%2C%20%2060%5D%2C%0A%20%20%20%20%20%20%20%20%20%22brick%22%3A%20%20%20%20%20%5B1%2C%20%20%2010%5D%7D%2C%0A%20%20%20%20%20%20%20%20index%3D%5B%22value%22%2C%20%22weight%22%5D%2C%0A%20%20%20%20)%0A%20%20%20%20print(item_df.to_string())%0A%20%20%20%20return%20(item_df%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%20Pass%20the%20%60pd.DataFrame%60%20via%20%60input_data%60.%20MoselPy%20maps%20rows%20to%20%60FIELDS%60%20and%20columns%20to%20%60ITEMS%60%20automatically.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(MODELS%2C%20item_df%2C%20mp)%3A%0A%20%20%20%20mp.compile_model(f'%7BMODELS%7D%2Fburglar_df_p.mos'%2C%20f'%7BMODELS%7D%2Fburglar_df_p.bim')%0A%20%20%20%20model_3%20%3D%20mp.load_model(f'%7BMODELS%7D%2Fburglar_df_p.bim')%0A%20%20%20%20model_3.set_default_stream(mp.StreamType.OUTPUT%2C%20'null%3A')%0A%20%20%20%20output_3%20%3D%20model_3.run(exec_params%3D%7B'DATASOURCE'%3A%20'moselpy%3A'%2C%20'OUTFILE'%3A%20'moselpy%3A'%7D%2C%20input_data%3D%7B'I'%3A%20item_df%7D)%0A%20%20%20%20if%20model_3.prob_status.is_solution_available%3A%0A%20%20%20%20%20%20%20%20print(f'Total%20value%3A%20%24%7Boutput_3%5B'obj_val'%5D%3A.0f%7D')%0A%20%20%20%20%20%20%20%20taken_3%20%3D%20%7Bitem%3A%20v%20for%20item%2C%20v%20in%20output_3%5B'sol_take'%5D.items()%20if%20v%20%3E%200.5%7D%0A%20%20%20%20%20%20%20%20print(f'Items%20taken%3A%20%7Bsorted(taken_3.keys())%7D')%0A%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20print(f'No%20solution%20found%20(%7Bmodel_3.prob_status%7D)')%0A%20%20%20%20return%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%20Price%20breaks%3A%20all-item%20discount%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*Introductory%20example%3A%20Chapter%204%20-%20Special%20Ordered%20Sets*%0A%0A%20%20%20%20%3E%20**Mosel%20models%3A**%20%5B%60pricebrai_p.mos%60%5D(https%3A%2F%2Fgithub.com%2Ffico-xpress%2Fpython-notebooks%2Fblob%2Fmain%2Fmarimo%2Fpublic%2Fmoselpy%2Fmodels%2Fintro%2Fpricebrai_p.mos)%2C%20%5B%60pricebrai2_p.mos%60%5D(https%3A%2F%2Fgithub.com%2Ffico-xpress%2Fpython-notebooks%2Fblob%2Fmain%2Fmarimo%2Fpublic%2Fmoselpy%2Fmodels%2Fintro%2Fpricebrai2_p.mos)%0A%0A%20%20%20%20We%20fix%20the%20purchased%20quantity%20to%20%60DEM%60%3D150%20units%20to%20make%20the%20code%20runnable.%20Unit%20price%20depends%20on%20the%20total%20quantity%20purchased%3A%0A%0A%20%20%20%20%7C%20Band%20%7C%20Quantity%20range%20%7C%20Unit%20cost%20%7C%0A%20%20%20%20%7C------%7C---------------%7C-----------%7C%0A%20%20%20%20%7C%201%20%7C%200-50%20%7C%201.00%20%7C%0A%20%20%20%20%7C%202%20%7C%2050-120%20%7C%200.70%20%7C%0A%20%20%20%20%7C%203%20%7C%20120-200%20%7C%200.50%20%7C%0A%0A%20%20%20%20All%20units%20are%20charged%20at%20whichever%20band%20rate%20applies%20to%20the%20total%20quantity%20bought.%0A%0A%20%20%20%20**Decision%20variables%3A**%20%24x_i%20%5Cgeq%200%24%20(quantity%20bought%20at%20price%20%24%5Ctext%7BCOST%7D_i%24)%2C%20%24b_i%20%5Cin%20%5C%7B0%2C1%5C%7D%24%20(active%20price%20band).%0A%0A%20%20%20%20%24%24%0A%20%20%20%20%5Cbegin%7Balign*%7D%0A%20%20%20%20%5Ctext%7Bminimize%7D%20%20%20%5Cquad%20%26%20%5Csum_%7Bi%3D1%7D%5E%7B3%7D%20%5Ctext%7BCOST%7D_i%5C%2Cx_i%20%5C%5C%0A%20%20%20%20%5Ctext%7Bsubject%20to%7D%20%5Cquad%20%26%20%5Csum_%7Bi%3D1%7D%5E%7B3%7D%20x_i%20%3D%20%5Ctext%7BDEM%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%20B_%7Bi-1%7D%5C%2Cb_i%20%5Cleq%20x_i%20%5Cleq%20B_i%5C%2Cb_i%20%5Cquad%20%5Cforall%5C%2Ci%20%5C%5C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%26%20%5Csum_%7Bi%3D1%7D%5E%7B3%7D%20b_i%20%3D%201%2C%20%5Cquad%20b_i%20%5Cin%20%5C%7B0%2C1%5C%7D%0A%20%20%20%20%5Cend%7Balign*%7D%0A%20%20%20%20%24%24%0A%0A%20%20%20%20Data%20(%60DEM%60%2C%20%60B%60%2C%20%60COST%60)%20is%20read%20via%20a%20%60DATASOURCE%60%20parameter%2C%20which%20lets%20us%20pass%20Python%20values%20directly%20to%20Mosel%20using%20%60input_data%60.%20Mosel%20arrays%20are%201-indexed%20by%20default%2C%20but%20%60B%60%20is%20declared%20as%20%60array(0..NB)%60%20so%20its%20keys%20run%20from%200%20to%203%20-%20the%20dict%20passed%20for%20%60B%60%20must%20match%20that%20range.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(MODELS%2C%20mp)%3A%0A%20%20%20%20B%20%3D%20%5B0%2C%2050%2C%20120%2C%20200%5D%0A%20%20%20%20COST%20%3D%20%5B1.0%2C%200.7%2C%200.5%5D%0A%20%20%20%20DEM%20%3D%20150%0A%20%20%20%20mp.compile_model(f'%7BMODELS%7D%2Fpricebrai_p.mos'%2C%20f'%7BMODELS%7D%2Fpricebrai_p.bim')%0A%20%20%20%20model_4%20%3D%20mp.load_model(f'%7BMODELS%7D%2Fpricebrai_p.bim')%0A%20%20%20%20model_4.set_default_stream(mp.StreamType.OUTPUT%2C%20'null%3A')%0A%20%20%20%20output_4%20%3D%20model_4.run(exec_params%3D%7B'DATASOURCE'%3A%20'moselpy%3A'%2C%20'OUTFILE'%3A%20'moselpy%3A'%7D%2C%20input_data%3D%7B'DEM'%3A%20DEM%2C%20'B'%3A%20%7Bi%3A%20B%5Bi%5D%20for%20i%20in%20range(len(B))%7D%2C%20'COST'%3A%20%7Bi%20%2B%201%3A%20COST%5Bi%5D%20for%20i%20in%20range(len(COST))%7D%7D)%0A%20%20%20%20if%20model_4.prob_status.is_solution_available%3A%0A%20%20%20%20%20%20%20%20print(f'Total%20cost%3A%20%7Boutput_4%5B'obj_val'%5D%3A.2f%7D%20%20(avg%20per%20unit%3A%20%7Boutput_4%5B'obj_val'%5D%20%2F%20DEM%3A.4f%7D)')%0A%20%20%20%20%20%20%20%20print(f'%7B'Band'%3A%3C6%7D%20%7B'Quantity'%3A%3E10%7D%20%7B'Unit%20cost'%3A%3E10%7D')%0A%20%20%20%20%20%20%20%20for%20_i%20in%20range(1%2C%204)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20print(f'%7B_i%3A%3C6%7D%20%7Boutput_4%5B'sol_x'%5D%5B_i%5D%3A%3E10.2f%7D%20%7BCOST%5B_i%20-%201%5D%3A%3E10.2f%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%20B%2C%20COST%2C%20DEM%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%23%20All-item%20discount%20via%20%60pwlin%60%20(pricebrai2_p.mos)%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%60pricebrai2_p.mos%60%20reformulates%20the%20same%20problem%20using%20a%20%60pwlin%60%20piecewise-linear%20expression%20(requires%20%60mmxnlp%60).%20The%20model%20reduces%20to%20a%20single%20continuous%20variable%20%24x%24%20with%20a%20piecewise%20cost%20function.%0A%0A%20%20%20%20As%20with%20%60chess2_p.mos%60%2C%20we%20use%20%60execute_model%60%20for%20a%20one-step%20run.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(DEM%2C%20MODELS%2C%20mp)%3A%0A%20%20%20%20model_5%2C%20output_5%20%3D%20mp.execute_model(f'%7BMODELS%7D%2Fpricebrai2_p.mos'%2C%20exec_params%3D%7B'OUTFILE'%3A%20'moselpy%3A'%7D)%0A%20%20%20%20if%20model_5.prob_status.is_solution_available%3A%0A%20%20%20%20%20%20%20%20print(f'Total%20cost%3A%20%7Boutput_5%5B'obj_val'%5D%3A.2f%7D%20%20(avg%20per%20unit%3A%20%7Boutput_5%5B'obj_val'%5D%20%2F%20DEM%3A.4f%7D)')%0A%20%20%20%20%20%20%20%20print(f'Quantity%20bought%3A%20%7Boutput_5%5B'sol_x'%5D%3A.0f%7D')%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%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%20Price%20breaks%3A%20incremental%20discount%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*Introductory%20example%3A%20Chapter%204%20-%20Special%20Ordered%20Sets*%0A%0A%20%20%20%20%3E%20**Mosel%20models%3A**%20%5B%60pricebrinc_p.mos%60%5D(https%3A%2F%2Fgithub.com%2Ffico-xpress%2Fpython-notebooks%2Fblob%2Fmain%2Fmarimo%2Fpublic%2Fmoselpy%2Fmodels%2Fintro%2Fpricebrinc_p.mos)%2C%20%5B%60pricebrinc2_p.mos%60%5D(https%3A%2F%2Fgithub.com%2Ffico-xpress%2Fpython-notebooks%2Fblob%2Fmain%2Fmarimo%2Fpublic%2Fmoselpy%2Fmodels%2Fintro%2Fpricebrinc2_p.mos)%2C%20%5B%60pricebrinc3_p.mos%60%5D(https%3A%2F%2Fgithub.com%2Ffico-xpress%2Fpython-notebooks%2Fblob%2Fmain%2Fmarimo%2Fpublic%2Fmoselpy%2Fmodels%2Fintro%2Fpricebrinc3_p.mos)%0A%0A%20%20%20%20Same%20quantity%20bands%20as%20above%2C%20but%20pricing%20is%20now%20**incremental**%3A%20the%20first%2050%20units%20cost%200.80%20each%2C%20the%20next%2070%20(up%20to%20120)%20cost%200.50%20each%2C%20and%20anything%20beyond%20120%20costs%200.30%20each.%0A%0A%20%20%20%20%60pricebrinc_p.mos%60%20models%20this%20with%20an%20SOS2%20(Special%20Ordered%20Set%20of%20Type%202).%20At%20each%20of%20the%20four%20breakpoints%20%24B_0%2C%20B_1%2C%20B_2%2C%20B_3%24%2C%20a%20weight%20variable%20%24w_i%20%5Cgeq%200%24%20is%20introduced%3B%20%24%5Ctext%7BCBP%7D_i%24%20is%20the%20total%20cost%20of%20buying%20exactly%20%24B_i%24%20units.%20The%20SOS2%20constraint%20allows%20**at%20most%20two%20adjacent**%20%24w_i%24%20to%20be%20non-zero%20-this%20forces%20the%20solution%20onto%20one%20linear%20segment%20of%20the%20piecewise%20cost%20function.%0A%0A%20%20%20%20**Decision%20variables%3A**%20%24x%20%5Cgeq%200%24%20(total%20quantity)%2C%20%24w_i%20%5Cgeq%200%24%2C%20%24i%20%3D%200..3%24%20(SOS2%20interpolation%20weights).%0A%0A%20%20%20%20%24%24%0A%20%20%20%20%5Cbegin%7Balign*%7D%0A%20%20%20%20%5Ctext%7Bminimize%7D%20%20%20%5Cquad%20%26%20%5Csum_%7Bi%3D0%7D%5E%7B3%7D%20%5Ctext%7BCBP%7D_i%5C%2C%20w_i%20%5C%5C%0A%20%20%20%20%5Ctext%7Bsubject%20to%7D%20%5Cquad%20%26%20x%20%3D%20%5Ctext%7BDEM%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%20x%20%3D%20%5Csum_%7Bi%3D0%7D%5E%7B3%7D%20B_i%5C%2C%20w_i%20%5C%5C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%26%20%5Csum_%7Bi%3D0%7D%5E%7B3%7D%20w_i%20%3D%201%2C%20%5Cquad%20w%20%5Cin%20%5Ctext%7BSOS2%7D%0A%20%20%20%20%5Cend%7Balign*%7D%0A%20%20%20%20%24%24%0A%0A%20%20%20%20A%20key%20subtlety%3A%20%60makesos2%60%20is%20used%20instead%20of%20%60is_sos2%60%20because%20the%20reference%20row%20contains%20a%20zero-valued%20coefficient%20(%24B_0%20%3D%200%24)%2C%20which%20%60is_sos2%60%20cannot%20handle%20correctly.%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%23%20Injecting%20data%20from%20Python%0A%0A%20%20%20%20%60pricebrinc_p.mos%60%20accepts%20its%20data%20via%20a%20%60DATASOURCE%60%20parameter%20(defaulting%20to%20a%20%60.dat%60%20file%20when%20run%20standalone).%20Passing%20%60exec_params%3D%7B%22DATASOURCE%22%3A%20%22moselpy%3A%22%7D%60%20redirects%20the%20%60initializations%20from%20DATASOURCE%60%20block%20to%20read%20from%20Python%20instead.%20The%20%60input_data%60%20dict%20then%20maps%20Python%20values%20directly%20to%20Mosel%20identifiers%20by%20name.%0A%0A%20%20%20%20This%20is%20the%20standard%20MoselPy%20pattern%20for%20pushing%20data%20into%20a%20model%20at%20runtime%20-%20no%20%60.dat%60%20file%20needed.%20The%20three-step%20workflow%20is%20used%20here%20because%20the%20loaded%20%60model%60%20is%20reused%20in%20the%20parametric%20run%20in%20the%20next%20cell.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(B%2C%20DEM%2C%20MODELS%2C%20mp)%3A%0A%20%20%20%20%23%20Three-step%20workflow%3A%20model%20is%20loaded%20once%20here%20and%20reused%20in%20the%20parametric%20loop%20below%0A%20%20%20%20COST_INC%20%3D%20%5B0.8%2C%200.5%2C%200.3%5D%20%20%23%20unit%20cost%20per%20band%20(reused%20in%20viz%20below)%0A%20%20%20%20mp.compile_model(f'%7BMODELS%7D%2Fpricebrinc_p.mos'%2C%20f'%7BMODELS%7D%2Fpricebrinc_p.bim')%0A%20%20%20%20model_6%20%3D%20mp.load_model(f'%7BMODELS%7D%2Fpricebrinc_p.bim')%0A%20%20%20%20output_6%20%3D%20model_6.run(exec_params%3D%7B'DATASOURCE'%3A%20'moselpy%3A'%2C%20'OUTFILE'%3A%20'moselpy%3A'%7D%2C%20input_data%3D%7B'DEM'%3A%20DEM%2C%20'B'%3A%20%7Bi%3A%20b%20for%20i%2C%20b%20in%20enumerate(B)%7D%2C%20'COST'%3A%20%7Bi%20%2B%201%3A%20c%20for%20i%2C%20c%20in%20enumerate(COST_INC)%7D%7D)%0A%20%20%20%20if%20model_6.prob_status.is_solution_available%3A%0A%20%20%20%20%23%20exec_params%3D%7B%22DATASOURCE%22%3A%20%22moselpy%3A%22%7D%20redirects%20'initializations%20from%20DATASOURCE'%0A%20%20%20%20%23%20to%20read%20from%20Python%3B%20input_data%20maps%20Python%20values%20to%20Mosel%20identifiers%0A%20%20%20%20%20%20%20%20print(f'Total%20cost%3A%20%7Boutput_6%5B'obj_val'%5D%3A.2f%7D%20%20(avg%20per%20unit%3A%20%7Boutput_6%5B'obj_val'%5D%20%2F%20DEM%3A.4f%7D)')%0A%20%20%20%20%20%20%20%20print(f'Quantity%20bought%3A%20%7Boutput_6%5B'sol_x'%5D%3A.0f%7D')%0A%20%20%20%20%20%20%20%20print(f'SOS2%20weights%3A%20%7B%20%7Bi%3A%20f'%7Bv%3A.4f%7D'%20for%20i%2C%20v%20in%20output_6%5B'sol_w'%5D.items()%7D%7D')%0A%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20print(f'No%20solution%20found%20(%7Bmodel_6.prob_status%7D)')%0A%20%20%20%20return%20COST_INC%2C%20model_6%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%20Varying%20demand%0A%0A%20%20%20%20When%20the%20same%20model%20runs%20many%20times%20with%20different%20data%2C%20the%20three-step%20workflow%20pays%20off%3A%20compile%20and%20load%20once%2C%20then%20call%20%60run()%60%20in%20a%20loop.%20%60run()%60%20resets%20the%20model%20automatically%20before%20each%20execution%20-%20no%20manual%20%60reset()%60%20needed.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(B%2C%20COST_INC%2C%20model_6%2C%20pd)%3A%0A%20%20%20%20demands%20%3D%20%5B50%2C%2080%2C%20120%2C%20150%2C%20180%2C%20200%5D%0A%20%20%20%20rows%20%3D%20%5B%5D%0A%20%20%20%20for%20dem%20in%20demands%3A%0A%20%20%20%20%20%20%20%20_out%20%3D%20model_6.run(exec_params%3D%7B'DATASOURCE'%3A%20'moselpy%3A'%2C%20'OUTFILE'%3A%20'moselpy%3A'%7D%2C%20input_data%3D%7B'DEM'%3A%20dem%2C%20'B'%3A%20%7Bi%3A%20b%20for%20i%2C%20b%20in%20enumerate(B)%7D%2C%20'COST'%3A%20%7Bi%20%2B%201%3A%20c%20for%20i%2C%20c%20in%20enumerate(COST_INC)%7D%7D)%0A%20%20%20%20%20%20%20%20rows.append(%7B'demand'%3A%20dem%2C%20'total_cost'%3A%20_out%5B'obj_val'%5D%2C%20'avg_unit_cost'%3A%20_out%5B'obj_val'%5D%20%2F%20dem%7D)%0A%20%20%20%20df%20%3D%20pd.DataFrame(rows).set_index('demand')%0A%20%20%20%20print(df.to_string(float_format%3D'%7B%3A.4f%7D'.format))%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%23%20Incremental%20discount%20with%20binary%20variables%20(pricebrinc2_p.mos)%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%20An%20alternative%20formulation%20of%20the%20same%20incremental%20problem%20using%20explicit%20binary%20variables%20%24b_i%24%20instead%20of%20SOS2.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(COST_INC%2C%20DEM%2C%20MODELS%2C%20mp)%3A%0A%20%20%20%20model_7%2C%20output_7%20%3D%20mp.execute_model(f'%7BMODELS%7D%2Fpricebrinc2_p.mos'%2C%20exec_params%3D%7B'OUTFILE'%3A%20'moselpy%3A'%7D)%0A%20%20%20%20if%20model_7.prob_status.is_solution_available%3A%0A%20%20%20%20%20%20%20%20print(f'Total%20cost%3A%20%7Boutput_7%5B'obj_val'%5D%3A.2f%7D%20%20(avg%20per%20unit%3A%20%7Boutput_7%5B'obj_val'%5D%20%2F%20DEM%3A.4f%7D)')%0A%20%20%20%20%20%20%20%20print(f'%7B'Band'%3A%3C6%7D%20%7B'Quantity'%3A%3E10%7D%20%7B'Unit%20cost'%3A%3E10%7D')%0A%20%20%20%20%20%20%20%20for%20_i%20in%20range(1%2C%204)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20print(f'%7B_i%3A%3C6%7D%20%7Boutput_7%5B'sol_x'%5D%5B_i%5D%3A%3E10.2f%7D%20%7BCOST_INC%5B_i%20-%201%5D%3A%3E10.2f%7D')%0A%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20print(f'No%20solution%20found%20(%7Bmodel_7.prob_status%7D)')%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%23%20Incremental%20discount%20via%20%60pwlin%60%20(pricebrinc3_p.mos)%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%20A%20third%20formulation%20using%20a%20%60pwlin%60%20piecewise-linear%20expression%20(requires%20%60mmxnlp%60).%20The%20cost%20function%20is%20specified%20directly%20as%20a%20piecewise%20linear%20function%20of%20the%20total%20quantity%20%24x%24.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(DEM%2C%20MODELS%2C%20mp)%3A%0A%20%20%20%20model_8%2C%20output_8%20%3D%20mp.execute_model(f'%7BMODELS%7D%2Fpricebrinc3_p.mos'%2C%20exec_params%3D%7B'OUTFILE'%3A%20'moselpy%3A'%7D)%0A%20%20%20%20if%20model_8.prob_status.is_solution_available%3A%0A%20%20%20%20%20%20%20%20print(f'Total%20cost%3A%20%7Boutput_8%5B'obj_val'%5D%3A.2f%7D%20%20(avg%20per%20unit%3A%20%7Boutput_8%5B'obj_val'%5D%20%2F%20DEM%3A.4f%7D)')%0A%20%20%20%20%20%20%20%20print(f'Quantity%20bought%3A%20%7Boutput_8%5B'sol_x'%5D%3A.0f%7D')%0A%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20print(f'No%20solution%20found%20(%7Bmodel_8.prob_status%7D)')%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%23%20Visualizing%20the%20two%20pricing%20models%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(B%2C%20COST%2C%20COST_INC%2C%20mo%2C%20np%2C%20plt)%3A%0A%20%20%20%20%23%20B%2C%20COST%20defined%20in%20the%20all-item%20section%3B%20COST_INC%20defined%20in%20the%20SOS2%20section%0A%20%20%20%20cbp%20%3D%20%5B0%5D%0A%20%20%20%20for%20_i%20in%20range(3)%3A%0A%20%20%20%20%20%20%20%20cbp.append(cbp%5B-1%5D%20%2B%20COST_INC%5B_i%5D%20*%20(B%5B_i%20%2B%201%5D%20-%20B%5B_i%5D))%0A%0A%20%20%20%20def%20allitem_cost(q)%3A%0A%20%20%20%20%20%20%20%20for%20i%20in%20range(2%2C%20-1%2C%20-1)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20q%20%3E%3D%20B%5Bi%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20COST%5Bi%5D%20*%20q%0A%20%20%20%20%20%20%20%20return%20COST%5B0%5D%20*%20q%0A%0A%20%20%20%20def%20incremental_cost(q)%3A%0A%20%20%20%20%20%20%20%20for%20i%20in%20range(3)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20q%20%3C%3D%20B%5Bi%20%2B%201%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20cbp%5Bi%5D%20%2B%20COST_INC%5Bi%5D%20*%20(q%20-%20B%5Bi%5D)%0A%20%20%20%20%20%20%20%20return%20cbp%5B3%5D%0A%20%20%20%20qty%20%3D%20np.linspace(1%2C%20200%2C%20400)%0A%20%20%20%20ai%20%3D%20%5Ballitem_cost(q)%20for%20q%20in%20qty%5D%0A%20%20%20%20inc%20%3D%20%5Bincremental_cost(q)%20for%20q%20in%20qty%5D%0A%20%20%20%20_fig%2C%20_ax%20%3D%20plt.subplots(figsize%3D(8%2C%204))%0A%20%20%20%20_ax.plot(qty%2C%20ai%2C%20label%3D'All-item%20discount%20(pricebrai)'%2C%20color%3D'steelblue')%0A%20%20%20%20_ax.plot(qty%2C%20inc%2C%20label%3D'Incremental%20discount%20(pricebrinc)'%2C%20color%3D'darkorange')%0A%20%20%20%20for%20b%20in%20B%5B1%3A-1%5D%3A%0A%20%20%20%20%20%20%20%20_ax.axvline(b%2C%20color%3D'grey'%2C%20linestyle%3D'--'%2C%20linewidth%3D0.8)%0A%20%20%20%20_ax.axvline(150%2C%20color%3D'red'%2C%20linestyle%3D'%3A'%2C%20linewidth%3D1.2%2C%20label%3D'Demand%20%3D%20150')%0A%20%20%20%20_ax.set_xlabel('Quantity')%0A%20%20%20%20_ax.set_ylabel('Total%20cost%20(%24)')%0A%20%20%20%20_ax.set_title('All-item%20vs%20incremental%20price%20breaks')%0A%20%20%20%20_ax.legend()%0A%20%20%20%20plt.tight_layout()%0A%20%20%20%20mo.output.append(plt.gcf())%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%20Function%20approximation%20with%20SOS2%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*Introductory%20example%3A%20Chapter%205%20-%20Piecewise%20Linear%20Programming*%0A%0A%20%20%20%20%3E%20**Mosel%20models%3A**%20%5B%60approx_p.mos%60%5D(https%3A%2F%2Fgithub.com%2Ffico-xpress%2Fpython-notebooks%2Fblob%2Fmain%2Fmarimo%2Fpublic%2Fmoselpy%2Fmodels%2Fintro%2Fapprox_p.mos)%2C%20%5B%60approx2_p.mos%60%5D(https%3A%2F%2Fgithub.com%2Ffico-xpress%2Fpython-notebooks%2Fblob%2Fmain%2Fmarimo%2Fpublic%2Fmoselpy%2Fmodels%2Fintro%2Fapprox2_p.mos)%0A%0A%20%20%20%20Minimize%20a%20piecewise%20linear%20function%20%24f(x)%24%20defined%20by%20five%20breakpoints%3A%0A%0A%20%20%20%20%7C%20%24i%24%20%7C%20%24%5Ctext%7BR%7D_i%24%20%7C%20%24%5Ctext%7BF%7D_i%24%20%7C%0A%20%20%20%20%7C-----%7C-------%7C-------%7C%0A%20%20%20%20%7C%201%20%7C%201.0%20%7C%202.0%20%7C%0A%20%20%20%20%7C%202%20%7C%202.2%20%7C%203.2%20%7C%0A%20%20%20%20%7C%203%20%7C%203.4%20%7C%201.4%20%7C%0A%20%20%20%20%7C%204%20%7C%204.8%20%7C%202.5%20%7C%0A%20%20%20%20%7C%205%20%7C%206.5%20%7C%200.8%20%7C%0A%0A%20%20%20%20**Decision%20variables%3A**%20%24x%2C%20f%20%5Cgeq%200%24%20(position%20and%20function%20value)%2C%20%24y_i%20%5Cgeq%200%24%20(SOS2%20interpolation%20weights%20at%20each%20breakpoint).%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%20f%20%5C%5C%0A%20%20%20%20%5Ctext%7Bsubject%20to%7D%20%5Cquad%20%26%20f%20%3D%20%5Csum_%7Bi%3D1%7D%5E%7B5%7D%20%5Ctext%7BF%7D_i%5C%2Cy_i%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%20x%20%3D%20%5Csum_%7Bi%3D1%7D%5E%7B5%7D%20%5Ctext%7BR%7D_i%5C%2Cy_i%2C%20%5Cquad%20%5Csum_%7Bi%3D1%7D%5E%7B5%7D%20y_i%20%3D%201%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%20R_1%20%5Cleq%20x%20%5Cleq%20R_5%2C%20%5Cquad%20y%20%5Cin%20%5Ctext%7BSOS2%7D%0A%20%20%20%20%5Cend%7Balign*%7D%0A%20%20%20%20%24%24%0A%0A%20%20%20%20The%20breakpoint%20coordinates%20(%24R_i%24%2C%20%24F_i%24)%20are%20defined%20in%20Python%20as%20plain%20lists%20and%20injected%20into%20the%20model%20via%20%60input_data%60.%20The%20model%20receives%20them%20through%20an%20%60initializations%20from%20DATASOURCE%60%20block%2C%20with%20%60DATASOURCE%60%20overridden%20to%20%60%22moselpy%3A%22%60%20at%20runtime%20-%20the%20same%20pattern%20used%20throughout%20these%20examples.%20This%20makes%20it%20trivial%20to%20experiment%20with%20different%20breakpoint%20configurations%20without%20editing%20the%20Mosel%20source.%20The%20model%20also%20ships%20with%20%60approx_p.dat%60%20and%20can%20run%20standalone%20with%20%60mosel%20exec%20approx_p.mos%60.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(MODELS%2C%20mo%2C%20mp%2C%20plt)%3A%0A%20%20%20%20mp.compile_model(f'%7BMODELS%7D%2Fapprox_p.mos'%2C%20f'%7BMODELS%7D%2Fapprox_p.bim')%0A%20%20%20%20model_9%20%3D%20mp.load_model(f'%7BMODELS%7D%2Fapprox_p.bim')%0A%20%20%20%20model_9.set_default_stream(mp.StreamType.OUTPUT%2C%20'null%3A')%0A%20%20%20%20R%20%3D%20%5B1.0%2C%202.2%2C%203.4%2C%204.8%2C%206.5%5D%0A%20%20%20%20F%20%3D%20%5B2.0%2C%203.2%2C%201.4%2C%202.5%2C%200.8%5D%0A%20%20%20%20output_9%20%3D%20model_9.run(exec_params%3D%7B'DATASOURCE'%3A%20'moselpy%3A'%2C%20'OUTFILE'%3A%20'moselpy%3A'%7D%2C%20input_data%3D%7B'R'%3A%20%7Bi%20%2B%201%3A%20v%20for%20i%2C%20v%20in%20enumerate(R)%7D%2C%20'F'%3A%20%7Bi%20%2B%201%3A%20v%20for%20i%2C%20v%20in%20enumerate(F)%7D%7D)%0A%20%20%20%20if%20model_9.prob_status.is_solution_available%3A%0A%20%20%20%20%20%20%20%20print(f'Optimal%20f%3A%20%7Boutput_9%5B'sol_f'%5D%3A.4f%7D%20%20at%20x%20%3D%20%7Boutput_9%5B'sol_x'%5D%3A.4f%7D')%0A%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20print(f'No%20solution%20found%20(%7Bmodel_9.prob_status%7D)')%0A%20%20%20%20_fig%2C%20_ax%20%3D%20plt.subplots(figsize%3D(6%2C%203))%0A%20%20%20%20_ax.plot(R%2C%20F%2C%20'b-o'%2C%20label%3D'f(x)')%0A%20%20%20%20_ax.plot(output_9%5B'sol_x'%5D%2C%20output_9%5B'sol_f'%5D%2C%20'r*'%2C%20markersize%3D14%2C%20label%3Df'minimum%3A%20(%7Boutput_9%5B'sol_x'%5D%3A.2f%7D%2C%20%7Boutput_9%5B'sol_f'%5D%3A.2f%7D)')%0A%20%20%20%20_ax.set_xlabel('x')%0A%20%20%20%20_ax.set_ylabel('f(x)')%0A%20%20%20%20_ax.set_title('Piecewise%20linear%20function%20-%20minimum%20via%20SOS2')%0A%20%20%20%20_ax.legend()%0A%20%20%20%20plt.tight_layout()%0A%20%20%20%20mo.output.append(plt.gcf())%0A%20%20%20%20return%20F%2C%20R%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%20Piecewise%20linear%20approximation%20via%20%60pwlin%60%20(approx2_p.mos)%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%60approx2_p.mos%60%20uses%20a%20%60pwlin%60%20expression%20(requires%20%60mmxnlp%60)%20to%20state%20the%20same%20piecewise%20linear%20constraint%20-%20a%20more%20compact%20formulation%20handled%20natively%20by%20the%20MIP%20solver.%20The%20same%20breakpoint%20data%20from%20the%20cell%20above%20(%60R%60%20and%20%60F%60)%20is%20reused%20and%20passed%20under%20the%20model's%20variable%20names%20%60X%60%20and%20%60FX%60.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(F%2C%20MODELS%2C%20R%2C%20mp)%3A%0A%20%20%20%20%23%20Reuse%20the%20same%20breakpoint%20data%20from%20above%2C%20passed%20as%20X%20and%20FX%0A%20%20%20%20model_10%2C%20output_10%20%3D%20mp.execute_model(f'%7BMODELS%7D%2Fapprox2_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'X'%3A%20%7Bi%20%2B%201%3A%20v%20for%20i%2C%20v%20in%20enumerate(R)%7D%2C%20'FX'%3A%20%7Bi%20%2B%201%3A%20v%20for%20i%2C%20v%20in%20enumerate(F)%7D%7D)%0A%20%20%20%20if%20model_10.prob_status.is_solution_available%3A%0A%20%20%20%20%20%20%20%20print(f'Optimal%20f%3A%20%7Boutput_10%5B'sol_f'%5D%3A.4f%7D%20%20at%20x%20%3D%20%7Boutput_10%5B'sol_x'%5D%3A.4f%7D')%0A%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20print(f'No%20solution%20found%20(%7Bmodel_10.prob_status%7D)')%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
632477bb5a96f241d473cb9f296f8ff9