| """ |
| Routines for removing redundant (linearly dependent) equations from linear |
| programming equality constraints. |
| """ |
| |
|
|
| import numpy as np |
| from scipy.linalg import svd |
| from scipy.linalg.interpolative import interp_decomp |
| import scipy |
| from scipy.linalg.blas import dtrsm |
|
|
|
|
| def _row_count(A): |
| """ |
| Counts the number of nonzeros in each row of input array A. |
| Nonzeros are defined as any element with absolute value greater than |
| tol = 1e-13. This value should probably be an input to the function. |
| |
| Parameters |
| ---------- |
| A : 2-D array |
| An array representing a matrix |
| |
| Returns |
| ------- |
| rowcount : 1-D array |
| Number of nonzeros in each row of A |
| |
| """ |
| tol = 1e-13 |
| return np.array((abs(A) > tol).sum(axis=1)).flatten() |
|
|
|
|
| def _get_densest(A, eligibleRows): |
| """ |
| Returns the index of the densest row of A. Ignores rows that are not |
| eligible for consideration. |
| |
| Parameters |
| ---------- |
| A : 2-D array |
| An array representing a matrix |
| eligibleRows : 1-D logical array |
| Values indicate whether the corresponding row of A is eligible |
| to be considered |
| |
| Returns |
| ------- |
| i_densest : int |
| Index of the densest row in A eligible for consideration |
| |
| """ |
| rowCounts = _row_count(A) |
| return np.argmax(rowCounts * eligibleRows) |
|
|
|
|
| def _remove_zero_rows(A, b): |
| """ |
| Eliminates trivial equations from system of equations defined by Ax = b |
| and identifies trivial infeasibilities |
| |
| Parameters |
| ---------- |
| A : 2-D array |
| An array representing the left-hand side of a system of equations |
| b : 1-D array |
| An array representing the right-hand side of a system of equations |
| |
| Returns |
| ------- |
| A : 2-D array |
| An array representing the left-hand side of a system of equations |
| b : 1-D array |
| An array representing the right-hand side of a system of equations |
| status: int |
| An integer indicating the status of the removal operation |
| 0: No infeasibility identified |
| 2: Trivially infeasible |
| message : str |
| A string descriptor of the exit status of the optimization. |
| |
| """ |
| status = 0 |
| message = "" |
| i_zero = _row_count(A) == 0 |
| A = A[np.logical_not(i_zero), :] |
| if not np.allclose(b[i_zero], 0): |
| status = 2 |
| message = "There is a zero row in A_eq with a nonzero corresponding " \ |
| "entry in b_eq. The problem is infeasible." |
| b = b[np.logical_not(i_zero)] |
| return A, b, status, message |
|
|
|
|
| def bg_update_dense(plu, perm_r, v, j): |
| LU, p = plu |
|
|
| vperm = v[perm_r] |
| u = dtrsm(1, LU, vperm, lower=1, diag=1) |
| LU[:j+1, j] = u[:j+1] |
| l = u[j+1:] |
| piv = LU[j, j] |
| LU[j+1:, j] += (l/piv) |
| return LU, p |
|
|
|
|
| def _remove_redundancy_pivot_dense(A, rhs, true_rank=None): |
| """ |
| Eliminates redundant equations from system of equations defined by Ax = b |
| and identifies infeasibilities. |
| |
| Parameters |
| ---------- |
| A : 2-D sparse matrix |
| An matrix representing the left-hand side of a system of equations |
| rhs : 1-D array |
| An array representing the right-hand side of a system of equations |
| |
| Returns |
| ------- |
| A : 2-D sparse matrix |
| A matrix representing the left-hand side of a system of equations |
| rhs : 1-D array |
| An array representing the right-hand side of a system of equations |
| status: int |
| An integer indicating the status of the system |
| 0: No infeasibility identified |
| 2: Trivially infeasible |
| message : str |
| A string descriptor of the exit status of the optimization. |
| |
| References |
| ---------- |
| .. [2] Andersen, Erling D. "Finding all linearly dependent rows in |
| large-scale linear programming." Optimization Methods and Software |
| 6.3 (1995): 219-227. |
| |
| """ |
| tolapiv = 1e-8 |
| tolprimal = 1e-8 |
| status = 0 |
| message = "" |
| inconsistent = ("There is a linear combination of rows of A_eq that " |
| "results in zero, suggesting a redundant constraint. " |
| "However the same linear combination of b_eq is " |
| "nonzero, suggesting that the constraints conflict " |
| "and the problem is infeasible.") |
| A, rhs, status, message = _remove_zero_rows(A, rhs) |
|
|
| if status != 0: |
| return A, rhs, status, message |
|
|
| m, n = A.shape |
|
|
| v = list(range(m)) |
| b = list(v) |
| |
| |
| d = [] |
| perm_r = None |
|
|
| A_orig = A |
| A = np.zeros((m, m + n), order='F') |
| np.fill_diagonal(A, 1) |
| A[:, m:] = A_orig |
| e = np.zeros(m) |
|
|
| js_candidates = np.arange(m, m+n, dtype=int) |
| |
| js_mask = np.ones(js_candidates.shape, dtype=bool) |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| lu = np.eye(m, order='F'), np.arange(m) |
| perm_r = lu[1] |
| for i in v: |
|
|
| e[i] = 1 |
| if i > 0: |
| e[i-1] = 0 |
|
|
| try: |
| j = b[i-1] |
| lu = bg_update_dense(lu, perm_r, A[:, j], i-1) |
| except Exception: |
| lu = scipy.linalg.lu_factor(A[:, b]) |
| LU, p = lu |
| perm_r = list(range(m)) |
| for i1, i2 in enumerate(p): |
| perm_r[i1], perm_r[i2] = perm_r[i2], perm_r[i1] |
|
|
| pi = scipy.linalg.lu_solve(lu, e, trans=1) |
|
|
| js = js_candidates[js_mask] |
| batch = 50 |
|
|
| |
| |
| for j_index in range(0, len(js), batch): |
| j_indices = js[j_index: min(j_index+batch, len(js))] |
|
|
| c = abs(A[:, j_indices].transpose().dot(pi)) |
| if (c > tolapiv).any(): |
| j = js[j_index + np.argmax(c)] |
| b[i] = j |
| js_mask[j-m] = False |
| break |
| else: |
| bibar = pi.T.dot(rhs.reshape(-1, 1)) |
| bnorm = np.linalg.norm(rhs) |
| if abs(bibar)/(1+bnorm) > tolprimal: |
| status = 2 |
| message = inconsistent |
| return A_orig, rhs, status, message |
| else: |
| d.append(i) |
| if true_rank is not None and len(d) == m - true_rank: |
| break |
|
|
| keep = set(range(m)) |
| keep = list(keep - set(d)) |
| return A_orig[keep, :], rhs[keep], status, message |
|
|
|
|
| def _remove_redundancy_pivot_sparse(A, rhs): |
| """ |
| Eliminates redundant equations from system of equations defined by Ax = b |
| and identifies infeasibilities. |
| |
| Parameters |
| ---------- |
| A : 2-D sparse matrix |
| An matrix representing the left-hand side of a system of equations |
| rhs : 1-D array |
| An array representing the right-hand side of a system of equations |
| |
| Returns |
| ------- |
| A : 2-D sparse matrix |
| A matrix representing the left-hand side of a system of equations |
| rhs : 1-D array |
| An array representing the right-hand side of a system of equations |
| status: int |
| An integer indicating the status of the system |
| 0: No infeasibility identified |
| 2: Trivially infeasible |
| message : str |
| A string descriptor of the exit status of the optimization. |
| |
| References |
| ---------- |
| .. [2] Andersen, Erling D. "Finding all linearly dependent rows in |
| large-scale linear programming." Optimization Methods and Software |
| 6.3 (1995): 219-227. |
| |
| """ |
|
|
| tolapiv = 1e-8 |
| tolprimal = 1e-8 |
| status = 0 |
| message = "" |
| inconsistent = ("There is a linear combination of rows of A_eq that " |
| "results in zero, suggesting a redundant constraint. " |
| "However the same linear combination of b_eq is " |
| "nonzero, suggesting that the constraints conflict " |
| "and the problem is infeasible.") |
| A, rhs, status, message = _remove_zero_rows(A, rhs) |
|
|
| if status != 0: |
| return A, rhs, status, message |
|
|
| m, n = A.shape |
|
|
| v = list(range(m)) |
| b = list(v) |
| |
| |
| k = set(range(m, m+n)) |
| d = [] |
|
|
| A_orig = A |
| A = scipy.sparse.hstack((scipy.sparse.eye(m), A)).tocsc() |
| e = np.zeros(m) |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| for i in v: |
| B = A[:, b] |
|
|
| e[i] = 1 |
| if i > 0: |
| e[i-1] = 0 |
|
|
| pi = scipy.sparse.linalg.spsolve(B.transpose(), e).reshape(-1, 1) |
|
|
| js = list(k-set(b)) |
|
|
| |
| |
| |
| |
| |
| |
|
|
| c = (np.abs(A[:, js].transpose().dot(pi)) > tolapiv).nonzero()[0] |
| if len(c) > 0: |
| j = js[c[0]] |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| b[i] = j |
| else: |
| bibar = pi.T.dot(rhs.reshape(-1, 1)) |
| bnorm = np.linalg.norm(rhs) |
| if abs(bibar)/(1 + bnorm) > tolprimal: |
| status = 2 |
| message = inconsistent |
| return A_orig, rhs, status, message |
| else: |
| d.append(i) |
|
|
| keep = set(range(m)) |
| keep = list(keep - set(d)) |
| return A_orig[keep, :], rhs[keep], status, message |
|
|
|
|
| def _remove_redundancy_svd(A, b): |
| """ |
| Eliminates redundant equations from system of equations defined by Ax = b |
| and identifies infeasibilities. |
| |
| Parameters |
| ---------- |
| A : 2-D array |
| An array representing the left-hand side of a system of equations |
| b : 1-D array |
| An array representing the right-hand side of a system of equations |
| |
| Returns |
| ------- |
| A : 2-D array |
| An array representing the left-hand side of a system of equations |
| b : 1-D array |
| An array representing the right-hand side of a system of equations |
| status: int |
| An integer indicating the status of the system |
| 0: No infeasibility identified |
| 2: Trivially infeasible |
| message : str |
| A string descriptor of the exit status of the optimization. |
| |
| References |
| ---------- |
| .. [2] Andersen, Erling D. "Finding all linearly dependent rows in |
| large-scale linear programming." Optimization Methods and Software |
| 6.3 (1995): 219-227. |
| |
| """ |
|
|
| A, b, status, message = _remove_zero_rows(A, b) |
|
|
| if status != 0: |
| return A, b, status, message |
|
|
| U, s, Vh = svd(A) |
| eps = np.finfo(float).eps |
| tol = s.max() * max(A.shape) * eps |
|
|
| m, n = A.shape |
| s_min = s[-1] if m <= n else 0 |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| while abs(s_min) < tol: |
| v = U[:, -1] |
| |
| eligibleRows = np.abs(v) > tol * 10e6 |
| if not np.any(eligibleRows) or np.any(np.abs(v.dot(A)) > tol): |
| status = 4 |
| message = ("Due to numerical issues, redundant equality " |
| "constraints could not be removed automatically. " |
| "Try providing your constraint matrices as sparse " |
| "matrices to activate sparse presolve, try turning " |
| "off redundancy removal, or try turning off presolve " |
| "altogether.") |
| break |
| if np.any(np.abs(v.dot(b)) > tol * 100): |
| status = 2 |
| message = ("There is a linear combination of rows of A_eq that " |
| "results in zero, suggesting a redundant constraint. " |
| "However the same linear combination of b_eq is " |
| "nonzero, suggesting that the constraints conflict " |
| "and the problem is infeasible.") |
| break |
|
|
| i_remove = _get_densest(A, eligibleRows) |
| A = np.delete(A, i_remove, axis=0) |
| b = np.delete(b, i_remove) |
| U, s, Vh = svd(A) |
| m, n = A.shape |
| s_min = s[-1] if m <= n else 0 |
|
|
| return A, b, status, message |
|
|
|
|
| def _remove_redundancy_id(A, rhs, rank=None, randomized=True): |
| """Eliminates redundant equations from a system of equations. |
| |
| Eliminates redundant equations from system of equations defined by Ax = b |
| and identifies infeasibilities. |
| |
| Parameters |
| ---------- |
| A : 2-D array |
| An array representing the left-hand side of a system of equations |
| rhs : 1-D array |
| An array representing the right-hand side of a system of equations |
| rank : int, optional |
| The rank of A |
| randomized: bool, optional |
| True for randomized interpolative decomposition |
| |
| Returns |
| ------- |
| A : 2-D array |
| An array representing the left-hand side of a system of equations |
| rhs : 1-D array |
| An array representing the right-hand side of a system of equations |
| status: int |
| An integer indicating the status of the system |
| 0: No infeasibility identified |
| 2: Trivially infeasible |
| message : str |
| A string descriptor of the exit status of the optimization. |
| |
| """ |
|
|
| status = 0 |
| message = "" |
| inconsistent = ("There is a linear combination of rows of A_eq that " |
| "results in zero, suggesting a redundant constraint. " |
| "However the same linear combination of b_eq is " |
| "nonzero, suggesting that the constraints conflict " |
| "and the problem is infeasible.") |
|
|
| A, rhs, status, message = _remove_zero_rows(A, rhs) |
|
|
| if status != 0: |
| return A, rhs, status, message |
|
|
| m, n = A.shape |
|
|
| k = rank |
| if rank is None: |
| k = np.linalg.matrix_rank(A) |
|
|
| idx, proj = interp_decomp(A.T, k, rand=randomized) |
|
|
| |
| |
| |
| |
| |
| |
| if not np.allclose(rhs[idx[:k]] @ proj, rhs[idx[k:]]): |
| status = 2 |
| message = inconsistent |
|
|
| |
| |
| idx = sorted(idx[:k]) |
| A2 = A[idx, :] |
| rhs2 = rhs[idx] |
| return A2, rhs2, status, message |
|
|