Coverage for /home/martinb/.local/share/virtualenvs/camcops/lib/python3.6/site-packages/scipy/optimize/_remove_redundancy.py : 7%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1"""
2Routines for removing redundant (linearly dependent) equations from linear
3programming equality constraints.
4"""
5# Author: Matt Haberland
7import numpy as np
8from scipy.linalg import svd
9import scipy
10from scipy.linalg.blas import dtrsm
13def _row_count(A):
14 """
15 Counts the number of nonzeros in each row of input array A.
16 Nonzeros are defined as any element with absolute value greater than
17 tol = 1e-13. This value should probably be an input to the function.
19 Parameters
20 ----------
21 A : 2-D array
22 An array representing a matrix
24 Returns
25 -------
26 rowcount : 1-D array
27 Number of nonzeros in each row of A
29 """
30 tol = 1e-13
31 return np.array((abs(A) > tol).sum(axis=1)).flatten()
34def _get_densest(A, eligibleRows):
35 """
36 Returns the index of the densest row of A. Ignores rows that are not
37 eligible for consideration.
39 Parameters
40 ----------
41 A : 2-D array
42 An array representing a matrix
43 eligibleRows : 1-D logical array
44 Values indicate whether the corresponding row of A is eligible
45 to be considered
47 Returns
48 -------
49 i_densest : int
50 Index of the densest row in A eligible for consideration
52 """
53 rowCounts = _row_count(A)
54 return np.argmax(rowCounts * eligibleRows)
57def _remove_zero_rows(A, b):
58 """
59 Eliminates trivial equations from system of equations defined by Ax = b
60 and identifies trivial infeasibilities
62 Parameters
63 ----------
64 A : 2-D array
65 An array representing the left-hand side of a system of equations
66 b : 1-D array
67 An array representing the right-hand side of a system of equations
69 Returns
70 -------
71 A : 2-D array
72 An array representing the left-hand side of a system of equations
73 b : 1-D array
74 An array representing the right-hand side of a system of equations
75 status: int
76 An integer indicating the status of the removal operation
77 0: No infeasibility identified
78 2: Trivially infeasible
79 message : str
80 A string descriptor of the exit status of the optimization.
82 """
83 status = 0
84 message = ""
85 i_zero = _row_count(A) == 0
86 A = A[np.logical_not(i_zero), :]
87 if not(np.allclose(b[i_zero], 0)):
88 status = 2
89 message = "There is a zero row in A_eq with a nonzero corresponding " \
90 "entry in b_eq. The problem is infeasible."
91 b = b[np.logical_not(i_zero)]
92 return A, b, status, message
95def bg_update_dense(plu, perm_r, v, j):
96 LU, p = plu
98 vperm = v[perm_r]
99 u = dtrsm(1, LU, vperm, lower=1, diag=1)
100 LU[:j+1, j] = u[:j+1]
101 l = u[j+1:]
102 piv = LU[j, j]
103 LU[j+1:, j] += (l/piv)
104 return LU, p
107def _remove_redundancy_dense(A, rhs, true_rank=None):
108 """
109 Eliminates redundant equations from system of equations defined by Ax = b
110 and identifies infeasibilities.
112 Parameters
113 ----------
114 A : 2-D sparse matrix
115 An matrix representing the left-hand side of a system of equations
116 rhs : 1-D array
117 An array representing the right-hand side of a system of equations
119 Returns
120 ----------
121 A : 2-D sparse matrix
122 A matrix representing the left-hand side of a system of equations
123 rhs : 1-D array
124 An array representing the right-hand side of a system of equations
125 status: int
126 An integer indicating the status of the system
127 0: No infeasibility identified
128 2: Trivially infeasible
129 message : str
130 A string descriptor of the exit status of the optimization.
132 References
133 ----------
134 .. [2] Andersen, Erling D. "Finding all linearly dependent rows in
135 large-scale linear programming." Optimization Methods and Software
136 6.3 (1995): 219-227.
138 """
139 tolapiv = 1e-8
140 tolprimal = 1e-8
141 status = 0
142 message = ""
143 inconsistent = ("There is a linear combination of rows of A_eq that "
144 "results in zero, suggesting a redundant constraint. "
145 "However the same linear combination of b_eq is "
146 "nonzero, suggesting that the constraints conflict "
147 "and the problem is infeasible.")
148 A, rhs, status, message = _remove_zero_rows(A, rhs)
150 if status != 0:
151 return A, rhs, status, message
153 m, n = A.shape
155 v = list(range(m)) # Artificial column indices.
156 b = list(v) # Basis column indices.
157 # This is better as a list than a set because column order of basis matrix
158 # needs to be consistent.
159 d = [] # Indices of dependent rows
160 perm_r = None
162 A_orig = A
163 A = np.zeros((m, m + n), order='F')
164 np.fill_diagonal(A, 1)
165 A[:, m:] = A_orig
166 e = np.zeros(m)
168 js_candidates = np.arange(m, m+n, dtype=int) # candidate columns for basis
169 # manual masking was faster than masked array
170 js_mask = np.ones(js_candidates.shape, dtype=bool)
172 # Implements basic algorithm from [2]
173 # Uses some of the suggested improvements (removing zero rows and
174 # Bartels-Golub update idea).
175 # Removing column singletons would be easy, but it is not as important
176 # because the procedure is performed only on the equality constraint
177 # matrix from the original problem - not on the canonical form matrix,
178 # which would have many more column singletons due to slack variables
179 # from the inequality constraints.
180 # The thoughts on "crashing" the initial basis are only really useful if
181 # the matrix is sparse.
183 lu = np.eye(m, order='F'), np.arange(m) # initial LU is trivial
184 perm_r = lu[1]
185 for i in v:
187 e[i] = 1
188 if i > 0:
189 e[i-1] = 0
191 try: # fails for i==0 and any time it gets ill-conditioned
192 j = b[i-1]
193 lu = bg_update_dense(lu, perm_r, A[:, j], i-1)
194 except Exception:
195 lu = scipy.linalg.lu_factor(A[:, b])
196 LU, p = lu
197 perm_r = list(range(m))
198 for i1, i2 in enumerate(p):
199 perm_r[i1], perm_r[i2] = perm_r[i2], perm_r[i1]
201 pi = scipy.linalg.lu_solve(lu, e, trans=1)
203 js = js_candidates[js_mask]
204 batch = 50
206 # This is a tiny bit faster than looping over columns indivually,
207 # like for j in js: if abs(A[:,j].transpose().dot(pi)) > tolapiv:
208 for j_index in range(0, len(js), batch):
209 j_indices = js[j_index: min(j_index+batch, len(js))]
211 c = abs(A[:, j_indices].transpose().dot(pi))
212 if (c > tolapiv).any():
213 j = js[j_index + np.argmax(c)] # very independent column
214 b[i] = j
215 js_mask[j-m] = False
216 break
217 else:
218 bibar = pi.T.dot(rhs.reshape(-1, 1))
219 bnorm = np.linalg.norm(rhs)
220 if abs(bibar)/(1+bnorm) > tolprimal: # inconsistent
221 status = 2
222 message = inconsistent
223 return A_orig, rhs, status, message
224 else: # dependent
225 d.append(i)
226 if true_rank is not None and len(d) == m - true_rank:
227 break # found all redundancies
229 keep = set(range(m))
230 keep = list(keep - set(d))
231 return A_orig[keep, :], rhs[keep], status, message
234def _remove_redundancy_sparse(A, rhs):
235 """
236 Eliminates redundant equations from system of equations defined by Ax = b
237 and identifies infeasibilities.
239 Parameters
240 ----------
241 A : 2-D sparse matrix
242 An matrix representing the left-hand side of a system of equations
243 rhs : 1-D array
244 An array representing the right-hand side of a system of equations
246 Returns
247 -------
248 A : 2-D sparse matrix
249 A matrix representing the left-hand side of a system of equations
250 rhs : 1-D array
251 An array representing the right-hand side of a system of equations
252 status: int
253 An integer indicating the status of the system
254 0: No infeasibility identified
255 2: Trivially infeasible
256 message : str
257 A string descriptor of the exit status of the optimization.
259 References
260 ----------
261 .. [2] Andersen, Erling D. "Finding all linearly dependent rows in
262 large-scale linear programming." Optimization Methods and Software
263 6.3 (1995): 219-227.
265 """
267 tolapiv = 1e-8
268 tolprimal = 1e-8
269 status = 0
270 message = ""
271 inconsistent = ("There is a linear combination of rows of A_eq that "
272 "results in zero, suggesting a redundant constraint. "
273 "However the same linear combination of b_eq is "
274 "nonzero, suggesting that the constraints conflict "
275 "and the problem is infeasible.")
276 A, rhs, status, message = _remove_zero_rows(A, rhs)
278 if status != 0:
279 return A, rhs, status, message
281 m, n = A.shape
283 v = list(range(m)) # Artificial column indices.
284 b = list(v) # Basis column indices.
285 # This is better as a list than a set because column order of basis matrix
286 # needs to be consistent.
287 k = set(range(m, m+n)) # Structural column indices.
288 d = [] # Indices of dependent rows
290 A_orig = A
291 A = scipy.sparse.hstack((scipy.sparse.eye(m), A)).tocsc()
292 e = np.zeros(m)
294 # Implements basic algorithm from [2]
295 # Uses only one of the suggested improvements (removing zero rows).
296 # Removing column singletons would be easy, but it is not as important
297 # because the procedure is performed only on the equality constraint
298 # matrix from the original problem - not on the canonical form matrix,
299 # which would have many more column singletons due to slack variables
300 # from the inequality constraints.
301 # The thoughts on "crashing" the initial basis sound useful, but the
302 # description of the procedure seems to assume a lot of familiarity with
303 # the subject; it is not very explicit. I already went through enough
304 # trouble getting the basic algorithm working, so I was not interested in
305 # trying to decipher this, too. (Overall, the paper is fraught with
306 # mistakes and ambiguities - which is strange, because the rest of
307 # Andersen's papers are quite good.)
308 # I tried and tried and tried to improve performance using the
309 # Bartels-Golub update. It works, but it's only practical if the LU
310 # factorization can be specialized as described, and that is not possible
311 # until the SciPy SuperLU interface permits control over column
312 # permutation - see issue #7700.
314 for i in v:
315 B = A[:, b]
317 e[i] = 1
318 if i > 0:
319 e[i-1] = 0
321 pi = scipy.sparse.linalg.spsolve(B.transpose(), e).reshape(-1, 1)
323 js = list(k-set(b)) # not efficient, but this is not the time sink...
325 # Due to overhead, it tends to be faster (for problems tested) to
326 # compute the full matrix-vector product rather than individual
327 # vector-vector products (with the chance of terminating as soon
328 # as any are nonzero). For very large matrices, it might be worth
329 # it to compute, say, 100 or 1000 at a time and stop when a nonzero
330 # is found.
332 c = (np.abs(A[:, js].transpose().dot(pi)) > tolapiv).nonzero()[0]
333 if len(c) > 0: # independent
334 j = js[c[0]]
335 # in a previous commit, the previous line was changed to choose
336 # index j corresponding with the maximum dot product.
337 # While this avoided issues with almost
338 # singular matrices, it slowed the routine in most NETLIB tests.
339 # I think this is because these columns were denser than the
340 # first column with nonzero dot product (c[0]).
341 # It would be nice to have a heuristic that balances sparsity with
342 # high dot product, but I don't think it's worth the time to
343 # develop one right now. Bartels-Golub update is a much higher
344 # priority.
345 b[i] = j # replace artificial column
346 else:
347 bibar = pi.T.dot(rhs.reshape(-1, 1))
348 bnorm = np.linalg.norm(rhs)
349 if abs(bibar)/(1 + bnorm) > tolprimal:
350 status = 2
351 message = inconsistent
352 return A_orig, rhs, status, message
353 else: # dependent
354 d.append(i)
356 keep = set(range(m))
357 keep = list(keep - set(d))
358 return A_orig[keep, :], rhs[keep], status, message
361def _remove_redundancy(A, b):
362 """
363 Eliminates redundant equations from system of equations defined by Ax = b
364 and identifies infeasibilities.
366 Parameters
367 ----------
368 A : 2-D array
369 An array representing the left-hand side of a system of equations
370 b : 1-D array
371 An array representing the right-hand side of a system of equations
373 Returns
374 -------
375 A : 2-D array
376 An array representing the left-hand side of a system of equations
377 b : 1-D array
378 An array representing the right-hand side of a system of equations
379 status: int
380 An integer indicating the status of the system
381 0: No infeasibility identified
382 2: Trivially infeasible
383 message : str
384 A string descriptor of the exit status of the optimization.
386 References
387 ----------
388 .. [2] Andersen, Erling D. "Finding all linearly dependent rows in
389 large-scale linear programming." Optimization Methods and Software
390 6.3 (1995): 219-227.
392 """
394 A, b, status, message = _remove_zero_rows(A, b)
396 if status != 0:
397 return A, b, status, message
399 U, s, Vh = svd(A)
400 eps = np.finfo(float).eps
401 tol = s.max() * max(A.shape) * eps
403 m, n = A.shape
404 s_min = s[-1] if m <= n else 0
406 # this algorithm is faster than that of [2] when the nullspace is small
407 # but it could probably be improvement by randomized algorithms and with
408 # a sparse implementation.
409 # it relies on repeated singular value decomposition to find linearly
410 # dependent rows (as identified by columns of U that correspond with zero
411 # singular values). Unfortunately, only one row can be removed per
412 # decomposition (I tried otherwise; doing so can cause problems.)
413 # It would be nice if we could do truncated SVD like sp.sparse.linalg.svds
414 # but that function is unreliable at finding singular values near zero.
415 # Finding max eigenvalue L of A A^T, then largest eigenvalue (and
416 # associated eigenvector) of -A A^T + L I (I is identity) via power
417 # iteration would also work in theory, but is only efficient if the
418 # smallest nonzero eigenvalue of A A^T is close to the largest nonzero
419 # eigenvalue.
421 while abs(s_min) < tol:
422 v = U[:, -1] # TODO: return these so user can eliminate from problem?
423 # rows need to be represented in significant amount
424 eligibleRows = np.abs(v) > tol * 10e6
425 if not np.any(eligibleRows) or np.any(np.abs(v.dot(A)) > tol):
426 status = 4
427 message = ("Due to numerical issues, redundant equality "
428 "constraints could not be removed automatically. "
429 "Try providing your constraint matrices as sparse "
430 "matrices to activate sparse presolve, try turning "
431 "off redundancy removal, or try turning off presolve "
432 "altogether.")
433 break
434 if np.any(np.abs(v.dot(b)) > tol * 100): # factor of 100 to fix 10038 and 10349
435 status = 2
436 message = ("There is a linear combination of rows of A_eq that "
437 "results in zero, suggesting a redundant constraint. "
438 "However the same linear combination of b_eq is "
439 "nonzero, suggesting that the constraints conflict "
440 "and the problem is infeasible.")
441 break
443 i_remove = _get_densest(A, eligibleRows)
444 A = np.delete(A, i_remove, axis=0)
445 b = np.delete(b, i_remove)
446 U, s, Vh = svd(A)
447 m, n = A.shape
448 s_min = s[-1] if m <= n else 0
450 return A, b, status, message