Exemple #1
0
def edgeAMG(Anode,Acurl,D):
    nodalAMG = smoothed_aggregation_solver(Anode,max_coarse=10,keep=True)


    ##
    # construct multilevel structure
    levels = []
    levels.append( multilevel_solver.level() )
    levels[-1].A = Acurl
    levels[-1].D = D
    for i in range(1,len(nodalAMG.levels)):
        A = levels[-1].A
        Pnode = nodalAMG.levels[i-1].AggOp
        P = findPEdge(D, Pnode)
        R = P.T
        levels[-1].P = P
        levels[-1].R = R
        levels.append( multilevel_solver.level() )
        A = R*A*P
        D = csr_matrix(dia_matrix((1.0/((P.T*P).diagonal()),0),shape=(P.shape[1],P.shape[1]))*(P.T*D*Pnode))
        levels[-1].A = A
        levels[-1].D = D

    edgeML = multilevel_solver(levels)
    for i in range(0,len(edgeML.levels)):
        edgeML.levels[i].presmoother = setup_hiptmair(levels[i])
        edgeML.levels[i].postsmoother = setup_hiptmair(levels[i])
    return edgeML
Exemple #2
0
def edgeAMG(Anode, Acurl, D):
    nodalAMG = smoothed_aggregation_solver(Anode, max_coarse=10, keep=True)

    ##
    # construct multilevel structure
    levels = []
    levels.append(multilevel_solver.level())
    levels[-1].A = Acurl
    levels[-1].D = D
    for i in range(1, len(nodalAMG.levels)):
        A = levels[-1].A
        Pnode = nodalAMG.levels[i - 1].AggOp
        P = findPEdge(D, Pnode)
        R = P.T
        levels[-1].P = P
        levels[-1].R = R
        levels.append(multilevel_solver.level())
        A = R * A * P
        D = csr_matrix(
            dia_matrix((1.0 / ((P.T * P).diagonal()), 0),
                       shape=(P.shape[1], P.shape[1])) * (P.T * D * Pnode))
        levels[-1].A = A
        levels[-1].D = D

    edgeML = multilevel_solver(levels)
    for i in range(0, len(edgeML.levels)):
        edgeML.levels[i].presmoother = setup_hiptmair(levels[i])
        edgeML.levels[i].postsmoother = setup_hiptmair(levels[i])
    return edgeML
Exemple #3
0
    def test_cycle_complexity(self):
        # four levels
        levels = []
        levels.append(multilevel_solver.level())
        levels[0].A = csr_matrix(ones((10, 10)))
        levels[0].P = csr_matrix(ones((10, 5)))
        levels.append(multilevel_solver.level())
        levels[1].A = csr_matrix(ones((5, 5)))
        levels[1].P = csr_matrix(ones((5, 3)))
        levels.append(multilevel_solver.level())
        levels[2].A = csr_matrix(ones((3, 3)))
        levels[2].P = csr_matrix(ones((3, 2)))
        levels.append(multilevel_solver.level())
        levels[3].A = csr_matrix(ones((2, 2)))

        # one level hierarchy
        mg = multilevel_solver(levels[:1])
        assert_equal(mg.cycle_complexity(cycle='V'), 100.0 / 100.0)  # 1
        assert_equal(mg.cycle_complexity(cycle='W'), 100.0 / 100.0)  # 1
        assert_equal(mg.cycle_complexity(cycle='AMLI'), 100.0 / 100.0)  # 1
        assert_equal(mg.cycle_complexity(cycle='F'), 100.0 / 100.0)  # 1

        # two level hierarchy
        mg = multilevel_solver(levels[:2])
        change_smoothers(mg, 'gauss_seidel', 'gauss_seidel')
        assert_equal(mg.cycle_complexity(cycle='V'), 4.0)
        assert_equal(mg.cycle_complexity(cycle='W'), 4.0)
        assert_equal(mg.cycle_complexity(cycle='AMLI'), 4.0)
        assert_equal(mg.cycle_complexity(cycle='F'), 4.0)

        # three level hierarchy
        mg = multilevel_solver(levels[:3])
        change_smoothers(mg, ('gauss_seidel', {
            'iterations': 2
        }), 'gauss_seidel')
        assert_equal(mg.cycle_complexity(cycle='V'), 6.3)
        assert_equal(mg.cycle_complexity(cycle='W'), 7.6)
        assert_equal(mg.cycle_complexity(cycle='AMLI'), 7.6)
        assert_equal(mg.cycle_complexity(cycle='F'), 7.6)

        # four level hierarchy
        mg = multilevel_solver(levels[:4])
        change_smoothers(mg, ('gauss_seidel', {
            'sweep': 'symmetric'
        }), 'gauss_seidel')
        assert_equal(mg.cycle_complexity(cycle='V'), 6.78)
        assert_equal(mg.cycle_complexity(cycle='W'), 9.52)
        assert_equal(mg.cycle_complexity(cycle='AMLI'), 9.52)
        assert_equal(mg.cycle_complexity(cycle='F'), 9.04)
Exemple #4
0
def extend_hierarchy(levels, CF, l, maxp, theta_a, keep):
    """Extend the multigrid hierarchy."""
    def unpack_arg(v):
        if isinstance(v, tuple):
            return v[0], v[1]
        else:
            return v, {}

    A = levels[-1].A

    # Generate the C/F splitting
    fn, kwargs = unpack_arg(CF)
    if fn == 'CR':
        splitting = CR(A, **kwargs)
    else:
        raise ValueError('unknown C/F splitting method (%s)' % CF)

    # rs_C = classical_strength_of_connection(A, theta=0.25)
    # rs_splitting = split.RS(rs_C)
    # rs_P = direct_interpolation(A.copy(), rs_C.copy(), rs_splitting.copy())
    #
    # rs_P_sparsity = rs_P.copy()
    # rs_P_sparsity.data[:] = 1
    #
    # rs_fine = np.where(rs_splitting == 0)[0]
    # rs_coarse = np.where(rs_splitting == 1)[0]
    # rs_A_fc = A[rs_fine][:, rs_coarse]
    # rs_W = rs_P[rs_fine]
    # my_rs_P, my_rs_W = my_direct_interpolation(rs_A_fc, A, rs_W, rs_coarse, rs_fine)
    #
    # my_rs_P_sparsity = my_rs_P.copy()
    # my_rs_P_sparsity.data[:] = 1
    #
    # rs_A_sparsity = A[:, rs_coarse].copy()
    # rs_A_sparsity.data[:] = 1

    # Generate the interpolation matrix that maps from the coarse-grid to the
    # fine-grid
    P = truncation_interpolation(A, splitting, l, maxp, theta_a)
    # P = optimal_interpolation(A, splitting)
    # P = rs_P

    # Generate the restriction matrix that maps from the fine-grid to the
    # coarse-grid
    R = P.T.tocsr()

    # Store relevant information for this level
    if keep:
        levels[-1].splitting = splitting  # C/F splitting

    levels[-1].P = P  # prolongation operator
    levels[-1].R = R  # restriction operator

    levels.append(multilevel_solver.level())

    # Form next level through Galerkin product
    A = R * A * P
    levels[-1].A = A
    def test_cycle_complexity(self):
        # four levels
        levels = []
        levels.append(multilevel_solver.level())
        levels[0].A = csr_matrix(ones((10, 10)))
        levels[0].P = csr_matrix(ones((10, 5)))
        levels.append(multilevel_solver.level())
        levels[1].A = csr_matrix(ones((5, 5)))
        levels[1].P = csr_matrix(ones((5, 3)))
        levels.append(multilevel_solver.level())
        levels[2].A = csr_matrix(ones((3, 3)))
        levels[2].P = csr_matrix(ones((3, 2)))
        levels.append(multilevel_solver.level())
        levels[3].A = csr_matrix(ones((2, 2)))

        # one level hierarchy
        mg = multilevel_solver(levels[:1])
        assert_equal(mg.cycle_complexity(cycle='V'), 100.0/100.0)  # 1
        assert_equal(mg.cycle_complexity(cycle='W'), 100.0/100.0)  # 1
        assert_equal(mg.cycle_complexity(cycle='AMLI'), 100.0/100.0)  # 1
        assert_equal(mg.cycle_complexity(cycle='F'), 100.0/100.0)  # 1

        # two level hierarchy
        mg = multilevel_solver(levels[:2])
        change_smoothers(mg, 'gauss_seidel', 'gauss_seidel')
        assert_equal(mg.cycle_complexity(cycle='V'), 4.0)
        assert_equal(mg.cycle_complexity(cycle='W'), 4.0)
        assert_equal(mg.cycle_complexity(cycle='AMLI'), 4.0)
        assert_equal(mg.cycle_complexity(cycle='F'), 4.0)

        # three level hierarchy
        mg = multilevel_solver(levels[:3])
        change_smoothers(mg, ('gauss_seidel', {'iterations':2}) , 'gauss_seidel')
        assert_equal(mg.cycle_complexity(cycle='V'), 6.3)
        assert_equal(mg.cycle_complexity(cycle='W'), 7.6)
        assert_equal(mg.cycle_complexity(cycle='AMLI'), 7.6)
        assert_equal(mg.cycle_complexity(cycle='F'), 7.6)

        # four level hierarchy
        mg = multilevel_solver(levels[:4])
        change_smoothers(mg, ('gauss_seidel', {'sweep':'symmetric'}), 'gauss_seidel')
        assert_equal(mg.cycle_complexity(cycle='V'), 6.78)
        assert_equal(mg.cycle_complexity(cycle='W'), 9.52)
        assert_equal(mg.cycle_complexity(cycle='AMLI'), 9.52)
        assert_equal(mg.cycle_complexity(cycle='F'), 9.04)
    def test_cycle_complexity(self):
        # four levels
        levels = []
        levels.append(multilevel_solver.level())
        levels[0].A = csr_matrix(ones((10, 10)))
        levels[0].P = csr_matrix(ones((10, 5)))
        levels.append(multilevel_solver.level())
        levels[1].A = csr_matrix(ones((5, 5)))
        levels[1].P = csr_matrix(ones((5, 3)))
        levels.append(multilevel_solver.level())
        levels[2].A = csr_matrix(ones((3, 3)))
        levels[2].P = csr_matrix(ones((3, 2)))
        levels.append(multilevel_solver.level())
        levels[3].A = csr_matrix(ones((2, 2)))

        # one level hierarchy
        mg = multilevel_solver(levels[:1])
        assert_equal(mg.cycle_complexity(cycle='V'), 100.0 / 100.0)  # 1
        assert_equal(mg.cycle_complexity(cycle='W'), 100.0 / 100.0)  # 1
        assert_equal(mg.cycle_complexity(cycle='AMLI'), 100.0 / 100.0)  # 1
        assert_equal(mg.cycle_complexity(cycle='F'), 100.0 / 100.0)  # 1

        # two level hierarchy
        mg = multilevel_solver(levels[:2])
        assert_equal(mg.cycle_complexity(cycle='V'), 225.0 / 100.0)  # 2,1
        assert_equal(mg.cycle_complexity(cycle='W'), 225.0 / 100.0)  # 2,1
        assert_equal(mg.cycle_complexity(cycle='AMLI'), 225.0 / 100.0)  # 2,1
        assert_equal(mg.cycle_complexity(cycle='F'), 225.0 / 100.0)  # 2,1

        # three level hierarchy
        mg = multilevel_solver(levels[:3])
        assert_equal(mg.cycle_complexity(cycle='V'), 259.0 / 100.0)  # 2,2,1
        assert_equal(mg.cycle_complexity(cycle='W'), 318.0 / 100.0)  # 2,4,2
        assert_equal(mg.cycle_complexity(cycle='AMLI'), 318.0 / 100.0)  # 2,4,2
        assert_equal(mg.cycle_complexity(cycle='F'), 318.0 / 100.0)  # 2,4,2

        # four level hierarchy
        mg = multilevel_solver(levels[:4])
        assert_equal(mg.cycle_complexity(cycle='V'), 272.0 / 100.0)  # 2,2,2,1
        assert_equal(mg.cycle_complexity(cycle='W'), 388.0 / 100.0)  # 2,4,8,4
        assert_equal(mg.cycle_complexity(cycle='AMLI'),
                     388.0 / 100.0)  # 2,4,8,4
        assert_equal(mg.cycle_complexity(cycle='F'), 366.0 / 100.0)  # 2,4,6,3
Exemple #7
0
    def test_cycle_complexity(self):
        # four levels
        levels = []
        levels.append(multilevel_solver.level())
        levels[0].A = csr_matrix(ones((10, 10)))
        levels[0].P = csr_matrix(ones((10, 5)))
        levels.append(multilevel_solver.level())
        levels[1].A = csr_matrix(ones((5, 5)))
        levels[1].P = csr_matrix(ones((5, 3)))
        levels.append(multilevel_solver.level())
        levels[2].A = csr_matrix(ones((3, 3)))
        levels[2].P = csr_matrix(ones((3, 2)))
        levels.append(multilevel_solver.level())
        levels[3].A = csr_matrix(ones((2, 2)))

        # one level hierarchy
        mg = multilevel_solver(levels[:1])
        assert_equal(mg.cycle_complexity(cycle='V'), 100.0/100.0)  # 1
        assert_equal(mg.cycle_complexity(cycle='W'), 100.0/100.0)  # 1
        assert_equal(mg.cycle_complexity(cycle='AMLI'), 100.0/100.0)  # 1
        assert_equal(mg.cycle_complexity(cycle='F'), 100.0/100.0)  # 1

        # two level hierarchy
        mg = multilevel_solver(levels[:2])
        assert_equal(mg.cycle_complexity(cycle='V'), 225.0/100.0)  # 2,1
        assert_equal(mg.cycle_complexity(cycle='W'), 225.0/100.0)  # 2,1
        assert_equal(mg.cycle_complexity(cycle='AMLI'), 225.0/100.0)  # 2,1
        assert_equal(mg.cycle_complexity(cycle='F'), 225.0/100.0)  # 2,1

        # three level hierarchy
        mg = multilevel_solver(levels[:3])
        assert_equal(mg.cycle_complexity(cycle='V'), 259.0/100.0)  # 2,2,1
        assert_equal(mg.cycle_complexity(cycle='W'), 318.0/100.0)  # 2,4,2
        assert_equal(mg.cycle_complexity(cycle='AMLI'), 318.0/100.0)  # 2,4,2
        assert_equal(mg.cycle_complexity(cycle='F'), 318.0/100.0)  # 2,4,2

        # four level hierarchy
        mg = multilevel_solver(levels[:4])
        assert_equal(mg.cycle_complexity(cycle='V'), 272.0/100.0)  # 2,2,2,1
        assert_equal(mg.cycle_complexity(cycle='W'), 388.0/100.0)  # 2,4,8,4
        assert_equal(mg.cycle_complexity(cycle='AMLI'), 388.0/100.0)  # 2,4,8,4
        assert_equal(mg.cycle_complexity(cycle='F'), 366.0/100.0)  # 2,4,6,3
Exemple #8
0
def rootnode_solver(A, B=None, BH=None,
                    symmetry='hermitian', strength='symmetric',
                    aggregate='standard', smooth='energy',
                    presmoother=('block_gauss_seidel',
                                 {'sweep': 'symmetric'}),
                    postsmoother=('block_gauss_seidel',
                                  {'sweep': 'symmetric'}),
                    improve_candidates=('block_gauss_seidel',
                                        {'sweep': 'symmetric',
                                         'iterations': 4}),
                    max_levels = 10, max_coarse = 10,
                    diagonal_dominance=False, keep=False, **kwargs):
    """
    Create a multilevel solver using root-node based Smoothed Aggregation (SA).
    See the notes below, for the major differences with the classical-style
    smoothed aggregation solver in aggregation.smoothed_aggregation_solver.

    Parameters
    ----------
    A : {csr_matrix, bsr_matrix}
        Sparse NxN matrix in CSR or BSR format
    B : {None, array_like}
        Right near-nullspace candidates stored in the columns of an NxK array.
        K must be >= the blocksize of A (see reference [2]). The default value
        B=None is equivalent to choosing the constant over each block-variable,
        B=np.kron(np.ones((A.shape[0]/blocksize(A), 1)), np.eye(blocksize(A)))
    BH : {None, array_like}
        Left near-nullspace candidates stored in the columns of an NxK array.
        BH is only used if symmetry is 'nonsymmetric'.  K must be >= the
        blocksize of A (see reference [2]). The default value B=None is
        equivalent to choosing the constant over each block-variable,
        B=np.kron(np.ones((A.shape[0]/blocksize(A), 1)), np.eye(blocksize(A)))
    symmetry : {string}
        'symmetric' refers to both real and complex symmetric
        'hermitian' refers to both complex Hermitian and real Hermitian
        'nonsymmetric' i.e. nonsymmetric in a hermitian sense
        Note that for the strictly real case, symmetric and hermitian are
        the same
        Note that this flag does not denote definiteness of the operator.
    strength : {list} : default
        ['symmetric', 'classical', 'evolution', 'algebraic_distance', 'affinity',
            ('predefined', {'C' : csr_matrix}), None]
        Method used to determine the strength of connection between unknowns of
        the linear system.  Method-specific parameters may be passed in using a
        tuple, e.g. strength=('symmetric',{'theta' : 0.25 }). If strength=None,
        all nonzero entries of the matrix are considered strong.
        See notes below for varying this parameter on a per level basis.  Also,
        see notes below for using a predefined strength matrix on each level.
    aggregate : {list} : default ['standard', 'lloyd', 'naive',
                                  ('predefined', {'AggOp' : csr_matrix})]
        Method used to aggregate nodes.  See notes below for varying this
        parameter on a per level basis.  Also, see notes below for using a
        predefined aggregation on each level.
    smooth : {list} : default ['energy', None]
        Method used to smooth the tentative prolongator.  Method-specific
        parameters may be passed in using a tuple, e.g.  smooth=
        ('energy',{'krylov' : 'gmres'}).  Only 'energy' and None are valid
        prolongation smoothing options.  See notes below for varying this
        parameter on a per level basis.
    presmoother : {tuple, string, list} : default ('block_gauss_seidel',
                                                   {'sweep':'symmetric'})
        Defines the presmoother for the multilevel cycling.  The default block
        Gauss-Seidel option defaults to point-wise Gauss-Seidel, if the matrix
        is CSR or is a BSR matrix with blocksize of 1.  See notes below for
        varying this parameter on a per level basis.
    postsmoother : {tuple, string, list}
        Same as presmoother, except defines the postsmoother.
    improve_candidates : {tuple, string, list} : default
                         [('block_gauss_seidel',
                          {'sweep': 'symmetric', 'iterations': 4}), None]
        The ith entry defines the method used to improve the candidates B on
        level i.  If the list is shorter than max_levels, then the last entry
        will define the method for all levels lower.  If tuple or string, then
        this single relaxation descriptor defines improve_candidates on all
        levels.
        The list elements are relaxation descriptors of the form used for
        presmoother and postsmoother.  A value of None implies no action on B.
    max_levels : {integer} : default 10
        Maximum number of levels to be used in the multilevel solver.
    max_coarse : {integer} : default 500
        Maximum number of variables permitted on the coarse grid.
    diagonal_dominance : {bool, tuple} : default False
        If True (or the first tuple entry is True), then avoid coarsening
        diagonally dominant rows.  The second tuple entry requires a
        dictionary, where the key value 'theta' is used to tune the diagonal
        dominance threshold.
    keep : {bool} : default False
        Flag to indicate keeping extra operators in the hierarchy for
        diagnostics.  For example, if True, then strength of connection (C),
        tentative prolongation (T), aggregation (AggOp), and arrays
        storing the C-points (Cpts) and F-points (Fpts) are kept at
        each level.

    Other Parameters
    ----------------
    cycle_type : ['V','W','F']
        Structrure of multigrid cycle
    coarse_solver : ['splu', 'lu', 'cholesky, 'pinv', 'gauss_seidel', ... ]
        Solver used at the coarsest level of the MG hierarchy.
            Optionally, may be a tuple (fn, args), where fn is a string such as
        ['splu', 'lu', ...] or a callable function, and args is a dictionary of
        arguments to be passed to fn.
    setup_complexity : bool
        For a detailed, more accurate setup complexity, pass in 
        'setup_complexity' = True. This will slow down performance, but
        increase accuracy of complexity count. 

    Returns
    -------
    ml : multilevel_solver
        Multigrid hierarchy of matrices and prolongation operators

    See Also
    --------
    multilevel_solver, aggregation.smoothed_aggregation_solver,
    classical.ruge_stuben_solver

    Notes
    -----
         - Root-node style SA differs from classical SA primarily by preserving
           and identity block in the interpolation operator, P.  Each aggregate
           has a "root-node" or "center-node" associated with it, and this
           root-node is injected from the coarse grid to the fine grid.  The
           injection corresponds to the identity block.

         - Only smooth={'energy', None} is supported for prolongation
           smoothing.  See reference [2] below for more details on why the
           'energy' prolongation smoother is the natural counterpart to
           root-node style SA.

        - The additional parameters are passed through as arguments to
          multilevel_solver.  Refer to pyamg.multilevel_solver for additional
          documentation.

        - At each level, four steps are executed in order to define the coarser
          level operator.

          1. Matrix A is given and used to derive a strength matrix, C.

          2. Based on the strength matrix, indices are grouped or aggregated.

          3. The aggregates define coarse nodes and a tentative prolongation
             operator T is defined by injection

          4. The tentative prolongation operator is smoothed by a relaxation
             scheme to improve the quality and extent of interpolation from the
             aggregates to fine nodes.

        - The parameters smooth, strength, aggregate, presmoother, postsmoother
          can be varied on a per level basis.  For different methods on
          different levels, use a list as input so that the i-th entry defines
          the method at the i-th level.  If there are more levels in the
          hierarchy than list entries, the last entry will define the method
          for all levels lower.

          Examples are:
          smooth=[('jacobi', {'omega':1.0}), None, 'jacobi']
          presmoother=[('block_gauss_seidel', {'sweep':symmetric}), 'sor']
          aggregate=['standard', 'naive']
          strength=[('symmetric', {'theta':0.25}),
                    ('symmetric', {'theta':0.08})]

        - Predefined strength of connection and aggregation schemes can be
          specified.  These options are best used together, but aggregation can
          be predefined while strength of connection is not.

          For predefined strength of connection, use a list consisting of
          tuples of the form ('predefined', {'C' : C0}), where C0 is a
          csr_matrix and each degree-of-freedom in C0 represents a supernode.
          For instance to predefine a three-level hierarchy, use
          [('predefined', {'C' : C0}), ('predefined', {'C' : C1}) ].

          Similarly for predefined aggregation, use a list of tuples.  For
          instance to predefine a three-level hierarchy, use [('predefined',
          {'AggOp' : Agg0}), ('predefined', {'AggOp' : Agg1}) ], where the
          dimensions of A, Agg0 and Agg1 are compatible, i.e.  Agg0.shape[1] ==
          A.shape[0] and Agg1.shape[1] == Agg0.shape[0].  Each AggOp is a
          csr_matrix.

          Because this is a root-nodes solver, if a member of the predefined
          aggregation list is predefined, it must be of the form
          ('predefined', {'AggOp' : Agg, 'Cnodes' : Cnodes}).

    Examples
    --------
    >>> from pyamg import rootnode_solver
    >>> from pyamg.gallery import poisson
    >>> from scipy.sparse.linalg import cg
    >>> import numpy as np
    >>> A = poisson((100, 100), format='csr')           # matrix
    >>> b = np.ones((A.shape[0]))                   # RHS
    >>> ml = rootnode_solver(A)                     # AMG solver
    >>> M = ml.aspreconditioner(cycle='V')             # preconditioner
    >>> x, info = cg(A, b, tol=1e-8, maxiter=30, M=M)   # solve with CG

    References
    ----------
    .. [1] Vanek, P. and Mandel, J. and Brezina, M.,
       "Algebraic Multigrid by Smoothed Aggregation for
       Second and Fourth Order Elliptic Problems",
       Computing, vol. 56, no. 3, pp. 179--196, 1996.
       http://citeseer.ist.psu.edu/vanek96algebraic.html
    .. [2] Olson, L. and Schroder, J. and Tuminaro, R.,
       "A general interpolation strategy for algebraic
       multigrid using energy minimization", SIAM Journal
       on Scientific Computing (SISC), vol. 33, pp.
       966--991, 2011.
    """

    if ('setup_complexity' in kwargs):
        if kwargs['setup_complexity'] == True:
            mat_mat_complexity.__detailed__ = True
        del kwargs['setup_complexity']

    if not (isspmatrix_csr(A) or isspmatrix_bsr(A)):
        try:
            A = csr_matrix(A)
            warn("Implicit conversion of A to CSR",
                 SparseEfficiencyWarning)
        except:
            raise TypeError('Argument A must have type csr_matrix, \
                             bsr_matrix, or be convertible to csr_matrix')

    A = A.asfptype()

    if (symmetry != 'symmetric') and (symmetry != 'hermitian') and \
            (symmetry != 'nonsymmetric'):
        raise ValueError('expected \'symmetric\', \'nonsymmetric\' \
                          or \'hermitian\' for the symmetry parameter ')
    A.symmetry = symmetry

    if A.shape[0] != A.shape[1]:
        raise ValueError('expected square matrix')

    # Right near nullspace candidates use constant for each variable as default
    if B is None:
        B = np.kron(np.ones((int(A.shape[0]/blocksize(A)), 1), dtype=A.dtype),
                    np.eye(blocksize(A)))
    else:
        B = np.asarray(B, dtype=A.dtype)
        if len(B.shape) == 1:
            B = B.reshape(-1, 1)
        if B.shape[0] != A.shape[0]:
            raise ValueError('The near null-space modes B have incorrect \
                              dimensions for matrix A')
        if B.shape[1] < blocksize(A):
            raise ValueError('B.shape[1] must be >= the blocksize of A')

    # Left near nullspace candidates
    if A.symmetry == 'nonsymmetric':
        if BH is None:
            BH = B.copy()
        else:
            BH = np.asarray(BH, dtype=A.dtype)
            if len(BH.shape) == 1:
                BH = BH.reshape(-1, 1)
            if BH.shape[1] != B.shape[1]:
                raise ValueError('The number of left and right near \
                                  null-space modes B and BH, must be equal')
            if BH.shape[0] != A.shape[0]:
                raise ValueError('The near null-space modes BH have \
                                  incorrect dimensions for matrix A')

    # Levelize the user parameters, so that they become lists describing the
    # desired user option on each level.
    max_levels, max_coarse, strength =\
        levelize_strength_or_aggregation(strength, max_levels, max_coarse)
    max_levels, max_coarse, aggregate =\
        levelize_strength_or_aggregation(aggregate, max_levels, max_coarse)
    improve_candidates =\
        levelize_smooth_or_improve_candidates(improve_candidates, max_levels)
    smooth = levelize_smooth_or_improve_candidates(smooth, max_levels)

    # Construct multilevel structure
    levels = []
    levels.append(multilevel_solver.level())
    levels[-1].A = A          # matrix

    # Append near nullspace candidates
    levels[-1].B = B          # right candidates
    if A.symmetry == 'nonsymmetric':
        levels[-1].BH = BH    # left candidates

    while len(levels) < max_levels and \
            int(levels[-1].A.shape[0]/blocksize(levels[-1].A)) > max_coarse:
        extend_hierarchy(levels, strength, aggregate, smooth,
                         improve_candidates, diagonal_dominance, keep)

    # Construct and return multilevel hierarchy
    ml = multilevel_solver(levels, **kwargs)
    change_smoothers(ml, presmoother, postsmoother)
    return ml
