Exemplo n.º 1
0
def classical_strength_of_connection(A,
                                     theta=0.25,
                                     block=None,
                                     norm='abs',
                                     cost=[0]):
    """
    Return a strength of connection matrix using the classical AMG measure
    An off-diagonal entry A[i,j] is a strong connection iff::

            | A[i,j] | >= theta * max(| A[i,k] |), where k != i     (norm='abs')
             -A[i,j]   >= theta * max(| A[i,k] |), where k != i     (norm='min')

    Parameters
    ----------
    A : csr_matrix or bsr_matrix
        Square, sparse matrix in CSR or BSR format
    theta : float
        Threshold parameter in [0,1].
    block : string, default None for CSR matrix and 'block' for BSR matrix
        How to treat block structure of A:
            None         : Compute SOC based on A as CSR matrix.
            'block'      : Compute SOC based on norm of blocks of A.
            'amalgamate' : Compute SOC based on A as CSR matrix, then compute
                           norm of blocks in SOC matrix for a block SOC. 
    norm : 'string', default 'abs'
        Option to compute SOC between elements or blocks: 
            'abs'  : C_ij = k, where k is the maximum absolute value in block C_ij
            'min'  : C_ij = k, where k is the minimum (negative) value in block C_ij
            'fro'  : C_ij = k, where k is the Frobenius norm of block C_ij
                - Only valid for block matrices, block='block'

    Returns
    -------
    S : csr_matrix
        Matrix graph defining strong connections.  S[i,j]=1 if vertex i
        is strongly influenced by vertex j.

    See Also
    --------
    symmetric_strength_of_connection : symmetric measure used in SA
    evolution_strength_of_connection : relaxation based strength measure

    Notes
    -----
    - A symmetric A does not necessarily yield a symmetric strength matrix S
    - Calls C++ function classical_strength_of_connection

    References
    ----------

    .. [1] Briggs, W. L., Henson, V. E., McCormick, S. F., "A multigrid
       tutorial", Second edition. Society for Industrial and Applied
       Mathematics (SIAM), Philadelphia, PA, 2000. xii+193 pp.
       ISBN: 0-89871-462-1

    .. [2] Trottenberg, U., Oosterlee, C. W., Schuller, A., "Multigrid",
       Academic Press, Inc., San Diego, CA, 2001. xvi+631 pp.
       ISBN: 0-12-701070-X

    Examples
    --------
    >>> import numpy as np
    >>> from pyamg.gallery import stencil_grid
    >>> from pyamg.strength import classical_strength_of_connection
    >>> n=3
    >>> stencil = np.array([[-1.0,-1.0,-1.0],
    ...                        [-1.0, 8.0,-1.0],
    ...                        [-1.0,-1.0,-1.0]])
    >>> A = stencil_grid(stencil, (n,n), format='csr')
    >>> S = classical_strength_of_connection(A, 0.0)

    """
    if (theta < 0 or theta > 1):
        raise ValueError('expected theta in [0,1]')

    if (theta < 0 or theta > 1):
        raise ValueError('expected theta in [0,1]')

    if sparse.isspmatrix_bsr(A):
        blocksize = A.blocksize[0]
    else:
        blocksize = 1

    # Block structure considered before computing SOC
    if (block == 'block') or sparse.isspmatrix_bsr(A):
        R, C = A.blocksize
        if (R != C) or (R < 1):
            raise ValueError('Matrix must have square blocks')

        N = int(A.shape[0] / R)

        # SOC based on maximum absolute value element in each block
        if norm == 'abs':
            data = np.max(np.max(np.abs(A.data), axis=1), axis=1)
            cost[0] += 1
        # SOC based on hard minimum of entry in each off-diagonal block
        elif norm == 'min':
            data = np.min(np.min(A.data, axis=1), axis=1)
            cost[0] += 1
        # SOC based on Frobenius norms of blocks
        elif norm == 'fro':
            data = (np.conjugate(A.data) * A.data).reshape(-1,
                                                           R * C).sum(axis=1)
            cost[0] += 1
        else:
            raise ValueError("Invalid choice of norm.")

        data[np.abs(data) < 1e-16] = 0.0
        S_rowptr = np.empty_like(A.indptr)
        S_colinds = np.empty_like(A.indices)
        S_data = np.empty_like(data)

        if norm == 'abs' or norm == 'fro':
            amg_core.classical_strength_of_connection_abs(
                N, theta, A.indptr, A.indices, data, S_rowptr, S_colinds,
                S_data)
        elif norm == 'min':
            amg_core.classical_strength_of_connection_min(
                N, theta, A.indptr, A.indices, data, S_rowptr, S_colinds,
                S_data)
        else:
            raise ValueError("Unrecognized option for norm.")

        # One pass through nnz to find largest entry, one to filter
        S = sparse.csr_matrix((S_data, S_colinds, S_rowptr), shape=[N, N])
        cost[0] += 2

        # Take magnitude and scale by largest entry
        S.data = np.abs(S.data)
        S = scale_rows_by_largest_entry(S)
        S.eliminate_zeros()

        # Assume largest entry can be tracked from filtering.
        # 1 WU to scale matrix.
        cost[0] += float(S.nnz) / A.nnz

        return S

    # SOC computed based on A as CSR
    else:
        S_rowptr = np.empty_like(A.indptr)
        S_colinds = np.empty_like(A.indices)
        S_data = np.empty_like(A.data)

        if norm == 'abs' or norm == 'fro':
            amg_core.classical_strength_of_connection_abs(
                A.shape[0], theta, A.indptr, A.indices, A.data, S_rowptr,
                S_colinds, S_data)
        elif norm == 'min':
            amg_core.classical_strength_of_connection_min(
                A.shape[0], theta, A.indptr, A.indices, A.data, S_rowptr,
                S_colinds, S_data)
        else:
            raise ValueError("Unrecognized option for norm.")

        # One pass through nnz to find largest entry, one to filter
        S = sparse.csr_matrix((S_data, S_colinds, S_rowptr), shape=A.shape)
        cost[0] += 2

        if blocksize > 1 and block == 'amalgamate':
            S = amalgamate(S, blocksize, norm=norm)

        # Take magnitude and scale by largest entry
        S.data = np.abs(S.data)
        S = scale_rows_by_largest_entry(S)
        S.eliminate_zeros()

        # Assume largest entry can be tracked from filtering.
        # 1 WU to scale matrix.
        cost[0] += float(S.nnz) / A.nnz

        return S
