|
22 | 22 | # PETSc/petsc4y. |
23 | 23 | # * Construct a Hypre Auxiliary Maxwell Space (AMS) preconditioner for |
24 | 24 | # $H(\mathrm{div})$ problems in two-dimensions. |
| 25 | +# * Construct a monolithic Balancing Domain Decomposition by |
| 26 | +# Constraints (BDDC) preconditioner as described in the |
| 27 | +# [paper](https://doi.org/10.1137/16M1080653) |
25 | 28 | # |
26 | 29 | # ```{admonition} Download sources |
27 | 30 | # :class: download |
|
103 | 106 | from basix.ufl import element |
104 | 107 | from dolfinx import fem, mesh |
105 | 108 | from dolfinx.fem.petsc import discrete_gradient, interpolation_matrix |
106 | | -from dolfinx.mesh import CellType, create_unit_square |
| 109 | +from dolfinx.mesh import CellType, GhostMode, create_unit_square |
107 | 110 |
|
108 | 111 | # Solution scalar (e.g., float32, complex128) and geometry (float32/64) |
109 | 112 | # types |
110 | 113 | dtype = dolfinx.default_scalar_type |
111 | 114 | xdtype = dolfinx.default_real_type |
112 | 115 | # - |
113 | 116 |
|
114 | | -# Create a two-dimensional mesh. The iterative solver constructed |
115 | | -# later requires special construction that is specific to two |
| 117 | +# Create a two-dimensional mesh. The AMS based iterative solvers |
| 118 | +# constructed later require special construction that is specific to two |
116 | 119 | # dimensions. Application in three-dimensions would require a number of |
117 | 120 | # changes to the linear solver. |
| 121 | +# BDDC methods need the linear operator to be stored in unassembled format, |
| 122 | +# i.e. assembled on each MPI process associated with the subdomain part of |
| 123 | +# the mesh, but not across processes. |
| 124 | +# To this end, we require the mesh to not be distributed with overlap. |
118 | 125 |
|
119 | 126 | # + |
120 | | -msh = create_unit_square(MPI.COMM_WORLD, 96, 96, CellType.triangle, dtype=xdtype) |
| 127 | +msh = create_unit_square( |
| 128 | + MPI.COMM_WORLD, 96, 96, CellType.triangle, ghost_mode=GhostMode.none, dtype=xdtype |
| 129 | +) |
121 | 130 | # - |
122 | 131 | # |
123 | 132 | # Here we construct compatible function spaces for the mixed Poisson |
|
226 | 235 | # - |
227 | 236 |
|
228 | 237 | # We now create a linear problem solver for the mixed problem. |
| 238 | +# We use two separate functions to setup block preconditioning with AMS or |
| 239 | +# monolithic BDDC methods, as the matrix specification differs. |
| 240 | +# |
| 241 | +# The next function sets up the problem with block preconditioning. |
229 | 242 | # As we will use different preconditions for the individual blocks of |
230 | 243 | # the saddle point problem, we specify the matrix kind to be "nest", |
231 | 244 | # so that we can use # [`fieldsplit`](https://petsc.org/release/manual/ksp/#sec-block-matrices) |
232 | 245 | # (block) type and set the 'splits' between the $\sigma$ and $u$ fields. |
233 | | - |
234 | | - |
235 | | -# + |
236 | | - |
237 | | -problem = fem.petsc.LinearProblem( |
238 | | - a, |
239 | | - L, |
240 | | - u=[sigma, u], |
241 | | - P=a_p, |
242 | | - kind="nest", |
243 | | - bcs=bcs, |
244 | | - petsc_options_prefix="demo_mixed_poisson_", |
245 | | - petsc_options={ |
246 | | - "ksp_type": "gmres", |
247 | | - "pc_type": "fieldsplit", |
248 | | - "pc_fieldsplit_type": "additive", |
249 | | - "ksp_rtol": 1e-8, |
250 | | - "ksp_gmres_restart": 100, |
251 | | - "ksp_view": "", |
252 | | - }, |
253 | | -) |
254 | | -# - |
255 | | - |
256 | | - |
257 | | -# + |
258 | | -ksp = problem.solver |
259 | | -ksp.setMonitor( |
260 | | - lambda _, its, rnorm: PETSc.Sys.Print(f"Iteration: {its:>4d}, residual: {rnorm:.3e}") |
261 | | -) |
262 | | -# - |
263 | | - |
264 | 246 | # For the $P_{11}$ block, which is the discontinuous Lagrange mass |
265 | 247 | # matrix, we let the preconditioner be the default, which is incomplete |
266 | 248 | # LU factorisation and which can solve the block exactly in one |
|
273 | 255 | # $H({\rm div})$ and $H({\rm curl})$ spaces are effectively the same in |
274 | 256 | # two-dimensions, just rotated by $\pi/2. |
275 | 257 |
|
| 258 | + |
276 | 259 | # + |
277 | | -ksp_sigma, ksp_u = ksp.getPC().getFieldSplitSubKSP() |
278 | | -pc_sigma = ksp_sigma.getPC() |
279 | | -if PETSc.Sys().hasExternalPackage("hypre") and not np.issubdtype(dtype, np.complexfloating): |
280 | | - pc_sigma.setType("hypre") |
281 | | - pc_sigma.setHYPREType("ams") |
282 | | - |
283 | | - opts = PETSc.Options() |
284 | | - opts[f"{ksp_sigma.prefix}pc_hypre_ams_cycle_type"] = 7 |
285 | | - opts[f"{ksp_sigma.prefix}pc_hypre_ams_relax_times"] = 2 |
286 | | - |
287 | | - # Construct and set the 'discrete gradient' operator, which maps |
288 | | - # grad H1 -> H(curl), i.e. the gradient of a scalar Lagrange space |
289 | | - # to a H(curl) space |
290 | | - V_H1 = fem.functionspace(msh, element("Lagrange", msh.basix_cell(), k, dtype=xdtype)) |
291 | | - V_curl = fem.functionspace(msh, element("N1curl", msh.basix_cell(), k, dtype=xdtype)) |
292 | | - G = discrete_gradient(V_H1, V_curl) |
293 | | - G.assemble() |
294 | | - pc_sigma.setHYPREDiscreteGradient(G) |
295 | | - |
296 | | - assert k > 0, "Element degree must be at least 1." |
297 | | - if k == 1: |
298 | | - # For the lowest order base (k=1), we can supply interpolation |
299 | | - # of the '1' vectors in the space V. Hypre can then construct |
300 | | - # the required operators from G and the '1' vectors. |
301 | | - cvec0, cvec1 = fem.Function(V), fem.Function(V) |
302 | | - cvec0.interpolate(lambda x: np.vstack((np.ones_like(x[0]), np.zeros_like(x[1])))) |
303 | | - cvec1.interpolate(lambda x: np.vstack((np.zeros_like(x[0]), np.ones_like(x[1])))) |
304 | | - pc_sigma.setHYPRESetEdgeConstantVectors(cvec0.x.petsc_vec, cvec1.x.petsc_vec, None) |
| 260 | +def block_solver(): |
| 261 | + problem = fem.petsc.LinearProblem( |
| 262 | + a, |
| 263 | + L, |
| 264 | + u=[sigma, u], |
| 265 | + P=a_p, |
| 266 | + kind="nest", |
| 267 | + bcs=bcs, |
| 268 | + petsc_options_prefix="demo_mixed_poisson_", |
| 269 | + petsc_options={ |
| 270 | + "ksp_type": "gmres", |
| 271 | + "pc_type": "fieldsplit", |
| 272 | + "pc_fieldsplit_type": "additive", |
| 273 | + "ksp_rtol": 1e-8, |
| 274 | + "ksp_gmres_restart": 100, |
| 275 | + "ksp_view": "", |
| 276 | + }, |
| 277 | + ) |
| 278 | + |
| 279 | + ksp = problem.solver |
| 280 | + ksp_sigma, ksp_u = ksp.getPC().getFieldSplitSubKSP() |
| 281 | + pc_sigma = ksp_sigma.getPC() |
| 282 | + if PETSc.Sys().hasExternalPackage("hypre") and not np.issubdtype(dtype, np.complexfloating): |
| 283 | + pc_sigma.setType("hypre") |
| 284 | + pc_sigma.setHYPREType("ams") |
| 285 | + |
| 286 | + opts = PETSc.Options() |
| 287 | + opts[f"{ksp_sigma.prefix}pc_hypre_ams_cycle_type"] = 7 |
| 288 | + opts[f"{ksp_sigma.prefix}pc_hypre_ams_relax_times"] = 2 |
| 289 | + |
| 290 | + # Construct and set the 'discrete gradient' operator, which maps |
| 291 | + # grad H1 -> H(curl), i.e. the gradient of a scalar Lagrange space |
| 292 | + # to a H(curl) space |
| 293 | + V_H1 = fem.functionspace(msh, element("Lagrange", msh.basix_cell(), k, dtype=xdtype)) |
| 294 | + V_curl = fem.functionspace(msh, element("N1curl", msh.basix_cell(), k, dtype=xdtype)) |
| 295 | + G = discrete_gradient(V_H1, V_curl) |
| 296 | + G.assemble() |
| 297 | + pc_sigma.setHYPREDiscreteGradient(G) |
| 298 | + |
| 299 | + assert k > 0, "Element degree must be at least 1." |
| 300 | + if k == 1: |
| 301 | + # For the lowest order base (k=1), we can supply interpolation |
| 302 | + # of the '1' vectors in the space V. Hypre can then construct |
| 303 | + # the required operators from G and the '1' vectors. |
| 304 | + cvec0, cvec1 = fem.Function(V), fem.Function(V) |
| 305 | + cvec0.interpolate(lambda x: np.vstack((np.ones_like(x[0]), np.zeros_like(x[1])))) |
| 306 | + cvec1.interpolate(lambda x: np.vstack((np.zeros_like(x[0]), np.ones_like(x[1])))) |
| 307 | + pc_sigma.setHYPRESetEdgeConstantVectors(cvec0.x.petsc_vec, cvec1.x.petsc_vec, None) |
| 308 | + else: |
| 309 | + # For high-order spaces, we must provide the (H1)^d -> H(div) |
| 310 | + # interpolation operator/matrix |
| 311 | + V_H1d = fem.functionspace(msh, ("Lagrange", k, (msh.geometry.dim,))) |
| 312 | + Pi = interpolation_matrix(V_H1d, V) # (H1)^d -> H(div) |
| 313 | + Pi.assemble() |
| 314 | + pc_sigma.setHYPRESetInterpolations(msh.geometry.dim, None, None, Pi, None) |
| 315 | + |
| 316 | + # High-order elements generally converge less well than the |
| 317 | + # lowest-order case with algebraic multigrid, so we perform |
| 318 | + # extra work at the multigrid stage |
| 319 | + opts[f"{ksp_sigma.prefix}pc_hypre_ams_tol"] = 1e-12 |
| 320 | + opts[f"{ksp_sigma.prefix}pc_hypre_ams_max_iter"] = 3 |
| 321 | + |
| 322 | + ksp_sigma.setFromOptions() |
305 | 323 | else: |
306 | | - # For high-order spaces, we must provide the (H1)^d -> H(div) |
307 | | - # interpolation operator/matrix |
308 | | - V_H1d = fem.functionspace(msh, ("Lagrange", k, (msh.geometry.dim,))) |
309 | | - Pi = interpolation_matrix(V_H1d, V) # (H1)^d -> H(div) |
310 | | - Pi.assemble() |
311 | | - pc_sigma.setHYPRESetInterpolations(msh.geometry.dim, None, None, Pi, None) |
312 | | - |
313 | | - # High-order elements generally converge less well than the |
314 | | - # lowest-order case with algebraic multigrid, so we perform |
315 | | - # extra work at the multigrid stage |
316 | | - opts[f"{ksp_sigma.prefix}pc_hypre_ams_tol"] = 1e-12 |
317 | | - opts[f"{ksp_sigma.prefix}pc_hypre_ams_max_iter"] = 3 |
318 | | - |
319 | | - ksp_sigma.setFromOptions() |
320 | | -else: |
321 | | - # If Hypre is not available, use LU factorisation on the $P_{00}$ |
322 | | - # block |
323 | | - pc_sigma.setType("lu") |
324 | | - use_superlu = PETSc.IntType == np.int64 |
325 | | - if PETSc.Sys().hasExternalPackage("mumps") and not use_superlu: |
326 | | - pc_sigma.setFactorSolverType("mumps") |
327 | | - elif PETSc.Sys().hasExternalPackage("superlu_dist"): |
328 | | - pc_sigma.setFactorSolverType("superlu_dist") |
| 324 | + # If Hypre is not available, use LU factorisation on the $P_{00}$ |
| 325 | + # block |
| 326 | + pc_sigma.setType("lu") |
| 327 | + use_superlu = PETSc.IntType == np.int64 |
| 328 | + if PETSc.Sys().hasExternalPackage("mumps") and not use_superlu: |
| 329 | + pc_sigma.setFactorSolverType("mumps") |
| 330 | + elif PETSc.Sys().hasExternalPackage("superlu_dist"): |
| 331 | + pc_sigma.setFactorSolverType("superlu_dist") |
| 332 | + |
| 333 | + return problem |
| 334 | + |
| 335 | + |
329 | 336 | # - |
330 | 337 |
|
331 | | -# Once we have set the preconditioners for the two blocks, we can |
332 | | -# solve the linear system. The `LinearProblem` class will |
333 | | -# automatically assemble the linear system, apply the boundary |
334 | | -# conditions, call the Krylov solver and update the solution |
335 | | -# vectors `u` and `sigma`. |
| 338 | +# In the next function we create a monolithic linear problem solver. |
| 339 | +# As we will use BDDC, we specify the matrix kind to be "is". |
| 340 | +# We also show a minimal customization for the method to be |
| 341 | +# applied monolithically on the symmetric indefinite linear |
| 342 | +# system arising from the mixed problem. |
| 343 | +# In particular, we need solvers for the subproblems that can |
| 344 | +# support the saddle point formulation. |
| 345 | + |
336 | 346 |
|
337 | 347 | # + |
338 | | -problem.solve() |
339 | | -converged_reason = problem.solver.getConvergedReason() |
340 | | -assert converged_reason > 0, f"Krylov solver has not converged, reason: {converged_reason}." |
| 348 | +def monolithic_solver(): |
| 349 | + # Some defaults solver strategies based on what PETSc has |
| 350 | + # been configured with |
| 351 | + local_solver = "svd" |
| 352 | + local_solver_type = "dummy" |
| 353 | + coarse_solver = "svd" |
| 354 | + coarse_solver_type = "dummy" |
| 355 | + if PETSc.Sys().hasExternalPackage("mumps"): |
| 356 | + local_solver = "cholesky" |
| 357 | + local_solver_type = "mumps" |
| 358 | + coarse_solver = "cholesky" |
| 359 | + coarse_solver_type = "mumps" |
| 360 | + elif PETSc.Sys().hasExternalPackage("superlu"): |
| 361 | + local_solver = "lu" |
| 362 | + local_solver_type = "superlu" |
| 363 | + elif PETSc.Sys().hasExternalPackage("umfpack"): |
| 364 | + local_solver = "lu" |
| 365 | + local_solver_type = "umfpack" |
| 366 | + if coarse_solver == "svd" and PETSc.Sys().hasExternalPackage("superlu_dist"): |
| 367 | + coarse_solver = "lu" |
| 368 | + coarse_solver_type = "superlu_dist" |
| 369 | + |
| 370 | + return fem.petsc.LinearProblem( |
| 371 | + a, |
| 372 | + L, |
| 373 | + u=[sigma, u], |
| 374 | + kind="is", |
| 375 | + bcs=bcs, |
| 376 | + petsc_options_prefix="demo_mixed_poisson_", |
| 377 | + petsc_options={ |
| 378 | + "ksp_rtol": 1e-8, |
| 379 | + "ksp_view": None, |
| 380 | + "pc_type": "bddc", |
| 381 | + "pc_bddc_use_local_mat_graph": False, |
| 382 | + "pc_bddc_benign_trick": None, |
| 383 | + "pc_bddc_nonetflux": None, |
| 384 | + "pc_bddc_detect_disconnected": None, |
| 385 | + "pc_bddc_dirichlet_pc_type": local_solver, |
| 386 | + "pc_bddc_dirichlet_pc_factor_mat_solver_type": local_solver_type, |
| 387 | + "pc_bddc_neumann_pc_type": local_solver, |
| 388 | + "pc_bddc_neumann_pc_factor_mat_solver_type": local_solver_type, |
| 389 | + "pc_bddc_coarse_pc_type": coarse_solver, |
| 390 | + "pc_bddc_coarse_pc_factor_mat_solver_type": coarse_solver_type, |
| 391 | + }, |
| 392 | + ) |
| 393 | + |
| 394 | + |
341 | 395 | # - |
342 | 396 |
|
343 | | -# We save the solution `u` in VTX format: |
| 397 | +# Once we have created the problem solvers, we can |
| 398 | +# solve the linear systems. The `LinearProblem` class will |
| 399 | +# automatically assemble the linear system, apply the boundary |
| 400 | +# conditions, call the Krylov solver and update the solution |
| 401 | +# vectors `u` and `sigma`. For each solve, we save the solution |
| 402 | +# `u` in VTX format: |
344 | 403 |
|
345 | 404 | # + |
346 | | -if dolfinx.has_adios2: |
347 | | - from dolfinx.io import VTXWriter |
348 | | - |
349 | | - u.name = "u" |
350 | | - with VTXWriter(msh.comm, "output_mixed_poisson.bp", u, "bp4") as f: |
351 | | - f.write(0.0) |
352 | | -else: |
353 | | - print("ADIOS2 required for VTX output.") |
| 405 | +if not dolfinx.has_adios2: |
| 406 | + PETSc.Sys.Print("ADIOS2 required for VTX output.") |
| 407 | + |
| 408 | + |
| 409 | +for strategy_name, strategy in ("block", block_solver), ("monolithic", monolithic_solver): |
| 410 | + problem = strategy() |
| 411 | + ksp = problem.solver |
| 412 | + ksp.setMonitor( |
| 413 | + lambda _, its, rnorm: PETSc.Sys.Print(f" iteration: {its:>4d}, residual: {rnorm:.3e}") |
| 414 | + ) |
| 415 | + PETSc.Sys.Print(f"\n\nSolving mixed poisson problem with {strategy_name} solver.") |
| 416 | + problem.solve() |
| 417 | + converged_reason = problem.solver.getConvergedReason() |
| 418 | + assert converged_reason > 0, ( |
| 419 | + f"Krylov solver for {strategy_name} has not converged, reason: {converged_reason}." |
| 420 | + ) |
| 421 | + |
| 422 | + if dolfinx.has_adios2: |
| 423 | + from dolfinx.io import VTXWriter |
| 424 | + |
| 425 | + u.name = "u" |
| 426 | + with VTXWriter(msh.comm, f"output_mixed_poisson_{strategy_name}.bp", u, "bp4") as f: |
| 427 | + f.write(0.0) |
354 | 428 | # - |
0 commit comments