Coder Social home page Coder Social logo

cvxpygen's Introduction

CVXPYgen - Code generation with CVXPY

CVXPYgen 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 our manuscript.

CVXPYgen accepts CVXPY problems that are compliant with Disciplined 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 to Disciplined 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 package cvxpy_codegen, which appears to be unsupported.

Important: When generating code with the ECOS solver, the generated code is licensed under the GNU General Public License v3.0.

Installation

  1. Install cvxpygen (on Windows preferably with Python 3.9).

    pip install cvxpygen
    
  2. On Linux or Mac, install the GCC compiler. On Windows, install Microsoft Visual Studio with the 'Desktop development with C++' workload. CVXPYgen is tested with Visual Studio 2019 and 2022, older versions might work as well.

  3. Optional: If you wish to use the example notebooks located in examples/, install ipykernel, jupyter, matplotlib, and register a new kernel spec with Jupyter.

    pip install ipykernel jupyter matplotlib
    ipython kernel install --user --name=cvxpygen
    

If you wish to use the Clarabel solver, you need to install Rust and Eigen.

Example

We define a simple 'nonnegative least squares' problem, generate code for it, and solve the problem with example parameter values.

1. Generate Code

Let's step through the first part of examples/main.py. Define a convex optimization problem the way you are used to with CVXPY. Everything that is described as cp.Parameter() is assumed to be changing between multiple solves. For constant properties, use cp.Constant().

import cvxpy as cp

m, n = 3, 2
x = 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 the name attribute for variables and parameters to recognize them after generating code. The attribute sparsity is a list of 2-tuples that indicate the coordinates of nonzero entries of matrix A. Parameter sparsity is only taken into account for matrices.

Assign parameter values and test-solve.

import numpy as np

np.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,

from cvxpygen import cpg

cpg.generate_code(problem, code_dir='nonneg_LS', solver='SCS')

where the generated code is stored inside nonneg_LS and the SCS solver is used. Next to the positional argument problem, all keyword arguments for the generate_code() method are summarized below.

Argument Meaning Type Default
code_dir directory for code to be stored in String 'CPG_code'
solver canonical solver to generate code with String CVXPY default
solver_opts options passed to canonical solver Dict None
enable_settings enabled settings that are otherwise locked by embedded solver List of Strings []
unroll unroll loops in canonicalization code Bool False
prefix prefix for unique code symbols when dealing with multiple problems String ''
wrapper compile Python wrapper for CVXPY interface Bool True

You can find an overview of the code generation result in nonneg_LS/README.html.

2. Solve & Compare

As summarized in the second part of examples/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 method cpg_solve.

import time
import sys

# import extension module and register custom CVXPY solve method
from nonneg_LS.cpg_solver import cpg_solve
problem.register_solve('cpg', cpg_solve)

