Linear solvers
We suppose that the KKT system has been assembled previously into a given AbstractKKTSystem
. Then, it remains to compute the Newton step by solving the KKT system for a given right-hand-side (given as a AbstractKKTVector
). That's exactly the role of the linear solver.
If we do not assume any structure, the KKT system writes in generic form
\[K x = b\]
with $K$ the KKT matrix and $b$ the current right-hand-side. MadNLP provides a suite of specialized linear solvers to solve the linear system.
Inertia detection
If the matrix $K$ has negative eigenvalues, we have no guarantee that the solution of the KKT system is a descent direction with regards to the original nonlinear problem. That's the reason why most of the linear solvers compute the inertia of the linear system when factorizing the matrix $K$. The inertia counts the number of positive, negative and zero eigenvalues in the matrix. If the inertia does not meet a given criteria, then the matrix $K$ is regularized by adding a multiple of the identity to it: $K_r = K + \alpha I$.
We recall that the inertia of a matrix $K$ is given as a triplet $(n,m,p)$, with $n$ the number of positive eigenvalues, $m$ the number of negative eigenvalues and $p$ the number of zero eigenvalues.
Factorization algorithm
In nonlinear programming, it is common to employ a LBL factorization to decompose the symmetric indefinite matrix $K$, as this algorithm returns the inertia of the matrix directly as a result of the factorization.
When MadNLP runs in inertia-free mode, the algorithm does not require to compute the inertia when factorizing the matrix $K$. In that case, MadNLP can use a classical LU or QR factorization to solve the linear system $Kx = b$.
Solving a KKT system with MadNLP
We suppose available a AbstractKKTSystem
kkt
, properly assembled following the procedure presented previously. We can query the assembled matrix $K$ as
K = MadNLP.get_kkt(kkt)
6×6 SparseArrays.SparseMatrixCSC{Float64, Int32} with 13 stored entries:
2.0 ⋅ ⋅ ⋅ ⋅ ⋅
0.0 200.0 ⋅ ⋅ ⋅ ⋅
⋅ ⋅ 0.0 ⋅ ⋅ ⋅
⋅ ⋅ ⋅ 0.0 ⋅ ⋅
0.0 0.0 -1.0 ⋅ 0.0 ⋅
1.0 0.0 ⋅ -1.0 ⋅ 0.0
Then, if we want to pass the KKT matrix K
to Lapack, this translates to
linear_solver = LapackCPUSolver(K)
LapackCPUSolver{Float64, SparseArrays.SparseMatrixCSC{Float64, Int32}}(sparse(Int32[1, 2, 5, 6, 2, 5, 6, 3, 5, 4, 6, 5, 6], [1, 1, 1, 1, 2, 2, 2, 3, 3, 4, 4, 5, 6], [2.0, 0.0, 0.0, 1.0, 200.0, 0.0, 0.0, 0.0, -1.0, 0.0, -1.0, 0.0, 0.0], 6, 6), [2.97079410834e-313 5.5171890578e-313 … 6.3659873744e-314 8.4879831886e-314; 4.45619116177e-313 2.1219957964e-314 … 9.9733802199e-313 1.08221785341e-312; … ; 2.97079410804e-313 6.79038653114e-313 … 8.487983188e-314 8.4879831896e-314; 1.9097962131e-313 4.2439915977e-314 … 1.0609978955e-312 1.12465776923e-312], [6.9005632882627e-310], -1, Base.RefValue{Int64}(0), Dict{Symbol, Any}(), MadNLP.LapackOptions(MadNLP.BUNCHKAUFMAN), MadNLP.MadNLPLogger(MadNLP.INFO, MadNLP.INFO, nothing))
The instance linear_solver
does not copy the matrix $K$ and instead keep a reference to it.
linear_solver.A === K
true
That way every time we re-assemble the matrix $K$ in kkt
, the values are directly updated inside linear_solver
.
To compute the factorization inside linear_solver
, one simply as to call:
MadNLP.factorize!(linear_solver)
LapackCPUSolver{Float64, SparseArrays.SparseMatrixCSC{Float64, Int32}}(sparse(Int32[1, 2, 5, 6, 2, 5, 6, 3, 5, 4, 6, 5, 6], [1, 1, 1, 1, 2, 2, 2, 3, 3, 4, 4, 5, 6], [2.0, 0.0, 0.0, 1.0, 200.0, 0.0, 0.0, 0.0, -1.0, 0.0, -1.0, 0.0, 0.0], 6, 6), [2.0 0.0 … 0.0 0.0; 0.0 200.0 … 0.0 0.0; … ; 0.0 0.0 … 0.0 0.0; 0.5 0.0 … -1.0 -0.5], [384.0, 6.9005632879414e-310, 6.90055995540655e-310, 6.90056328795285e-310, 6.9005632879414e-310, 6.9005632879414e-310, 3.2523211e-316, 8.487983164e-314, 3.25244917e-316, 2.4e-322 … 6.9005603775552e-310, 6.9005603775647e-310, 1.21e-321, 1.21e-321, 6.9005603775742e-310, 6.9005603775805e-310, 1.215e-321, 1.215e-321, 6.90056037758367e-310, 6.90056037758683e-310], 384, Base.RefValue{Int64}(0), Dict{Symbol, Any}(:ipiv => [1, 2, -5, -5, -6, -6]), MadNLP.LapackOptions(MadNLP.BUNCHKAUFMAN), MadNLP.MadNLPLogger(MadNLP.INFO, MadNLP.INFO, nothing))
Once the factorization computed, computing the backsolve for a right-hand-side b
amounts to
nk = size(kkt, 1)
b = rand(nk)
MadNLP.solve!(linear_solver, b)
6-element Vector{Float64}:
0.4473746155065908
0.0032884755830188198
-0.9216967425897742
-0.11664718531864932
-0.24975096798819174
-0.6551664836512676
The values of b
being modified inplace to store the solution $x$ of the linear system $Kx =b$.