Coverage for /usr/share/miniconda3/envs/dolfin/lib/python3.8/site-packages/block/algebraic/petsc/precond.py: 68%

176 statements  

« prev     ^ index     » next       coverage.py v7.2.1, created at 2023-03-20 13:03 +0000

1from __future__ import division 

2 

3from builtins import str 

4from block.block_base import block_base 

5from block.helpers import supports_mpi 

6from block.object_pool import vec_pool 

7from petsc4py import PETSc 

8import dolfin as df 

9import numpy as np 

10import hashlib 

11from time import time 

12from dolfin import info 

13 

14 

15class precond(block_base): 

16 def __init__(self, A, prectype, pdes, nullspace, prefix, options, V=None, create_cb=None, defaults={}): 

17 Ad = mat(A) 

18 

19 if nullspace: 

20 from block.block_util import isscalar 

21 ns = PETSc.NullSpace() 

22 if isscalar(nullspace): 22 ↛ 23line 22 didn't jump to line 23, because the condition on line 22 was never true

23 ns.create(constant=True) 

24 else: 

25 ns.create(constant=False, vectors=[v.down_cast().vec() for v in nullspace]) 

26 try: 

27 Ad.setNearNullSpace(ns) 

28 except: 

29 info('failed to set near null space (not supported in petsc4py version)') 

30 

31 # The PETSc options database is global, so we create a unique prefix 

32 # that precisely matches the supplied options. The user can override 

33 # this prefix if necessary. 

34 if prefix is None: 34 ↛ 43line 34 didn't jump to line 43, because the condition on line 34 was never false

35 # Use class name instead of hash of defaults (we assume defaults 

36 # are constant for a given class) 

37 prefix = self.__class__.__name__ 

38 if options: 38 ↛ 39line 38 didn't jump to line 39, because the condition on line 38 was never true

39 sha = hashlib.sha256(str(tuple(sorted(options.items()))).encode()) 

40 prefix += sha.hexdigest()[:8] 

41 prefix += ':' 

42 

43 self.optsDB = PETSc.Options(prefix) 

44 params = defaults.copy() 

45 if options: 45 ↛ 46line 45 didn't jump to line 46, because the condition on line 45 was never true

46 params.update(options) 

47 for key, val in params.items(): 

48 # Allow overriding defaults by setting key='del'. Command-line 

49 # options (already in optsDB) are never overwritten. 

50 if val!='del' and not self.optsDB.hasName(key): 

51 self.optsDB.setValue(key, val) 

52 

53 if prectype == PETSc.PC.Type.HYPRE and nullspace: 

54 if not all(self.optsDB.hasName(n) for n in ['pc_hypre_boomeramg_nodal_coarsen', 'pc_hypre_boomeramg_vec_interp_variant']): 54 ↛ exit,   54 ↛ 572 missed branches: 1) line 54 didn't finish the generator expression on line 54, 2) line 54 didn't jump to line 57, because the condition on line 54 was never false

55 print('Near null space is ignored by hypre UNLESS you also supply pc_hypre_boomeramg_nodal_coarsen and pc_hypre_boomeramg_vec_interp_variant') 

56 

57 self.A = A 

58 self.petsc_prec = PETSc.PC().create(V.mesh().mpi_comm() if V else None) 

59 self.petsc_prec.setOptionsPrefix(prefix) 

60 try: 

61 self.petsc_prec.setType(prectype) 

62 except PETSc.Error as e: 

63 if e.ierr == 86: 

64 raise ValueError(f'Unknown PETSc type "{prectype}"') 

65 else: 

66 raise 

67 self.petsc_prec.setOperators(Ad) 

68 self.petsc_prec.setFromOptions() 

69 

70 self.setup_time = -1. 

71 

72 def matvec(self, b): 

73 # NOTE: we want to be lazy only setup when the action is needed 

74 if self.setup_time < 0: 

75 T = time() 

76 # Create preconditioner based on the options database 

77 self.petsc_prec.setUp() 

78 

79 setup_time = time()-T 

80 comm = self.petsc_prec.getComm().tompi4py() 

81 

82 setup_time = comm.allreduce(setup_time)/comm.size 