# solve problem conventionally
t0 = time.time()
val = problem.solve(solver='SCS')
t1 = time.time()
sys.stdout.write('\nCVXPY\nSolve time: %.3f ms\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 wrapper
t0 = time.time()
val = problem.solve(method='cpg', updated_params=['A', 'b'], verbose=False)
t1 = time.time()
sys.stdout.write('\nCVXPYgen\nSolve time: %.3f ms\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 argument updated_params specifies 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 arguments like without code generation. Here, we use verbose=False to suppress printing. The list of changeable settings differs by solver and is documented in <code_dir>/README.html after 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. Executable

In 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 height h will be the i+j*h-th entry of the flattened matrix in C. For sparse parameters, i.e. matrices, the k-th entry of the C array is the k-th nonzero entry encountered when proceeding through the parameter column by column.

Before compiling the example executable, make sure that CMake 3.5 or newer is installed.

On Unix platforms, run the following commands in your terminal to compile and run the program:

cd nonneg_LS/c/build
cmake ..
cmake --build . --target cpg_example
./cpg_example

On Windows, type:

cd nonneg_LS\c\build
cmake ..
cmake --build . --target cpg_example --config release
Release\cpg_example

Tests

To run tests, install pytest via

conda install pytest

and execute:

cd tests
pytest

cvxpygen's People

Contributors

akshayka avatar ghorn avatar jpreiss avatar maximilianschaller avatar maxschaller avatar phschiele avatar stevediamond avatar syanga avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

cvxpygen's Issues

How to change the values of a cp.Parameter in the c code?

As the question title suggests, I would like to reuse the same code to solve a family of problems, changing only the values of a few parameters defined via cp.Parameter. Do you think this could be possible?
I looked at the MPC example, where x0, A, B are parameters, but I don't know what to do to call the c code with updated values (e.g. updated initial conditions of the MPC)

ECOS fails when solving second time

Hey,

I've written a simple shortest path problem with constraints. When I compile the program with ECOS, the program is able to find a solution the first time it's queried. However, the second time it fails, even if the input parameters are the same. It seems to work fine if I run with SCS. The program that I compile is presented below.

def compile_optimization_program(self):
    m, n, w = self.m, self.n, self.w
    self.centers = cp.Parameter((m * n, w), name="centers")
    self.radiis = cp.Parameter((m * n), name="radiis")
    self.p_start = cp.Parameter((w, ), name="p_start")
    self.p_end = cp.Parameter((w,), name="p_end")
    self.Ps_stacked = cp.Variable((m * n, w), name=f"Ps")
    objective = cp.norm(self.Ps_stacked[1:] - self.Ps_stacked[:-1], axis=1).sum()
    const = (
            [
                cp.norm(self.Ps_stacked - self.centers, axis=1) <= self.radiis
            ]
            +
            [
                self.Ps_stacked[i-1] == self.Ps_stacked[i] for i in range(n, n*m, n)
            ]
            +
            [
                self.Ps_stacked[0] == self.p_start,
                self.Ps_stacked[-1] == self.p_end
            ]
    )
    self.problem = cp.Problem(cp.Minimize(objective), constraints=const)

I'm running python 3.10.12 and cmake 3.29.0.

Complete code:
bug.zip

Example not working

Hi. I tried to follow the example on the website, and I got the following error. Any help would be greatly appreciated.

Generating code with CVXPYgen ...
CVXPYgen finished generating code.
Compiling python wrapper with CVXPYgen ...
-- The C compiler identification is GNU 12.2.0
-- The CXX compiler identification is GNU 12.2.0
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working C compiler: /mnt/disks/condaman/mamba/bin/cc - skipped
-- Detecting C compile features
-- Detecting C compile features - done
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: /mnt/disks/condaman/mamba/bin/c++ - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Setting build type to 'Release' as none was specified.
-- Single precision floats (32bit) are OFF
-- Long integers (64bit) are OFF
-- COMPILER_OPTS = -DUSE_LAPACK -DCTRLC
-- Configuring done (0.3s)
-- Generating done (0.0s)
-- Build files have been written to: /home/james_c_pinkerton/src/california/nonneg_LS/c/build
[  0%] Building C object CMakeFiles/cpg.dir/src/cpg_workspace.c.o
[  3%] Building C object CMakeFiles/cpg.dir/src/cpg_solve.c.o
[  6%] Building C object CMakeFiles/cpg.dir/solver_code/src/aa.c.o
[ 10%] Building C object CMakeFiles/cpg.dir/solver_code/src/cones.c.o
[ 13%] Building C object CMakeFiles/cpg.dir/solver_code/src/ctrlc.c.o
[ 17%] Building C object CMakeFiles/cpg.dir/solver_code/src/linalg.c.o
[ 20%] Building C object CMakeFiles/cpg.dir/solver_code/src/normalize.c.o
[ 24%] Building C object CMakeFiles/cpg.dir/solver_code/src/rw.c.o
[ 27%] Building C object CMakeFiles/cpg.dir/solver_code/src/scs.c.o
[ 31%] Building C object CMakeFiles/cpg.dir/solver_code/src/scs_version.c.o
[ 34%] Building C object CMakeFiles/cpg.dir/solver_code/src/util.c.o
[ 37%] Building C object CMakeFiles/cpg.dir/solver_code/linsys/csparse.c.o
[ 41%] Building C object CMakeFiles/cpg.dir/solver_code/linsys/scs_matrix.c.o
[ 44%] Building C object CMakeFiles/cpg.dir/solver_code/linsys/cpu/direct/private.c.o
[ 48%] Building C object CMakeFiles/cpg.dir/solver_code/linsys/external/qdldl/qdldl.c.o
[ 51%] Building C object CMakeFiles/cpg.dir/solver_code/linsys/external/amd/SuiteSparse_config.c.o
[ 55%] Building C object CMakeFiles/cpg.dir/solver_code/linsys/external/amd/amd_1.c.o
[ 58%] Building C object CMakeFiles/cpg.dir/solver_code/linsys/external/amd/amd_2.c.o
[ 62%] Building C object CMakeFiles/cpg.dir/solver_code/linsys/external/amd/amd_aat.c.o
[ 65%] Building C object CMakeFiles/cpg.dir/solver_code/linsys/external/amd/amd_control.c.o
[ 68%] Building C object CMakeFiles/cpg.dir/solver_code/linsys/external/amd/amd_defaults.c.o
[ 72%] Building C object CMakeFiles/cpg.dir/solver_code/linsys/external/amd/amd_dump.c.o
[ 75%] Building C object CMakeFiles/cpg.dir/solver_code/linsys/external/amd/amd_global.c.o
[ 79%] Building C object CMakeFiles/cpg.dir/solver_code/linsys/external/amd/amd_info.c.o
[ 82%] Building C object CMakeFiles/cpg.dir/solver_code/linsys/external/amd/amd_order.c.o
[ 86%] Building C object CMakeFiles/cpg.dir/solver_code/linsys/external/amd/amd_post_tree.c.o
[ 89%] Building C object CMakeFiles/cpg.dir/solver_code/linsys/external/amd/amd_postorder.c.o
[ 93%] Building C object CMakeFiles/cpg.dir/solver_code/linsys/external/amd/amd_preprocess.c.o
[ 96%] Building C object CMakeFiles/cpg.dir/solver_code/linsys/external/amd/amd_valid.c.o
[100%] Linking C static library out/libcpg.a
Error running link command: No such file or directory
make[3]: *** [CMakeFiles/cpg.dir/build.make:546: out/libcpg.a] Error 2
make[2]: *** [CMakeFiles/Makefile2:131: CMakeFiles/cpg.dir/all] Error 2
make[1]: *** [CMakeFiles/Makefile2:138: CMakeFiles/cpg.dir/rule] Error 2
make: *** [Makefile:170: cpg] Error 2
/mnt/disks/condaman/mamba/compiler_compat/ld: cannot find /home/nonneg_LS/c/build/out/libcpg.a: No such file or directory
collect2: error: ld returned 1 exit status
error: command '/mnt/disks/condaman/mamba/bin/g++' failed with exit code 1
CVXPYgen finished compiling python wrapper.

Encountering challenges when integrating cvxpygen into existing projects.

I'm having difficulty integrating cvxpygen into my existing project. I'm unsure how to utilize the generated code in my established project.

The compiler complains about missing files. I suspect the issue lies in my CMakeLists.txt file:

CMake

cmake_minimum_required(VERSION 3.0.0)
project(mpc)

include(CTest)
enable_testing()

set(CPACK_PROJECT_NAME ${PROJECT_NAME})
set(CPACK_PROJECT_VERSION ${PROJECT_VERSION})
include(CPack)

add_subdirectory(mpc-EST)
include_directories(include ${cpg_include})

add_executable(mpc main.c ${cpg_head} ${cpg_src})

My main.c file is simply meant to print the objective function value to verify the code's functionality:

C

#include <stdio.h>
#include "cpg_workspace.h"
#include "cpg_solve.h"

int main() {
  printf("Hello, World!\n");

  // Solve the problem instance
  cpg_solve();

  // Print objective function value
  printf("obj = %f\n", CPG_Result.info->obj_val);

  return 0;
}

Thank you for your assistance.

Scalar parameter/variable support

Code generation seems to fail for scalar parameters and variables, as opposed to array parameters/variables. For example, suppose we have something like
w_a = cp.Parameter(1,name='w_a')
a = cp.Variable(1,name='a')

objective = cp.Minimize(w*a)

This will generate code with errors in both the example and cpg_solve, since the code seems to assume each parameter and variable is an array. The cpg_example will call cpg_update_w(0, w_value), but the cpg_update_w function only accepts one argument. Similarly, cpg_solve->cpg_retrieve_prim() will attempt to assign CPG_Prim.a = ecos_workspace[k], which will throw an error: incompatible types when assigning to type ‘c_float *’ {aka ‘double *’} from type ‘pfloat’ {aka ‘double’}

Is there a simple way to deal with this (e.g. some scalar=True flag)? A workaround might be to make all scalars arrays of length 2 and only use the first index, but obviously that's not desirable.

Edit:
A better reproduction is to set n=1 in the network.ipynb example. This will give the same kind of error.

CPG Solver Produces 'primal infeasible' Solution for Conventionally Solvable Problem

Thanks for developing this great project.

When using the CPG solver from the cvxpygen project on a valid optimization problem, the solver reports a 'primal infeasible' solution, while the conventional solver produces an optimal solution.

Minimal example reproducing the problem

import cvxpy as cp
import numpy as np
import sys
import os
import pickle
from cvxpygen import cpg

# define var & param
n = 7
var_0 = cp.Variable(n, name="var_0")
var_1 = cp.Variable(n, name="var_1")
param_0 = cp.Parameter(n, name="param_0")
param_1 = cp.Parameter(n, name="param_1")
param_2 = cp.Parameter(n, nonneg=True, name="param_2")
param_3 = cp.Parameter(n, name="param_3")
param_4 = cp.Parameter(n, name="param_4")
param_5 = cp.Parameter((n, n), name="param_5")

# define objective
objective = cp.Maximize(param_0.T @ var_0 - param_2 @ cp.abs(var_1) - cp.sum_squares((3 * param_5) @ var_0))

# define constraints
constraints = []
constraints.append(var_0 == param_1 + var_1)
constraints.append(cp.abs(cp.sum(var_0)) <= 2)
constraints.append(var_1 <= param_3)
constraints.append(var_1 >= -param_4)
for i in range(n):
    constraints.append(var_0[i] <= 1)
    constraints.append(var_0[i] >= -1)

# define problem
problem = cp.Problem(objective, constraints)

# generate C source
assert os.path.isfile(f"{os.getcwd()}/__init__.py"), "Must run this in the root of a packege. e.g., $ mkdir -p /tmp/foo && touch /tmp/foo/__init__.py "
sys.path.insert(0, os.getcwd()) # manually add the path for safety
cpg.generate_code(problem, code_dir='my_test', solver='OSQP', wrapper=True)
from my_test.cpg_solver import cpg_solve
del problem # to avoid mistaken using

# load the problem
with open("my_test/problem.pickle", "rb") as f:
    prob = pickle.load(f)

# assign param value
def make_array(shape):
    return np.arange(0, np.prod(shape), dtype='<f8').reshape(shape)

prob.param_dict['param_0'].value = make_array([n])
prob.param_dict['param_1'].value = make_array([n])
prob.param_dict['param_2'].value = make_array([n])
prob.param_dict['param_3'].value = make_array([n])
prob.param_dict['param_4'].value = make_array([n])
prob.param_dict['param_5'].value = make_array([n, n])

# solve problem conventionally
solver_params={"solver": "OSQP", "enforce_dpp": True, "max_iter": 10000, "eps_abs": 1e-6, "eps_rel": 1e-6}
prob.solve(**solver_params)
print("Conventional:")
print(dict(status=prob.solution.status, opt_val=prob.solution.opt_val, num_iters=prob.solution.attr["num_iters"]))

# solve using CPG
solver_params.pop('solver')
solver_params.pop('enforce_dpp')
prob.register_solve('CPG', cpg_solve)
prob.solve(method='CPG', **solver_params)
print("\nCPG:")
print(dict(status=prob.solution.status, opt_val=prob.solution.opt_val, num_iters=prob.solution.attr["num_iters"]))

Output

...
Conventional:
{'status': 'optimal', 'opt_val': -90.9993696512673, 'num_iters': 7500}

CPG:
{'status': 'dual infeasible', 'opt_val': inf, 'num_iters': 125}

Version

cvxpygen 0.3.1
cvxpy 1.3.1
osqp 0.6.3

What I tried

I made a private build of osqp which prints the workspace inside osqp_solve. Turned out the only differences are the signs of some fields in A, l, u. For example, for A.x:

...
    Numerical Values (x):
      0: 0.9195
      1: 0.9426
      2: 0.9599
      3: 0.9733
      4: 0.9839
      5: 0.9927
      6: 0.9999
      7: 0.0665
 -    8: -0.0665
 -    9: 0.0665
 -    10: -0.9953
 -    11: 0.9953
 +    8: 0.0665
 +    9: -0.0665
 +    10: 0.9953
 +    11: -0.9953
      12: 0.9336
      13: 0.9527
      14: 0.9669
      15: 0.9779
      16: 0.9868
      17: 0.9939
      18: 0.9999
      19: 0.0657
 -    20: -0.0657
 -    21: 0.0657
 -    22: -0.9952
 -    23: 0.9952
 +    20: 0.0657
 +    21: -0.0657
 +    22: 0.9952
 +    23: -0.9952
...

It would be highly appreciated if anyone can have a look when getting a moment. Thanks.

AttributeError while running examples/main.py

After installing cvxpy and cvxpygen using pip on Ubuntu 20.04, with GCC version 9.4.0, I tried to run the example but got the following error:

Generating code with CVXPYgen ...
Traceback (most recent call last):
File "cvxpygen_test.py", line 32, in
cpg.generate_code(problem, code_dir='nonneg_LS', solver='SCS')
File "/home/jonne/.local/lib/python3.8/site-packages/cvxpygen/cpg.py", line 274, in generate_code
indices_constr, indptr_constr, shape_constr = p_prob.problem_data_index
AttributeError: 'ParamConeProg' object has no attribute 'problem_data_index'

I got a similar error when using the OSQP solver

Generating code with CVXPYgen ...
Traceback (most recent call last):
File "cvxpygen_test.py", line 32, in
cpg.generate_code(problem, code_dir='nonneg_LS', solver='OSQP')
File "/home/jonne/.local/lib/python3.8/site-packages/cvxpygen/cpg.py", line 259, in generate_code
indices_obj, indptr_obj, shape_obj = p_prob.problem_data_index_P
AttributeError: 'ParamQuadProg' object has no attribute 'problem_data_index_P'

Does this error get reproduced for others? Thanks

`generate_code` raises IndexError when `solver="CLARABEL"`

Hello, I'm trying to use cvxpygen but running into an error when I try using CLARABEL as a solver.

Minimal example

import cvxpy as cp
from cvxpygen import cpg
import numpy as np

# define CVXPY problem
m, n = 3, 2
x = 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])