Exemplo n.º 2
0
def classical_strength_of_connection(A, theta=0.0):
    """
    Return a strength of connection matrix using the classical AMG measure
    An off-diagonal entry A[i,j] is a strong connection iff::

            | A[i,j] | >= theta * max(| A[i,k] |), where k != i

    Parameters
    ----------
    A : csr_matrix or bsr_matrix
        Square, sparse matrix in CSR or BSR format
    theta : float
        Threshold parameter in [0,1].

    Returns
    -------
    S : csr_matrix
        Matrix graph defining strong connections.  S[i,j]=1 if vertex i
        is strongly influenced by vertex j.

    See Also
    --------
    symmetric_strength_of_connection : symmetric measure used in SA
    evolution_strength_of_connection : relaxation based strength measure

    Notes
    -----
    - A symmetric A does not necessarily yield a symmetric strength matrix S
    - Calls C++ function classical_strength_of_connection
    - The version as implemented is designed form M-matrices.  Trottenberg et
      al. use max A[i,k] over all negative entries, which is the same.  A
      positive edge weight never indicates a strong connection.

    References
    ----------

    .. [1] Briggs, W. L., Henson, V. E., McCormick, S. F., "A multigrid
       tutorial", Second edition. Society for Industrial and Applied
       Mathematics (SIAM), Philadelphia, PA, 2000. xii+193 pp.
       ISBN: 0-89871-462-1

    .. [2] Trottenberg, U., Oosterlee, C. W., Schuller, A., "Multigrid",
       Academic Press, Inc., San Diego, CA, 2001. xvi+631 pp.
       ISBN: 0-12-701070-X

    Examples
    --------
    >>> import numpy as np
    >>> from pyamg.gallery import stencil_grid
    >>> from pyamg.strength import classical_strength_of_connection
    >>> n=3
    >>> stencil = np.array([[-1.0,-1.0,-1.0],
    ...                        [-1.0, 8.0,-1.0],
    ...                        [-1.0,-1.0,-1.0]])
    >>> A = stencil_grid(stencil, (n,n), format='csr')
    >>> S = classical_strength_of_connection(A, 0.0)

    """

    if sparse.isspmatrix_bsr(A):
        blocksize = A.blocksize[0]
    else:
        blocksize = 1

    if not sparse.isspmatrix_csr(A):
        warn("Implicit conversion of A to csr", sparse.SparseEfficiencyWarning)
        A = sparse.csr_matrix(A)

    if (theta < 0 or theta > 1):
        raise ValueError('expected theta in [0,1]')

    Sp = np.empty_like(A.indptr)
    Sj = np.empty_like(A.indices)
    Sx = np.empty_like(A.data)

    fn = amg_core.classical_strength_of_connection
    fn(A.shape[0], theta, A.indptr, A.indices, A.data, Sp, Sj, Sx)
    S = sparse.csr_matrix((Sx, Sj, Sp), shape=A.shape)

    if blocksize > 1:
        S = amalgamate(S, blocksize)

    # Strength represents "distance", so take the magnitude
    S.data = np.abs(S.data)

    # Scale S by the largest magnitude entry in each row
    S = scale_rows_by_largest_entry(S)

    return S
