Skip to content

Commit a445cd4

Browse files
authored
Add tests for 3D zany elements (#4459)
* TSFC: physical_normals * Test 3D zany elements
1 parent 0d0bff4 commit a445cd4

File tree

2 files changed

+53
-44
lines changed

2 files changed

+53
-44
lines changed

tests/firedrake/regression/test_projection_zany.py

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,17 @@ def jrc(a, b, n):
2424

2525

2626
@pytest.fixture
27-
def hierarchy(request):
27+
def hierarchy_2d(request):
2828
msh = UnitSquareMesh(2, 2)
2929
return MeshHierarchy(msh, 4)
3030

3131

32+
@pytest.fixture
33+
def hierarchy_3d(request):
34+
n = 2
35+
return [UnitCubeMesh(n*2**r, n*2**r, n*2**r) for r in range(3)]
36+
37+
3238
def do_projection(mesh, el_type, degree):
3339
V = FunctionSpace(mesh, el_type, degree)
3440

@@ -57,6 +63,12 @@ def do_projection(mesh, el_type, degree):
5763
return sqrt(assemble(inner(fh - f, fh - f) * dx))
5864

5965

66+
def run_convergence_test(mh, el, degree, convrate):
67+
err = [do_projection(m, el, degree) for m in mh]
68+
conv = np.diff(-np.log2(err))
69+
assert (conv > convrate).all()
70+
71+
6072
@pytest.mark.parametrize(('el', 'deg', 'convrate'),
6173
[('Johnson-Mercier', 1, 1.8),
6274
('Morley', 2, 2.4),
@@ -69,10 +81,15 @@ def do_projection(mesh, el_type, degree):
6981
('Bell', 5, 4.7),
7082
('Argyris', 5, 5.8),
7183
('Argyris', 6, 6.7)])
72-
def test_projection_zany_convergence_2d(hierarchy, el, deg, convrate):
73-
diff = np.array([do_projection(m, el, deg) for m in hierarchy[2:]])
74-
conv = np.log2(diff[:-1] / diff[1:])
75-
assert (np.array(conv) > convrate).all()
84+
def test_projection_zany_convergence_2d(hierarchy_2d, el, deg, convrate):
85+
run_convergence_test(hierarchy_2d[2:], el, deg, convrate)
86+
87+
88+
@pytest.mark.parametrize(('el', 'deg', 'convrate'),
89+
[('Johnson-Mercier', 1, 1.8),
90+
('Morley', 2, 2.4)])
91+
def test_projection_zany_convergence_3d(hierarchy_3d, el, deg, convrate):
92+
run_convergence_test(hierarchy_3d, el, deg, convrate)
7693

7794

7895
@pytest.mark.parametrize(('element', 'degree'),
@@ -81,9 +98,9 @@ def test_projection_zany_convergence_2d(hierarchy, el, deg, convrate):
8198
('HCT', 4),
8299
('Argyris', 5),
83100
('Argyris', 6)])
84-
def test_mass_conditioning(element, degree, hierarchy):
101+
def test_mass_conditioning(element, degree, hierarchy_2d):
85102
mass_cond = []
86-
for msh in hierarchy[1:4]:
103+
for msh in hierarchy_2d[1:4]:
87104
V = FunctionSpace(msh, element, degree)
88105
u = TrialFunction(V)
89106
v = TestFunction(V)

tsfc/fem.py

Lines changed: 29 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -155,36 +155,33 @@ def config(self):
155155
config["interface"] = self.interface
156156
return config
157157

158-
def cell_size(self):
159-
return self.interface.cell_size(self.mt.restriction)
160-
161-
def jacobian_at(self, point):
162-
ps = PointSingleton(point)
163-
expr = Jacobian(extract_unique_domain(self.mt.terminal))
164-
assert ps.expression.shape == (extract_unique_domain(expr).topological_dimension(), )
158+
def translate_point_expression(self, expr, point=None):
165159
if self.mt.restriction == '+':
166160
expr = PositiveRestricted(expr)
167161
elif self.mt.restriction == '-':
168162
expr = NegativeRestricted(expr)
163+
164+
cell = self.interface.fiat_cell
165+
sd = cell.get_spatial_dimension()
166+
if point is None:
167+
point, = cell.make_points(sd, 0, sd+1)
169168
config = {"point_set": PointSingleton(point)}
170169
config.update(self.config)
171170
config.update(use_canonical_quadrature_point_ordering=False) # quad point ordering not relevant.
172171
context = PointSetContext(**config)
173172
expr = self.preprocess(expr, context)
174173
return map_expr_dag(context.translator, expr)
175174

