-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathkkt_sys_solver.py
167 lines (135 loc) · 5.97 KB
/
kkt_sys_solver.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
import numpy as np
from scipy.linalg import ldl
from scipy import sparse
import eigenpy
from qps import ConvexQP
from matplotlib import pyplot as plt
class KKTSysSolver():
def __init__(self) -> None:
"""
Abstract class that defines the linear system solve for the KKT system of a Quadratic Program.
"""
pass
def solve(self, qp: ConvexQP, tau):
"""
Execute the 3 steps that are involved when solving the KKT system of a QP:
1. prepare the linear system (from residuals and system matrices), might eliminate some variables
2. solve the linear system using the linsys solution method of the class (LU or LDLT)
3. in case variables whrere eliminated from KKT system recover the full step.
Set the computed step length in the qp
"""
r = qp.get_residual(tau)
M_sym, r_sym = self.prepare_lin_sys(qp, r)
delta_p = self.solve_lin_sys(M_sym, r_sym)
step = self.recover_step(qp, delta_p, r)
qp.set_p(step)
return step
def prepare_lin_sys(self, qp: ConvexQP, r):
"""
Builds the KKT system from the QPs matrices and current iterate of primal and dual variables and residuals.
"""
raise NotImplementedError(f"symmetrize method has to be implemented by a derived class.")
def recover_step(self, qp: ConvexQP, delta_p, r):
"""
Recovers step for QP in all primal and dual variables in case some of the where eliminated to obtain a
condensed KKT system.
"""
raise NotImplementedError(f"recover_step method has to be implemented by a derived class.")
class LDLTSolver(KKTSysSolver):
def prepare_lin_sys(self, qp: ConvexQP, r):
"""
Eliminate mu and s and build a condensed KKT system
"""
idx_e = qp.nx
idx_i = idx_e + qp.ne
idx_c = idx_i + qp.ni
S_inv = np.diag(1 / qp.s)
U = np.diag(qp.mu)
M = np.vstack((np.hstack((qp.Q + qp.C.T @ S_inv @ U @ qp.C, qp.A.T)),
np.hstack((qp.A, np.zeros((qp.ne, qp.ne))))))
r = np.concatenate((r[:idx_e] + qp.C.T @ S_inv @ (U @ r[idx_i:idx_c] - r[idx_c:]),
r[idx_e:idx_i]))
return M, r
def recover_step(self, qp: ConvexQP, delta_p, r):
"""
Compute step for mu and s from the steps in x and lambda from the condensed KKT system and set the
step stored with the QP.
"""
idx_e = qp.nx
idx_i = idx_e + qp.ne
idx_c = idx_i + qp.ni
S = np.diag(qp.s)
S_inv = np.diag(1 / qp.s)
U = np.diag(qp.mu)
U_inv = np.diag(1 / qp.mu)
delta_x, delta_l = np.split(delta_p, [qp.nx])
delta_mu = S_inv @ (U @ (r[idx_i:idx_c] + qp.C @ delta_x) - r[idx_c:])
delta_s = - U_inv @ (r[idx_c:] + S @ delta_mu)
return np.concatenate((delta_x, delta_l, delta_mu, delta_s))
class LDLTSolverEigen(LDLTSolver):
def solve_lin_sys(self, M, r):
"""
Solve symmetric linear system using LDLT factorization through the implementation in the eigenpy / Eigen
package.
"""
decomp = eigenpy.LDLT(M)
return decomp.solve(-r)
class LDLTSolverScipy(LDLTSolver):
def solve_lin_sys(self, M, r):
"""
Solve symmetric linear system by obtaining LDLT factorization from scipy and a backsubstitution procedure on the
factorized linear system.
"""
n = len(M)
L, D, perm = ldl(M)
# scipy.linalg.ldl returns L such that permutation needs to be applied first to make it triangular
# create permutation matrix first
P = np.zeros_like(M)
for i, j in enumerate(perm):
P[i, j] = 1
# create references lower triangular matrix:
L_tilde = P @ L
# solve linear system for p via backsubstitution from:
# L_tilde @ D @ L_tilde.T @ P @ p = - P @ r
# convert residual to RHS and match the row permutation of M
# perform all computations in-place
p = - P @ r
# p_1: backsubstitute to obtain RHS for term in brackets
# L_tilde @ (D @ L_tilde.T @ P @ p) = - P @ r
for i in range(n):
p[i] = p[i] - np.sum(L_tilde[i, :i] * p[:i])
# p_2: scale to obtain RHS for term in brackets
# D @ (L_tilde.T @ P @ p) = D @ L_tilde.T @ P @ p
# unfortunately need to use inverse here because D might only be block diagonal of unknown block size
# p = 1 / np.diag(D) * p
p = np.linalg.inv(D) @ p
# p_3: backsubstitute to obtain RHS for term in brackets
# L_tilde.T @ (P @ p) = L_tilde.T @ P @ p
for i in reversed(range(n)):
p[i] = p[i] - np.sum((L_tilde.T)[i, i+1:] * p[i+1:])
# p_4: undo permutation to obtain target value p
p = P.T @ p
return p
class LUSolver(KKTSysSolver):
def prepare_lin_sys(self, qp: ConvexQP, r):
"""
Construct the full KKT system without any elimination of variables.
"""
# compute the linear system matrix
J_L = np.hstack((qp.Q, qp.A.T, qp.C.T, np.zeros((qp.nx, qp.ni))))
J_e = np.hstack((qp.A, np.zeros((qp.ne, qp.ne + 2 * qp.ni))))
J_i = np.hstack((qp.C, np.zeros((qp.ni, qp.ne + qp.ni)), np.eye(qp.ni)))
J_c = np.hstack((np.zeros((qp.ni, qp.nx + qp.ne)), np.diag(qp.s), np.diag(qp.mu)))
M = np.vstack((J_L, J_e, J_i, J_c))
return M, r
def recover_step(self, qp: ConvexQP, delta_p, r):
"""
Nothing to do in LU case as we already computed the full step.
"""
return delta_p
class LUSolverNumpy(LUSolver):
def solve_lin_sys(self, M, r):
"""
linear system solve via LU factorization as implemented in numpy (using LAPACK).
"""
return np.linalg.solve(M, -r)