Exemple #9
0
def extend_hierarchy(levels, strength, CF, keep):
    """ helper function for local methods """
    def unpack_arg(v):
        if isinstance(v, tuple):
            return v[0], v[1]
        else:
            return v, {}

    A = levels[-1].A

    # Compute the strength-of-connection matrix C, where larger
    # C[i,j] denote stronger couplings between i and j.
    fn, kwargs = unpack_arg(strength)
    if fn == 'symmetric':
        C = symmetric_strength_of_connection(A, **kwargs)
    elif fn == 'classical':
        C = classical_strength_of_connection(A, **kwargs)
    elif fn == 'distance':
        C = distance_strength_of_connection(A, **kwargs)
    elif (fn == 'ode') or (fn == 'evolution'):
        C = evolution_strength_of_connection(A, **kwargs)
    elif fn == 'energy_based':
        C = energy_based_strength_of_connection(A, **kwargs)
    elif fn == 'algebraic_distance':
        C = algebraic_distance(A, **kwargs)
    elif fn is None:
        C = A
    else:
        raise ValueError('unrecognized strength of connection method: %s' %
                         str(fn))

    # Generate the C/F splitting
    fn, kwargs = unpack_arg(CF)
    if fn == 'RS':
        splitting = split.RS(C)
    elif fn == 'PMIS':
        splitting = split.PMIS(C)
    elif fn == 'PMISc':
        splitting = split.PMISc(C)
    elif fn == 'CLJP':
        splitting = split.CLJP(C)
    elif fn == 'CLJPc':
        splitting = split.CLJPc(C)
    else:
        raise ValueError('unknown C/F splitting method (%s)' % CF)

    # Generate the interpolation matrix that maps from the coarse-grid to the
    # fine-grid
    P = direct_interpolation(A, C, splitting)

    # Generate the restriction matrix that maps from the fine-grid to the
    # coarse-grid
    R = P.T.tocsr()

    # Store relevant information for this level
    if keep:
        levels[-1].C = C                  # strength of connection matrix
        levels[-1].splitting = splitting  # C/F splitting

    levels[-1].P = P                  # prolongation operator
    levels[-1].R = R                  # restriction operator

    levels.append(multilevel_solver.level())

    # Form next level through Galerkin product
    A = R * A * P
    levels[-1].A = A
Exemple #10
0
def extend_hierarchy(levels, strength, aggregate, smooth, improve_candidates,
                     diagonal_dominance=False, keep=True, test_ind=0):
    """Service routine to implement the strength of connection, aggregation,
    tentative prolongation construction, and prolongation smoothing.  Called by
    smoothed_aggregation_solver.
    """

    def unpack_arg(v):
        if isinstance(v, tuple):
            return v[0], v[1]
        else:
            return v, {}

    A = levels[-1].A
    B = levels[-1].B
    if A.symmetry == "nonsymmetric":
        AH = A.H.asformat(A.format)
        BH = levels[-1].BH

    # Improve near nullspace candidates by relaxing on A B = 0
    fn, kwargs = unpack_arg(improve_candidates[len(levels)-1])
    if fn is not None:
        b = np.zeros((A.shape[0], 1), dtype=A.dtype)
        B = relaxation_as_linear_operator((fn, kwargs), A, b) * B
        levels[-1].B = B
        if A.symmetry == "nonsymmetric":
            BH = relaxation_as_linear_operator((fn, kwargs), AH, b) * BH
            levels[-1].BH = BH

    # Compute the strength-of-connection matrix C, where larger
    # C[i, j] denote stronger couplings between i and j.
    fn, kwargs = unpack_arg(strength[len(levels)-1])
    if fn == 'symmetric':
        C = symmetric_strength_of_connection(A, **kwargs)
    elif fn == 'classical':
        C = classical_strength_of_connection(A, **kwargs)
    elif fn == 'distance':
        C = distance_strength_of_connection(A, **kwargs)
    elif (fn == 'ode') or (fn == 'evolution'):
        if 'B' in kwargs:
            C = evolution_strength_of_connection(A, **kwargs)
        else:
            C = evolution_strength_of_connection(A, B, **kwargs)
    elif fn == 'energy_based':
        C = energy_based_strength_of_connection(A, **kwargs)
    elif fn == 'predefined':
        C = kwargs['C'].tocsr()
    elif fn == 'algebraic_distance':
        C = algebraic_distance(A, **kwargs)
    elif fn is None:
        C = A.tocsr()
    else:
        raise ValueError('unrecognized strength of connection method: %s' %
                         str(fn))

    # Avoid coarsening diagonally dominant rows
    flag, kwargs = unpack_arg(diagonal_dominance)
    if flag:
        C = eliminate_diag_dom_nodes(A, C, **kwargs)

    # Compute the aggregation matrix AggOp (i.e., the nodal coarsening of A).
    # AggOp is a boolean matrix, where the sparsity pattern for the k-th column
    # denotes the fine-grid nodes agglomerated into k-th coarse-grid node.
    fn, kwargs = unpack_arg(aggregate[len(levels)-1])
    if fn == 'standard':
        AggOp, Cnodes = standard_aggregation(C, **kwargs)
    elif fn == 'naive':
        AggOp, Cnodes = naive_aggregation(C, **kwargs)
    elif fn == 'lloyd':
        AggOp, Cnodes = lloyd_aggregation(C, **kwargs)
    elif fn == 'pairwise':
        AggOp, Cnodes = pairwise_aggregation(A, B, **kwargs)
    elif fn == 'predefined':
        AggOp = kwargs['AggOp'].tocsr()
        Cnodes = kwargs['Cnodes']
    else:
        raise ValueError('unrecognized aggregation method %s' % str(fn))

# ----------------------------------------------------------------------------- #
# ------------------- New ideal interpolation constructed --------------------  #
# ----------------------------------------------------------------------------- #

    # pdb.set_trace()

    # splitting = CR(A)
    # Cpts = [i for i in range(0,AggOp.shape[0]) if splitting[i]==1]

    # Compute prolongation operator.
    if test_ind==0:
        T = new_ideal_interpolation(A=A, AggOp=AggOp, Cnodes=Cnodes, B=B[:, 0:blocksize(A)], SOC=C)
    else: 
        T = py_ideal_interpolation(A=A, AggOp=AggOp, Cnodes=Cnodes, B=B[:, 0:blocksize(A)], SOC=C)

    print "\nSize of sparsity pattern - ", T.nnz

    # Smooth the tentative prolongator, so that it's accuracy is greatly
    # improved for algebraically smooth error.
    # fn, kwargs = unpack_arg(smooth[len(levels)-1])
    # if fn == 'jacobi':
    #     P = jacobi_prolongation_smoother(A, T, C, B, **kwargs)
    # elif fn == 'richardson':
    #     P = richardson_prolongation_smoother(A, T, **kwargs)
    # elif fn == 'energy':
    #     P = energy_prolongation_smoother(A, T, C, B, None, (False, {}),
    #                                      **kwargs)
    # elif fn is None:
    #     P = T
    # else:
    #     raise ValueError('unrecognized prolongation smoother method %s' %
    #                      str(fn))
    P = T
  
# ----------------------------------------------------------------------------- #
# ----------------------------------------------------------------------------- #

    # Compute the restriction matrix R, which interpolates from the fine-grid
    # to the coarse-grid.  If A is nonsymmetric, then R must be constructed
    # based on A.H.  Otherwise R = P.H or P.T.
    symmetry = A.symmetry
    if symmetry == 'hermitian':
        # symmetrically scale out the diagonal, include scaling in P, R
        A = P.H * A * P
        [dum, Dinv, dum] = symmetric_rescaling(A,copy=False)
        P = bsr_matrix(P * diags(Dinv,offsets=0,format='csr'), blocksize=A.blocksize)
        del dum
        R = P.H
    elif symmetry == 'symmetric':
        # symmetrically scale out the diagonal, include scaling in P, R
        A = P.T * A * P
        [dum, Dinv, dum] = symmetric_rescaling(A,copy=False)
        P = bsr_matrix(P * diags(Dinv,offsets=0,format='csr'), blocksize=A.blocksize)
        del dum
        R = P.T
    elif symmetry == 'nonsymmetric':
        raise TypeError('New ideal interpolation not implemented for non-symmetric matrix.')

    if keep:
        levels[-1].C = C                        # strength of connection matrix
        levels[-1].AggOp = AggOp                # aggregation operator
        levels[-1].Fpts = [i for i in range(0,AggOp.shape[0]) if i not in Cnodes]

    levels[-1].P = P                            # smoothed prolongator
    levels[-1].R = R                            # restriction operator
    levels[-1].Cpts = Cnodes                    # Cpts (i.e., rootnodes)

    levels.append(multilevel_solver.level())

    A.symmetry = symmetry
    levels[-1].A = A
    levels[-1].B = R*B                     # right near nullspace candidates

    test = A.tocsr()
    print "\nSize of coarse operator - ", test.nnz

    if A.symmetry == "nonsymmetric":
        levels[-1].BH = BH                      # left near nullspace candidates
Exemple #11
0
def extend_hierarchy(levels, strength, aggregate, smooth, improve_candidates,
                     diagonal_dominance=False, keep=True):
    """Service routine to implement the strength of connection, aggregation,
    tentative prolongation construction, and prolongation smoothing.  Called by
    smoothed_aggregation_solver.
    """

    A = levels[-1].A
    B = levels[-1].B
    if A.symmetry == "nonsymmetric":
        AH = A.H.asformat(A.format)
        BH = levels[-1].BH

    # Compute the strength-of-connection matrix C, where larger
    # C[i,j] denote stronger couplings between i and j.
    fn, kwargs = unpack_arg(strength[len(levels)-1])
    if fn == 'symmetric':
        C = symmetric_strength_of_connection(A, **kwargs)
    elif fn == 'classical':
        C = classical_strength_of_connection(A, **kwargs)
    elif fn == 'distance':
        C = distance_strength_of_connection(A, **kwargs)
    elif (fn == 'ode') or (fn == 'evolution'):
        if 'B' in kwargs:
            C = evolution_strength_of_connection(A, **kwargs)
        else:
            C = evolution_strength_of_connection(A, B, **kwargs)
    elif fn == 'energy_based':
        C = energy_based_strength_of_connection(A, **kwargs)
    elif fn == 'predefined':
        C = kwargs['C'].tocsr()
    elif fn == 'algebraic_distance':
        C = algebraic_distance(A, **kwargs)
    elif fn == 'affinity':
        C = affinity_distance(A, **kwargs)
    elif fn is None:
        C = A.tocsr()
    else:
        raise ValueError('unrecognized strength of connection method: %s' %
                         str(fn))

    levels[-1].complexity['strength'] = kwargs['cost'][0]
 
    # Avoid coarsening diagonally dominant rows
    flag, kwargs = unpack_arg(diagonal_dominance)
    if flag:
        C = eliminate_diag_dom_nodes(A, C, **kwargs)
        levels[-1].complexity['diag_dom'] = kwargs['cost'][0]

    # Compute the aggregation matrix AggOp (i.e., the nodal coarsening of A).
    # AggOp is a boolean matrix, where the sparsity pattern for the k-th column
    # denotes the fine-grid nodes agglomerated into k-th coarse-grid node.
    fn, kwargs = unpack_arg(aggregate[len(levels)-1])
    if fn == 'standard':
        AggOp = standard_aggregation(C, **kwargs)[0]
    elif fn == 'naive':
        AggOp = naive_aggregation(C, **kwargs)[0]
    elif fn == 'lloyd':
        AggOp = lloyd_aggregation(C, **kwargs)[0]
    elif fn == 'predefined':
        AggOp = kwargs['AggOp'].tocsr()
    else:
        raise ValueError('unrecognized aggregation method %s' % str(fn))

    levels[-1].complexity['aggregation'] = kwargs['cost'][0] * (float(C.nnz)/A.nnz)

    # Improve near nullspace candidates by relaxing on A B = 0
    temp_cost = [0.0]
    fn, kwargs = unpack_arg(improve_candidates[len(levels)-1], cost=False)
    if fn is not None:
        b = np.zeros((A.shape[0], 1), dtype=A.dtype)
        B = relaxation_as_linear_operator((fn, kwargs), A, b, temp_cost) * B
        levels[-1].B = B
        if A.symmetry == "nonsymmetric":
            BH = relaxation_as_linear_operator((fn, kwargs), AH, b, temp_cost) * BH
            levels[-1].BH = BH

    levels[-1].complexity['candidates'] = temp_cost[0] * B.shape[1]

    # Compute the tentative prolongator, T, which is a tentative interpolation
    # matrix from the coarse-grid to the fine-grid.  T exactly interpolates
    # B_fine = T B_coarse. Orthogonalization complexity ~ 2nk^2, k=B.shape[1].
    temp_cost=[0.0]
    T, B = fit_candidates(AggOp, B, cost=temp_cost)
    if A.symmetry == "nonsymmetric":
        TH, BH = fit_candidates(AggOp, BH, cost=temp_cost)

    levels[-1].complexity['tentative'] = temp_cost[0]/A.nnz

    # Smooth the tentative prolongator, so that it's accuracy is greatly
    # improved for algebraically smooth error.
    fn, kwargs = unpack_arg(smooth[len(levels)-1])
    if fn == 'jacobi':
        P = jacobi_prolongation_smoother(A, T, C, B, **kwargs)
    elif fn == 'richardson':
        P = richardson_prolongation_smoother(A, T, **kwargs)
    elif fn == 'energy':
        P = energy_prolongation_smoother(A, T, C, B, None, (False, {}),
                                         **kwargs)
    elif fn is None:
        P = T
    else:
        raise ValueError('unrecognized prolongation smoother method %s' %
                         str(fn))

    levels[-1].complexity['smooth_P'] = kwargs['cost'][0]

    # Compute the restriction matrix, R, which interpolates from the fine-grid
    # to the coarse-grid.  If A is nonsymmetric, then R must be constructed
    # based on A.H.  Otherwise R = P.H or P.T.
    symmetry = A.symmetry
    if symmetry == 'hermitian':
        R = P.H
    elif symmetry == 'symmetric':
        R = P.T
    elif symmetry == 'nonsymmetric':
        fn, kwargs = unpack_arg(smooth[len(levels)-1])
        if fn == 'jacobi':
            R = jacobi_prolongation_smoother(AH, TH, C, BH, **kwargs).H
        elif fn == 'richardson':
            R = richardson_prolongation_smoother(AH, TH, **kwargs).H
        elif fn == 'energy':
            R = energy_prolongation_smoother(AH, TH, C, BH, None, (False, {}),
                                             **kwargs)
            R = R.H
        elif fn is None:
            R = T.H
        else:
            raise ValueError('unrecognized prolongation smoother method %s' %
                             str(fn))
        levels[-1].complexity['smooth_R'] = kwargs['cost'][0]

    if keep:
        levels[-1].C = C            # strength of connection matrix
        levels[-1].AggOp = AggOp    # aggregation operator
        levels[-1].T = T            # tentative prolongator

    levels[-1].P = P  # smoothed prolongator
    levels[-1].R = R  # restriction operator

    # Form coarse grid operator, get complexity
    levels[-1].complexity['RAP'] = mat_mat_complexity(R,A) / float(A.nnz)
    RA = R * A
    levels[-1].complexity['RAP'] += mat_mat_complexity(RA,P) / float(A.nnz)
    A = RA * P      # Galerkin operator, Ac = RAP
    A.symmetry = symmetry

    levels.append(multilevel_solver.level())
    levels[-1].A = A
    levels[-1].B = B           # right near nullspace candidates

    if A.symmetry == "nonsymmetric":
        levels[-1].BH = BH     # left near nullspace candidates
