Exemple #1
0
Fpts = [i for i in range(0, n) if i not in Cpts]
num_Fpts = len(Fpts)
num_Cpts = len(Cpts)
num_bad_guys = 1
cf_ratio = float(num_Cpts) / num_Fpts

# Permutation matrix to sort rows
permute = identity(n, format='csr')
permute.indices = np.concatenate((Fpts, Cpts))
permute = permute.T

# Smooth bad guys
b = np.ones((A.shape[0], 1), dtype=A.dtype)
smooth_fn = ('gauss_seidel', {'sweep': 'symmetric', 'iterations': 4})
B = relaxation_as_linear_operator((smooth_fn), A, b) * B

# Compute eigenvalues / eigenvectors
[eval_A, evec_A] = linalg.eigsh(A, k=n / 4, which='SM')
[norm_A, dum] = linalg.eigsh(A, k=1, which='LM')
norm_A = norm_A[0]
del dum

# B = np.array(evec_A[:,0:2])		# Let bad guy be smoothest eigenvector(s)
# B = np.array(evec_A[:,0:1])		# Let bad guy be smoothest eigenvector(s)
# B = np.array((evec_A[:,0],evec_A[:,0])).T
# B = B / np.mean(B)

# Form operators
Afc = -A[Fpts, :][:, Cpts]
Acf = Afc.transpose()
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 #3
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
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 #5
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 #6
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
def adaptive_pairwise_solver(A,
                             initial_targets=None,
                             symmetry='hermitian',
                             desired_convergence=0.5,
                             test_iterations=10,
                             test_cycle='V',
                             test_accel=None,
                             strength=None,
                             smooth=None,
                             aggregate=('drake', {
                                 'levels': 2
                             }),
                             presmoother=('block_gauss_seidel', {
                                 'sweep': 'symmetric'
                             }),
                             postsmoother=('block_gauss_seidel', {
                                 'sweep': 'symmetric'
                             }),
                             max_levels=30,
                             max_coarse=100,
                             diagonal_dominance=False,
                             coarse_solver='pinv',
                             keep=False,
                             additive=False,
                             reconstruct=False,
                             max_hierarchies=10,
                             use_ritz=False,
                             improve_candidates=[('block_gauss_seidel', {
                                 'sweep': 'symmetric',
                                 'iterations': 4
                             })],
                             **kwargs):
    def unpack_arg(v):
        if isinstance(v, tuple):
            return v[0], v[1]
        elif v is None:
            return None
        else:
            return v, {}

    if isspmatrix_bsr(A):
        warn("Only currently implemented for CSR matrices.")

    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')

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

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

    A = A.asfptype()
    A.symmetry = symmetry
    n = A.shape[0]
    test_rhs = np.zeros((n, 1))

    # SHOULD I START WITH CONSTANT VECTOR OR SMOOTHED RANDOM VECTOR?
    # Right near nullspace candidates
    if initial_targets is None:
        initial_targets = np.kron(
            np.ones((A.shape[0] / blocksize(A), 1), dtype=A.dtype),
            np.eye(blocksize(A)))
    else:
        initial_targets = np.asarray(initial_targets, dtype=A.dtype)
        if len(initial_targets.shape) == 1:
            initial_targets = initial_targets.reshape(-1, 1)
        if initial_targets.shape[0] != A.shape[0]:
            raise ValueError(
                'The near null-space modes initial_targets have incorrect \
                              dimensions for matrix A')
        if initial_targets.shape[1] < blocksize(A):
            raise ValueError(
                'initial_targets.shape[1] must be >= the blocksize of A')

    # Improve near nullspace candidates by relaxing on A B = 0
    if improve_candidates is not None:
        fn, temp_args = unpack_arg(improve_candidates[0])
    else:
        fn = None

    if fn is not None:
        b = np.zeros((A.shape[0], 1), dtype=A.dtype)
        initial_targets = relaxation_as_linear_operator(
            (fn, temp_args), A, b) * initial_targets
        if A.symmetry == "nonsymmetric":
            AH = A.H.asformat(A.format)
            BH = relaxation_as_linear_operator((fn, temp_args), AH, b) * BH

    # Empty set of solver hierarchies
    solvers = multilevel_solver_set()
    target = initial_targets
    B = initial_targets
    cf = 1.0

    # Aggregation process on the finest level is the same each iteration.
    # To prevent repeating processes, we compute it here and provide it to the
    # sovler construction.
    AggOp = get_aggregate(A,
                          strength=strength,
                          aggregate=aggregate,
                          diagonal_dominance=diagonal_dominance,
                          B=initial_targets)
    if isinstance(aggregate, tuple):
        aggregate = [('predefined', {'AggOp': AggOp}), aggregate]
    elif isinstance(aggregate, list):
        aggregate.insert(0, ('predefined', {'AggOp': AggOp}))
    else:
        raise TypeError("Aggregate variable must be list or tuple.")

    # Continue adding hierarchies until desired convergence factor achieved,
    # or maximum number of hierarchies constructed
    it = 0
    while (cf > desired_convergence) and (it < max_hierarchies):

        # pdb.set_trace()
        # Make target vector orthogonal and energy orthonormal and reconstruct hierarchy
        if use_ritz and it > 0:
            B = global_ritz_process(A, B, weak_tol=100)
            reconstruct_hierarchy(solver_set=solvers,
                                  A=A,
                                  new_B=B,
                                  symmetry=symmetry,
                                  aggregate=aggregate,
                                  presmoother=presmoother,
                                  postsmoother=postsmoother,
                                  smooth=smooth,
                                  strength=strength,
                                  max_levels=max_levels,
                                  max_coarse=max_coarse,
                                  coarse_solver=coarse_solver,
                                  diagonal_dominance=diagonal_dominance,
                                  keep=keep,
                                  **kwargs)
            print "Hierarchy reconstructed."
        # Otherwise just add new hierarchy to solver set.
        else:
            solvers.add_hierarchy(
                smoothed_aggregation_solver(
                    A,
                    B=B[:, -1],
                    symmetry=symmetry,
                    aggregate=aggregate,
                    presmoother=presmoother,
                    postsmoother=postsmoother,
                    smooth=smooth,
                    strength=strength,
                    max_levels=max_levels,
                    max_coarse=max_coarse,
                    diagonal_dominance=diagonal_dominance,
                    coarse_solver=coarse_solver,
                    improve_candidates=improve_candidates,
                    keep=keep,
                    **kwargs))
        # Test for convergence factor using new hierarchy.
        x0 = np.random.rand(n, 1)
        residuals = []
        target = solvers.solve(test_rhs,
                               x0=x0,
                               tol=1e-12,
                               maxiter=test_iterations,
                               cycle=test_cycle,
                               accel=test_accel,
                               residuals=residuals,
                               additive=additive)
        cf = residuals[-1] / residuals[-2]
        B = np.hstack((B, target))
        it += 1
        print "Added new hierarchy, convergence factor = ", cf

    B = B[:, :-1]
    # B2 = global_ritz_process(A, B, weak_tol=1.0)
    angles = test_targets(A, B)
    # angles = test_targets(A, B2)

    # -------------------------------------------------------------------------------------- #
    # -------------------------------------------------------------------------------------- #
    # -------------------------------------------------------------------------------------- #

    # b = np.zeros((n,1))
    # asa_residuals = []
    # sol = solvers.solve(b, x0, tol=1e-8, residuals=asa_residuals, accel=None)
    # asa_conv_factors = np.zeros((len(asa_residuals)-1,1))
    # for i in range(0,len(asa_residuals)-1):
    #   asa_conv_factors[i] = asa_residuals[i]/asa_residuals[i-1]

    # print "Original adaptive SA/AMG - ", np.mean(asa_conv_factors[1:])

    # if reconstruct:
    #     reconstruct_hierarchy(solver_set=solvers, A=A, new_B=B2, symmetry=symmetry,
    #                         aggregate=aggregate, presmoother=presmoother,
    #                         postsmoother=postsmoother, smooth=smooth,
    #                         strength=strength, max_levels=max_levels,
    #                         max_coarse=max_coarse, coarse_solver=coarse_solver,
    #                         diagonal_dominance=diagonal_dominance,
    #                         keep=keep, **kwargs)
    #     print "Hierarchy reconstructed."

    # asa_residuals2 = []
    # sol = solvers.solve(b, x0, tol=1e-8, residuals=asa_residuals2, accel=None)
    # asa_conv_factors2 = np.zeros((len(asa_residuals2)-1,1))
    # for i in range(0,len(asa_residuals2)-1):
    #   asa_conv_factors2[i] = asa_residuals2[i]/asa_residuals2[i-1]

    # print "Ritz adaptive SA/AMG - ", np.mean(asa_conv_factors2[1:])

    # if reconstruct:
    #     reconstruct_hierarchy(solver_set=solvers, A=A, new_B=B[:,:-1], symmetry=symmetry,
    #                         aggregate=aggregate, presmoother=presmoother,
    #                         postsmoother=postsmoother, smooth=smooth,
    #                         strength=strength, max_levels=max_levels,
    #                         max_coarse=max_coarse, coarse_solver=coarse_solver,
    #                         diagonal_dominance=diagonal_dominance,
    #                         keep=keep, **kwargs)
    #     print "Hierarchy reconstructed."

    # asa_residuals2 = []
    # sol = solvers.solve(b, x0, tol=1e-8, residuals=asa_residuals2, accel=None)
    # asa_conv_factors2 = np.zeros((len(asa_residuals2)-1,1))
    # for i in range(0,len(asa_residuals2)-1):
    #   asa_conv_factors2[i] = asa_residuals2[i]/asa_residuals2[i-1]

    # print "Original(-1) SA/AMG - ", np.mean(asa_conv_factors2[1:])

    # if reconstruct:
    #     reconstruct_hierarchy(solver_set=solvers, A=A, new_B=B2[:,:-1], symmetry=symmetry,
    #                         aggregate=aggregate, presmoother=presmoother,
    #                         postsmoother=postsmoother, smooth=smooth,
    #                         strength=strength, max_levels=max_levels,
    #                         max_coarse=max_coarse, coarse_solver=coarse_solver,
    #                         diagonal_dominance=diagonal_dominance,
    #                         keep=keep, **kwargs)
    #     print "Hierarchy reconstructed."

    # asa_residuals2 = []
    # sol = solvers.solve(b, x0, tol=1e-8, residuals=asa_residuals2, accel=None)
    # asa_conv_factors2 = np.zeros((len(asa_residuals2)-1,1))
    # for i in range(0,len(asa_residuals2)-1):
    #   asa_conv_factors2[i] = asa_residuals2[i]/asa_residuals2[i-1]

    # print "Ritz(-1) SA/AMG - ", np.mean(asa_conv_factors2[1:])

    # pdb.set_trace()
    # -------------------------------------------------------------------------------------- #
    # -------------------------------------------------------------------------------------- #
    # -------------------------------------------------------------------------------------- #

    return solvers
