def classical_strength_of_connection(A, theta=0.25, block=None, norm='abs', cost=[0]): """ Return a strength of connection matrix using the classical AMG measure An off-diagonal entry A[i,j] is a strong connection iff:: | A[i,j] | >= theta * max(| A[i,k] |), where k != i (norm='abs') -A[i,j] >= theta * max(| A[i,k] |), where k != i (norm='min') Parameters ---------- A : csr_matrix or bsr_matrix Square, sparse matrix in CSR or BSR format theta : float Threshold parameter in [0,1]. block : string, default None for CSR matrix and 'block' for BSR matrix How to treat block structure of A: None : Compute SOC based on A as CSR matrix. 'block' : Compute SOC based on norm of blocks of A. 'amalgamate' : Compute SOC based on A as CSR matrix, then compute norm of blocks in SOC matrix for a block SOC. norm : 'string', default 'abs' Option to compute SOC between elements or blocks: 'abs' : C_ij = k, where k is the maximum absolute value in block C_ij 'min' : C_ij = k, where k is the minimum (negative) value in block C_ij 'fro' : C_ij = k, where k is the Frobenius norm of block C_ij - Only valid for block matrices, block='block' Returns ------- S : csr_matrix Matrix graph defining strong connections. S[i,j]=1 if vertex i is strongly influenced by vertex j. See Also -------- symmetric_strength_of_connection : symmetric measure used in SA evolution_strength_of_connection : relaxation based strength measure Notes ----- - A symmetric A does not necessarily yield a symmetric strength matrix S - Calls C++ function classical_strength_of_connection References ---------- .. [1] Briggs, W. L., Henson, V. E., McCormick, S. F., "A multigrid tutorial", Second edition. Society for Industrial and Applied Mathematics (SIAM), Philadelphia, PA, 2000. xii+193 pp. ISBN: 0-89871-462-1 .. [2] Trottenberg, U., Oosterlee, C. W., Schuller, A., "Multigrid", Academic Press, Inc., San Diego, CA, 2001. xvi+631 pp. ISBN: 0-12-701070-X Examples -------- >>> import numpy as np >>> from pyamg.gallery import stencil_grid >>> from pyamg.strength import classical_strength_of_connection >>> n=3 >>> stencil = np.array([[-1.0,-1.0,-1.0], ... [-1.0, 8.0,-1.0], ... [-1.0,-1.0,-1.0]]) >>> A = stencil_grid(stencil, (n,n), format='csr') >>> S = classical_strength_of_connection(A, 0.0) """ if (theta < 0 or theta > 1): raise ValueError('expected theta in [0,1]') if (theta < 0 or theta > 1): raise ValueError('expected theta in [0,1]') if sparse.isspmatrix_bsr(A): blocksize = A.blocksize[0] else: blocksize = 1 # Block structure considered before computing SOC if (block == 'block') or sparse.isspmatrix_bsr(A): R, C = A.blocksize if (R != C) or (R < 1): raise ValueError('Matrix must have square blocks') N = int(A.shape[0] / R) # SOC based on maximum absolute value element in each block if norm == 'abs': data = np.max(np.max(np.abs(A.data), axis=1), axis=1) cost[0] += 1 # SOC based on hard minimum of entry in each off-diagonal block elif norm == 'min': data = np.min(np.min(A.data, axis=1), axis=1) cost[0] += 1 # SOC based on Frobenius norms of blocks elif norm == 'fro': data = (np.conjugate(A.data) * A.data).reshape(-1, R * C).sum(axis=1) cost[0] += 1 else: raise ValueError("Invalid choice of norm.") data[np.abs(data) < 1e-16] = 0.0 S_rowptr = np.empty_like(A.indptr) S_colinds = np.empty_like(A.indices) S_data = np.empty_like(data) if norm == 'abs' or norm == 'fro': amg_core.classical_strength_of_connection_abs( N, theta, A.indptr, A.indices, data, S_rowptr, S_colinds, S_data) elif norm == 'min': amg_core.classical_strength_of_connection_min( N, theta, A.indptr, A.indices, data, S_rowptr, S_colinds, S_data) else: raise ValueError("Unrecognized option for norm.") # One pass through nnz to find largest entry, one to filter S = sparse.csr_matrix((S_data, S_colinds, S_rowptr), shape=[N, N]) cost[0] += 2 # Take magnitude and scale by largest entry S.data = np.abs(S.data) S = scale_rows_by_largest_entry(S) S.eliminate_zeros() # Assume largest entry can be tracked from filtering. # 1 WU to scale matrix. cost[0] += float(S.nnz) / A.nnz return S # SOC computed based on A as CSR else: S_rowptr = np.empty_like(A.indptr) S_colinds = np.empty_like(A.indices) S_data = np.empty_like(A.data) if norm == 'abs' or norm == 'fro': amg_core.classical_strength_of_connection_abs( A.shape[0], theta, A.indptr, A.indices, A.data, S_rowptr, S_colinds, S_data) elif norm == 'min': amg_core.classical_strength_of_connection_min( A.shape[0], theta, A.indptr, A.indices, A.data, S_rowptr, S_colinds, S_data) else: raise ValueError("Unrecognized option for norm.") # One pass through nnz to find largest entry, one to filter S = sparse.csr_matrix((S_data, S_colinds, S_rowptr), shape=A.shape) cost[0] += 2 if blocksize > 1 and block == 'amalgamate': S = amalgamate(S, blocksize, norm=norm) # Take magnitude and scale by largest entry S.data = np.abs(S.data) S = scale_rows_by_largest_entry(S) S.eliminate_zeros() # Assume largest entry can be tracked from filtering. # 1 WU to scale matrix. cost[0] += float(S.nnz) / A.nnz return S
def classical_strength_of_connection(A, theta=0.0): """ Return a strength of connection matrix using the classical AMG measure An off-diagonal entry A[i,j] is a strong connection iff:: | A[i,j] | >= theta * max(| A[i,k] |), where k != i Parameters ---------- A : csr_matrix or bsr_matrix Square, sparse matrix in CSR or BSR format theta : float Threshold parameter in [0,1]. Returns ------- S : csr_matrix Matrix graph defining strong connections. S[i,j]=1 if vertex i is strongly influenced by vertex j. See Also -------- symmetric_strength_of_connection : symmetric measure used in SA evolution_strength_of_connection : relaxation based strength measure Notes ----- - A symmetric A does not necessarily yield a symmetric strength matrix S - Calls C++ function classical_strength_of_connection - The version as implemented is designed form M-matrices. Trottenberg et al. use max A[i,k] over all negative entries, which is the same. A positive edge weight never indicates a strong connection. References ---------- .. [1] Briggs, W. L., Henson, V. E., McCormick, S. F., "A multigrid tutorial", Second edition. Society for Industrial and Applied Mathematics (SIAM), Philadelphia, PA, 2000. xii+193 pp. ISBN: 0-89871-462-1 .. [2] Trottenberg, U., Oosterlee, C. W., Schuller, A., "Multigrid", Academic Press, Inc., San Diego, CA, 2001. xvi+631 pp. ISBN: 0-12-701070-X Examples -------- >>> import numpy as np >>> from pyamg.gallery import stencil_grid >>> from pyamg.strength import classical_strength_of_connection >>> n=3 >>> stencil = np.array([[-1.0,-1.0,-1.0], ... [-1.0, 8.0,-1.0], ... [-1.0,-1.0,-1.0]]) >>> A = stencil_grid(stencil, (n,n), format='csr') >>> S = classical_strength_of_connection(A, 0.0) """ if sparse.isspmatrix_bsr(A): blocksize = A.blocksize[0] else: blocksize = 1 if not sparse.isspmatrix_csr(A): warn("Implicit conversion of A to csr", sparse.SparseEfficiencyWarning) A = sparse.csr_matrix(A) if (theta < 0 or theta > 1): raise ValueError('expected theta in [0,1]') Sp = np.empty_like(A.indptr) Sj = np.empty_like(A.indices) Sx = np.empty_like(A.data) fn = amg_core.classical_strength_of_connection fn(A.shape[0], theta, A.indptr, A.indices, A.data, Sp, Sj, Sx) S = sparse.csr_matrix((Sx, Sj, Sp), shape=A.shape) if blocksize > 1: S = amalgamate(S, blocksize) # Strength represents "distance", so take the magnitude S.data = np.abs(S.data) # Scale S by the largest magnitude entry in each row S = scale_rows_by_largest_entry(S) return S
def initial_setup_stage(A, symmetry, pdef, candidate_iters, epsilon, max_levels, max_coarse, aggregate, prepostsmoother, smooth, strength, work, initial_candidate=None): """ Computes a complete aggregation and the first near-nullspace candidate following Algorithm 3 in Brezina et al. Parameters ---------- candidate_iters number of test relaxation iterations epsilon minimum acceptable relaxation convergence factor References ---------- .. [1] Brezina, Falgout, MacLachlan, Manteuffel, McCormick, and Ruge "Adaptive Smoothed Aggregation ($\alpha$SA) Multigrid" SIAM Review Volume 47, Issue 2 (2005) http://www.cs.umn.edu/~maclach/research/aSA2.pdf """ # Define relaxation routine def relax(A, x): fn, kwargs = unpack_arg(prepostsmoother) if fn == 'gauss_seidel': gauss_seidel(A, x, np.zeros_like(x), iterations=candidate_iters, sweep='symmetric') elif fn == 'gauss_seidel_nr': gauss_seidel_nr(A, x, np.zeros_like(x), iterations=candidate_iters, sweep='symmetric') elif fn == 'gauss_seidel_ne': gauss_seidel_ne(A, x, np.zeros_like(x), iterations=candidate_iters, sweep='symmetric') elif fn == 'jacobi': jacobi(A, x, np.zeros_like(x), iterations=1, omega=1.0 / rho_D_inv_A(A)) elif fn == 'richardson': polynomial(A, x, np.zeros_like(x), iterations=1, coefficients=[1.0 / approximate_spectral_radius(A)]) elif fn == 'gmres': x[:] = (gmres(A, np.zeros_like(x), x0=x, maxiter=candidate_iters)[0]).reshape(x.shape) else: raise TypeError('Unrecognized smoother') # flag for skipping steps f-i in step 4 skip_f_to_i = True # step 1 A_l = A if initial_candidate is None: x = sp.rand(A_l.shape[0], 1).astype(A_l.dtype) # The following type check matches the usual 'complex' type, # but also numpy data types such as 'complex64', 'complex128' # and 'complex256'. if A_l.dtype.name.startswith('complex'): x = x + 1.0j * sp.rand(A_l.shape[0], 1) else: x = np.array(initial_candidate, dtype=A_l.dtype) # step 2 relax(A_l, x) work[:] += A_l.nnz * candidate_iters * 2 # step 3 # not advised to stop the iteration here: often the first relaxation pass # _is_ good, but the remaining passes are poor # if x_A_x/x_A_x_old < epsilon: # # relaxation alone is sufficient # print 'relaxation alone works: %g'%(x_A_x/x_A_x_old) # return x, [] # step 4 As = [A] xs = [x] Ps = [] AggOps = [] StrengthOps = [] while A.shape[0] > max_coarse and max_levels > 1: # The real check to break from the while loop is below # Begin constructing next level fn, kwargs = unpack_arg(strength[len(As) - 1]) # step 4b if fn == 'symmetric': C_l = symmetric_strength_of_connection(A_l, **kwargs) # Diagonal must be nonzero C_l = C_l + eye(C_l.shape[0], C_l.shape[1], format='csr') elif fn == 'classical': C_l = classical_strength_of_connection(A_l, **kwargs) # Diagonal must be nonzero C_l = C_l + eye(C_l.shape[0], C_l.shape[1], format='csr') if isspmatrix_bsr(A_l): C_l = amalgamate(C_l, A_l.blocksize[0]) elif (fn == 'ode') or (fn == 'evolution'): C_l = evolution_strength_of_connection( A_l, np.ones((A_l.shape[0], 1), dtype=A.dtype), **kwargs) elif fn == 'predefined': C_l = kwargs['C'].tocsr() elif fn is None: C_l = A_l.tocsr() else: raise ValueError('unrecognized strength of connection method: %s' % str(fn)) # In SA, strength represents "distance", so we take magnitude of # complex values if C_l.dtype.name.startswith('complex'): C_l.data = np.abs(C_l.data) # Create a unified strength framework so that large values represent # strong connections and small values represent weak connections if (fn == 'ode') or (fn == 'evolution') or (fn == 'energy_based'): C_l.data = 1.0 / C_l.data # aggregation fn, kwargs = unpack_arg(aggregate[len(As) - 1]) if fn == 'standard': AggOp = standard_aggregation(C_l, **kwargs)[0] elif fn == 'lloyd': AggOp = lloyd_aggregation(C_l, **kwargs)[0] elif fn == 'predefined': AggOp = kwargs['AggOp'].tocsr() else: raise ValueError('unrecognized aggregation method %s' % str(fn)) T_l, x = fit_candidates(AggOp, x) # step 4c fn, kwargs = unpack_arg(smooth[len(As) - 1]) # step 4d if fn == 'jacobi': P_l = jacobi_prolongation_smoother(A_l, T_l, C_l, x, **kwargs) elif fn == 'richardson': P_l = richardson_prolongation_smoother(A_l, T_l, **kwargs) elif fn == 'energy': P_l = energy_prolongation_smoother(A_l, T_l, C_l, x, None, (False, {}), **kwargs) elif fn is None: P_l = T_l else: raise ValueError('unrecognized prolongation smoother method %s' % str(fn)) # R should reflect A's structure # step 4e if symmetry == 'symmetric': A_l = P_l.T.asformat(P_l.format) * A_l * P_l elif symmetry == 'hermitian': A_l = P_l.H.asformat(P_l.format) * A_l * P_l StrengthOps.append(C_l) AggOps.append(AggOp) Ps.append(P_l) As.append(A_l) # skip to step 5 as in step 4e if (A_l.shape[0] <= max_coarse) or (len(AggOps) + 1 >= max_levels): break if not skip_f_to_i: x_hat = x.copy() # step 4g relax(A_l, x) # step 4h work[:] += A_l.nnz * candidate_iters * 2 if pdef is True: x_A_x = np.dot(np.conjugate(x).T, A_l * x) xhat_A_xhat = np.dot(np.conjugate(x_hat).T, A_l * x_hat) err_ratio = (x_A_x / xhat_A_xhat)**(1.0 / candidate_iters) else: # use A.H A inner-product Ax = A_l * x # Axhat = A_l * x_hat x_A_x = np.dot(np.conjugate(Ax).T, Ax) xhat_A_xhat = np.dot(np.conjugate(x_hat).T, A_l * x_hat) err_ratio = (x_A_x / xhat_A_xhat)**(1.0 / candidate_iters) if err_ratio < epsilon: # step 4i # print "sufficient convergence, skipping" skip_f_to_i = True if x_A_x == 0: x = x_hat # need to restore x else: # just carry out relaxation, don't check for convergence relax(A_l, x) # step 4h work[:] += 2 * A_l.nnz * candidate_iters # store xs for diagnostic use and for use in step 5 xs.append(x) # step 5 # Extend coarse-level candidate to the finest level # --> note that we start with the x from the second coarsest level x = xs[-1] # make sure that xs[-1] has been relaxed by step 4h, i.e. relax(As[-2], x) for lev in range(len(Ps) - 2, -1, -1): # lev = coarsest ... finest-1 P = Ps[lev] # I: lev --> lev+1 A = As[lev] # A on lev+1 x = P * x relax(A, x) work[:] += A.nnz * candidate_iters * 2 # Set predefined strength of connection and aggregation if len(AggOps) > 1: aggregate = [('predefined', { 'AggOp': AggOps[i] }) for i in range(len(AggOps))] strength = [('predefined', { 'C': StrengthOps[i] }) for i in range(len(StrengthOps))] return x, aggregate, strength # first candidate
def initial_setup_stage(A, symmetry, pdef, candidate_iters, epsilon, max_levels, max_coarse, aggregate, prepostsmoother, smooth, strength, work, initial_candidate=None): """Compute aggregation and the first near-nullspace candidate following Algorithm 3 in Brezina et al. Parameters ---------- candidate_iters number of test relaxation iterations epsilon minimum acceptable relaxation convergence factor References ---------- .. [1] Brezina, Falgout, MacLachlan, Manteuffel, McCormick, and Ruge "Adaptive Smoothed Aggregation (aSA) Multigrid" SIAM Review Volume 47, Issue 2 (2005) http://www.cs.umn.edu/~maclach/research/aSA2.pdf """ # Define relaxation routine def relax(A, x): fn, kwargs = unpack_arg(prepostsmoother) if fn == 'gauss_seidel': gauss_seidel(A, x, np.zeros_like(x), iterations=candidate_iters, sweep='symmetric') elif fn == 'gauss_seidel_nr': gauss_seidel_nr(A, x, np.zeros_like(x), iterations=candidate_iters, sweep='symmetric') elif fn == 'gauss_seidel_ne': gauss_seidel_ne(A, x, np.zeros_like(x), iterations=candidate_iters, sweep='symmetric') elif fn == 'jacobi': jacobi(A, x, np.zeros_like(x), iterations=1, omega=1.0 / rho_D_inv_A(A)) elif fn == 'richardson': polynomial(A, x, np.zeros_like(x), iterations=1, coefficients=[1.0/approximate_spectral_radius(A)]) elif fn == 'gmres': x[:] = (gmres(A, np.zeros_like(x), x0=x, maxiter=candidate_iters)[0]).reshape(x.shape) else: raise TypeError('Unrecognized smoother') # flag for skipping steps f-i in step 4 skip_f_to_i = True # step 1 A_l = A if initial_candidate is None: x = sp.rand(A_l.shape[0], 1).astype(A_l.dtype) # The following type check matches the usual 'complex' type, # but also numpy data types such as 'complex64', 'complex128' # and 'complex256'. if A_l.dtype.name.startswith('complex'): x = x + 1.0j*sp.rand(A_l.shape[0], 1) else: x = np.array(initial_candidate, dtype=A_l.dtype) # step 2 relax(A_l, x) work[:] += A_l.nnz * candidate_iters*2 # step 3 # not advised to stop the iteration here: often the first relaxation pass # _is_ good, but the remaining passes are poor # if x_A_x/x_A_x_old < epsilon: # # relaxation alone is sufficient # print 'relaxation alone works: %g'%(x_A_x/x_A_x_old) # return x, [] # step 4 As = [A] xs = [x] Ps = [] AggOps = [] StrengthOps = [] while A.shape[0] > max_coarse and max_levels > 1: # The real check to break from the while loop is below # Begin constructing next level fn, kwargs = unpack_arg(strength[len(As)-1]) # step 4b if fn == 'symmetric': C_l = symmetric_strength_of_connection(A_l, **kwargs) # Diagonal must be nonzero C_l = C_l + eye(C_l.shape[0], C_l.shape[1], format='csr') elif fn == 'classical': C_l = classical_strength_of_connection(A_l, **kwargs) # Diagonal must be nonzero C_l = C_l + eye(C_l.shape[0], C_l.shape[1], format='csr') if isspmatrix_bsr(A_l): C_l = amalgamate(C_l, A_l.blocksize[0]) elif (fn == 'ode') or (fn == 'evolution'): C_l = evolution_strength_of_connection(A_l, np.ones( (A_l.shape[0], 1), dtype=A.dtype), **kwargs) elif fn == 'predefined': C_l = kwargs['C'].tocsr() elif fn is None: C_l = A_l.tocsr() else: raise ValueError('unrecognized strength of connection method: %s' % str(fn)) # In SA, strength represents "distance", so we take magnitude of # complex values if C_l.dtype.name.startswith('complex'): C_l.data = np.abs(C_l.data) # Create a unified strength framework so that large values represent # strong connections and small values represent weak connections if (fn == 'ode') or (fn == 'evolution') or (fn == 'energy_based'): C_l.data = 1.0 / C_l.data # aggregation fn, kwargs = unpack_arg(aggregate[len(As) - 1]) if fn == 'standard': AggOp = standard_aggregation(C_l, **kwargs)[0] elif fn == 'lloyd': AggOp = lloyd_aggregation(C_l, **kwargs)[0] elif fn == 'predefined': AggOp = kwargs['AggOp'].tocsr() else: raise ValueError('unrecognized aggregation method %s' % str(fn)) T_l, x = fit_candidates(AggOp, x) # step 4c fn, kwargs = unpack_arg(smooth[len(As)-1]) # step 4d if fn == 'jacobi': P_l = jacobi_prolongation_smoother(A_l, T_l, C_l, x, **kwargs) elif fn == 'richardson': P_l = richardson_prolongation_smoother(A_l, T_l, **kwargs) elif fn == 'energy': P_l = energy_prolongation_smoother(A_l, T_l, C_l, x, None, (False, {}), **kwargs) elif fn is None: P_l = T_l else: raise ValueError('unrecognized prolongation smoother method %s' % str(fn)) # R should reflect A's structure # step 4e if symmetry == 'symmetric': A_l = P_l.T.asformat(P_l.format) * A_l * P_l elif symmetry == 'hermitian': A_l = P_l.H.asformat(P_l.format) * A_l * P_l StrengthOps.append(C_l) AggOps.append(AggOp) Ps.append(P_l) As.append(A_l) # skip to step 5 as in step 4e if (A_l.shape[0] <= max_coarse) or (len(AggOps) + 1 >= max_levels): break if not skip_f_to_i: x_hat = x.copy() # step 4g relax(A_l, x) # step 4h work[:] += A_l.nnz*candidate_iters*2 if pdef is True: x_A_x = np.dot(np.conjugate(x).T, A_l*x) xhat_A_xhat = np.dot(np.conjugate(x_hat).T, A_l*x_hat) err_ratio = (x_A_x/xhat_A_xhat)**(1.0/candidate_iters) else: # use A.H A inner-product Ax = A_l * x # Axhat = A_l * x_hat x_A_x = np.dot(np.conjugate(Ax).T, Ax) xhat_A_xhat = np.dot(np.conjugate(x_hat).T, A_l*x_hat) err_ratio = (x_A_x/xhat_A_xhat)**(1.0/candidate_iters) if err_ratio < epsilon: # step 4i # print "sufficient convergence, skipping" skip_f_to_i = True if x_A_x == 0: x = x_hat # need to restore x else: # just carry out relaxation, don't check for convergence relax(A_l, x) # step 4h work[:] += 2 * A_l.nnz * candidate_iters # store xs for diagnostic use and for use in step 5 xs.append(x) # step 5 # Extend coarse-level candidate to the finest level # --> note that we start with the x from the second coarsest level x = xs[-1] # make sure that xs[-1] has been relaxed by step 4h, i.e. relax(As[-2], x) for lev in range(len(Ps)-2, -1, -1): # lev = coarsest ... finest-1 P = Ps[lev] # I: lev --> lev+1 A = As[lev] # A on lev+1 x = P * x relax(A, x) work[:] += A.nnz*candidate_iters*2 # Set predefined strength of connection and aggregation if len(AggOps) > 1: aggregate = [('predefined', {'AggOp': AggOps[i]}) for i in range(len(AggOps))] strength = [('predefined', {'C': StrengthOps[i]}) for i in range(len(StrengthOps))] return x, aggregate, strength # first candidate
def classical_strength_of_connection(A, theta=0.25, block=None, norm='abs', cost=[0]): """ Return a strength of connection matrix using the classical AMG measure An off-diagonal entry A[i,j] is a strong connection iff:: | A[i,j] | >= theta * max(| A[i,k] |), where k != i (norm='abs') -A[i,j] >= theta * max(| A[i,k] |), where k != i (norm='min') Parameters ---------- A : csr_matrix or bsr_matrix Square, sparse matrix in CSR or BSR format theta : float Threshold parameter in [0,1]. block : string, default None for CSR matrix and 'block' for BSR matrix How to treat block structure of A: None : Compute SOC based on A as CSR matrix. 'block' : Compute SOC based on norm of blocks of A. 'amalgamate' : Compute SOC based on A as CSR matrix, then compute norm of blocks in SOC matrix for a block SOC. norm : 'string', default 'abs' Option to compute SOC between elements or blocks: 'abs' : C_ij = k, where k is the maximum absolute value in block C_ij 'min' : C_ij = k, where k is the minimum (negative) value in block C_ij 'fro' : C_ij = k, where k is the Frobenius norm of block C_ij - Only valid for block matrices, block='block' Returns ------- S : csr_matrix Matrix graph defining strong connections. S[i,j]=1 if vertex i is strongly influenced by vertex j. See Also -------- symmetric_strength_of_connection : symmetric measure used in SA evolution_strength_of_connection : relaxation based strength measure Notes ----- - A symmetric A does not necessarily yield a symmetric strength matrix S - Calls C++ function classical_strength_of_connection References ---------- .. [1] Briggs, W. L., Henson, V. E., McCormick, S. F., "A multigrid tutorial", Second edition. Society for Industrial and Applied Mathematics (SIAM), Philadelphia, PA, 2000. xii+193 pp. ISBN: 0-89871-462-1 .. [2] Trottenberg, U., Oosterlee, C. W., Schuller, A., "Multigrid", Academic Press, Inc., San Diego, CA, 2001. xvi+631 pp. ISBN: 0-12-701070-X Examples -------- >>> import numpy as np >>> from pyamg.gallery import stencil_grid >>> from pyamg.strength import classical_strength_of_connection >>> n=3 >>> stencil = np.array([[-1.0,-1.0,-1.0], ... [-1.0, 8.0,-1.0], ... [-1.0,-1.0,-1.0]]) >>> A = stencil_grid(stencil, (n,n), format='csr') >>> S = classical_strength_of_connection(A, 0.0) """ if (theta < 0 or theta > 1): raise ValueError('expected theta in [0,1]') if (theta < 0 or theta > 1): raise ValueError('expected theta in [0,1]') if sparse.isspmatrix_bsr(A): blocksize = A.blocksize[0] else: blocksize = 1 # Block structure considered before computing SOC if (block == 'block') or sparse.isspmatrix_bsr(A): R, C = A.blocksize if (R != C) or (R < 1): raise ValueError('Matrix must have square blocks') N = int(A.shape[0] / R) # SOC based on maximum absolute value element in each block if norm == 'abs': data = np.max(np.max(np.abs(A.data),axis=1),axis=1) cost[0] += 1 # SOC based on hard minimum of entry in each off-diagonal block elif norm == 'min': data = np.min(np.min(A.data,axis=1),axis=1) cost[0] += 1 # SOC based on Frobenius norms of blocks elif norm == 'fro': data = (np.conjugate(A.data) * A.data).reshape(-1, R*C).sum(axis=1) cost[0] += 1 else: raise ValueError("Invalid choice of norm.") data[np.abs(data)<1e-16] = 0.0 S_rowptr = np.empty_like(A.indptr) S_colinds = np.empty_like(A.indices) S_data = np.empty_like(data) if norm == 'abs' or norm == 'fro': amg_core.classical_strength_of_connection_abs(N, theta, A.indptr, A.indices, data, S_rowptr, S_colinds, S_data) elif norm == 'min': amg_core.classical_strength_of_connection_min(N, theta, A.indptr, A.indices, data, S_rowptr, S_colinds, S_data) else: raise ValueError("Unrecognized option for norm.") # One pass through nnz to find largest entry, one to filter S = sparse.csr_matrix((S_data, S_colinds, S_rowptr), shape=[N, N]) cost[0] += 2 # Take magnitude and scale by largest entry S.data = np.abs(S.data) S = scale_rows_by_largest_entry(S) S.eliminate_zeros() # Assume largest entry can be tracked from filtering. # 1 WU to scale matrix. cost[0] += float(S.nnz) / A.nnz return S # SOC computed based on A as CSR else: S_rowptr = np.empty_like(A.indptr) S_colinds = np.empty_like(A.indices) S_data = np.empty_like(A.data) if norm == 'abs' or norm == 'fro': amg_core.classical_strength_of_connection_abs(A.shape[0], theta, A.indptr, A.indices, A.data, S_rowptr, S_colinds, S_data) elif norm == 'min': amg_core.classical_strength_of_connection_min(A.shape[0], theta, A.indptr, A.indices, A.data, S_rowptr, S_colinds, S_data) else: raise ValueError("Unrecognized option for norm.") # One pass through nnz to find largest entry, one to filter S = sparse.csr_matrix((S_data, S_colinds, S_rowptr), shape=A.shape) cost[0] += 2 if blocksize > 1 and block == 'amalgamate': S = amalgamate(S, blocksize, norm=norm) # Take magnitude and scale by largest entry S.data = np.abs(S.data) S = scale_rows_by_largest_entry(S) S.eliminate_zeros() # Assume largest entry can be tracked from filtering. # 1 WU to scale matrix. cost[0] += float(S.nnz) / A.nnz return S