def unique_equalityset(C, D, b, af, bf, abs_tol=1e-7, verbose=0): """Return equality set E with the following property: P_E = {x | af x = bf} intersection P where P is the polytope C x + D y < b The inequalities have to be satisfied with equality everywhere on the face defined by af and bf. """ if D is not None: A = np.hstack([C, D]) a = np.hstack([af, np.zeros(D.shape[1])]) else: A = C a = af E = [] for i in range(A.shape[0]): A_i = np.array(A[i, :]) b_i = b[i] sol = solvers._solve_lp_using_cvxopt(c=A_i, G=A, h=b, A=a.T, b=bf) if sol['status'] != "optimal": raise Exception("unique_equalityset: LP returned status " + str(sol['status'])) if np.abs(sol['primal objective'] - b_i) < abs_tol: # Constraint is active everywhere E.append(i) if len(E) == 0: raise Exception("unique_equalityset: empty E") return np.array(E)
def unique_equalityset(C, D, b, af, bf, abs_tol=1e-7, verbose=0): """Return equality set E with the following property: P_E = {x | af x = bf} intersection P where P is the polytope C x + D y < b The inequalities have to be satisfied with equality everywhere on the face defined by af and bf. """ if D is not None: A = np.hstack([C, D]) a = np.hstack([af, np.zeros(D.shape[1])]) else: A = C a = af E = [] for i in range(A.shape[0]): A_i = np.array(A[i, :]) b_i = b[i] sol = solvers._solve_lp_using_cvxopt( c=A_i, G=A, h=b, A=a.T, b=bf) if sol['status'] != "optimal": raise Exception( "unique_equalityset: LP returned status " + str(sol['status'])) if np.abs(sol['primal objective'] - b_i) < abs_tol: # Constraint is active everywhere E.append(i) if len(E) == 0: raise Exception("unique_equalityset: empty E") return np.array(E)
def is_dual_degenerate(c, G, h, A, b, x_opt, z_opt, abs_tol=1e-7): """Return `True` if pair of dual problems is dual degenerate. Checks if the pair of dual problems:: (P): min c'x (D): max h'z + b'y s.t Gx <= h s.t G'z + A'y = c Ax = b z <= 0 is dual degenerate, i.e. if (P) has several optimal solutions. Optimal solutions x* and z* are required. Input: `G,h,A,b`: Parameters of (P) `x_opt`: One optimal solution to (P) `z_opt`: The optimal solution to (D) corresponding to _inequality constraints_ in (P) Output: `dual`: Boolean indicating whether (P) has many optimal solutions. """ D = -G d = -h.flatten() mu = -z_opt.flatten() # mu >= 0 # Active constraints I = np.nonzero(np.abs(np.dot(D, x_opt).flatten() - d) < abs_tol)[0] # Positive elements in dual opt J = np.nonzero(mu > abs_tol)[0] # i, j i = mu < abs_tol # Zero elements in dual opt i = i.astype(int) j = np.zeros(len(mu), dtype=int) j[I] = 1 # 1 if active # Indices where active constraints have 0 dual opt L = np.nonzero(i + j == 2)[0] # sizes nI = len(I) nJ = len(J) nL = len(L) # constraints DI = D[I, :] # Active constraints DJ = D[J, :] # Constraints with positive lagrange mult DL = D[L, :] # Active constraints with zero dual opt dual = 0 if A is None: test = DI else: test = np.vstack([DI, A]) if rank(test) < np.amin(DI.shape): return True else: if len(L) > 0: if A is None: Ae = DJ else: Ae = np.vstack([DJ, A]) be = np.zeros(Ae.shape[0]) Ai = -DL bi = np.zeros(nL) sol = solvers._solve_lp_using_cvxopt(c=-np.sum(DL, axis=0), G=Ai, h=bi, A=Ae, b=be) if sol['status'] == "dual infeasible": # Dual infeasible -> primal unbounded -> value>epsilon return True if sol['primal objective'] > abs_tol: return True return False
def adjacent(C, D, b, rid_fac, abs_tol=1e-7): """Compute the (unique) adjacent facet. @param rid_fac: A Ridge_Facet object containing the parameters for a facet and one of its ridges. @return: (E_adj,a_adj,b_adj): The equality set and parameters for the adjacent facet such that:: P_{E_adj} = P intersection {x | a_adj x = b_adj} """ E = rid_fac.E_0 af = rid_fac.af bf = rid_fac.bf # E_r = rid_fac.E_r ar = rid_fac.ar br = rid_fac.br # shape d = C.shape[1] k = D.shape[1] # E_r slices C_er = C[E_r, :] D_er = D[E_r, :] b_er = b[E_r] # stack c = -np.hstack([ar, np.zeros(k)]) G = np.hstack([C_er, D_er]) h = b_er A = np.hstack([af, np.zeros(k)]) sol = solvers._solve_lp_using_cvxopt(c, G, h, A=A.T, b=bf * (1 - 0.01)) if sol['status'] != "optimal": print(G) print(h) print(af) print(bf) print(ar) print(br) print(np.dot(af, ar)) data = {} data["C"] = C data["D"] = D data["b"] = b sio.savemat("matlabdata", data) pickle.dump(data, open("polytope.p", "wb")) raise Exception("adjacent: Lp returned status " + str(sol['status'])) opt_sol = np.array(sol['x']).flatten() dual_opt_sol = np.array(sol['z']).flatten() x_opt = opt_sol[range(0, d)] y_opt = opt_sol[range(d, d + k)] if is_dual_degenerate(c.flatten(), G, h, A, bf * (1 - 0.01), opt_sol, dual_opt_sol, abs_tol=abs_tol): # If degenerate, compute affine hull and take preimage E_temp = np.nonzero(np.abs(np.dot(G, opt_sol) - h) < abs_tol)[0] a_temp, b_temp = proj_aff(C_er[E_temp, :], D_er[E_temp, :], b_er[E_temp], expected_dim=1, abs_tol=abs_tol) E_adj = unique_equalityset(C, D, b, a_temp, b_temp, abs_tol=abs_tol) if len(E_adj) == 0: data = {} data["C"] = C data["D"] = D data["b"] = b data["Er"] = E_r + 1 data["ar"] = ar data["br"] = br data["Ef"] = E + 1 data["af"] = af data["bf"] = bf sio.savemat("matlabdata", data) raise Exception( "adjacent: equality set computation returned empty set") else: r = np.abs(np.dot(C, x_opt) + np.dot(D, y_opt) - b) < abs_tol E_adj = np.nonzero(r)[0] C_eadj = C[E_adj, :] D_eadj = D[E_adj, :] b_eadj = b[E_adj] af_adj, bf_adj = proj_aff(C_eadj, D_eadj, b_eadj, abs_tol=abs_tol) return E_adj, af_adj, bf_adj
def ridge(C, D, b, E, af, bf, abs_tol=1e-7, verbose=0): """Compute all ridges of a facet in the projection. Input: `C,D,b`: Original polytope data `E,af,bf`: Equality set and affine hull of a facet in the projection Output: `ridge_list`: A list containing all the ridges of the facet as Ridge objects """ d = C.shape[1] k = D.shape[1] Er_list = [] q = C.shape[0] E_c = np.setdiff1d(range(q), E) # E slices C_E = C[E, :] D_E = D[E, :] b_E = b[E, :] # E_c slices C_Ec = C[E_c, :] D_Ec = D[E_c, :] b_Ec = b[E_c] # dots S = C_Ec - np.dot(np.dot(D_Ec, linalg.pinv(D_E)), C_E) L = np.dot(D_Ec, null_space(D_E)) t = b_Ec - np.dot(D_Ec, np.dot(linalg.pinv(D_E), b_E)) if rank(np.hstack([C_E, D_E])) < k + 1: if verbose > 1: print("Doing recursive ESP call") u, s, v = linalg.svd(np.array([af]), full_matrices=1) sigma = s[0] v = v.T * u[0, 0] # Correct sign V_hat = v[:, [0]] V_tilde = v[:, range(1, v.shape[1])] Cnew = np.dot(S, V_tilde) Dnew = L bnew = t - np.dot(S, V_hat).flatten() * bf / sigma Anew = np.hstack([Cnew, Dnew]) xc2, yc2, cen2 = cheby_center(Cnew, Dnew, bnew) bnew = bnew - np.dot(Cnew, xc2).flatten() - np.dot(Dnew, yc2).flatten() Gt, gt, E_t = esp(Cnew, Dnew, bnew, centered=True, abs_tol=abs_tol, verbose=0) if (len(E_t[0]) == 0) or (len(E_t[1]) == 0): raise Exception( "ridge: recursive call did not return any equality sets") for i in range(len(E_t)): E_f = E_t[i] er = np.sort(np.hstack([E, E_c[E_f]])) ar = np.dot(Gt[i, :], V_tilde.T).flatten() br0 = gt[i].flatten() # Make orthogonal to facet ar = ar - af * np.dot(af.flatten(), ar.flatten()) br = br0 - bf * np.dot(af.flatten(), ar.flatten()) # Normalize and make ridge equation point outwards norm = np.sqrt(np.sum(ar * ar)) ar = ar * np.sign(br) / norm br = br * np.sign(br) / norm # Restore center br = br + np.dot(Gt[i, :], xc2) / norm if len(ar) > d: raise Exception("ridge: wrong length of new ridge!") Er_list.append(Ridge(er, ar, br)) else: if verbose > 0: print("Doing direct calculation of ridges") X = np.arange(S.shape[0]) while len(X) > 0: i = X[0] X = np.setdiff1d(X, i) if np.linalg.norm(S[i, :]) < abs_tol: continue Si = S[i, :] Si = Si / np.linalg.norm(Si) if np.linalg.norm(af - np.dot(Si, af) * Si) > abs_tol: test1 = null_space(np.vstack( [np.hstack([af, bf]), np.hstack([S[i, :], t[i]])]), nonempty=True) test2 = np.hstack([S, np.array([t]).T]) test = np.dot(test1.T, test2.T) test = np.sum(np.abs(test), 0) Q_i = np.nonzero(test > abs_tol)[0] Q = np.nonzero(test < abs_tol)[0] X = np.setdiff1d(X, Q) # Have Q_i Sq = S[Q_i, :] tq = t[Q_i] c = np.zeros(d + 1) c[0] = 1 Gup = np.hstack([-np.ones([Sq.shape[0], 1]), Sq]) Gdo = np.hstack([-1, np.zeros(Sq.shape[1])]) G = np.vstack([Gup, Gdo]) h = np.hstack([tq, 1]) Al = np.zeros([2, 1]) Ar = np.vstack([af, S[i, :]]) A = np.hstack([Al, Ar]) bb = np.hstack([bf, t[i]]) sol = solvers._solve_lp_using_cvxopt(c, G, h, A=A, b=bb) if sol['status'] == 'optimal': tau = sol['x'][0] if tau < -abs_tol: ar = np.array([S[i, :]]).flatten() br = t[i].flatten() # Make orthogonal to facet ar = ar - af * np.dot(af.flatten(), ar.flatten()) br = br - bf * np.dot(af.flatten(), ar.flatten()) # Normalize and make ridge equation point outwards norm = np.sqrt(np.sum(ar * ar)) ar = ar / norm br = br / norm # accumulate Er_list.append( Ridge(np.sort(np.hstack([E, E_c[Q]])), ar, br)) return Er_list
def is_dual_degenerate(c, G, h, A, b, x_opt, z_opt, abs_tol=1e-7): """Return `True` if pair of dual problems is dual degenerate. Checks if the pair of dual problems:: (P): min c'x (D): max h'z + b'y s.t Gx <= h s.t G'z + A'y = c Ax = b z <= 0 is dual degenerate, i.e. if (P) has several optimal solutions. Optimal solutions x* and z* are required. Input: `G,h,A,b`: Parameters of (P) `x_opt`: One optimal solution to (P) `z_opt`: The optimal solution to (D) corresponding to _inequality constraints_ in (P) Output: `dual`: Boolean indicating whether (P) has many optimal solutions. """ D = - G d = - h.flatten() mu = - z_opt.flatten() # mu >= 0 # Active constraints I = np.nonzero(np.abs(np.dot(D, x_opt).flatten() - d) < abs_tol)[0] # Positive elements in dual opt J = np.nonzero(mu > abs_tol)[0] # i, j i = mu < abs_tol # Zero elements in dual opt i = i.astype(int) j = np.zeros(len(mu), dtype=int) j[I] = 1 # 1 if active # Indices where active constraints have 0 dual opt L = np.nonzero(i + j == 2)[0] # sizes nI = len(I) nJ = len(J) nL = len(L) # constraints DI = D[I, :] # Active constraints DJ = D[J, :] # Constraints with positive lagrange mult DL = D[L, :] # Active constraints with zero dual opt dual = 0 if A is None: test = DI else: test = np.vstack([DI, A]) if rank(test) < np.amin(DI.shape): return True else: if len(L) > 0: if A is None: Ae = DJ else: Ae = np.vstack([DJ, A]) be = np.zeros(Ae.shape[0]) Ai = - DL bi = np.zeros(nL) sol = solvers._solve_lp_using_cvxopt( c= - np.sum(DL, axis=0), G=Ai, h=bi, A=Ae, b=be) if sol['status'] == "dual infeasible": # Dual infeasible -> primal unbounded -> value>epsilon return True if sol['primal objective'] > abs_tol: return True return False
def adjacent(C, D, b, rid_fac, abs_tol=1e-7): """Compute the (unique) adjacent facet. @param rid_fac: A Ridge_Facet object containing the parameters for a facet and one of its ridges. @return: (E_adj,a_adj,b_adj): The equality set and parameters for the adjacent facet such that:: P_{E_adj} = P intersection {x | a_adj x = b_adj} """ E = rid_fac.E_0 af = rid_fac.af bf = rid_fac.bf # E_r = rid_fac.E_r ar = rid_fac.ar br = rid_fac.br # shape d = C.shape[1] k = D.shape[1] # E_r slices C_er = C[E_r, :] D_er = D[E_r, :] b_er = b[E_r] # stack c = -np.hstack([ar, np.zeros(k)]) G = np.hstack([C_er, D_er]) h = b_er A = np.hstack([af, np.zeros(k)]) sol = solvers._solve_lp_using_cvxopt( c, G, h, A=A.T, b=bf * (1 - 0.01)) if sol['status'] != "optimal": print(G) print(h) print(af) print(bf) print(ar) print(br) print(np.dot(af, ar)) data = {} data["C"] = C data["D"] = D data["b"] = b sio.savemat("matlabdata", data) pickle.dump(data, open("polytope.p", "wb")) raise Exception( "adjacent: Lp returned status " + str(sol['status'])) opt_sol = np.array(sol['x']).flatten() dual_opt_sol = np.array(sol['z']).flatten() x_opt = opt_sol[range(0, d)] y_opt = opt_sol[range(d, d + k)] if is_dual_degenerate( c.flatten(), G, h, A, bf * (1 - 0.01), opt_sol, dual_opt_sol, abs_tol=abs_tol): # If degenerate, compute affine hull and take preimage E_temp = np.nonzero(np.abs(np.dot(G, opt_sol) - h) < abs_tol)[0] a_temp, b_temp = proj_aff( C_er[E_temp, :], D_er[E_temp, :], b_er[E_temp], expected_dim=1, abs_tol=abs_tol) E_adj = unique_equalityset(C, D, b, a_temp, b_temp, abs_tol=abs_tol) if len(E_adj) == 0: data = {} data["C"] = C data["D"] = D data["b"] = b data["Er"] = E_r + 1 data["ar"] = ar data["br"] = br data["Ef"] = E + 1 data["af"] = af data["bf"] = bf sio.savemat("matlabdata", data) raise Exception( "adjacent: equality set computation returned empty set") else: r = np.abs(np.dot(C, x_opt) + np.dot(D, y_opt) - b) < abs_tol E_adj = np.nonzero(r)[0] C_eadj = C[E_adj, :] D_eadj = D[E_adj, :] b_eadj = b[E_adj] af_adj, bf_adj = proj_aff(C_eadj, D_eadj, b_eadj, abs_tol=abs_tol) return E_adj, af_adj, bf_adj
def ridge(C, D, b, E, af, bf, abs_tol=1e-7, verbose=0): """Compute all ridges of a facet in the projection. Input: `C,D,b`: Original polytope data `E,af,bf`: Equality set and affine hull of a facet in the projection Output: `ridge_list`: A list containing all the ridges of the facet as Ridge objects """ d = C.shape[1] k = D.shape[1] Er_list = [] q = C.shape[0] E_c = np.setdiff1d(range(q), E) # E slices C_E = C[E, :] D_E = D[E, :] b_E = b[E, :] # E_c slices C_Ec = C[E_c, :] D_Ec = D[E_c, :] b_Ec = b[E_c] # dots S = C_Ec - np.dot(np.dot(D_Ec, linalg.pinv(D_E)), C_E) L = np.dot(D_Ec, null_space(D_E)) t = b_Ec - np.dot(D_Ec, np.dot(linalg.pinv(D_E), b_E)) if rank(np.hstack([C_E, D_E])) < k + 1: if verbose > 1: print("Doing recursive ESP call") u, s, v = linalg.svd(np.array([af]), full_matrices=1) sigma = s[0] v = v.T * u[0, 0] # Correct sign V_hat = v[:, [0]] V_tilde = v[:, range(1, v.shape[1])] Cnew = np.dot(S, V_tilde) Dnew = L bnew = t - np.dot(S, V_hat).flatten() * bf / sigma Anew = np.hstack([Cnew, Dnew]) xc2, yc2, cen2 = cheby_center(Cnew, Dnew, bnew) bnew = bnew - np.dot(Cnew, xc2).flatten() - np.dot(Dnew, yc2).flatten() Gt, gt, E_t = esp( Cnew, Dnew, bnew, centered=True, abs_tol=abs_tol, verbose=0) if (len(E_t[0]) == 0) or (len(E_t[1]) == 0): raise Exception( "ridge: recursive call did not return any equality sets") for i in range(len(E_t)): E_f = E_t[i] er = np.sort(np.hstack([E, E_c[E_f]])) ar = np.dot(Gt[i, :], V_tilde.T).flatten() br0 = gt[i].flatten() # Make orthogonal to facet ar = ar - af * np.dot(af.flatten(), ar.flatten()) br = br0 - bf * np.dot(af.flatten(), ar.flatten()) # Normalize and make ridge equation point outwards norm = np.sqrt(np.sum(ar * ar)) ar = ar * np.sign(br) / norm br = br * np.sign(br) / norm # Restore center br = br + np.dot(Gt[i, :], xc2) / norm if len(ar) > d: raise Exception("ridge: wrong length of new ridge!") Er_list.append(Ridge(er, ar, br)) else: if verbose > 0: print("Doing direct calculation of ridges") X = np.arange(S.shape[0]) while len(X) > 0: i = X[0] X = np.setdiff1d(X, i) if np.linalg.norm(S[i, :]) < abs_tol: continue Si = S[i, :] Si = Si / np.linalg.norm(Si) if np.linalg.norm(af - np.dot(Si, af) * Si) > abs_tol: test1 = null_space( np.vstack([ np.hstack([af, bf]), np.hstack([S[i, :], t[i]])]), nonempty=True) test2 = np.hstack([S, np.array([t]).T]) test = np.dot(test1.T, test2.T) test = np.sum(np.abs(test), 0) Q_i = np.nonzero(test > abs_tol)[0] Q = np.nonzero(test < abs_tol)[0] X = np.setdiff1d(X, Q) # Have Q_i Sq = S[Q_i, :] tq = t[Q_i] c = np.zeros(d + 1) c[0] = 1 Gup = np.hstack([-np.ones([Sq.shape[0], 1]), Sq]) Gdo = np.hstack([-1, np.zeros(Sq.shape[1])]) G = np.vstack([Gup, Gdo]) h = np.hstack([tq, 1]) Al = np.zeros([2, 1]) Ar = np.vstack([af, S[i, :]]) A = np.hstack([Al, Ar]) bb = np.hstack([bf, t[i]]) sol = solvers._solve_lp_using_cvxopt( c, G, h, A=A, b=bb) if sol['status'] == 'optimal': tau = sol['x'][0] if tau < -abs_tol: ar = np.array([S[i, :]]).flatten() br = t[i].flatten() # Make orthogonal to facet ar = ar - af * np.dot(af.flatten(), ar.flatten()) br = br - bf * np.dot(af.flatten(), ar.flatten()) # Normalize and make ridge equation point outwards norm = np.sqrt(np.sum(ar * ar)) ar = ar / norm br = br / norm # accumulate Er_list.append( Ridge(np.sort(np.hstack([E, E_c[Q]])), ar, br)) return Er_list