Exemple #8
0
def pairwise_aggregation(A, B, Bh=None, symmetry='hermitian',
                        algorithm='drake_C', matchings=1,
                        weights=None, improve_candidates=None,
                        strength=None, **kwargs):
    """ Pairwise aggregation of nodes. 

    Parameters
    ----------
    A : csr_matrix or bsr_matrix
        matrix for linear system.
    B : array_like
        Right near-nullspace candidates stored in the columns of an NxK array.
    BH : array_like : default None
        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()
    algorithm : string : default 'drake'
        Algorithm to perform pairwise matching. Current options are 
        'drake', 'preis', 'notay', referring to the Drake (2003), 
        Preis (1999), and Notay (2010), respectively. 
    matchings : int : default 1
        Number of pairwise matchings to do. k matchings will lead to 
        a coarsening factor of under 2^k.
    weights : function handle : default None
        Optional function handle to compute weights used in the matching,
        e.g. a strength of connection routine. Additional arguments for
        this routine should be provided in **kwargs. 
    improve_candidates : {tuple, string, list} : default None
        The list elements are relaxation descriptors of the form used for
        presmoother and postsmoother.  A value of None implies no action on B.
    strength : {(int, string) or None} : default None
        If a strength of connection matrix should be returned along with
        aggregation. If None, no SOC matrix returned. To return a SOC matrix,
        pass in a tuple (a,b), for int a > matchings telling how many matchings
        to use to construct a SOC matrix, and string b with data type.
        E.g. strength = (4,'bool'). 

    THINGS TO NOTE
    --------------
        - Not implemented for non-symmetric and/or block systems
            + Need to set up pairwise aggregation to be applicable for 
              nonsymmetric matrices (think it actually is...) 
            + Need to define how a matching is done nodally.
            + Also must consider what targets are used to form coarse grid'
              in nodal approach...
        - Once we are done w/ Python implementations of matching, we can remove 
          the deepcopy of A to W --> Don't need it, waste of time/memory.  

    """

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

    if not isinstance(matchings, int):
        raise TypeError("Number of matchings must be an integer.")

    if matchings < 1:
        raise ValueError("Number of matchings must be > 0.")

    if (algorithm is not 'drake') and (algorithm is not 'preis') and \
       (algorithm is not 'notay') and (algorithm is not 'drake_C'):
       raise ValueError("Only drake, notay and preis algorithms implemeted.")

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

    if strength is not None:
        if strength[0] < matchings:
            warn("Expect number of matchings for SOC >= matchings for aggregation.")
            diff = 0
        else:
            diff = strength[0] - matchings  # How many more matchings to do for SOC
    else:
        diff = 0

    # Compute weights if function provided, otherwise let W = A
    if weights is not None:
        W = weights(A, **kwargs)
    else:
        W = deepcopy(A)

    if not isspmatrix_csr(W):
        warn("Requires CSR matrix - trying to convert.", SparseEfficiencyWarning)
        try:
            W = W.tocsr()
        except:
            raise TypeError("Could not convert to csr matrix.")

    n = A.shape[0]
    if (symmetry == 'nonsymmetric') and (Bh == None):
        print "Warning - no left near null-space vector provided for nonsymmetric matrix.\n\
        Copying right near null-space vector."
        Bh = deepcopy(B[0:n,0:1])

    # Dictionary of function names for matching algorithms 
    get_matching = {
        'drake': drake_matching,
        'preis': preis_matching_1999,
        'notay': notay_matching_2010
    }

    # Get initial matching
    [M,S] = get_matching[algorithm](W, order='backward', **kwargs)
    num_pairs = M.shape[0]
    num_sing = S.shape[0]
    Nc = num_pairs+num_sing
    # Pick C-points and save in list
    Cpts = np.zeros((Nc,),dtype=int)
    Cpts[0:num_pairs] = M[:,0]
    Cpts[num_pairs:Nc] = S

    # Form sparse P from pairwise aggregation
    row_inds = np.empty(n)
    row_inds[0:(2*num_pairs)] = M.flatten()
    row_inds[(2*num_pairs):n] = S
    col_inds = np.empty(n)
    col_inds[0:(2*num_pairs)] = ( np.array( ( np.arange(0,num_pairs),np.arange(0,num_pairs) ) ).T).flatten()
    col_inds[(2*num_pairs):n] = np.arange(num_pairs,Nc)
    AggOp = csr_matrix( (np.ones((n,), dtype=bool), (row_inds,col_inds)), shape=(n,Nc) )

    # Predefine SOC matrix is only one pairwise pass is done for aggregation
    if (matchings == 1) and (diff > 0):
        AggOp2 = csr_matrix(AggOp, dtype=strength[1])

    # If performing multiple pairwise matchings, form coarse grid operator
    # and repeat process
    if (matchings+diff) > 1:
        P = csr_matrix( (B[0:n,0], (row_inds,col_inds)), shape=(n,Nc) )
        Bc = np.ones((Nc,1))
        if symmetry == 'hermitian':
            R = P.H
            Ac = R*A*P
        elif symmetry == 'symmetric':
            R = P.T            
            Ac = R*A*P
        elif symmetry == 'nonsymmetric':
            R = csr_matrix( (Bh[0:n,0], (col_inds,row_inds)), shape=(Nc,n) )
            Ac = R*A*P
            AcH = Ac.H.asformat(Ac.format)
            Bhc = np.ones((Nc,1))

        # Loop over the number of pairwise matchings to be done
        for i in range(1,(matchings+diff)):
            if weights is not None:
                W = weights(Ac, **kwargs)
            else:
                W = Ac
            # Get matching
            [M,S] = get_matching[algorithm](W, order='forward', **kwargs)
            n = Ac.shape[0]
            num_pairs = M.shape[0]
            num_sing = S.shape[0]
            Nc = num_pairs+num_sing
            # Pick C-points and save in list
            temp = np.zeros((Nc,),dtype=int)
            temp[0:num_pairs] = M[:,0]
            temp[num_pairs:Nc] = S
            Cpts = Cpts[temp]

            # Improve near nullspace candidates by relaxing on A B = 0
            fn, kwargs = unpack_arg(improve_candidates)
            if fn is not None:
                b = np.zeros((n, 1), dtype=Ac.dtype)
                Bc = relaxation_as_linear_operator((fn, kwargs), Ac, b) * Bc
                if symmetry == "nonsymmetric":
                    Bhc = relaxation_as_linear_operator((fn, kwargs), AcH, b) * Bhc

            # Form sparse P from pairwise aggregation
            row_inds = np.empty(n)
            row_inds[0:(2*num_pairs)] = M.flatten()
            row_inds[(2*num_pairs):n] = S
            col_inds = np.empty(n)
            col_inds[0:(2*num_pairs)] = ( np.array( ( np.arange(0,num_pairs),np.arange(0,num_pairs) ) ).T).flatten()
            col_inds[(2*num_pairs):n] = np.arange(num_pairs,Nc)

            # Form coarse grid operator and update aggregation matrix
            if i < (matchings-1):
                P = csr_matrix( (Bc[0:n,0], (row_inds,col_inds)), shape=(n,Nc) )
                if symmetry == 'hermitian':
                    R = P.H
                    Ac = R*Ac*P
                elif symmetry == 'symmetric':
                    R = P.T            
                    Ac = R*Ac*P
                elif symmetry == 'nonsymmetric':
                    R = csr_matrix( (Bhc[0:n,0], (col_inds,row_inds)), shape=(Nc,n) )
                    Ac = R*Ac*P
                    AcH = Ac.H.asformat(Ac.format)
                    Bhc = np.ones((Nc,1))

                AggOp = csr_matrix(AggOp * P, dtype=bool)
                Bc = np.ones((Nc,1))
            # Construct final aggregation matrix
            elif i == (matchings-1):
                P = csr_matrix( (np.ones((n,), dtype=bool), (row_inds,col_inds)), shape=(n,Nc) )
                AggOp = csr_matrix(AggOp * P, dtype=bool)
                # Construct coarse grids and additional aggregation matrix
                # only if doing more matchings for SOC.
                if diff > 0:
                    if symmetry == 'hermitian':
                        R = P.H
                        Ac = R*Ac*P
                    elif symmetry == 'symmetric':
                        R = P.T            
                        Ac = R*Ac*P
                    elif symmetry == 'nonsymmetric':
                        R = csr_matrix( (Bhc[0:n,0], (col_inds,row_inds)), shape=(Nc,n) )
                        Ac = R*Ac*P
                        AcH = Ac.H.asformat(Ac.format)
                        Bhc = np.ones((Nc,1))

                    Bc = np.ones((Nc,1))
                    AggOp2 = csr_matrix(AggOp, dtype=strength[1])
            # Pairwise iterations for SOC
            elif i < (matchings+diff-1):
                P = csr_matrix( (Bc[0:n,0], (row_inds,col_inds)), shape=(n,Nc) )
                if symmetry == 'hermitian':
                    R = P.H
                    Ac = R*Ac*P
                elif symmetry == 'symmetric':
                    R = P.T            
                    Ac = R*Ac*P
                elif symmetry == 'nonsymmetric':
                    R = csr_matrix( (Bhc[0:n,0], (col_inds,row_inds)), shape=(Nc,n) )
                    Ac = R*Ac*P
                    AcH = Ac.H.asformat(Ac.format)
                    Bhc = np.ones((Nc,1))

                AggOp2 = csr_matrix(AggOp2 * P, dtype=bool)
                Bc = np.ones((Nc,1))
            # Final matching for SOC matrix. Construct SOC as AggOp*AggOp^T.
            elif i == (matchings+diff-1):
                P = csr_matrix( (np.ones((n,), dtype=bool), (row_inds,col_inds)), shape=(n,Nc) )
                AggOp2 = csr_matrix(AggOp2 * P, dtype=bool)
                AggOp2 = csr_matrix(AggOp2*AggOp2.T, dtype=strength[1])

    if strength is None:
        return AggOp, Cpts
    else:
        return AggOp, Cpts, AggOp2
Exemple #9
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