Skip to content

Commit 2fffae1

Browse files
committed
Add tests for Clement interpolant
1 parent eb5c67f commit 2fffae1

1 file changed

Lines changed: 152 additions & 0 deletions

File tree

test/test_transfer_clement.py

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
"""Test the Clement interpolation functionality from the transfer module."""
2+
3+
import firedrake as fd
4+
import numpy as np
5+
import pytest
6+
import ufl
7+
8+
from adapt_common.transfer import clement_interpolant
9+
from adapt_common.utility import cofunction2function, get_function_space
10+
11+
12+
@pytest.fixture
13+
def n():
14+
"""Set number of elements in each direction."""
15+
return 5
16+
17+
18+
@pytest.fixture(params=[1, 2, 3], ids=["1D", "2D", "3D"])
19+
def topological_dimension(request):
20+
"""Set the topological dimension."""
21+
return request.param
22+
23+
24+
@pytest.fixture(
25+
params=[(), (1,), (2,), (3,), (1, 1), (1, 2), (2, 2), (2, 3), (3, 3)],
26+
ids=[
27+
"scalar",
28+
"1vector",
29+
"2vector",
30+
"3vector",
31+
"1x1tensor",
32+
"1x2tensor",
33+
"2x2tensor",
34+
"2x3tensor",
35+
"3x3tensor",
36+
],
37+
)
38+
def shape(request):
39+
"""Set the tensor shape."""
40+
return request.param
41+
42+
43+
@pytest.fixture
44+
def uniform_mesh(n, topological_dimension):
45+
"""Create a uniform unit simplex mesh with n elements in each direction."""
46+
return {
47+
1: fd.UnitIntervalMesh(n),
48+
2: fd.UnitSquareMesh(n, n),
49+
3: fd.UnitCubeMesh(n, n, n),
50+
}[topological_dimension]
51+
52+
53+
@pytest.fixture
54+
def P0(uniform_mesh, shape):
55+
"""Create a P0 function space of a given shape on the uniform mesh."""
56+
return get_function_space(uniform_mesh, "DG", 0, shape)
57+
58+
59+
@pytest.fixture
60+
def P1(uniform_mesh, shape):
61+
"""Create a P1 function space of a given shape on the uniform mesh."""
62+
return get_function_space(uniform_mesh, "CG", 1, shape)
63+
64+
65+
@pytest.fixture
66+
def expression(uniform_mesh, topological_dimension, shape, P1):
67+
"""Expression function for testing based on tensor shape."""
68+
x = fd.SpatialCoordinate(uniform_mesh)
69+
if len(shape) == 0:
70+
return sum(x)
71+
elif len(shape) == 1:
72+
dim = shape[0]
73+
return ufl.as_vector(
74+
x if dim == topological_dimension else [x[0] for _ in range(dim)]
75+
)
76+
rows = [
77+
fd.Constant(tuple(range(i + 1, i + 1 + topological_dimension)))
78+
for i in range(P1.block_size)
79+
]
80+
return ufl.as_tensor(np.reshape([ufl.dot(row, x) for row in rows], shape))
81+
82+
83+
def test_source_type_error():
84+
"""Test that providing an invalid source type raises a TypeError."""
85+
type_err = (
86+
"Expected Cofunction or Function, got '<class 'firedrake.constant.Constant'>'."
87+
)
88+
with pytest.raises(TypeError, match=type_err):
89+
clement_interpolant(fd.Constant(0.0))
90+
91+
92+
def test_source_space_error(uniform_mesh):
93+
"""Test that providing a non-P0 source function raises a ValueError."""
94+
shape = ()
95+
fs = get_function_space(uniform_mesh, "CG", 1, shape)
96+
val_err = "Source function provided must be from a P0 space."
97+
with pytest.raises(ValueError, match=val_err):
98+
clement_interpolant(fd.Function(fs))
99+
100+
101+
def test_target_function_space_error(uniform_mesh):
102+
"""Test that providing a non-P1 target space raises a ValueError."""
103+
shape = ()
104+
fs = get_function_space(uniform_mesh, "DG", 0, shape)
105+
val_err = "Target space provided must be P1."
106+
with pytest.raises(ValueError, match=val_err):
107+
clement_interpolant(fd.Function(fs), target=fs)
108+
109+
110+
def test_cofunction_dual_target_function_space(P0, P1):
111+
"""Test that Clement interpolation works with dual spaces."""
112+
source = fd.Cofunction(P0.dual())
113+
source.dat.data[:] = 1.0
114+
target = clement_interpolant(source, target=P1.dual())
115+
assert isinstance(target, fd.Cofunction)
116+
target_function = cofunction2function(target)
117+
118+
# Account for the fact that the Clement interpolant breaks down at domain boundaries
119+
expected = fd.Function(P1).assign(1.0)
120+
fd.DirichletBC(P1, expected, "on_boundary").apply(target_function)
121+
122+
np.testing.assert_almost_equal(target_function.dat.data, expected.dat.data)
123+
124+
125+
def test_cofunction_primal_target_function_space(P0, P1):
126+
"""Test that Clement interpolation works with primal target spaces."""
127+
source = fd.Cofunction(P0.dual())
128+
source.dat.data[:] = 1.0
129+
target = clement_interpolant(source, target=P1)
130+
assert isinstance(target, fd.Function)
131+
132+
# Account for the fact that the Clement interpolant breaks down at domain boundaries
133+
expected = fd.Function(P1).assign(1.0)
134+
fd.DirichletBC(P1, expected, "on_boundary").apply(target)
135+
136+
np.testing.assert_almost_equal(target.dat.data, expected.dat.data)
137+
138+
139+
def test_volume_average(P0, P1, expression):
140+
"""Test Clement interpolation in the interior of a 2D domain."""
141+
exact = expression
142+
source_space = P0
143+
source = fd.Function(source_space).project(exact)
144+
target = clement_interpolant(source)
145+
target_function_space = P1
146+
expected = fd.Function(target_function_space).interpolate(exact)
147+
148+
# Account for the fact that the Clement interpolant breaks down at domain boundaries
149+
bc = fd.DirichletBC(target_function_space, expected, "on_boundary")
150+
bc.apply(target)
151+
152+
np.testing.assert_almost_equal(target.dat.data, expected.dat.data)

0 commit comments

Comments
 (0)