Exemplo n.º 3
0
def classical_strength_of_connection(A, theta=0.0):
    """
    Return a strength of connection matrix using the classical AMG measure
    An off-diagonal entry A[i,j] is a strong connection iff::

            | A[i,j] | >= theta * max(| A[i,k] |), where k != i

    Parameters
    ----------
    A : csr_matrix or bsr_matrix
        Square, sparse matrix in CSR or BSR format
    theta : float
        Threshold parameter in [0,1].

    Returns
    -------
    S : csr_matrix
        Matrix graph defining strong connections.  S[i,j]=1 if vertex i
        is strongly influenced by vertex j.

    See Also
    --------
    symmetric_strength_of_connection : symmetric measure used in SA
    evolution_strength_of_connection : relaxation based strength measure

    Notes
    -----
    - A symmetric A does not necessarily yield a symmetric strength matrix S
    - Calls C++ function classical_strength_of_connection
    - The version as implemented is designed form M-matrices.  Trottenberg et
      al. use max A[i,k] over all negative entries, which is the same.  A
      positive edge weight never indicates a strong connection.

    References
    ----------

    .. [1] Briggs, W. L., Henson, V. E., McCormick, S. F., "A multigrid
       tutorial", Second edition. Society for Industrial and Applied
       Mathematics (SIAM), Philadelphia, PA, 2000. xii+193 pp.
       ISBN: 0-89871-462-1

    .. [2] Trottenberg, U., Oosterlee, C. W., Schuller, A., "Multigrid",
       Academic Press, Inc., San Diego, CA, 2001. xvi+631 pp.
       ISBN: 0-12-701070-X

    Examples
    --------
    >>> import numpy as np
    >>> from pyamg.gallery import stencil_grid
    >>> from pyamg.strength import classical_strength_of_connection
    >>> n=3
    >>> stencil = np.array([[-1.0,-1.0,-1.0],
    ...                        [-1.0, 8.0,-1.0],
    ...                        [-1.0,-1.0,-1.0]])
    >>> A = stencil_grid(stencil, (n,n), format='csr')
    >>> S = classical_strength_of_connection(A, 0.0)

    """

    if sparse.isspmatrix_bsr(A):
        blocksize = A.blocksize[0]
    else:
        blocksize = 1

    if not sparse.isspmatrix_csr(A):
        warn("Implicit conversion of A to csr", sparse.SparseEfficiencyWarning)
        A = sparse.csr_matrix(A)

    if (theta < 0 or theta > 1):
        raise ValueError('expected theta in [0,1]')

    Sp = np.empty_like(A.indptr)
    Sj = np.empty_like(A.indices)
    Sx = np.empty_like(A.data)

    fn = amg_core.classical_strength_of_connection
    fn(A.shape[0], theta, A.indptr, A.indices, A.data, Sp, Sj, Sx)
    S = sparse.csr_matrix((Sx, Sj, Sp), shape=A.shape)

    if blocksize > 1:
        S = amalgamate(S, blocksize)

    # Strength represents "distance", so take the magnitude
    S.data = np.abs(S.data)

    # Scale S by the largest magnitude entry in each row
    S = scale_rows_by_largest_entry(S)

    return S
