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