175+
def cell_size(self):
176+
return self.interface.cell_size(self.mt.restriction)
177+
178+
def jacobian_at(self, point):
179+
expr = Jacobian(extract_unique_domain(self.mt.terminal))
180+
return self.translate_point_expression(expr, point=point)
181+
176182
def detJ_at(self, point):
177183
expr = JacobianDeterminant(extract_unique_domain(self.mt.terminal))
178-
if self.mt.restriction == '+':
179-
expr = PositiveRestricted(expr)
180-
elif self.mt.restriction == '-':
181-
expr = NegativeRestricted(expr)
182-
config = {"point_set": PointSingleton(point)}
183-
config.update(self.config)
184-
config.update(use_canonical_quadrature_point_ordering=False) # quad point ordering not relevant.
185-
context = PointSetContext(**config)
186-
expr = self.preprocess(expr, context)
187-
return map_expr_dag(context.translator, expr)
184+
return self.translate_point_expression(expr, point=point)
188185

189186
def reference_normals(self):
190187
cell = self.interface.fiat_cell
@@ -210,30 +207,25 @@ def physical_tangents(self):
210207

211208
def physical_normals(self):
212209
cell = self.interface.fiat_cell
213-
if not (isinstance(cell, UFCSimplex) and cell.get_dimension() == 2):
210+
sd = cell.get_spatial_dimension()
211+
num_faces = len(cell.get_topology()[sd-1])
212+
if isinstance(cell, UFCSimplex) and sd == 2:
213+
pts = self.physical_tangents()
214+
return gem.ListTensor([[pts[i, 1], -1*pts[i, 0]] for i in range(num_faces)])
215+
elif isinstance(cell, UFCSimplex) and sd == 3:
216+
t = ufl.classes.CellEdgeVectors(extract_unique_domain(self.mt.terminal))
217+
edges = cell.get_connectivity()[(sd-1, 1)]
218+
normalize = lambda x: x / ufl.sqrt(ufl.dot(x, x))
219+
expr = ufl.as_tensor([-2.0*normalize(ufl.cross(t[edges[i][0], :], t[edges[i][1], :]))
220+
for i in range(num_faces)])
221+
return self.translate_point_expression(expr)
222+
else:
214223
raise NotImplementedError("Can't do physical normals on that cell yet")
215224

216-
num_edges = len(cell.get_topology()[1])
217-
pts = self.physical_tangents()
218-
return gem.ListTensor([[pts[i, 1], -1*pts[i, 0]] for i in range(num_edges)])
219-
220225
def physical_edge_lengths(self):
221226
expr = ufl.classes.CellEdgeVectors(extract_unique_domain(self.mt.terminal))
222-
if self.mt.restriction == '+':
223-
expr = PositiveRestricted(expr)
224-
elif self.mt.restriction == '-':
225-
expr = NegativeRestricted(expr)
226-
227-
cell = self.interface.fiat_cell
228-
sd = cell.get_spatial_dimension()
229-
num_edges = len(cell.get_topology()[1])
230-
expr = ufl.as_vector([ufl.sqrt(ufl.dot(expr[i, :], expr[i, :])) for i in range(num_edges)])
231-
config = {"point_set": PointSingleton(cell.make_points(sd, 0, sd+1)[0])}
232-
config.update(self.config)
233-
config.update(use_canonical_quadrature_point_ordering=False) # quad point ordering not relevant.
234-
context = PointSetContext(**config)
235-
expr = self.preprocess(expr, context)
236-
return map_expr_dag(context.translator, expr)
227+
expr = ufl.as_vector([ufl.sqrt(ufl.dot(expr[i, :], expr[i, :])) for i in range(expr.ufl_shape[0])])
228+
return self.translate_point_expression(expr)
237229

238230
def physical_points(self, point_set, entity=None):
239231
"""Converts point_set from reference to physical space"""

0 commit comments

Comments
 (0)