Skip to content

Commit 9f624cb

Browse files
authored
Fix Hypre auxiliary spaces (#4712)
* Fix Hypre auxiliary spaces
1 parent 6afc43b commit 9f624cb

File tree

4 files changed

+104
-164
lines changed

4 files changed

+104
-164
lines changed

firedrake/preconditioners/hypre_ads.py

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
from firedrake.preconditioners.base import PCBase
2+
from firedrake.preconditioners.fdm import tabulate_exterior_derivative
23
from firedrake.petsc import PETSc
34
from firedrake.function import Function
4-
from firedrake.ufl_expr import TestFunction
5+
from firedrake.ufl_expr import TrialFunction
56
from firedrake.dmhooks import get_function_space
67
from firedrake.preconditioners.hypre_ams import chop
78
from firedrake.interpolation import interpolate
8-
from finat.ufl import VectorElement
9+
from finat.ufl import FiniteElement, TensorElement, VectorElement
910
from ufl import grad, curl, SpatialCoordinate
1011
from pyop2.utils import as_tuple
1112

@@ -27,16 +28,34 @@ def initialize(self, obj):
2728
if formdegree != 2 or degree != 1:
2829
raise ValueError("Hypre ADS requires lowest order RT elements! (not %s of degree %d)" % (family, degree))
2930

30-
P1 = V.reconstruct(family="Lagrange", degree=1)
31-
NC1 = V.reconstruct(family="N1curl" if mesh.ufl_cell().is_simplex() else "NCE", degree=1)
31+
# Get the auxiliary Nedelec and Lagrange spaces and the coordinate space
32+
cell = V.ufl_element().cell
33+
NC1_element = FiniteElement("N1curl" if cell.is_simplex() else "NCE", cell=cell, degree=1)
34+
P1_element = FiniteElement("Lagrange", cell=cell, degree=1)
35+
coords_element = VectorElement(P1_element, dim=mesh.geometric_dimension())
36+
if V.shape:
37+
NC1_element = TensorElement(NC1_element, shape=V.shape)
38+
P1_element = TensorElement(P1_element, shape=V.shape)
39+
40+
NC1 = V.reconstruct(element=NC1_element)
41+
P1 = V.reconstruct(element=P1_element)
42+
VectorP1 = V.reconstruct(element=coords_element)
43+
3244
G_callback = appctx.get("get_gradient", None)
3345
if G_callback is None:
34-
G = chop(assemble(interpolate(grad(TestFunction(P1)), NC1)).petscmat)
46+
try:
47+
G = chop(assemble(interpolate(grad(TrialFunction(P1)), NC1)).petscmat)
48+
except NotImplementedError:
49+
# dual evaluation not yet implemented see https://github.com/firedrakeproject/fiat/issues/109
50+
G = tabulate_exterior_derivative(P1, NC1)
3551
else:
3652
G = G_callback(P1, NC1)
3753
C_callback = appctx.get("get_curl", None)
3854
if C_callback is None:
39-
C = chop(assemble(interpolate(curl(TestFunction(NC1)), V)).petscmat)
55+
try:
56+
C = chop(assemble(interpolate(curl(TrialFunction(NC1)), V)).petscmat)
57+
except NotImplementedError:
58+
C = tabulate_exterior_derivative(NC1, V)
4059
else:
4160
C = C_callback(NC1, V)
4261

@@ -50,7 +69,6 @@ def initialize(self, obj):
5069
pc.setHYPREDiscreteGradient(G)
5170
pc.setHYPREDiscreteCurl(C)
5271

53-
VectorP1 = P1.reconstruct(element=VectorElement(P1.ufl_element()))
5472
coords = Function(VectorP1).interpolate(SpatialCoordinate(mesh))
5573
pc.setCoordinates(coords.dat.data_ro.copy())
5674

firedrake/preconditioners/hypre_ams.py

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
import petsctools
22
from firedrake.preconditioners.base import PCBase
3+
from firedrake.preconditioners.fdm import tabulate_exterior_derivative
34
from firedrake.petsc import PETSc
45
from firedrake.function import Function
5-
from firedrake.ufl_expr import TestFunction
6+
from firedrake.ufl_expr import TrialFunction
67
from firedrake.dmhooks import get_function_space
78
from firedrake.utils import complex_mode
89
from firedrake.interpolation import interpolate
910
from ufl import grad, SpatialCoordinate
10-
from finat.ufl import VectorElement
11+
from finat.ufl import FiniteElement, TensorElement, VectorElement
1112
from pyop2.utils import as_tuple
1213

1314
__all__ = ("HypreAMS",)
@@ -48,10 +49,21 @@ def initialize(self, obj):
4849
if formdegree != 1 or degree != 1:
4950
raise ValueError("Hypre AMS requires lowest order Nedelec elements! (not %s of degree %d)" % (family, degree))
5051

51-
P1 = V.reconstruct(family="Lagrange", degree=1)
52+
# Get the auxiliary Lagrange space and the coordinate space
53+
P1_element = FiniteElement("Lagrange", degree=1)
54+
coords_element = VectorElement(P1_element, dim=mesh.geometric_dimension())
55+
if V.shape:
56+
P1_element = TensorElement(P1_element, shape=V.shape)
57+
P1 = V.reconstruct(element=P1_element)
58+
VectorP1 = V.reconstruct(element=coords_element)
59+
5260
G_callback = appctx.get("get_gradient", None)
5361
if G_callback is None:
54-
G = chop(assemble(interpolate(grad(TestFunction(P1)), V)).petscmat)
62+
try:
63+
G = chop(assemble(interpolate(grad(TrialFunction(P1)), V)).petscmat)
64+
except NotImplementedError:
65+
# dual evaluation not yet implemented see https://github.com/firedrakeproject/fiat/issues/109
66+
G = tabulate_exterior_derivative(P1, V)
5567
else:
5668
G = G_callback(P1, V)
5769

@@ -68,7 +80,6 @@ def initialize(self, obj):
6880
if zero_beta:
6981
pc.setHYPRESetBetaPoissonMatrix(None)
7082

71-
VectorP1 = P1.reconstruct(element=VectorElement(P1.ufl_element()))
7283
coords = Function(VectorP1).interpolate(SpatialCoordinate(mesh))
7384
pc.setCoordinates(coords.dat.data_ro.copy())
7485
pc.setFromOptions()

tests/firedrake/regression/test_hypre_ads.py

Lines changed: 28 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -2,89 +2,47 @@
22
from firedrake import *
33

44

5-
@pytest.mark.skiphypre
6-
@pytest.mark.skipcomplex
7-
def test_homogeneous_field_linear():
8-
mesh = UnitCubeMesh(10, 10, 10)
9-
V = FunctionSpace(mesh, "RT", 1)
10-
11-
u = TrialFunction(V)
12-
v = TestFunction(V)
13-
14-
a = inner(div(u), div(v))*dx + inner(u, v)*dx
15-
L = inner(Constant((1, 0.5, 4)), v)*dx
16-
17-
bc = DirichletBC(V, Constant((1, 0.5, 4)), (1, 2, 3, 4))
18-
19-
params = {'snes_type': 'ksponly',
20-
'ksp_type': 'cg',
21-
'ksp_max_it': '30',
22-
'ksp_rtol': '1e-15',
23-
'ksp_atol': '1e-15',
24-
'pc_type': 'python',
25-
'pc_python_type': 'firedrake.HypreADS',
26-
}
27-
28-
u = Function(V)
29-
solve(a == L, u, bc, solver_parameters=params)
30-
assert (errornorm(Constant((1, 0.5, 4)), u, 'L2') < 1e-10)
5+
@pytest.fixture(params=["simplex", "hexahedron"])
6+
def V(request):
7+
cell = request.param
8+
if cell == "simplex":
9+
mesh = UnitCubeMesh(10, 10, 10)
10+
V = FunctionSpace(mesh, "RT", 1)
11+
elif cell == "hexahedron":
12+
mesh = ExtrudedMesh(UnitSquareMesh(10, 10, quadrilateral=True), 10)
13+
V = FunctionSpace(mesh, "NCF", 1)
14+
else:
15+
raise ValueError(f"Unrecognized cell {cell}.")
16+
return V
3117

3218

3319
@pytest.mark.skiphypre
3420
@pytest.mark.skipcomplex
35-
def test_homogeneous_field_matfree():
36-
mesh = UnitCubeMesh(10, 10, 10)
37-
V = FunctionSpace(mesh, "RT", 1)
38-
21+
@pytest.mark.parametrize("mat_type,interface", [("aij", "linear"), ("matfree", "linear"), ("aij", "nonlinear")])
22+
def test_homogeneous_field(V, mat_type, interface):
3923
u = TrialFunction(V)
4024
v = TestFunction(V)
4125

26+
u_exact = Constant((1, 0.5, 4))
4227
a = inner(div(u), div(v))*dx + inner(u, v)*dx
43-
L = inner(Constant((1, 0.5, 4)), v)*dx
28+
L = inner(u_exact, v)*dx
4429

45-
bc = DirichletBC(V, Constant((1, 0.5, 4)), (1, 2, 3, 4))
30+
bc = DirichletBC(V, u_exact, (1, 2, 3, 4))
4631

47-
params = {'snes_type': 'ksponly',
48-
'mat_type': 'matfree',
49-
'ksp_type': 'cg',
50-
'ksp_max_it': '30',
51-
'ksp_rtol': '1e-15',
52-
'ksp_atol': '1e-15',
53-
'pc_type': 'python',
54-
'pc_python_type': 'firedrake.AssembledPC',
55-
'assembled_pc_type': 'python',
56-
'assembled_pc_python_type': 'firedrake.HypreADS',
57-
}
32+
params = {
33+
'snes_type': 'ksponly',
34+
'mat_type': mat_type,
35+
'pmat_type': 'aij',
36+
'ksp_type': 'cg',
37+
'ksp_max_it': '30',
38+
'ksp_rtol': '2e-15',
39+
'pc_type': 'python',
40+
'pc_python_type': 'firedrake.HypreADS',
41+
}
5842

5943
u = Function(V)
6044
solve(a == L, u, bc, solver_parameters=params)
61-
assert (errornorm(Constant((1, 0.5, 4)), u, 'L2') < 1e-10)
62-
63-
64-
@pytest.mark.skiphypre
65-
@pytest.mark.skipcomplex
66-
def test_homogeneous_field_nonlinear():
67-
mesh = UnitCubeMesh(10, 10, 10)
68-
V = FunctionSpace(mesh, "RT", 1)
69-
70-
u = Function(V)
71-
v = TestFunction(V)
72-
73-
F = inner(div(u), div(v))*dx + inner(u, v)*dx - inner(Constant((1, 0.5, 4)), v)*dx
74-
75-
bc = DirichletBC(V, Constant((1, 0.5, 4)), (1, 2, 3, 4))
76-
77-
params = {'snes_type': 'ksponly',
78-
'ksp_type': 'cg',
79-
'ksp_itmax': '30',
80-
'ksp_rtol': '1e-15',
81-
'ksp_atol': '1e-15',
82-
'pc_type': 'python',
83-
'pc_python_type': 'firedrake.HypreADS',
84-
}
85-
86-
solve(F == 0, u, bc, solver_parameters=params)
87-
assert (errornorm(Constant((1, 0.5, 4)), u, 'L2') < 1e-10)
45+
assert (errornorm(u_exact, u, 'L2') < 1e-10)
8846

8947

9048
@pytest.mark.skiphypre

tests/firedrake/regression/test_hypre_ams.py

Lines changed: 35 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -3,107 +3,60 @@
33
from firedrake import *
44

55

6-
@pytest.mark.skiphypre
7-
@pytest.mark.skipcomplex
8-
def test_homogeneous_field_linear():
9-
mesh = UnitCubeMesh(5, 5, 5)
10-
V = FunctionSpace(mesh, "N1curl", 1)
11-
V0 = VectorFunctionSpace(mesh, "DG", 0)
12-
13-
u = TrialFunction(V)
14-
v = TestFunction(V)
15-
16-
a = inner(curl(u), curl(v))*dx
17-
L = inner(Constant((0., 0., 0.)), v)*dx
18-
19-
x, y, z = SpatialCoordinate(mesh)
20-
B0 = 1
21-
constant_field = as_vector([-0.5*B0*(y - 0.5), 0.5*B0*(x - 0.5), 0])
22-
23-
bc = DirichletBC(V, constant_field, (1, 2, 3, 4))
24-
25-
params = {'snes_type': 'ksponly',
26-
'ksp_type': 'cg',
27-
'ksp_max_it': '30',
28-
'ksp_rtol': '2e-15',
29-
'pc_type': 'python',
30-
'pc_python_type': 'firedrake.HypreAMS',
31-
'pc_hypre_ams_zero_beta_poisson': True
32-
}
33-
34-
A = Function(V)
35-
solve(a == L, A, bc, solver_parameters=params)
36-
B = project(curl(A), V0)
37-
assert numpy.allclose(B.dat.data_ro, numpy.array((0., 0., 1.)), atol=1e-6)
6+
@pytest.fixture(params=["simplex", "hexahedron"])
7+
def V(request):
8+
cell = request.param
9+
if cell == "simplex":
10+
mesh = UnitCubeMesh(5, 5, 5)
11+
V = FunctionSpace(mesh, "N1curl", 1)
12+
elif cell == "hexahedron":
13+
mesh = ExtrudedMesh(UnitSquareMesh(5, 5, quadrilateral=True), 5)
14+
V = FunctionSpace(mesh, "NCE", 1)
15+
else:
16+
raise ValueError(f"Unrecognized cell {cell}.")
17+
return V
3818

3919

4020
@pytest.mark.skiphypre
4121
@pytest.mark.skipcomplex
42-
def test_homogeneous_field_matfree():
43-
mesh = UnitCubeMesh(5, 5, 5)
44-
V = FunctionSpace(mesh, "N1curl", 1)
45-
V0 = VectorFunctionSpace(mesh, "DG", 0)
46-
22+
@pytest.mark.parametrize("mat_type,interface", [("aij", "linear"), ("matfree", "linear"), ("aij", "nonlinear")])
23+
def test_homogeneous_field(V, mat_type, interface):
24+
mesh = V.mesh()
4725
u = TrialFunction(V)
4826
v = TestFunction(V)
4927

5028
a = inner(curl(u), curl(v))*dx
51-
L = inner(Constant((0., 0., 0.)), v)*dx
29+
L = inner(Constant([0, 0, 0]), v) * dx
5230

5331
x, y, z = SpatialCoordinate(mesh)
5432
B0 = 1
5533
constant_field = as_vector([-0.5*B0*(y - 0.5), 0.5*B0*(x - 0.5), 0])
5634

5735
bc = DirichletBC(V, constant_field, (1, 2, 3, 4))
5836

59-
params = {'snes_type': 'ksponly',
60-
'mat_type': 'matfree',
61-
'ksp_type': 'cg',
62-
'ksp_max_it': '30',
63-
'ksp_rtol': '2e-15',
64-
'pc_type': 'python',
65-
'pc_python_type': 'firedrake.AssembledPC',
66-
'assembled_pc_type': 'python',
67-
'assembled_pc_python_type': 'firedrake.HypreAMS',
68-
'assembled_pc_hypre_ams_zero_beta_poisson': True
69-
}
37+
params = {
38+
'snes_type': 'ksponly',
39+
'mat_type': mat_type,
40+
'pmat_type': 'aij',
41+
'ksp_type': 'cg',
42+
'ksp_max_it': '30',
43+
'ksp_rtol': '2e-15',
44+
'pc_type': 'python',
45+
'pc_python_type': 'firedrake.HypreAMS',
46+
'pc_hypre_ams_zero_beta_poisson': True
47+
}
7048

7149
A = Function(V)
72-
solve(a == L, A, bc, solver_parameters=params)
73-
B = project(curl(A), V0)
74-
assert numpy.allclose(B.dat.data_ro, numpy.array((0., 0., 1.)), atol=1e-6)
75-
50+
if interface == "linear":
51+
solve(a == L, A, bc, solver_parameters=params)
52+
elif interface == "nonlinear":
53+
F = action(a, A) - L
54+
solve(F == 0, A, bc, solver_parameters=params)
55+
else:
56+
raise ValueError(f"Unrecognized interface {interface}.")
7657

77-
@pytest.mark.skiphypre
78-
@pytest.mark.skipcomplex
79-
def test_homogeneous_field_nonlinear():
80-
mesh = UnitCubeMesh(5, 5, 5)
81-
V = FunctionSpace(mesh, "N1curl", 1)
8258
V0 = VectorFunctionSpace(mesh, "DG", 0)
83-
84-
u = Function(V)
85-
v = TestFunction(V)
86-
87-
a = inner(curl(u), curl(v))*dx
88-
L = inner(Constant((0., 0., 0.)), v)*dx
89-
90-
x, y, z = SpatialCoordinate(mesh)
91-
B0 = 1
92-
constant_field = as_vector([-0.5*B0*(y - 0.5), 0.5*B0*(x - 0.5), 0])
93-
94-
bc = DirichletBC(V, constant_field, (1, 2, 3, 4))
95-
96-
params = {'snes_type': 'ksponly',
97-
'ksp_type': 'cg',
98-
'ksp_itmax': '30',
99-
'ksp_rtol': '2e-15',
100-
'pc_type': 'python',
101-
'pc_python_type': 'firedrake.HypreAMS',
102-
'pc_hypre_ams_zero_beta_poisson': True
103-
}
104-
105-
solve(a - L == 0, u, bc, solver_parameters=params)
106-
B = project(curl(u), V0)
59+
B = project(curl(A), V0)
10760
assert numpy.allclose(B.dat.data_ro, numpy.array((0., 0., 1.)), atol=1e-6)
10861

10962

0 commit comments

Comments
 (0)