Exemple #12
0
def smoothed_aggregation_solver(A,
                                B=None,
                                BH=None,
                                symmetry='hermitian',
                                strength='symmetric',
                                aggregate='standard',
                                smooth=('jacobi', {
                                    'omega': 4.0 / 3.0
                                }),
                                presmoother=('block_gauss_seidel', {
                                    'sweep': 'symmetric'
                                }),
                                postsmoother=('block_gauss_seidel', {
                                    'sweep': 'symmetric'
                                }),
                                improve_candidates=[('block_gauss_seidel', {
                                    'sweep': 'symmetric',
                                    'iterations': 4
                                }), None],
                                max_levels=10,
                                max_coarse=10,
                                diagonal_dominance=False,
                                keep=False,
                                **kwargs):
    """Create a multilevel solver using classical-style Smoothed Aggregation (SA).

    Parameters
    ----------
    A : csr_matrix, bsr_matrix
        Sparse NxN matrix in CSR or BSR format

    B : None, array_like
        Right near-nullspace candidates stored in the columns of an NxK array.
        The default value B=None is equivalent to B=ones((N,1))

    BH : None, array_like
        Left near-nullspace candidates stored in the columns of an NxK array.
        BH is only used if symmetry is 'nonsymmetric'.
        The default value B=None is equivalent to BH=B.copy()

    symmetry : string
        'symmetric' refers to both real and complex symmetric
        'hermitian' refers to both complex Hermitian and real Hermitian
        'nonsymmetric' i.e. nonsymmetric in a hermitian sense
        Note, in the strictly real case, symmetric and hermitian are the same.
        Note, this flag does not denote definiteness of the operator.

    strength : string or list
        Method used to determine the strength of connection between unknowns of
        the linear system.  Method-specific parameters may be passed in using a
        tuple, e.g. strength=('symmetric',{'theta' : 0.25 }). If strength=None,
        all nonzero entries of the matrix are considered strong.
        Choose from 'symmetric', 'classical', 'evolution', 'algebraic_distance',
        'affinity', ('predefined', {'C' : csr_matrix}), None

    aggregate : string or list
        Method used to aggregate nodes.
        Choose from 'standard', 'lloyd', 'naive',
        ('predefined', {'AggOp' : csr_matrix})

    smooth : list
        Method used to smooth the tentative prolongator.  Method-specific
        parameters may be passed in using a tuple, e.g.  smooth=
        ('jacobi',{'filter' : True }).
        Choose from 'jacobi', 'richardson', 'energy', None

    presmoother : tuple, string, list
        Defines the presmoother for the multilevel cycling.  The default block
        Gauss-Seidel option defaults to point-wise Gauss-Seidel, if the matrix
        is CSR or is a BSR matrix with blocksize of 1.

    postsmoother : tuple, string, list
        Same as presmoother, except defines the postsmoother.

    improve_candidates : tuple, string, list
        The ith entry defines the method used to improve the candidates B on
        level i.  If the list is shorter than max_levels, then the last entry
        will define the method for all levels lower.  If tuple or string, then
        this single relaxation descriptor defines improve_candidates on all
        levels.
        The list elements are relaxation descriptors of the form used for
        presmoother and postsmoother.  A value of None implies no action on B.

    max_levels : integer
        Maximum number of levels to be used in the multilevel solver.

    max_coarse : integer
        Maximum number of variables permitted on the coarse grid.

    diagonal_dominance : bool, tuple
        If True (or the first tuple entry is True), then avoid coarsening
        diagonally dominant rows.  The second tuple entry requires a
        dictionary, where the key value 'theta' is used to tune the diagonal
        dominance threshold.

    keep : bool
        Flag to indicate keeping extra operators in the hierarchy for
        diagnostics.  For example, if True, then strength of connection (C),
        tentative prolongation (T), and aggregation (AggOp) are kept.

    Other Parameters
    ----------------
    cycle_type : ['V','W','F']
        Structrure of multigrid cycle

    coarse_solver : ['splu', 'lu', 'cholesky, 'pinv', 'gauss_seidel', ... ]
        Solver used at the coarsest level of the MG hierarchy.
        Optionally, may be a tuple (fn, args), where fn is a string such as
        ['splu', 'lu', ...] or a callable function, and args is a dictionary of
        arguments to be passed to fn.

    Returns
    -------
    ml : multilevel_solver
        Multigrid hierarchy of matrices and prolongation operators

    See Also
    --------
    multilevel_solver, classical.ruge_stuben_solver,
    aggregation.smoothed_aggregation_solver

    Notes
    -----
        - This method implements classical-style SA, not root-node style SA
          (see aggregation.rootnode_solver).

        - The additional parameters are passed through as arguments to
          multilevel_solver.  Refer to pyamg.multilevel_solver for additional
          documentation.

        - At each level, four steps are executed in order to define the coarser
          level operator.

          1. Matrix A is given and used to derive a strength matrix, C.

          2. Based on the strength matrix, indices are grouped or aggregated.

          3. The aggregates define coarse nodes and a tentative prolongation
             operator T is defined by injection

          4. The tentative prolongation operator is smoothed by a relaxation
             scheme to improve the quality and extent of interpolation from the
             aggregates to fine nodes.

        - The parameters smooth, strength, aggregate, presmoother, postsmoother
          can be varied on a per level basis.  For different methods on
          different levels, use a list as input so that the i-th entry defines
          the method at the i-th level.  If there are more levels in the
          hierarchy than list entries, the last entry will define the method
          for all levels lower.

          Examples are:
          smooth=[('jacobi', {'omega':1.0}), None, 'jacobi']
          presmoother=[('block_gauss_seidel', {'sweep':symmetric}), 'sor']
          aggregate=['standard', 'naive']
          strength=[('symmetric', {'theta':0.25}), ('symmetric', {'theta':0.08})]

        - Predefined strength of connection and aggregation schemes can be
          specified.  These options are best used together, but aggregation can
          be predefined while strength of connection is not.

          For predefined strength of connection, use a list consisting of
          tuples of the form ('predefined', {'C' : C0}), where C0 is a
          csr_matrix and each degree-of-freedom in C0 represents a supernode.
          For instance to predefine a three-level hierarchy, use
          [('predefined', {'C' : C0}), ('predefined', {'C' : C1}) ].

          Similarly for predefined aggregation, use a list of tuples.  For
          instance to predefine a three-level hierarchy, use [('predefined',
          {'AggOp' : Agg0}), ('predefined', {'AggOp' : Agg1}) ], where the
          dimensions of A, Agg0 and Agg1 are compatible, i.e.  Agg0.shape[1] ==
          A.shape[0] and Agg1.shape[1] == Agg0.shape[0].  Each AggOp is a
          csr_matrix.

    Examples
    --------
    >>> from pyamg import smoothed_aggregation_solver
    >>> from pyamg.gallery import poisson
    >>> from scipy.sparse.linalg import cg
    >>> import numpy as np
    >>> A = poisson((100,100), format='csr')           # matrix
    >>> b = np.ones((A.shape[0]))                      # RHS
    >>> ml = smoothed_aggregation_solver(A)            # AMG solver
    >>> M = ml.aspreconditioner(cycle='V')             # preconditioner
    >>> x,info = cg(A, b, tol=1e-8, maxiter=30, M=M)   # solve with CG

    References
    ----------
    .. [1996VaMaBr] Vanek, P. and Mandel, J. and Brezina, M.,
       "Algebraic Multigrid by Smoothed Aggregation for
       Second and Fourth Order Elliptic Problems",
       Computing, vol. 56, no. 3, pp. 179--196, 1996.
       http://citeseer.ist.psu.edu/vanek96algebraic.html

    """
    if not (isspmatrix_csr(A) or isspmatrix_bsr(A)):
        try:
            A = csr_matrix(A)
            warn("Implicit conversion of A to CSR", SparseEfficiencyWarning)
        except BaseException:
            raise TypeError(
                'Argument A must have type csr_matrix or bsr_matrix, or be convertible to csr_matrix'
            )

    A = A.asfptype()

    if (symmetry != 'symmetric') and (symmetry != 'hermitian') and\
            (symmetry != 'nonsymmetric'):
        raise ValueError(
            'expected \'symmetric\', \'nonsymmetric\' or \'hermitian\' for the symmetry parameter '
        )
    A.symmetry = symmetry

    if A.shape[0] != A.shape[1]:
        raise ValueError('expected square matrix')

    # Right near nullspace candidates use constant for each variable as default
    if B is None:
        B = np.kron(
            np.ones((int(A.shape[0] / blocksize(A)), 1), dtype=A.dtype),
            np.eye(blocksize(A), dtype=A.dtype))
    else:
        B = np.asarray(B, dtype=A.dtype)
        if len(B.shape) == 1:
            B = B.reshape(-1, 1)
        if B.shape[0] != A.shape[0]:
            raise ValueError(
                'The near null-space modes B have incorrect dimensions for matrix A'
            )
        if B.shape[1] < blocksize(A):
            warn(
                'Having less target vectors, B.shape[1], than blocksize of A can degrade convergence factors.'
            )

    # Left near nullspace candidates
    if A.symmetry == 'nonsymmetric':
        if BH is None:
            BH = B.copy()
        else:
            BH = np.asarray(BH, dtype=A.dtype)
            if len(BH.shape) == 1:
                BH = BH.reshape(-1, 1)
            if BH.shape[1] != B.shape[1]:
                raise ValueError(
                    'The number of left and right near null-space modes B and BH, must be equal'
                )
            if BH.shape[0] != A.shape[0]:
                raise ValueError(
                    'The near null-space modes BH have incorrect dimensions for matrix A'
                )

    # Levelize the user parameters, so that they become lists describing the
    # desired user option on each level.
    max_levels, max_coarse, strength =\
        levelize_strength_or_aggregation(strength, max_levels, max_coarse)
    max_levels, max_coarse, aggregate =\
        levelize_strength_or_aggregation(aggregate, max_levels, max_coarse)
    improve_candidates =\
        levelize_smooth_or_improve_candidates(improve_candidates, max_levels)
    smooth = levelize_smooth_or_improve_candidates(smooth, max_levels)

    # Construct multilevel structure
    levels = []
    levels.append(multilevel_solver.level())
    levels[-1].A = A  # matrix

    # Append near nullspace candidates
    levels[-1].B = B  # right candidates
    if A.symmetry == 'nonsymmetric':
        levels[-1].BH = BH  # left candidates

    while len(levels) < max_levels and\
            int(levels[-1].A.shape[0]/blocksize(levels[-1].A)) > max_coarse:
        extend_hierarchy(levels, strength, aggregate, smooth,
                         improve_candidates, diagonal_dominance, keep)

    ml = multilevel_solver(levels, **kwargs)
    change_smoothers(ml, presmoother, postsmoother)
    return ml
Exemple #13
0
def extend_hierarchy(levels, strength, CF, interp, keep):
    """ helper function for local methods """
    def unpack_arg(v):
        if isinstance(v, tuple):
            return v[0], v[1]
        else:
            return v, {}

    A = levels[-1].A
    block_starts = levels[-1].block_starts
    verts = levels[-1].verts

    # If this is a system, apply the unknown approach by coarsening and generating interpolation based on each diagonal block of A
    if (block_starts):
        A_diag = extract_diagonal_blocks(A, block_starts)
    else:
        A_diag = [A]

    # Compute the strength-of-connection matrix C, where larger
    # C[i,j] denote stronger couplings between i and j.
    C_diag = []
    P_diag = []
    splitting = []
    next_lvl_block_starts = [0]
    block_cnt = 0
    for mat in A_diag:
        fn, kwargs = unpack_arg(strength)
        if fn == 'symmetric':
            C_diag.append( symmetric_strength_of_connection(mat, **kwargs) )
        elif fn == 'classical':
            C_diag.append( classical_strength_of_connection(mat, **kwargs) )
        elif fn == 'distance':
            C_diag.append( distance_strength_of_connection(mat, **kwargs) )
        elif (fn == 'ode') or (fn == 'evolution'):
            C_diag.append( evolution_strength_of_connection(mat, **kwargs) )
        elif fn == 'energy_based':
            C_diag.append( energy_based_strength_of_connection(mat, **kwargs) )
        elif fn == 'algebraic_distance':
            C_diag.append( algebraic_distance(mat, **kwargs) )
        elif fn == 'affinity':
            C_diag.append( affinity_distance(mat, **kwargs) )
        elif fn is None:
            C_diag.append( mat )
        else:
            raise ValueError('unrecognized strength of connection method: %s' %
                             str(fn))

        # Generate the C/F splitting
        fn, kwargs = unpack_arg(CF)
        if fn == 'RS':
            splitting.append( split.RS(C_diag[-1]) )
        elif fn == 'PMIS':
            splitting.append( split.PMIS(C_diag[-1]) )
        elif fn == 'PMISc':
            splitting.append( split.PMISc(C_diag[-1]) )
        elif fn == 'CLJP':
            splitting.append( split.CLJP(C_diag[-1]) )
        elif fn == 'CLJPc':
            splitting.append( split.CLJPc(C_diag[-1]) )
        elif fn == 'Shifted2DCoarsening':
            splitting.append( split.Shifted2DCoarsening(C_diag[-1]) )
        else:
            raise ValueError('unknown C/F splitting method (%s)' % CF)

        # Generate the interpolation matrix that maps from the coarse-grid to the
        # fine-grid
        fn, kwargs = unpack_arg(interp)
        if fn == 'standard':
            P_diag.append( standard_interpolation(mat, C_diag[-1], splitting[-1]) )
        elif fn == 'direct':
            P_diag.append( direct_interpolation(mat, C_diag[-1], splitting[-1]) )
        else:
            raise ValueError('unknown interpolation method (%s)' % interp)

        next_lvl_block_starts.append( next_lvl_block_starts[-1] + P_diag[-1].shape[1])

        block_cnt = block_cnt + 1

    P = block_diag(P_diag)

    # Generate the restriction matrix that maps from the fine-grid to the
    # coarse-grid
    R = P.T.tocsr()

    # Store relevant information for this level
    splitting = numpy.concatenate(splitting)
    if keep:
        C = block_diag(C_diag)
        levels[-1].C = C                  # strength of connection matrix
        levels[-1].splitting = splitting  # C/F splitting

    levels[-1].P = P                  # prolongation operator
    levels[-1].R = R                  # restriction operator

    levels.append(multilevel_solver.level())

    # Form next level through Galerkin product
    # !!! For systems, how do I propogate the block structure information down to the next grid? Especially if the blocks are different sizes? !!!
    A = R * A * P
    levels[-1].A = A

    if (block_starts):
        levels[-1].block_starts = next_lvl_block_starts
    else:
        levels[-1].block_starts = None

    # If called for, output a visualization of the C/F splitting
    if (verts.any()):
        new_verts = numpy.empty([P.shape[1], 2])
        cnt = 0
        for i in range(len(splitting)):
            if (splitting[i]):
                new_verts[cnt] = verts[i]
                cnt = cnt + 1
        levels[-1].verts = new_verts
    else:
        levels[-1].verts = numpy.zeros(1)
Exemple #14
0
def ruge_stuben_solver(A,
                       strength=('classical', {
                           'theta': 0.25
                       }),
                       CF='RS',
                       presmoother=('gauss_seidel', {
                           'sweep': 'symmetric'
                       }),
                       postsmoother=('gauss_seidel', {
                           'sweep': 'symmetric'
                       }),
                       max_levels=10,
                       max_coarse=500,
                       keep=False,
                       **kwargs):
    """Create a multilevel solver using Classical AMG (Ruge-Stuben AMG)

    Parameters
    ----------
    A : csr_matrix
        Square matrix in CSR format
    strength : ['symmetric', 'classical', 'evolution', None]
        Method used to determine the strength of connection between unknowns
        of the linear system.  Method-specific parameters may be passed in
        using a tuple, e.g. strength=('symmetric',{'theta' : 0.25 }). If
        strength=None, all nonzero entries of the matrix are considered strong.
    CF : {string} : default 'RS'
        Method used for coarse grid selection (C/F splitting)
        Supported methods are RS, PMIS, PMISc, CLJP, and CLJPc
    presmoother : {string or dict}
        Method used for presmoothing at each level.  Method-specific parameters
        may be passed in using a tuple, e.g.
        presmoother=('gauss_seidel',{'sweep':'symmetric}), the default.
    postsmoother : {string or dict}
        Postsmoothing method with the same usage as presmoother
    max_levels: {integer} : default 10
        Maximum number of levels to be used in the multilevel solver.
    max_coarse: {integer} : default 500
        Maximum number of variables permitted on the coarse grid.
    keep: {bool} : default False
        Flag to indicate keeping extra operators in the hierarchy for
        diagnostics.  For example, if True, then strength of connection (C) and
        tentative prolongation (T) are kept.

    Returns
    -------
    ml : multilevel_solver
        Multigrid hierarchy of matrices and prolongation operators

    Examples
    --------
    >>> from pyamg.gallery import poisson
    >>> from pyamg import ruge_stuben_solver
    >>> A = poisson((10,),format='csr')
    >>> ml = ruge_stuben_solver(A,max_coarse=3)

    Notes
    -----

    "coarse_solver" is an optional argument and is the solver used at the
    coarsest grid.  The default is a pseudo-inverse.  Most simply,
    coarse_solver can be one of ['splu', 'lu', 'cholesky, 'pinv',
    'gauss_seidel', ... ].  Additionally, coarse_solver may be a tuple
    (fn, args), where fn is a string such as ['splu', 'lu', ...] or a callable
    function, and args is a dictionary of arguments to be passed to fn.


    References
    ----------
    .. [1] Trottenberg, U., Oosterlee, C. W., and Schuller, A.,
       "Multigrid" San Diego: Academic Press, 2001.  Appendix A

    See Also
    --------
    aggregation.smoothed_aggregation_solver, multilevel_solver,
    aggregation.rootnode_solver

    """

    levels = [multilevel_solver.level()]

    # convert A to csr
    if not isspmatrix_csr(A):
        try:
            A = csr_matrix(A)
            warn("Implicit conversion of A to CSR", SparseEfficiencyWarning)
        except:
            raise TypeError('Argument A must have type csr_matrix, \
                             or be convertible to csr_matrix')
    # preprocess A
    A = A.asfptype()
    if A.shape[0] != A.shape[1]:
        raise ValueError('expected square matrix')

    levels[-1].A = A

    while len(levels) < max_levels and levels[-1].A.shape[0] > max_coarse:
        extend_hierarchy(levels, strength, CF, keep)

    ml = multilevel_solver(levels, **kwargs)
    change_smoothers(ml, presmoother, postsmoother)
    return ml
Exemple #15
0
def extend_hierarchy(levels, strength, CF, interpolation, restriction, keep):
    """ helper function for local methods """

    A = levels[-1].A

    # Compute the strength-of-connection matrix C, where larger
    # C[i,j] denote stronger couplings between i and j.
    fn, kwargs = unpack_arg(strength)
    if fn == 'symmetric':
        C = symmetric_strength_of_connection(A, **kwargs)
    elif fn == 'classical':
        C = classical_strength_of_connection(A, **kwargs)
    elif fn == 'distance':
        C = distance_strength_of_connection(A, **kwargs)
    elif (fn == 'ode') or (fn == 'evolution'):
        C = evolution_strength_of_connection(A, **kwargs)
    elif fn == 'energy_based':
        C = energy_based_strength_of_connection(A, **kwargs)
    elif fn == 'algebraic_distance':
        C = algebraic_distance(A, **kwargs)
    elif fn == 'affinity':
        C = affinity_distance(A, **kwargs)
    elif fn is None:
        C = A
    else:
        raise ValueError('unrecognized strength of connection method: %s' %
                         str(fn))

    levels[-1].complexity['strength'] = kwargs['cost'][0]

    # Generate the C/F splitting
    fn, kwargs = unpack_arg(CF)
    if fn == 'RS':
        splitting = split.RS(C, **kwargs)
    elif fn == 'PMIS':
        splitting = split.PMIS(C, **kwargs)
    elif fn == 'PMISc':
        splitting = split.PMISc(C, **kwargs)
    elif fn == 'CLJP':
        splitting = split.CLJP(C, **kwargs)
    elif fn == 'CLJPc':
        splitting = split.CLJPc(C, **kwargs)
    elif fn == 'CR':
        splitting = CR(C, **kwargs)
    else:
        raise ValueError('unknown C/F splitting method (%s)' % CF)

    levels[-1].complexity['CF'] = kwargs['cost'][0]

    # Generate the interpolation matrix that maps from the coarse-grid to the
    # fine-grid
    fn, kwargs = unpack_arg(interpolation)
    if fn == 'standard':
        P = standard_interpolation(A, C, splitting, **kwargs)
    elif fn == 'distance_two':
        P = distance_two_interpolation(A, C, splitting, **kwargs)
    elif fn == 'direct':
        P = direct_interpolation(A, C, splitting, **kwargs)
    elif fn == 'one_point':
        P = one_point_interpolation(A, C, splitting, **kwargs)
    elif fn == 'injection':
        P = injection_interpolation(A, splitting, **kwargs)
    else:
        raise ValueError('unknown interpolation method (%s)' % interpolation)
    levels[-1].complexity['interpolate'] = kwargs['cost'][0]

    # Generate the restriction matrix that maps from the fine-grid to the
    # coarse-grid. Must make sure transpose matrices remain in CSR or BSR
    fn, kwargs = unpack_arg(restriction)
    if isspmatrix_csr(A):
        if restriction == 'galerkin':
            R = P.T.tocsr()
        elif fn == 'standard':
            temp_A = A.T.tocsr()
            temp_C = C.T.tocsr()
            R = standard_interpolation(temp_A, temp_C, splitting, **kwargs)
            R = R.T.tocsr()
        elif fn == 'distance_two':
            temp_A = A.T.tocsr()
            temp_C = C.T.tocsr()
            R = distance_two_interpolation(temp_A, temp_C, splitting, **kwargs)
            R = R.T.tocsr()
        elif fn == 'direct':
            temp_A = A.T.tocsr()
            temp_C = C.T.tocsr()
            R = direct_interpolation(temp_A, temp_C, splitting, **kwargs)
            R = R.T.tocsr()
        elif fn == 'one_point':  # Don't need A^T here
            temp_C = C.T.tocsr()
            R = one_point_interpolation(A, temp_C, splitting, **kwargs)
            R = R.T.tocsr()
        elif fn == 'injection':  # Don't need A^T or C^T here
            R = injection_interpolation(A, splitting, **kwargs)
            R = R.T.tocsr()
        else:
            raise ValueError('unknown interpolation method (%s)' %
                             interpolation)
    else:
        if restriction == 'galerkin':
            R = P.T.tobsr()
        elif fn == 'standard':
            temp_A = A.T.tobsr()
            temp_C = C.T.tocsr()
            R = standard_interpolation(temp_A, temp_C, splitting, **kwargs)
            R = R.T.tobsr()
        elif fn == 'distance_two':
            temp_A = A.T.tobsr()
            temp_C = C.T.tocsr()
            R = distance_two_interpolation(temp_A, temp_C, splitting, **kwargs)
            R = R.T.tobsr()
        elif fn == 'direct':
            temp_A = A.T.tobsr()
            temp_C = C.T.tocsr()
            R = direct_interpolation(temp_A, temp_C, splitting, **kwargs)
            R = R.T.tobsr()
        elif fn == 'one_point':  # Don't need A^T here
            temp_C = C.T.tocsr()
            R = one_point_interpolation(A, temp_C, splitting, **kwargs)
            R = R.T.tobsr()
        elif fn == 'injection':  # Don't need A^T or C^T here
            R = injection_interpolation(A, splitting, **kwargs)
            R = R.T.tobsr()
        else:
            raise ValueError('unknown interpolation method (%s)' %
                             interpolation)

    levels[-1].complexity['restriction'] = kwargs['cost'][0]

    # Store relevant information for this level
    if keep:
        levels[-1].C = C  # strength of connection matrix

    levels[-1].P = P  # prolongation operator
    levels[-1].R = R  # restriction operator
    levels[-1].splitting = splitting  # C/F splitting

    # Form coarse grid operator, get complexity
    levels[-1].complexity['RAP'] = mat_mat_complexity(R, A) / float(A.nnz)
    RA = R * A
    levels[-1].complexity['RAP'] += mat_mat_complexity(RA, P) / float(A.nnz)
    A = RA * P  # Galerkin operator, Ac = RAP

    # Make sure coarse-grid operator is in correct sparse format
    if (isspmatrix_csr(P) and (not isspmatrix_csr(A))):
        A = A.tocsr()
    elif (isspmatrix_bsr(P) and (not isspmatrix_bsr(A))):
        A = A.tobsr()

    # Form next level through Galerkin product
    levels.append(multilevel_solver.level())
    levels[-1].A = A
