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%20Economics%20and%20Finance%22%0A%23%20description%20%3D%20%22Loans%2C%20portfolio%2C%20retirement%2C%20frontier%22%0A%23%20image%20%3D%20%22__marimo__%2Fthumbnail-moselpy-economicsfinance.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%20H%3A%20Economics%20and%20Finance**%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_economics_finance.py***%20-%20Economics%20and%20finance%20problems%20from%20the%20Xpress%20Applications%20Book%20-%20loan%20selection%2C%20publicity%20planning%2C%20portfolio%20composition%2C%20retirement%20funding%2C%20family%20budgeting%2C%20company%20expansion%2C%20and%20quadratic%20mean-variance%20portfolio%20selection%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%22economics_finance%22)%0A%20%20%20%20DATA%20%20%20%3D%20str(_root%20%2F%20%22data%22%20%2F%20%22economics_finance%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%20Economics%20and%20finance%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%20seven%20LP%2C%20MIP%2C%20and%20QP%20examples%20from%20Chapter%20H%20of%20the%20Xpress%20Applications%20Book%2C%20solved%20via%20%5BMoselPy%5D(https%3A%2F%2Fpypi.org%2Fproject%2Fmoselpy%2F).%20Examples%20range%20from%20loan%20selection%20and%20publicity%20planning%20to%20portfolio%20optimization%2C%20retirement%20fund%20design%2C%20family%20budgeting%2C%20company%20expansion%2C%20and%20quadratic%20mean-variance%20portfolio%20selection.%0A%0A%20%20%20%20%3E%20**Mosel%20source%20files%3A**%20%5B%60models%2Feconomics_finance%2F%60%5D(https%3A%2F%2Fgithub.com%2Ffico-xpress%2Fpython-notebooks%2Ftree%2Fmain%2Fmarimo%2Fpublic%2Fmoselpy%2Fmodels%2Feconomics_finance)%20%26nbsp%3B%7C%26nbsp%3B%20**Data%20files%3A**%20%5B%60data%2Feconomics_finance%2F%60%5D(https%3A%2F%2Fgithub.com%2Ffico-xpress%2Fpython-notebooks%2Ftree%2Fmain%2Fmarimo%2Fpublic%2Fmoselpy%2Fdata%2Feconomics_finance)%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_8)%0A%0A%20%20%20%20%23%23%23%20MoselPy%20features%20demonstrated%20in%20this%20chapter%0A%0A%20%20%20%20-%20**String-keyed%20dict%20input**%3A%20H-1%20populates%20the%20%60SHOPS%60%20set%20automatically%20from%20%60PRICE%60%20dict%20keys%3B%20no%20need%20to%20pass%20the%20set%20separately.%0A%20%20%20%20-%20**%60exec_params%60%20to%20override%20model%20parameters**%3A%20H-2%20controls%20%60BUDGET%60%20and%20%60TARGET%60%3B%20H-3%20controls%20portfolio%20composition%20limits%20from%20Python%20without%20editing%20the%20model.%0A%20%20%20%20-%20**%60set%20of%20integer%60%20passed%20from%20Python**%3A%20H-3%20passes%20%60EU%60%20and%20%60TECHNOLOGY%60%20directly%20as%20Python%20%60set%60%20objects.%0A%20%20%20%20-%20**Solving%20as%20an%20LP%20via%20%60XPRS_LIN%60**%3A%20H-6%20calls%20%60maximize(XPRS_LIN%2C%20Profit)%60%20to%20solve%20the%20problem%20first%20as%20an%20LP%2C%20ignoring%20any%20MIP%20information%2C%20then%20%60maximize(Profit)%60%20for%20the%20full%20MIP.%0A%20%20%20%20-%20**%60mmnl%60%20module%20for%20quadratic%20objectives**%3A%20H-7%20uses%20the%20Mosel%20nonlinear%20module%20for%20mean-variance%20QP%20and%20MIQP.%0A%20%20%20%20-%20**Efficient%20frontier%20sensitivity**%3A%20H-7%20varies%20the%20%60TARGET%60%20yield%20across%20a%20parametric%20loop%20and%20collects%20variance%20vs%20return%20into%20a%20%60pd.DataFrame%60.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%20H-1%20Loan%20choice%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%20H%2C%20Example%201*%0A%0A%20%20%20%20%3E%20**Mosel%20model%3A**%20%5B%60h1loan_p.mos%60%5D(https%3A%2F%2Fgithub.com%2Ffico-xpress%2Fpython-notebooks%2Fblob%2Fmain%2Fmarimo%2Fpublic%2Fmoselpy%2Fmodels%2Feconomics_finance%2Fh1loan_p.mos)%0A%0A%20%20%20%20A%20chain%20of%20shops%20aims%20to%20open%20three%20new%20shops%20in%20London%2C%20Munich%2C%20and%20Rome.%20Three%20banks%20offer%20loans%20at%20different%20interest%20rates%20for%20each%20project%2C%20with%20a%20common%20maximum%20total%20loan%20volume.%20The%20model%20minimizes%20total%20annual%20interest%20payments%20over%20an%208-year%20repayment%20period.%0A%0A%20%20%20%20**Sets%3A**%20%24%5Ctext%7BBANKS%7D%20%3D%20%5C%7B1%2C2%2C3%5C%7D%24%2C%20%24%5Ctext%7BSHOPS%7D%20%3D%20%5C%7B%5Ctext%7BLondon%7D%2C%5Ctext%7BMunich%7D%2C%5Ctext%7BRome%7D%5C%7D%24%0A%0A%20%20%20%20**Parameters%3A**%20%24%5Ctext%7BPRICE%7D_s%24%20-%20opening%20cost%20of%20shop%20%24s%24%3B%20%24%5Ctext%7BRATE%7D_%7Bb%2Cs%7D%24%20-%20annual%20rate%20of%20bank%20%24b%24%20for%20shop%20%24s%24%3B%20%24%5Ctext%7BVMAX%7D%24%20-%20max%20total%20loan%20per%20bank%3B%20%24%5Ctext%7BDUR%7D%24%20-%20repayment%20period%0A%0A%20%20%20%20**Decision%20variables%3A**%20%24%5Cmathit%7Bborrow%7D_%7Bb%2Cs%7D%20%5Cgeq%200%24%20-%20loan%20from%20bank%20%24b%24%20for%20shop%20%24s%24%0A%0A%20%20%20%20%24%24%0A%20%20%20%20%5Cbegin%7Balign*%7D%0A%20%20%20%20%5Ctext%7Bminimize%7D%20%20%20%5Cquad%20%26%20%5Csum_%7Bb%2Cs%7D%20%5Cmathit%7Bborrow%7D_%7Bb%2Cs%7D%20%5Ccdot%20%5Cfrac%7B%5Ctext%7BRATE%7D_%7Bb%2Cs%7D%7D%7B1-(1%2B%5Ctext%7BRATE%7D_%7Bb%2Cs%7D)%5E%7B-%5Ctext%7BDUR%7D%7D%7D%20%5C%5C%0A%20%20%20%20%5Ctext%7Bsubject%20to%7D%20%5Cquad%20%26%20%5Csum_b%20%5Cmathit%7Bborrow%7D_%7Bb%2Cs%7D%20%3D%20%5Ctext%7BPRICE%7D_s%20%5Cquad%20%5Cforall%5C%2C%20s%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_s%20%5Cmathit%7Bborrow%7D_%7Bb%2Cs%7D%20%5Cleq%20%5Ctext%7BVMAX%7D%20%5Cquad%20%5Cforall%5C%2C%20b%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%20shop%20prices%20as%20a%20string-keyed%20dict%20and%20the%20rate%20matrix%20as%20a%20dict%20with%20%60(bank%2C%20shop)%60%20tuple%20keys.%20Passing%20%60PRICE%60%20via%20%60input_data%60%20automatically%20populates%20the%20%60SHOPS%60%20set%20of%20strings%20in%20the%20Mosel%20model%20from%20the%20dict%20keys.%20The%20scalar%20parameters%20%60DUR%60%20and%20%60VMAX%60%20are%20already%20set%20as%20model%20defaults%20and%20can%20be%20overridden%20via%20%60exec_params%60.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(pd)%3A%0A%20%20%20%20PRICE%20%3D%20%7B%22London%22%3A%202_500_000%2C%20%22Munich%22%3A%201_000_000%2C%20%22Rome%22%3A%201_700_000%7D%0A%0A%20%20%20%20RATE%20%3D%20%7B%0A%20%20%20%20%20%20%20%20(1%2C%20%22London%22)%3A%200.050%2C%20(1%2C%20%22Munich%22)%3A%200.065%2C%20(1%2C%20%22Rome%22)%3A%200.061%2C%0A%20%20%20%20%20%20%20%20(2%2C%20%22London%22)%3A%200.052%2C%20(2%2C%20%22Munich%22)%3A%200.062%2C%20(2%2C%20%22Rome%22)%3A%200.062%2C%0A%20%20%20%20%20%20%20%20(3%2C%20%22London%22)%3A%200.055%2C%20(3%2C%20%22Munich%22)%3A%200.058%2C%20(3%2C%20%22Rome%22)%3A%200.065%2C%0A%20%20%20%20%7D%0A%0A%20%20%20%20shops%20%3D%20list(PRICE.keys())%0A%20%20%20%20banks%20%3D%20%5B1%2C%202%2C%203%5D%0A%0A%20%20%20%20rate_df%20%3D%20pd.DataFrame(%0A%20%20%20%20%20%20%20%20%7Bs%3A%20%5BRATE%5B(b%2C%20s)%5D%20*%20100%20for%20b%20in%20banks%5D%20for%20s%20in%20shops%7D%2C%0A%20%20%20%20%20%20%20%20index%3D%5Bf%22Bank%20%7Bb%7D%22%20for%20b%20in%20banks%5D%2C%0A%20%20%20%20)%0A%20%20%20%20rate_df.index.name%20%3D%20%22Bank%22%0A%20%20%20%20print(f%22Shop%20prices%3A%20%7B%20%7Bs%3A%20f'%7Bv%3A%2C.0f%7D'%20for%20s%2Cv%20in%20PRICE.items()%7D%20%7D%22)%0A%20%20%20%20print(f%22%5CnInterest%20rates%20(%25)%3A%22)%0A%20%20%20%20print(rate_df.to_string())%0A%20%20%20%20return%20PRICE%2C%20RATE%2C%20banks%2C%20shops%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20Use%20the%20single%20execution%20command.%20The%20%60SHOPS%60%20set%20of%20strings%20auto-populates%20from%20the%20%60PRICE%60%20dict%20keys.%20After%20solving%2C%20%60sol_borrow%60%20is%20a%20dict%20with%20%60(bank%2C%20shop)%60%20tuple%20keys.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(MODELS%2C%20PRICE%2C%20RATE%2C%20banks%2C%20mp%2C%20shops)%3A%0A%20%20%20%20model%2C%20_output%20%3D%20mp.execute_model(f'%7BMODELS%7D%2Fh1loan_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'PRICE'%3A%20PRICE%2C%20'RATE'%3A%20RATE%7D)%0A%20%20%20%20if%20model.prob_status.is_solution_available%3A%0A%20%20%20%20%20%20%20%20obj_val_h1%20%3D%20_output%5B'obj_val'%5D%0A%20%20%20%20%20%20%20%20sol_borrow%20%3D%20_output%5B'sol_borrow'%5D%0A%20%20%20%20%20%20%20%20print(f'Total%20annual%20interest%3A%20%7Bobj_val_h1%3A%2C.2f%7D')%0A%20%20%20%20%20%20%20%20print()%0A%20%20%20%20%20%20%20%20for%20_s%20in%20shops%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20loans%20%3D%20%7Bb%3A%20sol_borrow.get((b%2C%20_s)%2C%200)%20for%20b%20in%20banks%20if%20sol_borrow.get((b%2C%20_s)%2C%200)%20%3E%200.5%7D%0A%20%20%20%20%20%20%20%20%20%20%20%20parts%20%3D%20%5Bf'bank%20%7Bb%7D%3A%20%7Bv%20%2F%201000000.0%3A.2f%7DM'%20for%20b%2C%20v%20in%20loans.items()%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20print(f'%20%20%7B_s%7D%3A%20%7B'%2C%20'.join(parts)%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_h1%2C%20sol_borrow%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20Stacked%20bar%20chart%20showing%20the%20loan%20split%20per%20shop%20across%20the%20three%20banks.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(RATE%2C%20banks%2C%20mo%2C%20np%2C%20obj_val_h1%2C%20plt%2C%20shops%2C%20sol_borrow)%3A%0A%20%20%20%20if%20'sol_borrow'%20in%20locals()%3A%0A%20%20%20%20%20%20%20%20bank_colors%20%3D%20%7B1%3A%20'%231E88E5'%2C%202%3A%20'%23FB8C00'%2C%203%3A%20'%2343A047'%7D%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%20bottom%20%3D%20np.zeros(len(shops))%0A%20%20%20%20%20%20%20%20for%20_b%20in%20banks%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20vals%20%3D%20%5Bsol_borrow.get((_b%2C%20s)%2C%200)%20%2F%201000000.0%20for%20s%20in%20shops%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20_ax.bar(shops%2C%20vals%2C%20bottom%3Dbottom%2C%20color%3Dbank_colors%5B_b%5D%2C%20edgecolor%3D'white'%2C%20label%3Df'Bank%20%7B_b%7D%20(%7BRATE%5B_b%2C%20shops%5B0%5D%5D%20*%20100%3A.1f%7D%25-%7Bmax((RATE%5B_b%2C%20s%5D%20for%20s%20in%20shops))%20*%20100%3A.1f%7D%25)')%0A%20%20%20%20%20%20%20%20%20%20%20%20bottom%20%3D%20bottom%20%2B%20np.array(vals)%0A%20%20%20%20%20%20%20%20_ax.set_ylabel('Loan%20amount%20(millions)')%0A%20%20%20%20%20%20%20%20_ax.set_title(f'H-1%20Loan%20allocation%20(total%20interest%3D%7Bobj_val_h1%3A%2C.0f%7D)')%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%20solution%20available%20-%20skipping%20loan%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%20H-2%20Publicity%20campaign%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%20H%2C%20Example%202*%0A%0A%20%20%20%20%3E%20**Mosel%20model%3A**%20%5B%60h2publ_p.mos%60%5D(https%3A%2F%2Fgithub.com%2Ffico-xpress%2Fpython-notebooks%2Fblob%2Fmain%2Fmarimo%2Fpublic%2Fmoselpy%2Fmodels%2Feconomics_finance%2Fh2publ_p.mos)%0A%0A%20%20%20%20A%20company%20launching%20a%20new%20product%20needs%20to%20design%20a%20publicity%20campaign%20across%206%20media%20types%20(weekly%20newspaper%2C%20monthly%20magazine%2C%20weekly%20magazine%2C%20radio%20spot%2C%20billboard%2C%20TV%20spot).%20Each%20medium%20has%20a%20reach%2C%20cost%2C%20max%20usage%20count%2C%20and%20quality%20score.%20The%20model%20maximizes%20total%20perception%20quality%20subject%20to%20budget%20and%20minimum%20audience%20constraints.%0A%0A%20%20%20%20**Sets%3A**%20%24%5Ctext%7BMEDIA%7D%20%3D%201..6%24%0A%0A%20%20%20%20**Parameters%3A**%20%24%5Ctext%7BREACH%7D_m%24%2C%20%24%5Ctext%7BCOST%7D_m%24%2C%20%24%5Ctext%7BMAXUSE%7D_m%24%2C%20%24%5Ctext%7BSCORE%7D_m%24%2C%20%24%5Ctext%7BBUDGET%7D%24%2C%20%24%5Ctext%7BTARGET%7D%24%0A%0A%20%20%20%20**Decision%20variables%3A**%20%24%5Cmathit%7Buse%7D_m%20%5Cin%20%5Cmathbb%7BZ%7D_%7B%5Cgeq%200%7D%24%20-%20number%20of%20times%20medium%20%24m%24%20is%20used%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_m%20%5Ctext%7BSCORE%7D_m%20%5Ccdot%20%5Cmathit%7Buse%7D_m%20%5C%5C%0A%20%20%20%20%5Ctext%7Bsubject%20to%7D%20%5Cquad%20%26%20%5Csum_m%20%5Ctext%7BCOST%7D_m%20%5Ccdot%20%5Cmathit%7Buse%7D_m%20%5Cleq%20%5Ctext%7BBUDGET%7D%20%5C%5C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%26%20%5Csum_m%20%5Ctext%7BREACH%7D_m%20%5Ccdot%20%5Cmathit%7Buse%7D_m%20%5Cgeq%20%5Ctext%7BTARGET%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%200%20%5Cleq%20%5Cmathit%7Buse%7D_m%20%5Cleq%20%5Ctext%7BMAXUSE%7D_m%20%5Cquad%20%5Cforall%5C%2C%20m%0A%20%20%20%20%5Cend%7Balign*%7D%0A%20%20%20%20%24%24%0A%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%20all%20media%20data%20as%201-indexed%20dicts.%20%60BUDGET%60%20and%20%60TARGET%60%20are%20passed%20as%20model%20defaults%20but%20can%20be%20overridden%20via%20%60exec_params%60.%20The%20media%20names%20are%20defined%20in%20Python%20for%20display%3B%20they%20are%20not%20required%20by%20the%20model.%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%20NAMES%20%20%3D%20%7B1%3A%22Weekly%20newspaper%22%2C%202%3A%22Monthly%20magazine%22%2C%203%3A%22Weekly%20magazine%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%204%3A%22Radio%20spot%22%2C%20%20%20%20%20%20%20%205%3A%22Billboard%22%2C%20%20%20%20%20%20%20%206%3A%22TV%20spot%22%7D%0A%20%20%20%20REACH%20%20%3D%20%7B1%3A12000%2C%202%3A1500%2C%20%203%3A2000%2C%20%204%3A6000%2C%205%3A3000%2C%20%206%3A9000%7D%0A%20%20%20%20COST%20%20%20%3D%20%7B1%3A1500%2C%20%202%3A8000%2C%20%203%3A12000%2C%204%3A9000%2C%205%3A24000%2C%206%3A51000%7D%0A%20%20%20%20MAXUSE%20%3D%20%7B1%3A4%2C%20%20%20%20%202%3A2%2C%20%20%20%20%203%3A8%2C%20%20%20%20%204%3A60%2C%20%20%205%3A4%2C%20%20%20%20%206%3A8%7D%0A%20%20%20%20SCORE%20%20%3D%20%7B1%3A3%2C%20%20%20%20%202%3A7%2C%20%20%20%20%203%3A8%2C%20%20%20%20%204%3A2%2C%20%20%20%205%3A6%2C%20%20%20%20%206%3A9%7D%0A%20%20%20%20BUDGET%20%3D%20250_000%0A%20%20%20%20TARGET%20%3D%20100_000%0A%0A%20%20%20%20media_df%20%3D%20pd.DataFrame(%7B%22Reach%22%3A%20REACH%2C%20%22Cost%22%3A%20COST%2C%20%22Max%20uses%22%3A%20MAXUSE%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22Score%22%3A%20SCORE%2C%20%22Name%22%3A%20NAMES%7D).T%0A%20%20%20%20print(media_df.to_string())%0A%20%20%20%20return%20BUDGET%2C%20COST%2C%20MAXUSE%2C%20NAMES%2C%20REACH%2C%20SCORE%2C%20TARGET%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%20rerunning%20with%20different%20%60BUDGET%60%20or%20%60TARGET%60%20values.%20After%20solving%2C%20%60sol_use%60%20contains%20the%20optimal%20usage%20counts%20per%20medium.%20%60total_reach%60%20is%20exported%20directly%20from%20the%20model.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(BUDGET%2C%20COST%2C%20MAXUSE%2C%20MODELS%2C%20NAMES%2C%20REACH%2C%20SCORE%2C%20TARGET%2C%20mp)%3A%0A%20%20%20%20mp.compile_model(f'%7BMODELS%7D%2Fh2publ_p.mos'%2C%20f'%7BMODELS%7D%2Fh2publ_p.bim')%0A%20%20%20%20model_1%20%3D%20mp.load_model(f'%7BMODELS%7D%2Fh2publ_p.bim')%0A%20%20%20%20model_1.set_default_stream(mp.StreamType.OUTPUT%2C%20'null%3A')%0A%20%20%20%20_output%20%3D%20model_1.run(exec_params%3D%7B'DATASOURCE'%3A%20'moselpy%3A'%2C%20'OUTFILE'%3A%20'moselpy%3A'%2C%20'BUDGET'%3A%20BUDGET%2C%20'TARGET'%3A%20TARGET%7D%2C%20input_data%3D%7B'REACH'%3A%20REACH%2C%20'COST'%3A%20COST%2C%20'MAXUSE'%3A%20MAXUSE%2C%20'SCORE'%3A%20SCORE%7D)%0A%20%20%20%20if%20model_1.prob_status.is_solution_available%3A%0A%20%20%20%20%20%20%20%20obj_val_h2%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%20total_reach%20%3D%20_output%5B'total_reach'%5D%0A%20%20%20%20%20%20%20%20total_cost%20%3D%20sum((COST%5Bm%5D%20*%20round(sol_use.get(m%2C%200))%20for%20m%20in%20range(1%2C%207)))%0A%20%20%20%20%20%20%20%20print(f'Perception%20score%3A%20%7Bobj_val_h2%3A.0f%7D%20%20%7C%20%20people%20reached%3A%20%7Btotal_reach%3A%2C.0f%7D%20%20%7C%20%20budget%20used%3A%20%7Btotal_cost%3A%2C%7D')%0A%20%20%20%20%20%20%20%20print()%0A%20%20%20%20%20%20%20%20for%20_m%20in%20range(1%2C%207)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20_uses%20%3D%20round(sol_use.get(_m%2C%200))%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20_uses%20%3E%200%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20print(f'%20%20%7BNAMES%5B_m%5D%7D%3A%20%7B_uses%7D%20use(s)%20%20(score%20contribution%3A%20%7BSCORE%5B_m%5D%20*%20_uses%7D)')%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_h2%2C%20sol_use%2C%20total_reach%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20Bar%20chart%20of%20media%20usage%2C%20colored%20by%20quality%20score.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(NAMES%2C%20SCORE%2C%20mo%2C%20obj_val_h2%2C%20plt%2C%20sol_use%2C%20total_reach)%3A%0A%20%20%20%20if%20'sol_use'%20in%20locals()%3A%0A%20%20%20%20%20%20%20%20_uses%20%3D%20%5Bround(sol_use.get(m%2C%200))%20for%20m%20in%20range(1%2C%207)%5D%0A%20%20%20%20%20%20%20%20scores%20%3D%20%5BSCORE%5Bm%5D%20*%20_uses%5Bm%20-%201%5D%20for%20m%20in%20range(1%2C%207)%5D%0A%20%20%20%20%20%20%20%20_colors%20%3D%20%5B'%2343A047'%20if%20_uses%5Bm%20-%201%5D%20%3E%200%20else%20'%23BDBDBD'%20for%20m%20in%20range(1%2C%207)%5D%0A%20%20%20%20%20%20%20%20_labels%20%3D%20%5BNAMES%5Bm%5D%20for%20m%20in%20range(1%2C%207)%5D%0A%20%20%20%20%20%20%20%20_fig%2C%20_ax%20%3D%20plt.subplots(figsize%3D(9%2C%204))%0A%20%20%20%20%20%20%20%20_bars%20%3D%20_ax.bar(range(6)%2C%20_uses%2C%20color%3D_colors%2C%20edgecolor%3D'white')%0A%20%20%20%20%20%20%20%20for%20_i%2C%20(u%2C%20_s)%20in%20enumerate(zip(_uses%2C%20scores))%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20u%20%3E%200%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_ax.text(_i%2C%20u%20%2B%200.1%2C%20f'score%3A%20%7B_s%7D'%2C%20ha%3D'center'%2C%20va%3D'bottom'%2C%20fontsize%3D8)%0A%20%20%20%20%20%20%20%20_ax.set_xticks(range(6))%0A%20%20%20%20%20%20%20%20_ax.set_xticklabels(_labels%2C%20rotation%3D20%2C%20ha%3D'right'%2C%20fontsize%3D8)%0A%20%20%20%20%20%20%20%20_ax.set_ylabel('Number%20of%20uses')%0A%20%20%20%20%20%20%20%20_ax.set_title(f'H-2%20Publicity%20campaign%20(score%3D%7Bobj_val_h2%3A.0f%7D%2C%20reach%3D%7Btotal_reach%3A%2C.0f%7D)')%0A%20%20%20%20%20%20%20%20plt.tight_layout()%0A%20%20%20%20%20%20%20%20mo.output.append(plt.gcf())%0A%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20print('No%20solution%20available%20-%20skipping%20publicity%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%20H-3%20Investment%20portfolio%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%20H%2C%20Example%203*%0A%0A%20%20%20%20%3E%20**Mosel%20model%3A**%20%5B%60h3portf_p.mos%60%5D(https%3A%2F%2Fgithub.com%2Ffico-xpress%2Fpython-notebooks%2Fblob%2Fmain%2Fmarimo%2Fpublic%2Fmoselpy%2Fmodels%2Feconomics_finance%2Fh3portf_p.mos)%0A%0A%20%20%20%20A%20financial%20consultant%20allocates%20100%2C000%20in%20capital%20across%206%20shares.%20Each%20share%20investment%20must%20be%20between%20VMIN%20and%20VMAX.%20Technology%20shares%20are%20capped%20at%2030%25%20of%20capital%3B%20European%20shares%20must%20represent%20at%20least%2050%25.%20The%20model%20maximizes%20total%20expected%20return%20on%20investment.%0A%0A%20%20%20%20**Sets%3A**%20%24%5Ctext%7BSHARES%7D%20%3D%20%5C%7B1..6%5C%7D%24%2C%20%24%5Ctext%7BEU%7D%20%5Csubseteq%20%5Ctext%7BSHARES%7D%24%2C%20%24%5Ctext%7BTECHNOLOGY%7D%20%5Csubseteq%20%5Ctext%7BSHARES%7D%24%0A%0A%20%20%20%20**Parameters%3A**%20%24%5Ctext%7BRET%7D_s%24%20-%20expected%20return%20(%25)%3B%20%24%5Ctext%7BCAPITAL%7D%24%3B%20%24%5Ctext%7BMAXTECH%7D%24%3B%20%24%5Ctext%7BMINEU%7D%24%3B%20%24%5Ctext%7BVMIN%7D%24%3B%20%24%5Ctext%7BVMAX%7D%24%0A%0A%20%20%20%20**Decision%20variables%3A**%20%24%5Cmathit%7Bbuy%7D_s%20%5Cgeq%200%24%20-%20amount%20invested%20in%20share%20%24s%24%0A%0A%20%20%20%20%24%24%0A%20%20%20%20%5Cbegin%7Balign*%7D%0A%20%20%20%20%5Ctext%7Bmaximize%7D%20%20%20%5Cquad%20%26%20%5Csum_s%20%5Cfrac%7B%5Ctext%7BRET%7D_s%7D%7B100%7D%20%5Ccdot%20%5Cmathit%7Bbuy%7D_s%20%5C%5C%0A%20%20%20%20%5Ctext%7Bsubject%20to%7D%20%5Cquad%20%26%20%5Csum_s%20%5Cmathit%7Bbuy%7D_s%20%3D%20%5Ctext%7BCAPITAL%7D%20%5C%5C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%26%20%5Csum_%7Bs%20%5Cin%20%5Ctext%7BTECHNOLOGY%7D%7D%20%5Cmathit%7Bbuy%7D_s%20%5Cleq%20%5Ctext%7BMAXTECH%7D%20%5Ccdot%20%5Ctext%7BCAPITAL%7D%20%5C%5C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%26%20%5Csum_%7Bs%20%5Cin%20%5Ctext%7BEU%7D%7D%20%5Cmathit%7Bbuy%7D_s%20%5Cgeq%20%5Ctext%7BMINEU%7D%20%5Ccdot%20%5Ctext%7BCAPITAL%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%5Ctext%7BVMIN%7D%20%5Cleq%20%5Cmathit%7Bbuy%7D_s%20%5Cleq%20%5Ctext%7BVMAX%7D%20%5Cquad%20%5Cforall%5C%2C%20s%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%20The%20%60EU%60%20and%20%60TECHNOLOGY%60%20subset%20constraints%20use%20Python%20%60set%60%20objects%20passed%20via%20%60input_data%60.%20These%20are%20%60set%20of%20integer%60%20in%20Mosel%20(not%20used%20as%20array%20indices)%2C%20so%20they%20can%20be%20injected%20directly%20from%20Python.%20Portfolio%20composition%20limits%20are%20passed%20as%20%60exec_params%60.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_()%3A%0A%20%20%20%20RET%20%20%20%20%20%20%20%20%3D%20%7B1%3A%205.3%2C%202%3A%206.2%2C%203%3A%205.1%2C%204%3A%204.9%2C%205%3A%206.5%2C%206%3A%203.4%7D%0A%20%20%20%20EU%20%20%20%20%20%20%20%20%20%3D%20%7B2%2C%203%2C%205%2C%206%7D%20%20%20%20%20%20%20%20%23%20European%20shares%0A%20%20%20%20TECHNOLOGY%20%3D%20%7B1%2C%202%2C%203%7D%20%20%20%20%20%20%20%20%20%20%20%23%20Technology%20shares%0A%20%20%20%20CAPITAL%20%20%20%20%3D%20100_000%0A%20%20%20%20MAXTECH%20%20%20%20%3D%200.30%0A%20%20%20%20MINEU%20%20%20%20%20%20%3D%200.50%0A%20%20%20%20VMIN%20%20%20%20%20%20%20%3D%205_000%0A%20%20%20%20VMAX%20%20%20%20%20%20%20%3D%2040_000%0A%0A%20%20%20%20print(f%22Expected%20returns%20(%25)%3A%20%7B%20%7Bs%3A%20f'%7Br%3A.1f%7D'%20for%20s%2Cr%20in%20RET.items()%7D%20%7D%22)%0A%20%20%20%20print(f%22European%20shares%3A%20%7BEU%7D%22)%0A%20%20%20%20print(f%22Technology%20shares%3A%20%7BTECHNOLOGY%7D%22)%0A%20%20%20%20print(f%22Constraints%3A%20VMIN%3D%7BVMIN%3A%2C%7D%20%20VMAX%3D%7BVMAX%3A%2C%7D%20%20MAXTECH%3D%7BMAXTECH%3A.0%25%7D%20%20MINEU%3D%7BMINEU%3A.0%25%7D%22)%0A%20%20%20%20return%20CAPITAL%2C%20EU%2C%20MAXTECH%2C%20MINEU%2C%20RET%2C%20TECHNOLOGY%2C%20VMAX%2C%20VMIN%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%60EU%60%20and%20%60TECHNOLOGY%60%20are%20Python%20%60set%60%20objects%2C%20passed%20as%20%60input_data%60%20alongside%20the%20return%20array.%20Portfolio%20composition%20limits%20are%20set%20via%20%60exec_params%60.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(CAPITAL%2C%20EU%2C%20MAXTECH%2C%20MINEU%2C%20MODELS%2C%20RET%2C%20TECHNOLOGY%2C%20VMAX%2C%20VMIN%2C%20mp)%3A%0A%20%20%20%20model_2%2C%20_output%20%3D%20mp.execute_model(f'%7BMODELS%7D%2Fh3portf_p.mos'%2C%20exec_params%3D%7B'DATASOURCE'%3A%20'moselpy%3A'%2C%20'OUTFILE'%3A%20'moselpy%3A'%2C%20'CAPITAL'%3A%20CAPITAL%2C%20'MAXTECH'%3A%20MAXTECH%2C%20'MINEU'%3A%20MINEU%2C%20'VMIN'%3A%20VMIN%2C%20'VMAX'%3A%20VMAX%7D%2C%20input_data%3D%7B'RET'%3A%20RET%2C%20'EU'%3A%20EU%2C%20'TECHNOLOGY'%3A%20TECHNOLOGY%7D)%0A%20%20%20%20if%20model_2.prob_status.is_solution_available%3A%0A%20%20%20%20%20%20%20%20obj_val_h3%20%3D%20_output%5B'obj_val'%5D%0A%20%20%20%20%20%20%20%20sol_buy%20%3D%20_output%5B'sol_buy'%5D%0A%20%20%20%20%20%20%20%20print(f'Total%20expected%20return%3A%20%7Bobj_val_h3%3A%2C.0f%7D')%0A%20%20%20%20%20%20%20%20print()%0A%20%20%20%20%20%20%20%20total_tech%20%3D%20sum((sol_buy.get(s%2C%200)%20for%20s%20in%20TECHNOLOGY))%0A%20%20%20%20%20%20%20%20total_eu%20%3D%20sum((sol_buy.get(s%2C%200)%20for%20s%20in%20EU))%0A%20%20%20%20%20%20%20%20print(f'Technology%20cap%3A%20%7Btotal_tech%3A%2C.0f%7D%20%2F%20%7BMAXTECH%20*%20CAPITAL%3A%2C.0f%7D%20%20(limit%3A%20%7BMAXTECH%3A.0%25%7D)')%0A%20%20%20%20%20%20%20%20print(f'European%20min%3A%20%20%20%7Btotal_eu%3A%2C.0f%7D%20%2F%20%7BMINEU%20*%20CAPITAL%3A%2C.0f%7D%20%20(limit%3A%20%7BMINEU%3A.0%25%7D)')%0A%20%20%20%20%20%20%20%20print()%0A%20%20%20%20%20%20%20%20for%20_s%20in%20range(1%2C%207)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20tag%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20_s%20in%20EU%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20tag.append('EU')%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20_s%20in%20TECHNOLOGY%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20tag.append('Tech')%0A%20%20%20%20%20%20%20%20%20%20%20%20_label%20%3D%20f'%20(%7B'%2C%20'.join(tag)%7D)'%20if%20tag%20else%20''%0A%20%20%20%20%20%20%20%20%20%20%20%20print(f'%20%20Share%20%7B_s%7D%7B_label%7D%3A%20%7Bsol_buy.get(_s%2C%200)%3A%3E8%2C.0f%7D%20%20(return%3A%20%7BRET%5B_s%5D%20*%20sol_buy.get(_s%2C%200)%20%2F%20100%3A%2C.0f%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%20obj_val_h3%2C%20sol_buy%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%20Pie%20chart%20of%20portfolio%20allocation%2C%20with%20EU%20and%20Technology%20shares%20highlighted.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(EU%2C%20TECHNOLOGY%2C%20mo%2C%20mpatches%2C%20obj_val_h3%2C%20plt%2C%20sol_buy)%3A%0A%20%20%20%20if%20'sol_buy'%20in%20locals()%3A%0A%20%20%20%20%20%20%20%20allocs%20%3D%20%5Bsol_buy.get(s%2C%200)%20for%20s%20in%20range(1%2C%207)%5D%0A%20%20%20%20%20%20%20%20_colors%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20for%20_s%20in%20range(1%2C%207)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20_s%20in%20TECHNOLOGY%20and%20_s%20in%20EU%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_colors.append('%23FF7043')%0A%20%20%20%20%20%20%20%20%20%20%20%20elif%20_s%20in%20TECHNOLOGY%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_colors.append('%231E88E5')%0A%20%20%20%20%20%20%20%20%20%20%20%20elif%20_s%20in%20EU%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_colors.append('%2343A047')%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_colors.append('%2390CAF9')%0A%20%20%20%20%20%20%20%20_labels%20%3D%20%5Bf'Share%20%7Bs%7D%5Cn%7Ballocs%5Bs%20-%201%5D%3A%2C.0f%7D'%20for%20s%20in%20range(1%2C%207)%5D%0A%20%20%20%20%20%20%20%20_fig%2C%20_ax%20%3D%20plt.subplots(figsize%3D(7%2C%205))%0A%20%20%20%20%20%20%20%20wedges%2C%20texts%2C%20autotexts%20%3D%20_ax.pie(allocs%2C%20labels%3D_labels%2C%20colors%3D_colors%2C%20autopct%3D'%251.0f%25%25'%2C%20startangle%3D90%2C%20pctdistance%3D0.75)%0A%20%20%20%20%20%20%20%20for%20at%20in%20autotexts%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20at.set_fontsize(8)%0A%20%20%20%20%20%20%20%20tech_p%20%3D%20mpatches.Patch(color%3D'%231E88E5'%2C%20label%3D'Technology%20only')%0A%20%20%20%20%20%20%20%20eu_p%20%3D%20mpatches.Patch(color%3D'%2343A047'%2C%20label%3D'European%20only')%0A%20%20%20%20%20%20%20%20both_p%20%3D%20mpatches.Patch(color%3D'%23FF7043'%2C%20label%3D'Tech%20%2B%20EU')%0A%20%20%20%20%20%20%20%20other_p%20%3D%20mpatches.Patch(color%3D'%2390CAF9'%2C%20label%3D'Other')%0A%20%20%20%20%20%20%20%20_ax.legend(handles%3D%5Btech_p%2C%20eu_p%2C%20both_p%2C%20other_p%5D%2C%20fontsize%3D8%2C%20loc%3D'lower%20right')%0A%20%20%20%20%20%20%20%20_ax.set_title(f'H-3%20Portfolio%20allocation%20(return%3D%7Bobj_val_h3%3A%2C.0f%7D)')%0A%20%20%20%20%20%20%20%20plt.tight_layout()%0A%20%20%20%20%20%20%20%20mo.output.append(plt.gcf())%0A%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20print('No%20solution%20available%20-%20skipping%20portfolio%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%20H-4%20Retirement%20fund%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%20H%2C%20Example%204*%0A%0A%20%20%20%20%3E%20**Mosel%20model%3A**%20%5B%60h4retire_p.mos%60%5D(https%3A%2F%2Fgithub.com%2Ffico-xpress%2Fpython-notebooks%2Fblob%2Fmain%2Fmarimo%2Fpublic%2Fmoselpy%2Fmodels%2Feconomics_finance%2Fh4retire_p.mos)%0A%0A%20%20%20%20A%20bank%20needs%20to%20finance%20a%20retirement%20scheme%20requiring%20fixed%20annual%20payments%20over%207%20years.%20It%20can%20invest%20in%20three%20bond%20types%3A%20SNCF%20(5-year%2C%207%25)%2C%20Fujitsu%20(4-year%2C%207%25)%2C%20and%20Treasury%20(6-year%2C%206.5%25).%20Bonds%20are%20purchased%20at%20the%20start%20of%20year%201%20only%3B%20remaining%20cash%20earns%203.2%25%20interest.%20The%20model%20minimizes%20the%20total%20capital%20required.%0A%0A%20%20%20%20**Sets%3A**%20%24%5Ctext%7BBONDS%7D%20%3D%20%5C%7B%5Ctext%7BSNCF%7D%2C%20%5Ctext%7BFujitsu%7D%2C%20%5Ctext%7BTreasury%7D%5C%7D%24%2C%20%24%5Ctext%7BYEARS%7D%20%3D%201..7%24%0A%0A%20%20%20%20**Parameters%3A**%20%24%5Ctext%7BDEM%7D_t%24%20-%20retirement%20payment%20in%20year%20%24t%24%3B%20%24%5Ctext%7BVALUE%7D_b%24%2C%20%24%5Ctext%7BRATE%7D_b%24%2C%20%24%5Ctext%7BDUR%7D_b%24%20-%20bond%20characteristics%3B%20%24%5Ctext%7BINTEREST%7D%24%20-%20cash%20rate%0A%0A%20%20%20%20**Decision%20variables%3A**%20%24%5Cmathit%7Bbuy%7D_b%20%5Cin%20%5Cmathbb%7BZ%7D_%7B%5Cgeq%200%7D%24%20-%20bonds%20purchased%3B%20%24%5Cmathit%7Binvest%7D_t%20%5Cgeq%200%24%20-%20cash%20invested%20in%20year%20%24t%24%3B%20%24%5Cmathit%7Bcapital%7D%24%20-%20total%20capital%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%5Cquad%20%26%20%5Cmathit%7Bcapital%7D%20%5C%5C%0A%20%20%20%20%5Ctext%7Bs.t.%7D%20%5Cquad%20%26%20%5Cmathit%7Bcapital%7D%20-%20%5Ctextstyle%5Csum_b%20%5Ctext%7BVALUE%7D_b%20%5Ccdot%20%5Cmathit%7Bbuy%7D_b%20-%20%5Cmathit%7Binvest%7D_1%20%3D%20%5Ctext%7BDEM%7D_1%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%26%20%5Ctextstyle%5Csum_%7Bb%3A%5C%2C%20%5Ctext%7BDUR%7D_b%2B1%20%5Cgeq%20t%7D%20%5Cbigl(%5Ctext%7BRET%7D_b%20%5Ccdot%20%5Cmathit%7Bbuy%7D_b%20%2B%20%5B%5Ctext%7BDUR%7D_b%2B1%3Dt%5D%5C%2C%5Ctext%7BVALUE%7D_b%20%5Ccdot%20%5Cmathit%7Bbuy%7D_b%5Cbigr)%20%2B%20(1%2Br)%5C%2C%5Cmathit%7Binvest%7D_%7Bt-1%7D%20-%20%5Cmathit%7Binvest%7D_t%20%3D%20%5Ctext%7BDEM%7D_t%20%5Cquad%20%5Cforall%5C%2C%20t%20%5Cgeq%202%0A%20%20%20%20%5Cend%7Balign*%7D%0A%20%20%20%20%24%24%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20Define%20bond%20characteristics%20and%20annual%20payment%20requirements%20in%20Python.%20The%20%60BONDS%60%20set%20of%20strings%20auto-populates%20from%20the%20%60VALUE%60%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%20DEM%20%3D%20%7B1%3A%201000%2C%202%3A%20600%2C%203%3A%20640%2C%204%3A%20480%2C%205%3A%20760%2C%206%3A%201020%2C%207%3A%20950%7D%0A%20%20%20%20VALUE%20%3D%20%7B'SNCF'%3A%201.0%2C%20'Fujitsu'%3A%200.8%2C%20'Treasury'%3A%200.5%7D%0A%20%20%20%20RATE_1%20%3D%20%7B'SNCF'%3A%207.0%2C%20'Fujitsu'%3A%207.0%2C%20'Treasury'%3A%206.5%7D%0A%20%20%20%20DUR%20%3D%20%7B'SNCF'%3A%205%2C%20'Fujitsu'%3A%204%2C%20'Treasury'%3A%206%7D%0A%20%20%20%20INTEREST%20%3D%203.2%0A%20%20%20%20print('Bond%20characteristics%3A')%0A%20%20%20%20for%20_b%20in%20VALUE%3A%0A%20%20%20%20%20%20%20%20print(f'%20%20%7B_b%7D%3A%20price%3D%7BVALUE%5B_b%5D%7D%2C%20rate%3D%7BRATE_1%5B_b%5D%7D%25%2C%20duration%3D%7BDUR%5B_b%5D%7D%20years')%0A%20%20%20%20print(f'%5CnAnnual%20payments%3A%20%7B%20%7Bt%3A%20f'%7Bv%3A%2C%7D'%20for%20t%2C%20v%20in%20DEM.items()%7D%7D')%0A%20%20%20%20return%20DEM%2C%20DUR%2C%20INTEREST%2C%20RATE_1%2C%20VALUE%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%20rerunning%20with%20different%20planning%20horizons%20or%20payment%20schedules.%20After%20solving%2C%20%60sol_buy%60%20contains%20bond%20quantities%20per%20type%20and%20%60sol_invest%60%20contains%20the%20cash%20invested%20each%20year.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(DEM%2C%20DUR%2C%20INTEREST%2C%20MODELS%2C%20RATE_1%2C%20VALUE%2C%20mp)%3A%0A%20%20%20%20mp.compile_model(f'%7BMODELS%7D%2Fh4retire_p.mos'%2C%20f'%7BMODELS%7D%2Fh4retire_p.bim')%0A%20%20%20%20model_3%20%3D%20mp.load_model(f'%7BMODELS%7D%2Fh4retire_p.bim')%0A%20%20%20%20model_3.set_default_stream(mp.StreamType.OUTPUT%2C%20'null%3A')%0A%20%20%20%20_output%20%3D%20model_3.run(exec_params%3D%7B'DATASOURCE'%3A%20'moselpy%3A'%2C%20'OUTFILE'%3A%20'moselpy%3A'%7D%2C%20input_data%3D%7B'DEM'%3A%20DEM%2C%20'VALUE'%3A%20VALUE%2C%20'RATE'%3A%20RATE_1%2C%20'DUR'%3A%20DUR%2C%20'INTEREST'%3A%20INTEREST%7D)%0A%20%20%20%20if%20model_3.prob_status.is_solution_available%3A%0A%20%20%20%20%20%20%20%20obj_val_h4%20%3D%20_output%5B'obj_val'%5D%0A%20%20%20%20%20%20%20%20sol_buy_1%20%3D%20_output%5B'sol_buy'%5D%0A%20%20%20%20%20%20%20%20sol_invest%20%3D%20_output%5B'sol_invest'%5D%0A%20%20%20%20%20%20%20%20sol_value%20%3D%20_output%5B'VALUE'%5D%0A%20%20%20%20%20%20%20%20print(f'Minimum%20capital%20required%3A%20%7Bobj_val_h4%3A%2C.3f%7D')%0A%20%20%20%20%20%20%20%20print()%0A%20%20%20%20%20%20%20%20print('Bonds%20to%20purchase%3A')%0A%20%20%20%20%20%20%20%20for%20_b%20in%20VALUE%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20_n%20%3D%20round(sol_buy_1.get(_b%2C%200))%0A%20%20%20%20%20%20%20%20%20%20%20%20cost%20%3D%20_n%20*%20sol_value.get(_b%2C%20VALUE%5B_b%5D)%0A%20%20%20%20%20%20%20%20%20%20%20%20print(f'%20%20%7B_b%7D%3A%20%7B_n%3A%2C%7D%20bonds%20%20(cost%3A%20%7Bcost%3A%2C.1f%7D)')%0A%20%20%20%20%20%20%20%20print()%0A%20%20%20%20%20%20%20%20print('Cash%20investment%20by%20year%3A')%0A%20%20%20%20%20%20%20%20for%20t%20in%20range(1%2C%207)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20print(f'%20%20Year%20%7Bt%7D%3A%20%7Bsol_invest.get(t%2C%200)%3A%3E10.3f%7D')%0A%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20print(f'No%20solution%20found%20(%7Bmodel_3.prob_status%7D)')%0A%20%20%20%20return%20obj_val_h4%2C%20sol_buy_1%2C%20sol_invest%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20Two-panel%20chart%3A%20(left)%20bond%20purchase%20cost%20breakdown%3B%20(right)%20cash%20flow%20timeline%20showing%20annual%20retirement%20payments%20and%20interest%20investment%20balance.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(DEM%2C%20VALUE%2C%20mo%2C%20obj_val_h4%2C%20plt%2C%20sol_buy_1%2C%20sol_invest)%3A%0A%20%20%20%20if%20'sol_buy'%20in%20locals()%3A%0A%20%20%20%20%20%20%20%20_fig%2C%20_axes%20%3D%20plt.subplots(1%2C%202%2C%20figsize%3D(12%2C%204))%0A%20%20%20%20%20%20%20%20_ax%20%3D%20_axes%5B0%5D%0A%20%20%20%20%20%20%20%20bond_colors%20%3D%20%7B'SNCF'%3A%20'%231E88E5'%2C%20'Fujitsu'%3A%20'%23FB8C00'%2C%20'Treasury'%3A%20'%2343A047'%7D%0A%20%20%20%20%20%20%20%20bond_names%20%3D%20list(VALUE.keys())%0A%20%20%20%20%20%20%20%20bond_costs%20%3D%20%5Bround(sol_buy_1.get(b%2C%200))%20*%20VALUE%5Bb%5D%20for%20b%20in%20bond_names%5D%0A%20%20%20%20%20%20%20%20_ax.bar(bond_names%2C%20bond_costs%2C%20color%3D%5Bbond_colors%5Bb%5D%20for%20b%20in%20bond_names%5D%2C%20edgecolor%3D'white')%0A%20%20%20%20%20%20%20%20for%20_i%2C%20(_b%2C%20c)%20in%20enumerate(zip(bond_names%2C%20bond_costs))%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20_n%20%3D%20round(sol_buy_1.get(_b%2C%200))%0A%20%20%20%20%20%20%20%20%20%20%20%20_ax.text(_i%2C%20c%20%2B%205%2C%20f'%7B_n%3A%2C%7D%20bonds'%2C%20ha%3D'center'%2C%20va%3D'bottom'%2C%20fontsize%3D8)%0A%20%20%20%20%20%20%20%20_ax.set_ylabel('Total%20bond%20cost')%0A%20%20%20%20%20%20%20%20_ax.set_title('H-4%20Bond%20purchases')%0A%20%20%20%20%20%20%20%20_ax2%20%3D%20_axes%5B1%5D%0A%20%20%20%20%20%20%20%20years%20%3D%20list(range(1%2C%208))%0A%20%20%20%20%20%20%20%20invest%20%3D%20%5Bsol_invest.get(t%2C%200)%20for%20t%20in%20years%5D%0A%20%20%20%20%20%20%20%20demand%20%3D%20%5BDEM%5Bt%5D%20for%20t%20in%20years%5D%0A%20%20%20%20%20%20%20%20_ax2.bar(years%2C%20demand%2C%20color%3D'%23E53935'%2C%20alpha%3D0.7%2C%20label%3D'Retirement%20payment'%2C%20edgecolor%3D'white')%0A%20%20%20%20%20%20%20%20_ax2.plot(years%2C%20invest%2C%20marker%3D'o'%2C%20color%3D'%231E88E5'%2C%20linewidth%3D2%2C%20label%3D'Cash%20invested')%0A%20%20%20%20%20%20%20%20_ax2.set_xlabel('Year')%0A%20%20%20%20%20%20%20%20_ax2.set_ylabel('Amount')%0A%20%20%20%20%20%20%20%20_ax2.set_title(f'H-4%20Cash%20flow%20(capital%3D%7Bobj_val_h4%3A%2C.2f%7D)')%0A%20%20%20%20%20%20%20%20_ax2.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%20retirement%20charts')%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%20H-5%20Family%20budget%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%20H%2C%20Example%205*%0A%0A%20%20%20%20A%20family%20earns%20a%20fixed%20monthly%20income%20plus%20an%20allowance.%20Regular%20expenses%20recur%20at%20different%20periodicities%20(monthly%2C%20bimonthly%2C%20semi-annual).%20Each%20month%20a%20minimum%20amount%20must%20be%20spent%20on%20hobbies%3B%20surplus%20income%20is%20saved%20and%20carries%20forward.%20The%20model%20maximizes%20total%20hobby%20spending%20over%2012%20months.%0A%0A%20%20%20%20*Applications%20Book%20Chapter%20H%2C%20Example%205*%0A%0A%20%20%20%20%3E%20**Mosel%20model%3A**%20%5B%60h5budget_p.mos%60%5D(https%3A%2F%2Fgithub.com%2Ffico-xpress%2Fpython-notebooks%2Fblob%2Fmain%2Fmarimo%2Fpublic%2Fmoselpy%2Fmodels%2Feconomics_finance%2Fh5budget_p.mos)%0A%0A%20%20%20%20**Note%3A**%20the%20original%20model%20reads%20%60EXPENSE%60%20and%20%60FREQ%60%20via%20the%20aliased%20block%20%60%5BEXPENSE%2C%20FREQ%5D%20as%20'PAYMENT'%60.%20The%20adapted%20model%20reads%20them%20individually%20by%20name%20so%20Python%20can%20inject%20them%20directly.%0A%0A%20%20%20%20**Sets%3A**%20%24%5Ctext%7BMONTHS%7D%20%3D%201..12%24%2C%20%24%5Ctext%7BITEMS%7D%24%20(expense%20categories%2C%20auto-populated)%0A%0A%20%20%20%20**Parameters%3A**%20%24%5Ctext%7BINCOME%7D%24%2C%20%24%5Ctext%7BALLOW%7D%24%2C%20%24%5Ctext%7BHMIN%7D%24%3B%20%24%5Ctext%7BEXPENSE%7D_i%24%2C%20%24%5Ctext%7BFREQ%7D_i%24%20-%20amount%20and%20periodicity%20of%20expense%20category%20%24i%24%0A%0A%20%20%20%20**Decision%20variables%3A**%20%24%5Cmathit%7Bhobby%7D_m%20%5Cgeq%20%5Ctext%7BHMIN%7D%24%2C%20%24%5Cmathit%7Bsaving%7D_m%20%5Cgeq%200%24%0A%0A%20%20%20%20%24%24%0A%20%20%20%20%5Cbegin%7Balign*%7D%0A%20%20%20%20%5Ctext%7Bmaximize%7D%20%20%20%5Cquad%20%26%20%5Csum_%7Bm%3D1%7D%5E%7B12%7D%20%5Cmathit%7Bhobby%7D_m%20%5C%5C%0A%20%20%20%20%5Ctext%7Bsubject%20to%7D%20%5Cquad%20%26%20%5Csum_%7Bi%3A%5C%2C%20m%20%5Cbmod%20%5Ctext%7BFREQ%7D_i%20%3D%200%7D%20%5Ctext%7BEXPENSE%7D_i%20%2B%20%5Cmathit%7Bhobby%7D_m%20%2B%20%5Cmathit%7Bsaving%7D_m%20%5Cleq%20%5Ctext%7BINCOME%7D%20%2B%20%5Ctext%7BALLOW%7D%20%2B%20%5Cmathit%7Bsaving%7D_%7Bm-1%7D%20%5Cquad%20%5Cforall%5C%2C%20m%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%20expenses%20as%20string-keyed%20dicts%20for%20%60EXPENSE%60%20(amounts)%20and%20%60FREQ%60%20(periodicities%20in%20months).%20The%20%60ITEMS%60%20set%20of%20strings%20auto-populates%20from%20the%20%60EXPENSE%60%20dict%20keys.%20%60INCOME%60%2C%20%60ALLOW%60%2C%20and%20%60HMIN%60%20are%20scalars%20passed%20via%20%60input_data%60.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_()%3A%0A%20%20%20%20EXPENSE%20%3D%20%7B%22Living%22%3A%20550%2C%20%22Rent%22%3A%20630%2C%20%22Telephone%22%3A%20135%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22Electricity%22%3A%20850%2C%20%22Car%22%3A%20340%2C%20%22Tax%22%3A%20100%7D%0A%20%20%20%20FREQ%20%20%20%20%3D%20%7B%22Living%22%3A%201%2C%20%22Rent%22%3A%201%2C%20%22Telephone%22%3A%202%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22Electricity%22%3A%206%2C%20%22Car%22%3A%201%2C%20%22Tax%22%3A%204%7D%0A%20%20%20%20INCOME%20%20%3D%201900%0A%20%20%20%20ALLOW%20%20%20%3D%20150%0A%20%20%20%20HMIN%20%20%20%20%3D%20165%0A%0A%20%20%20%20print(%22Expense%20schedule%3A%22)%0A%20%20%20%20for%20item%20in%20EXPENSE%3A%0A%20%20%20%20%20%20%20%20period%20%3D%20%22monthly%22%20if%20FREQ%5Bitem%5D%20%3D%3D%201%20else%20f%22every%20%7BFREQ%5Bitem%5D%7D%20months%22%0A%20%20%20%20%20%20%20%20print(f%22%20%20%7Bitem%7D%3A%20%7BEXPENSE%5Bitem%5D%3A%2C%7D%20(%7Bperiod%7D)%22)%0A%20%20%20%20print(f%22%5CnMonthly%20income%3A%20%7BINCOME%3A%2C%7D%20%2B%20allowance%3A%20%7BALLOW%3A%2C%7D%20%20%7C%20%20Min%20hobby%20budget%3A%20%7BHMIN%3A%2C%7D%22)%0A%20%20%20%20return%20ALLOW%2C%20EXPENSE%2C%20FREQ%2C%20HMIN%2C%20INCOME%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20Use%20the%20single%20execution%20command.%20The%20%60ITEMS%60%20set%20of%20strings%20auto-populates%20from%20the%20%60EXPENSE%60%20dict%20keys.%20The%20%60mod%60%20operator%20in%20the%20Mosel%20model%20handles%20the%20payment%20periodicity.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(ALLOW%2C%20EXPENSE%2C%20FREQ%2C%20HMIN%2C%20INCOME%2C%20MODELS%2C%20mp)%3A%0A%20%20%20%20model_4%2C%20_output%20%3D%20mp.execute_model(f'%7BMODELS%7D%2Fh5budget_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'EXPENSE'%3A%20EXPENSE%2C%20'FREQ'%3A%20FREQ%2C%20'INCOME'%3A%20INCOME%2C%20'ALLOW'%3A%20ALLOW%2C%20'HMIN'%3A%20HMIN%7D)%0A%20%20%20%20if%20model_4.prob_status.is_solution_available%3A%0A%20%20%20%20%20%20%20%20obj_val_h5%20%3D%20_output%5B'obj_val'%5D%0A%20%20%20%20%20%20%20%20sol_hobby%20%3D%20_output%5B'sol_hobby'%5D%0A%20%20%20%20%20%20%20%20sol_saving%20%3D%20_output%5B'sol_saving'%5D%0A%20%20%20%20%20%20%20%20print(f'Total%20hobby%20money%20over%20the%20year%3A%20%7Bobj_val_h5%3A%2C.0f%7D')%0A%20%20%20%20%20%20%20%20print()%0A%20%20%20%20%20%20%20%20_month_names%20%3D%20%5B'Jan'%2C%20'Feb'%2C%20'Mar'%2C%20'Apr'%2C%20'May'%2C%20'Jun'%2C%20'Jul'%2C%20'Aug'%2C%20'Sep'%2C%20'Oct'%2C%20'Nov'%2C%20'Dec'%5D%0A%20%20%20%20%20%20%20%20print(f'%7B'Month'%3A%3C6%7D%20%7B'Hobby'%3A%3E7%7D%20%7B'Saving'%3A%3E8%7D')%0A%20%20%20%20%20%20%20%20for%20_m%20in%20range(1%2C%2013)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20print(f'%7B_month_names%5B_m%20-%201%5D%3A%3C6%7D%20%7Bsol_hobby.get(_m%2C%200)%3A%3E7.0f%7D%20%7Bsol_saving.get(_m%2C%200)%3A%3E8.0f%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%20obj_val_h5%2C%20sol_hobby%2C%20sol_saving%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%20Monthly%20budget%20breakdown%3A%20stacked%20bars%20showing%20hobby%20spending%20and%20savings%2C%20with%20expense%20commitments%20annotated.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(%0A%20%20%20%20ALLOW%2C%0A%20%20%20%20EXPENSE%2C%0A%20%20%20%20FREQ%2C%0A%20%20%20%20INCOME%2C%0A%20%20%20%20mo%2C%0A%20%20%20%20obj_val_h5%2C%0A%20%20%20%20plt%2C%0A%20%20%20%20sol_hobby%2C%0A%20%20%20%20sol_saving%2C%0A)%3A%0A%20%20%20%20if%20'sol_hobby'%20in%20locals()%3A%0A%20%20%20%20%20%20%20%20_month_names%20%3D%20%5B'Jan'%2C%20'Feb'%2C%20'Mar'%2C%20'Apr'%2C%20'May'%2C%20'Jun'%2C%20'Jul'%2C%20'Aug'%2C%20'Sep'%2C%20'Oct'%2C%20'Nov'%2C%20'Dec'%5D%0A%20%20%20%20%20%20%20%20hobby_vals%20%3D%20%5Bsol_hobby.get(m%2C%200)%20for%20m%20in%20range(1%2C%2013)%5D%0A%20%20%20%20%20%20%20%20saving_vals%20%3D%20%5Bsol_saving.get(m%2C%200)%20for%20m%20in%20range(1%2C%2013)%5D%0A%20%20%20%20%20%20%20%20monthly_exp%20%3D%20%5Bsum((EXPENSE%5Bi%5D%20for%20i%20in%20EXPENSE%20if%20m%20%25%20FREQ%5Bi%5D%20%3D%3D%200))%20for%20m%20in%20range(1%2C%2013)%5D%0A%20%20%20%20%20%20%20%20_fig%2C%20_ax%20%3D%20plt.subplots(figsize%3D(11%2C%204))%20%20%23%20Monthly%20fixed%20expenses%0A%20%20%20%20%20%20%20%20x%20%3D%20range(12)%0A%20%20%20%20%20%20%20%20_ax.bar(x%2C%20hobby_vals%2C%20label%3D'Hobby'%2C%20color%3D'%2343A047'%2C%20edgecolor%3D'white')%0A%20%20%20%20%20%20%20%20_ax.bar(x%2C%20saving_vals%2C%20label%3D'Savings'%2C%20color%3D'%231E88E5'%2C%20edgecolor%3D'white'%2C%20bottom%3Dhobby_vals)%0A%20%20%20%20%20%20%20%20_ax.step(x%2C%20monthly_exp%2C%20where%3D'mid'%2C%20color%3D'%23E53935'%2C%20linewidth%3D1.5%2C%20linestyle%3D'--'%2C%20label%3D'Fixed%20expenses')%0A%20%20%20%20%20%20%20%20_ax.axhline(INCOME%20%2B%20ALLOW%2C%20color%3D'%23FB8C00'%2C%20linewidth%3D1%2C%20linestyle%3D'%3A'%2C%20label%3Df'Income%2Ballowance%3D%7BINCOME%20%2B%20ALLOW%3A%2C%7D')%0A%20%20%20%20%20%20%20%20_ax.set_xticks(range(12))%0A%20%20%20%20%20%20%20%20_ax.set_xticklabels(_month_names)%0A%20%20%20%20%20%20%20%20_ax.set_ylabel('Amount')%0A%20%20%20%20%20%20%20%20_ax.set_title(f'H-5%20Family%20budget%20(total%20hobby%3D%7Bobj_val_h5%3A%2C.0f%7D)')%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%20solution%20available%20-%20skipping%20budget%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%20H-6%20Company%20expansion%20(IP%20with%20LP%20relaxation)%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%20H%2C%20Example%206*%0A%0A%20%20%20%20%3E%20**Mosel%20model%3A**%20%5B%60h6expand_p.mos%60%5D(https%3A%2F%2Fgithub.com%2Ffico-xpress%2Fpython-notebooks%2Fblob%2Fmain%2Fmarimo%2Fpublic%2Fmoselpy%2Fmodels%2Feconomics_finance%2Fh6expand_p.mos)%0A%0A%20%20%20%20A%20company%20chooses%20from%205%20candidate%20expansion%20projects%20over%20a%205-year%20horizon.%20Each%20project%20has%20annual%20costs%20and%20a%205-year%20profit.%20The%20model%20maximizes%20total%20profit%20subject%20to%20annual%20capital%20availability.%20The%20problem%20is%20solved%20first%20as%20an%20LP%20with%20%60maximize(XPRS_LIN%2C%20Profit)%60%2C%20then%20as%20the%20full%20MIP%20with%20%60maximize(Profit)%60.%20A%20%60FORCED%60%20parameter%20allows%20locking%20in%20a%20specific%20project%20for%20a%20third%20solve.%0A%0A%20%20%20%20**Sets%3A**%20%24%5Ctext%7BPROJECTS%7D%20%3D%201..5%24%2C%20%24%5Ctext%7BTIME%7D%20%3D%201..5%24%0A%0A%20%20%20%20**Parameters%3A**%20%24%5Ctext%7BCOST%7D_%7Bp%2Ct%7D%24%2C%20%24%5Ctext%7BCAP%7D_t%24%2C%20%24%5Ctext%7BRET%7D_p%24%3B%20%24%5Ctext%7BFORCED%7D%24%20-%20project%20to%20force%20(0%20%3D%20none)%0A%0A%20%20%20%20**Decision%20variables%3A**%20%24%5Cmathit%7Bchoose%7D_p%20%5Cin%20%5C%7B0%2C1%5C%7D%24%20-%201%20if%20project%20%24p%24%20is%20selected%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_p%20%5Ctext%7BRET%7D_p%20%5Ccdot%20%5Cmathit%7Bchoose%7D_p%20%5C%5C%0A%20%20%20%20%5Ctext%7Bsubject%20to%7D%20%5Cquad%20%26%20%5Csum_p%20%5Ctext%7BCOST%7D_%7Bp%2Ct%7D%20%5Ccdot%20%5Cmathit%7Bchoose%7D_p%20%5Cleq%20%5Ctext%7BCAP%7D_t%20%5Cquad%20%5Cforall%5C%2C%20t%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%205x5%20cost%20matrix%2C%20annual%20capital%20limits%2C%20project%20returns%2C%20and%20project%20descriptions.%20All%20arrays%20are%201-indexed%20dicts.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(pd)%3A%0A%20%20%20%20cost_raw%20%3D%20%5B%5B1.8%2C%202.4%2C%202.4%2C%201.8%2C%201.5%5D%2C%20%5B1.2%2C%201.8%2C%202.4%2C%200.6%2C%200.5%5D%2C%20%5B1.2%2C%201.0%2C%200.0%2C%200.48%2C%200.0%5D%2C%20%5B1.4%2C%201.4%2C%201.2%2C%201.2%2C%201.2%5D%2C%20%5B1.6%2C%202.1%2C%202.5%2C%202.0%2C%201.8%5D%5D%0A%20%20%20%20COST_1%20%3D%20%7B(p%20%2B%201%2C%20t%20%2B%201)%3A%20cost_raw%5Bp%5D%5Bt%5D%20for%20p%20in%20range(5)%20for%20t%20in%20range(5)%7D%0A%20%20%20%20CAP%20%3D%20%7B1%3A%204.8%2C%202%3A%206.0%2C%203%3A%204.8%2C%204%3A%204.2%2C%205%3A%203.5%7D%0A%20%20%20%20RET_1%20%3D%20%7B1%3A%2010.8%2C%202%3A%204.8%2C%203%3A%203.2%2C%204%3A%204.44%2C%205%3A%2012.25%7D%0A%20%20%20%20DESCR%20%3D%20%7B1%3A%20'Expand%20assembly%20line'%2C%202%3A%20'Reorganize%20shop'%2C%203%3A%20'New%20painting%20facility'%2C%204%3A%20'Concept%20car'%2C%205%3A%20'Reorganize%20logistics'%7D%0A%20%20%20%20cost_df%20%3D%20pd.DataFrame(cost_raw%2C%20index%3D%5Bf'P%7Bp%7D'%20for%20p%20in%20range(1%2C%206)%5D%2C%20columns%3D%5Bf'Year%20%7Bt%7D'%20for%20t%20in%20range(1%2C%206)%5D)%0A%20%20%20%20cost_df.index.name%20%3D%20'Project'%0A%20%20%20%20print('Annual%20costs%20per%20project%3A')%0A%20%20%20%20print(cost_df.to_string())%0A%20%20%20%20print(f'%5CnExpected%20returns%3A%20%7B%20%7Bp%3A%20f'%7Br%3A.2f%7DM'%20for%20p%2C%20r%20in%20RET_1.items()%7D%7D')%0A%20%20%20%20return%20CAP%2C%20COST_1%2C%20DESCR%2C%20RET_1%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%23%20LP%20relaxation%20and%20MIP%20comparison%0A%0A%20%20%20%20The%20model%20is%20compiled%20once%20and%20run%20twice%3A%20first%20with%20%60FORCED%3D0%60%20(returns%20LP%20and%20MIP%20results%20in%20a%20single%20call)%2C%20then%20with%20%60FORCED%3D1%60%20to%20lock%20in%20project%201%20and%20observe%20how%20it%20changes%20the%20optimal%20portfolio.%0A%0A%20%20%20%20Calling%20%60maximize(XPRS_LIN%2C%20Profit)%60%20solves%20the%20problem%20first%20as%20an%20LP%2C%20ignoring%20any%20MIP%20information%3B%20the%20model%20then%20saves%20the%20LP%20bound%20and%20solves%20the%20full%20MIP%20with%20%60maximize(Profit)%60.%20This%20pattern%20is%20useful%20for%20bounding%20analysis%20and%20warm-starting.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(CAP%2C%20COST_1%2C%20DESCR%2C%20MODELS%2C%20RET_1%2C%20mp)%3A%0A%20%20%20%20mp.compile_model(f'%7BMODELS%7D%2Fh6expand_p.mos'%2C%20f'%7BMODELS%7D%2Fh6expand_p.bim')%0A%20%20%20%20model_5%20%3D%20mp.load_model(f'%7BMODELS%7D%2Fh6expand_p.bim')%0A%20%20%20%20model_5.set_default_stream(mp.StreamType.OUTPUT%2C%20'null%3A')%0A%20%20%20%20common_data%20%3D%20%7B'COST'%3A%20COST_1%2C%20'CAP'%3A%20CAP%2C%20'RET'%3A%20RET_1%2C%20'DESCR'%3A%20DESCR%7D%0A%20%20%20%20common_params%20%3D%20%7B'DATASOURCE'%3A%20'moselpy%3A'%2C%20'OUTFILE'%3A%20'moselpy%3A'%7D%0A%20%20%20%20out0%20%3D%20model_5.run(exec_params%3D%7B**common_params%2C%20'FORCED'%3A%200%7D%2C%20input_data%3Dcommon_data)%0A%20%20%20%20out1%20%3D%20model_5.run(exec_params%3D%7B**common_params%2C%20'FORCED'%3A%201%7D%2C%20input_data%3Dcommon_data)%0A%20%20%20%20results%20%3D%20%7B%7D%0A%20%20%20%20for%20_label%2C%20_out%20in%20%5B('No%20forcing'%2C%20out0)%2C%20('Force%20project%201'%2C%20out1)%5D%3A%0A%20%20%20%20%20%20%20%20if%20model_5.prob_status.is_solution_available%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20chosen%20%3D%20%5Bp%20for%20p%20in%20range(1%2C%206)%20if%20round(_out.get('sol_choose'%2C%20%7B%7D).get(p%2C%200))%20%3E%200%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20results%5B_label%5D%20%3D%20%7B'obj'%3A%20_out%5B'obj_val'%5D%2C%20'projects'%3A%20chosen%7D%0A%20%20%20%20%20%20%20%20%20%20%20%20print(f'%7B_label%7D%3A%20total%20profit%20%3D%20%7B_out%5B'obj_val'%5D%3A.2f%7D')%0A%20%20%20%20%20%20%20%20%20%20%20%20for%20p%20in%20chosen%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20print(f'%20%20%20%20%7Bp%7D.%20%7BDESCR%5Bp%5D%7D')%0A%20%20%20%20%20%20%20%20print()%0A%20%20%20%20if%20out0%20is%20not%20None%3A%0A%20%20%20%20%20%20%20%20print(f'LP%20relaxation%3A%20%7Bout0%5B'obj_lp'%5D%3A.3f%7D%20%20%7C%20%20MIP%3A%20%7Bout0%5B'obj_mip'%5D%3A.2f%7D')%0A%20%20%20%20return%20out0%2C%20out1%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20Bar%20chart%20comparing%20LP%20relaxation%2C%20MIP%2C%20and%20forced%20MIP%20objective%20values.%20Project%20selection%20indicators%20shown%20below.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(mo%2C%20out0%2C%20out1%2C%20plt)%3A%0A%20%20%20%20if%20out0%20is%20not%20None%20and%20out1%20is%20not%20None%3A%0A%20%20%20%20%20%20%20%20_fig%2C%20_axes%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%20_axes%5B0%5D%0A%20%20%20%20%20%20%20%20labels_bar%20%3D%20%5B'LP%20relaxation'%2C%20'MIP%20(free)'%2C%20'MIP%20(force%20P1)'%5D%20%20%23%20Left%3A%20objective%20comparison%0A%20%20%20%20%20%20%20%20values_bar%20%3D%20%5Bout0%5B'obj_lp'%5D%2C%20out0%5B'obj_mip'%5D%2C%20out1%5B'obj_val'%5D%5D%0A%20%20%20%20%20%20%20%20colors_bar%20%3D%20%5B'%2390CAF9'%2C%20'%231E88E5'%2C%20'%23FB8C00'%5D%0A%20%20%20%20%20%20%20%20_bars%20%3D%20_ax.bar(labels_bar%2C%20values_bar%2C%20color%3Dcolors_bar%2C%20edgecolor%3D'white'%2C%20width%3D0.5)%0A%20%20%20%20%20%20%20%20for%20bar%2C%20v%20in%20zip(_bars%2C%20values_bar)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20_ax.text(bar.get_x()%20%2B%20bar.get_width()%20%2F%202%2C%20v%20%2B%200.1%2C%20f'%7Bv%3A.2f%7D'%2C%20ha%3D'center'%2C%20va%3D'bottom'%2C%20fontsize%3D9)%0A%20%20%20%20%20%20%20%20_ax.set_ylabel('Total%20profit%20(M)')%0A%20%20%20%20%20%20%20%20_ax.set_title('H-6%20Expansion%20-%20objective%20comparison')%0A%20%20%20%20%20%20%20%20_ax2%20%3D%20_axes%5B1%5D%0A%20%20%20%20%20%20%20%20scenarios%20%3D%20%5B'MIP%20(free)'%2C%20'MIP%20(force%20P1)'%5D%0A%20%20%20%20%20%20%20%20out_list%20%3D%20%5Bout0%2C%20out1%5D%0A%20%20%20%20%20%20%20%20sel_matrix%20%3D%20%5B%5Bround(o.get('sol_choose'%2C%20%7B%7D).get(p%2C%200))%20for%20p%20in%20range(1%2C%206)%5D%20for%20o%20in%20out_list%5D%0A%20%20%20%20%20%20%20%20_im%20%3D%20_ax2.imshow(sel_matrix%2C%20cmap%3D'Blues'%2C%20aspect%3D'auto'%2C%20vmin%3D0%2C%20vmax%3D1)%20%20%23%20Right%3A%20project%20selection%20heatmap%0A%20%20%20%20%20%20%20%20_ax2.set_xticks(range(5))%0A%20%20%20%20%20%20%20%20_ax2.set_xticklabels(%5Bf'P%7Bp%7D'%20for%20p%20in%20range(1%2C%206)%5D%2C%20fontsize%3D9)%0A%20%20%20%20%20%20%20%20_ax2.set_yticks(range(2))%0A%20%20%20%20%20%20%20%20_ax2.set_yticklabels(scenarios%2C%20fontsize%3D8)%0A%20%20%20%20%20%20%20%20for%20_i%20in%20range(2)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20for%20j%20in%20range(5)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_ax2.text(j%2C%20_i%2C%20str(sel_matrix%5B_i%5D%5Bj%5D)%2C%20ha%3D'center'%2C%20va%3D'center'%2C%20fontsize%3D10%2C%20color%3D'white'%20if%20sel_matrix%5B_i%5D%5Bj%5D%20%3E%200.5%20else%20'black')%0A%20%20%20%20%20%20%20%20_ax2.set_title('H-6%20Project%20selection%20(1%20%3D%20chosen)')%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%20expansion%20charts')%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%20H-7%20Mean-variance%20portfolio%20(QP%2FMIQP)%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%20H%2C%20Example%207*%0A%0A%20%20%20%20%3E%20**Mosel%20model%3A**%20%5B%60h7qportf_p.mos%60%5D(https%3A%2F%2Fgithub.com%2Ffico-xpress%2Fpython-notebooks%2Fblob%2Fmain%2Fmarimo%2Fpublic%2Fmoselpy%2Fmodels%2Feconomics_finance%2Fh7qportf_p.mos)%0A%0A%20%20%20%20An%20investor%20allocates%20capital%20across%204%20securities%3A%20hardware%2C%20software%2C%20show-biz%2C%20and%20T-bills.%20The%20model%20minimizes%20portfolio%20variance%20(risk)%20subject%20to%20achieving%20a%20minimum%20target%20yield.%20Two%20variants%20are%20solved%3A%20(1)%20a%20QP%20with%20no%20asset%20limit%2C%20(2)%20a%20MIQP%20restricting%20the%20portfolio%20to%20at%20most%20%60MAXASSETS%60%20securities.%20The%20quadratic%20objective%20requires%20the%20%60mmnl%60%20Mosel%20module.%0A%0A%20%20%20%20**Sets%3A**%20%24%5Ctext%7BSECS%7D%20%3D%20%5C%7B1..4%5C%7D%24%0A%0A%20%20%20%20**Parameters%3A**%20%24%5Ctext%7BRET%7D_s%24%20-%20expected%20yield%3B%20%24%5Ctext%7BVAR%7D_%7Bs%2Ct%7D%24%20-%20variance%2Fcovariance%20matrix%3B%20%24%5Ctext%7BTARGET%7D%24%20-%20minimum%20yield%3B%20%24%5Ctext%7BMAXASSETS%7D%24%20-%20max%20securities%20in%20MIQP%0A%0A%20%20%20%20**Decision%20variables%3A**%20%24%5Cmathit%7Bfrac%7D_s%20%5Cin%20%5B0%2C1%5D%24%20-%20fraction%20of%20capital%20in%20security%20%24s%24%3B%20%24%5Cmathit%7Bbuy%7D_s%20%5Cin%20%5C%7B0%2C1%5C%7D%24%20(MIQP%20only)%0A%0A%20%20%20%20%24%24%0A%20%20%20%20%5Cbegin%7Balign*%7D%0A%20%20%20%20%5Ctext%7Bminimize%7D%20%20%20%5Cquad%20%26%20%5Csum_%7Bs%2Ct%7D%20%5Ctext%7BVAR%7D_%7Bs%2Ct%7D%20%5Ccdot%20%5Cmathit%7Bfrac%7D_s%20%5Ccdot%20%5Cmathit%7Bfrac%7D_t%20%5C%5C%0A%20%20%20%20%5Ctext%7Bsubject%20to%7D%20%5Cquad%20%26%20%5Csum_s%20%5Cmathit%7Bfrac%7D_s%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%20%5Csum_s%20%5Ctext%7BRET%7D_s%20%5Ccdot%20%5Cmathit%7Bfrac%7D_s%20%5Cgeq%20%5Ctext%7BTARGET%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%7Bfrac%7D_s%20%5Cleq%20%5Cmathit%7Bbuy%7D_s%20%5Cquad%20%5Cforall%5C%2C%20s%20%5Cquad%20%5Ctext%7B(MIQP%20only)%7D%0A%20%20%20%20%5Cend%7Balign*%7D%0A%20%20%20%20%24%24%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20Define%20the%20return%20vector%20and%20variance%2Fcovariance%20matrix.%20T-bills%20have%20zero%20variance%20and%20a%20fixed%207%25%20yield%2C%20making%20them%20the%20natural%20risk-free%20anchor.%20The%20target%20yield%20and%20maximum%20assets%20are%20overridable%20via%20%60exec_params%60.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(pd)%3A%0A%20%20%20%20NAMES_1%20%3D%20%7B1%3A%20'Hardware'%2C%202%3A%20'Software'%2C%203%3A%20'Show-biz'%2C%204%3A%20'T-bills'%7D%0A%20%20%20%20RET_2%20%3D%20%7B1%3A%208%2C%202%3A%209%2C%203%3A%2012%2C%204%3A%207%7D%0A%20%20%20%20VAR%20%3D%20%7B(1%2C%201)%3A%204%2C%20(1%2C%202)%3A%203%2C%20(1%2C%203)%3A%20-1%2C%20(1%2C%204)%3A%200%2C%20(2%2C%201)%3A%203%2C%20(2%2C%202)%3A%206%2C%20(2%2C%203)%3A%201%2C%20(2%2C%204)%3A%200%2C%20(3%2C%201)%3A%20-1%2C%20(3%2C%202)%3A%201%2C%20(3%2C%203)%3A%2010%2C%20(3%2C%204)%3A%200%2C%20(4%2C%201)%3A%200%2C%20(4%2C%202)%3A%200%2C%20(4%2C%203)%3A%200%2C%20(4%2C%204)%3A%200%7D%0A%20%20%20%20TARGET_1%20%3D%207.0%0A%20%20%20%20MAXASSETS%20%3D%204%0A%20%20%20%20var_df%20%3D%20pd.DataFrame(%7BNAMES_1%5Bs%5D%3A%20%5BVAR%5Bs%2C%20t%5D%20for%20t%20in%20range(1%2C%205)%5D%20for%20s%20in%20range(1%2C%205)%7D%2C%20index%3D%5BNAMES_1%5Bt%5D%20for%20t%20in%20range(1%2C%205)%5D)%0A%20%20%20%20print('Variance%2Fcovariance%20matrix%3A')%0A%20%20%20%20print(var_df.to_string())%0A%20%20%20%20print(f'%5CnExpected%20yields%3A%20%7B%20%7BNAMES_1%5Bs%5D%3A%20f'%7BRET_2%5Bs%5D%7D%25'%20for%20s%20in%20range(1%2C%205)%7D%7D')%0A%20%20%20%20return%20MAXASSETS%2C%20NAMES_1%2C%20RET_2%2C%20TARGET_1%2C%20VAR%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%20enable%20TARGET%20sensitivity%20analysis.%20The%20model%20runs%20both%20the%20QP%20and%20MIQP%20variants%20in%20a%20single%20call%20and%20returns%20%60obj_qp%60%2C%20%60qp_frac%60%2C%20%60obj_miqp%60%2C%20and%20%60miqp_frac%60.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(MAXASSETS%2C%20MODELS%2C%20NAMES_1%2C%20RET_2%2C%20TARGET_1%2C%20VAR%2C%20mp)%3A%0A%20%20%20%20mp.compile_model(f'%7BMODELS%7D%2Fh7qportf_p.mos'%2C%20f'%7BMODELS%7D%2Fh7qportf_p.bim')%0A%20%20%20%20model_6%20%3D%20mp.load_model(f'%7BMODELS%7D%2Fh7qportf_p.bim')%0A%20%20%20%20model_6.set_default_stream(mp.StreamType.OUTPUT%2C%20'null%3A')%0A%20%20%20%20_output%20%3D%20model_6.run(exec_params%3D%7B'DATASOURCE'%3A%20'moselpy%3A'%2C%20'OUTFILE'%3A%20'moselpy%3A'%2C%20'TARGET'%3A%20TARGET_1%2C%20'MAXASSETS'%3A%20MAXASSETS%7D%2C%20input_data%3D%7B'RET'%3A%20RET_2%2C%20'VAR'%3A%20VAR%7D)%0A%20%20%20%20if%20model_6.prob_status.is_solution_available%3A%0A%20%20%20%20%20%20%20%20obj_qp%20%3D%20_output%5B'obj_qp'%5D%0A%20%20%20%20%20%20%20%20obj_miqp%20%3D%20_output%5B'obj_miqp'%5D%0A%20%20%20%20%20%20%20%20qp_frac%20%3D%20_output%5B'qp_frac'%5D%0A%20%20%20%20%20%20%20%20miqp_frac%20%3D%20_output%5B'miqp_frac'%5D%0A%20%20%20%20%20%20%20%20sol_buy_2%20%3D%20_output%5B'sol_buy'%5D%0A%20%20%20%20%20%20%20%20print(f'QP%20%20(unlimited%20assets)%3A%20%20%20%20%20%20variance%20%3D%20%7Bobj_qp%3A.6f%7D')%0A%20%20%20%20%20%20%20%20for%20_s%20in%20range(1%2C%205)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20pct%20%3D%20qp_frac.get(_s%2C%200)%20*%20100%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20pct%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%20%20%7BNAMES_1%5B_s%5D%7D%3A%20%7Bpct%3A.2f%7D%25')%0A%20%20%20%20%20%20%20%20print(f'%5CnMIQP%20(max%20%7BMAXASSETS%7D%20assets)%3A%20variance%20%3D%20%7Bobj_miqp%3A.6f%7D')%0A%20%20%20%20%20%20%20%20for%20_s%20in%20range(1%2C%205)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20pct%20%3D%20miqp_frac.get(_s%2C%200)%20*%20100%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20pct%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%20%20%7BNAMES_1%5B_s%5D%7D%3A%20%7Bpct%3A.2f%7D%25')%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%20(model_6%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%20Efficient%20frontier%3A%20variance%20vs%20target%20yield%0A%0A%20%20%20%20The%20model%20is%20already%20compiled%20and%20loaded.%20Run%20it%20multiple%20times%20with%20increasing%20%60TARGET%60%20values%20to%20trace%20the%20efficient%20frontier%3A%20the%20minimum-variance%20portfolio%20for%20each%20required%20yield%20level.%20As%20the%20target%20yield%20increases%20beyond%207%25%20(T-bills)%2C%20the%20optimizer%20must%20include%20riskier%20assets%2C%20driving%20up%20variance.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(NAMES_1%2C%20RET_2%2C%20VAR%2C%20model_6%2C%20pd)%3A%0A%20%20%20%20targets%20%3D%20%5B7.0%2C%207.5%2C%208.0%2C%208.5%2C%209.0%2C%209.5%2C%2010.0%2C%2011.0%2C%2012.0%5D%0A%20%20%20%20frontier%20%3D%20%5B%5D%0A%20%20%20%20for%20tgt%20in%20targets%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'%2C%20'TARGET'%3A%20tgt%2C%20'MAXASSETS'%3A%204%7D%2C%20input_data%3D%7B'RET'%3A%20RET_2%2C%20'VAR'%3A%20VAR%7D)%0A%20%20%20%20%20%20%20%20if%20model_6.prob_status.is_solution_available%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20fracs%20%3D%20%7BNAMES_1%5Bs%5D%3A%20round(_out%5B'qp_frac'%5D.get(s%2C%200)%20*%20100%2C%202)%20for%20s%20in%20range(1%2C%205)%7D%0A%20%20%20%20%20%20%20%20%20%20%20%20frontier.append(%7B'Target%20yield%20(%25)'%3A%20tgt%2C%20'Min%20variance'%3A%20_out%5B'obj_qp'%5D%2C%20**fracs%7D)%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20print(f'No%20solution%20for%20TARGET%3D%7Btgt%7D')%0A%20%20%20%20frontier_df%20%3D%20pd.DataFrame(frontier).set_index('Target%20yield%20(%25)')%0A%20%20%20%20print('Efficient%20frontier%20(QP)%3A')%0A%20%20%20%20print(frontier_df.to_string(float_format%3D'%7B%3A.4f%7D'.format))%0A%20%20%20%20return%20frontier_df%2C%20targets%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%20Efficient%20frontier%20plot%20and%20portfolio%20composition%20heatmap%20showing%20how%20allocations%20shift%20as%20the%20target%20yield%20increases.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(NAMES_1%2C%20TARGET_1%2C%20frontier_df%2C%20mo%2C%20plt%2C%20targets)%3A%0A%20%20%20%20if%20frontier_df%20is%20not%20None%20and%20(not%20frontier_df.empty)%3A%0A%20%20%20%20%20%20%20%20_fig%2C%20_axes%20%3D%20plt.subplots(1%2C%202%2C%20figsize%3D(13%2C%205))%0A%20%20%20%20%20%20%20%20_ax%20%3D%20_axes%5B0%5D%0A%20%20%20%20%20%20%20%20_ax.plot(frontier_df.index%2C%20frontier_df%5B'Min%20variance'%5D%2C%20marker%3D'o'%2C%20color%3D'%231E88E5'%2C%20linewidth%3D2)%0A%20%20%20%20%20%20%20%20_ax.axvline(TARGET_1%2C%20color%3D'gray'%2C%20linestyle%3D'--'%2C%20linewidth%3D1%2C%20label%3Df'Base%20target%3D%7BTARGET_1%7D%25')%0A%20%20%20%20%20%20%20%20_ax.set_xlabel('Target%20yield%20(%25)')%0A%20%20%20%20%20%20%20%20_ax.set_ylabel('Minimum%20variance')%0A%20%20%20%20%20%20%20%20_ax.set_title('H-7%20Efficient%20frontier%20(QP)')%0A%20%20%20%20%20%20%20%20_ax.legend(fontsize%3D8)%0A%20%20%20%20%20%20%20%20_ax2%20%3D%20_axes%5B1%5D%0A%20%20%20%20%20%20%20%20comp_cols%20%3D%20%5BNAMES_1%5Bs%5D%20for%20s%20in%20range(1%2C%205)%5D%0A%20%20%20%20%20%20%20%20comp%20%3D%20frontier_df%5Bcomp_cols%5D.values%0A%20%20%20%20%20%20%20%20_im%20%3D%20_ax2.imshow(comp.T%2C%20cmap%3D'Blues'%2C%20aspect%3D'auto'%2C%20vmin%3D0%2C%20vmax%3D100)%0A%20%20%20%20%20%20%20%20plt.colorbar(_im%2C%20ax%3D_ax2%2C%20label%3D'Allocation%20(%25)')%0A%20%20%20%20%20%20%20%20_ax2.set_xticks(range(len(targets)))%0A%20%20%20%20%20%20%20%20_ax2.set_xticklabels(%5Bf'%7Bt%3A.1f%7D%25'%20for%20t%20in%20targets%5D%2C%20rotation%3D45%2C%20ha%3D'right'%2C%20fontsize%3D7)%0A%20%20%20%20%20%20%20%20_ax2.set_yticks(range(4))%0A%20%20%20%20%20%20%20%20_ax2.set_yticklabels(comp_cols%2C%20fontsize%3D8)%0A%20%20%20%20%20%20%20%20_ax2.set_title('H-7%20Portfolio%20allocation%20vs%20target%20yield')%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%20frontier%20data%20available%20-%20skipping%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
bdf6d43775f35f4f1cc3e31f2e08dc63