Exemplo n.º 4
0
def initial_setup_stage(A,
                        symmetry,
                        pdef,
                        candidate_iters,
                        epsilon,
                        max_levels,
                        max_coarse,
                        aggregate,
                        prepostsmoother,
                        smooth,
                        strength,
                        work,
                        initial_candidate=None):
    """
    Computes a complete aggregation and the first near-nullspace candidate
    following Algorithm 3 in Brezina et al.

    Parameters
    ----------
    candidate_iters
        number of test relaxation iterations
    epsilon
        minimum acceptable relaxation convergence factor

    References
    ----------
    .. [1] Brezina, Falgout, MacLachlan, Manteuffel, McCormick, and Ruge
       "Adaptive Smoothed Aggregation ($\alpha$SA) Multigrid"
       SIAM Review Volume 47,  Issue 2  (2005)
       http://www.cs.umn.edu/~maclach/research/aSA2.pdf
    """

    # Define relaxation routine
    def relax(A, x):
        fn, kwargs = unpack_arg(prepostsmoother)
        if fn == 'gauss_seidel':
            gauss_seidel(A,
                         x,
                         np.zeros_like(x),
                         iterations=candidate_iters,
                         sweep='symmetric')
        elif fn == 'gauss_seidel_nr':
            gauss_seidel_nr(A,
                            x,
                            np.zeros_like(x),
                            iterations=candidate_iters,
                            sweep='symmetric')
        elif fn == 'gauss_seidel_ne':
            gauss_seidel_ne(A,
                            x,
                            np.zeros_like(x),
                            iterations=candidate_iters,
                            sweep='symmetric')
        elif fn == 'jacobi':
            jacobi(A,
                   x,
                   np.zeros_like(x),
                   iterations=1,
                   omega=1.0 / rho_D_inv_A(A))
        elif fn == 'richardson':
            polynomial(A,
                       x,
                       np.zeros_like(x),
                       iterations=1,
                       coefficients=[1.0 / approximate_spectral_radius(A)])
        elif fn == 'gmres':
            x[:] = (gmres(A, np.zeros_like(x), x0=x,
                          maxiter=candidate_iters)[0]).reshape(x.shape)
        else:
            raise TypeError('Unrecognized smoother')

    # flag for skipping steps f-i in step 4
    skip_f_to_i = True

    # step 1
    A_l = A
    if initial_candidate is None:
        x = sp.rand(A_l.shape[0], 1).astype(A_l.dtype)
        # The following type check matches the usual 'complex' type,
        # but also numpy data types such as 'complex64', 'complex128'
        # and 'complex256'.
        if A_l.dtype.name.startswith('complex'):
            x = x + 1.0j * sp.rand(A_l.shape[0], 1)
    else:
        x = np.array(initial_candidate, dtype=A_l.dtype)

    # step 2
    relax(A_l, x)
    work[:] += A_l.nnz * candidate_iters * 2

    # step 3
    # not advised to stop the iteration here: often the first relaxation pass
    # _is_ good, but the remaining passes are poor
    # if x_A_x/x_A_x_old < epsilon:
    #    # relaxation alone is sufficient
    #    print 'relaxation alone works: %g'%(x_A_x/x_A_x_old)
    #    return x, []

    # step 4
    As = [A]
    xs = [x]
    Ps = []
    AggOps = []
    StrengthOps = []

    while A.shape[0] > max_coarse and max_levels > 1:
        # The real check to break from the while loop is below

        # Begin constructing next level
        fn, kwargs = unpack_arg(strength[len(As) - 1])  # step 4b
        if fn == 'symmetric':
            C_l = symmetric_strength_of_connection(A_l, **kwargs)
            # Diagonal must be nonzero
            C_l = C_l + eye(C_l.shape[0], C_l.shape[1], format='csr')
        elif fn == 'classical':
            C_l = classical_strength_of_connection(A_l, **kwargs)
            # Diagonal must be nonzero
            C_l = C_l + eye(C_l.shape[0], C_l.shape[1], format='csr')
            if isspmatrix_bsr(A_l):
                C_l = amalgamate(C_l, A_l.blocksize[0])
        elif (fn == 'ode') or (fn == 'evolution'):
            C_l = evolution_strength_of_connection(
                A_l, np.ones((A_l.shape[0], 1), dtype=A.dtype), **kwargs)
        elif fn == 'predefined':
            C_l = kwargs['C'].tocsr()
        elif fn is None:
            C_l = A_l.tocsr()
        else:
            raise ValueError('unrecognized strength of connection method: %s' %
                             str(fn))

        # In SA, strength represents "distance", so we take magnitude of
        # complex values
        if C_l.dtype.name.startswith('complex'):
            C_l.data = np.abs(C_l.data)

        # Create a unified strength framework so that large values represent
        # strong connections and small values represent weak connections
        if (fn == 'ode') or (fn == 'evolution') or (fn == 'energy_based'):
            C_l.data = 1.0 / C_l.data

        # aggregation
        fn, kwargs = unpack_arg(aggregate[len(As) - 1])
        if fn == 'standard':
            AggOp = standard_aggregation(C_l, **kwargs)[0]
        elif fn == 'lloyd':
            AggOp = lloyd_aggregation(C_l, **kwargs)[0]
        elif fn == 'predefined':
            AggOp = kwargs['AggOp'].tocsr()
        else:
            raise ValueError('unrecognized aggregation method %s' % str(fn))

        T_l, x = fit_candidates(AggOp, x)  # step 4c

        fn, kwargs = unpack_arg(smooth[len(As) - 1])  # step 4d
        if fn == 'jacobi':
            P_l = jacobi_prolongation_smoother(A_l, T_l, C_l, x, **kwargs)
        elif fn == 'richardson':
            P_l = richardson_prolongation_smoother(A_l, T_l, **kwargs)
        elif fn == 'energy':
            P_l = energy_prolongation_smoother(A_l, T_l, C_l, x, None,
                                               (False, {}), **kwargs)
        elif fn is None:
            P_l = T_l
        else:
            raise ValueError('unrecognized prolongation smoother method %s' %
                             str(fn))

        # R should reflect A's structure # step 4e
        if symmetry == 'symmetric':
            A_l = P_l.T.asformat(P_l.format) * A_l * P_l
        elif symmetry == 'hermitian':
            A_l = P_l.H.asformat(P_l.format) * A_l * P_l

        StrengthOps.append(C_l)
        AggOps.append(AggOp)
        Ps.append(P_l)
        As.append(A_l)

        # skip to step 5 as in step 4e
        if (A_l.shape[0] <= max_coarse) or (len(AggOps) + 1 >= max_levels):
            break

        if not skip_f_to_i:
            x_hat = x.copy()  # step 4g
            relax(A_l, x)  # step 4h
            work[:] += A_l.nnz * candidate_iters * 2
            if pdef is True:
                x_A_x = np.dot(np.conjugate(x).T, A_l * x)
                xhat_A_xhat = np.dot(np.conjugate(x_hat).T, A_l * x_hat)
                err_ratio = (x_A_x / xhat_A_xhat)**(1.0 / candidate_iters)
            else:
                # use A.H A inner-product
                Ax = A_l * x
                # Axhat = A_l * x_hat
                x_A_x = np.dot(np.conjugate(Ax).T, Ax)
                xhat_A_xhat = np.dot(np.conjugate(x_hat).T, A_l * x_hat)
                err_ratio = (x_A_x / xhat_A_xhat)**(1.0 / candidate_iters)

            if err_ratio < epsilon:  # step 4i
                # print "sufficient convergence, skipping"
                skip_f_to_i = True
                if x_A_x == 0:
                    x = x_hat  # need to restore x
        else:
            # just carry out relaxation, don't check for convergence
            relax(A_l, x)  # step 4h
            work[:] += 2 * A_l.nnz * candidate_iters

        # store xs for diagnostic use and for use in step 5
        xs.append(x)

    # step 5
    # Extend coarse-level candidate to the finest level
    # --> note that we start with the x from the second coarsest level
    x = xs[-1]
    # make sure that xs[-1] has been relaxed by step 4h, i.e. relax(As[-2], x)
    for lev in range(len(Ps) - 2, -1, -1):  # lev = coarsest ... finest-1
        P = Ps[lev]  # I: lev --> lev+1
        A = As[lev]  # A on lev+1
        x = P * x
        relax(A, x)
        work[:] += A.nnz * candidate_iters * 2

    # Set predefined strength of connection and aggregation
    if len(AggOps) > 1:
        aggregate = [('predefined', {
            'AggOp': AggOps[i]
        }) for i in range(len(AggOps))]
        strength = [('predefined', {
            'C': StrengthOps[i]
        }) for i in range(len(StrengthOps))]

    return x, aggregate, strength  # first candidate