83 info('constructed %s preconditioner in %.2f s'%(self.__class__.__name__, setup_time)) 

84 

85 self.setup_time = setup_time 

86 

87 from dolfin import GenericVector 

88 if not isinstance(b, GenericVector): 88 ↛ 89line 88 didn't jump to line 89, because the condition on line 88 was never true

89 return NotImplemented 

90 x = self.A.create_vec(dim=1) 

91 if len(x) != len(b): 91 ↛ 92line 91 didn't jump to line 92, because the condition on line 91 was never true

92 raise RuntimeError( 

93 'incompatible dimensions for PETSc matvec, %d != %d'%(len(x),len(b))) 

94 

95 self.petsc_prec.apply(b.down_cast().vec(), x.down_cast().vec()) 

96 return x 

97 

98 def down_cast(self): 

99 return self.petsc_prec 

100 

101 def __str__(self): 

102 return '<%s prec of %s>'%(self.__class__.__name__, str(self.A)) 

103 

104 def create_vec(self, dim=1): 

105 return self.A.create_vec(dim) 

106 

107 

108class ML(precond): 

109 def __init__(self, A, parameters=None, pdes=1, nullspace=None, prefix=None): 

110 super().__init__(A, PETSc.PC.Type.ML, 

111 pdes=pdes, nullspace=nullspace, options=parameters, prefix=prefix, 

112 defaults={ 

113 # Symmetry- and PD-preserving smoother 

114 'mg_levels_ksp_type': 'chebyshev', 

115 'mg_levels_pc_type': 'jacobi', 

116 # Fixed number of iterations to preserve linearity 

117 'mg_levels_ksp_max_it': 2, 

118 'mg_levels_ksp_check_norm_iteration': 9999, 

119 # Exact inverse on coarse grid 

120 'mg_coarse_ksp_type': 'preonly', 

121 'mg_coarse_pc_type': 'lu', 

122 }) 

123 

124class ILU(precond): 

125 def __init__(self, A, parameters=None, pdes=1, nullspace=None, prefix=None): 

126 supports_mpi(False, 'PETSc ILU does not work in parallel', mat(A).comm.size) 

127 super().__init__(A, PETSc.PC.Type.ILU, 

128 pdes=pdes, nullspace=nullspace, options=parameters, prefix=prefix) 

129 

130class Cholesky(precond): 

131 def __init__(self, A, parameters=None, prefix=None): 

132 super().__init__(A, PETSc.PC.Type.CHOLESKY, 

133 pdes=1, nullspace=None, options=parameters, prefix=prefix) 

134 

135class LU(precond): 

136 def __init__(self, A, parameters=None, prefix=None, defaults={}): 

137 super().__init__(A, PETSc.PC.Type.LU, 

138 pdes=1, nullspace=None, options=parameters, prefix=prefix, 

139 defaults=defaults) 

140 

141 

142class MUMPS_LU(LU): 

143 def __init__(self, A, parameters=None, prefix=None): 

144 super().__init__(A, 

145 parameters=parameters, prefix=prefix, 

146 defaults={ 

147 'pc_factor_mat_solver_package': 'mumps', 

148 }) 

149 

150 

151class SUPERLU_LU(LU): 

152 def __init__(self, A, parameters=None, prefix=None): 

153 super().__init__(A, 

154 parameters=parameters, prefix=prefix, 

155 defaults={ 

156 'pc_factor_mat_solver_package': 'superlu', 

157 }) 

158 self.petsc_prec.setFactorPivot(1E-16) 

159 

160class AMG(precond): 

161 """ 

162 BoomerAMG preconditioner from the Hypre Library 

163 """ 

164 def __init__(self, A, parameters=None, pdes=1, nullspace=None, prefix=None): 

