Skip to content

Commit f127bc7

Browse files
mscroggsanzil
andauthored
Add Sinwel construction for HHJ (#235)
* Add Sinwel construction for HHJ (#234) Co-authored-by: Andreas Zilian <[email protected]> * fix docs and isort * changelog * changelog --------- Co-authored-by: anzil <[email protected]> Co-authored-by: Andreas Zilian <[email protected]>
1 parent d22c7b3 commit f127bc7

File tree

5 files changed

+90
-9
lines changed

5 files changed

+90
-9
lines changed

CHANGELOG_SINCE_LAST_VERSION.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
- Bugfixes for piecewise functions on an interval
2+
- Added HHJ on a tetrahedron

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,7 @@ The reference tetrahedron has vertices (0, 0, 0), (1, 0, 0), (0, 1, 0), and (0,
248248
- enriched Galerkin (alternative names: EG)
249249
- enriched vector Galerkin (alternative names: locking-free enriched Galerkin, LFEG)
250250
- Guzman-Neilan
251+
- Hellan-Herrmann-Johnson (alternative names: HHJ)
251252
- Hermite
252253
- Kong-Mulder-Veldhuizen (alternative names: KMV)
253254
- Lagrange (alternative names: P)

symfem/elements/hhj.py

Lines changed: 45 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,20 @@
22
33
This element's definition appears in https://arxiv.org/abs/1909.09687
44
(Arnold, Walker, 2020)
5+
6+
For an alternative construction see (Sinwel, 2009) and sections 4.4.2.2 and 4.4.3.2
7+
https://numa.jku.at/media/filer_public/b7/42/b74263c9-f723-4076-b1b2-c2726126bf32/phd-sinwel.pdf
58
"""
69

710
import typing
811

912
from ..finite_element import CiarletElement
1013
from ..functionals import IntegralMoment, ListOfFunctionals, NormalInnerProductIntegralMoment
11-
from ..functions import FunctionInput
14+
from ..functions import FunctionInput, MatrixFunction
1215
from ..moments import make_integral_moment_dofs
1316
from ..polynomials import polynomial_set_vector
1417
from ..references import NonDefaultReferenceError, Reference
15-
from .lagrange import Lagrange, SymmetricMatrixLagrange
18+
from .lagrange import Lagrange
1619

1720

1821
class HellanHerrmannJohnson(CiarletElement):
@@ -28,20 +31,53 @@ def __init__(self, reference: Reference, order: int, variant: str = "equispaced"
2831
"""
2932
if reference.vertices != reference.reference_vertices:
3033
raise NonDefaultReferenceError()
31-
assert reference.name == "triangle"
34+
assert reference.name == "triangle" or reference.name == "tetrahedron"
3235

3336
poly: typing.List[FunctionInput] = []
34-
poly += [((p[0], p[1]), (p[1], p[2]))
35-
for p in polynomial_set_vector(reference.tdim, 3, order)]
37+
directions: typing.List[typing.Tuple[typing.Tuple[int, ...], ...]] = []
38+
directions_extra: typing.List[typing.Tuple[typing.Tuple[int, ...], ...]] = []
39+
40+
if reference.tdim == 2:
41+
poly = [((p[0], p[1]), (p[1], p[2]))
42+
for p in polynomial_set_vector(reference.tdim, 3, order)]
43+
directions = [((0, 1), (1, 0)),
44+
((-2, 1), (1, 0)),
45+
((0, -1), (-1, 2))]
46+
directions_extra = []
47+
if reference.tdim == 3:
48+
poly = [((p[0], p[1], p[2]), (p[1], p[3], p[4]), (p[2], p[4], p[5]))
49+
for p in polynomial_set_vector(reference.tdim, 6, order)]
50+
directions = [((-2, 1, 0), (1, 0, 0), (0, 0, 0)),
51+
((0, 1, -1), (1, -2, 1), (-1, 1, 0)),
52+
((0, 0, 0), (0, 0, -1), (0, -1, 2)),
53+
((0, 0, 1), (0, 0, 0), (1, 0, 0))]
54+
directions_extra = [((0, 0, -1), (0, 0, 1), (-1, 1, 0)),
55+
((0, -1, 0), (-1, 0, 1), (0, 1, 0))]
3656

3757
dofs: ListOfFunctionals = make_integral_moment_dofs(
3858
reference,
3959
facets=(NormalInnerProductIntegralMoment, Lagrange, order,
4060
{"variant": variant}),
41-
cells=(IntegralMoment, SymmetricMatrixLagrange, order - 1,
42-
{"variant": variant}),
4361
)
4462

63+
# cell functions
64+
if order > 0:
65+
space = Lagrange(reference, order - 1, variant)
66+
basis = space.get_basis_functions()
67+
for p, dof in zip(basis, space.dofs):
68+
for d in directions:
69+
dofs.append(IntegralMoment(
70+
reference, p * MatrixFunction(d), dof, entity=(reference.tdim, 0),
71+
mapping="double_covariant"))
72+
# cell functions extra
73+
space_extra = Lagrange(reference, order, variant)
74+
basis_extra = space_extra.get_basis_functions()
75+
for p, dof in zip(basis_extra, space_extra.dofs):
76+
for d in directions_extra:
77+
dofs.append(IntegralMoment(
78+
reference, p * MatrixFunction(d), dof, entity=(reference.tdim, 0),
79+
mapping="double_covariant"))
80+
4581
self.variant = variant
4682

4783
super().__init__(reference, order, poly, dofs, reference.tdim, reference.tdim ** 2,
@@ -56,7 +92,7 @@ def init_kwargs(self) -> typing.Dict[str, typing.Any]:
5692
return {"variant": self.variant}
5793

5894
names = ["Hellan-Herrmann-Johnson", "HHJ"]
59-
references = ["triangle"]
95+
references = ["triangle", "tetrahedron"]
6096
min_order = 0
6197
continuity = "inner H(div)"
62-
last_updated = "2023.06"
98+
last_updated = "2023.08"
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
"""Test Hellan-Herrmann-Johnson element."""
2+
3+
import pytest
4+
5+
from symfem import create_element
6+
from symfem.functions import VectorFunction
7+
from symfem.symbols import x
8+
from symfem.utils import allequal
9+
10+
11+
@pytest.mark.parametrize('reference', ['triangle', 'tetrahedron'])
12+
@pytest.mark.parametrize('order', [0, 1, 2])
13+
def test_create(reference, order):
14+
15+
element = create_element(reference, "HHJ", order)
16+
17+
# Get the basis functions associated with the interior
18+
basis = element.get_basis_functions()
19+
functions = [basis[d] for d in element.entity_dofs(element.reference.tdim, 0)]
20+
21+
if reference == "triangle":
22+
# Check that these tensor functions have zero normal normal component on each edge
23+
for f in functions:
24+
M, n = f.subs(x[0], 1 - x[1]), VectorFunction((1, 1))
25+
assert allequal((M @ n).dot(n), 0)
26+
M, n = f.subs(x[0], 0), VectorFunction((-1, 0))
27+
assert allequal((M @ n).dot(n), 0)
28+
M, n = f.subs(x[1], 0), VectorFunction((0, -1))
29+
assert allequal((M @ n).dot(n), 0)
30+
31+
if reference == "tetrahedron":
32+
# Check that these tensor functions have zero normal normal component on each face
33+
for f in functions:
34+
M, n = f.subs(x[0], 1 - x[1] - x[2]), VectorFunction((1, 1, 1))
35+
assert allequal((M @ n).dot(n), 0)
36+
M, n = f.subs(x[0], 0), VectorFunction((-1, 0, 0))
37+
assert allequal((M @ n).dot(n), 0)
38+
M, n = f.subs(x[1], 0), VectorFunction((0, -1, 0))
39+
assert allequal((M @ n).dot(n), 0)
40+
M, n = f.subs(x[2], 0), VectorFunction((0, 0, -1))
41+
assert allequal((M @ n).dot(n), 0)

test/utils.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@
7878
"bubble": [({"variant": "equispaced"}, [4])],
7979
"CR": [({"variant": "equispaced"}, [1])],
8080
"Regge": [({"variant": "point"}, range(3)), ({"variant": "integral"}, range(2))],
81+
"HHJ": [({"variant": "equispaced"}, range(3))],
8182
"Nedelec1": [({"variant": "equispaced"}, range(1, 3))],
8283
"Nedelec2": [({"variant": "equispaced"}, range(1, 3))],
8384
"RT": [({"variant": "equispaced"}, range(1, 3))],

0 commit comments

Comments
 (0)