def test_merge_nc(self): symb = cp.symbolic(self.A_nc, p = None, merge_function = cp.merge_size_fill(0,0)) self.assertEqual(symb.n, 23) self.assertEqual(symb.nnz, 150) self.assertEqual(symb.clique_number, 12) self.assertEqual(symb.Nsn, 10) self.assertEqual(symb.fill,(73,0)) p = amd.order(self.A_nc) symb = cp.symbolic(self.A_nc, p = p, merge_function = cp.merge_size_fill(4,4)) self.assertEqual(symb.n, 23) self.assertTrue(symb.nnz > 150) self.assertTrue(symb.Nsn < 10) self.assertTrue(symb.fill[0] >= 36) self.assertTrue(symb.fill[1] > 0)
def test_merge(self): symb = cp.symbolic(self.A, p = None, merge_function = cp.merge_size_fill(0,0)) self.assertEqual(symb.n, 17) self.assertEqual(symb.nnz, 56) self.assertEqual(symb.Nsn, 9) self.assertEqual(symb.fill,(0,0)) self.assertEqual(symb.clique_number, 5) #self.assertEqual(symb.p, None) #self.assertEqual(symb.ip, None) symb = cp.symbolic(self.A, p = None, merge_function = cp.merge_size_fill(2,2)) self.assertEqual(symb.n, 17) self.assertTrue(symb.nnz > 56) self.assertTrue(symb.Nsn < 9) self.assertTrue(symb.fill[0] >= 0) self.assertTrue(symb.fill[1] > 0)
def embed_SDP(P,order="AMD",cholmod=False): if not isinstance(P,SDP): raise ValueError, "not an SDP object" if order=='AMD': from cvxopt.amd import order elif order=='METIS': from cvxopt.metis import order else: raise ValueError, "unknown ordering: %s " %(order) p = order(P.V) if cholmod: from cvxopt import cholmod V = +P.V + spmatrix([float(i+1) for i in xrange(P.n)],xrange(P.n),xrange(P.n)) F = cholmod.symbolic(V,p=p) cholmod.numeric(V,F) f = cholmod.getfactor(F) fd = [(j,i) for i,j in enumerate(f[:P.n**2:P.n+1])] fd.sort() ip = matrix([j for _,j in fd]) Ve = chompack.tril(chompack.perm(chompack.symmetrize(f),ip)) Ie = misc.sub2ind((P.n,P.n),Ve.I,Ve.J) else: #Vc,n = chompack.embed(P.V,p) symb = chompack.symbolic(P.V,p) #Ve = chompack.sparse(Vc) Ve = symb.sparsity_pattern(reordered=False) Ie = misc.sub2ind((P.n,P.n),Ve.I,Ve.J) Pe = SDP() Pe._A = +P.A; Pe._b = +P.b Pe._A[:,0] += spmatrix(0.0,Ie,[0 for i in range(len(Ie))],(Pe._A.size[0],1)) Pe._agg_sparsity() Pe._pname = P._pname + "_embed" Pe._ischordal = True; Pe._blockstruct = P._blockstruct return Pe
def test_symbolic_nc(self): symb = cp.symbolic(self.A_nc, p = None) self.assertEqual(symb.n, 23) self.assertEqual(symb.nnz, 150) self.assertEqual(symb.clique_number, 12) self.assertEqual(symb.Nsn, 10) self.assertEqual(symb.fill,(73,0)) #self.assertEqual(symb.p, None) #self.assertEqual(symb.ip, None) p = amd.order(self.A_nc) symb = cp.symbolic(self.A_nc, p = p) self.assertEqual(symb.n, 23) self.assertEqual(symb.nnz, 113) self.assertEqual(symb.clique_number, 9) self.assertEqual(symb.Nsn, 15) self.assertEqual(symb.fill,(36,0))
def test_symbolic(self): symb = cp.symbolic(self.A, p = None) self.assertEqual(symb.n, 17) self.assertEqual(symb.nnz, 56) self.assertEqual(symb.clique_number, 5) self.assertEqual(symb.Nsn, 9) self.assertEqual(symb.fill,(0,0))
def test_symbolic_nc(self): symb = cp.symbolic(self.A_nc, p=None) self.assertEqual(symb.n, 23) self.assertEqual(symb.nnz, 150) self.assertEqual(symb.clique_number, 12) self.assertEqual(symb.Nsn, 10) self.assertEqual(symb.fill, (73, 0)) #self.assertEqual(symb.p, None) #self.assertEqual(symb.ip, None) p = amd.order(self.A_nc) symb = cp.symbolic(self.A_nc, p=p) self.assertEqual(symb.n, 23) self.assertEqual(symb.nnz, 113) self.assertEqual(symb.clique_number, 9) self.assertEqual(symb.Nsn, 15) self.assertEqual(symb.fill, (36, 0))
def setUp(self): n = 31 nnz = int(round(0.15 * n**2)) random.seed(1) nz = matrix(random.sample(range(n**2), nnz), tc='i') self.A = cp.tril(spmatrix(matrix(range(1,nnz+1),tc='d')/nnz, nz%n, matrix([int(ii) for ii in nz/n]), (n,n)))\ + spmatrix(10.0,range(n),range(n)) self.symb = cp.symbolic(self.A, p=amd.order)
def test_symbolic(self): symb = cp.symbolic(self.A, p=None) self.assertEqual(symb.n, 17) self.assertEqual(symb.nnz, 56) self.assertEqual(symb.clique_number, 5) self.assertEqual(symb.Nsn, 9) self.assertEqual(symb.fill, (0, 0))
def setUp(self): n = 31 nnz = int(round(0.15*n**2)) random.seed(1) nz = matrix(random.sample(range(n**2), nnz), tc='i') self.A = cp.tril(spmatrix(matrix(range(1,nnz+1),tc='d')/nnz, nz%n, matrix([int(ii) for ii in nz/n]), (n,n)))\ + spmatrix(10.0,range(n),range(n)) self.symb = cp.symbolic(self.A, p = amd.order)
def sparsedecomp(A, LB, LC): # check if A is square matrix if A.size[0] != A.size[1]: print "Matrix dimensions does not match. A matrix must be square." return # check if other matrices have same dimension as A if LB != []: for i in LB: if i.size != A.size: print "Dimension of equality matrices (LB) do not match. Must have same dimension as A matrix" return # check if other matrices have same dimension as A if LC != []: for i in LC: if i.size != A.size: print "Dimension of inequality matrices (LC) do not match. Must have same dimension as A matrix" return # generate maximum sparsity pattern M = copy(A) for i in LC: M += abs(i) for i in LB: M += abs(i) MC = cp.symbolic(cv.sparse(M), p=cv.amd.order) # All maximal cliques of objective # matrix A are extracted and returned as a list dA = clique_extract(MC, A) # All maximal cliques of all (in-)equality constraint matrices are extracted and returned as a list dLB = [] if LB != []: for i in LB: dLB.append(clique_extract(MC, i)) # All maximal cliques of all (in-)equality constraint matrices are extracted and returned as a list dLC = [] if LC != []: for i in LC: dLC.append(clique_extract(MC, i)) # permutation matrix P = cv.spmatrix(1, MC.p, range(MC.sparsity_pattern().size[0])) E, H = eq_const(MC) # dA = list containing all cliques of A [CHECK] # dLB = 2D-List containing all cliques of every equality constraint [CHECK] # list of equality contraints # permutation list/matrix [CHECK] return dA, dLB, dLC, P, E, H
def clique_hist(P, file=None): if not P.ischordal: raise TypeError("Nonchordal") if file == None: plt.ion() else: plt.ioff() V = +P.V p = chompack.maxcardsearch(V) #Vc,n = chompack.embed(V,p) symb = chompack.symbolic(V, p) #D = chompack.info(Vc); N = len(D) N = symb.Nsn #Ns = [len(v['S']) for v in D]; Nu = [len(v['U']) for v in D] #Nw = [len(v['U']) + len(v['S']) for v in D] Ns = [len(v) for v in symb.supernodes()] Nu = [len(v) for v in symb.separators()] Nw = [len(v) for v in symb.cliques()] f = plt.figure() f.clf() f.text(0.58, 0.40, "Number of cliques: %i" % (len(Nw))) f.text(0.61, 0.40 - 1 * 0.07, "$\sum | W_i | = %i$" % (sum(Nw))) f.text(0.61, 0.40 - 2 * 0.07, "$\sum | V_i | = %i$" % (sum(Ns))) f.text(0.61, 0.40 - 3 * 0.07, "$\sum | U_i | = %i$" % (sum(Nu))) f.text(0.61, 0.40 - 4 * 0.07, "$\max_i\,| W_i | = %i$" % (max(Nw))) plt.subplot(221) Nmax = max(Nw) y = matrix(0, (Nmax, 1)) for n in Nw: y[n - 1] += 1 y = sparse(y) plt.stem(y.I + 1, y.V, 'k') plt.xlim(0, Nmax + 1) plt.title("Cliques") Nmax = max(Nu) y = matrix(0, (Nmax, 1)) if Nmax > 0: plt.subplot(222) for n in Nu: y[n - 1] += 1 y = sparse(y) plt.stem(y.I + 1, y.V, 'k') plt.xlim(0, Nmax + 1) plt.title("Separators") plt.subplot(223) Nmax = max(Ns) y = matrix(0, (Nmax, 1)) for n in Ns: y[n - 1] += 1 y = sparse(y) plt.stem(y.I + 1, y.V, 'k') plt.xlim(0, Nmax + 1) plt.title("Residuals") if file: plt.savefig(file)
def test_merge_nc(self): symb = cp.symbolic(self.A_nc, p=None, merge_function=cp.merge_size_fill(0, 0)) self.assertEqual(symb.n, 23) self.assertEqual(symb.nnz, 150) self.assertEqual(symb.clique_number, 12) self.assertEqual(symb.Nsn, 10) self.assertEqual(symb.fill, (73, 0)) p = amd.order(self.A_nc) symb = cp.symbolic(self.A_nc, p=p, merge_function=cp.merge_size_fill(4, 4)) self.assertEqual(symb.n, 23) self.assertTrue(symb.nnz > 150) self.assertTrue(symb.Nsn < 10) self.assertTrue(symb.fill[0] >= 36) self.assertTrue(symb.fill[1] > 0)
def test_merge(self): symb = cp.symbolic(self.A, p=None, merge_function=cp.merge_size_fill(0, 0)) self.assertEqual(symb.n, 17) self.assertEqual(symb.nnz, 56) self.assertEqual(symb.Nsn, 9) self.assertEqual(symb.fill, (0, 0)) self.assertEqual(symb.clique_number, 5) #self.assertEqual(symb.p, None) #self.assertEqual(symb.ip, None) symb = cp.symbolic(self.A, p=None, merge_function=cp.merge_size_fill(2, 2)) self.assertEqual(symb.n, 17) self.assertTrue(symb.nnz > 56) self.assertTrue(symb.Nsn < 9) self.assertTrue(symb.fill[0] >= 0) self.assertTrue(symb.fill[1] > 0)
def clique_hist(P,file=None): if not P.ischordal: raise TypeError, "Nonchordal" if file==None: pylab.ion() else: pylab.ioff() V = +P.V p = chompack.maxcardsearch(V) #Vc,n = chompack.embed(V,p) symb = chompack.symbolic(V,p) #D = chompack.info(Vc); N = len(D) N = symb.Nsn #Ns = [len(v['S']) for v in D]; Nu = [len(v['U']) for v in D] #Nw = [len(v['U']) + len(v['S']) for v in D] Ns = [len(v) for v in symb.supernodes()] Nu = [len(v) for v in symb.separators()] Nw = [len(v) for v in symb.cliques()] f = pylab.figure(); f.clf() f.text(0.58,0.40,"Number of cliques: %i" % (len(Nw))) f.text(0.61,0.40-1*0.07,"$\sum | W_i | = %i$" % (sum(Nw))) f.text(0.61,0.40-2*0.07,"$\sum | V_i | = %i$" % (sum(Ns))) f.text(0.61,0.40-3*0.07,"$\sum | U_i | = %i$" % (sum(Nu))) f.text(0.61,0.40-4*0.07,"$\max_i\,| W_i | = %i$" % (max(Nw))) pylab.subplot(221) Nmax = max(Nw) y = matrix(0,(Nmax,1)) for n in Nw : y[n-1] += 1 y = sparse(y) pylab.stem(y.I+1,y.V,'k'); pylab.xlim(0, Nmax+1) pylab.title("Cliques") Nmax = max(Nu) y = matrix(0,(Nmax,1)) if Nmax > 0: pylab.subplot(222) for n in Nu : y[n-1] += 1 y = sparse(y) pylab.stem(y.I+1,y.V,'k'); pylab.xlim(0, Nmax+1) pylab.title("Separators") pylab.subplot(223) Nmax = max(Ns) y = matrix(0,(Nmax,1)) for n in Ns : y[n-1] += 1 y = sparse(y) pylab.stem(y.I+1,y.V,'k'); pylab.xlim(0, Nmax+1) pylab.title("Residuals") if file: pylab.savefig(file)
def setUp(self): I = list(range(17)) + [ 2, 2, 3, 3, 4, 14, 4, 14, 8, 14, 15, 8, 15, 7, 8, 14, 8, 14, 14, 15, 10, 12, 13, 16, 12, 13, 16, 12, 13, 15, 16, 13, 15, 16, 15, 16, 15, 16, 16 ] J = list(range(17)) + [ 0, 1, 1, 2, 2, 2, 3, 3, 4, 4, 4, 5, 5, 6, 6, 6, 7, 7, 8, 8, 9, 9, 9, 9, 10, 10, 10, 11, 11, 11, 11, 12, 12, 12, 13, 13, 14, 14, 15 ] self.A = spmatrix(matrix(range(len(I)), tc='d'), I, J, (17, 17)) self.symb = cp.symbolic(self.A, p=amd.order)
def setUp(self): I = list(range(17)) + [ 2, 2, 3, 3, 4, 14, 4, 14, 8, 14, 15, 8, 15, 7, 8, 14, 8, 14, 14, 15, 10, 12, 13, 16, 12, 13, 16, 12, 13, 15, 16, 13, 15, 16, 15, 16, 15, 16, 16 ] J = list(range(17)) + [ 0, 1, 1, 2, 2, 2, 3, 3, 4, 4, 4, 5, 5, 6, 6, 6, 7, 7, 8, 8, 9, 9, 9, 9, 10, 10, 10, 11, 11, 11, 11, 12, 12, 12, 13, 13, 14, 14, 15 ] self.A = spmatrix(1.0, I, J, (17, 17)) self.A[0, 0] += 1.0 self.A[1, 1] += 1.0 self.symb = cp.symbolic(self.A, p=amd.order)
def mk_rand(V, cone='posdef', seed=0): """ Generates random matrix U with sparsity pattern V. - U is positive definite if cone is 'posdef'. - U is completable if cone is 'completable'. """ if not (cone == 'posdef' or cone == 'completable'): raise ValueError, "cone must be 'posdef' (default) or 'completable' " from cvxopt import amd setseed(seed) n = V.size[0] U = +V U.V *= 0.0 for i in xrange(n): u = normal(n, 1) / sqrt(n) base.syrk(u, U, beta=1.0, partial=True) # test if U is in cone: if not, add multiple of identity t = 0.1 Ut = +U p = amd.order(Ut) # Vc, NF = chompack.embed(U,p) symb = chompack.symbolic(U, p) Vc = chompack.cspmatrix(symb) + U while True: # Uc = chompack.project(Vc,Ut) Uc = chompack.cspmatrix(symb) + Ut try: if cone == 'posdef': # positive definite? # Y = chompack.cholesky(Uc) Y = Uc.copy() chompack.cholesky(Y) elif cone == 'completable': # completable? # Y = chompack.completion(Uc) Y = Uc.copy() chompack.completion(Y) # Success: Ut is in cone U = +Ut break except: Ut = U + spmatrix(t, xrange(n), xrange(n), (n, n)) t *= 2.0 return U
def mk_rand(V,cone='posdef',seed=0): """ Generates random matrix U with sparsity pattern V. - U is positive definite if cone is 'posdef'. - U is completable if cone is 'completable'. """ if not (cone=='posdef' or cone=='completable'): raise ValueError("cone must be 'posdef' (default) or 'completable' ") from cvxopt import amd setseed(seed) n = V.size[0] U = +V U.V *= 0.0 for i in range(n): u = normal(n,1)/sqrt(n) base.syrk(u,U,beta=1.0,partial=True) # test if U is in cone: if not, add multiple of identity t = 0.1; Ut = +U p = amd.order(Ut) # Vc, NF = chompack.embed(U,p) symb = chompack.symbolic(U,p) Vc = chompack.cspmatrix(symb) + U while True: # Uc = chompack.project(Vc,Ut) Uc = chompack.cspmatrix(symb) + Ut try: if cone=='posdef': # positive definite? # Y = chompack.cholesky(Uc) Y = Uc.copy() chompack.cholesky(Y) elif cone=='completable': # completable? # Y = chompack.completion(Uc) Y = Uc.copy() chompack.completion(Y) # Success: Ut is in cone U = +Ut break except: Ut = U + spmatrix(t,range(n),range(n),(n,n)) t*=2.0 return U
def completion(X): """ Returns maximum-determinant positive definite completion of X if it exists, and otherwise an exception is raised. """ from cvxopt.amd import order n = X.size[0] Xt = chompack.tril(X) p = order(Xt) # Xc,N = chompack.embed(Xt,p) # L = chompack.completion(Xc) symb = chompack.symbolic(Xt,p) L = chompack.cspmatrix(symb) + Xt chompack.completion(L) Xt = matrix(0.,(n,n)) Xt[::n+1] = 1. # chompack.solve(L, Xt, mode=0) # chompack.solve(L, Xt, mode=1) chompack.trsm(L, Xt) chompack.trsm(L, Xt, trans = 'T') return Xt
def completion(X): """ Returns maximum-determinant positive definite completion of X if it exists, and otherwise an exception is raised. """ from cvxopt.amd import order n = X.size[0] Xt = chompack.tril(X) p = order(Xt) # Xc,N = chompack.embed(Xt,p) # L = chompack.completion(Xc) symb = chompack.symbolic(Xt, p) L = chompack.cspmatrix(symb) + Xt chompack.completion(L) Xt = matrix(0., (n, n)) Xt[::n + 1] = 1. # chompack.solve(L, Xt, mode=0) # chompack.solve(L, Xt, mode=1) chompack.trsm(L, Xt) chompack.trsm(L, Xt, trans='T') return Xt
def setUp(self): I = list(range(17)) + [2,2,3,3,4,14,4,14,8,14,15,8,15,7,8,14,8,14,14,15,10,12,13,16,12,13,16,12,13,15,16,13,15,16,15,16,15,16,16] J = list(range(17)) + [0,1,1,2,2,2,3,3,4,4,4,5,5,6,6,6,7,7,8,8,9,9,9,9,10,10,10,11,11,11,11,12,12,12,13,13,14,14,15] self.A = spmatrix(matrix(range(len(I)),tc='d'),I,J,(17,17)) self.symb = cp.symbolic(self.A, p = amd.order)
print("Solving SDP with CVXOPT..") prob = (c, G, matrix(h), dims) sol = solvers.conelp(*prob) Z1 = matrix(sol['z'], (n,n)) # convert SDP and solve prob2, blocks_to_sparse, symbs = cp.convert_conelp(*prob) print("Solving converted SDP (no merging)..") sol2 = solvers.conelp(*prob2) # convert block-diagonal solution to spmatrix blki,I,J,bn = blocks_to_sparse[0] Z2 = spmatrix(sol2['z'][blki],I,J) # compute completion symb = cp.symbolic(Z2, p=cp.maxcardsearch) Z2c = cp.psdcompletion(cp.cspmatrix(symb)+Z2, reordered=False) prob3, blocks_to_sparse, symbs = cp.convert_conelp(*prob, coupling = 'full', merge_function = cp.merge_size_fill(5,5)) print("Solving converted SDP (with merging)..") sol3 = solvers.conelp(*prob3) # convert block-diagonal solution to spmatrix blki,I,J,bn = blocks_to_sparse[0] Z3 = spmatrix(sol3['z'][blki],I,J) # compute completion symb = cp.symbolic(Z3, p=cp.maxcardsearch) Z3c = cp.psdcompletion(cp.cspmatrix(symb)+Z3, reordered=False)
def softmargin_completion(Q, d, gamma): """ Solves the QP minimize (1/2)*y'*Qc^{-1}*y + gamma*sum(v) subject to diag(d)*(y + b*ones) + v >= 1 v >= 0 (with variables y, b, v) and its dual, the 'soft-margin' SVM problem, maximize -(1/2)*z'*Qc*z + d'*z subject to 0 <= diag(d)*z <= gamma*ones sum(z) = 0 (with variables z). Qc is the max determinant completion of Q. Input arguments. Q is a sparse N x N sparse matrix with chordal sparsity pattern and a positive definite completion d is an N-vector of labels -1 or 1. gamma is a positive parameter. F is the chompack pattern corresponding to Q. If F is None, the pattern is computed. Output. z, y, b, v, optval, L, iters """ if verbose: solvers.options['show_progress'] = True else: solvers.options['show_progress'] = False N = Q.size[0] p = chompack.maxcardsearch(Q) symb = chompack.symbolic(Q, p) Qc = chompack.cspmatrix(symb) + Q # Qinv is the inverse of the max. determinant p.d. completion of Q Lc = Qc.copy() chompack.completion(Lc) Qinv = Lc.copy() chompack.llt(Qinv) Qinv = Qinv.spmatrix(reordered=False) Qinv = chompack.symmetrize(Qinv) def P(u, v, alpha=1.0, beta=0.0): """ v := alpha * [ Qc^-1, 0, 0; 0, 0, 0; 0, 0, 0 ] * u + beta * v """ v *= beta base.symv(Qinv, u, v, alpha=alpha, beta=1.0) def G(u, v, alpha=1.0, beta=0.0, trans='N'): """ If trans is 'N': v := alpha * [ -diag(d), -d, -I; 0, 0, -I ] * u + beta * v. If trans is 'T': v := alpha * [ -diag(d), 0; -d', 0; -I, -I ] * u + beta * v. """ v *= beta if trans is 'N': v[:N] -= alpha * (base.mul(d, u[:N] + u[N]) + u[-N:]) v[-N:] -= alpha * u[-N:] else: v[:N] -= alpha * base.mul(d, u[:N]) v[N] -= alpha * blas.dot(d, u, n=N) v[-N:] -= alpha * (u[:N] + u[N:]) K = spmatrix(0.0, Qinv.I, Qinv.J) dy1, dy2 = matrix(0.0, (N, 1)), matrix(0.0, (N, 1)) def Fkkt(W): """ Custom KKT solver for [ Qinv 0 0 -D 0 ] [ ux_y ] [ bx_y ] [ 0 0 0 -d' 0 ] [ ux_b ] [ bx_b ] [ 0 0 0 -I -I ] [ ux_v ] = [ bx_v ] [ -D -d -I -D1 0 ] [ uz_z ] [ bz_z ] [ 0 0 -I 0 -D2 ] [ uz_w ] [ bz_w ] with D1 = diag(d1), D2 = diag(d2), d1 = W['d'][:N]**2, d2 = W['d'][N:])**2. """ d1, d2 = W['d'][:N]**2, W['d'][N:]**2 d3, d4 = (d1 + d2)**-1, (d1**-1 + d2**-1)**-1 # Factor the chordal matrix K = Qinv + (D_1+D_2)^-1. K.V = Qinv.V K[::N + 1] = K[::N + 1] + d3 L = chompack.cspmatrix(symb) + K chompack.cholesky(L) # Solve (Qinv + (D1+D2)^-1) * dy2 = (D1 + D2)^{-1} * 1 blas.copy(d3, dy2) chompack.trsm(L, dy2, trans='N') chompack.trsm(L, dy2, trans='T') def g(x, y, z): # Solve # # [ K d3 ] [ ux_y ] # [ ] [ ] = # [ d3' 1'*d3 ] [ ux_b ] # # [ bx_y ] [ D ] # [ ] - [ ] * D3 * (D2 * bx_v + bx_z - bx_w). # [ bx_b ] [ d' ] x[:N] -= mul(d, mul(d3, mul(d2, x[-N:]) + z[:N] - z[-N:])) x[N] -= blas.dot(d, mul(d3, mul(d2, x[-N:]) + z[:N] - z[-N:])) # Solve dy1 := K^-1 * x[:N] blas.copy(x, dy1, n=N) chompack.trsm(L, dy1, trans='N') chompack.trsm(L, dy1, trans='T') # Find ux_y = dy1 - ux_b * dy2 s.t # # d3' * ( dy1 - ux_b * dy2 + ux_b ) = x[N] # # i.e. x[N] := ( x[N] - d3'* dy1 ) / ( d3'* ( 1 - dy2 ) ). x[N] = ( x[N] - blas.dot(d3, dy1) ) / \ ( blas.asum(d3) - blas.dot(d3, dy2) ) x[:N] = dy1 - x[N] * dy2 # ux_v = D4 * ( bx_v - D1^-1 (bz_z + D * (ux_y + ux_b)) # - D2^-1 * bz_w ) x[-N:] = mul( d4, x[-N:] - div(z[:N] + mul(d, x[:N] + x[N]), d1) - div(z[N:], d2)) # uz_z = - D1^-1 * ( bx_z - D * ( ux_y + ux_b ) - ux_v ) # uz_w = - D2^-1 * ( bx_w - uz_w ) z[:N] += base.mul(d, x[:N] + x[N]) + x[-N:] z[-N:] += x[-N:] blas.scal(-1.0, z) # Return W['di'] * uz blas.tbmv(W['di'], z, n=2 * N, k=0, ldA=1) return g q = matrix(0.0, (2 * N + 1, 1)) if weights is 'proportional': dlist = list(d) C1 = 0.5 * N * gamma / dlist.count(1) C2 = 0.5 * N * gamma / dlist.count(-1) gvec = matrix([C1 if w == 1 else C2 for w in dlist], (N, 1)) del dlist q[-N:] = gvec elif weights is 'equal': q[-N:] = gamma h = matrix(0.0, (2 * N, 1)) h[:N] = -1.0 sol = solvers.coneqp(P, q, G, h, kktsolver=Fkkt) u = matrix(0.0, (N, 1)) y, b, v = sol['x'][:N], sol['x'][N], sol['x'][N + 1:] z = mul(d, sol['z'][:N]) base.symv(Qinv, y, u) optval = 0.5 * blas.dot(y, u) + gamma * sum(v) return y, b, v, z, optval, Lc, sol['iterations']
random.seed(1) # Generate random sparse matrix of order ... n = 200 # and with density ... rho = 0.02 As = sp_rand(n,n,rho) + spmatrix(10.0,range(n),range(n)) # We will use (greedy) clique merging in this example: fmerge = merge_size_fill(16,4) # Compute symbolic factorization with AMD ordering and clique merging; only lower triangular part of As is accessed print("Computing symbolic factorization..") p = amd.order symb = symbolic(As, p = p, merge_function = fmerge) print("Order of matrix : %i" % (symb.n)) print("Number of nonzeros : %i" % (symb.nnz)) print("Number of supernodes : %i" % (symb.Nsn)) print("Largest supernode : %i" % (max([symb.snptr[k+1]-symb.snptr[k] for k in range(symb.Nsn)]))) print("Largest clique : %i\n" % (symb.clique_number)) A = cspmatrix(symb) # create new cspmatrix from symbolic factorization A += As # add spmatrix 'As' to cspmatrix 'A'; this ignores the upper triangular entries in As print("Computing Cholesky factorization..") L = A.copy() # make a copy of A cholesky(L) # compute Cholesky factorization; overwrites L print("Computing Cholesky product..")
def solve_phase1(self, kktsolver='chol', MM=1e5): """ Solves primal Phase I problem using the feasible start solver. Returns primal feasible X. """ from cvxopt import cholmod, amd k = 1e-3 # compute Schur complement matrix Id = [i * (self.n + 1) for i in range(self.n)] As = self._A[:, 1:] As[Id, :] /= sqrt(2.0) M = spmatrix([], [], [], (self.m, self.m)) base.syrk(As, M, trans='T') u = +self.b # compute least-norm solution F = cholmod.symbolic(M) cholmod.numeric(M, F) cholmod.solve(F, u) x = 0.5 * self._A[:, 1:] * u X0 = spmatrix(x[self.V[:].I], self.V.I, self.V.J, (self.n, self.n)) # test feasibility p = amd.order(self.V) #Xc,Nf = chompack.embed(X0,p) #E = chompack.project(Xc,spmatrix(1.0,range(self.n),range(self.n))) symb = chompack.symbolic(self.V, p) Xc = chompack.cspmatrix(symb) + X0 try: # L = chompack.completion(Xc) L = Xc.copy() chompack.completion(L) # least-norm solution is feasible return X0 except: pass # create Phase I SDP object trA = matrix(0.0, (self.m + 1, 1)) e = matrix(1.0, (self.n, 1)) Aa = self._A[Id, 1:] base.gemv(Aa, e, trA, trans='T') trA[-1] = MM P1 = SDP() P1._A = misc.phase1_sdp(self._A, trA) P1._b = matrix([self.b - k * trA[:self.m], MM]) P1._agg_sparsity() # find feasible starting point for Phase I problem tMIN = 0.0 tMAX = 1.0 while True: t = (tMIN + tMAX) / 2.0 #Xt = chompack.copy(Xc) #chompack.axpy(E,Xt,t) Xt = Xc.copy() + spmatrix(t, range(self.n), range(self.n)) try: # L = chompack.completion(Xt) L = Xt.copy() chompack.completion(L) tMAX = t if tMAX - tMIN < 1e-1: break except: tMAX *= 2.0 tMIN = t tt = t + 1.0 U = X0 + spmatrix(tt, range(self.n, ), range(self.n)) trU = sum(U[:][Id]) Z0 = spdiag([U, spmatrix([tt + k, MM - trU], [0, 1], [0, 1], (2, 2))]) sol = P1.solve_feas(primalstart={'x': Z0}, kktsolver=kktsolver) s = sol['x'][-2, -2] - k if s > 0: return None, P1 else: sol.pop('y') sol.pop('s') X0 = sol.pop('x')[:self.n,:self.n]\ - spmatrix(s,range(self.n),range(self.n)) return X0, sol
J = range(17) + [0,1,1,2,2,2,3,3,4,4,4,5,5,6,6,6,7,7,8,8,9,9,9,9,10,10,10,11,11,11,11,12,12,12,13,13,14,14,15] A = spmatrix(1.0,I,J,(17,17)) # Test if A is chordal p = cp.maxcardsearch(A) print("\nMaximum cardinality search") print(" -- perfect elimination order:"), cp.peo(A,p) # Test if natural ordering 0,1,2,...,17 is a perfect elimination order p = range(17) print("\nNatural ordering") print(" -- perfect elimination order:"), cp.peo(A,p) p = amd.order(A) print("\nAMD ordering") print(" -- perfect elimination order:"), cp.peo(A,p) # Compute a symbolic factorization symb = cp.symbolic(A, p) print("\nSymbolic factorization:") print("Fill :"), sum(symb.fill) print("Number of cliques :"), symb.Nsn print(symb) # Compute a symbolic factorization with clique merging symb2 = cp.symbolic(A, p, merge_function = cp.merge_size_fill(3,3)) print("Symbolic factorization with clique merging:") print("Fill (fact.+merging) :"), sum(symb2.fill) print("Number of cliques :"), symb2.Nsn print(symb2)
#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ Created on Thu Jun 20 19:29:12 2019 @author: arcturus """ #%% from cvxopt import spmatrix, amd import chompack as cp #%% # generate sparse matrix I = [0, 1, 3, 1, 5, 2, 6, 3, 4, 5, 4, 5, 6, 5, 6] J = [0, 0, 0, 1, 1, 2, 2, 3, 3, 3, 4, 4, 4, 5, 6] A = spmatrix(1.0, I, J, (7, 7)) symb = cp.symbolic(A, p=amd.order) print(A)
] A = spmatrix(1.0, I, J, (17, 17)) # Test if A is chordal p = cp.maxcardsearch(A) print("\nMaximum cardinality search") print(" -- perfect elimination order:"), cp.peo(A, p) # Test if natural ordering 0,1,2,...,17 is a perfect elimination order p = range(17) print("\nNatural ordering") print(" -- perfect elimination order:"), cp.peo(A, p) p = amd.order(A) print("\nAMD ordering") print(" -- perfect elimination order:"), cp.peo(A, p) # Compute a symbolic factorization symb = cp.symbolic(A, p) print("\nSymbolic factorization:") print("Fill :"), sum(symb.fill) print("Number of cliques :"), symb.Nsn print(symb) # Compute a symbolic factorization with clique merging symb2 = cp.symbolic(A, p, merge_function=cp.merge_size_fill(3, 3)) print("Symbolic factorization with clique merging:") print("Fill (fact.+merging) :"), sum(symb2.fill) print("Number of cliques :"), symb2.Nsn print(symb2)
def solve_phase1(self,kktsolver='chol',MM = 1e5): """ Solves primal Phase I problem using the feasible start solver. Returns primal feasible X. """ from cvxopt import cholmod, amd k = 1e-3 # compute Schur complement matrix Id = [i*(self.n+1) for i in range(self.n)] As = self._A[:,1:] As[Id,:] /= sqrt(2.0) M = spmatrix([],[],[],(self.m,self.m)) base.syrk(As,M,trans='T') u = +self.b # compute least-norm solution F = cholmod.symbolic(M) cholmod.numeric(M,F) cholmod.solve(F,u) x = 0.5*self._A[:,1:]*u X0 = spmatrix(x[self.V[:].I],self.V.I,self.V.J,(self.n,self.n)) # test feasibility p = amd.order(self.V) #Xc,Nf = chompack.embed(X0,p) #E = chompack.project(Xc,spmatrix(1.0,range(self.n),range(self.n))) symb = chompack.symbolic(self.V,p) Xc = chompack.cspmatrix(symb) + X0 try: # L = chompack.completion(Xc) L = Xc.copy() chompack.completion(L) # least-norm solution is feasible return X0,None except: pass # create Phase I SDP object trA = matrix(0.0,(self.m+1,1)) e = matrix(1.0,(self.n,1)) Aa = self._A[Id,1:] base.gemv(Aa,e,trA,trans='T') trA[-1] = MM P1 = SDP() P1._A = misc.phase1_sdp(self._A,trA) P1._b = matrix([self.b-k*trA[:self.m],MM]) P1._agg_sparsity() # find feasible starting point for Phase I problem tMIN = 0.0 tMAX = 1.0 while True: t = (tMIN+tMAX)/2.0 #Xt = chompack.copy(Xc) #chompack.axpy(E,Xt,t) Xt = Xc.copy() + spmatrix(t,list(range(self.n)),list(range(self.n))) try: # L = chompack.completion(Xt) L = Xt.copy() chompack.completion(L) tMAX = t if tMAX - tMIN < 1e-1: break except: tMAX *= 2.0 tMIN = t tt = t + 1.0 U = X0 + spmatrix(tt,list(range(self.n,)),list(range(self.n))) trU = sum(U[:][Id]) Z0 = spdiag([U,spmatrix([tt+k,MM-trU],[0,1],[0,1],(2,2))]) sol = P1.solve_feas(primalstart = {'x':Z0}, kktsolver = kktsolver) s = sol['x'][-2,-2] - k if s > 0: return None,P1 else: sol.pop('y') sol.pop('s') X0 = sol.pop('x')[:self.n,:self.n]\ - spmatrix(s,list(range(self.n)),list(range(self.n))) return X0,sol
def kernel_matrix(X, kernel, sigma=1.0, theta=1.0, degree=1, V=None, width=None): """ Computes the kernel matrix or a partial kernel matrix. Input arguments. X is an N x n matrix. kernel is a string with values 'linear', 'rfb', 'poly', or 'tanh'. 'linear': k(u,v) = u'*v/sigma. 'rbf': k(u,v) = exp(-||u - v||^2 / (2*sigma)). 'poly': k(u,v) = (u'*v/sigma)**degree. 'tanh': k(u,v) = tanh(u'*v/sigma - theta). kernel is a sigma and theta are positive numbers. degree is a positive integer. V is an N x N sparse matrix (default is None). width is a positive integer (default is None). Output. Q, an N x N matrix or sparse matrix. If V is a sparse matrix, a partial kernel matrix with the sparsity pattern V is returned. If width is specified and V = 'band', a partial kernel matrix with band sparsity is returned (width is the half-bandwidth). a, an N x 1 matrix with the products <xi,xi>/sigma. """ N, n = X.size #### dense (full) kernel matrix if V is None: if verbose: print("building kernel matrix ..") # Qij = xi'*xj / sigma Q = matrix(0.0, (N, N)) blas.syrk(X, Q, alpha=1.0 / sigma) a = Q[::N + 1] # ai = ||xi||**2 / sigma if kernel == 'linear': pass elif kernel == 'rbf': # Qij := Qij - 0.5 * ( ai + aj ) # = -||xi - xj||^2 / (2*sigma) ones = matrix(1.0, (N, 1)) blas.syr2(a, ones, Q, alpha=-0.5) Q = exp(Q) elif kernel == 'tanh': Q = exp(Q - theta) Q = div(Q - Q**-1, Q + Q**-1) elif kernel == 'poly': Q = Q**degree else: raise ValueError('invalid kernel type') #### general sparse partial kernel matrix elif type(V) is cvxopt.base.spmatrix: if verbose: print("building projected kernel matrix ...") Q = +V base.syrk(X, Q, partial=True, alpha=1.0 / sigma) # ai = ||xi||**2 / sigma a = matrix(Q[::N + 1], (N, 1)) if kernel == 'linear': pass elif kernel == 'rbf': ones = matrix(1.0, (N, 1)) # Qij := Qij - 0.5 * ( ai + aj ) # = -||xi - xj||^2 / (2*sigma) p = chompack.maxcardsearch(V) symb = chompack.symbolic(Q, p) Qc = chompack.cspmatrix(symb) + Q chompack.syr2(Qc, a, ones, alpha=-0.5) Q = Qc.spmatrix(reordered=False) Q.V = exp(Q.V) elif kernel == 'tanh': v = +Q.V v = exp(v - theta) v = div(v - v**-1, v + v**-1) Q.V = v elif kernel == 'poly': Q.V = Q.V**degree else: raise ValueError('invalid kernel type') #### banded partial kernel matrix elif V == 'band' and width is not None: # Lower triangular part of band matrix with bandwidth 2*w+1. if verbose: print("building projected kernel matrix ...") I = [i for k in range(N) for i in range(k, min(width + k + 1, N))] J = [k for k in range(N) for i in range(min(width + 1, N - k))] V = matrix(0.0, (len(I), 1)) oy = 0 for k in range(N): # V[:,k] = Xtrain[k:k+w, :] * Xtrain[k,:].T m = min(width + 1, N - k) blas.gemv(X, X, V, m=m, ldA=N, incx=N, offsetA=k, offsetx=k, offsety=oy) oy += m blas.scal(1.0 / sigma, V) # ai = ||xi||**2 / sigma a = matrix(V[[i for i in range(len(I)) if I[i] == J[i]]], (N, 1)) if kernel == 'linear': Q = spmatrix(V, I, J, (N, N)) elif kernel == 'rbf': Q = spmatrix(V, I, J, (N, N)) ones = matrix(1.0, (N, 1)) # Qij := Qij - 0.5 * ( ai + aj ) # = -||xi - xj||^2 / (2*sigma) symb = chompack.symbolic(Q) Qc = chompack.cspmatrix(symb) + Q chompack.syr2(Qc, a, ones, alpha=-0.5) Q = Qc.spmatrix(reordered=False) Q.V = exp(Q.V) elif kernel == 'tanh': V = exp(V - theta) V = div(V - V**-1, V + V**-1) Q = spmatrix(V, I, J, (N, N)) elif kernel == 'poly': Q = spmatrix(V**degree, I, J, (N, N)) else: raise ValueError('invalid kernel type') else: raise TypeError('invalid type V') return Q, a
def __get_sdp_decomposition(self, dim, ch_ext=0): """Implements a semidefinite decomposition, finding the relevant index set, and its relevant sliced coefficients: - For chosen n-dimensionality (dim), using the sparsity pattern of Q (or its chordal extension for dim=3), build the index set P'_dim (or bar(P_3), bar(P*_3)) described in the manuscript. - Then for each element of the index set, aggregate sliced coefficients needed for further computations. """ Q_arr, Q_adj, nb_vars = self._Q_arr, self._Q_adj, self._nb_vars self._agg_list = None # if an old value from a previous instance is stored # If we consider chordal extensions in the sparsity pattern if ch_ext in [1, 2]: # Save backup of the Q sparsity pattern (without chordal extension) Q_adjc = deepcopy(Q_adj) # Sparsity pattern must include diagonal elements for chompack for i in range(nb_vars): Q_adj[i, i] = 1 # Compute symbolic factorization using AMD ordering symb = cp.symbolic(Q_adj, p=amd.order) # Update Q_adj sparsity pattern with the chordal extension Q_adj = symb.sparsity_pattern(reordered=False, symmetric=True) self._Q_adj = Q_adj # Hold the list of indices for each sub-problem in the decomposition idx_list = [] # Build list of indices (idx_list) according to dimensionality (dim) if dim == 3: # idx_list = P'_3 # no chordal extension, select all unique cliques <=3 (that aren't subcliques <=3 ) in Q_adj # with ordered elements e.g. (i1, i2, i3) if ch_ext == 0: for i1 in range(nb_vars): for i2 in range(i1 + 1, nb_vars): if Q_adj[i1, i2]: triple_flag = False # look forward (i3>i2) for triple clique to add for i3 in range(i2 + 1, nb_vars): if Q_adj[i1, i3] and Q_adj[i2, i3]: idx_list.append(([i1, i2, i3], 3)) triple_flag = True # look backward (i3<i2) for triple clique to account for # (already added in a previous iteration) if not triple_flag: for i3 in (set(range(i2)) - {i1}): if Q_adj[i1, i3] and Q_adj[i2, i3]: triple_flag = True break # if no triple clique found add double clique if not triple_flag: idx_list.append(([i1, i2], 2)) # idx_list = bar(P_3), select all triple cliques in Q_adj chordal extension elif ch_ext == 1: for i1 in range(nb_vars): for i2 in range(i1 + 1, nb_vars): if Q_adj[i1, i2]: for i3 in range(i2 + 1, nb_vars): if Q_adj[i1, i3] and Q_adj[i2, i3]: # if no edge from S = {i1, i2, i3} is in Q_adjc (from which Q_adj was # chordally extended) then no McCormick cut applies to any of the corresponding # X_S variables, so X_S can be all 0s and as such [1, x_S.T; x_S, X_S]>=0 # => no valid psd cuts in such cases - ignore them # if Q_adjc[i1, i2] or Q_adjc[i1, i3] or Q_adjc[i2, i3]: idx_list.append(([i1, i2, i3], 3)) # idx_list = bar(P*_3) # Add only subsets of cliques in Q_adj (chordal ext) that are connected graphs in Q_adjc (no chordal ext.) # In practice: for triple (dim=3) cliques add clique in Q_adj if it has at least 2 edges in Q_adjc # otherwise add connected subset (double) elif ch_ext == 2: # logic is the same as for ch_ext=0 (P'_3), but on the the chordal ext. Q_adj and with the # extra check for nb_edges>=2 (on the original Q_adjc) for i1 in range(nb_vars): for i2 in range(i1 + 1, nb_vars): if Q_adj[i1, i2]: triple_flag = False for i3 in range(i2 + 1, nb_vars): if Q_adj[i1, i3] and Q_adj[i2, i3]: nb_edges = int(Q_adjc[i1, i2] + Q_adjc[i1, i3] + Q_adjc[i2, i3]) if nb_edges >= 2: idx_list.append(([i1, i2, i3], 3)) triple_flag = True if not triple_flag: for i3 in (set(range(i2)) - {i1}): nb_edges = int(Q_adjc[i1, i2] + Q_adjc[i1, i3] + Q_adjc[i2, i3]) if Q_adj[i1, i3] and Q_adj[i2, i3] and nb_edges >= 2: triple_flag = True break if not triple_flag and Q_adjc[i1, i2]: idx_list.append(([i1, i2], 2)) # idx_list = P_3 elif ch_ext == -1: for i1 in range(nb_vars): for i2 in range(i1 + 1, nb_vars): for i3 in range(i2 + 1, nb_vars): idx_list.append(([i1, i2, i3], 3)) # idx_list = P'_4, extends logic for P'_3 (see dim=3 case) elif dim == 4: for i1 in range(nb_vars): for i2 in range(i1 + 1, nb_vars): if Q_adj[i1, i2]: triple_flag = False for i3 in range(i2 + 1, nb_vars): if Q_adj[i1, i3] and Q_adj[i2, i3]: quad_flag = False triple_flag = True for i4 in range(i3 + 1, nb_vars): if Q_adj[i1, i4] and Q_adj[i2, i4] and Q_adj[i3, i4]: idx_list.append(([i1, i2, i3, i4], 4)) quad_flag = True if not quad_flag: for i4 in (set(range(i3)) - {i1, i2}): if Q_adj[i1, i4] and Q_adj[i2, i4] and Q_adj[i3, i4]: quad_flag = True break if not quad_flag: idx_list.append(([i1, i2, i3], 3)) if not triple_flag: for i3 in (set(range(i2)) - {i1}): if Q_adj[i1, i3] and Q_adj[i2, i3]: triple_flag = True break if not triple_flag: idx_list.append(([i1, i2], 2)) # idx_list = P'_5, extends logic for P'_3 (see dim=3 case) elif dim == 5: for i1 in range(nb_vars): for i2 in range(i1 + 1, nb_vars): if Q_adj[i1, i2]: triple_flag = False for i3 in range(i2 + 1, nb_vars): if Q_adj[i1, i3] and Q_adj[i2, i3]: quad_flag = False triple_flag = True for i4 in range(i3 + 1, nb_vars): if Q_adj[i1, i4] and Q_adj[i2, i4] and Q_adj[i3, i4]: cinq_flag = False quad_flag = True for i5 in range(i4 + 1, nb_vars): if Q_adj[i1, i5] and Q_adj[i2, i5] and Q_adj[i3, i5] and Q_adj[i4, i5]: idx_list.append(([i1, i2, i3, i4, i5], 5)) cinq_flag = True if not cinq_flag: for i5 in (set(range(i4)) - {i1, i2, i3}): if Q_adj[i1, i5] and Q_adj[i2, i5] and Q_adj[i3, i5] and Q_adj[i4, i5]: cinq_flag = True break if not cinq_flag: idx_list.append(([i1, i2, i3, i4], 4)) if not quad_flag: for i4 in (set(range(i3)) - {i1, i2}): if Q_adj[i1, i4] and Q_adj[i2, i4] and Q_adj[i3, i4]: quad_flag = True break if not quad_flag: idx_list.append(([i1, i2, i3], 3)) if not triple_flag: for i3 in (set(range(i2)) - {i1}): if Q_adj[i1, i3] and Q_adj[i2, i3]: triple_flag = True break if not triple_flag: idx_list.append(([i1, i2], 2)) # Pool all needed information for each element in the decomposition index set agg_list = [0] * len(idx_list) # stores all info needed for the semidefinite decomposition # Don't compute (agg_list) when it is possibly too large to store in RAM memory if len(agg_list) >= CutSolver._THRES_MAX_SUBS: return len(agg_list) for idx, (setInds, lenInds) in enumerate(idx_list): set_idxs = list(itertools.combinations_with_replacement(setInds, 2)) Xarr_inds = [nb_vars * si[0] - si[0] * (si[0] + 1) // 2 + si[1] for si in set_idxs] # Slice symmetric matrix Q on indices set_idxs (only upper triangular part) Q_slice = itemgetter(*Xarr_inds)(Q_arr) # Bound input eigenvalues in [-1,1] via Lemma 4.1.2 # max_elem can be 0 when using P_3 sub-problems, so set it to 1 then max_elem = lenInds * abs(max(Q_slice, key=abs)) max_elem += 1 if not max_elem else 0 Q_slice = np.divide(Q_slice, max_elem) agg_list[idx] = (setInds, Xarr_inds, tuple(Q_slice), max_elem) self._agg_list = agg_list return len(agg_list)