# assign parameter values and test-solve
np.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(solver='CLARABEL')

# generate code
cpg.generate_code(problem, code_dir='nonneg_LS', solver='CLARABEL')

Output

Generating code with CVXPYgen ...
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/Users/kunalmenda/anaconda3/envs/test/lib/python3.10/site-packages/cvxpygen/cpg.py", line 155, in generate_code
    for dual_id in dual_id_maps[0].keys():
IndexError: list index out of range

Is there any way to avoid this error?

Thanks,
Kunal

How to implement the LTV MPC in the CVXPYGEN

Hi, first of all, sorry for creating this on the issue section, I think rather than an issue this should be a discussion.

I implemented the LTV-MPC with CVXPY, and I have the intention to generate the code using CVXPYGEN for deploying the program to the real hardware.

In the CVXPY we can do for loop to assign the constraints of the LTV system for each horizon

# My program with CVXPY
for t in range(self.N - 1):
      cost += cp.quad_form(self.u[:, t], R)
  
      if t != 0:
          cost += cp.quad_form(xref[:, t] - self.x[:, t], Q)
  
      self.A, self.B, self.C = self.get_linear_model(
          xbar[2, t], xbar[3, t], dref[0, t])
      constraints += [self.x[:, t + 1] == self.A @ self.x[:, t] + self.B @ self.u[:, t] + self.C]
  
      if t < (self.N - 2):
          cost += cp.quad_form(self.u[:, t + 1] - self.u[:, t], Rd)
          constraints += [cp.abs(self.u[1, t + 1] - self.u[1, t]) <=
                          self.MAX_ACT * self.DT]