Exemplo n.º 5
0
def initial_setup_stage(A, symmetry, pdef, candidate_iters, epsilon,
                        max_levels, max_coarse, aggregate, prepostsmoother,
                        smooth, strength, work, initial_candidate=None):
    """Compute aggregation and the first near-nullspace candidate following Algorithm 3 in Brezina et al.

    Parameters
    ----------
    candidate_iters
        number of test relaxation iterations
    epsilon
        minimum acceptable relaxation convergence factor

    References
    ----------
    .. [1] Brezina, Falgout, MacLachlan, Manteuffel, McCormick, and Ruge
       "Adaptive Smoothed Aggregation (aSA) Multigrid"
       SIAM Review Volume 47,  Issue 2  (2005)
       http://www.cs.umn.edu/~maclach/research/aSA2.pdf

    """
    # Define relaxation routine
    def relax(A, x):
        fn, kwargs = unpack_arg(prepostsmoother)
        if fn == 'gauss_seidel':
            gauss_seidel(A, x, np.zeros_like(x),
                         iterations=candidate_iters, sweep='symmetric')
        elif fn == 'gauss_seidel_nr':
            gauss_seidel_nr(A, x, np.zeros_like(x),
                            iterations=candidate_iters, sweep='symmetric')
        elif fn == 'gauss_seidel_ne':
            gauss_seidel_ne(A, x, np.zeros_like(x),
                            iterations=candidate_iters, sweep='symmetric')
        elif fn == 'jacobi':
            jacobi(A, x, np.zeros_like(x), iterations=1,
                   omega=1.0 / rho_D_inv_A(A))
        elif fn == 'richardson':
            polynomial(A, x, np.zeros_like(x), iterations=1,
                       coefficients=[1.0/approximate_spectral_radius(A)])
        elif fn == 'gmres':
            x[:] = (gmres(A, np.zeros_like(x), x0=x,
                          maxiter=candidate_iters)[0]).reshape(x.shape)
        else:
            raise TypeError('Unrecognized smoother')

    # flag for skipping steps f-i in step 4
    skip_f_to_i = True

    # step 1
    A_l = A
    if initial_candidate is None:
        x = sp.rand(A_l.shape[0], 1).astype(A_l.dtype)
        # The following type check matches the usual 'complex' type,
        # but also numpy data types such as 'complex64', 'complex128'
        # and 'complex256'.
        if A_l.dtype.name.startswith('complex'):
            x = x + 1.0j*sp.rand(A_l.shape[0], 1)
    else:
        x = np.array(initial_candidate, dtype=A_l.dtype)

    # step 2
    relax(A_l, x)
    work[:] += A_l.nnz * candidate_iters*2

    # step 3
    # not advised to stop the iteration here: often the first relaxation pass
    # _is_ good, but the remaining passes are poor
    # if x_A_x/x_A_x_old < epsilon:
    #    # relaxation alone is sufficient
    #    print 'relaxation alone works: %g'%(x_A_x/x_A_x_old)
    #    return x, []

    # step 4
    As = [A]
    xs = [x]
    Ps = []
    AggOps = []
    StrengthOps = []

    while A.shape[0] > max_coarse and max_levels > 1:
        # The real check to break from the while loop is below

        # Begin constructing next level
        fn, kwargs = unpack_arg(strength[len(As)-1])  # step 4b
        if fn == 'symmetric':
            C_l = symmetric_strength_of_connection(A_l, **kwargs)
            # Diagonal must be nonzero
            C_l = C_l + eye(C_l.shape[0], C_l.shape[1], format='csr')
        elif fn == 'classical':
            C_l = classical_strength_of_connection(A_l, **kwargs)
            # Diagonal must be nonzero
            C_l = C_l + eye(C_l.shape[0], C_l.shape[1], format='csr')
            if isspmatrix_bsr(A_l):
                C_l = amalgamate(C_l, A_l.blocksize[0])
        elif (fn == 'ode') or (fn == 'evolution'):
            C_l = evolution_strength_of_connection(A_l,
                                                   np.ones(
                                                       (A_l.shape[0], 1),
                                                       dtype=A.dtype),
                                                   **kwargs)
        elif fn == 'predefined':
            C_l = kwargs['C'].tocsr()
        elif fn is None:
            C_l = A_l.tocsr()
        else:
            raise ValueError('unrecognized strength of connection method: %s' %
                             str(fn))

        # In SA, strength represents "distance", so we take magnitude of
        # complex values
        if C_l.dtype.name.startswith('complex'):
            C_l.data = np.abs(C_l.data)

        # Create a unified strength framework so that large values represent
        # strong connections and small values represent weak connections
        if (fn == 'ode') or (fn == 'evolution') or (fn == 'energy_based'):
            C_l.data = 1.0 / C_l.data

        # aggregation
        fn, kwargs = unpack_arg(aggregate[len(As) - 1])
        if fn == 'standard':
            AggOp = standard_aggregation(C_l, **kwargs)[0]
        elif fn == 'lloyd':
            AggOp = lloyd_aggregation(C_l, **kwargs)[0]
        elif fn == 'predefined':
            AggOp = kwargs['AggOp'].tocsr()
        else:
            raise ValueError('unrecognized aggregation method %s' % str(fn))

        T_l, x = fit_candidates(AggOp, x)  # step 4c

        fn, kwargs = unpack_arg(smooth[len(As)-1])  # step 4d
        if fn == 'jacobi':
            P_l = jacobi_prolongation_smoother(A_l, T_l, C_l, x, **kwargs)
        elif fn == 'richardson':
            P_l = richardson_prolongation_smoother(A_l, T_l, **kwargs)
        elif fn == 'energy':
            P_l = energy_prolongation_smoother(A_l, T_l, C_l, x, None,
                                               (False, {}), **kwargs)
        elif fn is None:
            P_l = T_l
        else:
            raise ValueError('unrecognized prolongation smoother method %s' %
                             str(fn))

        # R should reflect A's structure # step 4e
        if symmetry == 'symmetric':
            A_l = P_l.T.asformat(P_l.format) * A_l * P_l
        elif symmetry == 'hermitian':
            A_l = P_l.H.asformat(P_l.format) * A_l * P_l

        StrengthOps.append(C_l)
        AggOps.append(AggOp)
        Ps.append(P_l)
        As.append(A_l)

        # skip to step 5 as in step 4e
        if (A_l.shape[0] <= max_coarse) or (len(AggOps) + 1 >= max_levels):
            break

        if not skip_f_to_i:
            x_hat = x.copy()  # step 4g
            relax(A_l, x)  # step 4h
            work[:] += A_l.nnz*candidate_iters*2
            if pdef is True:
                x_A_x = np.dot(np.conjugate(x).T, A_l*x)
                xhat_A_xhat = np.dot(np.conjugate(x_hat).T, A_l*x_hat)
                err_ratio = (x_A_x/xhat_A_xhat)**(1.0/candidate_iters)
            else:
                # use A.H A inner-product
                Ax = A_l * x
                # Axhat = A_l * x_hat
                x_A_x = np.dot(np.conjugate(Ax).T, Ax)
                xhat_A_xhat = np.dot(np.conjugate(x_hat).T, A_l*x_hat)
                err_ratio = (x_A_x/xhat_A_xhat)**(1.0/candidate_iters)

            if err_ratio < epsilon:  # step 4i
                # print "sufficient convergence, skipping"
                skip_f_to_i = True
                if x_A_x == 0:
                    x = x_hat  # need to restore x
        else:
            # just carry out relaxation, don't check for convergence
            relax(A_l, x)  # step 4h
            work[:] += 2 * A_l.nnz * candidate_iters

        # store xs for diagnostic use and for use in step 5
        xs.append(x)

    # step 5
    # Extend coarse-level candidate to the finest level
    # --> note that we start with the x from the second coarsest level
    x = xs[-1]
    # make sure that xs[-1] has been relaxed by step 4h, i.e. relax(As[-2], x)
    for lev in range(len(Ps)-2, -1, -1):  # lev = coarsest ... finest-1
        P = Ps[lev]                     # I: lev --> lev+1
        A = As[lev]                     # A on lev+1
        x = P * x
        relax(A, x)
        work[:] += A.nnz*candidate_iters*2

    # Set predefined strength of connection and aggregation
    if len(AggOps) > 1:
        aggregate = [('predefined', {'AggOp': AggOps[i]})
                     for i in range(len(AggOps))]
        strength = [('predefined', {'C': StrengthOps[i]})
                    for i in range(len(StrengthOps))]

    return x, aggregate, strength  # first candidate
