Skip to content

Commit cc73f78

Browse files
demo BDDC mixed poisson
1 parent 4d4591e commit cc73f78

File tree

1 file changed

+177
-103
lines changed

1 file changed

+177
-103
lines changed

python/demo/demo_mixed-poisson.py

Lines changed: 177 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@
2222
# PETSc/petsc4y.
2323
# * Construct a Hypre Auxiliary Maxwell Space (AMS) preconditioner for
2424
# $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)
2528
#
2629
# ```{admonition} Download sources
2730
# :class: download
@@ -103,21 +106,27 @@
103106
from basix.ufl import element
104107
from dolfinx import fem, mesh
105108
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
107110

108111
# Solution scalar (e.g., float32, complex128) and geometry (float32/64)
109112
# types
110113
dtype = dolfinx.default_scalar_type
111114
xdtype = dolfinx.default_real_type
112115
# -
113116

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
116119
# dimensions. Application in three-dimensions would require a number of
117120
# 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.
118125

119126
# +
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+
)
121130
# -
122131
#
123132
# Here we construct compatible function spaces for the mixed Poisson
@@ -226,41 +235,14 @@
226235
# -
227236

228237
# 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.
229242
# As we will use different preconditions for the individual blocks of
230243
# the saddle point problem, we specify the matrix kind to be "nest",
231244
# so that we can use # [`fieldsplit`](https://petsc.org/release/manual/ksp/#sec-block-matrices)
232245
# (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-
264246
# For the $P_{11}$ block, which is the discontinuous Lagrange mass
265247
# matrix, we let the preconditioner be the default, which is incomplete
266248
# LU factorisation and which can solve the block exactly in one
@@ -273,82 +255,174 @@
273255
# $H({\rm div})$ and $H({\rm curl})$ spaces are effectively the same in
274256
# two-dimensions, just rotated by $\pi/2.
275257

258+
276259
# +
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()
305323
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+
329336
# -
330337

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+
336346

337347
# +
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+
341395
# -
342396

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:
344403

345404
# +
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)
354428
# -

0 commit comments

Comments
 (0)