Exemple #16
0
def extend_hierarchy(levels, strength, aggregate, smooth, improve_candidates,
                     diagonal_dominance=False, keep=True):
    """Service routine to implement the strength of connection, aggregation,
    tentative prolongation construction, and prolongation smoothing.  Called by
    smoothed_aggregation_solver.
    """

    A = levels[-1].A
    B = levels[-1].B
    if A.symmetry == "nonsymmetric":
        AH = A.H.asformat(A.format)
        BH = levels[-1].BH

    # Compute the strength-of-connection matrix C, where larger
    # C[i, j] denote stronger couplings between i and j.
    fn, kwargs = unpack_arg(strength[len(levels)-1])
    if fn == 'symmetric':
        C = symmetric_strength_of_connection(A, **kwargs)
    elif fn == 'classical':
        C = classical_strength_of_connection(A, **kwargs)
    elif fn == 'distance':
        C = distance_strength_of_connection(A, **kwargs)
    elif (fn == 'ode') or (fn == 'evolution'):
        if 'B' in kwargs:
            C = evolution_strength_of_connection(A, **kwargs)
        else:
            C = evolution_strength_of_connection(A, B, **kwargs)
    elif fn == 'energy_based':
        C = energy_based_strength_of_connection(A, **kwargs)
    elif fn == 'predefined':
        C = kwargs['C'].tocsr()
    elif fn == 'algebraic_distance':
        C = algebraic_distance(A, **kwargs)
    elif fn == 'affinity':
        C = affinity_distance(A, **kwargs)
    elif fn is None:
        C = A.tocsr()
    else:
        raise ValueError('unrecognized strength of connection method: %s' %
                         str(fn))
    
    levels[-1].complexity['strength'] = kwargs['cost'][0]

    # Avoid coarsening diagonally dominant rows
    flag, kwargs = unpack_arg(diagonal_dominance)
    if flag:
        C = eliminate_diag_dom_nodes(A, C, **kwargs)
        levels[-1].complexity['diag_dom'] = kwargs['cost'][0]

    # Compute the aggregation matrix AggOp (i.e., the nodal coarsening of A).
    # AggOp is a boolean matrix, where the sparsity pattern for the k-th column
    # denotes the fine-grid nodes agglomerated into k-th coarse-grid node.
    fn, kwargs = unpack_arg(aggregate[len(levels)-1])
    if fn == 'standard':
        AggOp, Cnodes = standard_aggregation(C, **kwargs)
    elif fn == 'naive':
        AggOp, Cnodes = naive_aggregation(C, **kwargs)
    elif fn == 'lloyd':
        AggOp, Cnodes = lloyd_aggregation(C, **kwargs)
    elif fn == 'predefined':
        AggOp = kwargs['AggOp'].tocsr()
        Cnodes = kwargs['Cnodes']
    else:
        raise ValueError('unrecognized aggregation method %s' % str(fn))
    
    levels[-1].complexity['aggregation'] = kwargs['cost'][0] * (float(C.nnz)/A.nnz)

    # Improve near nullspace candidates by relaxing on A B = 0
    temp_cost = [0.0]
    fn, kwargs = unpack_arg(improve_candidates[len(levels)-1],cost=False)
    if fn is not None:
        b = np.zeros((A.shape[0], 1), dtype=A.dtype)
        B = relaxation_as_linear_operator((fn, kwargs), A, b, temp_cost) * B
        levels[-1].B = B
        if A.symmetry == "nonsymmetric":
            BH = relaxation_as_linear_operator((fn, kwargs), AH, b, temp_cost) * BH
            levels[-1].BH = BH

    levels[-1].complexity['candidates'] = temp_cost[0] * B.shape[1]

    # Compute the tentative prolongator, T, which is a tentative interpolation
    # matrix from the coarse-grid to the fine-grid.  T exactly interpolates
    # B_fine[:, 0:blocksize(A)] = T B_coarse[:, 0:blocksize(A)].
    # Orthogonalization complexity ~ 2nk^2, k = blocksize(A).
    temp_cost=[0.0]
    T, dummy = fit_candidates(AggOp, B[:, 0:blocksize(A)], cost=temp_cost)
    del dummy
    if A.symmetry == "nonsymmetric":
        TH, dummyH = fit_candidates(AggOp, BH[:, 0:blocksize(A)], cost=temp_cost)
        del dummyH

    levels[-1].complexity['tentative'] = temp_cost[0]/A.nnz
    
    # Create necessary root node matrices
    Cpt_params = (True, get_Cpt_params(A, Cnodes, AggOp, T))
    T = scale_T(T, Cpt_params[1]['P_I'], Cpt_params[1]['I_F'])
    levels[-1].complexity['tentative'] += T.nnz / float(A.nnz)
    if A.symmetry == "nonsymmetric":
        TH = scale_T(TH, Cpt_params[1]['P_I'], Cpt_params[1]['I_F'])
        levels[-1].complexity['tentative'] += TH.nnz / float(A.nnz)

    # Set coarse grid near nullspace modes as injected fine grid near
    # null-space modes
    B = Cpt_params[1]['P_I'].T*levels[-1].B
    if A.symmetry == "nonsymmetric":
        BH = Cpt_params[1]['P_I'].T*levels[-1].BH

    # Smooth the tentative prolongator, so that it's accuracy is greatly
    # improved for algebraically smooth error.
    fn, kwargs = unpack_arg(smooth[len(levels)-1])
    if fn == 'energy':
        P = energy_prolongation_smoother(A, T, C, B, levels[-1].B,
                                         Cpt_params=Cpt_params, **kwargs)
    elif fn is None:
        P = T
    else:
        raise ValueError('unrecognized prolongation smoother \
                          method %s' % str(fn))

    levels[-1].complexity['smooth_P'] = kwargs['cost'][0]

    # Compute the restriction matrix R, which interpolates from the fine-grid
    # to the coarse-grid.  If A is nonsymmetric, then R must be constructed
    # based on A.H.  Otherwise R = P.H or P.T.
    symmetry = A.symmetry
    if symmetry == 'hermitian':
        R = P.H
    elif symmetry == 'symmetric':
        R = P.T
    elif symmetry == 'nonsymmetric':
        fn, kwargs = unpack_arg(smooth[len(levels)-1])
        if fn == 'energy':
            R = energy_prolongation_smoother(AH, TH, C, BH, levels[-1].BH,
                                             Cpt_params=Cpt_params, **kwargs)
            R = R.H
            levels[-1].complexity['smooth_R'] = kwargs['cost'][0]
        elif fn is None:
            R = T.H
        else:
            raise ValueError('unrecognized prolongation smoother \
                              method %s' % str(fn))

    if keep:
        levels[-1].C = C                        # strength of connection matrix
        levels[-1].AggOp = AggOp                # aggregation operator
        levels[-1].T = T                        # tentative prolongator
        levels[-1].Fpts = Cpt_params[1]['Fpts'] # Fpts
        levels[-1].P_I = Cpt_params[1]['P_I']   # Injection operator
        levels[-1].I_F = Cpt_params[1]['I_F']   # Identity on F-pts
        levels[-1].I_C = Cpt_params[1]['I_C']   # Identity on C-pts

    levels[-1].P = P                            # smoothed prolongator
    levels[-1].R = R                            # restriction operator
    levels[-1].Cpts = Cpt_params[1]['Cpts']     # Cpts (i.e., rootnodes)

    # Form coarse grid operator, get complexity
    levels[-1].complexity['RAP'] = mat_mat_complexity(R,A) / float(A.nnz)
    RA = R * A
    levels[-1].complexity['RAP'] += mat_mat_complexity(RA,P) / float(A.nnz)
    A = RA * P      # Galerkin operator, Ac = RAP
    A.symmetry = symmetry

    levels.append(multilevel_solver.level())
    levels[-1].A = A
    levels[-1].B = B                          # right near nullspace candidates

    if A.symmetry == "nonsymmetric":
        levels[-1].BH = BH                   # left near nullspace candidates
Exemple #17
0
def ruge_stuben_solver(A,
                       strength=('classical', {
                           'theta': 0.25
                       }),
                       CF='RS',
                       interpolation='direct',
                       restriction='galerkin',
                       presmoother=('gauss_seidel', {
                           'sweep': 'symmetric'
                       }),
                       postsmoother=('gauss_seidel', {
                           'sweep': 'symmetric'
                       }),
                       max_levels=10,
                       max_coarse=10,
                       keep=False,
                       **kwargs):
    """Create a multilevel solver using Classical AMG (Ruge-Stuben AMG)

    Parameters
    ----------
    A : csr_matrix
        Square matrix in CSR format
    strength : ['symmetric', 'classical', 'evolution', 'distance',
                'algebraic_distance','affinity', 'energy_based', None]
        Method used to determine the strength of connection between unknowns
        of the linear system.  Method-specific parameters may be passed in
        using a tuple, e.g. strength=('symmetric',{'theta' : 0.25 }). If
        strength=None, all nonzero entries of the matrix are considered strong.
    CF : {string} : default 'RS'
        Method used for coarse grid selection (C/F splitting)
        Supported methods are RS, PMIS, PMISc, CLJP, CLJPc, and CR.
    interpolation : {string} : default 'direct'
        Method for interpolation. Options include 'direct', 'standard', 'injection',
        'one_point', and 'distance_two'.
    restriction : {string or dict} : default 'galerkin'
        'Galerkin' means set R := P^T for a Galerkin coarse-grid operator. Can also specify
        an interpolation method as above, to build the restriciton operator based on A^T. 
    presmoother : {string or dict}
        Method used for presmoothing at each level.  Method-specific parameters
        may be passed in using a tuple, e.g.
        presmoother=('gauss_seidel',{'sweep':'symmetric}), the default.
    postsmoother : {string or dict}
        Postsmoothing method with the same usage as presmoother
    max_levels: {integer} : default 10
        Maximum number of levels to be used in the multilevel solver.
    max_coarse: {integer} : default 500
        Maximum number of variables permitted on the coarse grid.
    keep: {bool} : default False
        Flag to indicate keeping extra operators in the hierarchy for
        diagnostics.  For example, if True, then strength of connection (C) and
        tentative prolongation (T) are kept.

    Returns
    -------
    ml : multilevel_solver
        Multigrid hierarchy of matrices and prolongation operators

    Other Parameters
    ----------------
    cycle_type : ['V','W','F']
        Structrure of multigrid cycle
    coarse_solver : ['splu', 'lu', 'cholesky, 'pinv', 'gauss_seidel', ... ]
        Solver used at the coarsest level of the MG hierarchy.
            Optionally, may be a tuple (fn, args), where fn is a string such as
        ['splu', 'lu', ...] or a callable function, and args is a dictionary of
        arguments to be passed to fn.
    setup_complexity : bool
        For a detailed, more accurate setup complexity, pass in 
        'setup_complexity' = True. This will slow down performance, but
        increase accuracy of complexity count. 

    Examples
    --------
    >>> from pyamg.gallery import poisson
    >>> from pyamg import ruge_stuben_solver
    >>> A = poisson((10,),format='csr')
    >>> ml = ruge_stuben_solver(A,max_coarse=3)

    Notes
    -----

    Standard interpolation is generally considered more robust than
    direct, but direct is the currently the default until our new 
    implementation of standard has been more rigorously tested.

    "coarse_solver" is an optional argument and is the solver used at the
    coarsest grid.  The default is a pseudo-inverse.  Most simply,
    coarse_solver can be one of ['splu', 'lu', 'cholesky, 'pinv',
    'gauss_seidel', ... ].  Additionally, coarse_solver may be a tuple
    (fn, args), where fn is a string such as ['splu', 'lu', ...] or a callable
    function, and args is a dictionary of arguments to be passed to fn.


    References
    ----------
    .. [1] Trottenberg, U., Oosterlee, C. W., and Schuller, A.,
       "Multigrid" San Diego: Academic Press, 2001.  Appendix A

    See Also
    --------
    aggregation.smoothed_aggregation_solver, multilevel_solver,
    aggregation.rootnode_solver

    """

    if ('setup_complexity' in kwargs):
        if kwargs['setup_complexity'] == True:
            mat_mat_complexity.__detailed__ = True
        del kwargs['setup_complexity']

    # Convert A to csr
    if not (isspmatrix_csr(A) or isspmatrix_bsr(A)):
        try:
            A = csr_matrix(A)
            warn("Implicit conversion of A to CSR", SparseEfficiencyWarning)
        except:
            raise TypeError(
                'Argument A must have type csr_matrix, bsr_matrix, \
                             or be convertible to csr_matrix')

    # if isspmatrix_bsr(A):
    #     warn("Classical AMG is often more effective on CSR matrices.")

    # preprocess A
    A = A.asfptype()
    if A.shape[0] != A.shape[1]:
        raise ValueError('expected square matrix')

    levels = [multilevel_solver.level()]
    levels[-1].A = A

    while len(levels) < max_levels and levels[-1].A.shape[0] > max_coarse:
        extend_hierarchy(levels, strength, CF, interpolation, restriction,
                         keep)

    ml = multilevel_solver(levels, **kwargs)
    change_smoothers(ml, presmoother, postsmoother)
    return ml
Exemple #18
0
def extend_hierarchy(levels, strength, aggregate, smooth, improve_candidates, diagonal_dominance=False, keep=True):
    """Service routine to implement the strength of connection, aggregation,
    tentative prolongation construction, and prolongation smoothing.  Called by
    smoothed_aggregation_solver.
    """

    def unpack_arg(v):
        if isinstance(v, tuple):
            return v[0], v[1]
        else:
            return v, {}

    A = levels[-1].A
    B = levels[-1].B
    if A.symmetry == "nonsymmetric":
        AH = A.H.asformat(A.format)
        BH = levels[-1].BH

    # Compute the strength-of-connection matrix C, where larger
    # C[i, j] denote stronger couplings between i and j.
    fn, kwargs = unpack_arg(strength[len(levels) - 1])
    if fn == "symmetric":
        C = symmetric_strength_of_connection(A, **kwargs)
    elif fn == "classical":
        C = classical_strength_of_connection(A, **kwargs)
    elif fn == "distance":
        C = distance_strength_of_connection(A, **kwargs)
    elif (fn == "ode") or (fn == "evolution"):
        if "B" in kwargs:
            C = evolution_strength_of_connection(A, **kwargs)
        else:
            C = evolution_strength_of_connection(A, B, **kwargs)
    elif fn == "energy_based":
        C = energy_based_strength_of_connection(A, **kwargs)
    elif fn == "predefined":
        C = kwargs["C"].tocsr()
    elif fn == "algebraic_distance":
        C = algebraic_distance(A, **kwargs)
    elif fn is None:
        C = A.tocsr()
    else:
        raise ValueError("unrecognized strength of connection method: %s" % str(fn))

    # Avoid coarsening diagonally dominant rows
    flag, kwargs = unpack_arg(diagonal_dominance)
    if flag:
        C = eliminate_diag_dom_nodes(A, C, **kwargs)

    # Compute the aggregation matrix AggOp (i.e., the nodal coarsening of A).
    # AggOp is a boolean matrix, where the sparsity pattern for the k-th column
    # denotes the fine-grid nodes agglomerated into k-th coarse-grid node.
    fn, kwargs = unpack_arg(aggregate[len(levels) - 1])
    if fn == "standard":
        AggOp, Cnodes = standard_aggregation(C, **kwargs)
    elif fn == "naive":
        AggOp, Cnodes = naive_aggregation(C, **kwargs)
    elif fn == "lloyd":
        AggOp, Cnodes = lloyd_aggregation(C, **kwargs)
    elif fn == "predefined":
        AggOp = kwargs["AggOp"].tocsr()
        Cnodes = kwargs["Cnodes"]
    else:
        raise ValueError("unrecognized aggregation method %s" % str(fn))

    # Improve near nullspace candidates by relaxing on A B = 0
    fn, kwargs = unpack_arg(improve_candidates[len(levels) - 1])
    if fn is not None:
        b = np.zeros((A.shape[0], 1), dtype=A.dtype)
        B = relaxation_as_linear_operator((fn, kwargs), A, b) * B
        levels[-1].B = B
        if A.symmetry == "nonsymmetric":
            BH = relaxation_as_linear_operator((fn, kwargs), AH, b) * BH
            levels[-1].BH = BH

    # Compute the tentative prolongator, T, which is a tentative interpolation
    # matrix from the coarse-grid to the fine-grid.  T exactly interpolates
    # B_fine[:, 0:blocksize(A)] = T B_coarse[:, 0:blocksize(A)].
    T, dummy = fit_candidates(AggOp, B[:, 0 : blocksize(A)])
    del dummy
    if A.symmetry == "nonsymmetric":
        TH, dummyH = fit_candidates(AggOp, BH[:, 0 : blocksize(A)])
        del dummyH

    # Create necessary root node matrices
    Cpt_params = (True, get_Cpt_params(A, Cnodes, AggOp, T))
    T = scale_T(T, Cpt_params[1]["P_I"], Cpt_params[1]["I_F"])
    if A.symmetry == "nonsymmetric":
        TH = scale_T(TH, Cpt_params[1]["P_I"], Cpt_params[1]["I_F"])

    # Set coarse grid near nullspace modes as injected fine grid near
    # null-space modes
    B = Cpt_params[1]["P_I"].T * levels[-1].B
    if A.symmetry == "nonsymmetric":
        BH = Cpt_params[1]["P_I"].T * levels[-1].BH

    # Smooth the tentative prolongator, so that it's accuracy is greatly
    # improved for algebraically smooth error.
    fn, kwargs = unpack_arg(smooth[len(levels) - 1])
    if fn == "energy":
        P = energy_prolongation_smoother(A, T, C, B, levels[-1].B, Cpt_params=Cpt_params, **kwargs)
    elif fn is None:
        P = T
    else:
        raise ValueError(
            "unrecognized prolongation smoother \
                          method %s"
            % str(fn)
        )

    # Compute the restriction matrix R, which interpolates from the fine-grid
    # to the coarse-grid.  If A is nonsymmetric, then R must be constructed
    # based on A.H.  Otherwise R = P.H or P.T.
    symmetry = A.symmetry
    if symmetry == "hermitian":
        R = P.H
    elif symmetry == "symmetric":
        R = P.T
    elif symmetry == "nonsymmetric":
        fn, kwargs = unpack_arg(smooth[len(levels) - 1])
        if fn == "energy":
            R = energy_prolongation_smoother(AH, TH, C, BH, levels[-1].BH, Cpt_params=Cpt_params, **kwargs)
            R = R.H
        elif fn is None:
            R = T.H
        else:
            raise ValueError(
                "unrecognized prolongation smoother \
                              method %s"
                % str(fn)
            )

    if keep:
        levels[-1].C = C  # strength of connection matrix
        levels[-1].AggOp = AggOp  # aggregation operator
        levels[-1].T = T  # tentative prolongator
        levels[-1].Fpts = Cpt_params[1]["Fpts"]  # Fpts
        levels[-1].P_I = Cpt_params[1]["P_I"]  # Injection operator
        levels[-1].I_F = Cpt_params[1]["I_F"]  # Identity on F-pts
        levels[-1].I_C = Cpt_params[1]["I_C"]  # Identity on C-pts

    levels[-1].P = P  # smoothed prolongator
    levels[-1].R = R  # restriction operator
    levels[-1].Cpts = Cpt_params[1]["Cpts"]  # Cpts (i.e., rootnodes)

    levels.append(multilevel_solver.level())
    A = R * A * P  # Galerkin operator
    A.symmetry = symmetry
    levels[-1].A = A
    levels[-1].B = B  # right near nullspace candidates

    if A.symmetry == "nonsymmetric":
        levels[-1].BH = BH  # left near nullspace candidates
Exemple #19
0
def extend_hierarchy(levels, strength, CF, interpolation, restriction, keep):
    """ helper function for local methods """

    A = levels[-1].A

    # Compute the strength-of-connection matrix C, where larger
    # C[i,j] denote stronger couplings between i and j.
    fn, kwargs = unpack_arg(strength)
    if fn == 'symmetric':
        C = symmetric_strength_of_connection(A, **kwargs)
    elif fn == 'classical':
        C = classical_strength_of_connection(A, **kwargs)
    elif fn == 'distance':
        C = distance_strength_of_connection(A, **kwargs)
    elif (fn == 'ode') or (fn == 'evolution'):
        C = evolution_strength_of_connection(A, **kwargs)
    elif fn == 'energy_based':
        C = energy_based_strength_of_connection(A, **kwargs)
    elif fn == 'algebraic_distance':
        C = algebraic_distance(A, **kwargs)
    elif fn == 'affinity':
        C = affinity_distance(A, **kwargs)
    elif fn is None:
        C = A
    else:
        raise ValueError('unrecognized strength of connection method: %s' %
                         str(fn))

    levels[-1].complexity['strength'] = kwargs['cost'][0]

    # Generate the C/F splitting
    fn, kwargs = unpack_arg(CF)
    if fn == 'RS':
        splitting = split.RS(C, **kwargs)
    elif fn == 'PMIS':
        splitting = split.PMIS(C, **kwargs)
    elif fn == 'PMISc':
        splitting = split.PMISc(C, **kwargs)
    elif fn == 'CLJP':
        splitting = split.CLJP(C, **kwargs)
    elif fn == 'CLJPc':
        splitting = split.CLJPc(C, **kwargs)
    elif fn == 'CR':
        splitting = CR(C, **kwargs)
    else:
        raise ValueError('unknown C/F splitting method (%s)' % CF)

    levels[-1].complexity['CF'] = kwargs['cost'][0]

    # Generate the interpolation matrix that maps from the coarse-grid to the
    # fine-grid
    fn, kwargs = unpack_arg(interpolation)
    if fn == 'standard':
        P = standard_interpolation(A, C, splitting, **kwargs)
    elif fn == 'distance_two':
        P = distance_two_interpolation(A, C, splitting, **kwargs)
    elif fn == 'direct':
        P = direct_interpolation(A, C, splitting, **kwargs)
    elif fn == 'one_point':
        P = one_point_interpolation(A, C, splitting, **kwargs)
    elif fn == 'injection':
        P = injection_interpolation(A, splitting, **kwargs)
    else:
        raise ValueError('unknown interpolation method (%s)' % interpolation)
    levels[-1].complexity['interpolate'] = kwargs['cost'][0]

    # Generate the restriction matrix that maps from the fine-grid to the
    # coarse-grid. Must make sure transpose matrices remain in CSR or BSR
    fn, kwargs = unpack_arg(restriction)
    if isspmatrix_csr(A):
        if restriction == 'galerkin':
            R = P.T.tocsr()
        elif fn == 'standard':
            temp_A = A.T.tocsr()
            temp_C = C.T.tocsr()
            R = standard_interpolation(temp_A, temp_C, splitting, **kwargs)
            R = R.T.tocsr()
        elif fn == 'distance_two':
            temp_A = A.T.tocsr()
            temp_C = C.T.tocsr()
            R = distance_two_interpolation(temp_A, temp_C, splitting, **kwargs)
            R = R.T.tocsr()
        elif fn == 'direct':
            temp_A = A.T.tocsr()
            temp_C = C.T.tocsr()
            R = direct_interpolation(temp_A, temp_C, splitting, **kwargs)
            R = R.T.tocsr()
        elif fn == 'one_point':         # Don't need A^T here
            temp_C = C.T.tocsr()
            R = one_point_interpolation(A, temp_C, splitting, **kwargs)
            R = R.T.tocsr()
        elif fn == 'injection':         # Don't need A^T or C^T here
            R = injection_interpolation(A, splitting, **kwargs)
            R = R.T.tocsr()
        else:
            raise ValueError('unknown interpolation method (%s)' % interpolation)
    else: 
        if restriction == 'galerkin':
            R = P.T.tobsr()
        elif fn == 'standard':
            temp_A = A.T.tobsr()
            temp_C = C.T.tocsr()
            R = standard_interpolation(temp_A, temp_C, splitting, **kwargs)
            R = R.T.tobsr()
        elif fn == 'distance_two':
            temp_A = A.T.tobsr()
            temp_C = C.T.tocsr()
            R = distance_two_interpolation(temp_A, temp_C, splitting, **kwargs)
            R = R.T.tobsr()
        elif fn == 'direct':
            temp_A = A.T.tobsr()
            temp_C = C.T.tocsr()
            R = direct_interpolation(temp_A, temp_C, splitting, **kwargs)
            R = R.T.tobsr()
        elif fn == 'one_point':         # Don't need A^T here
            temp_C = C.T.tocsr()
            R = one_point_interpolation(A, temp_C, splitting, **kwargs)
            R = R.T.tobsr()
        elif fn == 'injection':         # Don't need A^T or C^T here
            R = injection_interpolation(A, splitting, **kwargs)
            R = R.T.tobsr()
        else:
            raise ValueError('unknown interpolation method (%s)' % interpolation)
    
    levels[-1].complexity['restriction'] = kwargs['cost'][0]

    # Store relevant information for this level
    if keep:
        levels[-1].C = C                  # strength of connection matrix

    levels[-1].P = P                  # prolongation operator
    levels[-1].R = R                  # restriction operator
    levels[-1].splitting = splitting  # C/F splitting

    # Form coarse grid operator, get complexity
    levels[-1].complexity['RAP'] = mat_mat_complexity(R,A) / float(A.nnz)
    RA = R * A
    levels[-1].complexity['RAP'] += mat_mat_complexity(RA,P) / float(A.nnz)
    A = RA * P      # Galerkin operator, Ac = RAP

    # Make sure coarse-grid operator is in correct sparse format
    if (isspmatrix_csr(P) and (not isspmatrix_csr(A))):
        A = A.tocsr()
    elif (isspmatrix_bsr(P) and (not isspmatrix_bsr(A))):
        A = A.tobsr()

    # Form next level through Galerkin product
    levels.append(multilevel_solver.level())
    levels[-1].A = A
