Beispiel #1
0
def maxcardsearch(A, ve=None):
    """
    Maximum cardinality search ordering of a sparse chordal matrix.

    Returns the maximum cardinality search ordering of a symmetric
    chordal matrix :math:`A`. Only the lower triangular part of
    :math:`A` is accessed. The maximum cardinality search ordering
    is a perfect elimination ordering in the factorization
    :math:`PAP^T = LL^T`. The optional argument `ve` is the index of
    the last vertex to be eliminated (the default value is n-1).

    :param A:   :py:class:`spmatrix`

    :param ve:  integer between 0 and `A.size[0]`-1 (optional)
    
    """

    n = A.size[0]
    assert A.size[1] == n, "A must be a square matrix"
    assert type(A) is spmatrix, "A must be a sparse matrix"
    if ve is None:
        ve = n - 1
    else:
        assert type(ve) is int and 0<=ve<n,\
          "ve must be an integer between 0 and A.size[0]-1"
    As = symmetrize(A)
    cp, ri, _ = As.CCS

    # permutation vector
    p = matrix(0, (n, 1))

    # weight array
    w = matrix(0, (n, 1))
    max_w = 0
    S = [list(range(ve)) + list(range(ve + 1, n)) + [ve]
         ] + [[] for i in range(n - 1)]

    for i in range(n - 1, -1, -1):
        while True:
            if len(S[max_w]) > 0:
                v = S[max_w].pop()
                if w[v] >= 0: break
            else:
                max_w -= 1
        p[i] = v
        w[v] = -1  # set w[v] = -1 to mark that node v has been numbered

        # increase weights for all unnumbered neighbors
        for r in ri[cp[v]:cp[v + 1]]:
            if w[r] >= 0:
                w[r] += 1
                S[w[r]].append(r)  # bump r up to S[w[r]]
                max_w = max(max_w, w[r])

    return p
Beispiel #2
0
def maxcardsearch(A, ve = None):
    """
    Maximum cardinality search ordering of a sparse chordal matrix.

    Returns the maximum cardinality search ordering of a symmetric
    chordal matrix :math:`A`. Only the lower triangular part of
    :math:`A` is accessed. The maximum cardinality search ordering
    is a perfect elimination ordering in the factorization
    :math:`PAP^T = LL^T`. The optional argument `ve` is the index of
    the last vertex to be eliminated (the default value is n-1).

    :param A:   :py:class:`spmatrix`

    :param ve:  integer between 0 and `A.size[0]`-1 (optional)
    
    """
    
    n = A.size[0]
    assert A.size[1] == n, "A must be a square matrix"
    assert type(A) is spmatrix, "A must be a sparse matrix"
    if ve is None: 
        ve = n-1
    else:
        assert type(ve) is int and 0<=ve<n,\
          "ve must be an integer between 0 and A.size[0]-1"    
    As = symmetrize(A)
    cp,ri,_ = As.CCS
    
    # permutation vector 
    p = matrix(0,(n,1))
    
    # weight array
    w = matrix(0,(n,1))
    max_w = 0
    S = [list(range(ve))+list(range(ve+1,n))+[ve]] + [[] for i in range(n-1)]
        
    for i in range(n-1,-1,-1):
        while True:
            if len(S[max_w]) > 0:
                v = S[max_w].pop()
                if w[v] >= 0: break
            else:
                max_w -= 1
        p[i] = v    
        w[v] = -1   # set w[v] = -1 to mark that node v has been numbered

        # increase weights for all unnumbered neighbors
        for r in ri[cp[v]:cp[v+1]]:
            if w[r] >= 0: 
                w[r] += 1
                S[w[r]].append(r)     # bump r up to S[w[r]]
                max_w = max(max_w,w[r])   

    return p
Beispiel #3
0
def peo(A, p):
    """
    Checks whether an ordering is a perfect elmimination order.

    Returns `True` if the permutation :math:`p` is a perfect elimination order
    for a Cholesky factorization :math:`PAP^T = LL^T`. Only the lower
    triangular part of :math:`A` is accessed.
    
    :param A:   :py:class:`spmatrix`
    
    :param p:   :py:class:`matrix` or :class:`list` of length `A.size[0]`
    """

    n = A.size[0]
    assert type(A) == spmatrix, "A must be a sparse matrix"
    assert A.size[1] == n, "A must be a square matrix"
    assert len(p) == n, "length of p must be equal to the order of A" 
    if isinstance(p, list): p = matrix(p)
    
    As = symmetrize(A)
    cp,ri,_ = As.CCS

    # compute inverse permutation array
    ip = matrix(0,(n,1))
    ip[p] = matrix(range(n),(n,1))

    # test set inclusion
    for k in range(n):
        v = p[k]  # next vertex to be eliminated

        # indices of neighbors that correspond to strictly lower triangular elements in reordered pattern
        r = set([rj for rj in ri[cp[v]:cp[v+1]] if ip[rj] > k])  

        for rj in r:
            if not r.issubset(set(ri[cp[rj]:cp[rj+1]])): return False
            
    return True
Beispiel #4
0
    def _iadd_spmatrix(self, X, alpha = 1.0):
        """
        Add a sparse matrix :math:`X` to :py:class:`cspmatrix`.
        """
        assert self.is_factor is False, "cannot add spmatrix to a cspmatrix factor"

        n = self.symb.n
        snptr = self.symb.snptr
        snode = self.symb.snode
        relptr = self.symb.relptr
        snrowidx = self.symb.snrowidx
        sncolptr = self.symb.sncolptr
        blkptr = self.symb.blkptr
        blkval = self.blkval

        if self.symb.p is not None:
            Xp = tril(perm(symmetrize(X),self.symb.p))
        else:
            Xp = tril(X)
        cp, ri, val = Xp.CCS

        # for each block ...
        for k in range(self.symb.Nsn):
            nn = snptr[k+1]-snptr[k]
            na = relptr[k+1]-relptr[k]
            nj = nn + na

            r = list(snrowidx[sncolptr[k]:sncolptr[k+1]])
            # copy cols from A to block
            for i in range(nn):
                j = snode[snptr[k]+i]
                offset = blkptr[k] + nj*i
                # extract correct indices and add values
                I = [offset + r.index(idx) for idx in ri[cp[j]:cp[j+1]]]

                blkval[I] += alpha*val[cp[j]:cp[j+1]]
        return