165 super().__init__(A, PETSc.PC.Type.HYPRE, 

166 pdes=pdes, nullspace=nullspace, options=parameters, prefix=prefix, 

167 defaults={ 

168 "pc_hypre_type": "boomeramg", 

169 #"pc_hypre_boomeramg_cycle_type": "V", # (V,W) 

170 #"pc_hypre_boomeramg_max_levels": 25, 

171 #"pc_hypre_boomeramg_max_iter": 1, 

172 #"pc_hypre_boomeramg_tol": 0,  

173 #"pc_hypre_boomeramg_truncfactor" : 0, # Truncation factor for interpolation 

174 #"pc_hypre_boomeramg_P_max": 0, # Max elements per row for interpolation 

175 #"pc_hypre_boomeramg_agg_nl": 0, # Number of levels of aggressive coarsening 

176 #"pc_hypre_boomeramg_agg_num_paths": 1, # Number of paths for aggressive coarsening 

177 #"pc_hypre_boomeramg_strong_threshold": .25,# Threshold for being strongly connected 

178 #"pc_hypre_boomeramg_max_row_sum": 0.9, 

179 #"pc_hypre_boomeramg_grid_sweeps_all": 1, # Number of sweeps for the up and down grid levels  

180 #"pc_hypre_boomeramg_grid_sweeps_down": 1,  

181 #"pc_hypre_boomeramg_grid_sweeps_up":1, 

182 #"pc_hypre_boomeramg_grid_sweeps_coarse": 1,# Number of sweeps for the coarse level (None) 

183 #"pc_hypre_boomeramg_relax_type_all": "symmetric-SOR/Jacobi", # (Jacobi, sequential-Gauss-Seidel, seqboundary-Gauss-Seidel,  

184 # SOR/Jacobi, backward-SOR/Jacobi, symmetric-SOR/Jacobi,  

185 # l1scaled-SOR/Jacobi Gaussian-elimination, CG, Chebyshev,  

186 # FCF-Jacobi, l1scaled-Jacobi) 

187 #"pc_hypre_boomeramg_relax_type_down": "symmetric-SOR/Jacobi", 

188 #"pc_hypre_boomeramg_relax_type_up": "symmetric-SOR/Jacobi", 

189 #"pc_hypre_boomeramg_relax_type_coarse": "Gaussian-elimination", 

190 #"pc_hypre_boomeramg_relax_weight_all": 1, # Relaxation weight for all levels (0 = hypre estimates, -k = determined with k CG steps) 

191 #"pc_hypre_boomeramg_relax_weight_level": (1,1), # Set the relaxation weight for a particular level 

192 #"pc_hypre_boomeramg_outer_relax_weight_all": 1, 

193 #"pc_hypre_boomeramg_outer_relax_weight_level": (1,1), 

194 #"pc_hypre_boomeramg_no_CF": "", # Do not use CF-relaxation  

195 #"pc_hypre_boomeramg_measure_type": "local", # (local global) 

196 #"pc_hypre_boomeramg_coarsen_type": "Falgout", # (Ruge-Stueben, modifiedRuge-Stueben, Falgout, PMIS, HMIS) 

197 #"pc_hypre_boomeramg_interp_type": "classical",# (classical, direct, multipass, multipass-wts, ext+i, ext+i-cc, standard, standard-wts, FF, FF1) 

198 #"pc_hypre_boomeramg_print_statistics": "", 

199 #"pc_hypre_boomeramg_print_debug": "", 

200 #"pc_hypre_boomeramg_nodal_coarsen": "", 

201 #"pc_hypre_boomeramg_nodal_relaxation": "", 

202 }) 

203 

204 

205class SOR(precond): 

206 def __init__(self, A, parameters=None, pdes=1, nullspace=None, prefix=None): 

207 options = { 

208 } 

209 super().__init__(A, PETSc.PC.Type.SOR, 

210 pdes=pdes, nullspace=nullspace, options=parameters, prefix=prefix, 

211 defaults={ 

212 "pc_sor_omega": 1, # relaxation factor (0 < omega < 2, 1 is Gauss-Seidel) 

213 "pc_sor_its": 1, # number of inner SOR iterations 

214 "pc_sor_lits": 1, # number of local inner SOR iterations 

215 "pc_sor_symmetric": "", # for SSOR 

216 #"pc_sor_backward": "", 

217 #"pc_sor_forward": "",  

218 #"tmp_pc_sor_local_symmetric": "", # use SSOR separately on each processor 

219 #"tmp_pc_sor_local_backward": "",  

220 #"tmp_pc_sor_local_forward": "", 

221 }) 