Exemple #20
0
def extend_hierarchy(levels, strength, CF, keep):
    """ helper function for local methods """
    def unpack_arg(v):
        if isinstance(v, tuple):
            return v[0], v[1]
        else:
            return v, {}

    A = levels[-1].A

    # Compute the strength-of-connection matrix C, where larger
    # C[i,j] denote stronger couplings between i and j.
    fn, kwargs = unpack_arg(strength)
    if fn == 'symmetric':
        C = symmetric_strength_of_connection(A, **kwargs)
    elif fn == 'classical':
        C = classical_strength_of_connection(A, **kwargs)
    elif fn == 'distance':
        C = distance_strength_of_connection(A, **kwargs)
    elif (fn == 'ode') or (fn == 'evolution'):
        C = evolution_strength_of_connection(A, **kwargs)
    elif fn == 'energy_based':
        C = energy_based_strength_of_connection(A, **kwargs)
    elif fn == 'algebraic_distance':
        C = algebraic_distance(A, **kwargs)
    elif fn is None:
        C = A
    else:
        raise ValueError('unrecognized strength of connection method: %s' %
                         str(fn))

    # Generate the C/F splitting
    fn, kwargs = unpack_arg(CF)
    if fn == 'RS':
        splitting = split.RS(C)
    elif fn == 'PMIS':
        splitting = split.PMIS(C)
    elif fn == 'PMISc':
        splitting = split.PMISc(C)
    elif fn == 'CLJP':
        splitting = split.CLJP(C)
    elif fn == 'CLJPc':
        splitting = split.CLJPc(C)
    else:
        raise ValueError('unknown C/F splitting method (%s)' % CF)

    # Generate the interpolation matrix that maps from the coarse-grid to the
    # fine-grid
    P = direct_interpolation(A, C, splitting)

    # Generate the restriction matrix that maps from the fine-grid to the
    # coarse-grid
    R = P.T.tocsr()

    # Store relevant information for this level
    if keep:
        levels[-1].C = C  # strength of connection matrix
        levels[-1].splitting = splitting  # C/F splitting

    levels[-1].P = P  # prolongation operator
    levels[-1].R = R  # restriction operator

    levels.append(multilevel_solver.level())

    # Form next level through Galerkin product
    A = R * A * P
    levels[-1].A = A
Exemple #21
0
def ruge_stuben_solver(A,
                       strength=('classical', {'theta': 0.25}),
                       CF='RS',
                       interpolation='direct',
                       restriction='galerkin',
                       presmoother=('gauss_seidel', {'sweep': 'symmetric'}),
                       postsmoother=('gauss_seidel', {'sweep': 'symmetric'}),
                       max_levels=10, max_coarse=10, keep=False, **kwargs):
    """Create a multilevel solver using Classical AMG (Ruge-Stuben AMG)

    Parameters
    ----------
    A : csr_matrix
        Square matrix in CSR format
    strength : ['symmetric', 'classical', 'evolution', 'distance',
                'algebraic_distance','affinity', 'energy_based', None]
        Method used to determine the strength of connection between unknowns
        of the linear system.  Method-specific parameters may be passed in
        using a tuple, e.g. strength=('symmetric',{'theta' : 0.25 }). If
        strength=None, all nonzero entries of the matrix are considered strong.
    CF : {string} : default 'RS'
        Method used for coarse grid selection (C/F splitting)
        Supported methods are RS, PMIS, PMISc, CLJP, CLJPc, and CR.
    interpolation : {string} : default 'direct'
        Method for interpolation. Options include 'direct', 'standard', 'injection',
        'one_point', and 'distance_two'.
    restriction : {string or dict} : default 'galerkin'
        'Galerkin' means set R := P^T for a Galerkin coarse-grid operator. Can also specify
        an interpolation method as above, to build the restriciton operator based on A^T. 
    presmoother : {string or dict}
        Method used for presmoothing at each level.  Method-specific parameters
        may be passed in using a tuple, e.g.
        presmoother=('gauss_seidel',{'sweep':'symmetric}), the default.
    postsmoother : {string or dict}
        Postsmoothing method with the same usage as presmoother
    max_levels: {integer} : default 10
        Maximum number of levels to be used in the multilevel solver.
    max_coarse: {integer} : default 500
        Maximum number of variables permitted on the coarse grid.
    keep: {bool} : default False
        Flag to indicate keeping extra operators in the hierarchy for
        diagnostics.  For example, if True, then strength of connection (C) and
        tentative prolongation (T) are kept.

    Returns
    -------
    ml : multilevel_solver
        Multigrid hierarchy of matrices and prolongation operators

    Other Parameters
    ----------------
    cycle_type : ['V','W','F']
        Structrure of multigrid cycle
    coarse_solver : ['splu', 'lu', 'cholesky, 'pinv', 'gauss_seidel', ... ]
        Solver used at the coarsest level of the MG hierarchy.
            Optionally, may be a tuple (fn, args), where fn is a string such as
        ['splu', 'lu', ...] or a callable function, and args is a dictionary of
        arguments to be passed to fn.
    setup_complexity : bool
        For a detailed, more accurate setup complexity, pass in 
        'setup_complexity' = True. This will slow down performance, but
        increase accuracy of complexity count. 

    Examples
    --------
    >>> from pyamg.gallery import poisson
    >>> from pyamg import ruge_stuben_solver
    >>> A = poisson((10,),format='csr')
    >>> ml = ruge_stuben_solver(A,max_coarse=3)

    Notes
    -----

    Standard interpolation is generally considered more robust than
    direct, but direct is the currently the default until our new 
    implementation of standard has been more rigorously tested.

    "coarse_solver" is an optional argument and is the solver used at the
    coarsest grid.  The default is a pseudo-inverse.  Most simply,
    coarse_solver can be one of ['splu', 'lu', 'cholesky, 'pinv',
    'gauss_seidel', ... ].  Additionally, coarse_solver may be a tuple
    (fn, args), where fn is a string such as ['splu', 'lu', ...] or a callable
    function, and args is a dictionary of arguments to be passed to fn.


    References
    ----------
    .. [1] Trottenberg, U., Oosterlee, C. W., and Schuller, A.,
       "Multigrid" San Diego: Academic Press, 2001.  Appendix A

    See Also
    --------
    aggregation.smoothed_aggregation_solver, multilevel_solver,
    aggregation.rootnode_solver

    """

    if ('setup_complexity' in kwargs):
        if kwargs['setup_complexity'] == True:
            mat_mat_complexity.__detailed__ = True
        del kwargs['setup_complexity']

    # Convert A to csr
    if not (isspmatrix_csr(A) or isspmatrix_bsr(A)):
        try:
            A = csr_matrix(A)
            warn("Implicit conversion of A to CSR", SparseEfficiencyWarning)
        except:
            raise TypeError('Argument A must have type csr_matrix, bsr_matrix, \
                             or be convertible to csr_matrix')
    
    # if isspmatrix_bsr(A):
    #     warn("Classical AMG is often more effective on CSR matrices.")

    # preprocess A
    A = A.asfptype()
    if A.shape[0] != A.shape[1]:
        raise ValueError('expected square matrix')

    levels = [multilevel_solver.level()]
    levels[-1].A = A

    while len(levels) < max_levels and levels[-1].A.shape[0] > max_coarse:
        extend_hierarchy(levels, strength, CF, interpolation, restriction, keep)

    ml = multilevel_solver(levels, **kwargs)
    change_smoothers(ml, presmoother, postsmoother)
    return ml
Exemple #22
0
def smoothed_aggregation_helmholtz_solver(A,
                                          planewaves,
                                          use_constant=(True, {
                                              'last_level': 0
                                          }),
                                          symmetry='symmetric',
                                          strength='symmetric',
                                          aggregate='standard',
                                          smooth=('energy', {
                                              'krylov': 'gmres'
                                          }),
                                          presmoother=('gauss_seidel_nr', {
                                              'sweep': 'symmetric'
                                          }),
                                          postsmoother=('gauss_seidel_nr', {
                                              'sweep': 'symmetric'
                                          }),
                                          improve_candidates='default',
                                          max_levels=10,
                                          max_coarse=100,
                                          **kwargs):
    """
    Create a multilevel solver using Smoothed Aggregation (SA) for a 2D Helmholtz operator

    Parameters
    ----------
    A : {csr_matrix, bsr_matrix}
        Sparse NxN matrix in CSR or BSR format
    planewaves : { list }
        [pw_0, pw_1, ..., pw_n], where the k-th tuple pw_k is of the form (fn,
        args).  fn is a callable and args is a dictionary of arguments for fn.
        This k-th tuple is used to define any new planewaves (i.e., new coarse
        grid basis functions) to be appended to the existing B_k at that level. 
            The function fn must return functions defined on the finest level, 
        i.e., a collection of vector(s) of length A.shape[0].  These vectors
        are then restricted to the appropriate level, where they enrich the 
        coarse space.
            Instead of a tuple, None can be used to stipulate no introduction
        of planewaves at that level.  If len(planewaves) < max_levels, the 
        last entry is used to define coarser level planewaves.
    use_constant : {tuple}
        Tuple of the form (bool, {'last_level':int}).  The boolean denotes 
        whether to introduce the constant in B at level 0.  'last_level' denotes
        the final level to use the constant in B.  That is, if 'last_level' is 1,
        then the vector in B corresponding to the constant on level 0 is dropped 
        from B at level 2.
            This is important, because using constant based interpolation beyond
        the Nyquist rate will result in poor solver performance.
    symmetry : {string}
        'symmetric' refers to both real and complex symmetric
        'hermitian' refers to both complex Hermitian and real Hermitian
        'nonsymmetric' i.e. nonsymmetric in a hermitian sense
        Note that for the strictly real case, symmetric and hermitian are the same
        Note that this flag does not denote definiteness of the operator.
    strength : ['symmetric', 'classical', 'evolution', ('predefined', {'C' : csr_matrix}), None]
        Method used to determine the strength of connection between unknowns of
        the linear system.  Method-specific parameters may be passed in using a
        tuple, e.g. strength=('symmetric',{'theta' : 0.25 }). If strength=None,
        all nonzero entries of the matrix are considered strong.  
            See notes below for varying this parameter on a per level basis.  Also,
        see notes below for using a predefined strength matrix on each level.
    aggregate : ['standard', 'lloyd', 'naive', ('predefined', {'AggOp' : csr_matrix})]
        Method used to aggregate nodes.  See notes below for varying this
        parameter on a per level basis.  Also, see notes below for using a
        predefined aggregation on each level.
    smooth : ['jacobi', 'richardson', 'energy', None]
        Method used to smooth the tentative prolongator.  Method-specific
        parameters may be passed in using a tuple, e.g.  smooth=
        ('jacobi',{'filter' : True }).  See notes below for varying this
        parameter on a per level basis.
    presmoother : {tuple, string, list} : default ('block_gauss_seidel', {'sweep':'symmetric'})
        Defines the presmoother for the multilevel cycling.  The default block
        Gauss-Seidel option defaults to point-wise Gauss-Seidel, if the matrix
        is CSR or is a BSR matrix with blocksize of 1.  See notes below for
        varying this parameter on a per level basis.
    postsmoother : {tuple, string, list}
        Same as presmoother, except defines the postsmoother.
    improve_candidates : {list} : default [('block_gauss_seidel', {'sweep':'symmetric'}), None]
        The ith entry defines the method used to improve the candidates B on
        level i.  If the list is shorter than max_levels, then the last entry
        will define the method for all levels lower.
            The list elements are relaxation descriptors of the form used for
        presmoother and postsmoother.  A value of None implies no action on B.
    max_levels : {integer} : default 10
        Maximum number of levels to be used in the multilevel solver.
    max_coarse : {integer} : default 500
        Maximum number of variables permitted on the coarse grid. 

    Other Parameters
    ----------------
    coarse_solver : ['splu','lu', ... ]
        Solver used at the coarsest level of the MG hierarchy 

    Returns
    -------
    ml : multilevel_solver
        Multigrid hierarchy of matrices and prolongation operators

    See Also
    --------
    multilevel_solver, smoothed_aggregation_solver

    Notes
    -----
    - The additional parameters are passed through as arguments to
      multilevel_solver.  Refer to pyamg.multilevel_solver for additional
      documentation.

    - The parameters smooth, strength, aggregate, presmoother, postsmoother can
      be varied on a per level basis.  For different methods on different
      levels, use a list as input so that the ith entry defines the method at
      the ith level.  If there are more levels in the hierarchy than list
      entries, the last entry will define the method for all levels lower.
      
      Examples are:
      smooth=[('jacobi', {'omega':1.0}), None, 'jacobi']
      presmoother=[('block_gauss_seidel', {'sweep':symmetric}), 'sor']
      aggregate=['standard', 'naive']
      strength=[('symmetric', {'theta':0.25}), ('symmetric',{'theta':0.08})]

    - Predefined strength of connection and aggregation schemes can be
      specified.  These options are best used together, but aggregation can be
      predefined while strength of connection is not.

      For predefined strength of connection, use a list consisting of tuples of
      the form ('predefined', {'C' : C0}), where C0 is a csr_matrix and each
      degree-of-freedom in C0 represents a supernode.  For instance to
      predefine a three-level hierarchy, use [('predefined', {'C' : C0}),
      ('predefined', {'C' : C1}) ].
      
      Similarly for predefined aggregation, use a list of tuples.  For instance
      to predefine a three-level hierarchy, use [('predefined', {'AggOp' :
      Agg0}), ('predefined', {'AggOp' : Agg1}) ], where the dimensions of A,
      Agg0 and Agg1 are compatible, i.e.  Agg0.shape[1] == A.shape[0] and
      Agg1.shape[1] == Agg0.shape[0].  Each AggOp is a csr_matrix.

    Examples
    --------
    >>> from pyamg import smoothed_aggregation_helmholtz_solver, poisson
    >>> from scipy.sparse.linalg import cg
    >>> from scipy import rand
    >>> A = poisson((100,100), format='csr')           # matrix
    >>> b = rand(A.shape[0])                           # random RHS
    >>> ml = smoothed_aggregation_solver(A)            # AMG solver
    >>> M = ml.aspreconditioner(cycle='V')             # preconditioner
    >>> x,info = cg(A, b, tol=1e-8, maxiter=30, M=M)   # solve with CG

    References
    ----------
    .. [1] L. N. Olson and J. B. Schroder. Smoothed Aggregation for Helmholtz
    Problems. Numerical Linear Algebra with Applications.  pp. 361--386.  17
    (2010).

    """
    if not (isspmatrix_csr(A) or isspmatrix_bsr(A)):
        raise TypeError('argument A must have type csr_matrix or bsr_matrix')

    A = A.asfptype()

    if (symmetry != 'symmetric') and (symmetry != 'hermitian') and (
            symmetry != 'nonsymmetric'):
        raise ValueError(
            'expected \'symmetric\', \'nonsymmetric\' or \'hermitian\' for the symmetry parameter '
        )
    A.symmetry = symmetry

    if A.shape[0] != A.shape[1]:
        raise ValueError('expected square matrix')

    ##
    # Preprocess and extend planewaves to length max_levels
    planewaves = preprocess_planewaves(planewaves, max_levels)
    # Check that the user has defined functions for B at each level
    use_const, args = unpack_arg(use_constant)
    first_planewave_level = -1
    for pw in planewaves:
        first_planewave_level += 1
        if pw is not None:
            break
    ##
    if (use_const == False) and (planewaves[0] == None):
        raise ValueError('No functions defined for B on the finest level, ' + \
              'either use_constant must be true, or planewaves must be defined for level 0')
    elif (use_const
          == True) and (args['last_level'] < first_planewave_level - 1):
        raise ValueError('Some levels have no function(s) defined for B.  ' + \
                         'Change use_constant and/or planewave arguments.')

    ##
    # Levelize the user parameters, so that they become lists describing the
    # desired user option on each level.
    max_levels, max_coarse, strength =\
        levelize_strength_or_aggregation(strength, max_levels, max_coarse)
    max_levels, max_coarse, aggregate =\
        levelize_strength_or_aggregation(aggregate, max_levels, max_coarse)
    improve_candidates = levelize_smooth_or_improve_candidates(
        improve_candidates, max_levels)
    smooth = levelize_smooth_or_improve_candidates(smooth, max_levels)

    ##
    # Start first level
    levels = []
    levels.append(multilevel_solver.level())
    levels[-1].A = A  # matrix
    levels[-1].B = numpy.zeros(
        (A.shape[0], 0))  # place-holder for near-nullspace candidates

    zeros_0 = numpy.zeros((levels[0].A.shape[0], ), dtype=A.dtype)
    while len(levels) < max_levels and levels[-1].A.shape[0] > max_coarse:
        A = levels[0].A
        A_l = levels[-1].A
        zeros_l = numpy.zeros((levels[-1].A.shape[0], ), dtype=A.dtype)

        ##
        # Generate additions to n-th level candidates
        if planewaves[len(levels) - 1] != None:
            fn, args = unpack_arg(planewaves[len(levels) - 1])
            Bcoarse2 = numpy.array(fn(**args))

            ##
            # As in alpha-SA, relax the candidates before restriction
            if improve_candidates[0] is not None:
                Bcoarse2 = relaxation_as_linear_operator(
                    improve_candidates[0], A, zeros_0) * Bcoarse2

            ##
            # Restrict Bcoarse2 to current level
            for i in range(len(levels) - 1):
                Bcoarse2 = levels[i].R * Bcoarse2
            # relax after restriction
            if improve_candidates[len(levels) - 1] is not None:
                Bcoarse2 = relaxation_as_linear_operator(
                    improve_candidates[len(levels) - 1], A_l,
                    zeros_l) * Bcoarse2
        else:
            Bcoarse2 = numpy.zeros((A_l.shape[0], 0), dtype=A.dtype)

        ##
        # Deal with the use of constant in interpolation
        use_const, args = unpack_arg(use_constant)
        if use_const and len(levels) == 1:
            # If level 0, and the constant is to be used in interpolation
            levels[0].B = numpy.hstack((numpy.ones((A.shape[0], 1),
                                                   dtype=A.dtype), Bcoarse2))
        elif use_const and args['last_level'] == len(levels) - 2:
            # If the previous level was the last level to use the constant, then remove the
            # coarse grid function based on the constant from B
            levels[-1].B = numpy.hstack((levels[-1].B[:, 1:], Bcoarse2))
        else:
            levels[-1].B = numpy.hstack((levels[-1].B, Bcoarse2))

        ##
        # Create and Append new level
        extend_hierarchy(levels,
                         strength,
                         aggregate,
                         smooth, [None for i in range(max_levels)],
                         keep=True)

    ml = multilevel_solver(levels, **kwargs)
    change_smoothers(ml, presmoother, postsmoother)
    return ml
