cvxpy/cvxcore/README.md
cvxcore was originally written to serve as a C++ back-end for multiple high-level optimization modeling languages, including CVX, CVXPY, and Convex.jl. It didn't end up catching on with that full scope, and so now it's only used in CVXPY. cvxcore is officially deprecated and may undergo API-breaking changes without notice.
This file contains informal notes written by CVXPY project maintainers for the rare situations when we want to change cvxcore.
You will need an appropriately configured SWIG installation to make changes to cvxcore and actually see them in CVXPY.
To get started, install a tarball from the swig GitHub releases and follow the installation instructions. (note that swig is not a Python package, so you shouldn't try to get it from PyPI).
Changes you make to cvxcore will most likely be confined to files in cvxpy/cvxcore/src/.
Once you make your desired changes, change directory so that src is in your working directory, and run
swig -Isrc -c++ -python python/cvxcore.i
That step can succeed even if your new code leads to compiler errors (e.g., missing semicolons).
The next step is to rebuild all of CVXPY -- including cvxcore -- by first changing directory so you
can see CVXPY's setup.py and then running
pip install -e .
That step can result in compiler errors if you made bad edits to cvxcore.
You can combine those steps into one by starting in the directory with setup.py and then running
bash rebuild_cvxcore.sh
Rebuilding cvxcore will automatically generate cvxpy/cvxcore/python/cvxpy.py.
That generated file will probably have linter errors.
If you use pre-commit as part of development then it will
automatically fix those, but you'll need to add the modified
file again before attempting to commit.
Before you commit your changes, you should enable debugging for cvxcore by adding
undef_macros = [ "NDEBUG" ]
to the Extension object in setup.py, and then rebuilding cvxcore.
The directory cvxcore/python contains cvxcore.py and canonInterface.py.
The former file is generated by SWIG.
The latter file is written by hand and handles the task of connecting Python LinOp objects (cvxpy/lin_ops/lin_op.py:LinOp)
to C++ LinOp objects (cvxpy/cvxcore/src/LinOpp.hpp:LinOp).
That task is actually pretty delicate.
There is only one LinOp class in Python; the mathematical nature of a specific Python LinOp lin is indicated
with a string stored in lin.type.
Example LinOp types are "mul", "rmul", "transpose", and "kron_r".
It sometimes happens that we want new types of affine operators in CVXPY.
For example, cp.kron(A, B) originally required that A was constant.
In 2022, we extended kron to let B be a constant and A be a non-constant affine Expression.
This required renaming the existing occurrences of the "kron" LinOp (in Python and C++) to "kron_r"
and creating new a LinOp (again, in Python and C++) called "kron_l".
Changes to LinOps at the Python level were needed in cvxpy/lin_ops/lin_op.py, cvxpy/lin_ops/lin_utils.py, and
cvxpy/atoms/affine/kron.py:kron.
The respective changes in each file were to ...
KRON_R and KRON_L string constants.kron_l function for building Python LinOp's that represent kron(A, B) where A is non-constant.
Rename the existing kron function to kron_r.kron.__init__ so the left operand to cp.kron could be non-constant.
Have kron.graph_implementation call kron_r if the right operand is non-constant and
kron_l if the left operand was non-constant.Creating a LinOp at the C++ level required changing the enumeration defined in
cvxpy/cvxcore/src/LinOp.hpp: we added KRON_R and KRON_L after the existing KRON.
We also made the following changes to cvxpy/cvxcore/src/LinOpOperations.cpp:
get_kron_mat to get_kronr_mat. This function did the heavy
lifting in canonicalizing Kronecker products when the right-side operand was non-constant.get_kronl_mat.get_node_coeffs so (1) it checked for the new KRON_R and KRON_L enums and (2)
it routed the old KRON enum to the get_kronr_mat function for backwards compatibility.The cvxcore Matrix datatype is equal to Eigen::SparseMatrix<double>. That datatype can store
information in a generalized CSC or CSR format. The default format is a generalized CSC. You can
assume a SparseMatrix object mat is standard CSC or CSR by calling mat.makeCompressed().
See cvxpy/cvxcore/src/Utils.hpp for the definitions of cvxcore's Vector, Triplet, and Tensor
datatypes.