def get_aggregate(A, strength, aggregate, diagonal_dominance, B, **kwargs): def unpack_arg(v): if isinstance(v, tuple): return v[0], v[1] else: return v, {} # Compute the strength-of-connection matrix C, where larger # C[i,j] denote stronger couplings between i and j. fn, kwargs = unpack_arg(strength) if fn == 'symmetric': C = symmetric_strength_of_connection(A, **kwargs) elif fn == 'classical': C = classical_strength_of_connection(A, **kwargs) elif fn == 'distance': C = distance_strength_of_connection(A, **kwargs) elif (fn == 'ode') or (fn == 'evolution'): if 'B' in kwargs: C = evolution_strength_of_connection(A, **kwargs) else: C = evolution_strength_of_connection(A, B, **kwargs) elif fn == 'energy_based': C = energy_based_strength_of_connection(A, **kwargs) elif fn == 'predefined': C = kwargs['C'].tocsr() elif fn == 'algebraic_distance': C = algebraic_distance(A, **kwargs) elif fn == 'affinity': C = affinity_distance(A, **kwargs) elif fn is None: C = A.tocsr() else: raise ValueError('unrecognized strength of connection method: %s' % str(fn)) # Avoid coarsening diagonally dominant rows flag, kwargs = unpack_arg(diagonal_dominance) if flag: C = eliminate_diag_dom_nodes(A, C, **kwargs) # Compute the aggregation matrix AggOp (i.e., the nodal coarsening of A). # AggOp is a boolean matrix, where the sparsity pattern for the k-th column # denotes the fine-grid nodes agglomerated into k-th coarse-grid node. fn, kwargs = unpack_arg(aggregate) 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 == 'pairwise': AggOp = pairwise_aggregation(A, B, **kwargs)[0] elif fn == 'predefined': AggOp = kwargs['AggOp'].tocsr() else: raise ValueError('unrecognized aggregation method %s' % str(fn)) return AggOp
def test_direct_interpolation(self): for A in self.cases: S = classical_strength_of_connection(A, 0.0) splitting = split.RS(S) result = direct_interpolation(A, S, splitting) expected = reference_direct_interpolation(A, S, splitting) assert_almost_equal(result.todense(), expected.todense())
def test_direct_interpolation(self): for A in self.cases: S = classical_strength_of_connection(A, 0.0) splitting = split.RS( S ) result = direct_interpolation(A,S,splitting) expected = reference_direct_interpolation( A, S, splitting ) assert_almost_equal( result.todense(), expected.todense() )
def test_cljpc_splitting(self): for A in self.cases: S = classical_strength_of_connection(A, 0.0) splitting = split.CLJPc(S) assert (splitting.min() >= 0) # could be all 1s assert_equal(splitting.max(), 1) S.data[:] = 1 # check that all F-nodes are strongly connected to a C-node assert ((splitting + S * splitting).min() > 0)
def test_cljpc_splitting(self): for A in self.cases: S = classical_strength_of_connection(A, 0.0) splitting = split.CLJPc( S ) assert( splitting.min() >= 0 ) #could be all 1s assert_equal( splitting.max(), 1 ) S.data[:] = 1 # check that all F-nodes are strongly connected to a C-node assert( (splitting + S*splitting).min() > 0 )
def test_standard_interpolation(self): for A in self.cases: # the reference code is very slow, so just take a small block of A mini = min(100, A.shape[0]) A = ((A.tocsr()[0:mini, :])[:,0:mini]).tocsr() S = classical_strength_of_connection(A, 0.0) splitting = split.RS(S) result = standard_interpolation(A, S, splitting) expected = reference_standard_interpolation(A, S, splitting) # elasticity produces large entries, so normalize Diff = result - expected Diff.data = abs(Diff.data) expected.data = 1./abs(expected.data) Diff = Diff.multiply(expected) Diff.data[ Diff.data < 1e-7] = 0.0 Diff.eliminate_zeros() assert( Diff.nnz == 0)
def test_classical(self): for A in self.cases: # theta = 0, no entries dropped theta = 0.0 cost = [0] classical_strength_of_connection(A, theta, cost) assert_almost_equal(cost[0], 3.0) for theta in [0.1, 0.25, 0.5, 0.75, 0.9]: cost = [0] classical_strength_of_connection(A, theta, cost) assert (cost[0] <= 3.0) assert (cost[0] > 0.0) # theta = 1, only largest entries in each row remain theta = 1.0 cost = [0] classical_strength_of_connection(A, theta, cost) est = 2.0 + float(A.shape[0]) / A.nnz assert_almost_equal(cost[0], est)
def test_classical(self): for A in self.cases: # theta = 0, no entries dropped theta = 0.0 cost = [0] classical_strength_of_connection(A, theta, cost) assert_almost_equal(cost[0], 3.0) for theta in [0.1,0.25,0.5,0.75,0.9]: cost = [0] classical_strength_of_connection(A, theta, cost) assert(cost[0] <= 3.0) assert(cost[0] > 0.0) # theta = 1, only largest entries in each row remain theta = 1.0 cost = [0] classical_strength_of_connection(A, theta, cost) est = 2.0 + float(A.shape[0]) / A.nnz assert_almost_equal(cost[0],est)
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
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 extend_hierarchy(levels, strength, aggregate, smooth, improve_candidates, diagonal_dominance=False, keep=True): """Extend the multigrid hierarchy. Service routine to implement the strength of connection, aggregation, tentative prolongation construction, and prolongation smoothing. Called by smoothed_aggregation_solver. """ def unpack_arg(v): if isinstance(v, tuple): return v[0], v[1] else: return v, {} A = levels[-1].A B = levels[-1].B if A.symmetry == "nonsymmetric": AH = A.H.asformat(A.format) BH = levels[-1].BH # Compute the strength-of-connection matrix C, where larger # C[i,j] denote stronger couplings between i and j. fn, kwargs = unpack_arg(strength[len(levels) - 1]) if fn == 'symmetric': C = symmetric_strength_of_connection(A, **kwargs) elif fn == 'classical': C = classical_strength_of_connection(A, **kwargs) elif fn == 'distance': C = distance_strength_of_connection(A, **kwargs) elif (fn == 'ode') or (fn == 'evolution'): if 'B' in kwargs: C = evolution_strength_of_connection(A, **kwargs) else: C = evolution_strength_of_connection(A, B, **kwargs) elif fn == 'energy_based': C = energy_based_strength_of_connection(A, **kwargs) elif fn == 'predefined': C = kwargs['C'].tocsr() elif fn == 'algebraic_distance': C = algebraic_distance(A, **kwargs) elif fn == 'affinity': C = affinity_distance(A, **kwargs) elif fn is None: C = A.tocsr() else: raise ValueError('unrecognized strength of connection method: %s' % str(fn)) # Avoid coarsening diagonally dominant rows flag, kwargs = unpack_arg(diagonal_dominance) if flag: C = eliminate_diag_dom_nodes(A, C, **kwargs) # Compute the aggregation matrix AggOp (i.e., the nodal coarsening of A). # AggOp is a boolean matrix, where the sparsity pattern for the k-th column # denotes the fine-grid nodes agglomerated into k-th coarse-grid node. fn, kwargs = unpack_arg(aggregate[len(levels) - 1]) if fn == 'standard': AggOp = standard_aggregation(C, **kwargs)[0] elif fn == 'naive': AggOp = naive_aggregation(C, **kwargs)[0] elif fn == 'lloyd': AggOp = lloyd_aggregation(C, **kwargs)[0] elif fn == 'predefined': AggOp = kwargs['AggOp'].tocsr() else: raise ValueError('unrecognized aggregation method %s' % str(fn)) # Improve near nullspace candidates by relaxing on A B = 0 fn, kwargs = unpack_arg(improve_candidates[len(levels) - 1]) if fn is not None: b = np.zeros((A.shape[0], 1), dtype=A.dtype) B = relaxation_as_linear_operator((fn, kwargs), A, b) * B levels[-1].B = B if A.symmetry == "nonsymmetric": BH = relaxation_as_linear_operator((fn, kwargs), AH, b) * BH levels[-1].BH = BH # Compute the tentative prolongator, T, which is a tentative interpolation # matrix from the coarse-grid to the fine-grid. T exactly interpolates # B_fine = T B_coarse. T, B = fit_candidates(AggOp, B) if A.symmetry == "nonsymmetric": TH, BH = fit_candidates(AggOp, BH) # Smooth the tentative prolongator, so that it's accuracy is greatly # improved for algebraically smooth error. fn, kwargs = unpack_arg(smooth[len(levels) - 1]) if fn == 'jacobi': P = jacobi_prolongation_smoother(A, T, C, B, **kwargs) elif fn == 'richardson': P = richardson_prolongation_smoother(A, T, **kwargs) elif fn == 'energy': P = energy_prolongation_smoother(A, T, C, B, None, (False, {}), **kwargs) elif fn is None: P = T else: raise ValueError('unrecognized prolongation smoother method %s' % str(fn)) # Compute the restriction matrix, R, which interpolates from the fine-grid # to the coarse-grid. If A is nonsymmetric, then R must be constructed # based on A.H. Otherwise R = P.H or P.T. symmetry = A.symmetry if symmetry == 'hermitian': R = P.H elif symmetry == 'symmetric': R = P.T elif symmetry == 'nonsymmetric': fn, kwargs = unpack_arg(smooth[len(levels) - 1]) if fn == 'jacobi': R = jacobi_prolongation_smoother(AH, TH, C, BH, **kwargs).H elif fn == 'richardson': R = richardson_prolongation_smoother(AH, TH, **kwargs).H elif fn == 'energy': R = energy_prolongation_smoother(AH, TH, C, BH, None, (False, {}), **kwargs) R = R.H elif fn is None: R = T.H else: raise ValueError('unrecognized prolongation smoother method %s' % str(fn)) if keep: levels[-1].C = C # strength of connection matrix levels[-1].AggOp = AggOp # aggregation operator levels[-1].T = T # tentative prolongator levels[-1].P = P # smoothed prolongator levels[-1].R = R # restriction operator levels.append(multilevel_solver.level()) A = R * A * P # Galerkin operator A.symmetry = symmetry levels[-1].A = A levels[-1].B = B # right near nullspace candidates if A.symmetry == "nonsymmetric": levels[-1].BH = BH # left near nullspace candidates
def extend_hierarchy(levels, strength, CF, keep): """ helper function for local methods """ def unpack_arg(v): if isinstance(v, tuple): return v[0], v[1] else: return v, {} A = levels[-1].A # Compute the strength-of-connection matrix C, where larger # C[i,j] denote stronger couplings between i and j. fn, kwargs = unpack_arg(strength) if fn == 'symmetric': C = symmetric_strength_of_connection(A, **kwargs) elif fn == 'classical': C = classical_strength_of_connection(A, **kwargs) elif fn == 'distance': C = distance_strength_of_connection(A, **kwargs) elif (fn == 'ode') or (fn == 'evolution'): C = evolution_strength_of_connection(A, **kwargs) elif fn == 'energy_based': C = energy_based_strength_of_connection(A, **kwargs) elif fn == 'algebraic_distance': C = algebraic_distance(A, **kwargs) elif fn is None: C = A else: raise ValueError('unrecognized strength of connection method: %s' % str(fn)) # Generate the C/F splitting fn, kwargs = unpack_arg(CF) if fn == 'RS': splitting = split.RS(C) elif fn == 'PMIS': splitting = split.PMIS(C) elif fn == 'PMISc': splitting = split.PMISc(C) elif fn == 'CLJP': splitting = split.CLJP(C) elif fn == 'CLJPc': splitting = split.CLJPc(C) else: raise ValueError('unknown C/F splitting method (%s)' % CF) # Generate the interpolation matrix that maps from the coarse-grid to the # fine-grid P = direct_interpolation(A, C, splitting) # Generate the restriction matrix that maps from the fine-grid to the # coarse-grid R = P.T.tocsr() # Store relevant information for this level if keep: levels[-1].C = C # strength of connection matrix levels[-1].splitting = splitting # C/F splitting levels[-1].P = P # prolongation operator levels[-1].R = R # restriction operator levels.append(multilevel_solver.level()) # Form next level through Galerkin product A = R * A * P levels[-1].A = A
def distance_two_interpolation(A, C, splitting, theta=None, norm='min', plus_i=True, cost=[0]): """Create prolongator using distance-two AMG interpolation (extended+i interpolaton). Parameters ---------- A : {csr_matrix} NxN matrix in CSR format C : {csr_matrix} Strength-of-Connection matrix Must have zero diagonal splitting : array C/F splitting stored in an array of length N theta : float in [0,1), default None theta value defining strong connections in a classical AMG sense. Provide if different SOC used for P than for CF-splitting; otherwise, theta = None. norm : string, default 'abs' Norm used in redefining classical SOC. Options are 'min' and 'abs' for CSR matrices, and 'min', 'abs', and 'fro' for BSR matrices. See strength.py for more information. plus_i : bool, default True Use "Extended+i" interpolation from [0] as opposed to "Extended" interpolation. Typically gives better interpolation with minimal added expense. Returns ------- P : {csr_matrix} Prolongator using standard interpolation References ---------- [0] "Distance-Two Interpolation for Parallel Algebraic Multigrid," H. De Sterck, R. Falgout, J. Nolting, U. M. Yang, (2007). Examples -------- >>> from pyamg.gallery import poisson >>> from pyamg.classical import standard_interpolation >>> import numpy as np >>> A = poisson((5,),format='csr') >>> splitting = np.array([1,0,1,0,1], dtype='intc') >>> P = standard_interpolation(A, A, splitting) >>> print P.todense() [[ 1. 0. 0. ] [ 0.5 0.5 0. ] [ 0. 1. 0. ] [ 0. 0.5 0.5] [ 0. 0. 1. ]] """ if not isspmatrix_csr(C): raise TypeError('Expected csr_matrix SOC matrix, C.') nc = np.sum(splitting) n = A.shape[0] # Block BSR format. Transfer A to CSR and the splitting and SOC matrix to have # DOFs corresponding to CSR A if isspmatrix_bsr(A): temp_A = A.tocsr() splitting0 = splitting * np.ones((A.blocksize[0], 1), dtype='intc') splitting0 = np.reshape(splitting0, (np.prod(splitting0.shape), ), order='F') if theta is not None: C0 = classical_strength_of_connection(A, theta=theta, norm=norm, cost=cost) C0 = UnAmal(C0, A.blocksize[0], A.blocksize[1]) else: C0 = UnAmal(C, A.blocksize[0], A.blocksize[1]) C0 = C0.tocsr() C0.eliminate_zeros() # Interpolation weights are computed based on entries in A, but subject to # the sparsity pattern of C. So, copy the entries of A into the # sparsity pattern of C. C0.data[:] = 1.0 C0 = C0.multiply(temp_A) P_indptr = np.empty_like(temp_A.indptr) amg_core.distance_two_amg_interpolation_pass1(temp_A.shape[0], C0.indptr, C0.indices, splitting0, P_indptr) nnz = P_indptr[-1] P_colinds = np.empty(nnz, dtype=P_indptr.dtype) P_data = np.empty(nnz, dtype=temp_A.dtype) if plus_i: amg_core.extended_plusi_interpolation_pass2( temp_A.shape[0], temp_A.indptr, temp_A.indices, temp_A.data, C0.indptr, C0.indices, C0.data, splitting0, P_indptr, P_colinds, P_data) else: amg_core.extended_interpolation_pass2(temp_A.shape[0], temp_A.indptr, temp_A.indices, temp_A.data, C0.indptr, C0.indices, C0.data, splitting0, P_indptr, P_colinds, P_data) nc = np.sum(splitting0) n = A.shape[0] P = csr_matrix((P_data, P_colinds, P_indptr), shape=[n, nc]) return P.tobsr(blocksize=A.blocksize) # CSR format else: if theta is not None: C0 = classical_strength_of_connection(A, theta=theta, norm=norm, cost=cost) else: C0 = C.copy() C0.eliminate_zeros() # Interpolation weights are computed based on entries in A, but subject to # the sparsity pattern of C. So, copy the entries of A into the # sparsity pattern of C. C0.data[:] = 1.0 C0 = C0.multiply(A) P_indptr = np.empty_like(A.indptr) amg_core.distance_two_amg_interpolation_pass1(A.shape[0], C0.indptr, C0.indices, splitting, P_indptr) nnz = P_indptr[-1] P_colinds = np.empty(nnz, dtype=P_indptr.dtype) P_data = np.empty(nnz, dtype=A.dtype) if plus_i: amg_core.extended_plusi_interpolation_pass2( A.shape[0], A.indptr, A.indices, A.data, C0.indptr, C0.indices, C0.data, splitting, P_indptr, P_colinds, P_data) else: amg_core.extended_interpolation_pass2(A.shape[0], A.indptr, A.indices, A.data, C0.indptr, C0.indices, C0.data, splitting, P_indptr, P_colinds, P_data) nc = np.sum(splitting) n = A.shape[0] return csr_matrix((P_data, P_colinds, P_indptr), shape=[n, nc])
def weighted_matching(A, B=None, theta=0.5, use_weights=True, get_SOC=False, cost=[0.0], **kwargs): """ Pairwise aggregation of nodes using Drake approximate 1/2-matching algorithm. Parameters ---------- A : csr_matrix or bsr_matrix matrix for linear system. B : array_like : default None Right near-nullspace candidates stored in the columns of an NxK array. If no target vector provided, constant vector is used. In the case of multiple targets, k>1, only the first is used to construct coarse grid matrices for pairwise aggregations. use_weights : {bool} : default True 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. get_SOC : {bool} : default False TODO theta : float connections deemed "strong" if |a_ij| > theta*|a_ii| NOTES ----- - Not implemented for block systems or complex. + Need to define how a matching is done nodally. + Also must consider what targets are used to form coarse grid in nodal approach... + Drake should be accessible in complex, but not Notay due to the hard minimum. Is there a < operator overloaded for complex? Could I overload it perhaps? Probably would do magnitude or something though, which is not what we want... REFERENCES ---------- [1] D'Ambra, Pasqua, and Panayot S. Vassilevski. "Adaptive AMG with coarsening based on compatible weighted matching." Computing and Visualization in Science 16.2 (2013): 59-76. [2] Drake, Doratha E., and Stefan Hougardy. "A simple approximation algorithm for the weighted matching problem." Information Processing Letters 85.4 (2003): 211-213. """ def unpack_arg(v): if isinstance(v, tuple): return v[0], v[1] else: return v, {} if A.dtype == 'complex': raise TypeError("Not currently implemented for complex.") if (A.getformat() != 'csr'): try: A = A.tocsr() except: raise TypeError("Must pass in CSR matrix, or sparse matrix " "which can be converted to CSR.") n = A.shape[0] # If target vectors provided, take first. if B is not None: if len(B.shape) == 2: target = B[:, 0] else: target = B[:, ] else: target = None # Compute weights if function provided, otherwise let W = A if use_weights: weights = np.empty((A.nnz, ), dtype=A.dtype) temp_cost = np.ones((1, ), dtype=A.dtype) if target is None: amg_core.compute_weights(A.indptr, A.indices, A.data, weights, temp_cost) else: amg_core.compute_weights(A.indptr, A.indices, A.data, weights, target, temp_cost) cost[0] += temp_cost[0] / float(A.nnz) else: weights = A.data # Get CF splitting temp_cost = np.ones((1, ), dtype=A.dtype) splitting = np.empty(n, dtype='int32') amg_core.drake_CF_matching(A.indptr, A.indices, weights, splitting, theta, temp_cost) cost[0] += temp_cost[0] / float(A.nnz) if get_SOC: temp = csr_matrix((weights, A.indices, A.indptr), copy=True) C = classical_strength_of_connection(temp, theta, cost=cost) return splitting, C else: return splitting, None
def extend_hierarchy(levels, strength, CF, interpolation, restriction, keep): """ helper function for local methods """ A = levels[-1].A # Compute the strength-of-connection matrix C, where larger # C[i,j] denote stronger couplings between i and j. fn, kwargs = unpack_arg(strength) if fn == 'symmetric': C = symmetric_strength_of_connection(A, **kwargs) elif fn == 'classical': C = classical_strength_of_connection(A, **kwargs) elif fn == 'distance': C = distance_strength_of_connection(A, **kwargs) elif (fn == 'ode') or (fn == 'evolution'): C = evolution_strength_of_connection(A, **kwargs) elif fn == 'energy_based': C = energy_based_strength_of_connection(A, **kwargs) elif fn == 'algebraic_distance': C = algebraic_distance(A, **kwargs) elif fn == 'affinity': C = affinity_distance(A, **kwargs) elif fn is None: C = A else: raise ValueError('unrecognized strength of connection method: %s' % str(fn)) levels[-1].complexity['strength'] = kwargs['cost'][0] # Generate the C/F splitting fn, kwargs = unpack_arg(CF) if fn == 'RS': splitting = split.RS(C, **kwargs) elif fn == 'PMIS': splitting = split.PMIS(C, **kwargs) elif fn == 'PMISc': splitting = split.PMISc(C, **kwargs) elif fn == 'CLJP': splitting = split.CLJP(C, **kwargs) elif fn == 'CLJPc': splitting = split.CLJPc(C, **kwargs) elif fn == 'CR': splitting = CR(C, **kwargs) else: raise ValueError('unknown C/F splitting method (%s)' % CF) levels[-1].complexity['CF'] = kwargs['cost'][0] # Generate the interpolation matrix that maps from the coarse-grid to the # fine-grid fn, kwargs = unpack_arg(interpolation) if fn == 'standard': P = standard_interpolation(A, C, splitting, **kwargs) elif fn == 'distance_two': P = distance_two_interpolation(A, C, splitting, **kwargs) elif fn == 'direct': P = direct_interpolation(A, C, splitting, **kwargs) elif fn == 'one_point': P = one_point_interpolation(A, C, splitting, **kwargs) elif fn == 'injection': P = injection_interpolation(A, splitting, **kwargs) else: raise ValueError('unknown interpolation method (%s)' % interpolation) levels[-1].complexity['interpolate'] = kwargs['cost'][0] # Generate the restriction matrix that maps from the fine-grid to the # coarse-grid. Must make sure transpose matrices remain in CSR or BSR fn, kwargs = unpack_arg(restriction) if isspmatrix_csr(A): if restriction == 'galerkin': R = P.T.tocsr() elif fn == 'standard': temp_A = A.T.tocsr() temp_C = C.T.tocsr() R = standard_interpolation(temp_A, temp_C, splitting, **kwargs) R = R.T.tocsr() elif fn == 'distance_two': temp_A = A.T.tocsr() temp_C = C.T.tocsr() R = distance_two_interpolation(temp_A, temp_C, splitting, **kwargs) R = R.T.tocsr() elif fn == 'direct': temp_A = A.T.tocsr() temp_C = C.T.tocsr() R = direct_interpolation(temp_A, temp_C, splitting, **kwargs) R = R.T.tocsr() elif fn == 'one_point': # Don't need A^T here temp_C = C.T.tocsr() R = one_point_interpolation(A, temp_C, splitting, **kwargs) R = R.T.tocsr() elif fn == 'injection': # Don't need A^T or C^T here R = injection_interpolation(A, splitting, **kwargs) R = R.T.tocsr() else: raise ValueError('unknown interpolation method (%s)' % interpolation) else: if restriction == 'galerkin': R = P.T.tobsr() elif fn == 'standard': temp_A = A.T.tobsr() temp_C = C.T.tocsr() R = standard_interpolation(temp_A, temp_C, splitting, **kwargs) R = R.T.tobsr() elif fn == 'distance_two': temp_A = A.T.tobsr() temp_C = C.T.tocsr() R = distance_two_interpolation(temp_A, temp_C, splitting, **kwargs) R = R.T.tobsr() elif fn == 'direct': temp_A = A.T.tobsr() temp_C = C.T.tocsr() R = direct_interpolation(temp_A, temp_C, splitting, **kwargs) R = R.T.tobsr() elif fn == 'one_point': # Don't need A^T here temp_C = C.T.tocsr() R = one_point_interpolation(A, temp_C, splitting, **kwargs) R = R.T.tobsr() elif fn == 'injection': # Don't need A^T or C^T here R = injection_interpolation(A, splitting, **kwargs) R = R.T.tobsr() else: raise ValueError('unknown interpolation method (%s)' % interpolation) levels[-1].complexity['restriction'] = kwargs['cost'][0] # Store relevant information for this level if keep: levels[-1].C = C # strength of connection matrix levels[-1].P = P # prolongation operator levels[-1].R = R # restriction operator levels[-1].splitting = splitting # C/F splitting # Form coarse grid operator, get complexity levels[-1].complexity['RAP'] = mat_mat_complexity(R,A) / float(A.nnz) RA = R * A levels[-1].complexity['RAP'] += mat_mat_complexity(RA,P) / float(A.nnz) A = RA * P # Galerkin operator, Ac = RAP # Make sure coarse-grid operator is in correct sparse format if (isspmatrix_csr(P) and (not isspmatrix_csr(A))): A = A.tocsr() elif (isspmatrix_bsr(P) and (not isspmatrix_bsr(A))): A = A.tobsr() # Form next level through Galerkin product levels.append(multilevel_solver.level()) levels[-1].A = A
def extend_hierarchy(levels, strength, CF, interp, keep): """ helper function for local methods """ def unpack_arg(v): if isinstance(v, tuple): return v[0], v[1] else: return v, {} A = levels[-1].A block_starts = levels[-1].block_starts verts = levels[-1].verts # If this is a system, apply the unknown approach by coarsening and generating interpolation based on each diagonal block of A if (block_starts): A_diag = extract_diagonal_blocks(A, block_starts) else: A_diag = [A] # Compute the strength-of-connection matrix C, where larger # C[i,j] denote stronger couplings between i and j. C_diag = [] P_diag = [] splitting = [] next_lvl_block_starts = [0] block_cnt = 0 for mat in A_diag: fn, kwargs = unpack_arg(strength) if fn == 'symmetric': C_diag.append( symmetric_strength_of_connection(mat, **kwargs) ) elif fn == 'classical': C_diag.append( classical_strength_of_connection(mat, **kwargs) ) elif fn == 'distance': C_diag.append( distance_strength_of_connection(mat, **kwargs) ) elif (fn == 'ode') or (fn == 'evolution'): C_diag.append( evolution_strength_of_connection(mat, **kwargs) ) elif fn == 'energy_based': C_diag.append( energy_based_strength_of_connection(mat, **kwargs) ) elif fn == 'algebraic_distance': C_diag.append( algebraic_distance(mat, **kwargs) ) elif fn == 'affinity': C_diag.append( affinity_distance(mat, **kwargs) ) elif fn is None: C_diag.append( mat ) else: raise ValueError('unrecognized strength of connection method: %s' % str(fn)) # Generate the C/F splitting fn, kwargs = unpack_arg(CF) if fn == 'RS': splitting.append( split.RS(C_diag[-1]) ) elif fn == 'PMIS': splitting.append( split.PMIS(C_diag[-1]) ) elif fn == 'PMISc': splitting.append( split.PMISc(C_diag[-1]) ) elif fn == 'CLJP': splitting.append( split.CLJP(C_diag[-1]) ) elif fn == 'CLJPc': splitting.append( split.CLJPc(C_diag[-1]) ) elif fn == 'Shifted2DCoarsening': splitting.append( split.Shifted2DCoarsening(C_diag[-1]) ) else: raise ValueError('unknown C/F splitting method (%s)' % CF) # Generate the interpolation matrix that maps from the coarse-grid to the # fine-grid fn, kwargs = unpack_arg(interp) if fn == 'standard': P_diag.append( standard_interpolation(mat, C_diag[-1], splitting[-1]) ) elif fn == 'direct': P_diag.append( direct_interpolation(mat, C_diag[-1], splitting[-1]) ) else: raise ValueError('unknown interpolation method (%s)' % interp) next_lvl_block_starts.append( next_lvl_block_starts[-1] + P_diag[-1].shape[1]) block_cnt = block_cnt + 1 P = block_diag(P_diag) # Generate the restriction matrix that maps from the fine-grid to the # coarse-grid R = P.T.tocsr() # Store relevant information for this level splitting = numpy.concatenate(splitting) if keep: C = block_diag(C_diag) levels[-1].C = C # strength of connection matrix levels[-1].splitting = splitting # C/F splitting levels[-1].P = P # prolongation operator levels[-1].R = R # restriction operator levels.append(multilevel_solver.level()) # Form next level through Galerkin product # !!! For systems, how do I propogate the block structure information down to the next grid? Especially if the blocks are different sizes? !!! A = R * A * P levels[-1].A = A if (block_starts): levels[-1].block_starts = next_lvl_block_starts else: levels[-1].block_starts = None # If called for, output a visualization of the C/F splitting if (verts.any()): new_verts = numpy.empty([P.shape[1], 2]) cnt = 0 for i in range(len(splitting)): if (splitting[i]): new_verts[cnt] = verts[i] cnt = cnt + 1 levels[-1].verts = new_verts else: levels[-1].verts = numpy.zeros(1)
def extend_hierarchy(levels, strength, CF, interpolation, restriction, keep): """ helper function for local methods """ A = levels[-1].A # Compute the strength-of-connection matrix C, where larger # C[i,j] denote stronger couplings between i and j. fn, kwargs = unpack_arg(strength) if fn == 'symmetric': C = symmetric_strength_of_connection(A, **kwargs) elif fn == 'classical': C = classical_strength_of_connection(A, **kwargs) elif fn == 'distance': C = distance_strength_of_connection(A, **kwargs) elif (fn == 'ode') or (fn == 'evolution'): C = evolution_strength_of_connection(A, **kwargs) elif fn == 'energy_based': C = energy_based_strength_of_connection(A, **kwargs) elif fn == 'algebraic_distance': C = algebraic_distance(A, **kwargs) elif fn == 'affinity': C = affinity_distance(A, **kwargs) elif fn is None: C = A else: raise ValueError('unrecognized strength of connection method: %s' % str(fn)) levels[-1].complexity['strength'] = kwargs['cost'][0] # Generate the C/F splitting fn, kwargs = unpack_arg(CF) if fn == 'RS': splitting = split.RS(C, **kwargs) elif fn == 'PMIS': splitting = split.PMIS(C, **kwargs) elif fn == 'PMISc': splitting = split.PMISc(C, **kwargs) elif fn == 'CLJP': splitting = split.CLJP(C, **kwargs) elif fn == 'CLJPc': splitting = split.CLJPc(C, **kwargs) elif fn == 'CR': splitting = CR(C, **kwargs) else: raise ValueError('unknown C/F splitting method (%s)' % CF) levels[-1].complexity['CF'] = kwargs['cost'][0] # Generate the interpolation matrix that maps from the coarse-grid to the # fine-grid fn, kwargs = unpack_arg(interpolation) if fn == 'standard': P = standard_interpolation(A, C, splitting, **kwargs) elif fn == 'distance_two': P = distance_two_interpolation(A, C, splitting, **kwargs) elif fn == 'direct': P = direct_interpolation(A, C, splitting, **kwargs) elif fn == 'one_point': P = one_point_interpolation(A, C, splitting, **kwargs) elif fn == 'injection': P = injection_interpolation(A, splitting, **kwargs) else: raise ValueError('unknown interpolation method (%s)' % interpolation) levels[-1].complexity['interpolate'] = kwargs['cost'][0] # Generate the restriction matrix that maps from the fine-grid to the # coarse-grid. Must make sure transpose matrices remain in CSR or BSR fn, kwargs = unpack_arg(restriction) if isspmatrix_csr(A): if restriction == 'galerkin': R = P.T.tocsr() elif fn == 'standard': temp_A = A.T.tocsr() temp_C = C.T.tocsr() R = standard_interpolation(temp_A, temp_C, splitting, **kwargs) R = R.T.tocsr() elif fn == 'distance_two': temp_A = A.T.tocsr() temp_C = C.T.tocsr() R = distance_two_interpolation(temp_A, temp_C, splitting, **kwargs) R = R.T.tocsr() elif fn == 'direct': temp_A = A.T.tocsr() temp_C = C.T.tocsr() R = direct_interpolation(temp_A, temp_C, splitting, **kwargs) R = R.T.tocsr() elif fn == 'one_point': # Don't need A^T here temp_C = C.T.tocsr() R = one_point_interpolation(A, temp_C, splitting, **kwargs) R = R.T.tocsr() elif fn == 'injection': # Don't need A^T or C^T here R = injection_interpolation(A, splitting, **kwargs) R = R.T.tocsr() else: raise ValueError('unknown interpolation method (%s)' % interpolation) else: if restriction == 'galerkin': R = P.T.tobsr() elif fn == 'standard': temp_A = A.T.tobsr() temp_C = C.T.tocsr() R = standard_interpolation(temp_A, temp_C, splitting, **kwargs) R = R.T.tobsr() elif fn == 'distance_two': temp_A = A.T.tobsr() temp_C = C.T.tocsr() R = distance_two_interpolation(temp_A, temp_C, splitting, **kwargs) R = R.T.tobsr() elif fn == 'direct': temp_A = A.T.tobsr() temp_C = C.T.tocsr() R = direct_interpolation(temp_A, temp_C, splitting, **kwargs) R = R.T.tobsr() elif fn == 'one_point': # Don't need A^T here temp_C = C.T.tocsr() R = one_point_interpolation(A, temp_C, splitting, **kwargs) R = R.T.tobsr() elif fn == 'injection': # Don't need A^T or C^T here R = injection_interpolation(A, splitting, **kwargs) R = R.T.tobsr() else: raise ValueError('unknown interpolation method (%s)' % interpolation) levels[-1].complexity['restriction'] = kwargs['cost'][0] # Store relevant information for this level if keep: levels[-1].C = C # strength of connection matrix levels[-1].P = P # prolongation operator levels[-1].R = R # restriction operator levels[-1].splitting = splitting # C/F splitting # Form coarse grid operator, get complexity levels[-1].complexity['RAP'] = mat_mat_complexity(R, A) / float(A.nnz) RA = R * A levels[-1].complexity['RAP'] += mat_mat_complexity(RA, P) / float(A.nnz) A = RA * P # Galerkin operator, Ac = RAP # Make sure coarse-grid operator is in correct sparse format if (isspmatrix_csr(P) and (not isspmatrix_csr(A))): A = A.tocsr() elif (isspmatrix_bsr(P) and (not isspmatrix_bsr(A))): A = A.tobsr() # Form next level through Galerkin product levels.append(multilevel_solver.level()) levels[-1].A = A
def standard_interpolation(A, C, splitting, theta=None, norm='min', modified=True, cost=[0]): """Create prolongator using standard interpolation Parameters ---------- A : {csr_matrix} NxN matrix in CSR format C : {csr_matrix} Strength-of-Connection matrix Must have zero diagonal splitting : array C/F splitting stored in an array of length N theta : float in [0,1), default None theta value defining strong connections in a classical AMG sense. Provide if different SOC used for P than for CF-splitting; otherwise, theta = None. norm : string, default 'abs' Norm used in redefining classical SOC. Options are 'min' and 'abs' for CSR matrices, and 'min', 'abs', and 'fro' for BSR matrices. See strength.py for more information. modified : bool, default True Use modified classical interpolation. More robust if RS coarsening with second pass is not used for CF splitting. Ignores interpolating from strong F-connections without a common C-neighbor. Returns ------- P : {csr_matrix} Prolongator using standard interpolation Examples -------- >>> from pyamg.gallery import poisson >>> from pyamg.classical import standard_interpolation >>> import numpy as np >>> A = poisson((5,),format='csr') >>> splitting = np.array([1,0,1,0,1], dtype='intc') >>> P = standard_interpolation(A, A, splitting) >>> print P.todense() [[ 1. 0. 0. ] [ 0.5 0.5 0. ] [ 0. 1. 0. ] [ 0. 0.5 0.5] [ 0. 0. 1. ]] """ if not isspmatrix_csr(C): raise TypeError('Expected csr_matrix SOC matrix, C.') nc = np.sum(splitting) n = A.shape[0] # Block BSR format. Transfer A to CSR and the splitting and SOC matrix to have # DOFs corresponding to CSR A if isspmatrix_bsr(A): temp_A = A.tocsr() splitting0 = splitting * np.ones((A.blocksize[0], 1), dtype='intc') splitting0 = np.reshape(splitting0, (np.prod(splitting0.shape), ), order='F') if theta is not None: C0 = classical_strength_of_connection(A, theta=theta, norm=norm, cost=cost) C0 = UnAmal(C0, A.blocksize[0], A.blocksize[1]) else: C0 = UnAmal(C, A.blocksize[0], A.blocksize[1]) C0 = C0.tocsr() # Use modified standard interpolation by ignoring strong F-connections that do # not have a common C-point. if modified: amg_core.remove_strong_FF_connections(temp_A.shape[0], C0.indptr, C0.indices, C0.data, splitting) C0.eliminate_zeros() # Interpolation weights are computed based on entries in A, but subject to # the sparsity pattern of C. So, copy the entries of A into the # sparsity pattern of C. C0.data[:] = 1.0 C0 = C0.multiply(temp_A) P_indptr = np.empty_like(temp_A.indptr) amg_core.rs_standard_interpolation_pass1(temp_A.shape[0], C0.indptr, C0.indices, splitting0, P_indptr) nnz = P_indptr[-1] P_colinds = np.empty(nnz, dtype=P_indptr.dtype) P_data = np.empty(nnz, dtype=temp_A.dtype) if modified: amg_core.mod_standard_interpolation_pass2( temp_A.shape[0], temp_A.indptr, temp_A.indices, temp_A.data, C0.indptr, C0.indices, C0.data, splitting0, P_indptr, P_colinds, P_data) else: amg_core.rs_standard_interpolation_pass2( temp_A.shape[0], temp_A.indptr, temp_A.indices, temp_A.data, C0.indptr, C0.indices, C0.data, splitting0, P_indptr, P_colinds, P_data) nc = np.sum(splitting0) n = A.shape[0] P = csr_matrix((P_data, P_colinds, P_indptr), shape=[n, nc]) return P.tobsr(blocksize=A.blocksize) # CSR format else: if theta is not None: C0 = classical_strength_of_connection(A, theta=theta, norm=norm, cost=cost) else: C0 = C.copy() # Use modified standard interpolation by ignoring strong F-connections that do # not have a common C-point. if modified: amg_core.remove_strong_FF_connections(A.shape[0], C0.indptr, C0.indices, C0.data, splitting) C0.eliminate_zeros() # Interpolation weights are computed based on entries in A, but subject to # the sparsity pattern of C. So, copy the entries of A into the # sparsity pattern of C. C0.data[:] = 1.0 C0 = C0.multiply(A) P_indptr = np.empty_like(A.indptr) amg_core.rs_standard_interpolation_pass1(A.shape[0], C0.indptr, C0.indices, splitting, P_indptr) nnz = P_indptr[-1] P_colinds = np.empty(nnz, dtype=P_indptr.dtype) P_data = np.empty(nnz, dtype=A.dtype) if modified: amg_core.mod_standard_interpolation_pass2( A.shape[0], A.indptr, A.indices, A.data, C0.indptr, C0.indices, C0.data, splitting, P_indptr, P_colinds, P_data) else: amg_core.rs_standard_interpolation_pass2( A.shape[0], A.indptr, A.indices, A.data, C0.indptr, C0.indices, C0.data, splitting, P_indptr, P_colinds, P_data) nc = np.sum(splitting) n = A.shape[0] return csr_matrix((P_data, P_colinds, P_indptr), shape=[n, nc])
def extend_hierarchy(levels, strength, CF, interp, restrict, filter_operator, coarse_grid_P, coarse_grid_R, keep): """ helper function for local methods """ # Filter operator. Need to keep original matrix on fineest level for # computing residuals if (filter_operator is not None) and (filter_operator[1] != 0): if len(levels) == 1: A = deepcopy(levels[-1].A) else: A = levels[-1].A filter_matrix_rows(A, filter_operator[1], diagonal=True, lump=filter_operator[0]) else: A = levels[-1].A # Check if matrix was filtered to be diagonal --> coarsest grid if A.nnz == A.shape[0]: return 1 # Zero initial complexities for strength, splitting and interpolation levels[-1].complexity['CF'] = 0.0 levels[-1].complexity['strength'] = 0.0 levels[-1].complexity['interpolate'] = 0.0 # Compute the strength-of-connection matrix C, where larger # C[i,j] denote stronger couplings between i and j. fn, kwargs = unpack_arg(strength) if fn == 'symmetric': C = symmetric_strength_of_connection(A, **kwargs) elif fn == 'classical': C = classical_strength_of_connection(A, **kwargs) elif fn == 'distance': C = distance_strength_of_connection(A, **kwargs) elif (fn == 'ode') or (fn == 'evolution'): C = evolution_strength_of_connection(A, **kwargs) elif fn == 'energy_based': C = energy_based_strength_of_connection(A, **kwargs) elif fn == 'algebraic_distance': C = algebraic_distance(A, **kwargs) elif fn == 'affinity': C = affinity_distance(A, **kwargs) elif fn is None: C = A else: raise ValueError('unrecognized strength of connection method: %s' % str(fn)) levels[-1].complexity['strength'] += kwargs['cost'][0] * A.nnz / float( A.nnz) # Generate the C/F splitting fn, kwargs = unpack_arg(CF) if fn == 'RS': splitting = RS(C, **kwargs) elif fn == 'PMIS': splitting = PMIS(C, **kwargs) elif fn == 'PMISc': splitting = PMISc(C, **kwargs) elif fn == 'CLJP': splitting = CLJP(C, **kwargs) elif fn == 'CLJPc': splitting = CLJPc(C, **kwargs) elif fn == 'CR': splitting = CR(C, **kwargs) elif fn == 'weighted_matching': splitting, soc = weighted_matching(C, **kwargs) if soc is not None: C = soc else: raise ValueError('unknown C/F splitting method (%s)' % CF) levels[-1].complexity['CF'] += kwargs['cost'][0] * C.nnz / float(A.nnz) temp = np.sum(splitting) if (temp == len(splitting)) or (temp == 0): return 1 # Generate the interpolation matrix that maps from the coarse-grid to the # fine-grid r_flag = False fn, kwargs = unpack_arg(interp) if fn == 'standard': P = standard_interpolation(A, C, splitting, **kwargs) elif fn == 'distance_two': P = distance_two_interpolation(A, C, splitting, **kwargs) elif fn == 'direct': P = direct_interpolation(A, C, splitting, **kwargs) elif fn == 'one_point': P = one_point_interpolation(A, C, splitting, **kwargs) elif fn == 'inject': P = injection_interpolation(A, splitting, **kwargs) elif fn == 'neumann': P = neumann_ideal_interpolation(A, splitting, **kwargs) elif fn == 'scaledAfc': P = scaled_Afc_interpolation(A, splitting, **kwargs) elif fn == 'air': if isspmatrix_bsr(A): temp_A = bsr_matrix(A.T) P = local_AIR(temp_A, splitting, **kwargs) P = bsr_matrix(P.T) else: temp_A = csr_matrix(A.T) P = local_AIR(temp_A, splitting, **kwargs) P = csr_matrix(P.T) elif fn == 'restrict': r_flag = True else: raise ValueError('unknown interpolation method (%s)' % interp) levels[-1].complexity['interpolate'] += kwargs['cost'][0] * A.nnz / float( A.nnz) # Build restriction operator fn, kwargs = unpack_arg(restrict) if fn is None: R = P.T elif fn == 'air': R = local_AIR(A, splitting, **kwargs) elif fn == 'neumann': R = neumann_AIR(A, splitting, **kwargs) elif fn == 'one_point': # Don't need A^T here temp_C = C.T.tocsr() R = one_point_interpolation(A, temp_C, splitting, **kwargs) if isspmatrix_bsr(A): R = R.T.tobsr() else: R = R.T.tocsr() elif fn == 'inject': # Don't need A^T or C^T here R = injection_interpolation(A, splitting, **kwargs) if isspmatrix_bsr(A): R = R.T.tobsr() else: R = R.T.tocsr() elif fn == 'standard': if isspmatrix_bsr(A): temp_A = A.T.tobsr() temp_C = C.T.tobsr() R = standard_interpolation(temp_A, temp_C, splitting, **kwargs) R = R.T.tobsr() else: temp_A = A.T.tocsr() temp_C = C.T.tocsr() R = standard_interpolation(temp_A, temp_C, splitting, **kwargs) R = R.T.tocsr() elif fn == 'distance_two': if isspmatrix_bsr(A): temp_A = A.T.tobsr() temp_C = C.T.tobsr() R = distance_two_interpolation(temp_A, temp_C, splitting, **kwargs) R = R.T.tobsr() else: temp_A = A.T.tocsr() temp_C = C.T.tocsr() R = distance_two_interpolation(temp_A, temp_C, splitting, **kwargs) R = R.T.tocsr() elif fn == 'direct': if isspmatrix_bsr(A): temp_A = A.T.tobsr() temp_C = C.T.tobsr() R = direct_interpolation(temp_A, temp_C, splitting, **kwargs) R = R.T.tobsr() else: temp_A = A.T.tocsr() temp_C = C.T.tocsr() R = direct_interpolation(temp_A, temp_C, splitting, **kwargs) R = R.T.tocsr() else: raise ValueError('unknown restriction method (%s)' % restrict) # If set P = R^T if r_flag: P = R.T # Optional different interpolation for RAP fn, kwargs = unpack_arg(coarse_grid_P) if fn == 'standard': P_temp = standard_interpolation(A, C, splitting, **kwargs) elif fn == 'distance_two': P_temp = distance_two_interpolation(A, C, splitting, **kwargs) elif fn == 'direct': P_temp = direct_interpolation(A, C, splitting, **kwargs) elif fn == 'one_point': P_temp = one_point_interpolation(A, C, splitting, **kwargs) elif fn == 'inject': P_temp = injection_interpolation(A, splitting, **kwargs) elif fn == 'neumann': P_temp = neumann_ideal_interpolation(A, splitting, **kwargs) elif fn == 'air': if isspmatrix_bsr(A): temp_A = bsr_matrix(A.T) P_temp = local_AIR(temp_A, splitting, **kwargs) P_temp = bsr_matrix(P_temp.T) else: temp_A = csr_matrix(A.T) P_temp = local_AIR(temp_A, splitting, **kwargs) P_temp = csr_matrix(P_temp.T) else: P_temp = P # Optional different restriction for RAP fn, kwargs = unpack_arg(coarse_grid_R) if fn == 'air': R_temp = local_AIR(A, splitting, **kwargs) elif fn == 'neumann': R_temp = neumann_AIR(A, splitting, **kwargs) elif fn == 'one_point': # Don't need A^T here temp_C = C.T.tocsr() R_temp = one_point_interpolation(A, temp_C, splitting, **kwargs) if isspmatrix_bsr(A): R_temp = R_temp.T.tobsr() else: R_temp = R_temp.T.tocsr() elif fn == 'inject': # Don't need A^T or C^T here R_temp = injection_interpolation(A, splitting, **kwargs) if isspmatrix_bsr(A): R_temp = R_temp.T.tobsr() else: R_temp = R_temp.T.tocsr() elif fn == 'standard': if isspmatrix_bsr(A): temp_A = A.T.tobsr() temp_C = C.T.tobsr() R_temp = standard_interpolation(temp_A, temp_C, splitting, **kwargs) R_temp = R_temp.T.tobsr() else: temp_A = A.T.tocsr() temp_C = C.T.tocsr() R_temp = standard_interpolation(temp_A, temp_C, splitting, **kwargs) R_temp = R_temp.T.tocsr() elif fn == 'distance_two': if isspmatrix_bsr(A): temp_A = A.T.tobsr() temp_C = C.T.tobsr() R_temp = distance_two_interpolation(temp_A, temp_C, splitting, **kwargs) R_temp = R_temp.T.tobsr() else: temp_A = A.T.tocsr() temp_C = C.T.tocsr() R_temp = distance_two_interpolation(temp_A, temp_C, splitting, **kwargs) R_temp = R_temp.T.tocsr() elif fn == 'direct': if isspmatrix_bsr(A): temp_A = A.T.tobsr() temp_C = C.T.tobsr() R_temp = direct_interpolation(temp_A, temp_C, splitting, **kwargs) R_temp = R_temp.T.tobsr() else: temp_A = A.T.tocsr() temp_C = C.T.tocsr() R_temp = direct_interpolation(temp_A, temp_C, splitting, **kwargs) R_temp = R_temp.T.tocsr() else: R_temp = R # Store relevant information for this level if keep: levels[-1].C = C # strength of connection matrix levels[-1].P = P # prolongation operator levels[-1].R = R # restriction operator levels[-1].splitting = splitting # C/F splitting # Form coarse grid operator, get complexity #levels[-1].complexity['RAP'] = mat_mat_complexity(R_temp,A) / float(A.nnz) #RA = R_temp * A #levels[-1].complexity['RAP'] += mat_mat_complexity(RA,P_temp) / float(A.nnz) #A = RA * P_temp # RL: RAP = R*(A*P) levels[-1].complexity['RAP'] = mat_mat_complexity(A, P_temp) / float(A.nnz) AP = A * P_temp levels[-1].complexity['RAP'] += mat_mat_complexity(R_temp, AP) / float( A.nnz) A = R_temp * AP # Make sure coarse-grid operator is in correct sparse format if (isspmatrix_csr(P) and (not isspmatrix_csr(A))): A = A.tocsr() elif (isspmatrix_bsr(P) and (not isspmatrix_bsr(A))): A = A.tobsr() A.eliminate_zeros() levels.append(multilevel_solver.level()) levels[-1].A = A return 0
def local_AIR(A, splitting, theta=0.1, norm='abs', degree=1, use_gmres=False, maxiter=10, precondition=True, cost=[0]): """ Compute approximate ideal restriction by setting RA = 0, within the sparsity pattern of R. Sparsity pattern of R for the ith row (i.e. ith C-point) is the set of all strongly connected F-points, or the max_row *most* strongly connected F-points. Parameters ---------- A : {csr_matrix} NxN matrix in CSR or BSR format splitting : array C/F splitting stored in an array of length N theta : float, default 0.1 Solve local system for each row of R for all values |A_ij| >= 0.1 * max_{i!=k} |A_ik| degree : int, default 1 Expand sparsity pattern for R by considering strongly connected neighbors within 'degree' of a given node. Only supports degree 1 and 2. use_gmres : bool Solve local linear system for each row of R using GMRES maxiter : int Maximum number of GMRES iterations precondition : bool Diagonally precondition GMRES Returns ------- Approximate ideal restriction, R, in same sparse format as A. Notes ----- - This was the original idea for approximating ideal restriction. In practice, however, a Neumann approximation is typically used. - Supports block bsr matrices as well. """ # Get SOC matrix containing neighborhood to be included in local solve if isspmatrix_bsr(A): C = classical_strength_of_connection(A=A, theta=theta, block='amalgamate', norm=norm) blocksize = A.blocksize[0] elif isspmatrix_csr(A): blocksize = 1 C = classical_strength_of_connection(A=A, theta=theta, block=None, norm=norm) else: try: A = A.tocsr() warn("Implicit conversion of A to csr", SparseEfficiencyWarning) C = classical_strength_of_connection(A=A, theta=theta, block=None, norm=norm) blocksize = 1 except: raise TypeError("Invalid matrix type, must be CSR or BSR.") Cpts = np.array(np.where(splitting == 1)[0], dtype='int32') nc = Cpts.shape[0] n = C.shape[0] R_rowptr = np.empty(nc + 1, dtype='int32') amg_core.approx_ideal_restriction_pass1(R_rowptr, C.indptr, C.indices, Cpts, splitting, degree) # Build restriction operator nnz = R_rowptr[-1] R_colinds = np.zeros(nnz, dtype='int32') # Block matrix if isspmatrix_bsr(A): R_data = np.zeros(nnz * blocksize * blocksize, dtype=A.dtype) amg_core.block_approx_ideal_restriction_pass2( R_rowptr, R_colinds, R_data, A.indptr, A.indices, A.data.ravel(), C.indptr, C.indices, C.data, Cpts, splitting, blocksize, degree, use_gmres, maxiter, precondition) R = bsr_matrix( (R_data.reshape(nnz, blocksize, blocksize), R_colinds, R_rowptr), blocksize=[blocksize, blocksize], shape=[nc * blocksize, A.shape[0]]) # Not block matrix else: R_data = np.zeros(nnz, dtype=A.dtype) amg_core.approx_ideal_restriction_pass2(R_rowptr, R_colinds, R_data, A.indptr, A.indices, A.data, C.indptr, C.indices, C.data, Cpts, splitting, degree, use_gmres, maxiter, precondition) R = csr_matrix((R_data, R_colinds, R_rowptr), shape=[nc, A.shape[0]]) R.eliminate_zeros() return R
def extend_hierarchy(levels, strength, CF, interp, restrict, filter_operator, coarse_grid_P, coarse_grid_R, keep): """ helper function for local methods """ # Filter operator. Need to keep original matrix on fineest level for # computing residuals if (filter_operator is not None) and (filter_operator[1] != 0): if len(levels) == 1: A = deepcopy(levels[-1].A) else: A = levels[-1].A filter_matrix_rows(A, filter_operator[1], diagonal=True, lump=filter_operator[0]) else: A = levels[-1].A # Check if matrix was filtered to be diagonal --> coarsest grid if A.nnz == A.shape[0]: return 1 # Zero initial complexities for strength, splitting and interpolation levels[-1].complexity['CF'] = 0.0 levels[-1].complexity['strength'] = 0.0 levels[-1].complexity['interpolate'] = 0.0 # Compute the strength-of-connection matrix C, where larger # C[i,j] denote stronger couplings between i and j. fn, kwargs = unpack_arg(strength) if fn == 'symmetric': C = symmetric_strength_of_connection(A, **kwargs) elif fn == 'classical': C = classical_strength_of_connection(A, **kwargs) elif fn == 'distance': C = distance_strength_of_connection(A, **kwargs) elif (fn == 'ode') or (fn == 'evolution'): C = evolution_strength_of_connection(A, **kwargs) elif fn == 'energy_based': C = energy_based_strength_of_connection(A, **kwargs) elif fn == 'algebraic_distance': C = algebraic_distance(A, **kwargs) elif fn == 'affinity': C = affinity_distance(A, **kwargs) elif fn is None: C = A else: raise ValueError('unrecognized strength of connection method: %s' % str(fn)) levels[-1].complexity['strength'] += kwargs['cost'][0] * A.nnz / float(A.nnz) # Generate the C/F splitting fn, kwargs = unpack_arg(CF) if fn == 'RS': splitting = RS(C, **kwargs) elif fn == 'PMIS': splitting = PMIS(C, **kwargs) elif fn == 'PMISc': splitting = PMISc(C, **kwargs) elif fn == 'CLJP': splitting = CLJP(C, **kwargs) elif fn == 'CLJPc': splitting = CLJPc(C, **kwargs) elif fn == 'CR': splitting = CR(C, **kwargs) elif fn == 'weighted_matching': splitting, soc = weighted_matching(C, **kwargs) if soc is not None: C = soc else: raise ValueError('unknown C/F splitting method (%s)' % CF) levels[-1].complexity['CF'] += kwargs['cost'][0] * C.nnz / float(A.nnz) temp = np.sum(splitting) if (temp == len(splitting)) or (temp == 0): return 1 # Generate the interpolation matrix that maps from the coarse-grid to the # fine-grid r_flag = False fn, kwargs = unpack_arg(interp) if fn == 'standard': P = standard_interpolation(A, C, splitting, **kwargs) elif fn == 'distance_two': P = distance_two_interpolation(A, C, splitting, **kwargs) elif fn == 'direct': P = direct_interpolation(A, C, splitting, **kwargs) elif fn == 'one_point': P = one_point_interpolation(A, C, splitting, **kwargs) elif fn == 'inject': P = injection_interpolation(A, splitting, **kwargs) elif fn == 'neumann': P = neumann_ideal_interpolation(A, splitting, **kwargs) elif fn == 'scaledAfc': P = scaled_Afc_interpolation(A, splitting, **kwargs) elif fn == 'air': if isspmatrix_bsr(A): temp_A = bsr_matrix(A.T) P = local_AIR(temp_A, splitting, **kwargs) P = bsr_matrix(P.T) else: temp_A = csr_matrix(A.T) P = local_AIR(temp_A, splitting, **kwargs) P = csr_matrix(P.T) elif fn == 'restrict': r_flag = True else: raise ValueError('unknown interpolation method (%s)' % interp) levels[-1].complexity['interpolate'] += kwargs['cost'][0] * A.nnz / float(A.nnz) # Build restriction operator fn, kwargs = unpack_arg(restrict) if fn is None: R = P.T elif fn == 'air': R = local_AIR(A, splitting, **kwargs) elif fn == 'neumann': R = neumann_AIR(A, splitting, **kwargs) elif fn == 'one_point': # Don't need A^T here temp_C = C.T.tocsr() R = one_point_interpolation(A, temp_C, splitting, **kwargs) if isspmatrix_bsr(A): R = R.T.tobsr() else: R = R.T.tocsr() elif fn == 'inject': # Don't need A^T or C^T here R = injection_interpolation(A, splitting, **kwargs) if isspmatrix_bsr(A): R = R.T.tobsr() else: R = R.T.tocsr() elif fn == 'standard': if isspmatrix_bsr(A): temp_A = A.T.tobsr() temp_C = C.T.tobsr() R = standard_interpolation(temp_A, temp_C, splitting, **kwargs) R = R.T.tobsr() else: temp_A = A.T.tocsr() temp_C = C.T.tocsr() R = standard_interpolation(temp_A, temp_C, splitting, **kwargs) R = R.T.tocsr() elif fn == 'distance_two': if isspmatrix_bsr(A): temp_A = A.T.tobsr() temp_C = C.T.tobsr() R = distance_two_interpolation(temp_A, temp_C, splitting, **kwargs) R = R.T.tobsr() else: temp_A = A.T.tocsr() temp_C = C.T.tocsr() R = distance_two_interpolation(temp_A, temp_C, splitting, **kwargs) R = R.T.tocsr() elif fn == 'direct': if isspmatrix_bsr(A): temp_A = A.T.tobsr() temp_C = C.T.tobsr() R = direct_interpolation(temp_A, temp_C, splitting, **kwargs) R = R.T.tobsr() else: temp_A = A.T.tocsr() temp_C = C.T.tocsr() R = direct_interpolation(temp_A, temp_C, splitting, **kwargs) R = R.T.tocsr() else: raise ValueError('unknown restriction method (%s)' % restrict) # If set P = R^T if r_flag: P = R.T # Optional different interpolation for RAP fn, kwargs = unpack_arg(coarse_grid_P) if fn == 'standard': P_temp = standard_interpolation(A, C, splitting, **kwargs) elif fn == 'distance_two': P_temp = distance_two_interpolation(A, C, splitting, **kwargs) elif fn == 'direct': P_temp = direct_interpolation(A, C, splitting, **kwargs) elif fn == 'one_point': P_temp = one_point_interpolation(A, C, splitting, **kwargs) elif fn == 'inject': P_temp = injection_interpolation(A, splitting, **kwargs) elif fn == 'neumann': P_temp = neumann_ideal_interpolation(A, splitting, **kwargs) elif fn == 'air': if isspmatrix_bsr(A): temp_A = bsr_matrix(A.T) P_temp = local_AIR(temp_A, splitting, **kwargs) P_temp = bsr_matrix(P_temp.T) else: temp_A = csr_matrix(A.T) P_temp = local_AIR(temp_A, splitting, **kwargs) P_temp = csr_matrix(P_temp.T) else: P_temp = P # Optional different restriction for RAP fn, kwargs = unpack_arg(coarse_grid_R) if fn == 'air': R_temp = local_AIR(A, splitting, **kwargs) elif fn == 'neumann': R_temp = neumann_AIR(A, splitting, **kwargs) elif fn == 'one_point': # Don't need A^T here temp_C = C.T.tocsr() R_temp = one_point_interpolation(A, temp_C, splitting, **kwargs) if isspmatrix_bsr(A): R_temp = R_temp.T.tobsr() else: R_temp = R_temp.T.tocsr() elif fn == 'inject': # Don't need A^T or C^T here R_temp = injection_interpolation(A, splitting, **kwargs) if isspmatrix_bsr(A): R_temp = R_temp.T.tobsr() else: R_temp = R_temp.T.tocsr() elif fn == 'standard': if isspmatrix_bsr(A): temp_A = A.T.tobsr() temp_C = C.T.tobsr() R_temp = standard_interpolation(temp_A, temp_C, splitting, **kwargs) R_temp = R_temp.T.tobsr() else: temp_A = A.T.tocsr() temp_C = C.T.tocsr() R_temp = standard_interpolation(temp_A, temp_C, splitting, **kwargs) R_temp = R_temp.T.tocsr() elif fn == 'distance_two': if isspmatrix_bsr(A): temp_A = A.T.tobsr() temp_C = C.T.tobsr() R_temp = distance_two_interpolation(temp_A, temp_C, splitting, **kwargs) R_temp = R_temp.T.tobsr() else: temp_A = A.T.tocsr() temp_C = C.T.tocsr() R_temp = distance_two_interpolation(temp_A, temp_C, splitting, **kwargs) R_temp = R_temp.T.tocsr() elif fn == 'direct': if isspmatrix_bsr(A): temp_A = A.T.tobsr() temp_C = C.T.tobsr() R_temp = direct_interpolation(temp_A, temp_C, splitting, **kwargs) R_temp = R_temp.T.tobsr() else: temp_A = A.T.tocsr() temp_C = C.T.tocsr() R_temp = direct_interpolation(temp_A, temp_C, splitting, **kwargs) R_temp = R_temp.T.tocsr() else: R_temp = R # Store relevant information for this level if keep: levels[-1].C = C # strength of connection matrix levels[-1].P = P # prolongation operator levels[-1].R = R # restriction operator levels[-1].splitting = splitting # C/F splitting # Form coarse grid operator, get complexity #levels[-1].complexity['RAP'] = mat_mat_complexity(R_temp,A) / float(A.nnz) #RA = R_temp * A #levels[-1].complexity['RAP'] += mat_mat_complexity(RA,P_temp) / float(A.nnz) #A = RA * P_temp # RL: RAP = R*(A*P) levels[-1].complexity['RAP'] = mat_mat_complexity(A, P_temp) / float(A.nnz) AP = A * P_temp levels[-1].complexity['RAP'] += mat_mat_complexity(R_temp, AP) / float(A.nnz) A = R_temp * AP # Make sure coarse-grid operator is in correct sparse format if (isspmatrix_csr(P) and (not isspmatrix_csr(A))): A = A.tocsr() elif (isspmatrix_bsr(P) and (not isspmatrix_bsr(A))): A = A.tobsr() A.eliminate_zeros() levels.append(multilevel_solver.level()) levels[-1].A = A return 0
import scipy from pyamg.gallery import stencil_grid from pyamg.gallery.diffusion import diffusion_stencil_2d from pyamg.strength import classical_strength_of_connection from pyamg.classical.classical import ruge_stuben_solver from convergence_tools import print_cycle_history if __name__ == '__main__': n = 100 nx = n ny = n # Rotated Anisotropic Diffusion stencil = diffusion_stencil_2d(type='FE',epsilon=0.001,theta=scipy.pi/3) A = stencil_grid(stencil, (nx,ny), format='csr') S = classical_strength_of_connection(A, 0.0) numpy.random.seed(625) x = scipy.rand(A.shape[0]) b = A*scipy.rand(A.shape[0]) ml = ruge_stuben_solver(A, max_coarse=10) resvec = [] x = ml.solve(b, x0=x, maxiter=20, tol=1e-14, residuals=resvec) print_cycle_history(resvec, ml, verbose=True, plotting=True)
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 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
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 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