Exemple #23
0
def extend_hierarchy(levels, strength, CF, interp, restrict, filter_operator,
                     coarse_grid_P, coarse_grid_R, keep):
    """ helper function for local methods """

    # Filter operator. Need to keep original matrix on fineest level for
    # computing residuals
    if (filter_operator is not None) and (filter_operator[1] != 0):
        if len(levels) == 1:
            A = deepcopy(levels[-1].A)
        else:
            A = levels[-1].A
        filter_matrix_rows(A,
                           filter_operator[1],
                           diagonal=True,
                           lump=filter_operator[0])
    else:
        A = levels[-1].A

    # Check if matrix was filtered to be diagonal --> coarsest grid
    if A.nnz == A.shape[0]:
        return 1

    # Zero initial complexities for strength, splitting and interpolation
    levels[-1].complexity['CF'] = 0.0
    levels[-1].complexity['strength'] = 0.0
    levels[-1].complexity['interpolate'] = 0.0

    # Compute the strength-of-connection matrix C, where larger
    # C[i,j] denote stronger couplings between i and j.
    fn, kwargs = unpack_arg(strength)
    if fn == 'symmetric':
        C = symmetric_strength_of_connection(A, **kwargs)
    elif fn == 'classical':
        C = classical_strength_of_connection(A, **kwargs)
    elif fn == 'distance':
        C = distance_strength_of_connection(A, **kwargs)
    elif (fn == 'ode') or (fn == 'evolution'):
        C = evolution_strength_of_connection(A, **kwargs)
    elif fn == 'energy_based':
        C = energy_based_strength_of_connection(A, **kwargs)
    elif fn == 'algebraic_distance':
        C = algebraic_distance(A, **kwargs)
    elif fn == 'affinity':
        C = affinity_distance(A, **kwargs)
    elif fn is None:
        C = A
    else:
        raise ValueError('unrecognized strength of connection method: %s' %
                         str(fn))
    levels[-1].complexity['strength'] += kwargs['cost'][0] * A.nnz / float(
        A.nnz)

    # Generate the C/F splitting
    fn, kwargs = unpack_arg(CF)
    if fn == 'RS':
        splitting = RS(C, **kwargs)
    elif fn == 'PMIS':
        splitting = PMIS(C, **kwargs)
    elif fn == 'PMISc':
        splitting = PMISc(C, **kwargs)
    elif fn == 'CLJP':
        splitting = CLJP(C, **kwargs)
    elif fn == 'CLJPc':
        splitting = CLJPc(C, **kwargs)
    elif fn == 'CR':
        splitting = CR(C, **kwargs)
    elif fn == 'weighted_matching':
        splitting, soc = weighted_matching(C, **kwargs)
        if soc is not None:
            C = soc
    else:
        raise ValueError('unknown C/F splitting method (%s)' % CF)
    levels[-1].complexity['CF'] += kwargs['cost'][0] * C.nnz / float(A.nnz)
    temp = np.sum(splitting)
    if (temp == len(splitting)) or (temp == 0):
        return 1

    # Generate the interpolation matrix that maps from the coarse-grid to the
    # fine-grid
    r_flag = False
    fn, kwargs = unpack_arg(interp)
    if fn == 'standard':
        P = standard_interpolation(A, C, splitting, **kwargs)
    elif fn == 'distance_two':
        P = distance_two_interpolation(A, C, splitting, **kwargs)
    elif fn == 'direct':
        P = direct_interpolation(A, C, splitting, **kwargs)
    elif fn == 'one_point':
        P = one_point_interpolation(A, C, splitting, **kwargs)
    elif fn == 'inject':
        P = injection_interpolation(A, splitting, **kwargs)
    elif fn == 'neumann':
        P = neumann_ideal_interpolation(A, splitting, **kwargs)
    elif fn == 'scaledAfc':
        P = scaled_Afc_interpolation(A, splitting, **kwargs)
    elif fn == 'air':
        if isspmatrix_bsr(A):
            temp_A = bsr_matrix(A.T)
            P = local_AIR(temp_A, splitting, **kwargs)
            P = bsr_matrix(P.T)
        else:
            temp_A = csr_matrix(A.T)
            P = local_AIR(temp_A, splitting, **kwargs)
            P = csr_matrix(P.T)
    elif fn == 'restrict':
        r_flag = True
    else:
        raise ValueError('unknown interpolation method (%s)' % interp)
    levels[-1].complexity['interpolate'] += kwargs['cost'][0] * A.nnz / float(
        A.nnz)

    # Build restriction operator
    fn, kwargs = unpack_arg(restrict)
    if fn is None:
        R = P.T
    elif fn == 'air':
        R = local_AIR(A, splitting, **kwargs)
    elif fn == 'neumann':
        R = neumann_AIR(A, splitting, **kwargs)
    elif fn == 'one_point':  # Don't need A^T here
        temp_C = C.T.tocsr()
        R = one_point_interpolation(A, temp_C, splitting, **kwargs)
        if isspmatrix_bsr(A):
            R = R.T.tobsr()
        else:
            R = R.T.tocsr()
    elif fn == 'inject':  # Don't need A^T or C^T here
        R = injection_interpolation(A, splitting, **kwargs)
        if isspmatrix_bsr(A):
            R = R.T.tobsr()
        else:
            R = R.T.tocsr()
    elif fn == 'standard':
        if isspmatrix_bsr(A):
            temp_A = A.T.tobsr()
            temp_C = C.T.tobsr()
            R = standard_interpolation(temp_A, temp_C, splitting, **kwargs)
            R = R.T.tobsr()
        else:
            temp_A = A.T.tocsr()
            temp_C = C.T.tocsr()
            R = standard_interpolation(temp_A, temp_C, splitting, **kwargs)
            R = R.T.tocsr()
    elif fn == 'distance_two':
        if isspmatrix_bsr(A):
            temp_A = A.T.tobsr()
            temp_C = C.T.tobsr()
            R = distance_two_interpolation(temp_A, temp_C, splitting, **kwargs)
            R = R.T.tobsr()
        else:
            temp_A = A.T.tocsr()
            temp_C = C.T.tocsr()
            R = distance_two_interpolation(temp_A, temp_C, splitting, **kwargs)
            R = R.T.tocsr()
    elif fn == 'direct':
        if isspmatrix_bsr(A):
            temp_A = A.T.tobsr()
            temp_C = C.T.tobsr()
            R = direct_interpolation(temp_A, temp_C, splitting, **kwargs)
            R = R.T.tobsr()
        else:
            temp_A = A.T.tocsr()
            temp_C = C.T.tocsr()
            R = direct_interpolation(temp_A, temp_C, splitting, **kwargs)
            R = R.T.tocsr()
    else:
        raise ValueError('unknown restriction method (%s)' % restrict)

    # If set P = R^T
    if r_flag:
        P = R.T

    # Optional different interpolation for RAP
    fn, kwargs = unpack_arg(coarse_grid_P)
    if fn == 'standard':
        P_temp = standard_interpolation(A, C, splitting, **kwargs)
    elif fn == 'distance_two':
        P_temp = distance_two_interpolation(A, C, splitting, **kwargs)
    elif fn == 'direct':
        P_temp = direct_interpolation(A, C, splitting, **kwargs)
    elif fn == 'one_point':
        P_temp = one_point_interpolation(A, C, splitting, **kwargs)
    elif fn == 'inject':
        P_temp = injection_interpolation(A, splitting, **kwargs)
    elif fn == 'neumann':
        P_temp = neumann_ideal_interpolation(A, splitting, **kwargs)
    elif fn == 'air':
        if isspmatrix_bsr(A):
            temp_A = bsr_matrix(A.T)
            P_temp = local_AIR(temp_A, splitting, **kwargs)
            P_temp = bsr_matrix(P_temp.T)
        else:
            temp_A = csr_matrix(A.T)
            P_temp = local_AIR(temp_A, splitting, **kwargs)
            P_temp = csr_matrix(P_temp.T)
    else:
        P_temp = P

    # Optional different restriction for RAP
    fn, kwargs = unpack_arg(coarse_grid_R)
    if fn == 'air':
        R_temp = local_AIR(A, splitting, **kwargs)
    elif fn == 'neumann':
        R_temp = neumann_AIR(A, splitting, **kwargs)
    elif fn == 'one_point':  # Don't need A^T here
        temp_C = C.T.tocsr()
        R_temp = one_point_interpolation(A, temp_C, splitting, **kwargs)
        if isspmatrix_bsr(A):
            R_temp = R_temp.T.tobsr()
        else:
            R_temp = R_temp.T.tocsr()
    elif fn == 'inject':  # Don't need A^T or C^T here
        R_temp = injection_interpolation(A, splitting, **kwargs)
        if isspmatrix_bsr(A):
            R_temp = R_temp.T.tobsr()
        else:
            R_temp = R_temp.T.tocsr()
    elif fn == 'standard':
        if isspmatrix_bsr(A):
            temp_A = A.T.tobsr()
            temp_C = C.T.tobsr()
            R_temp = standard_interpolation(temp_A, temp_C, splitting,
                                            **kwargs)
            R_temp = R_temp.T.tobsr()
        else:
            temp_A = A.T.tocsr()
            temp_C = C.T.tocsr()
            R_temp = standard_interpolation(temp_A, temp_C, splitting,
                                            **kwargs)
            R_temp = R_temp.T.tocsr()
    elif fn == 'distance_two':
        if isspmatrix_bsr(A):
            temp_A = A.T.tobsr()
            temp_C = C.T.tobsr()
            R_temp = distance_two_interpolation(temp_A, temp_C, splitting,
                                                **kwargs)
            R_temp = R_temp.T.tobsr()
        else:
            temp_A = A.T.tocsr()
            temp_C = C.T.tocsr()
            R_temp = distance_two_interpolation(temp_A, temp_C, splitting,
                                                **kwargs)
            R_temp = R_temp.T.tocsr()
    elif fn == 'direct':
        if isspmatrix_bsr(A):
            temp_A = A.T.tobsr()
            temp_C = C.T.tobsr()
            R_temp = direct_interpolation(temp_A, temp_C, splitting, **kwargs)
            R_temp = R_temp.T.tobsr()
        else:
            temp_A = A.T.tocsr()
            temp_C = C.T.tocsr()
            R_temp = direct_interpolation(temp_A, temp_C, splitting, **kwargs)
            R_temp = R_temp.T.tocsr()
    else:
        R_temp = R

    # Store relevant information for this level
    if keep:
        levels[-1].C = C  # strength of connection matrix

    levels[-1].P = P  # prolongation operator
    levels[-1].R = R  # restriction operator
    levels[-1].splitting = splitting  # C/F splitting

    # Form coarse grid operator, get complexity
    #levels[-1].complexity['RAP'] = mat_mat_complexity(R_temp,A) / float(A.nnz)
    #RA = R_temp * A
    #levels[-1].complexity['RAP'] += mat_mat_complexity(RA,P_temp) / float(A.nnz)
    #A = RA * P_temp

    # RL: RAP = R*(A*P)
    levels[-1].complexity['RAP'] = mat_mat_complexity(A, P_temp) / float(A.nnz)
    AP = A * P_temp
    levels[-1].complexity['RAP'] += mat_mat_complexity(R_temp, AP) / float(
        A.nnz)
    A = R_temp * AP

    # Make sure coarse-grid operator is in correct sparse format
    if (isspmatrix_csr(P) and (not isspmatrix_csr(A))):
        A = A.tocsr()
    elif (isspmatrix_bsr(P) and (not isspmatrix_bsr(A))):
        A = A.tobsr()

    A.eliminate_zeros()
    levels.append(multilevel_solver.level())
    levels[-1].A = A
    return 0
Exemple #24
0
def smoothed_aggregation_solver(A, B=None, BH=None,
                                symmetry='hermitian', strength='symmetric',
                                aggregate='standard',
                                smooth=('jacobi', {'omega': 4.0/3.0}),
                                presmoother=('block_gauss_seidel',
                                             {'sweep': 'symmetric'}),
                                postsmoother=('block_gauss_seidel',
                                              {'sweep': 'symmetric'}),
                                improve_candidates=[('block_gauss_seidel',
                                                    {'sweep': 'symmetric',
                                                     'iterations': 4}),
                                                    None],
                                max_levels = 10, max_coarse = 10,
                                diagonal_dominance=False,
                                keep=False, **kwargs):
    """
    Create a multilevel solver using classical-style Smoothed Aggregation (SA)

    Parameters
    ----------
    A : {csr_matrix, bsr_matrix}
        Sparse NxN matrix in CSR or BSR format
    B : {None, array_like}
        Right near-nullspace candidates stored in the columns of an NxK array.
        The default value B=None is equivalent to B=ones((N,1))
    BH : {None, array_like}
        Left near-nullspace candidates stored in the columns of an NxK array.
        BH is only used if symmetry is 'nonsymmetric'.
        The default value B=None is equivalent to BH=B.copy()
    symmetry : {string}
        'symmetric' refers to both real and complex symmetric
        'hermitian' refers to both complex Hermitian and real Hermitian
        'nonsymmetric' i.e. nonsymmetric in a hermitian sense
        Note, in the strictly real case, symmetric and hermitian are the same
        Note, this flag does not denote definiteness of the operator.
    strength : {list} : default ['symmetric', 'classical', 'evolution',
               'algebraic_distance', 'affinity',
               ('predefined', {'C' : csr_matrix}), None]
        Method used to determine the strength of connection between unknowns of
        the linear system.  Method-specific parameters may be passed in using a
        tuple, e.g. strength=('symmetric',{'theta' : 0.25 }). If strength=None,
        all nonzero entries of the matrix are considered strong.
        See notes below for varying this parameter on a per level basis.  Also,
        see notes below for using a predefined strength matrix on each level.
    aggregate : {list} : default ['standard', 'lloyd', 'naive',
                ('predefined', {'AggOp' : csr_matrix})]
        Method used to aggregate nodes.  See notes below for varying this
        parameter on a per level basis.  Also, see notes below for using a
        predefined aggregation on each level.
    smooth : {list} : default ['jacobi', 'richardson', 'energy', None]
        Method used to smooth the tentative prolongator.  Method-specific
        parameters may be passed in using a tuple, e.g.  smooth=
        ('jacobi',{'filter' : True }).  See notes below for varying this
        parameter on a per level basis.
    presmoother : {tuple, string, list} : default ('block_gauss_seidel',
                  {'sweep':'symmetric'})
        Defines the presmoother for the multilevel cycling.  The default block
        Gauss-Seidel option defaults to point-wise Gauss-Seidel, if the matrix
        is CSR or is a BSR matrix with blocksize of 1.  See notes below for
        varying this parameter on a per level basis.
    postsmoother : {tuple, string, list}
        Same as presmoother, except defines the postsmoother.
    improve_candidates : {tuple, string, list} : default
                        [('block_gauss_seidel',
                         {'sweep': 'symmetric', 'iterations': 4}), None]
        The ith entry defines the method used to improve the candidates B on
        level i.  If the list is shorter than max_levels, then the last entry
        will define the method for all levels lower.  If tuple or string, then
        this single relaxation descriptor defines improve_candidates on all
        levels.
        The list elements are relaxation descriptors of the form used for
        presmoother and postsmoother.  A value of None implies no action on B.
    max_levels : {integer} : default 10
        Maximum number of levels to be used in the multilevel solver.
    max_coarse : {integer} : default 500
        Maximum number of variables permitted on the coarse grid.
    diagonal_dominance : {bool, tuple} : default False
        If True (or the first tuple entry is True), then avoid coarsening
        diagonally dominant rows.  The second tuple entry requires a
        dictionary, where the key value 'theta' is used to tune the diagonal
        dominance threshold.
    keep : {bool} : default False
        Flag to indicate keeping extra operators in the hierarchy for
        diagnostics.  For example, if True, then strength of connection (C),
        tentative prolongation (T), and aggregation (AggOp) are kept.

    Other Parameters
    ----------------
    cycle_type : ['V','W','F']
        Structrure of multigrid cycle
    coarse_solver : ['splu', 'lu', 'cholesky, 'pinv', 'gauss_seidel', ... ]
        Solver used at the coarsest level of the MG hierarchy.
            Optionally, may be a tuple (fn, args), where fn is a string such as
        ['splu', 'lu', ...] or a callable function, and args is a dictionary of
        arguments to be passed to fn.
    setup_complexity : bool
        For a detailed, more accurate setup complexity, pass in 
        'setup_complexity' = True. This will slow down performance, but
        increase accuracy of complexity count. 

    Returns
    -------
    ml : multilevel_solver
        Multigrid hierarchy of matrices and prolongation operators

    See Also
    --------
    multilevel_solver, classical.ruge_stuben_solver,
    aggregation.smoothed_aggregation_solver

    Notes
    -----
        - This method implements classical-style SA, not root-node style SA
          (see aggregation.rootnode_solver).

        - The additional parameters are passed through as arguments to
          multilevel_solver.  Refer to pyamg.multilevel_solver for additional
          documentation.

        - At each level, four steps are executed in order to define the coarser
          level operator.

          1. Matrix A is given and used to derive a strength matrix, C.

          2. Based on the strength matrix, indices are grouped or aggregated.

          3. The aggregates define coarse nodes and a tentative prolongation
             operator T is defined by injection

          4. The tentative prolongation operator is smoothed by a relaxation
             scheme to improve the quality and extent of interpolation from the
             aggregates to fine nodes.

        - The parameters smooth, strength, aggregate, presmoother, postsmoother
          can be varied on a per level basis.  For different methods on
          different levels, use a list as input so that the i-th entry defines
          the method at the i-th level.  If there are more levels in the
          hierarchy than list entries, the last entry will define the method
          for all levels lower.

          Examples are:
          smooth=[('jacobi', {'omega':1.0}), None, 'jacobi']
          presmoother=[('block_gauss_seidel', {'sweep':symmetric}), 'sor']
          aggregate=['standard', 'naive']
          strength=[('symmetric', {'theta':0.25}), ('symmetric',
                                                    {'theta':0.08})]

        - Predefined strength of connection and aggregation schemes can be
          specified.  These options are best used together, but aggregation can
          be predefined while strength of connection is not.

          For predefined strength of connection, use a list consisting of
          tuples of the form ('predefined', {'C' : C0}), where C0 is a
          csr_matrix and each degree-of-freedom in C0 represents a supernode.
          For instance to predefine a three-level hierarchy, use
          [('predefined', {'C' : C0}), ('predefined', {'C' : C1}) ].

          Similarly for predefined aggregation, use a list of tuples.  For
          instance to predefine a three-level hierarchy, use [('predefined',
          {'AggOp' : Agg0}), ('predefined', {'AggOp' : Agg1}) ], where the
          dimensions of A, Agg0 and Agg1 are compatible, i.e.  Agg0.shape[1] ==
          A.shape[0] and Agg1.shape[1] == Agg0.shape[0].  Each AggOp is a
          csr_matrix.

    Examples
    --------
    >>> from pyamg import smoothed_aggregation_solver
    >>> from pyamg.gallery import poisson
    >>> from scipy.sparse.linalg import cg
    >>> import numpy as np
    >>> A = poisson((100,100), format='csr')           # matrix
    >>> b = np.ones((A.shape[0]))                      # RHS
    >>> ml = smoothed_aggregation_solver(A)            # AMG solver
    >>> M = ml.aspreconditioner(cycle='V')             # preconditioner
    >>> x,info = cg(A, b, tol=1e-8, maxiter=30, M=M)   # solve with CG

    References
    ----------
    .. [1] Vanek, P. and Mandel, J. and Brezina, M.,
       "Algebraic Multigrid by Smoothed Aggregation for
       Second and Fourth Order Elliptic Problems",
       Computing, vol. 56, no. 3, pp. 179--196, 1996.
       http://citeseer.ist.psu.edu/vanek96algebraic.html

    """

    if ('setup_complexity' in kwargs):
        if kwargs['setup_complexity'] == True:
            mat_mat_complexity.__detailed__ = True
        del kwargs['setup_complexity']

    if not (isspmatrix_csr(A) or isspmatrix_bsr(A)):
        try:
            A = csr_matrix(A)
            warn("Implicit conversion of A to CSR",
                 SparseEfficiencyWarning)
        except:
            raise TypeError('Argument A must have type csr_matrix or '
                            'bsr_matrix, or be convertible to csr_matrix')

    A = A.asfptype()

    if (symmetry != 'symmetric') and (symmetry != 'hermitian') and\
            (symmetry != 'nonsymmetric'):
        raise ValueError('expected \'symmetric\', \'nonsymmetric\' or '
                         'hermitian\' for the symmetry parameter ')
    A.symmetry = symmetry

    if A.shape[0] != A.shape[1]:
        raise ValueError('expected square matrix')

    # Right near nullspace candidates use constant for each variable as default
    if B is None:
        B = np.kron(np.ones((int(A.shape[0]/blocksize(A)), 1), dtype=A.dtype),
                    np.eye(blocksize(A)))
    else:
        B = np.asarray(B, dtype=A.dtype)
        if len(B.shape) == 1:
            B = B.reshape(-1, 1)
        if B.shape[0] != A.shape[0]:
            raise ValueError('The near null-space modes B have incorrect \
                              dimensions for matrix A')
        if B.shape[1] < blocksize(A):
            warn('Having less target vectors, B.shape[1], than \
                  blocksize of A can degrade convergence factors.')

    # Left near nullspace candidates
    if A.symmetry == 'nonsymmetric':
        if BH is None:
            BH = B.copy()
        else:
            BH = np.asarray(BH, dtype=A.dtype)
            if len(BH.shape) == 1:
                BH = BH.reshape(-1, 1)
            if BH.shape[1] != B.shape[1]:
                raise ValueError('The number of left and right near \
                                  null-space modes B and BH, must be equal')
            if BH.shape[0] != A.shape[0]:
                raise ValueError('The near null-space modes BH have \
                                  incorrect dimensions for matrix A')

    # Levelize the user parameters, so that they become lists describing the
    # desired user option on each level.
    max_levels, max_coarse, strength =\
        levelize_strength_or_aggregation(strength, max_levels, max_coarse)
    max_levels, max_coarse, aggregate =\
        levelize_strength_or_aggregation(aggregate, max_levels, max_coarse)
    improve_candidates =\
        levelize_smooth_or_improve_candidates(improve_candidates, max_levels)
    smooth = levelize_smooth_or_improve_candidates(smooth, max_levels)

    # Construct multilevel structure
    levels = []
    levels.append(multilevel_solver.level())
    levels[-1].A = A          # matrix

    # Append near nullspace candidates
    levels[-1].B = B          # right candidates
    if A.symmetry == 'nonsymmetric':
        levels[-1].BH = BH    # left candidates

    while len(levels) < max_levels and\
            int(levels[-1].A.shape[0]/blocksize(levels[-1].A)) > max_coarse:
        extend_hierarchy(levels, strength, aggregate, smooth,
                         improve_candidates, diagonal_dominance, keep)

    # Construct and return multilevel hierarchy
    ml = multilevel_solver(levels, **kwargs)
    change_smoothers(ml, presmoother, postsmoother)
    return ml
Exemple #25
0
def AIR_solver(A,
               strength=('classical', {
                   'theta': 0.3,
                   'norm': 'min'
               }),
               CF='RS',
               interp='one_point',
               restrict='neumann',
               presmoother=None,
               postsmoother=('FC_jacobi', {
                   'omega': 1.0,
                   'iterations': 1,
                   'withrho': False,
                   'F_iterations': 2,
                   'C_iterations': 0
               }),
               filter_operator=None,
               coarse_grid_P=None,
               coarse_grid_R=None,
               max_levels=20,
               max_coarse=20,
               keep=False,
               **kwargs):
    """Create a multilevel solver using Classical AMG (Ruge-Stuben AMG)

    Parameters
    ----------
    A : csr_matrix
        Square nonsymmetric matrix in CSR format
    strength : ['symmetric', 'classical', 'evolution', 'distance',
                'algebraic_distance','affinity', 'energy_based', None]
        Method used to determine the strength of connection between unknowns
        of the linear system.  Method-specific parameters may be passed in
        using a tuple, e.g. strength=('symmetric',{'theta' : 0.25 }). If
        strength=None, all nonzero entries of the matrix are considered strong.
    CF : {string} : default 'RS'
        Method used for coarse grid selection (C/F splitting)
        Supported methods are RS, PMIS, PMISc, CLJP, CLJPc, and CR.
    interp : {string} : default 'one-point'
        Options include 'direct', 'standard', 'inject' and 'one-point'.
    restrict : {string} : default 'neumann'
        Options include 'air' for approximate ideal
        restriction.
    presmoother : {string or dict} : default None
        Method used for presmoothing at each level.  Method-specific parameters
        may be passed in using a tuple, e.g.
        presmoother=('gauss_seidel',{'sweep':'symmetric}), the default.
    postsmoother : {string or dict} : default F-Jacobi
        Postsmoothing method with the same usage as presmoother
    filter_operator : (bool, tol) : default None
        Remove small entries in operators on each level if True. Entries are
        considered "small" if |a_ij| < tol |a_ii|.
    coarse_grid_P : {string} : default None
        Option to specify a different construction of P used in computing RAP
        vs. for interpolation in an actual solve.
    max_levels: {integer} : default 20
        Maximum number of levels to be used in the multilevel solver.
    max_coarse: {integer} : default 20
        Maximum number of variables permitted on the coarse grid.
    keep: {bool} : default False
        Flag to indicate keeping extra operators in the hierarchy for
        diagnostics.  For example, if True, then strength of connection (C) and
        tentative prolongation (T) are kept.

    Returns
    -------
    ml : multilevel_solver
        Multigrid hierarchy of matrices and prolongation operators

    Other Parameters
    ----------------
    coarse_solver : ['splu', 'lu', 'cholesky, 'pinv', 'gauss_seidel', ... ]
        Solver used at the coarsest level of the MG hierarchy.
            Optionally, may be a tuple (fn, args), where fn is a string such as
        ['splu', 'lu', ...] or a callable function, and args is a dictionary of
        arguments to be passed to fn.
    setup_complexity : bool
        For a detailed, more accurate setup complexity, pass in 
        'setup_complexity' = True. This will slow down performance, but
        increase accuracy of complexity count. 

    Notes
    -----




    References
    ----------
    .. [1] 

    See Also
    --------
    aggregation.smoothed_aggregation_solver, multilevel_solver,
    aggregation.rootnode_solver

    """

    if ('setup_complexity' in kwargs):
        if kwargs['setup_complexity'] == True:
            mat_mat_complexity.__detailed__ = True
        del kwargs['setup_complexity']

    # preprocess A
    A = A.asfptype()
    if A.shape[0] != A.shape[1]:
        raise ValueError('expected square matrix')

    levels = [multilevel_solver.level()]
    levels[-1].A = A

    while len(levels) < max_levels and levels[-1].A.shape[0] > max_coarse:
        bottom = extend_hierarchy(levels, strength, CF, interp, restrict,
                                  filter_operator, coarse_grid_P,
                                  coarse_grid_R, keep)
        if bottom:
            break

    ml = multilevel_solver(levels, **kwargs)
    change_smoothers(ml, presmoother, postsmoother)
    return ml
