# cvxcore

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.

## 1. Making changes in cvxcore visible from CVXPY

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](https://github.com/swig/swig/tags) 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.

## 2. Notes about Python files which interact with 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".


## 3. Creating a new LinOp

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 ...
 1. Define ``KRON_R`` and ``KRON_L`` string constants.
 2. Define a super simple ``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``.
 3. Modify ``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``:
 1. Renamed an existing function ``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.
 2. Created a nontrivial new function called ``get_kronl_mat``.
 3. Updated ``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.
 

## 4. Miscellaneous notes on cvxcore's C++ implementation

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.