222 supports_mpi(not self.optsDB.hasName('pc_sor_symmetric'), 'PETSc symmetric SOR not supported in parallel', mat(A).comm.size) 

223 

224 

225class Elasticity(precond): 

226 def __init__(self, A, parameters=None, pdes=1, nullspace=None, prefix=None): 

227 super().__init__(A, PETSc.PC.Type.GAMG, 

228 pdes=pdes, nullspace=nullspace, options=parameters, prefix=prefix, 

229 defaults={ 

230 'pc_mg_cycle_type': 'w', 

231 'pc_mg_multiplicative_cycles': 2 

232 }) 

233 

234class ASM(precond): 

235 """ 

236 Additive Scwharz Method. 

237 Defaults (or should default, not tested) to one block per process. 

238 """ 

239 def __init__(self, A, parameters=None, pdes=1, nullspace=None, prefix=None): 

240 super().__init__(A, PETSc.PC.Type.ASM, 

241 pdes=pdes, nullspace=nullspace, options=parameters, prefix=prefix, 

242 defaults={ 

243 #"pc_asm_blocks": 1, # Number of subdomains 

244 "pc_asm_overlap": 1, # Number of grid points overlap 

245 "pc_asm_type": "RESTRICT", # (NONE, RESTRICT, INTERPOLATE, BASIC) 

246 "sup_ksp_type": "preonly", # KSP solver for the subproblems 

247 "sub_pc_type": "ilu" # Preconditioner for the subproblems 

248 }) 

249 

250 

251class Jacobi(precond): 

252 """ 

253 Actually this is only a diagonal scaling preconditioner; no support for relaxation or multiple iterations. 

254 """ 

255 def __init__(self, A, parameters=None, pdes=1, nullspace=None, prefix=None): 

256 super().__init__(A, PETSc.PC.Type.JACOBI, 

257 pdes=pdes, nullspace=nullspace, options=parameters, prefix=prefix, 

258 defaults={ 

259 #"pc_jacobi_rowmax": "", # Use row maximums for diagonal 

260 #"pc_jacobi_rowsum": "", # Use row sums for diagonal 

261 #"pc_jacobi_abs":, "", # Use absolute values of diagaonal entries 

262 }) 

263 

264 

265def vec(x): 

266 return df.as_backend_type(x).vec() 

267 

268 

269def mat(A): 

270 return df.as_backend_type(A).mat() 

271 

272 

273class HypreAMS(precond): 

274 ''' 

275 AMG auxiliary space preconditioner for H(curl) problems in 2d/3d and 

276 H(div) problems in 2d. The bilinear form behind the matrix whose  

277 approximate inverse is constructed is: Find u in V 

278 

279 alpha*inner(curl(u), curl(v)) + beta*inner(u, v)*dx for all v in V 

280 

281 where alpha > 0 and beta >= 0. 

282 ''' 

283 def __init__(self, A, V, ams_zero_beta_poisson=False, prefix=None): 

284 mesh = V.mesh() 

285 if mesh.geometry().dim() == 2: 285 ↛ 288line 285 didn't jump to line 288, because the condition on line 285 was never false

286 assert V.ufl_element().family() in ('Raviart-Thomas', 'Nedelec 1st kind H(curl)') 

287 else: 

288 assert V.ufl_element().family() == 'Nedelec 1st kind H(curl)' 

289 # FIXME: only tested this for lower orders 

290 assert V.ufl_element().degree() == 1 

291 

292 # AMS setup requires auxiliary operators 

293 Q = df.FunctionSpace(mesh, 'CG', 1) 

294 G = df.DiscreteOperators.build_gradient(V, Q) 

295 

296 super().__init__(A, PETSc.PC.Type.HYPRE, V=V, 

297 pdes=None, nullspace=None, options=None, prefix=prefix, 

298 defaults={ 

299 'pc_hypre_type': 'ams', 

300 }) 

301 self.petsc_prec.setHYPREDiscreteGradient(mat(G)) 

302 

303 # Constants 

304 constants = [vec(df.interpolate(df.Constant(c), V).vector()) 

305 for c in np.eye(mesh.geometry().dim())] 

