Linear System Solver

At each iteration \(k\) SCS solves the following set of linear equations:

\[\begin{split}\begin{bmatrix} R_x + P & A^\top \\ A & -R_y \\ \end{bmatrix} \begin{bmatrix} x \\ y \end{bmatrix} = \begin{bmatrix} z^k_x \\ z^k_y \end{bmatrix}\end{split}\]

for a particular right-hand side \(z^k \in \mathbf{R}^{n+m}\). The presence of the diagonal scaling \(R \in \mathbf{R}^{(n+m) \times (n+m)}\) matrix is explained in Non-identity DR Scaling. The \(R_y\) term is negated to make the matrix quasi-definite; we can recover the solution to the original problem from this modified one. Note that the matrix does not change from iteration to iteration, which is a major advantage of this approach. Moreover, this system can be solved approximately so long as the errors satisfy a summability condition, which permits the use of approximate solvers that can scale to very large problems.

Available linear solvers

Each of the below linear solvers is included in their own binary. If linking against SCS directly, then to switch between them you must compile and link against the right binary. If calling SCS via one of the interfaces then you can choose between the different linear solvers using the appropriate settings.

Sparse direct

The direct method is the default linear system solver in SCS and factorizes the above matrix using a sparse permuted LDL factorization. Then it solves the linear system at each iteration using the cached factors. Since the linear system is quasidefinite we have strong existence guarantees about the factorization. It relies on the external (but included) AMD and QDLDL packages.

MKL Pardiso

The Intel MKL Pardiso solver is a shared-memory multiprocessing parallel direct sparse solver which is included Intel oneAPI MKL library. This offers an alternative to the single threaded AMD / QDLDL libraries which come bundled with SCS. Pardiso tends to be faster than AMD / QDLDL, especially for larger problems. If MKL is installed on your system then it is generally worth using MKL for both the blas / lapack usage as well as the linear system solve. Intel MKL is now available for free and without restrictions for everyone, though it only offers limited support for non-Intel CPUs.

Sparse indirect

The indirect method solves the above linear system approximately with a ‘matrix-free’ method. To do this it first reduces the system to solving

\[\begin{split}\begin{align} (R_x + P + A^\top R_y^{-1} A) x & = z^k_x + A^\top R_y^{-1} z^k_y \\ y & = R_y^{-1}(A x - z^k_y). \end{align}\end{split}\]

then solves the positive definite system using using conjugate gradients. Each iteration of CG requires one multiply each of sparse matrices \(P, A, A^\top\). The system is solved up to some tolerance, which is tuned to ensure that the overall algorithm converges. The tolerance decays with iteration \(k\) like \(O(1/k^\gamma)\) where \(\gamma > 1\) and is determined by the constant CG_RATE (defaults to \(1.5\)).

The indirect method has the advantage of not requiring an expensive factorization but typically is slower on a per-iteration basis. In most cases the factorization is relatively cheap so the direct method is the default, however for very large problems the indirect solver can be faster.

Sparse GPU indirect method

The above linear solvers all run on CPU. We also have support for a GPU version of the indirect solver, where the matrix multiplies are all performed on the GPU.

Implementing a new linear solver

In order to implement you own linear system solver, you need to implement the struct ScsLinSysWork that contains the workspace your solver requires, and implement the functions in include/linsys.h as detailed below. See linsys directory for examples.

typedef struct SCS_LIN_SYS_WORK ScsLinSysWork

Struct containing linear system workspace. Implemented by linear solver.

Functions

ScsLinSysWork *scs_init_lin_sys_work(const ScsMatrix *A, const ScsMatrix *P, const scs_float *diag_r)

Initialize ScsLinSysWork structure and perform any necessary preprocessing.

Parameters:
  • AA data matrix, m x n.

  • PP data matrix, n x n.

  • diag_rR > 0 diagonal entries of length m + n.

Returns:

Linear system solver workspace.

void scs_free_lin_sys_work(ScsLinSysWork *w)

Frees ScsLinSysWork structure and associated allocated memory.

Parameters:

w – Linear system private workspace.

scs_int scs_solve_lin_sys(ScsLinSysWork *w, scs_float *b, const scs_float *s, scs_float tol)

Solves the linear system as required by SCS at each iteration:

\[\begin{split} \begin{bmatrix} (R_x + P) & A^\top \\ A & -R_y \\ \end{bmatrix} x = b \end{split}\]

for x, where diag(R_x, R_y) = R. Overwrites b with result.

Parameters:
  • w – Linear system private workspace.

  • b – Right hand side, contains solution at the end.

  • s – Contains warm-start (may be NULL).

  • tol – Tolerance required for the system solve.

Returns:

status != 0 indicates failure.

void scs_update_lin_sys_diag_r(ScsLinSysWork *w, const scs_float *new_diag_r)

Update the linsys workspace when R is changed. For example, a direct method for solving the linear system might need to update the factorization of the matrix.

Parameters:
  • w – Linear system private workspace.

  • new_diag_r – Updated diag_r, diagonal entries of R.

const char *scs_get_lin_sys_method(void)

Name of the linear solver.

Returns:

name of method.