Beispiel #5
0
def peo(A, p):
    """
    Checks whether an ordering is a perfect elmimination order.

    Returns `True` if the permutation :math:`p` is a perfect elimination order
    for a Cholesky factorization :math:`PAP^T = LL^T`. Only the lower
    triangular part of :math:`A` is accessed.
    
    :param A:   :py:class:`spmatrix`
    
    :param p:   :py:class:`matrix` or :class:`list` of length `A.size[0]`
    """

    n = A.size[0]
    assert type(A) == spmatrix, "A must be a sparse matrix"
    assert A.size[1] == n, "A must be a square matrix"
    assert len(p) == n, "length of p must be equal to the order of A"
    if isinstance(p, list): p = matrix(p)

    As = symmetrize(A)
    cp, ri, _ = As.CCS

    # compute inverse permutation array
    ip = matrix(0, (n, 1))
    ip[p] = matrix(range(n), (n, 1))

    # test set inclusion
    for k in range(n):
        v = p[k]  # next vertex to be eliminated

        # indices of neighbors that correspond to strictly lower triangular elements in reordered pattern
        r = set([rj for rj in ri[cp[v]:cp[v + 1]] if ip[rj] > k])

        for rj in r:
            if not r.issubset(set(ri[cp[rj]:cp[rj + 1]])): return False

    return True
Beispiel #6
0
    def _iadd_spmatrix(self, X, alpha=1.0):
        """
        Add a sparse matrix :math:`X` to :py:class:`cspmatrix`.
        """
        assert self.is_factor is False, "cannot add spmatrix to a cspmatrix factor"

        n = self.symb.n
        snptr = self.symb.snptr
        snode = self.symb.snode
        relptr = self.symb.relptr
        snrowidx = self.symb.snrowidx
        sncolptr = self.symb.sncolptr
        blkptr = self.symb.blkptr
        blkval = self.blkval

        if self.symb.p is not None:
            Xp = tril(perm(symmetrize(X), self.symb.p))
        else:
            Xp = tril(X)
        cp, ri, val = Xp.CCS

        # for each block ...
        for k in range(self.symb.Nsn):
            nn = snptr[k + 1] - snptr[k]
            na = relptr[k + 1] - relptr[k]
            nj = nn + na

            r = list(snrowidx[sncolptr[k]:sncolptr[k + 1]])
            # copy cols from A to block
            for i in range(nn):
                j = snode[snptr[k] + i]
                offset = blkptr[k] + nj * i
                # extract correct indices and add values
                I = [offset + r.index(idx) for idx in ri[cp[j]:cp[j + 1]]]

                blkval[I] += alpha * val[cp[j]:cp[j + 1]]
        return
