import%20marimo%0A%0A__generated_with%20%3D%20%220.25.2%22%0Aapp%20%3D%20marimo.App()%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_()%3A%0A%20%20%20%20import%20marimo%20as%20mo%0A%20%20%20%20import%20os%0A%0A%20%20%20%20import%20cvxpy%20as%20cp%0A%20%20%20%20import%20numpy%20as%20np%0A%0A%20%20%20%20import%20fomtools.cvxpygen%20as%20fcpg%0A%20%20%20%20from%20fomtools.codegen%20import%20CodegenConfig%2C%20TerminationSpec%0A%0A%20%20%20%20%23%20Generating%20a%20verified%20embedded%20solver%20needs%20a%20real%20coding-agent%20backend.%0A%20%20%20%20%23%20When%20it%20isn't%20configured%20(e.g.%20a%20headless%20docs%20build)%2C%20the%20codegen%20and%0A%20%20%20%20%23%20solve%20cells%20below%20skip%20with%20a%20message%20instead%20of%20raising.%0A%20%20%20%20agent_available%20%3D%20os.environ.get(%22FOMTOOLS_CODEGEN_AGENT%22)%20%3D%3D%20%221%22%0A%20%20%20%20return%20CodegenConfig%2C%20TerminationSpec%2C%20agent_available%2C%20cp%2C%20fcpg%2C%20mo%2C%20np%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%20MPC%20with%20CVXPYgen%20and%20a%20generated%20solver%0A%0A%20%20%20%20%5BBack%20to%20the%20CVXPYgen%20tutorial%5D(cvxpygen%2F)%0A%0A%20%20%20%20In%20this%20tutorial%20we%20model%20a%20small%20model%20predictive%20control%20(MPC)%20problem%20in%20CVXPY%20and%20turn%20it%20into%20an%20embedded%20C%20solver.%0A%20%20%20%20%5BCVXPYgen%5D(https%3A%2F%2Fgithub.com%2Fcvxgrp%2Fcvxpygen)%20generates%20the%20code%20that%20maps%20the%20problem%20parameter%20to%20the%20data%20of%20a%20quadratic%20program%2C%20and%20FOMTools%20generates%20the%20first-order%20method%20that%20solves%20that%20quadratic%20program.%0A%20%20%20%20Both%20end%20up%20in%20one%20C%20project%20with%20a%20Python%20wrapper%2C%20so%20the%20CVXPY%20problem%20can%20be%20solved%20through%20the%20generated%20code%20with%20%60problem.solve(method%3D%22CPG%22)%60.%0A%0A%20%20%20%20Running%20the%20whole%20notebook%20needs%20the%20%60cvxpygen%60%20and%20%60codegen%60%20extras%20(%60pip%20install%20'fomtools%5Bcvxpygen%2Ccodegen%5D'%60)%20and%20%60cmake%60%20on%20your%20%60PATH%60%2C%20which%20CVXPYgen%20uses%20to%20build%20the%20Python%20wrapper.%0A%20%20%20%20The%20solver%20is%20written%20by%20an%20LLM%20coding%20agent%20and%20then%20verified%20against%20a%20Python%20reference%2C%20so%20generating%20it%20also%20needs%20a%20configured%20coding%20agent.%0A%20%20%20%20Set%20%60FOMTOOLS_CODEGEN_AGENT%3D1%60%20before%20starting%20marimo%20to%20use%20the%20Claude%20agent.%0A%20%20%20%20Without%20it%2C%20the%20generation%20and%20solve%20cells%20below%20print%20a%20message%20and%20skip%2C%20and%20the%20rest%20of%20the%20notebook%20still%20runs.%0A%20%20%20%20The%20solver%20is%20written%20to%20%60mpc_solver%2F%60%20relative%20to%20the%20working%20directory%2C%20so%20start%20marimo%20(or%20%60python%20cvxpygen_mpc.py%60)%20from%20this%20directory.%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%20Problem%20data%0A%0A%20%20%20%20The%20plant%20is%20a%20linear%20system%20%24s_%7Bt%2B1%7D%20%3D%20A%20s_t%20%2B%20B%20u_t%24%20with%20state%20%24s_t%20%5Cin%20%5Cmathbf%7BR%7D%5E%7Bn_s%7D%24%20and%20input%20%24u_t%20%5Cin%20%5Cmathbf%7BR%7D%5E%7Bn_u%7D%24%2C%20where%20%24n_s%20%3D%204%24%20and%20%24n_u%20%3D%202%24.%0A%20%20%20%20We%20draw%20%24A%24%20and%20%24B%24%20at%20random%20and%20scale%20%24A%24%20to%20spectral%20radius%20%240.9%24%2C%20so%20the%20uncontrolled%20system%20is%20stable.%0A%20%20%20%20The%20horizon%20is%20%24T%20%3D%208%24%2C%20the%20stage%20costs%20are%20%24Q%20%3D%20I%24%20and%20%24R%20%3D%200.1%20I%24%2C%20and%20the%20terminal%20cost%20is%20%24Q_T%20%3D%2010%20I%24.%0A%20%20%20%20In%20the%20code%2C%20%24A%24%2C%20%24B%24%2C%20%24Q_T%24%2C%20%24n_s%24%2C%20%24n_u%24%2C%20and%20%24T%24%20are%20%60Ad%60%2C%20%60Bd%60%2C%20%60QN%60%2C%20%60nx%60%2C%20%60nu%60%2C%20and%20%60N%60.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(np)%3A%0A%20%20%20%20nx%2C%20nu%2C%20N%20%3D%204%2C%202%2C%208%0A%20%20%20%20rng%20%3D%20np.random.default_rng(1)%0A%20%20%20%20Ad%20%3D%20rng.normal(size%3D(nx%2C%20nx))%0A%20%20%20%20Ad%20%3D%200.9%20*%20Ad%20%2F%20np.max(np.abs(np.linalg.eigvals(Ad)))%0A%20%20%20%20Bd%20%3D%200.5%20*%20rng.normal(size%3D(nx%2C%20nu))%0A%20%20%20%20Q%2C%20QN%2C%20R%20%3D%20np.eye(nx)%2C%2010.0%20*%20np.eye(nx)%2C%200.1%20*%20np.eye(nu)%0A%20%20%20%20return%20Ad%2C%20Bd%2C%20N%2C%20Q%2C%20QN%2C%20R%2C%20nu%2C%20nx%2C%20rng%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%20CVXPY%20model%0A%0A%20%20%20%20Given%20an%20initial%20state%20%24x%20%5Cin%20%5Cmathbf%7BR%7D%5E%7Bn_s%7D%24%2C%20the%20MPC%20problem%20is%0A%0A%20%20%20%20%5Cbegin%7Barray%7D%7Bll%7D%0A%20%20%20%20%20%20%20%20%5Ctext%7Bminimize%7D%20%26%20s_T%5ET%20Q_T%20s_T%20%2B%20%5Csum_%7Bt%3D0%7D%5E%7BT-1%7D%20%5Cleft(%20s_t%5ET%20Q%20s_t%20%2B%20u_t%5ET%20R%20u_t%20%5Cright)%20%5C%5C%0A%20%20%20%20%20%20%20%20%5Ctext%7Bsubject%20to%7D%20%26%20s_%7Bt%2B1%7D%20%3D%20A%20s_t%20%2B%20B%20u_t%2C%20%5Cquad%20t%20%3D%200%2C%20%5Cdots%2C%20T-1%20%5C%5C%0A%20%20%20%20%20%20%20%20%26%20%5C%7Cs_t%5C%7C_%5Cinfty%20%5Cle%205%2C%20%5Cquad%20%5C%7Cu_t%5C%7C_%5Cinfty%20%5Cle%201%2C%20%5Cquad%20t%20%3D%200%2C%20%5Cdots%2C%20T-1%20%5C%5C%0A%20%20%20%20%20%20%20%20%26%20s_0%20%3D%20x%2C%0A%20%20%20%20%5Cend%7Barray%7D%0A%0A%20%20%20%20with%20variables%20%24s_0%2C%20%5Cdots%2C%20s_T%24%20and%20%24u_0%2C%20%5Cdots%2C%20u_%7BT-1%7D%24%20(%60xs%60%20and%20%60us%60%20in%20the%20code).%0A%20%20%20%20The%20initial%20state%20%24x%24%20is%20a%20CVXPY%20%60Parameter%60%20named%20%60x0%60%2C%20so%20CVXPYgen%20treats%20it%20as%20data%20that%20can%20change%20between%20solves.%0A%20%20%20%20The%20value%20assigned%20to%20it%20here%20is%20only%20a%20placeholder%3B%20the%20solve%20cell%20below%20sets%20a%20new%20one.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(Ad%2C%20Bd%2C%20N%2C%20Q%2C%20QN%2C%20R%2C%20cp%2C%20nu%2C%20nx%2C%20rng)%3A%0A%20%20%20%20x0%20%3D%20cp.Parameter(nx%2C%20name%3D%22x0%22)%0A%20%20%20%20x0.value%20%3D%20rng.uniform(-0.5%2C%200.5%2C%20size%3Dnx)%0A%0A%20%20%20%20xs%20%3D%20cp.Variable((N%20%2B%201%2C%20nx)%2C%20name%3D%22x%22)%0A%20%20%20%20us%20%3D%20cp.Variable((N%2C%20nu)%2C%20name%3D%22u%22)%0A%20%20%20%20cost%20%3D%20cp.quad_form(xs%5BN%5D%2C%20QN)%0A%20%20%20%20constraints%20%3D%20%5Bxs%5B0%5D%20%3D%3D%20x0%5D%0A%20%20%20%20for%20t%20in%20range(N)%3A%0A%20%20%20%20%20%20%20%20cost%20%2B%3D%20cp.quad_form(xs%5Bt%5D%2C%20Q)%20%2B%20cp.quad_form(us%5Bt%5D%2C%20R)%0A%20%20%20%20%20%20%20%20constraints%20%2B%3D%20%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20xs%5Bt%20%2B%201%5D%20%3D%3D%20Ad%20%40%20xs%5Bt%5D%20%2B%20Bd%20%40%20us%5Bt%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20cp.abs(xs%5Bt%5D)%20%3C%3D%205.0%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20cp.abs(us%5Bt%5D)%20%3C%3D%201.0%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%5D%0A%20%20%20%20problem%20%3D%20cp.Problem(cp.Minimize(cost)%2C%20constraints)%0A%20%20%20%20return%20problem%2C%20us%2C%20x0%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%20Generating%20the%20solver%0A%0A%20%20%20%20A%20single%20call%20to%20%60generate_solver%60%20runs%20both%20code%20generators.%0A%20%20%20%20CVXPYgen%20first%20reduces%20the%20problem%20to%20the%20quadratic%20program%0A%0A%20%20%20%20%5Cbegin%7Barray%7D%7Bll%7D%0A%20%20%20%20%20%20%20%20%5Ctext%7Bminimize%7D%20%26%20(1%2F2)%20z%5ET%20P%20z%20%2B%20q%5ET%20z%20%5C%5C%0A%20%20%20%20%20%20%20%20%5Ctext%7Bsubject%20to%7D%20%26%20l(x)%20%5Cle%20M%20z%20%5Cle%20u(x)%2C%0A%20%20%20%20%5Cend%7Barray%7D%0A%0A%20%20%20%20where%20%24z%24%20stacks%20the%20states%2C%20the%20inputs%2C%20and%20the%20auxiliary%20variables%20CVXPYgen%20introduces.%0A%20%20%20%20Only%20the%20bounds%20%24l(x)%24%20and%20%24u(x)%24%20depend%20on%20%24x%24%2C%20through%20the%20constraint%20%24s_0%20%3D%20x%24%2C%20so%20%24P%24%2C%20%24q%24%2C%20and%20%24M%24%20are%20fixed%20and%20stored%20in%20the%20generated%20C%20code.%0A%0A%20%20%20%20FOMTools%20then%20records%20one%20iteration%20of%20the%20ADMM%20method%20used%20by%20OSQP%20(%60fom%3D%22admm%22%60%2C%20with%20OSQP's%20default%20parameters)%20on%20this%20quadratic%20program.%0A%20%20%20%20The%20ADMM%20linear%20system%20has%20a%20constant%20matrix%2C%20so%20it%20is%20factored%20once%20at%20generation%20time%20and%20each%20iteration%20only%20needs%20triangular%20solves.%0A%20%20%20%20A%20coding%20agent%20writes%20the%20iteration%20in%20C%2C%20and%20FOMTools%20compiles%20it%20and%20compares%20its%20iterates%20with%20the%20Python%20reference%20for%20several%20values%20of%20%24x%24%20drawn%20from%20the%20box%20in%20%60param_bounds%60%2C%20here%20%24x%20%5Cin%20%5B-1%2C%201%5D%5E%7Bn_s%7D%24.%0A%20%20%20%20If%20the%20comparison%20fails%2C%20the%20agent%20sees%20the%20error%20and%20tries%20again.%0A%20%20%20%20Once%20it%20passes%2C%20CVXPYgen%20builds%20the%20Python%20wrapper%20with%20CMake.%0A%20%20%20%20The%20whole%20step%20takes%20a%20few%20minutes%2C%20most%20of%20it%20spent%20by%20the%20agent.%0A%0A%20%20%20%20The%20solver%20runs%20a%20fixed%20%242000%24%20iterations%20(%60TerminationSpec(max_iters%3D2000)%60).%0A%20%20%20%20The%20iteration%20count%20is%20a%20runtime%20setting%2C%20so%20it%20can%20be%20changed%20later%20without%20regenerating%20the%20code.%0A%20%20%20%20The%20generated%20project%20is%20written%20to%20%60mpc_solver%2F%60%2C%20and%20the%20agent's%20solver%20loop%20is%20in%20%60mpc_solver%2Fc%2Fsolver_code%2Fsrc%2Ffom_solver.c%60.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(CodegenConfig%2C%20TerminationSpec%2C%20agent_available%2C%20fcpg%2C%20problem%2C%20x0)%3A%0A%20%20%20%20if%20agent_available%3A%0A%20%20%20%20%20%20%20%20result%20%3D%20fcpg.generate_solver(%0A%20%20%20%20%20%20%20%20%20%20%20%20problem%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22mpc_solver%22%2C%20%20%23%20relative%20path%3A%20cvxpygen%20imports%20%3Ccode_dir%3E.cpg_solver%0A%20%20%20%20%20%20%20%20%20%20%20%20fom%3D%22admm%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20param_bounds%3D%7Bx0%3A%20(-1.0%2C%201.0)%7D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20config%3DCodegenConfig(termination%3DTerminationSpec(max_iters%3D2000))%2C%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20print(f%22generated%20verified%20solver%20in%20%7Bresult.out_dir%7D%22)%0A%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20result%20%3D%20None%0A%20%20%20%20%20%20%20%20print(%0A%20%20%20%20%20%20%20%20%20%20%20%20%22Skipping%20solver%20generation%3A%20set%20FOMTOOLS_CODEGEN_AGENT%3D1%20(and%20configure%20a%20%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%22coding-agent%20backend)%20to%20generate%20and%20verify%20the%20embedded%20solver.%22%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20return%20(result%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%20Solving%20through%20the%20generated%20code%0A%0A%20%20%20%20%60generate_solver%60%20registers%20the%20generated%20code%20with%20the%20problem%20as%20the%20solve%20method%20%60%22CPG%22%60.%0A%20%20%20%20We%20draw%20a%20new%20initial%20state%20inside%20the%20verified%20box%2C%20solve%20through%20the%20generated%20C%20code%2C%20and%20print%20the%20first%20input%20%24u_0%24%2C%20which%20is%20the%20input%20an%20MPC%20controller%20applies%20before%20re-solving%20at%20the%20next%20time%20step.%0A%20%20%20%20As%20a%20check%2C%20we%20then%20solve%20the%20same%20problem%20with%20OSQP%20at%20tight%20tolerances.%0A%20%20%20%20After%20%242000%24%20iterations%20the%20two%20inputs%20agree%20to%20within%20%2410%5E%7B-10%7D%24.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(agent_available%2C%20cp%2C%20nx%2C%20problem%2C%20result%2C%20rng%2C%20us%2C%20x0)%3A%0A%20%20%20%20if%20agent_available%20and%20result%20is%20not%20None%3A%0A%20%20%20%20%20%20%20%20x0.value%20%3D%20rng.uniform(-0.5%2C%200.5%2C%20size%3Dnx)%0A%20%20%20%20%20%20%20%20problem.solve(method%3D%22CPG%22)%0A%20%20%20%20%20%20%20%20print(%22first%20input%3A%22%2C%20us.value%5B0%5D)%0A%0A%20%20%20%20%20%20%20%20problem.solve(solver%3Dcp.OSQP%2C%20eps_abs%3D1e-8%2C%20eps_rel%3D1e-8)%0A%20%20%20%20%20%20%20%20print(%22OSQP%20check%20%3A%22%2C%20us.value%5B0%5D)%0A%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20print(%22Skipping%20solve%3A%20no%20generated%20CPG%20solver%20(see%20the%20previous%20cell).%22)%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
9c0f04a2dc684a9664dd73b617b068d4