Package: cvxpygen
CVXPYgen - Code generation with CVXPYCVXPYgen takes a convex optimization problem family modeled with CVXPY and generates a custom solver implementation in C.
This generated solver is specific to the problem family and accepts different parameter values.
In particular, this solver is suitable for deployment on embedded systems.
In addition, CVXPYgen creates a Python wrapper for prototyping and desktop (non-embedded) applications.An overview of CVXPYgen can be found in ourmanuscript.CVXPYgen accepts CVXPY problems that are compliant withDisciplined Convex Programming (DCP).
DCP is a system for constructing mathematical expressions with known curvature from a given library of base functions.
CVXPY uses DCP to ensure that the specified optimization problems are convex.
In addition, problems need to be modeled according toDisciplined Parametrized Programming (DPP).
Solving a DPP-compliant problem repeatedly for different values of the parameters can be much faster than repeatedly solving a new problem.For now, CVXPYgen is a separate module, until it will be integrated into CVXPY.
As of today, CVXPYgen works with linear, quadratic, and second-order cone programs.This package has similar functionality as the packagecvxpy_codegen,
which appears to be unsupported.Important: When generating code with the ECOS solver, the generated code is licensed
under theGNU General Public License v3.0.InstallationInstallcvxpygen(on Windows preferably withPython 3.9).pip install cvxpygenOn Linux or Mac, install theGCC compiler.
On Windows, installMicrosoft Visual Studiowith the 'Desktop development with C++' workload.
CVXPYgen is tested with Visual Studio 2019 and 2022, older versions might work as well.Optional:If you wish to use the example notebooks located inexamples/,
installipykernel,jupyter,matplotlib, and register a new kernel spec with Jupyter.pip install ipykernel jupyter matplotlib
ipython kernel install --user --name=cvxpygenIf you wish to use theClarabelsolver, you need to installRustandEigen.ExampleWe define a simple 'nonnegative least squares' problem, generate code for it, and solve the problem with example parameter values.1. Generate CodeLet's step through the first part ofexamples/main.py.
Define a convex optimization problem the way you are used to with CVXPY.
Everything that is described ascp.Parameter()is assumed to be changing between multiple solves.
For constant properties, usecp.Constant().importcvxpyascpm,n=3,2x=cp.Variable(n,name='x')A=cp.Parameter((m,n),name='A',sparsity=[(0,0),(0,1),(1,1)])b=cp.Parameter(m,name='b')problem=cp.Problem(cp.Minimize(cp.sum_squares(A@x-b)),[x>=0])Specify thenameattribute for variables and parameters to recognize them after generating code.
The attributesparsityis a list of 2-tuples that indicate the coordinates of nonzero entries of matrixA.
Parameter sparsity is only taken into account for matrices.Assign parameter values and test-solve.importnumpyasnpnp.random.seed(0)A.value=np.zeros((m,n))A.value[0,0]=np.random.randn()A.value[0,1]=np.random.randn()A.value[1,1]=np.random.randn()b.value=np.random.randn(m)problem.solve()Generating C code for this problem is as simple as,fromcvxpygenimportcpgcpg.generate_code(problem,code_dir='nonneg_LS',solver='SCS')where the generated code is stored insidenonneg_LSand theSCSsolver is used.
Next to the positional argumentproblem, all keyword arguments for thegenerate_code()method are summarized below.ArgumentMeaningTypeDefaultcode_dirdirectory for code to be stored inString'CPG_code'solvercanonical solver to generate code withStringCVXPY defaultsolver_optsoptions passed to canonical solverDictNoneenable_settingsenabled settings that are otherwise locked by embedded solverList of Strings[]unrollunroll loops in canonicalization codeBoolFalseprefixprefix for unique code symbols when dealing with multiple problemsString''wrappercompile Python wrapper for CVXPY interfaceBoolTrueYou can find an overview of the code generation result innonneg_LS/README.html.2. Solve & CompareAs summarized in the second part ofexamples/main.py, after assigning parameter values, you can solve the problem both conventionally and via the generated code, which is wrapped inside the custom CVXPY solve methodcpg_solve.importtimeimportsys# import extension module and register custom CVXPY solve methodfromnonneg_LS.cpg_solverimportcpg_solveproblem.register_solve('cpg',cpg_solve)# solve problem conventionallyt0=time.time()val=problem.solve(solver='SCS')t1=time.time()sys.stdout.write('\nCVXPY\nSolve time:%.3fms\n'%(1000*(t1-t0)))sys.stdout.write('Primal solution: x = [%.6f,%.6f]\n'%tuple(x.value))sys.stdout.write('Dual solution: d0 = [%.6f,%.6f]\n'%tuple(problem.constraints[0].dual_value))sys.stdout.write('Objective function value:%.6f\n'%val)# solve problem with C code via python wrappert0=time.time()val=problem.solve(method='cpg',updated_params=['A','b'],verbose=False)t1=time.time()sys.stdout.write('\nCVXPYgen\nSolve time:%.3fms\n'%(1000*(t1-t0)))sys.stdout.write('Primal solution: x = [%.6f,%.6f]\n'%tuple(x.value))sys.stdout.write('Dual solution: d0 = [%.6f,%.6f]\n'%tuple(problem.constraints[0].dual_value))sys.stdout.write('Objective function value:%.6f\n'%val)The argumentupdated_paramsspecifies which user-defined parameter values are new.
If the argument is omitted, all parameter values are assumed to be new.
If only a subset of the user-defined parameters have new values, use this argument to speed up the solver.Most solver settings can be specified as keyword argumentslike without code generation.
Here, we useverbose=Falseto suppress printing.
The list of changeable settings differs by solver and is documented in<code_dir>/README.htmlafter code generation.Comparing the standard and codegen methods for this example, both the solutions and objective values are close.
Especially for smaller problems like this, the new solve method'cpg'is significantly faster than solving without code generation.3. ExecutableIn the C code, all of your parameters and variables are stored as vectors via Fortran-style flattening (vertical index moves fastest).
For example, the(i, j)-th entry of the original matrix with heighthwill be thei+j*h-th entry of the flattened matrix in C.
For sparseparameters, i.e. matrices, thek-th entry of the C array is thek-th nonzero entry encountered when proceeding
through the parameter column by column.Before compiling the example executable, make sure thatCMake 3.5or newer is installed.On Unix platforms, run the following commands in your terminal to compile and run the program:cdnonneg_LS/c/build
cmake..
cmake--build.--targetcpg_example
./cpg_exampleOn Windows, type:cdnonneg_LS\c\build
cmake..
cmake--build.--targetcpg_example--configrelease
Release\cpg_example