306 self.petsc_prec.setHYPRESetEdgeConstantVectors(*constants) 

307 

308 # NOTE: signal that we don't have lower order term 

309 ams_zero_beta_poisson and self.petsc_prec.setHYPRESetBetaPoissonMatrix(None) 

310 

311 

312class HypreADS(precond): 

313 ''' 

314 AMG auxiliary space preconditioner for H(div) problems in 3d. The bilinear  

315 form behind the matrix whose approximate inverse is constructed is: Find u in V 

316 

317 alpha*inner(div(u), div(v)) + beta*inner(u, v)*dx for all v in V 

318 

319 where alpha > 0 and beta >= 0. 

320 ''' 

321 @staticmethod 

322 def coordinates(mesh): 

323 '''As P1 functions''' 

324 V = df.FunctionSpace(mesh, 'CG', 1) 

325 

326 return V.tabulate_dof_coordinates() 

327 

328 @staticmethod 

329 def discrete_gradient(mesh): 

330 '''P1 to Ned1 map''' 

331 Ned = df.FunctionSpace(mesh, 'Nedelec 1st kind H(curl)', 1) 

332 P1 = df.FunctionSpace(mesh, 'CG', 1) 

333 

334 G = df.DiscreteOperators.build_gradient(Ned, P1) 

335 

336 return mat(G) 

337 

338 @staticmethod 

339 def discrete_curl(mesh): 

340 '''Ned1 to RT1 map''' 

341 assert mesh.geometry().dim() == 3 

342 assert mesh.topology().dim() == 3 

343 

344 Ned = df.FunctionSpace(mesh, 'Nedelec 1st kind H(curl)', 1) 

345 RT = df.FunctionSpace(mesh, 'RT', 1) 

346 

347 RT_f2dof = np.array(RT.dofmap().entity_dofs(mesh, 2)) 

348 Ned_e2dof = np.array(Ned.dofmap().entity_dofs(mesh, 1)) 

349 

350 # Alloc sparsity 

351 a = df.Constant(0)*df.inner(df.TestFunction(RT), df.TrialFunction(Ned))*df.dx 

352 C = df.PETScMatrix() 

353 df.assemble(a, tensor=C) 

354 

355 RT_f2dof = np.array(RT.dofmap().entity_dofs(mesh, 2)) 

356 Ned_e2dof = np.array(Ned.dofmap().entity_dofs(mesh, 1)) 

357 

358 # Facets in terms of edges 

359 mesh.init(2, 1) 

360 f2e = mesh.topology()(2, 1) 

361 

362 row_cols, row_values = np.zeros(3, dtype='int32'), np.array([-2, 2, -2]) 

363 Cmat = C.mat() 

364 

365 for facet, row in enumerate(RT_f2dof): 

366 row_cols[:] = Ned_e2dof[f2e(facet)] 

367 Cmat.setValues([row], row_cols, row_values, PETSc.InsertMode.INSERT_VALUES) 

368 Cmat.assemble() 

369 

370 return Cmat 

371 

372 def __init__(self, A, V, prefix=None): 

373 assert V.mesh().geometry().dim() == 3 

374 assert V.ufl_element().family() == 'Raviart-Thomas' 

375 assert V.ufl_element().degree() == 1 

376 

377 supports_mpi(False, 'ADS curl matrix needs to be fixed for parallel', mat(A).comm.size) # FIXME 

378 

379 mesh = V.mesh() 

380 C = HypreADS.discrete_curl(mesh) 

381 G = HypreADS.discrete_gradient(mesh) 

382 xyz = HypreADS.coordinates(mesh) 

383 

384 super().__init__(A, PETSc.PC.Type.HYPRE, V=V, 

385 pdes=None, nullspace=None, options=None, prefix=prefix, 

386 defaults={ 

387 'pc_hypre_type': 'ads', 

388 }) 

389 # Attach auxiliary operators 

390 self.petsc_prec.setHYPREDiscreteGradient(G) 

391 self.petsc_prec.setHYPREDiscreteCurl(G) 

392 self.petsc_prec.setCoordinates(xyz)