Nevertheless, I encounter a problem in which I am having difficulties converting my optimization problem to DDP Compliant.

Let's say we have an objective function like this

image

I have tried to look into the example of MPC with CVXPYGEN but it is only for the LTI MPC, I am wondering how to change the for loop like my code above to assign each constraint to match the DDP Compliant in the CVXPYGEN

Thanks!

generate_code raises TypeError

generate_code() raises a TypeError on an inexistent dictionary entry when called.

Python: 3.11.3
cvxpy: 1.4.1
cvxpygen: 0.3.1

Generating code with CVXPYgen ...

{
	"name": "TypeError",
	"message": "'NoneType' object is not subscriptable",
	"stack": "---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
c:\\Users\\redacted\\codegen\\EPO.ipynb Cell 24 line 3
      <a href='vscode-notebook-cell:/c%3A/Users/redacted/codegen/EPO.ipynb#X30sZmlsZQ%3D%3D?line=0'>1</a> from cvxpygen import cpg
----> <a href='vscode-notebook-cell:/c%3A/Users/redacted/codegen/EPO.ipynb#X30sZmlsZQ%3D%3D?line=2'>3</a> cpg.generate_code(problem, code_dir='./gen', solver='OSQP')

File ~\\.pyenv\\pyenv-win\\versions\\3.11.3\\Lib\\site-packages\\cvxpygen\\cpg.py:69, in generate_code(problem, code_dir, solver, solver_opts, enable_settings, unroll, prefix, wrapper)
     67 variable_info = get_variable_info(problem, inverse_data)
     68 dual_variable_info = get_dual_variable_info(inverse_data, solver_interface, cvxpy_interface_class)
---> 69 parameter_info = get_parameter_info(param_prob)
     70 constraint_info = get_constraint_info(solver_interface)
     72 adjacency, parameter_canon, canon_p_ids = process_canonical_parameters(
     73     constraint_info, param_prob, parameter_info, solver_interface, solver_opts, problem, cvxpy_interface_class
     74 )

File ~\\.pyenv\\pyenv-win\\versions\\3.11.3\\Lib\\site-packages\\cvxpygen\\cpg.py:466, in get_parameter_info(p_prob)
    462     return np.array(user_p_id_to_param[user_p_id].value)
    464 user_p_flat = cI.get_parameter_vector(user_p_total_size, user_p_id_to_col, user_p_id_to_size,
    465                                       user_p_value)
--> 466 user_p_flat_usp = user_p_flat[user_p_sparsity_mask]
    467 parameter_info = ParameterInfo(user_p_col_to_name_usp, user_p_flat_usp, user_p_id_to_col,
    468                                user_p_ids, user_p_name_to_shape, user_p_name_to_size_usp,
    469                                user_p_name_to_sparsity, user_p_name_to_sparsity_type,
    470                                user_p_names, user_p_num, user_p_sparsity_mask, user_p_writable)
    471 return parameter_info

TypeError: 'NoneType' object is not subscriptable"
}

Adding new solvers is extremely difficult

Thank you for developing this software, it is extremely interesting and fast.
One issue which I found was that if someone wants to add a new solver, the process is extremely difficult. You have to manually add to the elif statements in the generate_code function of the cpg script, and in the utils script. This is over 2400 lines of code, and is a long process.
It would be great if adding a new solver would be an easier process, and if more solvers would be supported.

Thank you!!!
Leo

generate code failed TypeError: 'NoneType' object is not subscriptable

Hello everyone,
this is my first time asking for help here. please kindly tell me if the problem is not clear enough.
I am trying to test cvxpygen for simple Markowitz optimisation problem.
Objective is to minimise portfolio variance with constraint on minimum return.

function to get portfolio return

ret = (np.array(expected_return) @ w )* annual # portfolio expected return as weighted sum of component expected return

function to get portfolio variance