Beispiel #7
0
def convert_block(G, h, dim, **kwargs):
    r"""
    Applies the clique conversion method to a single positive
    semidefinite block of a cone linear program

    .. math::
        \begin{array}{ll}
           \mbox{maximize}   & -h^T z \\
           \mbox{subject to} &  G^T z + c = 0 \\
                             &  \mathbf{smat}(z)\ \ \text{psd completable}
        \end{array}

    After conversion, the above problem is converted to a block-diagonal one 

    .. math::
        \begin{array}{ll}
           \mbox{maximize}   & -h_b^T z_b  \\
           \mbox{subject to} &  G_b^T z_b + c = 0 \\
                             &  G_c^T z_b = 0 \\
                             &  \mathbf{smat}(z_b)\ \ \text{psd block-diagonal}
        \end{array}
                             
    where :math:`z_b` is a vector representation of a block-diagonal
    matrix. The constraint :math:`G_b^T z_b + c = 0` corresponds to
    the original constraint :math:`G'z + c = 0`, and the constraint
    :math:`G_c^T z_b = 0` is a coupling constraint.
       
    :param G:                 :py:class:`spmatrix`
    :param h:                 :py:class:`matrix`
    :param dim:               integer
    :param merge_function:    routine that implements a merge heuristic (optional)
    :param coupling:          mode of conversion (optional)
    :param max_density:       float (default: 0.4)

    The following example illustrates how to apply the conversion method to a one-block SDP:
    
    .. code-block:: python

        block = (G, h, dim) 
        blockc, blk2sparse, symb = convert_block(*block)
    
    The return value `blk2sparse` is a 4-tuple
    (`blki,I,J,n`) that defines a mapping between the sparse
    matrix representation and the converted block-diagonal
    representation. If `blkvec` represents a block-diagonal matrix,
    then

    .. code-block:: python

        S = spmatrix(blkvec[blki], I, J) 

    maps `blkvec` into is a sparse matrix representation of the
    matrix. Similarly, a sparse matrix `S` can be converted to the
    block-diagonal matrix representation using the code
    
    .. code-block:: python

        blkvec = matrix(0.0, (len(S),1), tc=S.typecode)
        blkvec[blki] = S.V

    The optional argument `max_density` controls whether or not to perform
    conversion based on the aggregate sparsity of the block. Specifically,
    conversion is performed whenever the number of lower triangular nonzeros
    in the aggregate sparsity pattern is less than or equal to `max_density*dim`.
        
    The optional argument `coupling` controls the introduction
    of equality constraints in the conversion. Possible values
    are *full* (default), *sparse*, *sparse+tri*, and any nonnegative
    integer. Full coupling results in a conversion in which all
    coupling constraints are kept, and hence the converted problem is
    equivalent to the original problem. Sparse coupling yeilds a
    conversion in which only the coupling constraints corresponding to
    nonzero entries in the aggregate sparsity pattern are kept, and
    sparse-plus-tridiagonal (*sparse+tri*) yeilds a conversion with
    tridiagonal coupling in addition to coupling constraints corresponding
    to nonzero entries in the aggregate sparsity pattern. Setting `coupling`
    to a nonnegative integer *k* yields a conversion with coupling
    constraints corresponding to entries in a band with half-bandwidth *k*.

    .. seealso::

        M. S. Andersen, A. Hansson, and L. Vandenberghe, `Reduced-Complexity
        Semidefinite Relaxations of Optimal Power Flow Problems
        <http://dx.doi.org/10.1109/TPWRS.2013.2294479>`_,
        IEEE Transactions on Power Systems, 2014.
        
    """

    merge_function = kwargs.get('merge_function', None)
    coupling = kwargs.get('coupling', 'full')
    tskip = kwargs.get('max_density', 0.4)

    tc = G.typecode

    ###
    ### Find filled pattern, compute symbolic factorization using AMD
    ### ordering, and do "symbolic conversion"
    ###

    # find aggregate sparsity pattern
    h = sparse(h)
    LIa = matrix(list(set(G.I).union(set(h.I))))
    Ia = [i % dim for i in LIa]
    Ja = [j // dim for j in LIa]
    Va = spmatrix(1., Ia, Ja, (dim, dim))

    # find permutation, symmetrize, and permute
    Va = symmetrize(tril(Va))

    # if not very sparse, skip decomposition
    if float(len(Va)) / Va.size[0]**2 > tskip:
        return (G, h, None, [dim]), None, None

    # compute symbolic factorization
    F = symbolic(Va, merge_function=merge_function, p=amd.order)
    p = F.p
    ip = F.ip
    Va = F.sparsity_pattern(reordered=True, symmetric=True)

    # symbolic conversion
    if coupling == 'sparse': coupling = tril(Va)
    elif coupling == 'sparse+tri':
        coupling = tril(Va)
        coupling += spmatrix(1.0,[i for j in range(Va.size[0]) for i in range(j,min(Va.size[0],j+2))],\
                             [j for j in range(Va.size[0]) for i in range(j,min(Va.size[0],j+2))],Va.size)
    elif type(coupling) is int:
        assert coupling >= 0
        bw = +coupling
        coupling = spmatrix(1.0,[i for j in range(Va.size[0]) for i in range(j,min(Va.size[0],j+bw+1))],\
                            [j for j in range(Va.size[0]) for i in range(j,min(Va.size[0],j+bw+1))],Va.size)

    dims, sparse_to_block, constraints = symb_to_block(F, coupling=coupling)

    # dimension of block-diagonal representation
    N = sum([d**2 for d in dims])

    ###
    ### Convert problem data
    ###

    m = G.size[1]  # cols in G
    cp, ri, val = G.CCS

    IV = []  # list of m (row, value) tuples
    J = []
    for j in range(m):
        iv = []
        for i in range(cp[j + 1] - cp[j]):
            row = ri[cp[j] + i] % dim
            col = ri[cp[j] + i] // dim
            if row < col: continue  # ignore upper triangular entries
            k1 = ip[row]
            k2 = ip[col]
            blk_idx = sparse_to_block[min(k1, k2) * dim + max(k1, k2)]
            if k1 == k2:
                iv.append((blk_idx[0], val[cp[j] + i]))
            elif k1 > k2:
                iv.append((blk_idx[0], val[cp[j] + i]))
                iv.append((blk_idx[1], val[cp[j] + i].conjugate()))
            else:
                iv.append((blk_idx[0], val[cp[j] + i].conjugate()))
                iv.append((blk_idx[1], val[cp[j] + i]))
        iv.sort(key=lambda x: x[0])
        IV.extend(iv)
        J.extend(len(iv) * [j])

    # build G_converted
    I, V = zip(*IV)
    G_converted = spmatrix(V, I, J, (N, m), tc=tc)

    # convert and build new h
    _, ri, val = h.CCS
    iv = []
    for i in range(len(ri)):
        row = ri[i] % dim
        col = ri[i] // dim
        if row < col: continue  # ignore upper triangular entries
        k1 = ip[row]
        k2 = ip[col]
        blk_idx = sparse_to_block[min(k1, k2) * dim + max(k1, k2)]
        if k1 == k2:
            iv.append((blk_idx[0], val[i]))
        elif k1 > k2:
            iv.append((blk_idx[0], val[i]))
            iv.append((blk_idx[1], val[i].conjugate()))
        else:
            iv.append((blk_idx[0], val[i].conjugate()))
            iv.append((blk_idx[1], val[i]))

    iv.sort(key=lambda x: x[0])
    if iv:
        I, V = zip(*iv)
    else:
        I, V = [], []
    h_converted = spmatrix(V, I, len(I) * [0], (N, 1), tc=tc)

    ###
    ### Build matrix representation of coupling constraints
    ###

    IV = []  # list of (row, value) tuples
    J = []
    ncon = 0
    for j in range(len(constraints)):
        iv = []
        if len(constraints[j]) == 2:
            ii, jj = constraints[j]
            iv = sorted([(ii, 1.0), (jj, -1.0)], key=lambda x: x[0])
            jl = 2 * [ncon]
            ncon += 1
        elif len(constraints[j]) == 4:
            i1, j1, i2, j2 = constraints[j]
            iv = sorted([(i1, 1.0), (i2, 1.0), (j1, -1.0), (j2, -1.0)],
                        key=lambda x: x[0])
            jl = 4 * [ncon]
            ncon += 1
            if tc == 'z':
                iv.extend(
                    sorted([(i1, complex(0.0, 1.0)), (i2, complex(0.0, -1.0)),
                            (j1, complex(0.0, -1.0)), (j2, complex(0.0, 1.0))],
                           key=lambda x: x[0]))
                jl.extend(4 * [ncon])
                ncon += 1
        IV.extend(iv)
        J.extend(jl)

    # build G_converted
    if IV: I, V = zip(*IV)
    else: I, V = [], []
    G_coupling = spmatrix(V, I, J, (N, ncon), tc=tc)

    # generate indices for reverse mapping (block_to_sparse)
    idx = []
    for k in sparse_to_block.keys():
        k1 = p[k % dim]
        k2 = p[k // dim]
        idx.append((min(k1, k2) * dim + max(k1, k2), sparse_to_block[k][0]))

    idx.sort()
    idx, blki = zip(*idx)
    blki = matrix(blki)
    I = [v % dim for v in idx]
    J = [v // dim for v in idx]
    n = sum([di**2 for di in dims])

    return (G_converted, h_converted, G_coupling, dims), (blki, I, J, n), F
Beispiel #8
0
    def __init__(self, A, p = None, merge_function = None, **kwargs):

        assert isinstance(A,spmatrix), "A must be a sparse matrix"
        assert A.size[0] == A.size[1], "A must be a square matrix"

        supernodal = kwargs.get('supernodal',True)

        # Symmetrize A
        Ap = symmetrize(A)
        nnz_Ap = (len(Ap)+Ap.size[0])/2   # number of nonzeros in lower triangle        
        assert len([i for i,j in zip(Ap.I,Ap.J) if i==j]) == A.size[0], "the sparsity pattern of A must include diagonal elements" 

        # Permute if permutation vector p or ordering routine is specified
        if p is not None:
            if isinstance(p, BuiltinFunctionType) or isinstance(p, FunctionType):
                p = p(Ap)
            elif isinstance(p, list):
                p = matrix(p)
            assert len(p) == A.size[0], "length of permutation vector must be equal to the order of A"
            Ap = perm(Ap,p)

        # Symbolic factorization
        par = etree(Ap)
        post = post_order(par)
        colcount = counts(Ap, par, post)
        nnz_Ae = sum(colcount)
        if supernodal:
            snode, snptr, snpar = supernodes(par, post, colcount)
            snpost = post_order(snpar)
        else:
            snpar = par
            snpost = post
            snode = matrix(range(A.size[0]))
            snptr = matrix(range(A.size[0]+1))
        if merge_function:
            colcount, snode, snptr, snpar, snpost = amalgamate(colcount, snode, snptr, snpar, snpost, merge_function)

        # Post order nodes such that supernodes have consecutively numbered nodes
        pp = matrix([snode[snptr[snpost[k]]:snptr[snpost[k]+1]] for k in range(len(snpar))])
        snptr2 = matrix(0,(len(snptr),1))
        for k in range(len(snpar)):
            snptr2[k+1] = snptr2[k] + snptr[snpost[k]+1]-snptr[snpost[k]]
        colcount = colcount[pp]
        snposti = matrix(0,(len(snpost),1))
        snposti[snpost] = matrix(range(len(snpost)))
        snpar = matrix([snposti[snpar[snpost[k]]] for k in range(len(snpar)) ])        
        snode = matrix(range(len(snode)))
        snpost = matrix(range(len(snpost)))
        snptr = snptr2

        # Permute A and store effective permutation and its inverse
        Ap = perm(Ap,pp)
        if p is None:
            self.__p = pp
        else:
            self.__p = p[pp]
        self.__ip = matrix(0,(len(self.__p),1))
        self.__ip[self.__p] = matrix(range(len(self.__p)))

        # Compute embedding and relative indices
        sncolptr, snrowidx = embed(Ap, colcount, snode, snptr, snpar, snpost)
        relptr, relidx = relative_idx(sncolptr, snrowidx, snptr, snpar)
        
        # build chptr
        chptr = matrix(0, (len(snpar)+1,1))
        for j in snpost: 
            if snpar[j] != j: chptr[snpar[j]+1] += 1
        for j in range(1,len(chptr)):
            chptr[j] += chptr[j-1]

        # build chidx
        tmp = +chptr
        chidx = matrix(0,(chptr[-1],1))
        for j in snpost:
            if snpar[j] != j: 
                chidx[tmp[snpar[j]]] = j
                tmp[snpar[j]] += 1
        del tmp

        # compute stack size
        stack_size = 0
        stack_mem = 0
        stack_max = 0
        frontal_max = 0
        stack = []
        for k in snpost:
            nn = snptr[k+1]-snptr[k]
            na = relptr[k+1]-relptr[k]
            nj = na + nn
            frontal_max = max(frontal_max, nj**2)
            for j in range(chptr[k+1]-1,chptr[k]-1,-1):
                v = stack.pop()
                stack_mem -= v**2
            if (na > 0):
                stack.append(na)
                stack_mem += na**2
                stack_max = max(stack_max,stack_mem)
                stack_size = max(stack_size,len(stack))
        self.frontal_len = frontal_max
        self.stack_len = stack_max
        self.stack_size = stack_size
                
        # build blkptr
        blkptr = matrix(0, (len(snpar)+1,1))
        for i in range(len(snpar)):
            blkptr[i+1] = blkptr[i] + (snptr[i+1]-snptr[i])*(sncolptr[i+1]-sncolptr[i])

        # compute storage requirements
        stack = []
        stack_depth = 0

        stack_mem = 0
        stack_tmp = 0
        cln = 0

        stack_solve = 0
        stack_stmp = 0

        for k in snpost:
            nn = snptr[k+1]-snptr[k]       # |Nk|
            na = relptr[k+1]-relptr[k]     # |Ak|
            nj = na + nn
            cln = max(cln,nj)              # this is the clique number
            for i in range(chptr[k+1]-1,chptr[k]-1,-1):
                na_ch = stack.pop()
                stack_tmp -= na_ch**2
                stack_stmp -= na_ch
            if na > 0:
                stack.append(na)
                stack_tmp += na**2
                stack_mem = max(stack_tmp,stack_mem)
                stack_stmp += na
                stack_solve = max(stack_stmp,stack_solve)
            stack_depth = max(stack_depth,len(stack))
            
        self.__clique_number = cln
        self.__n = len(snode)
        self.__Nsn = len(snpar)
        self.__snode = snode
        self.__snptr = snptr
        self.__chptr = chptr
        self.__chidx = chidx
        self.__snpar = snpar
        self.__snpost = snpost
        self.__relptr = relptr
        self.__relidx = relidx
        self.__sncolptr = sncolptr
        self.__snrowidx = snrowidx
        self.__blkptr = blkptr
        self.__fill = (nnz_Ae-nnz_Ap,self.nnz-nnz_Ae)
        self.__memory = {'stack_depth':stack_depth,
                         'stack_mem':stack_mem,
                         'frontal_mem':cln**2,                         
                         'stack_solve':stack_solve}

        return
Beispiel #9
0
    def spmatrix(self, reordered = True, symmetric = False):
        """
        Converts the :py:class:`cspmatrix` :math:`A` to a sparse matrix. A reordered
        matrix is returned if the optional argument `reordered` is
        `True` (default), and otherwise the inverse permutation is applied. Only the
        default options are allowed if the :py:class:`cspmatrix` :math:`A` represents
        a Cholesky factor. 

        :param reordered:  boolean (default: True)
        :param symmetric:  boolean (default: False)			   
        """
        n = self.symb.n
        snptr = self.symb.snptr
        snode = self.symb.snode
        relptr = self.symb.relptr
        snrowidx = self.symb.snrowidx
        sncolptr = self.symb.sncolptr
        blkptr = self.symb.blkptr
        blkval = self.blkval
        
        if self.is_factor:
            if symmetric: raise ValueError("'symmetric = True' not implemented for Cholesky factors")
            if not reordered: raise ValueError("'reordered = False' not implemented for Cholesky factors")
            snpost = self.symb.snpost
            blkval = +blkval
            for k in snpost:
                j = snode[snptr[k]]            # representative vertex
                nn = snptr[k+1]-snptr[k]       # |Nk|
                na = relptr[k+1]-relptr[k]     # |Ak|
                if na == 0: continue
                nj = na + nn
                if nn == 1:
                    blas.scal(blkval[blkptr[k]],blkval,offset = blkptr[k]+1,n=na)
                else:
                    blas.trmm(blkval,blkval, transA = "N", diag = "N", side = "R",uplo = "L", \
                              m = na, n = nn, ldA = nj, ldB = nj, \
                              offsetA = blkptr[k],offsetB = blkptr[k] + nn)

        cc = matrix(0,(n,1))  # count number of nonzeros in each col
        for k in range(self.symb.Nsn):
            nn = snptr[k+1]-snptr[k]
            na = relptr[k+1]-relptr[k]
            nj = nn + na
            for i in range(nn):
                j = snode[snptr[k]+i]
                cc[j] = nj - i

        # build col. ptr
        cp = [0]
        for i in range(n): cp.append(cp[-1] + cc[i])
        cp = matrix(cp)

        # copy data and row indices
        val = matrix(0.0, (cp[-1],1))
        ri = matrix(0, (cp[-1],1))
        for k in range(self.symb.Nsn):
            nn = snptr[k+1]-snptr[k]
            na = relptr[k+1]-relptr[k]
            nj = nn + na
            for i in range(nn):
                j = snode[snptr[k]+i]
                blas.copy(blkval, val, offsetx = blkptr[k]+nj*i+i, offsety = cp[j], n = nj-i)
                ri[cp[j]:cp[j+1]] = snrowidx[sncolptr[k]+i:sncolptr[k+1]]

        I = []; J = []
        for i in range(n):
            I += list(ri[cp[i]:cp[i+1]])
            J += (cp[i+1]-cp[i])*[i]

        tmp = spmatrix(val, I, J, (n,n))  # tmp is reordered and lower tril.
        
        if reordered or self.symb.p is None:
            # reordered matrix (do not apply inverse permutation)
            if not symmetric: return tmp
            else: return symmetrize(tmp)
        else:
            # apply inverse permutation            
            tmp = perm(symmetrize(tmp), self.symb.ip)
            if symmetric: return tmp
            else: return tril(tmp) 
Beispiel #10
0
    def __init__(self, A, p=None, merge_function=None, **kwargs):

        assert isinstance(A, spmatrix), "A must be a sparse matrix"
        assert A.size[0] == A.size[1], "A must be a square matrix"

        supernodal = kwargs.get('supernodal', True)

        # Symmetrize A
        Ap = symmetrize(A)
        nnz_Ap = (len(Ap) +
                  Ap.size[0]) / 2  # number of nonzeros in lower triangle
        assert len([i for i, j in zip(Ap.I, Ap.J) if i == j]) == A.size[
            0], "the sparsity pattern of A must include diagonal elements"

        # Permute if permutation vector p or ordering routine is specified
        if p is not None:
            if isinstance(p, BuiltinFunctionType) or isinstance(
                    p, FunctionType):
                p = p(Ap)
            elif isinstance(p, list):
                p = matrix(p)
            assert len(p) == A.size[
                0], "length of permutation vector must be equal to the order of A"
            Ap = perm(Ap, p)

        # Symbolic factorization
        par = etree(Ap)
        post = post_order(par)
        colcount = counts(Ap, par, post)
        nnz_Ae = sum(colcount)
        if supernodal:
            snode, snptr, snpar = supernodes(par, post, colcount)
            snpost = post_order(snpar)
        else:
            snpar = par
            snpost = post
            snode = matrix(range(A.size[0]))
            snptr = matrix(range(A.size[0] + 1))
        if merge_function:
            colcount, snode, snptr, snpar, snpost = amalgamate(
                colcount, snode, snptr, snpar, snpost, merge_function)

        # Post order nodes such that supernodes have consecutively numbered nodes
        pp = matrix([
            snode[snptr[snpost[k]]:snptr[snpost[k] + 1]]
            for k in range(len(snpar))
        ])
        snptr2 = matrix(0, (len(snptr), 1))
        for k in range(len(snpar)):
            snptr2[k + 1] = snptr2[k] + snptr[snpost[k] + 1] - snptr[snpost[k]]
        colcount = colcount[pp]
        snposti = matrix(0, (len(snpost), 1))
        snposti[snpost] = matrix(range(len(snpost)))
        snpar = matrix([snposti[snpar[snpost[k]]] for k in range(len(snpar))])
        snode = matrix(range(len(snode)))
        snpost = matrix(range(len(snpost)))
        snptr = snptr2

        # Permute A and store effective permutation and its inverse
        Ap = perm(Ap, pp)
        if p is None:
            self.__p = pp
        else:
            self.__p = p[pp]
        self.__ip = matrix(0, (len(self.__p), 1))
        self.__ip[self.__p] = matrix(range(len(self.__p)))

        # Compute embedding and relative indices
        sncolptr, snrowidx = embed(Ap, colcount, snode, snptr, snpar, snpost)
        relptr, relidx = relative_idx(sncolptr, snrowidx, snptr, snpar)

        # build chptr
        chptr = matrix(0, (len(snpar) + 1, 1))
        for j in snpost:
            if snpar[j] != j: chptr[snpar[j] + 1] += 1
        for j in range(1, len(chptr)):
            chptr[j] += chptr[j - 1]

        # build chidx
        tmp = +chptr
        chidx = matrix(0, (chptr[-1], 1))
        for j in snpost:
            if snpar[j] != j:
                chidx[tmp[snpar[j]]] = j
                tmp[snpar[j]] += 1
        del tmp

        # compute stack size
        stack_size = 0
        stack_mem = 0
        stack_max = 0
        frontal_max = 0
        stack = []
        for k in snpost:
            nn = snptr[k + 1] - snptr[k]
            na = relptr[k + 1] - relptr[k]
            nj = na + nn
            frontal_max = max(frontal_max, nj**2)
            for j in range(chptr[k + 1] - 1, chptr[k] - 1, -1):
                v = stack.pop()
                stack_mem -= v**2
            if (na > 0):
                stack.append(na)
                stack_mem += na**2
                stack_max = max(stack_max, stack_mem)
                stack_size = max(stack_size, len(stack))
        self.frontal_len = frontal_max
        self.stack_len = stack_max
        self.stack_size = stack_size

        # build blkptr
        blkptr = matrix(0, (len(snpar) + 1, 1))
        for i in range(len(snpar)):
            blkptr[i + 1] = blkptr[i] + (snptr[i + 1] - snptr[i]) * (
                sncolptr[i + 1] - sncolptr[i])

        # compute storage requirements
        stack = []
        stack_depth = 0

        stack_mem = 0
        stack_tmp = 0
        cln = 0

        stack_solve = 0
        stack_stmp = 0

        for k in snpost:
            nn = snptr[k + 1] - snptr[k]  # |Nk|
            na = relptr[k + 1] - relptr[k]  # |Ak|
            nj = na + nn
            cln = max(cln, nj)  # this is the clique number
            for i in range(chptr[k + 1] - 1, chptr[k] - 1, -1):
                na_ch = stack.pop()
                stack_tmp -= na_ch**2
                stack_stmp -= na_ch
            if na > 0:
                stack.append(na)
                stack_tmp += na**2
                stack_mem = max(stack_tmp, stack_mem)
                stack_stmp += na
                stack_solve = max(stack_stmp, stack_solve)
            stack_depth = max(stack_depth, len(stack))

        self.__clique_number = cln
        self.__n = len(snode)
        self.__Nsn = len(snpar)
        self.__snode = snode
        self.__snptr = snptr
        self.__chptr = chptr
        self.__chidx = chidx
        self.__snpar = snpar
        self.__snpost = snpost
        self.__relptr = relptr
        self.__relidx = relidx
        self.__sncolptr = sncolptr
        self.__snrowidx = snrowidx
        self.__blkptr = blkptr
        self.__fill = (nnz_Ae - nnz_Ap, self.nnz - nnz_Ae)
        self.__memory = {
            'stack_depth': stack_depth,
            'stack_mem': stack_mem,
            'frontal_mem': cln**2,
            'stack_solve': stack_solve
        }

        return
Beispiel #11
0
    def spmatrix(self, reordered=True, symmetric=False):
        """
        Converts the :py:class:`cspmatrix` :math:`A` to a sparse matrix. A reordered
        matrix is returned if the optional argument `reordered` is
        `True` (default), and otherwise the inverse permutation is applied. Only the
        default options are allowed if the :py:class:`cspmatrix` :math:`A` represents
        a Cholesky factor. 

        :param reordered:  boolean (default: True)
        :param symmetric:  boolean (default: False)			   
        """
        n = self.symb.n
        snptr = self.symb.snptr
        snode = self.symb.snode
        relptr = self.symb.relptr
        snrowidx = self.symb.snrowidx
        sncolptr = self.symb.sncolptr
        blkptr = self.symb.blkptr
        blkval = self.blkval

        if self.is_factor:
            if symmetric:
                raise ValueError(
                    "'symmetric = True' not implemented for Cholesky factors")
            if not reordered:
                raise ValueError(
                    "'reordered = False' not implemented for Cholesky factors")
            snpost = self.symb.snpost
            blkval = +blkval
            for k in snpost:
                j = snode[snptr[k]]  # representative vertex
                nn = snptr[k + 1] - snptr[k]  # |Nk|
                na = relptr[k + 1] - relptr[k]  # |Ak|
                if na == 0: continue
                nj = na + nn
                if nn == 1:
                    blas.scal(blkval[blkptr[k]],
                              blkval,
                              offset=blkptr[k] + 1,
                              n=na)
                else:
                    blas.trmm(blkval,blkval, transA = "N", diag = "N", side = "R",uplo = "L", \
                              m = na, n = nn, ldA = nj, ldB = nj, \
                              offsetA = blkptr[k],offsetB = blkptr[k] + nn)

        cc = matrix(0, (n, 1))  # count number of nonzeros in each col
        for k in range(self.symb.Nsn):
            nn = snptr[k + 1] - snptr[k]
            na = relptr[k + 1] - relptr[k]
            nj = nn + na
            for i in range(nn):
                j = snode[snptr[k] + i]
                cc[j] = nj - i

        # build col. ptr
        cp = [0]
        for i in range(n):
            cp.append(cp[-1] + cc[i])
        cp = matrix(cp)

        # copy data and row indices
        val = matrix(0.0, (cp[-1], 1))
        ri = matrix(0, (cp[-1], 1))
        for k in range(self.symb.Nsn):
            nn = snptr[k + 1] - snptr[k]
            na = relptr[k + 1] - relptr[k]
            nj = nn + na
            for i in range(nn):
                j = snode[snptr[k] + i]
                blas.copy(blkval,
                          val,
                          offsetx=blkptr[k] + nj * i + i,
                          offsety=cp[j],
                          n=nj - i)
                ri[cp[j]:cp[j + 1]] = snrowidx[sncolptr[k] + i:sncolptr[k + 1]]

        I = []
        J = []
        for i in range(n):
            I += list(ri[cp[i]:cp[i + 1]])
            J += (cp[i + 1] - cp[i]) * [i]

        tmp = spmatrix(val, I, J, (n, n))  # tmp is reordered and lower tril.

        if reordered or self.symb.p is None:
            # reordered matrix (do not apply inverse permutation)
            if not symmetric: return tmp
            else: return symmetrize(tmp)
        else:
            # apply inverse permutation
            tmp = perm(symmetrize(tmp), self.symb.ip)
            if symmetric: return tmp
            else: return tril(tmp)
Beispiel #12
0
def convert_block(G, h, dim, **kwargs):
    r"""
    Applies the clique conversion method to a single positive
    semidefinite block of a cone linear program

    .. math::
        \begin{array}{ll}
           \mbox{maximize}   & -h^T z \\
           \mbox{subject to} &  G^T z + c = 0 \\
                             &  \mathbf{smat}(z)\ \ \text{psd completable}
        \end{array}

    After conversion, the above problem is converted to a block-diagonal one 

    .. math::
        \begin{array}{ll}
           \mbox{maximize}   & -h_b^T z_b  \\
           \mbox{subject to} &  G_b^T z_b + c = 0 \\
                             &  G_c^T z_b = 0 \\
                             &  \mathbf{smat}(z_b)\ \ \text{psd block-diagonal}
        \end{array}
                             
    where :math:`z_b` is a vector representation of a block-diagonal
    matrix. The constraint :math:`G_b^T z_b + c = 0` corresponds to
    the original constraint :math:`G'z + c = 0`, and the constraint
    :math:`G_c^T z_b = 0` is a coupling constraint.
       
    :param G:                 :py:class:`spmatrix`
    :param h:                 :py:class:`matrix`
    :param dim:               integer
    :param merge_function:    routine that implements a merge heuristic (optional)
    :param coupling:          mode of conversion (optional)
    :param max_density:       float (default: 0.4)

    The following example illustrates how to apply the conversion method to a one-block SDP:
    
    .. code-block:: python

        block = (G, h, dim) 
        blockc, blk2sparse, symb = convert_block(*block)
    
    The return value `blk2sparse` is a 4-tuple
    (`blki,I,J,n`) that defines a mapping between the sparse
    matrix representation and the converted block-diagonal
    representation. If `blkvec` represents a block-diagonal matrix,
    then

    .. code-block:: python

        S = spmatrix(blkvec[blki], I, J) 

    maps `blkvec` into is a sparse matrix representation of the
    matrix. Similarly, a sparse matrix `S` can be converted to the
    block-diagonal matrix representation using the code
    
    .. code-block:: python

        blkvec = matrix(0.0, (len(S),1), tc=S.typecode)
        blkvec[blki] = S.V

    The optional argument `max_density` controls whether or not to perform
    conversion based on the aggregate sparsity of the block. Specifically,
    conversion is performed whenever the number of lower triangular nonzeros
    in the aggregate sparsity pattern is less than or equal to `max_density*dim`.
        
    The optional argument `coupling` controls the introduction
    of equality constraints in the conversion. Possible values
    are *full* (default), *sparse*, *sparse+tri*, and any nonnegative
    integer. Full coupling results in a conversion in which all
    coupling constraints are kept, and hence the converted problem is
    equivalent to the original problem. Sparse coupling yeilds a
    conversion in which only the coupling constraints corresponding to
    nonzero entries in the aggregate sparsity pattern are kept, and
    sparse-plus-tridiagonal (*sparse+tri*) yeilds a conversion with
    tridiagonal coupling in addition to coupling constraints corresponding
    to nonzero entries in the aggregate sparsity pattern. Setting `coupling`
    to a nonnegative integer *k* yields a conversion with coupling
    constraints corresponding to entries in a band with half-bandwidth *k*.

    .. seealso::

        M. S. Andersen, A. Hansson, and L. Vandenberghe, `Reduced-Complexity
        Semidefinite Relaxations of Optimal Power Flow Problems
        <http://dx.doi.org/10.1109/TPWRS.2013.2294479>`_,
        IEEE Transactions on Power Systems, 2014.
        
    """
    
    merge_function = kwargs.get('merge_function', None)
    coupling = kwargs.get('coupling', 'full')
    tskip = kwargs.get('max_density',0.4)
    
    tc = G.typecode
    
    ###
    ### Find filled pattern, compute symbolic factorization using AMD
    ### ordering, and do "symbolic conversion"
    ###
    
    # find aggregate sparsity pattern
    h = sparse(h)
    LIa = matrix(list(set(G.I).union(set(h.I))))
    Ia = [i%dim for i in LIa]
    Ja = [j//dim for j in LIa]
    Va = spmatrix(1.,Ia,Ja,(dim,dim))
    
    # find permutation, symmetrize, and permute
    Va = symmetrize(tril(Va))
        
    # if not very sparse, skip decomposition 
    if float(len(Va))/Va.size[0]**2 > tskip:
        return (G, h, None, [dim]), None, None
    
    # compute symbolic factorization 
    F = symbolic(Va, merge_function = merge_function, p = amd.order)
    p = F.p
    ip = F.ip
    Va = F.sparsity_pattern(reordered = True, symmetric = True)
    
    # symbolic conversion
    if coupling == 'sparse': coupling = tril(Va)
    elif coupling == 'sparse+tri': 
        coupling = tril(Va)
        coupling += spmatrix(1.0,[i for j in range(Va.size[0]) for i in range(j,min(Va.size[0],j+2))],\
                             [j for j in range(Va.size[0]) for i in range(j,min(Va.size[0],j+2))],Va.size)
    elif type(coupling) is int:
        assert coupling >= 0
        bw = +coupling
        coupling = spmatrix(1.0,[i for j in range(Va.size[0]) for i in range(j,min(Va.size[0],j+bw+1))],\
                            [j for j in range(Va.size[0]) for i in range(j,min(Va.size[0],j+bw+1))],Va.size)
        
    dims, sparse_to_block, constraints = symb_to_block(F, coupling = coupling)
        
    # dimension of block-diagonal representation
    N = sum([d**2 for d in dims])      

    ###
    ### Convert problem data 
    ###
    
    m = G.size[1]           # cols in G
    cp, ri, val = G.CCS   
    
    IV = []                 # list of m (row, value) tuples
    J = []
    for j in range(m):
        iv = []
        for i in range(cp[j+1]-cp[j]):
            row = ri[cp[j]+i]%dim
            col = ri[cp[j]+i]//dim
            if row < col: continue   # ignore upper triangular entries
            k1 = ip[row]
            k2 = ip[col]
            blk_idx = sparse_to_block[min(k1,k2)*dim + max(k1,k2)]
            if k1 == k2:
                iv.append((blk_idx[0], val[cp[j]+i]))
            elif k1 > k2:
                iv.append((blk_idx[0], val[cp[j]+i]))
                iv.append((blk_idx[1], val[cp[j]+i].conjugate()))
            else:
                iv.append((blk_idx[0], val[cp[j]+i].conjugate()))
                iv.append((blk_idx[1], val[cp[j]+i]))                    
        iv.sort(key=lambda x: x[0])
        IV.extend(iv)
        J.extend(len(iv)*[j])
                    
    # build G_converted
    I, V = zip(*IV)
    G_converted = spmatrix(V, I, J, (N, m), tc = tc)
        
    # convert and build new h
    _, ri, val = h.CCS
    iv = []
    for i in range(len(ri)):
        row = ri[i]%dim
        col = ri[i]//dim
        if row < col: continue   # ignore upper triangular entries
        k1 = ip[row]
        k2 = ip[col]
        blk_idx = sparse_to_block[min(k1,k2)*dim + max(k1,k2)]
        if k1 == k2:
            iv.append((blk_idx[0], val[i]))
        elif k1 > k2:
            iv.append((blk_idx[0], val[i]))
            iv.append((blk_idx[1], val[i].conjugate()))
        else:
            iv.append((blk_idx[0], val[i].conjugate()))
            iv.append((blk_idx[1], val[i]))
    
    iv.sort(key=lambda x: x[0])
    if iv:
        I, V = zip(*iv)
    else:
        I, V = [], []
    h_converted = spmatrix(V, I, len(I)*[0], (N, 1), tc = tc)
    
    ###
    ### Build matrix representation of coupling constraints
    ###
    
    IV = []   # list of (row, value) tuples
    J = []
    ncon = 0
    for j in range(len(constraints)):
        iv = []
        if len(constraints[j]) == 2:
            ii, jj = constraints[j]
            iv = sorted([(ii, 1.0), (jj, -1.0)],key=lambda x: x[0])
            jl = 2*[ncon]
            ncon += 1
        elif len(constraints[j]) == 4:
            i1,j1,i2,j2 = constraints[j]
            iv = sorted([(i1, 1.0), (i2, 1.0), (j1, -1.0), (j2, -1.0)],key=lambda x: x[0])
            jl = 4*[ncon]
            ncon += 1
            if tc == 'z':
                iv.extend(sorted([(i1, complex(0.0,1.0)), (i2, complex(0.0,-1.0)),
                           (j1, complex(0.0,-1.0)), (j2, complex(0.0,1.0))],key=lambda x: x[0]))
                jl.extend(4*[ncon])
                ncon += 1
        IV.extend(iv)
        J.extend(jl)
                
    # build G_converted
    if IV: I, V = zip(*IV)
    else: I, V = [], []
    G_coupling = spmatrix(V, I, J, (N, ncon), tc = tc)
            
    # generate indices for reverse mapping (block_to_sparse)
    idx = []
    for k in sparse_to_block.keys():
        k1 = p[k%dim]
        k2 = p[k//dim]
        idx.append((min(k1,k2)*dim + max(k1,k2), sparse_to_block[k][0]))

    idx.sort()
    idx, blki = zip(*idx)
    blki = matrix(blki)
    I = [v%dim for v in idx]
    J = [v//dim for v in idx]
    n = sum([di**2 for di in dims])
    
    return (G_converted, h_converted, G_coupling, dims), (blki, I, J, n), F
Beispiel #13
0
def maxchord(A, ve=None):
    """
    Maximal chordal subgraph of sparsity graph.

    Returns a lower triangular sparse matrix which is the projection
    of :math:`A` on a maximal chordal subgraph and a perfect
    elimination order :math:`p`. Only the
    lower triangular part of :math:`A` is accessed. The
    optional argument `ve` is the index of the last vertex to be
    eliminated (the default value is `n-1`). If :math:`A` is chordal,
    then the matrix returned is equal to :math:`A`.

    :param A:   :py:class:`spmatrix`

    :param ve:  integer between 0 and `A.size[0]`-1 (optional)

    .. seealso::

         P. M. Dearing, D. R. Shier, D. D. Warner, `Maximal chordal
         subgraphs <http://dx.doi.org/10.1016/0166-218X(88)90075-3>`_,
         Discrete Applied Mathematics, 20:3, 1988, pp. 181-190.

    """

    n = A.size[0]
    assert A.size[1] == n, "A must be a square matrix"
    assert type(A) is spmatrix, "A must be a sparse matrix"
    if ve is None:
        ve = n - 1
    else:
        assert type(ve) is int and 0<=ve<n,\
          "ve must be an integer between 0 and A.size[0]-1"
    As = symmetrize(A)
    cp, ri, val = As.CCS

    # permutation vector
    p = matrix(0, (n, 1))

    # weight array
    w = matrix(0, (n, 1))
    max_w = 0
    S = [list(range(ve)) + list(range(ve + 1, n)) + [ve]
         ] + [[] for i in range(n - 1)]

    C = [set() for i in range(n)]
    E = [[] for i in range(n)]  # edge list
    V = [[] for i in range(n)]  # num. values

    for i in range(n - 1, -1, -1):
        # select next node to number
        while True:
            if len(S[max_w]) > 0:
                v = S[max_w].pop()
                if w[v] >= 0: break
            else:
                max_w -= 1
        p[i] = v
        w[v] = -1  # set w[v] = -1 to mark that node v has been numbered

        # loop over unnumbered neighbors of node v
        for ii in range(cp[v], cp[v + 1]):
            u = ri[ii]
            d = val[ii]
            if w[u] >= 0:
                if C[u].issubset(C[v]):
                    C[u].update([v])
                    w[u] += 1
                    S[w[u]].append(u)  # bump up u to S[w[u]]
                    max_w = max(max_w, w[u])  # update max deg.
                    E[min(u, v)].append(max(u, v))
                    V[min(u, v)].append(d)
            elif u == v:
                E[u].append(u)
                V[u].append(d)

    # build adjacency matrix of reordered max. chordal subgraph
    Am = spmatrix([d for d in chain.from_iterable(V)],[i for i in chain.from_iterable(E)],\
                  [i for i in chain.from_iterable([len(Ej)*[j] for j,Ej in enumerate(E)])],(n,n))

    return Am, p
Beispiel #14
0
def maxchord(A, ve = None):
    """
    Maximal chordal subgraph of sparsity graph.

    Returns a lower triangular sparse matrix which is the projection
    of :math:`A` on a maximal chordal subgraph and a perfect
    elimination order :math:`p`. Only the
    lower triangular part of :math:`A` is accessed. The
    optional argument `ve` is the index of the last vertex to be
    eliminated (the default value is `n-1`). If :math:`A` is chordal,
    then the matrix returned is equal to :math:`A`.

    :param A:   :py:class:`spmatrix`

    :param ve:  integer between 0 and `A.size[0]`-1 (optional)

    .. seealso::

         P. M. Dearing, D. R. Shier, D. D. Warner, `Maximal chordal
         subgraphs <http://dx.doi.org/10.1016/0166-218X(88)90075-3>`_,
         Discrete Applied Mathematics, 20:3, 1988, pp. 181-190.

    """

    n = A.size[0]
    assert A.size[1] == n, "A must be a square matrix"
    assert type(A) is spmatrix, "A must be a sparse matrix"
    if ve is None:
        ve = n-1
    else:
        assert type(ve) is int and 0<=ve<n,\
          "ve must be an integer between 0 and A.size[0]-1"
    As = symmetrize(A)
    cp,ri,val = As.CCS

    # permutation vector
    p = matrix(0,(n,1))

    # weight array
    w = matrix(0,(n,1))
    max_w = 0
    S = [list(range(ve))+list(range(ve+1,n))+[ve]] + [[] for i in range(n-1)]

    C = [set() for i in range(n)]
    E = [[] for i in range(n)]     # edge list
    V = [[] for i in range(n)]     # num. values

    for i in range(n-1,-1,-1):
        # select next node to number
        while True:
            if len(S[max_w]) > 0:
                v = S[max_w].pop()
                if w[v] >= 0: break
            else:
                max_w -= 1
        p[i] = v
        w[v] = -1   # set w[v] = -1 to mark that node v has been numbered

        # loop over unnumbered neighbors of node v
        for ii in range(cp[v],cp[v+1]):
            u = ri[ii]
            d = val[ii]
            if w[u] >= 0:
                if C[u].issubset(C[v]):
                    C[u].update([v])
                    w[u] += 1
                    S[w[u]].append(u)    # bump up u to S[w[u]]
                    max_w = max(max_w,w[u])  # update max deg.
                    E[min(u,v)].append(max(u,v))
                    V[min(u,v)].append(d)
            elif u == v:
                E[u].append(u)
                V[u].append(d)

    # build adjacency matrix of reordered max. chordal subgraph
    Am = spmatrix([d for d in chain.from_iterable(V)],[i for i in chain.from_iterable(E)],\
                  [i for i in chain.from_iterable([len(Ej)*[j] for j,Ej in enumerate(E)])],(n,n))

    return Am,p