def smoothed_aggregation_helmholtz_solver(A, planewaves, use_constant=(True, {'last_level':0}), 
        symmetry='symmetric', strength='symmetric', aggregate='standard',
        smooth=('energy', {'krylov': 'gmres'}),
        presmoother=('gauss_seidel_nr',{'sweep':'symmetric'}),
        postsmoother=('gauss_seidel_nr',{'sweep':'symmetric'}),
        improve_candidates='default', max_levels = 10, max_coarse = 100, **kwargs):
    
    """
    Create a multilevel solver using Smoothed Aggregation (SA) for a 2D Helmholtz operator

    Parameters
    ----------
    A : {csr_matrix, bsr_matrix}
        Sparse NxN matrix in CSR or BSR format
    planewaves : { list }
        [pw_0, pw_1, ..., pw_n], where the k-th tuple pw_k is of the form (fn,
        args).  fn is a callable and args is a dictionary of arguments for fn.
        This k-th tuple is used to define any new planewaves (i.e., new coarse
        grid basis functions) to be appended to the existing B_k at that level. 
            The function fn must return functions defined on the finest level, 
        i.e., a collection of vector(s) of length A.shape[0].  These vectors
        are then restricted to the appropriate level, where they enrich the 
        coarse space.
            Instead of a tuple, None can be used to stipulate no introduction
        of planewaves at that level.  If len(planewaves) < max_levels, the 
        last entry is used to define coarser level planewaves.
    use_constant : {tuple}
        Tuple of the form (bool, {'last_level':int}).  The boolean denotes 
        whether to introduce the constant in B at level 0.  'last_level' denotes
        the final level to use the constant in B.  That is, if 'last_level' is 1,
        then the vector in B corresponding to the constant on level 0 is dropped 
        from B at level 2.
            This is important, because using constant based interpolation beyond
        the Nyquist rate will result in poor solver performance.
    symmetry : {string}
        'symmetric' refers to both real and complex symmetric
        'hermitian' refers to both complex Hermitian and real Hermitian
        'nonsymmetric' i.e. nonsymmetric in a hermitian sense
        Note that for the strictly real case, symmetric and hermitian are the same
        Note that this flag does not denote definiteness of the operator.
    strength : ['symmetric', 'classical', 'evolution', ('predefined', {'C' : csr_matrix}), None]
        Method used to determine the strength of connection between unknowns of
        the linear system.  Method-specific parameters may be passed in using a
        tuple, e.g. strength=('symmetric',{'theta' : 0.25 }). If strength=None,
        all nonzero entries of the matrix are considered strong.  
            See notes below for varying this parameter on a per level basis.  Also,
        see notes below for using a predefined strength matrix on each level.
    aggregate : ['standard', 'lloyd', 'naive', ('predefined', {'AggOp' : csr_matrix})]
        Method used to aggregate nodes.  See notes below for varying this
        parameter on a per level basis.  Also, see notes below for using a
        predefined aggregation on each level.
    smooth : ['jacobi', 'richardson', 'energy', None]
        Method used to smooth the tentative prolongator.  Method-specific
        parameters may be passed in using a tuple, e.g.  smooth=
        ('jacobi',{'filter' : True }).  See notes below for varying this
        parameter on a per level basis.
    presmoother : {tuple, string, list} : default ('block_gauss_seidel', {'sweep':'symmetric'})
        Defines the presmoother for the multilevel cycling.  The default block
        Gauss-Seidel option defaults to point-wise Gauss-Seidel, if the matrix
        is CSR or is a BSR matrix with blocksize of 1.  See notes below for
        varying this parameter on a per level basis.
    postsmoother : {tuple, string, list}
        Same as presmoother, except defines the postsmoother.
    improve_candidates : {list} : default [('block_gauss_seidel', {'sweep':'symmetric'}), None]
        The ith entry defines the method used to improve the candidates B on
        level i.  If the list is shorter than max_levels, then the last entry
        will define the method for all levels lower.
            The list elements are relaxation descriptors of the form used for
        presmoother and postsmoother.  A value of None implies no action on B.
    max_levels : {integer} : default 10
        Maximum number of levels to be used in the multilevel solver.
    max_coarse : {integer} : default 500
        Maximum number of variables permitted on the coarse grid. 

    Other Parameters
    ----------------
    coarse_solver : ['splu','lu', ... ]
        Solver used at the coarsest level of the MG hierarchy 

    Returns
    -------
    ml : multilevel_solver
        Multigrid hierarchy of matrices and prolongation operators

    See Also
    --------
    multilevel_solver, smoothed_aggregation_solver

    Notes
    -----
    - The additional parameters are passed through as arguments to
      multilevel_solver.  Refer to pyamg.multilevel_solver for additional
      documentation.

    - The parameters smooth, strength, aggregate, presmoother, postsmoother can
      be varied on a per level basis.  For different methods on different
      levels, use a list as input so that the ith entry defines the method at
      the ith level.  If there are more levels in the hierarchy than list
      entries, the last entry will define the method for all levels lower.
      
      Examples are:
      smooth=[('jacobi', {'omega':1.0}), None, 'jacobi']
      presmoother=[('block_gauss_seidel', {'sweep':symmetric}), 'sor']
      aggregate=['standard', 'naive']
      strength=[('symmetric', {'theta':0.25}), ('symmetric',{'theta':0.08})]

    - Predefined strength of connection and aggregation schemes can be
      specified.  These options are best used together, but aggregation can be
      predefined while strength of connection is not.

      For predefined strength of connection, use a list consisting of tuples of
      the form ('predefined', {'C' : C0}), where C0 is a csr_matrix and each
      degree-of-freedom in C0 represents a supernode.  For instance to
      predefine a three-level hierarchy, use [('predefined', {'C' : C0}),
      ('predefined', {'C' : C1}) ].
      
      Similarly for predefined aggregation, use a list of tuples.  For instance
      to predefine a three-level hierarchy, use [('predefined', {'AggOp' :
      Agg0}), ('predefined', {'AggOp' : Agg1}) ], where the dimensions of A,
      Agg0 and Agg1 are compatible, i.e.  Agg0.shape[1] == A.shape[0] and
      Agg1.shape[1] == Agg0.shape[0].  Each AggOp is a csr_matrix.

    Examples
    --------
    >>> from pyamg import smoothed_aggregation_helmholtz_solver, poisson
    >>> from scipy.sparse.linalg import cg
    >>> from scipy import rand
    >>> A = poisson((100,100), format='csr')           # matrix
    >>> b = rand(A.shape[0])                           # random RHS
    >>> ml = smoothed_aggregation_solver(A)            # AMG solver
    >>> M = ml.aspreconditioner(cycle='V')             # preconditioner
    >>> x,info = cg(A, b, tol=1e-8, maxiter=30, M=M)   # solve with CG

    References
    ----------
    .. [1] L. N. Olson and J. B. Schroder. Smoothed Aggregation for Helmholtz
    Problems. Numerical Linear Algebra with Applications.  pp. 361--386.  17
    (2010).

    """
    if not (isspmatrix_csr(A) or isspmatrix_bsr(A)):
        raise TypeError('argument A must have type csr_matrix or bsr_matrix')

    A = A.asfptype()
    
    if (symmetry != 'symmetric') and (symmetry != 'hermitian') and (symmetry != 'nonsymmetric'):
        raise ValueError('expected \'symmetric\', \'nonsymmetric\' or \'hermitian\' for the symmetry parameter ')
    A.symmetry = symmetry

    if A.shape[0] != A.shape[1]:
        raise ValueError('expected square matrix')
    
    ##
    # Preprocess and extend planewaves to length max_levels
    planewaves = preprocess_planewaves(planewaves, max_levels)
    # Check that the user has defined functions for B at each level
    use_const, args = unpack_arg(use_constant)
    first_planewave_level = -1
    for pw in planewaves:
        first_planewave_level += 1
        if pw is not None:
            break
    ##    
    if (use_const == False) and (planewaves[0] == None):
        raise ValueError('No functions defined for B on the finest level, ' + \
              'either use_constant must be true, or planewaves must be defined for level 0')
    elif (use_const == True) and (args['last_level'] < first_planewave_level-1):
        raise ValueError('Some levels have no function(s) defined for B.  ' + \
                         'Change use_constant and/or planewave arguments.')
        
    ##
    # Levelize the user parameters, so that they become lists describing the
    # desired user option on each level.
    max_levels, max_coarse, strength =\
        levelize_strength_or_aggregation(strength, max_levels, max_coarse)
    max_levels, max_coarse, aggregate =\
        levelize_strength_or_aggregation(aggregate, max_levels, max_coarse)
    improve_candidates = levelize_smooth_or_improve_candidates(improve_candidates, max_levels)
    smooth = levelize_smooth_or_improve_candidates(smooth, max_levels)


    ##
    # Start first level
    levels = []
    levels.append( multilevel_solver.level() )
    levels[-1].A = A                            # matrix
    levels[-1].B = numpy.zeros((A.shape[0],0))  # place-holder for near-nullspace candidates

    zeros_0 = numpy.zeros((levels[0].A.shape[0],), dtype=A.dtype)
    while len(levels) < max_levels and levels[-1].A.shape[0] > max_coarse:
        A = levels[0].A
        A_l = levels[-1].A
        zeros_l = numpy.zeros((levels[-1].A.shape[0],), dtype=A.dtype)

        ##
        # Generate additions to n-th level candidates
        if planewaves[len(levels)-1] != None:
            fn, args = unpack_arg(planewaves[len(levels)-1])
            Bcoarse2 = numpy.array(fn(**args))

            ##
            # As in alpha-SA, relax the candidates before restriction
            if improve_candidates[0] is not None:
                Bcoarse2 = relaxation_as_linear_operator(improve_candidates[0], A, zeros_0)*Bcoarse2
            
            ##
            # Restrict Bcoarse2 to current level
            for i in range(len(levels)-1):
                Bcoarse2 = levels[i].R*Bcoarse2
            # relax after restriction
            if improve_candidates[len(levels)-1] is not None:
                Bcoarse2 =relaxation_as_linear_operator(improve_candidates[len(levels)-1],A_l,zeros_l)*Bcoarse2
        else:
            Bcoarse2 = numpy.zeros((A_l.shape[0],0),dtype=A.dtype)

        ##
        # Deal with the use of constant in interpolation
        use_const, args = unpack_arg(use_constant)
        if use_const and len(levels) == 1:
            # If level 0, and the constant is to be used in interpolation
           levels[0].B = numpy.hstack( (numpy.ones((A.shape[0],1), dtype=A.dtype), Bcoarse2) )
        elif use_const and args['last_level'] == len(levels)-2: 
            # If the previous level was the last level to use the constant, then remove the
            # coarse grid function based on the constant from B
            levels[-1].B = numpy.hstack( (levels[-1].B[:,1:], Bcoarse2) )
        else:
            levels[-1].B = numpy.hstack((levels[-1].B, Bcoarse2))
        
        ##
        # Create and Append new level
        extend_hierarchy(levels, strength, aggregate, smooth, [None for i in range(max_levels)] ,keep=True)
    
    ml = multilevel_solver(levels, **kwargs)
    change_smoothers(ml, presmoother, postsmoother)
    return ml
Exemple #27
0
def extend_hierarchy(levels, strength, CF, interp, restrict, filter_operator,
                     coarse_grid_P, coarse_grid_R, keep):
    """ helper function for local methods """

    # Filter operator. Need to keep original matrix on fineest level for
    # computing residuals
    if (filter_operator is not None) and (filter_operator[1] != 0): 
        if len(levels) == 1:
            A = deepcopy(levels[-1].A)
        else:
            A = levels[-1].A
        filter_matrix_rows(A, filter_operator[1], diagonal=True, lump=filter_operator[0])
    else:
        A = levels[-1].A

    # Check if matrix was filtered to be diagonal --> coarsest grid
    if A.nnz == A.shape[0]:
        return 1

    # Zero initial complexities for strength, splitting and interpolation
    levels[-1].complexity['CF'] = 0.0
    levels[-1].complexity['strength'] = 0.0
    levels[-1].complexity['interpolate'] = 0.0

    # Compute the strength-of-connection matrix C, where larger
    # C[i,j] denote stronger couplings between i and j.
    fn, kwargs = unpack_arg(strength)
    if fn == 'symmetric':
        C = symmetric_strength_of_connection(A, **kwargs)
    elif fn == 'classical':
        C = classical_strength_of_connection(A, **kwargs)
    elif fn == 'distance':
        C = distance_strength_of_connection(A, **kwargs)
    elif (fn == 'ode') or (fn == 'evolution'):
        C = evolution_strength_of_connection(A, **kwargs)
    elif fn == 'energy_based':
        C = energy_based_strength_of_connection(A, **kwargs)
    elif fn == 'algebraic_distance':
        C = algebraic_distance(A, **kwargs)
    elif fn == 'affinity':
        C = affinity_distance(A, **kwargs)
    elif fn is None:
        C = A
    else:
        raise ValueError('unrecognized strength of connection method: %s' %
                         str(fn))
    levels[-1].complexity['strength'] += kwargs['cost'][0] * A.nnz / float(A.nnz)

    # Generate the C/F splitting
    fn, kwargs = unpack_arg(CF)
    if fn == 'RS':
        splitting = RS(C, **kwargs)
    elif fn == 'PMIS':
        splitting = PMIS(C, **kwargs)
    elif fn == 'PMISc':
        splitting = PMISc(C, **kwargs)
    elif fn == 'CLJP':
        splitting = CLJP(C, **kwargs)
    elif fn == 'CLJPc':
        splitting = CLJPc(C, **kwargs)
    elif fn == 'CR':
        splitting = CR(C, **kwargs)
    elif fn == 'weighted_matching':
        splitting, soc = weighted_matching(C, **kwargs)
        if soc is not None:
            C = soc
    else:
        raise ValueError('unknown C/F splitting method (%s)' % CF)
    levels[-1].complexity['CF'] += kwargs['cost'][0] * C.nnz / float(A.nnz)
    temp = np.sum(splitting)
    if (temp == len(splitting)) or (temp == 0):
        return 1

    # Generate the interpolation matrix that maps from the coarse-grid to the
    # fine-grid
    r_flag = False
    fn, kwargs = unpack_arg(interp)
    if fn == 'standard':
        P = standard_interpolation(A, C, splitting, **kwargs)
    elif fn == 'distance_two':
        P = distance_two_interpolation(A, C, splitting, **kwargs)
    elif fn == 'direct':
        P = direct_interpolation(A, C, splitting, **kwargs)
    elif fn == 'one_point':
        P = one_point_interpolation(A, C, splitting, **kwargs)
    elif fn == 'inject':
        P = injection_interpolation(A, splitting, **kwargs)
    elif fn == 'neumann':
        P = neumann_ideal_interpolation(A, splitting, **kwargs)
    elif fn == 'scaledAfc':
        P = scaled_Afc_interpolation(A, splitting, **kwargs)
    elif fn == 'air':
        if isspmatrix_bsr(A):
            temp_A = bsr_matrix(A.T)
            P = local_AIR(temp_A, splitting, **kwargs)
            P = bsr_matrix(P.T)
        else:
            temp_A = csr_matrix(A.T)
            P = local_AIR(temp_A, splitting, **kwargs)
            P = csr_matrix(P.T)
    elif fn == 'restrict':
        r_flag = True
    else:
        raise ValueError('unknown interpolation method (%s)' % interp)
    levels[-1].complexity['interpolate'] += kwargs['cost'][0] * A.nnz / float(A.nnz)

    # Build restriction operator
    fn, kwargs = unpack_arg(restrict)
    if fn is None:
        R = P.T
    elif fn == 'air':
        R = local_AIR(A, splitting, **kwargs)
    elif fn == 'neumann':
        R = neumann_AIR(A, splitting, **kwargs)
    elif fn == 'one_point':         # Don't need A^T here
        temp_C = C.T.tocsr()
        R = one_point_interpolation(A, temp_C, splitting, **kwargs)
        if isspmatrix_bsr(A):
            R = R.T.tobsr()
        else:
            R = R.T.tocsr()
    elif fn == 'inject':            # Don't need A^T or C^T here
        R = injection_interpolation(A, splitting, **kwargs)
        if isspmatrix_bsr(A):
            R = R.T.tobsr()
        else:
            R = R.T.tocsr()
    elif fn == 'standard':
        if isspmatrix_bsr(A):
            temp_A = A.T.tobsr()
            temp_C = C.T.tobsr()
            R = standard_interpolation(temp_A, temp_C, splitting, **kwargs)
            R = R.T.tobsr()
        else: 
            temp_A = A.T.tocsr()
            temp_C = C.T.tocsr()
            R = standard_interpolation(temp_A, temp_C, splitting, **kwargs)
            R = R.T.tocsr()
    elif fn == 'distance_two':
        if isspmatrix_bsr(A):
            temp_A = A.T.tobsr()
            temp_C = C.T.tobsr()
            R = distance_two_interpolation(temp_A, temp_C, splitting, **kwargs)
            R = R.T.tobsr()
        else: 
            temp_A = A.T.tocsr()
            temp_C = C.T.tocsr()
            R = distance_two_interpolation(temp_A, temp_C, splitting, **kwargs)
            R = R.T.tocsr()
    elif fn == 'direct':
        if isspmatrix_bsr(A):
            temp_A = A.T.tobsr()
            temp_C = C.T.tobsr()
            R = direct_interpolation(temp_A, temp_C, splitting, **kwargs)
            R = R.T.tobsr()        
        else:
            temp_A = A.T.tocsr()
            temp_C = C.T.tocsr()
            R = direct_interpolation(temp_A, temp_C, splitting, **kwargs)
            R = R.T.tocsr()
    else:
        raise ValueError('unknown restriction method (%s)' % restrict)

    # If set P = R^T
    if r_flag:
        P = R.T

    # Optional different interpolation for RAP
    fn, kwargs = unpack_arg(coarse_grid_P)
    if fn == 'standard':
        P_temp = standard_interpolation(A, C, splitting, **kwargs)
    elif fn == 'distance_two':
        P_temp = distance_two_interpolation(A, C, splitting, **kwargs)
    elif fn == 'direct':
        P_temp = direct_interpolation(A, C, splitting, **kwargs)
    elif fn == 'one_point':
        P_temp = one_point_interpolation(A, C, splitting, **kwargs)
    elif fn == 'inject':
        P_temp = injection_interpolation(A, splitting, **kwargs)
    elif fn == 'neumann':
        P_temp = neumann_ideal_interpolation(A, splitting, **kwargs)
    elif fn == 'air':
        if isspmatrix_bsr(A): 
            temp_A = bsr_matrix(A.T)
            P_temp = local_AIR(temp_A, splitting, **kwargs)
            P_temp = bsr_matrix(P_temp.T)
        else:
            temp_A = csr_matrix(A.T)
            P_temp = local_AIR(temp_A, splitting, **kwargs)
            P_temp = csr_matrix(P_temp.T)
    else:
        P_temp = P

    # Optional different restriction for RAP
    fn, kwargs = unpack_arg(coarse_grid_R)
    if fn == 'air':
        R_temp = local_AIR(A, splitting, **kwargs)
    elif fn == 'neumann':
        R_temp = neumann_AIR(A, splitting, **kwargs)
    elif fn == 'one_point':         # Don't need A^T here
        temp_C = C.T.tocsr()
        R_temp = one_point_interpolation(A, temp_C, splitting, **kwargs)
        if isspmatrix_bsr(A):
            R_temp = R_temp.T.tobsr()
        else:
            R_temp = R_temp.T.tocsr()
    elif fn == 'inject':            # Don't need A^T or C^T here
        R_temp = injection_interpolation(A, splitting, **kwargs)
        if isspmatrix_bsr(A):
            R_temp = R_temp.T.tobsr()
        else:
            R_temp = R_temp.T.tocsr()
    elif fn == 'standard':
        if isspmatrix_bsr(A):
            temp_A = A.T.tobsr()
            temp_C = C.T.tobsr()
            R_temp = standard_interpolation(temp_A, temp_C, splitting, **kwargs)
            R_temp = R_temp.T.tobsr()
        else: 
            temp_A = A.T.tocsr()
            temp_C = C.T.tocsr()
            R_temp = standard_interpolation(temp_A, temp_C, splitting, **kwargs)
            R_temp = R_temp.T.tocsr()
    elif fn == 'distance_two':
        if isspmatrix_bsr(A):
            temp_A = A.T.tobsr()
            temp_C = C.T.tobsr()
            R_temp = distance_two_interpolation(temp_A, temp_C, splitting, **kwargs)
            R_temp = R_temp.T.tobsr()
        else: 
            temp_A = A.T.tocsr()
            temp_C = C.T.tocsr()
            R_temp = distance_two_interpolation(temp_A, temp_C, splitting, **kwargs)
            R_temp = R_temp.T.tocsr()
    elif fn == 'direct':
        if isspmatrix_bsr(A):
            temp_A = A.T.tobsr()
            temp_C = C.T.tobsr()
            R_temp = direct_interpolation(temp_A, temp_C, splitting, **kwargs)
            R_temp = R_temp.T.tobsr()        
        else:
            temp_A = A.T.tocsr()
            temp_C = C.T.tocsr()
            R_temp = direct_interpolation(temp_A, temp_C, splitting, **kwargs)
            R_temp = R_temp.T.tocsr()
    else:
        R_temp = R

    # Store relevant information for this level
    if keep:
        levels[-1].C = C              # strength of connection matrix

    levels[-1].P = P                  # prolongation operator
    levels[-1].R = R                  # restriction operator
    levels[-1].splitting = splitting  # C/F splitting

    # Form coarse grid operator, get complexity
    #levels[-1].complexity['RAP'] = mat_mat_complexity(R_temp,A) / float(A.nnz)
    #RA = R_temp * A
    #levels[-1].complexity['RAP'] += mat_mat_complexity(RA,P_temp) / float(A.nnz)
    #A = RA * P_temp
    
    # RL: RAP = R*(A*P)
    levels[-1].complexity['RAP'] = mat_mat_complexity(A, P_temp) / float(A.nnz)
    AP = A * P_temp
    levels[-1].complexity['RAP'] += mat_mat_complexity(R_temp, AP) / float(A.nnz)
    A = R_temp * AP
    

    # Make sure coarse-grid operator is in correct sparse format
    if (isspmatrix_csr(P) and (not isspmatrix_csr(A))):
        A = A.tocsr()
    elif (isspmatrix_bsr(P) and (not isspmatrix_bsr(A))):
        A = A.tobsr()

    A.eliminate_zeros()
    levels.append(multilevel_solver.level())
    levels[-1].A = A
    return 0