Exemplo n.º 6
0
def classical_strength_of_connection(A, theta=0.25, block=None, norm='abs', cost=[0]):
    """
    Return a strength of connection matrix using the classical AMG measure
    An off-diagonal entry A[i,j] is a strong connection iff::

            | A[i,j] | >= theta * max(| A[i,k] |), where k != i     (norm='abs')
             -A[i,j]   >= theta * max(| A[i,k] |), where k != i     (norm='min')

    Parameters
    ----------
    A : csr_matrix or bsr_matrix
        Square, sparse matrix in CSR or BSR format
    theta : float
        Threshold parameter in [0,1].
    block : string, default None for CSR matrix and 'block' for BSR matrix
        How to treat block structure of A:
            None         : Compute SOC based on A as CSR matrix.
            'block'      : Compute SOC based on norm of blocks of A.
            'amalgamate' : Compute SOC based on A as CSR matrix, then compute
                           norm of blocks in SOC matrix for a block SOC. 
    norm : 'string', default 'abs'
        Option to compute SOC between elements or blocks: 
            'abs'  : C_ij = k, where k is the maximum absolute value in block C_ij
            'min'  : C_ij = k, where k is the minimum (negative) value in block C_ij
            'fro'  : C_ij = k, where k is the Frobenius norm of block C_ij
                - Only valid for block matrices, block='block'

    Returns
    -------
    S : csr_matrix
        Matrix graph defining strong connections.  S[i,j]=1 if vertex i
        is strongly influenced by vertex j.

    See Also
    --------
    symmetric_strength_of_connection : symmetric measure used in SA
    evolution_strength_of_connection : relaxation based strength measure

    Notes
    -----
    - A symmetric A does not necessarily yield a symmetric strength matrix S
    - Calls C++ function classical_strength_of_connection

    References
    ----------

    .. [1] Briggs, W. L., Henson, V. E., McCormick, S. F., "A multigrid
       tutorial", Second edition. Society for Industrial and Applied
       Mathematics (SIAM), Philadelphia, PA, 2000. xii+193 pp.
       ISBN: 0-89871-462-1

    .. [2] Trottenberg, U., Oosterlee, C. W., Schuller, A., "Multigrid",
       Academic Press, Inc., San Diego, CA, 2001. xvi+631 pp.
       ISBN: 0-12-701070-X

    Examples
    --------
    >>> import numpy as np
    >>> from pyamg.gallery import stencil_grid
    >>> from pyamg.strength import classical_strength_of_connection
    >>> n=3
    >>> stencil = np.array([[-1.0,-1.0,-1.0],
    ...                        [-1.0, 8.0,-1.0],
    ...                        [-1.0,-1.0,-1.0]])
    >>> A = stencil_grid(stencil, (n,n), format='csr')
    >>> S = classical_strength_of_connection(A, 0.0)

    """
    if (theta < 0 or theta > 1):
        raise ValueError('expected theta in [0,1]')

    if (theta < 0 or theta > 1):
        raise ValueError('expected theta in [0,1]')

    if sparse.isspmatrix_bsr(A):
        blocksize = A.blocksize[0]
    else:
        blocksize = 1

    # Block structure considered before computing SOC
    if (block == 'block') or sparse.isspmatrix_bsr(A):
        R, C = A.blocksize
        if (R != C) or (R < 1):
            raise ValueError('Matrix must have square blocks')

        N = int(A.shape[0] / R)

        # SOC based on maximum absolute value element in each block
        if norm == 'abs':
            data = np.max(np.max(np.abs(A.data),axis=1),axis=1)
            cost[0] += 1
        # SOC based on hard minimum of entry in each off-diagonal block
        elif norm == 'min':
            data = np.min(np.min(A.data,axis=1),axis=1)
            cost[0] += 1
        # SOC based on Frobenius norms of blocks
        elif norm == 'fro':
            data = (np.conjugate(A.data) * A.data).reshape(-1, R*C).sum(axis=1)
            cost[0] += 1
        else:
            raise ValueError("Invalid choice of norm.")

        data[np.abs(data)<1e-16] = 0.0
        S_rowptr = np.empty_like(A.indptr)
        S_colinds = np.empty_like(A.indices)
        S_data = np.empty_like(data)

        if norm == 'abs' or norm == 'fro':
            amg_core.classical_strength_of_connection_abs(N, theta, A.indptr, A.indices, data,
                                                          S_rowptr, S_colinds, S_data)
        elif norm == 'min':
            amg_core.classical_strength_of_connection_min(N, theta, A.indptr, A.indices, data,
                                                          S_rowptr, S_colinds, S_data)
        else:  
            raise ValueError("Unrecognized option for norm.")
    
        # One pass through nnz to find largest entry, one to filter
        S = sparse.csr_matrix((S_data, S_colinds, S_rowptr), shape=[N, N])
        cost[0] += 2
        
        # Take magnitude and scale by largest entry
        S.data = np.abs(S.data)
        S = scale_rows_by_largest_entry(S)
        S.eliminate_zeros()

        # Assume largest entry can be tracked from filtering.
        # 1 WU to scale matrix. 
        cost[0] += float(S.nnz) / A.nnz 

        return S

    # SOC computed based on A as CSR
    else:
        S_rowptr = np.empty_like(A.indptr)
        S_colinds = np.empty_like(A.indices)
        S_data = np.empty_like(A.data)

        if norm == 'abs' or norm == 'fro':
            amg_core.classical_strength_of_connection_abs(A.shape[0], theta, A.indptr,
                                                          A.indices, A.data, S_rowptr, S_colinds, S_data)
        elif norm == 'min':
            amg_core.classical_strength_of_connection_min(A.shape[0], theta, A.indptr,
                                                          A.indices, A.data, S_rowptr, S_colinds, S_data)
        else:  
            raise ValueError("Unrecognized option for norm.")

        # One pass through nnz to find largest entry, one to filter
        S = sparse.csr_matrix((S_data, S_colinds, S_rowptr), shape=A.shape)
        cost[0] += 2

        if blocksize > 1 and block == 'amalgamate':
            S = amalgamate(S, blocksize, norm=norm)

        # Take magnitude and scale by largest entry
        S.data = np.abs(S.data)
        S = scale_rows_by_largest_entry(S)
        S.eliminate_zeros()

        # Assume largest entry can be tracked from filtering.
        # 1 WU to scale matrix. 
        cost[0] += float(S.nnz) / A.nnz 

        return S