port_var = cp.quad_form(w, cov) * annual #portfolio variance
constraints = [cp.sum(w) == 1, # sum of weights = 1
w <= 1, # weights <= 1
ret >= ret_target_min # min return]

define problems

prob = cp.Problem(cp.Minimize(port_var), constraints)

Using CVXPY I was able to get result
result_opt = prob.solve(verbose=False, eps_abs=1e-08)

trying with CVXPYGen

from cvxpygen import cpg
cpg.generate_code(prob, "nonneg_LS")
from nonneg_LS.cpg_solver import cpg_solve
prob.register_solve("cpg",cpg_solve)
val= prob.solve()

my code failed right at generating problem stage, with following error:
./lib/python3.11/site-packages/cvxpygen/cpg.py", line 252, in generate_code
user_p_flat_usp = user_p_flat[user_p_sparsity_mask]
~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^
TypeError: 'NoneType' object is not subscriptable
apparently user_p_flat is None

Any help is much appreciated.
Many thanks,

Import Error

Thank you for creating this repository! I was keen on trying it out, but I always get an import error in line 12
from nonneg_LS import cpg_module of the file cpg_solver.py

As far as I could track the error down, it seems like the Pybind rountine isn't working, as the cpg_module is generated from the C++ files.

Has anyone else had this error? It looks like a common Python problem but I don't seem to find a solution.

Thanks in advance

Suggestions for improvement

Thank you for developing this very useful package, which helps me improve the performance of my code. However, I hope it can support more solvers, such as MOSEK. Thank you very much for your work.

Code generation with OSQP fails

Code generation with OSQP fails with error "undefined symbol: osqp_update_verbose". It seems that osqp_update_verbose is not defined in osqp.h when EMBEDDED is set. Can anyone fix this? Thanks!

Would you eventually consider generating code in rust?

I think the C code can be wrapped in a rust model with FFI, but for use in a rust program it would be easier/safer to have a rust module/crate directly. Clarabel.rs could be used directly as well.

Perhaps could add the ability to (optionally) generate bindings for other languages, just as Python is generated automatically currently, like Julia, Rust, etc.

Regression with latest upgrade to cvxpy 1.4.0

- cvxpy>=1.3

It appears as though the latest bump to cvxpy (1.3.2 -> 1.4.0) has introduced a regression in cvxpygen (tested on v0.2.3) (presumably due to use of its non-public API). In my particular case this manifests as a problem always being dual infeasible (where previously the osqp backend would solve successfully).

Would cvxpygen be open to pinning the cvxpy dependency to an exact version to avoid problems such as these (on new cvxpy releases)?

To circumvent this issue - I have simply pinned both cvxpy & cvxpygen explicitly.

Default solver is not setup in configuration

Trying to generate C code fails when it tries to write the README.html file.
the variable configuration.solve_name has not been initialised and is None, resulting in a error when it tries to replace the HTML string "$CPGSOLVERNAME" with configuration.solver_name.

Pasted below is the trace:

dist.fetch_build_eggs(dist.setup_requires)
[done]
Copying code-generated Python solver to current directory... 	[done]
Traceback (most recent call last):
  File "/home/Inspecity/Codes/RPOS_control/src/MPC_CloseRange.py", line 70, in <module>
    cpg.generate_code(problem, code_dir="MPC_CloseRange")
  File "/home/Inspecity/Codes/Vision/.venv/lib/python3.11/site-packages/cvxpygen/cpg.py", line 85, in generate_code
    write_c_code(problem, configuration, variable_info, dual_variable_info,
  File "/home/Inspecity/Codes/Vision/.venv/lib/python3.11/site-packages/cvxpygen/cpg.py", line 390, in write_c_code
    read_write_file(os.path.join(configuration.code_dir, 'README.html'),
  File "/home/Inspecity/Codes/Vision/.venv/lib/python3.11/site-packages/cvxpygen/utils.py", line 28, in read_write_file
    data = function(data, *args)
           ^^^^^^^^^^^^^^^^^^^^^
  File "/home/Inspecity/Codes/Vision/.venv/lib/python3.11/site-packages/cvxpygen/utils.py", line 1379, in replace_html_data
    text = text.replace('$CPGSOLVERNAME', configuration.solver_name)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
TypeError: replace() argument 2 must be str, not None

Document updatable solver attributes

It is possible to pass solver settings at execution time. However, not all settings are supported for each solver, e.g., due to embeddability limits. While the auto-generated <code_dir>/README.html describes how to perform solver setting updates at execution time, we should add info on which settings can be updated.

Solve time using C code with python wrapper varies based on the chosen solver

I'm not sure if this is an issue with cvxpygen or just a discussion on the pros/cons of the different solvers.

I noticed that the solve times when solving the problem with C code via the python wrapper varied significantly depending on the solver and sometimes the problem.
Sometimes, this resulted in a slow-down instead of a speed-up. So, I thought I'd document the results here.
I used the MPC.ipynb as a benchmark. I have some additional observations on other problem instances at the end.

Changes to MPC.ipynb:

  • Added a SOLVER variable which I set to cp.ECOS, cp.OSQP, ...
  • val = problem.solve() changed to val = problem.solve(solver=SOLVER)
  • cpg.generate_code(problem, code_dir='MPC_code') changed to cpg.generate_code(problem, code_dir='MPC_code', solver=SOLVER)
  • val = prob.solve(eps_abs=1e-3, eps_rel=1e-3, max_iter=4000, polish=False) changed to val = prob.solve()

I am using cvxpygen 0.3.2, cvxpy 1.4.1, and Python 3.10.13.
The solver versions are listed below.

ECOS 2.0.12

CVXPY
Solve time: 9.618 ms
Objective function value: 134.722750


CVXPYgen
Solve time: 3.264 ms
Objective function value: 134.722740

OSQP 0.6.3

CVXPY
Solve time: 8.780 ms
Objective function value: 134.722750


CVXPYgen
Solve time: 0.828 ms
Objective function value: 134.703097

CLARABEL 0.6.0

CVXPY
Solve time: 9.794 ms
Objective function value: 134.722750


CVXPYgen
Solve time: 19.171 ms
Objective function value: 134.722745

Note: I tried passing verbose=False to prob.solve(method='CPG') but it didn't change the results much

SCS 3.2.4.post1

CVXPY
Solve time: 6.914 ms
Objective function value: 134.722750


CVXPYgen
Solve time: 1.139 ms
Objective function value: 134.722684

Other observations

I have also tried other problems such as a simple single integrator MPC with a Gaussian chance constraint (reformulated into a deterministic problem) and the distributionally robust portfolio optimization problem in eq. (27) from "Data-driven distributionally robust optimization using the Wasserstein metric: performance guarantees and tractable reformulations".

  • I have noticed that calling the C code generated using Clarabel is slower than solving the problem with Clarabel directly with cvxpy (as demonstrated above). This sometimes results in an order of magnitude slow-down when using the C code with the Python wrapper.
  • I have noticed mixed performance with SCS where on some problems calling the C code is faster but for other problems it is slower.
  • The performance of ECOS is good, but currently, per Issue #45 , it is unusable for any problems involving repeated solves since the results are wrong for all but the initial solution.

Compiled Clarabel code sometimes producing wrong results compared to cvxpy only

Hi,

I noticed that the cvxpygen C code, with the python wrapper, may produce some wrong results even when solving with cvxpy works fine.
I say may because I've noticed that this behavior is not consistent and varies based on the problem.
I also think that this is related to #45 and #46 (and the associated commits).

Here are my observations. Note that in all cases, using cvxpy works fine.

Observations

MPC.ipynb benchmark

  • with cvxpygen v0.3.2:
    • ECOS produces a wrong result on the second run.
    • CLARABEL, OSQP, SCS work (even on the second run)
      This was resolved through #45 and now with cvxpygen v0.3.3 or v0.3.4 all four solvers produce correct results even on the second run.

mpc_2d_robot benchmark (see code below)

  • with cvxpygen 0.3.2:
    • ECOS fails (again, this was addressed in #45) (control value jumps between max/min)
    • CLARABEL, OSQP, SCS work fine.
  • with cvxpygen 0.3.3 or 0.3.4
    • CLARABEL fails (control value constant and robot fails to reach the goal)
    • OSQP, SCS, ECOS work fine.

Notes

I'm on MacOS Sonoma 14.4.1 and using AppleClang. Python environment:

  • Python 3.10 (with Anaconda)
  • cvxpy 1.4.3
  • osqp 0.6.5
  • scs 3.2.4.post1
  • ecos 2.0.13
  • clarabel 0.7.1

The main issue is that with cvxpygen v0.3.3 and v0.3.4, CLARABEL compiled code sometimes doesn't work as expected.
Going back to v0.3.2, ECOS produced wrong code on the second run, but CLARABEL worked fine.

Code

mpc_2d_robot benchmark

import time
import cvxpy as cp
import numpy as np
from cvxpygen import cpg
from matplotlib import pyplot as plt


def mpc_2d_robot(use_cpg=False, gen_cpg=False, plot_res=False, solver=cp.CLARABEL):
    """
    Certainty equivalence MPC. Linear dynamics with additive noise.
    :return:
    """
    # Dynamics
    A, B = np.eye(2), np.eye(2)

    def dyn(x, u):
        return A @ x + B @ u  # dynamics

    T = 7  # horizon
    x0_val = np.array([-2, -0.8])  # initial state
    Q, R = np.diag([1, 1]), np.diag([1, 1])  # object cost matrices

    # params and vars
    x0 = cp.Parameter(2, 'x0')
    ctrl = cp.Variable((2, T), 'ctrl')
    state = cp.Variable((2, T + 1), 'state')

    def find_xQx(t_future, u, x_now):
        xt = find_xt(t_future, x_now, u)
        return cp.QuadForm(xt, Q)

    def find_xt(t_future, x_now, u):
        At = np.linalg.matrix_power(A, t_future)
        xt = At @ x_now
        for i in range(t_future):
            Ai = np.linalg.matrix_power(A, i)
            Bu = B @ u[:, t_future - 1 - i]
            AiBu = Ai @ Bu
            xt = xt + AiBu
        return xt

    # define optimization problem
    obj = 0
    for t in range(T):
        obj += find_xQx(t + 1, ctrl, x0)
        obj += cp.quad_form(ctrl[:, t], R)

    constr = [state[:, 0] == x0]
    for t in range(T):
        constr += [state[:, t + 1] == dyn(state[:, t], ctrl[:, t])]
    constr += [ctrl <= np.array([[0.2, 0.2]]).T,
               ctrl >= np.array([[-0.2, -0.2]]).T]

    prob = cp.Problem(cp.Minimize(obj), constr)

    if use_cpg:
        if gen_cpg:
            cpg.generate_code(prob, code_dir='mpc_2d', solver=solver)
        from mpc_2d.cpg_solver import cpg_solve
        prob.register_solve('cpg', cpg_solve)

    # sim settings
    steps = 20
    current_state = x0_val
    # plotting results
    x_hist = [current_state]
    u_hist = []
    t_hist = []
    for t in range(steps):
        x0.value = current_state
        if use_cpg:
            if solver in [cp.ECOS, cp.OSQP]:
                ts = time.time()
                prob.solve(method='cpg', updated_params=['x0'])
                te = time.time()
            else:
                ts = time.time()
                prob.solve(method='cpg', updated_params=['x0'], verbose=False)
                te = time.time()
        else:
            ts = time.time()
            prob.solve(solver)
            te = time.time()

        t_hist.append(te - ts)
        print(prob.status)
        u_now = ctrl.value[:, 0]
        next_state = dyn(current_state, u_now)
        x_hist.append(next_state)
        current_state = next_state
        print(current_state)
        u_hist.append(ctrl.value[:, 0])

    x_hist = np.array(x_hist)
    u_hist = np.array(u_hist)
    if plot_res:
        plt.plot(x_hist[:, 0], x_hist[:, 1])
        plt.scatter(0, 0)
        plt.show()
        fig, axs = plt.subplots(2)
        axs[0].plot(range(steps), u_hist[:, 0])
        axs[1].plot(range(steps), u_hist[:, 1])
        plt.show()
    return t_hist


if __name__ == "__main__":
    # solver_list = [cp.OSQP, cp.SCS, cp.CLARABEL, cp.ECOS]
    solver = cp.CLARABEL
    mpc_2d_robot(use_cpg=False, gen_cpg=False, plot_res=True, solver=solver)
    mpc_2d_robot(use_cpg=True, gen_cpg=True, plot_res=True, solver=solver)

# in all cases, cvxpy solves fine
# results for codegen:
# cvxpygen 0.3.2:
# - works: OSQP, SCS, CLARABEL
# - fails: ECOS
# cvxpygen 0.3.3:
# - works: OSQP, SCS, ECOS
# - fails: CLARABEL
# cvxpygen 0.3.4:
# - works: OSQP, SCS, ECOS
# - fails: CLARABEL

MPC.ipynb code (slightly modified version of MPC.ipynb)

import cvxpy as cp

# cvxpygen v0.3.2:
# - works: CLARABEL, OSQP, SCS,
# - fails: ECOS
# cvxpygen v0.3.3:
# - works: CLARABEL, OSQP, SCS, ECOS
# - fails: /
# cvxpygen v0.3.4:
# - works: CLARABEL, OSQP, SCS, ECOS
# - fails: /

# solver_list = [cp.OSQP, cp.SCS, cp.CLARABEL, cp.ECOS]
solver = cp.ECOS

# define dimensions
H, n, m = 10, 6, 3

# define variables
U = cp.Variable((m, H), name='U')
X = cp.Variable((n, H+1), name='X')

# define parameters
Psqrt = cp.Parameter((n, n), name='Psqrt')
Qsqrt = cp.Parameter((n, n), name='Qsqrt')
Rsqrt = cp.Parameter((m, m), name='Rsqrt')
A = cp.Parameter((n, n), name='A')
B = cp.Parameter((n, m), name='B')
x_init = cp.Parameter(n, name='x_init')

# define objective
objective = cp.Minimize(cp.sum_squares(Psqrt@X[:,H]) + cp.sum_squares(Qsqrt@X[:,:H]) + cp.sum_squares(Rsqrt@U))

# define constraints
constraints = [X[:,1:] == A@X[:,:H]+B@U,
               cp.abs(U) <= 1,
               X[:,0] == x_init]

# define problem
problem = cp.Problem(objective, constraints)

import numpy as np

# continuous-time dynmaics
A_cont = np.concatenate((np.array([[0, 0, 0, 1, 0, 0],
                                   [0, 0, 0, 0, 1, 0],
                                   [0, 0, 0, 0, 0, 1]]),
                         np.zeros((3,6))), axis=0)
mass = 1
B_cont = np.concatenate((np.zeros((3,3)),
                         (1/mass)*np.diag(np.ones(3))), axis=0)

# discrete-time dynamics
td = 0.1
A.value = np.eye(n)+td*A_cont
B.value = td*B_cont

# cost
Psqrt.value = np.eye(n)
Qsqrt.value = np.eye(n)
Rsqrt.value = np.sqrt(0.1)*np.eye(m)

# measurement
x_init.value = np.array([2, 2, 2, -1, -1, 1])

val = problem.solve(solver=solver)

from cvxpygen import cpg

cpg.generate_code(problem, code_dir='MPC_code', solver=solver)

from MPC_code.cpg_solver import cpg_solve
import numpy as np
import pickle
import time

# load the serialized problem formulation
with open('MPC_code/problem.pickle', 'rb') as f:
    prob = pickle.load(f)

# assign parameter values
prob.param_dict['A'].value = np.eye(n)+td*A_cont
prob.param_dict['B'].value = td*B_cont
prob.param_dict['Psqrt'].value = np.eye(n)
prob.param_dict['Qsqrt'].value = np.eye(n)
prob.param_dict['Rsqrt'].value = np.sqrt(0.1)*np.eye(m)
prob.param_dict['x_init'].value = np.array([2, 2, 2, -1, -1, 1])

# solve problem conventionally
t0 = time.time()
# CVXPY chooses eps_abs=eps_rel=1e-5, max_iter=10000, polish=True by default,
# however, we choose the OSQP default values here, as they are used for code generation as well
val = prob.solve(solver=solver)
t1 = time.time()
print('\nCVXPY\nSolve time: %.3f ms' % (1000 * (t1 - t0)))
print('Objective function value: %.6f\n' % val)
cvxpy_only_val = prob.value

# solve problem with C code via python wrapper
prob.register_solve('CPG', cpg_solve)
t0 = time.time()
val = prob.solve(method='CPG')
t1 = time.time()
print('\nCVXPYgen\nSolve time: %.3f ms' % (1000 * (t1 - t0)))
print('Objective function value: %.6f\n' % val)

prob.register_solve('CPG', cpg_solve)
t0 = time.time()
val = prob.solve(method='CPG')
t1 = time.time()
print('\nCVXPYgen\nSolve time: %.3f ms' % (1000 * (t1 - t0)))
print('Objective function value: %.6f\n' % val)

cvxpygen_val = prob.value

print("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~")
print("Summary: ")
print("cvxpy only:\n\t", cvxpy_only_val)
print("cvxpygen (2nd solve):\n\t", cvxpygen_val)

Feature Request: Support passing attributes to the solvers

Dear maintainers,

I am trying to pass polish=True or verbose=True in this code

https://github.com/cvxgrp/cvxpygen/blob/master/examples/portfolio.ipynb to the OSQP solver, and I get the following error:

AttributeError                            Traceback (most recent call last)
~/coding/cvxpygen/examples/portfolio_code/cpg_solver.py in cpg_solve(prob, updated_params, **kwargs)
     30         try:
---> 31             eval('cpg_module.set_solver_%s(value)' % key)
     32         except AttributeError:

~/coding/cvxpygen/examples/portfolio_code/cpg_solver.py in <module>

AttributeError: module 'portfolio_code.cpg_module' has no attribute 'set_solver_polish'

Could you please add the possibility to pass attributes to solvers? Thank you very much in advance.

SOC dimension issue

There seems to be an issue with the dimension of the SOC. When passing from a problem with a soc of dimension 100 to 150 I encounter the following problem:

cvxpygen_soc_issue

inconsistency in `cpg_csc` dimensions

The definition of cpg_csc is:

// Compressed sparse column matrix
typedef struct {
  cpg_int      nzmax;
  cpg_int      n;
  cpg_int      m;
  cpg_int      *p;
  cpg_int      *i;
  cpg_float    *x;
  cpg_int      nz;
} cpg_csc;

Note that n comes before m. When matrices are defined, the code is:

def write_mat_def(f, mat, name):
    """
    Write sparse matrix (scipy compressed sparse column) to file
    """
    write_vec_def(f, mat['i'], name + '_i', 'cpg_int')
    write_vec_def(f, mat['p'], name + '_p', 'cpg_int')
    write_vec_def(f, mat['x'], name + '_x', 'cpg_float')

    f.write(f'cpg_csc {name} = {{')
    f.write(f'{mat["nzmax"]}, ')
    f.write(f'{mat["m"]}, ')
    f.write(f'{mat["n"]}, ')
    f.write(f'{name}_p, ')
    f.write(f'{name}_i, ')
    f.write(f'{name}_x, ')
    f.write(f'{mat["nz"]}}};\n')

note that n and m are switched.

I think the reason that this works is that n and m are never used. Is that correct?

Generate code for Variable derivative

Dear cvxpygen developers,
First of all I would like to thank the developers for cvxpygen, it's a really fantastic extension, which can potentially accelerate any cvxpy code!
I'm wondering how I can generate C code for the derivate function of a cvxpy problem. I leave here the example, which I would like to convert completely into C code:

import cvxpy as cp
import numpy as np

# We can solve plasticity problems of solid mechanics with the help of cvxpy
# There is a simple example of the vonMises perfect plasticity 

sig0 = 1
E = 1
nu = 0.3
lmbda = E*nu/(1+nu)/(1-2*nu)
mu = E/2/(1+nu)
l, m = lmbda, mu

C = np.array([[l+2*m, l, l, 0],
              [l, l+2*m, l, 0],
              [l, l, l+2*m, 0],
              [0, 0, 0, 2*m]])

deps = cp.Parameter((4,), name='deps')
sig_old = cp.Parameter((4,), name='sig_old')
sig_elas = sig_old + C @ deps
sig = cp.Variable((4, ), name='sig')

obj = cp.quad_form(sig - sig_elas, np.linalg.inv(C))
problem = cp.Problem(cp.Minimize(obj), [np.sqrt(3/2)*cp.norm(sig) <= sig0])

N = 100
theta = np.linspace(0, 2*np.pi, N+1)
Eps = 0.1*np.vstack((np.cos(theta), np.sin(theta), 0*theta, 0*theta)).T
Sig = np.zeros_like(Eps)

for i in range(N + 1):
    sig_old.value = np.zeros((4, ))
    deps.value = Eps[i,:]
    
    # We can generate the corresponded C code of this solve function
    # But how can I take into account a `requires_grad` argument in cvxpygen?
    problem.solve(solver='SCS', requires_grad=True)
    Sig[i,:] = sig.value

    C_tang = np.zeros((4, 4))
    for i in range(4):
        z = np.zeros((4,))
        z[i] = 1
        # It would be a great advantage to generate appropriate C functions and variables for the following lines
        deps.delta = z
        problem.derivative()
        C_tang[i, :] = sig.delta

    # Here `C_tang` should be the same as `C`
    assert np.max(np.abs(C_tang - C)) < 1e-4

Is it possible to generate C code for this kind of problem? Can you please give some advice about how I can do it in cvxpygen?

About context. I'm interested in embedding a cvxpy solver into JIT compilable code, such as numba functions are, for instance. I considered cvxcore wrapping to implement this, but it's time-consuming and I didn’t find any C++ API… So it’s much faster with cvxpygen, which automatically generates the C-code! I wrote some kind of wrapper via the cffi library and could solve a convex problem inside a numba function. Here you can find a simple example.

If I could get a C analog of the problem.derivate() function and related cvxpy features, I would dramatically accelerate the performance of my code!

Thank you for your work!

feature request: structs instead of global workspace

The generated code uses a global workspace, including the third-party solver's workspace. I'm running into some drawbacks from this such as inability to simulate more than one actor simultaneously, inability to run multi-threaded, etc.

Was this done for simplicity, or is there a performance goal? Would you be open to PRs that encapsulate the workspace in a struct?

How to implement RK4 integration scheme in a DPP compliant manner

While developing an MPC controller for rendezvous, I have a varying time step for the algorithm.
Treating dt as a parameter allows solving TPBVP minimising fuel use.

The formulation of problem is DPP complaint for Euler integration scheme but not for RK4.

The code looks like:

def dX(X, U):
    return A @ X + B @ U

# Define optimization variables
U = cp.Variable((m, H), name='U')
X = cp.Variable((n, H+1), name='X')

# Define optimization parameters
A = cp.Parameter((n, n), name='A')
B = cp.Parameter((n, m), name='B')
x_init = cp.Parameter(n, name='x_init')
x_target = cp.Parameter(n, name='x_target')
max_velocity = cp.Parameter(name='max_velocity')
max_effort = cp.Parameter(name='max_effort')
dt = cp.Parameter(nonneg=True, name='dt') # dt declared as a parameter

# Dynamics constraints
constraints = [cp.abs(X[3:, :]) <= max_velocity,
               cp.abs(U) <= max_effort]

for i in range(H):
    # RK4 Timestep
    k1 = dX(X[:, i], U[:, i]) # Not DPP compliant
    k2 = dX(X[:, i] + 0.5*k1*dt, U[:, i])
    k3 = dX(X[:, i] + 0.5*k2*dt, U[:, i])
    k4 = dX(X[:, i] + k3*dt, U[:, I])
    constraints += [X[:, i+1] == X[:, i] + dt*(k1 + 2*k2 + 2*k3 + k4)/6]

    # Euler timestep
    #constraints += [X[:, i+1] == X[:,i] + dX(X[:, i], U[:, i])] # DPP compliant
# CODE WHICH CAUSES THE ISSUE ^^^^^^

# Boundary conditions
constraints += [X[:, 0] == x_init,
                X[:, -1] == x_target]
# Define objective function
objective = cp.Minimize(cp.norm(U))
# Define optimization problem
problem = cp.Problem(objective, constraints)

Are there any workaround to doing this. The documentation mentions introducing variables to resolve the issue for simpler problems.
But the thing is RK4 is 4th order so that results in dt^4 expression.

SDP option in CLARABEL

Hi,

The Clarabel.cpp project's SDP support is disabled by default, as stated in the README.md file. To enable it, you need to set the CLARABEL_FEATURE_SDP flag to one of the following options:

sdp-accelerate
sdp-netlib
sdp-openblas
sdp-mkl
sdp-r
While modifying the CMakeLists.txt file within the Clarabel.cpp folder inside the cvxpygen folder appears to enable the SDP feature, attempting to compile a cpg_example results in the following error:

/home/vboxuser/.cargo/registry/src/index.crates.io-6f17d22bba15001f/lapack-0.19.0/src/lapack-sys.rs:29385: undefined reference to `ssyevr_'

Despite having blas and lapack installed, this error persists. Resolving this issue is crucial because CLARABEL is currently the only solver capable of handling SDP optimization efficiently in CVXPYGEN.

Thank you for your attention to this matter.

Compilation breaks for problems with no constraints

In the example, when I replace
problem = cp.Problem(cp.Minimize(cp.sum_squares(A @ x - b)), [x >= 0])
with
problem = cp.Problem(cp.Minimize(cp.sum_squares(A @ x - b)))
I get

>>> cpg.generate_code(problem, code_dir='nonneg_LS', solver='SCS')
Generating code with CVXPYgen ...
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/opt/homebrew/Caskroom/miniforge/base/envs/veclib/lib/python3.10/site-packages/cvxpygen/cpg.py", line 672, in generate_code
    utils.write_module_def(f, info_opt, info_usr, info_can)
  File "/opt/homebrew/Caskroom/miniforge/base/envs/veclib/lib/python3.10/site-packages/cvxpygen/utils.py", line 1227, in write_module_def
    max(info_usr[C.D_NAME_TO_SIZE].values())
ValueError: max() arg is an empty sequence

Replacing line 1227 with
max(info_usr[C.D_NAME_TO_SIZE].values(), default=0)
seems to fix the issue.

underflow in generated code

I tried clarabel code generation and I got this error:

error: unsigned conversion from 'int' to 'long unsigned int' changes value from '-1' to '18446744073709551615' [-Werror=sign-conversion]
 5440 | cpg_csc acc_canon_A_map = {1802, 1802, 13, acc_canon_A_map_p, acc_canon_A_map_i, acc_canon_A_map_x, -1};

I'm compiling generated code myself, not using cvxpygen's built-in compilation, and have a lot of warning flags on. This one looked more suspicious than the others.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.