Exemple #28
0
def extend_hierarchy(levels,
                     strength,
                     aggregate,
                     smooth,
                     improve_candidates,
                     diagonal_dominance=False,
                     keep=True):
    """Extend the multigrid hierarchy.

    Service routine to implement the strength of connection, aggregation,
    tentative prolongation construction, and prolongation smoothing.  Called by
    smoothed_aggregation_solver.

    """
    def unpack_arg(v):
        if isinstance(v, tuple):
            return v[0], v[1]
        else:
            return v, {}

    A = levels[-1].A
    B = levels[-1].B
    if A.symmetry == "nonsymmetric":
        AH = A.H.asformat(A.format)
        BH = levels[-1].BH

    # Compute the strength-of-connection matrix C, where larger
    # C[i,j] denote stronger couplings between i and j.
    fn, kwargs = unpack_arg(strength[len(levels) - 1])
    if fn == 'symmetric':
        C = symmetric_strength_of_connection(A, **kwargs)
    elif fn == 'classical':
        C = classical_strength_of_connection(A, **kwargs)
    elif fn == 'distance':
        C = distance_strength_of_connection(A, **kwargs)
    elif (fn == 'ode') or (fn == 'evolution'):
        if 'B' in kwargs:
            C = evolution_strength_of_connection(A, **kwargs)
        else:
            C = evolution_strength_of_connection(A, B, **kwargs)
    elif fn == 'energy_based':
        C = energy_based_strength_of_connection(A, **kwargs)
    elif fn == 'predefined':
        C = kwargs['C'].tocsr()
    elif fn == 'algebraic_distance':
        C = algebraic_distance(A, **kwargs)
    elif fn == 'affinity':
        C = affinity_distance(A, **kwargs)
    elif fn is None:
        C = A.tocsr()
    else:
        raise ValueError('unrecognized strength of connection method: %s' %
                         str(fn))

    # Avoid coarsening diagonally dominant rows
    flag, kwargs = unpack_arg(diagonal_dominance)
    if flag:
        C = eliminate_diag_dom_nodes(A, C, **kwargs)

    # Compute the aggregation matrix AggOp (i.e., the nodal coarsening of A).
    # AggOp is a boolean matrix, where the sparsity pattern for the k-th column
    # denotes the fine-grid nodes agglomerated into k-th coarse-grid node.
    fn, kwargs = unpack_arg(aggregate[len(levels) - 1])
    if fn == 'standard':
        AggOp = standard_aggregation(C, **kwargs)[0]
    elif fn == 'naive':
        AggOp = naive_aggregation(C, **kwargs)[0]
    elif fn == 'lloyd':
        AggOp = lloyd_aggregation(C, **kwargs)[0]
    elif fn == 'predefined':
        AggOp = kwargs['AggOp'].tocsr()
    else:
        raise ValueError('unrecognized aggregation method %s' % str(fn))

    # Improve near nullspace candidates by relaxing on A B = 0
    fn, kwargs = unpack_arg(improve_candidates[len(levels) - 1])
    if fn is not None:
        b = np.zeros((A.shape[0], 1), dtype=A.dtype)
        B = relaxation_as_linear_operator((fn, kwargs), A, b) * B
        levels[-1].B = B
        if A.symmetry == "nonsymmetric":
            BH = relaxation_as_linear_operator((fn, kwargs), AH, b) * BH
            levels[-1].BH = BH

    # Compute the tentative prolongator, T, which is a tentative interpolation
    # matrix from the coarse-grid to the fine-grid.  T exactly interpolates
    # B_fine = T B_coarse.
    T, B = fit_candidates(AggOp, B)
    if A.symmetry == "nonsymmetric":
        TH, BH = fit_candidates(AggOp, BH)

    # Smooth the tentative prolongator, so that it's accuracy is greatly
    # improved for algebraically smooth error.
    fn, kwargs = unpack_arg(smooth[len(levels) - 1])
    if fn == 'jacobi':
        P = jacobi_prolongation_smoother(A, T, C, B, **kwargs)
    elif fn == 'richardson':
        P = richardson_prolongation_smoother(A, T, **kwargs)
    elif fn == 'energy':
        P = energy_prolongation_smoother(A, T, C, B, None, (False, {}),
                                         **kwargs)
    elif fn is None:
        P = T
    else:
        raise ValueError('unrecognized prolongation smoother method %s' %
                         str(fn))

    # Compute the restriction matrix, R, which interpolates from the fine-grid
    # to the coarse-grid.  If A is nonsymmetric, then R must be constructed
    # based on A.H.  Otherwise R = P.H or P.T.
    symmetry = A.symmetry
    if symmetry == 'hermitian':
        R = P.H
    elif symmetry == 'symmetric':
        R = P.T
    elif symmetry == 'nonsymmetric':
        fn, kwargs = unpack_arg(smooth[len(levels) - 1])
        if fn == 'jacobi':
            R = jacobi_prolongation_smoother(AH, TH, C, BH, **kwargs).H
        elif fn == 'richardson':
            R = richardson_prolongation_smoother(AH, TH, **kwargs).H
        elif fn == 'energy':
            R = energy_prolongation_smoother(AH, TH, C, BH, None, (False, {}),
                                             **kwargs)
            R = R.H
        elif fn is None:
            R = T.H
        else:
            raise ValueError('unrecognized prolongation smoother method %s' %
                             str(fn))

    if keep:
        levels[-1].C = C  # strength of connection matrix
        levels[-1].AggOp = AggOp  # aggregation operator
        levels[-1].T = T  # tentative prolongator

    levels[-1].P = P  # smoothed prolongator
    levels[-1].R = R  # restriction operator

    levels.append(multilevel_solver.level())
    A = R * A * P  # Galerkin operator
    A.symmetry = symmetry
    levels[-1].A = A
    levels[-1].B = B  # right near nullspace candidates

    if A.symmetry == "nonsymmetric":
        levels[-1].BH = BH  # left near nullspace candidates
Exemple #29
0
def AIR_solver(A,
               strength=('classical', {'theta': 0.3 ,'norm': 'min'}),
               CF='RS',
               interp='one_point',
               restrict='neumann',
               presmoother=None,
               postsmoother=('FC_jacobi', {'omega': 1.0, 'iterations': 1,
                              'withrho': False,  'F_iterations': 2,
                              'C_iterations': 0} ),
               filter_operator=None,
               coarse_grid_P=None, 
               coarse_grid_R=None, 
               max_levels=20, max_coarse=20,
               keep=False, **kwargs):
    """Create a multilevel solver using Classical AMG (Ruge-Stuben AMG)

    Parameters
    ----------
    A : csr_matrix
        Square nonsymmetric matrix in CSR format
    strength : ['symmetric', 'classical', 'evolution', 'distance',
                'algebraic_distance','affinity', 'energy_based', None]
        Method used to determine the strength of connection between unknowns
        of the linear system.  Method-specific parameters may be passed in
        using a tuple, e.g. strength=('symmetric',{'theta' : 0.25 }). If
        strength=None, all nonzero entries of the matrix are considered strong.
    CF : {string} : default 'RS'
        Method used for coarse grid selection (C/F splitting)
        Supported methods are RS, PMIS, PMISc, CLJP, CLJPc, and CR.
    interp : {string} : default 'one-point'
        Options include 'direct', 'standard', 'inject' and 'one-point'.
    restrict : {string} : default 'neumann'
        Options include 'air' for approximate ideal
        restriction.
    presmoother : {string or dict} : default None
        Method used for presmoothing at each level.  Method-specific parameters
        may be passed in using a tuple, e.g.
        presmoother=('gauss_seidel',{'sweep':'symmetric}), the default.
    postsmoother : {string or dict} : default F-Jacobi
        Postsmoothing method with the same usage as presmoother
    filter_operator : (bool, tol) : default None
        Remove small entries in operators on each level if True. Entries are
        considered "small" if |a_ij| < tol |a_ii|.
    coarse_grid_P : {string} : default None
        Option to specify a different construction of P used in computing RAP
        vs. for interpolation in an actual solve.
    max_levels: {integer} : default 20
        Maximum number of levels to be used in the multilevel solver.
    max_coarse: {integer} : default 20
        Maximum number of variables permitted on the coarse grid.
    keep: {bool} : default False
        Flag to indicate keeping extra operators in the hierarchy for
        diagnostics.  For example, if True, then strength of connection (C) and
        tentative prolongation (T) are kept.

    Returns
    -------
    ml : multilevel_solver
        Multigrid hierarchy of matrices and prolongation operators

    Other Parameters
    ----------------
    coarse_solver : ['splu', 'lu', 'cholesky, 'pinv', 'gauss_seidel', ... ]
        Solver used at the coarsest level of the MG hierarchy.
            Optionally, may be a tuple (fn, args), where fn is a string such as
        ['splu', 'lu', ...] or a callable function, and args is a dictionary of
        arguments to be passed to fn.
    setup_complexity : bool
        For a detailed, more accurate setup complexity, pass in 
        'setup_complexity' = True. This will slow down performance, but
        increase accuracy of complexity count. 

    Notes
    -----




    References
    ----------
    .. [1] 

    See Also
    --------
    aggregation.smoothed_aggregation_solver, multilevel_solver,
    aggregation.rootnode_solver

    """

    if ('setup_complexity' in kwargs):
        if kwargs['setup_complexity'] == True:
            mat_mat_complexity.__detailed__ = True
        del kwargs['setup_complexity']

    # preprocess A
    A = A.asfptype()
    if A.shape[0] != A.shape[1]:
        raise ValueError('expected square matrix')

    levels = [multilevel_solver.level()]
    levels[-1].A = A

    while len(levels) < max_levels and levels[-1].A.shape[0] > max_coarse:
        bottom = extend_hierarchy(levels, strength, CF, interp, restrict, filter_operator,
                                  coarse_grid_P, coarse_grid_R, keep)
        if bottom:
            break

    ml = multilevel_solver(levels, **kwargs)
    change_smoothers(ml, presmoother, postsmoother)
    return ml
Exemple #30
0
def ruge_stuben_solver(A,
                       strength=('classical', {'theta': 0.25}),
                       CF='RS',
                       presmoother=('gauss_seidel', {'sweep': 'symmetric'}),
                       postsmoother=('gauss_seidel', {'sweep': 'symmetric'}),
                       max_levels=10, max_coarse=500, keep=False, **kwargs):
    """Create a multilevel solver using Classical AMG (Ruge-Stuben AMG)

    Parameters
    ----------
    A : csr_matrix
        Square matrix in CSR format
    strength : ['symmetric', 'classical', 'evolution', None]
        Method used to determine the strength of connection between unknowns
        of the linear system.  Method-specific parameters may be passed in
        using a tuple, e.g. strength=('symmetric',{'theta' : 0.25 }). If
        strength=None, all nonzero entries of the matrix are considered strong.
    CF : {string} : default 'RS'
        Method used for coarse grid selection (C/F splitting)
        Supported methods are RS, PMIS, PMISc, CLJP, and CLJPc
    presmoother : {string or dict}
        Method used for presmoothing at each level.  Method-specific parameters
        may be passed in using a tuple, e.g.
        presmoother=('gauss_seidel',{'sweep':'symmetric}), the default.
    postsmoother : {string or dict}
        Postsmoothing method with the same usage as presmoother
    max_levels: {integer} : default 10
        Maximum number of levels to be used in the multilevel solver.
    max_coarse: {integer} : default 500
        Maximum number of variables permitted on the coarse grid.
    keep: {bool} : default False
        Flag to indicate keeping extra operators in the hierarchy for
        diagnostics.  For example, if True, then strength of connection (C) and
        tentative prolongation (T) are kept.

    Returns
    -------
    ml : multilevel_solver
        Multigrid hierarchy of matrices and prolongation operators

    Examples
    --------
    >>> from pyamg.gallery import poisson
    >>> from pyamg import ruge_stuben_solver
    >>> A = poisson((10,),format='csr')
    >>> ml = ruge_stuben_solver(A,max_coarse=3)

    Notes
    -----

    "coarse_solver" is an optional argument and is the solver used at the
    coarsest grid.  The default is a pseudo-inverse.  Most simply,
    coarse_solver can be one of ['splu', 'lu', 'cholesky, 'pinv',
    'gauss_seidel', ... ].  Additionally, coarse_solver may be a tuple
    (fn, args), where fn is a string such as ['splu', 'lu', ...] or a callable
    function, and args is a dictionary of arguments to be passed to fn.


    References
    ----------
    .. [1] Trottenberg, U., Oosterlee, C. W., and Schuller, A.,
       "Multigrid" San Diego: Academic Press, 2001.  Appendix A

    See Also
    --------
    aggregation.smoothed_aggregation_solver, multilevel_solver,
    aggregation.rootnode_solver

    """

    levels = [multilevel_solver.level()]

    # convert A to csr
    if not isspmatrix_csr(A):
        try:
            A = csr_matrix(A)
            warn("Implicit conversion of A to CSR",
                 SparseEfficiencyWarning)
        except:
            raise TypeError('Argument A must have type csr_matrix, \
                             or be convertible to csr_matrix')
    # preprocess A
    A = A.asfptype()
    if A.shape[0] != A.shape[1]:
        raise ValueError('expected square matrix')

    levels[-1].A = A

    while len(levels) < max_levels and levels[-1].A.shape[0] > max_coarse:
        extend_hierarchy(levels, strength, CF, keep)

    ml = multilevel_solver(levels, **kwargs)
    change_smoothers(ml, presmoother, postsmoother)
    return ml
Exemple #31
0
def rootnode_solver(A, B=None, BH=None,
                    symmetry='hermitian', strength='symmetric',
                    aggregate='standard', smooth='energy',
                    presmoother=('block_gauss_seidel',
                                 {'sweep': 'symmetric'}),
                    postsmoother=('block_gauss_seidel',
                                  {'sweep': 'symmetric'}),
                    improve_candidates=('block_gauss_seidel',
                                        {'sweep': 'symmetric',
                                         'iterations': 4}),
                    max_levels=10, max_coarse=10,
                    diagonal_dominance=False, keep=False, **kwargs):
    """Create a multilevel solver using root-node based Smoothed Aggregation (SA).

    See the notes below, for the major differences with the classical-style
    smoothed aggregation solver in aggregation.smoothed_aggregation_solver.

    Parameters
    ----------
    A : csr_matrix, bsr_matrix
        Sparse NxN matrix in CSR or BSR format

    B : None, array_like
        Right near-nullspace candidates stored in the columns of an NxK array.
        K must be >= the blocksize of A (see reference [2011OlScTu]_). The default value
        B=None is equivalent to choosing the constant over each block-variable,
        B=np.kron(np.ones((A.shape[0]/blocksize(A), 1)), np.eye(blocksize(A)))

    BH : None, array_like
        Left near-nullspace candidates stored in the columns of an NxK array.
        BH is only used if symmetry is 'nonsymmetric'.  K must be >= the
        blocksize of A (see reference [2011OlScTu]_). The default value B=None is
        equivalent to choosing the constant over each block-variable,
        B=np.kron(np.ones((A.shape[0]/blocksize(A), 1)), np.eye(blocksize(A)))

    symmetry : string
        'symmetric' refers to both real and complex symmetric
        'hermitian' refers to both complex Hermitian and real Hermitian
        'nonsymmetric' i.e. nonsymmetric in a hermitian sense
        Note that for the strictly real case, symmetric and hermitian are
        the same
        Note that this flag does not denote definiteness of the operator.

    strength : list
        Method used to determine the strength of connection between unknowns of
        the linear system.  Method-specific parameters may be passed in using a
        tuple, e.g. strength=('symmetric',{'theta' : 0.25 }). If strength=None,
        all nonzero entries of the matrix are considered strong.

    aggregate : list
        Method used to aggregate nodes.

    smooth : list
        Method used to smooth the tentative prolongator.  Method-specific
        parameters may be passed in using a tuple, e.g.  smooth=
        ('energy',{'krylov' : 'gmres'}).  Only 'energy' and None are valid
        prolongation smoothing options.

    presmoother : tuple, string, list
        Defines the presmoother for the multilevel cycling.  The default block
        Gauss-Seidel option defaults to point-wise Gauss-Seidel, if the matrix
        is CSR or is a BSR matrix with blocksize of 1.  See notes below for
        varying this parameter on a per level basis.

    postsmoother : tuple, string, list
        Same as presmoother, except defines the postsmoother.

    improve_candidates : tuple, string, list
        The ith entry defines the method used to improve the candidates B on
        level i.  If the list is shorter than max_levels, then the last entry
        will define the method for all levels lower.  If tuple or string, then
        this single relaxation descriptor defines improve_candidates on all
        levels.
        The list elements are relaxation descriptors of the form used for
        presmoother and postsmoother.  A value of None implies no action on B.

    max_levels : integer
        Maximum number of levels to be used in the multilevel solver.

    max_coarse : integer
        Maximum number of variables permitted on the coarse grid.

    diagonal_dominance : bool, tuple
        If True (or the first tuple entry is True), then avoid coarsening
        diagonally dominant rows.  The second tuple entry requires a
        dictionary, where the key value 'theta' is used to tune the diagonal
        dominance threshold.

    keep : bool
        Flag to indicate keeping extra operators in the hierarchy for
        diagnostics.  For example, if True, then strength of connection (C),
        tentative prolongation (T), aggregation (AggOp), and arrays
        storing the C-points (Cpts) and F-points (Fpts) are kept at
        each level.

    Other Parameters
    ----------------
    cycle_type : ['V','W','F']
        Structrure of multigrid cycle
    coarse_solver : ['splu', 'lu', 'cholesky, 'pinv', 'gauss_seidel', ... ]
        Solver used at the coarsest level of the MG hierarchy.
        Optionally, may be a tuple (fn, args), where fn is a string such as
        ['splu', 'lu', ...] or a callable function, and args is a dictionary of
        arguments to be passed to fn.

    Returns
    -------
    ml : multilevel_solver
        Multigrid hierarchy of matrices and prolongation operators

    See Also
    --------
    multilevel_solver, aggregation.smoothed_aggregation_solver,
    classical.ruge_stuben_solver

    Notes
    -----
         - Root-node style SA differs from classical SA primarily by preserving
           and identity block in the interpolation operator, P.  Each aggregate
           has a "root-node" or "center-node" associated with it, and this
           root-node is injected from the coarse grid to the fine grid.  The
           injection corresponds to the identity block.

         - Only smooth={'energy', None} is supported for prolongation
           smoothing.  See reference [2011OlScTu]_ below for more details on why the
           'energy' prolongation smoother is the natural counterpart to
           root-node style SA.

         - The additional parameters are passed through as arguments to
           multilevel_solver.  Refer to pyamg.multilevel_solver for additional
           documentation.

         - At each level, four steps are executed in order to define the coarser
           level operator.

           1. Matrix A is given and used to derive a strength matrix, C.

           2. Based on the strength matrix, indices are grouped or aggregated.

           3. The aggregates define coarse nodes and a tentative prolongation
              operator T is defined by injection

           4. The tentative prolongation operator is smoothed by a relaxation
              scheme to improve the quality and extent of interpolation from the
              aggregates to fine nodes.

         - The parameters smooth, strength, aggregate, presmoother, postsmoother
           can be varied on a per level basis.  For different methods on
           different levels, use a list as input so that the i-th entry defines
           the method at the i-th level.  If there are more levels in the
           hierarchy than list entries, the last entry will define the method
           for all levels lower.

           Examples are:
           smooth=[('jacobi', {'omega':1.0}), None, 'jacobi']
           presmoother=[('block_gauss_seidel', {'sweep':symmetric}), 'sor']
           aggregate=['standard', 'naive']
           strength=[('symmetric', {'theta':0.25}), ('symmetric', {'theta':0.08})]

         - Predefined strength of connection and aggregation schemes can be
           specified.  These options are best used together, but aggregation can
           be predefined while strength of connection is not.

           For predefined strength of connection, use a list consisting of
           tuples of the form ('predefined', {'C' : C0}), where C0 is a
           csr_matrix and each degree-of-freedom in C0 represents a supernode.
           For instance to predefine a three-level hierarchy, use
           [('predefined', {'C' : C0}), ('predefined', {'C' : C1}) ].

           Similarly for predefined aggregation, use a list of tuples.  For
           instance to predefine a three-level hierarchy, use [('predefined',
           {'AggOp' : Agg0}), ('predefined', {'AggOp' : Agg1}) ], where the
           dimensions of A, Agg0 and Agg1 are compatible, i.e.  Agg0.shape[1] ==
           A.shape[0] and Agg1.shape[1] == Agg0.shape[0].  Each AggOp is a
           csr_matrix.

           Because this is a root-nodes solver, if a member of the predefined
           aggregation list is predefined, it must be of the form
           ('predefined', {'AggOp' : Agg, 'Cnodes' : Cnodes}).

    Examples
    --------
    >>> from pyamg import rootnode_solver
    >>> from pyamg.gallery import poisson
    >>> from scipy.sparse.linalg import cg
    >>> import numpy as np
    >>> A = poisson((100, 100), format='csr')           # matrix
    >>> b = np.ones((A.shape[0]))                   # RHS
    >>> ml = rootnode_solver(A)                     # AMG solver
    >>> M = ml.aspreconditioner(cycle='V')             # preconditioner
    >>> x, info = cg(A, b, tol=1e-8, maxiter=30, M=M)   # solve with CG

    References
    ----------
    .. [1996VaMa] Vanek, P. and Mandel, J. and Brezina, M.,
       "Algebraic Multigrid by Smoothed Aggregation for
       Second and Fourth Order Elliptic Problems",
       Computing, vol. 56, no. 3, pp. 179--196, 1996.
       http://citeseer.ist.psu.edu/vanek96algebraic.html
    .. [2011OlScTu] Olson, L. and Schroder, J. and Tuminaro, R.,
       "A general interpolation strategy for algebraic
       multigrid using energy minimization", SIAM Journal
       on Scientific Computing (SISC), vol. 33, pp.
       966--991, 2011.

    """
    if not (isspmatrix_csr(A) or isspmatrix_bsr(A)):
        try:
            A = csr_matrix(A)
            warn("Implicit conversion of A to CSR",
                 SparseEfficiencyWarning)
        except BaseException:
            raise TypeError('Argument A must have type csr_matrix, \
                             bsr_matrix, or be convertible to csr_matrix')

    A = A.asfptype()

    if (symmetry != 'symmetric') and (symmetry != 'hermitian') and \
            (symmetry != 'nonsymmetric'):
        raise ValueError('expected \'symmetric\', \'nonsymmetric\' \
                          or \'hermitian\' for the symmetry parameter ')
    A.symmetry = symmetry

    if A.shape[0] != A.shape[1]:
        raise ValueError('expected square matrix')
    # Right near nullspace candidates use constant for each variable as default
    if B is None:
        B = np.kron(np.ones((int(A.shape[0]/blocksize(A)), 1), dtype=A.dtype),
                    np.eye(blocksize(A)))
    else:
        B = np.asarray(B, dtype=A.dtype)
        if len(B.shape) == 1:
            B = B.reshape(-1, 1)
        if B.shape[0] != A.shape[0]:
            raise ValueError('The near null-space modes B have incorrect \
                              dimensions for matrix A')
        if B.shape[1] < blocksize(A):
            raise ValueError('B.shape[1] must be >= the blocksize of A')

    # Left near nullspace candidates
    if A.symmetry == 'nonsymmetric':
        if BH is None:
            BH = B.copy()
        else:
            BH = np.asarray(BH, dtype=A.dtype)
            if len(BH.shape) == 1:
                BH = BH.reshape(-1, 1)
            if BH.shape[1] != B.shape[1]:
                raise ValueError('The number of left and right near \
                                  null-space modes B and BH, must be equal')
            if BH.shape[0] != A.shape[0]:
                raise ValueError('The near null-space modes BH have \
                                  incorrect dimensions for matrix A')

    # Levelize the user parameters, so that they become lists describing the
    # desired user option on each level.
    max_levels, max_coarse, strength =\
        levelize_strength_or_aggregation(strength, max_levels, max_coarse)
    max_levels, max_coarse, aggregate =\
        levelize_strength_or_aggregation(aggregate, max_levels, max_coarse)
    improve_candidates =\
        levelize_smooth_or_improve_candidates(improve_candidates, max_levels)
    smooth = levelize_smooth_or_improve_candidates(smooth, max_levels)

    # Construct multilevel structure
    levels = []
    levels.append(multilevel_solver.level())
    levels[-1].A = A          # matrix

    # Append near nullspace candidates
    levels[-1].B = B          # right candidates
    if A.symmetry == 'nonsymmetric':
        levels[-1].BH = BH    # left candidates

    while len(levels) < max_levels and \
            int(levels[-1].A.shape[0]/blocksize(levels[-1].A)) > max_coarse:
        extend_hierarchy(levels, strength, aggregate, smooth,
                         improve_candidates, diagonal_dominance, keep)

    ml = multilevel_solver(levels, **kwargs)
    change_smoothers(ml, presmoother, postsmoother)
    return ml