Skip to content

Commit b063854

Browse files
pbrubeckFAznaran
andauthored
Integral variant for Regge/HHJ (#96)
* docstring * cleanup test_fiat.py * Regge integral dofs * HHJ integral dofs in 2d/3d * support variant="integral(q)" * BidirectionalMoment dofs * address review comments * Cleanup H(div, S) elements (#98) * implementation of Hu-Zhang element --------- Co-authored-by: Francis Aznaran <faznaran@nd.edu>
1 parent c6369c7 commit b063854

20 files changed

+909
-671
lines changed

FIAT/__init__.py

+17-16
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,17 @@
22
evaluating arbitrary order Lagrange and many other elements.
33
Simplices in one, two, and three dimensions are supported."""
44

5-
import pkg_resources
5+
# Important functionality
6+
from FIAT.reference_element import ufc_cell, ufc_simplex # noqa: F401
7+
from FIAT.quadrature import make_quadrature # noqa: F401
8+
from FIAT.quadrature_schemes import create_quadrature # noqa: F401
9+
from FIAT.finite_element import FiniteElement, CiarletElement # noqa: F401
10+
from FIAT.hdivcurl import Hdiv, Hcurl # noqa: F401
11+
from FIAT.mixed import MixedElement # noqa: F401
12+
from FIAT.restricted import RestrictedElement # noqa: F401
13+
from FIAT.quadrature_element import QuadratureElement # noqa: F401
614

715
# Import finite element classes
8-
from FIAT.finite_element import FiniteElement, CiarletElement # noqa: F401
916
from FIAT.argyris import Argyris
1017
from FIAT.bernardi_raugel import BernardiRaugel
1118
from FIAT.bernstein import Bernstein
@@ -17,8 +24,7 @@
1724
from FIAT.christiansen_hu import ChristiansenHu
1825
from FIAT.johnson_mercier import JohnsonMercier
1926
from FIAT.brezzi_douglas_marini import BrezziDouglasMarini
20-
from FIAT.Sminus import TrimmedSerendipityEdge # noqa: F401
21-
from FIAT.Sminus import TrimmedSerendipityFace # noqa: F401
27+
from FIAT.Sminus import TrimmedSerendipityEdge, TrimmedSerendipityFace # noqa: F401
2228
from FIAT.SminusDiv import TrimmedSerendipityDiv # noqa: F401
2329
from FIAT.SminusCurl import TrimmedSerendipityCurl # noqa: F401
2430
from FIAT.brezzi_douglas_fortin_marini import BrezziDouglasFortinMarini
@@ -42,30 +48,22 @@
4248
from FIAT.raviart_thomas import RaviartThomas
4349
from FIAT.crouzeix_raviart import CrouzeixRaviart
4450
from FIAT.regge import Regge
51+
from FIAT.gopalakrishnan_lederer_schoberl import GopalakrishnanLedererSchoberlFirstKind
52+
from FIAT.gopalakrishnan_lederer_schoberl import GopalakrishnanLedererSchoberlSecondKind
4553
from FIAT.hellan_herrmann_johnson import HellanHerrmannJohnson
4654
from FIAT.arnold_winther import ArnoldWinther
4755
from FIAT.arnold_winther import ArnoldWintherNC
56+
from FIAT.hu_zhang import HuZhang
4857
from FIAT.mardal_tai_winther import MardalTaiWinther
4958
from FIAT.bubble import Bubble, FacetBubble
5059
from FIAT.tensor_product import TensorProductElement
5160
from FIAT.enriched import EnrichedElement
5261
from FIAT.nodal_enriched import NodalEnrichedElement
5362
from FIAT.discontinuous import DiscontinuousElement
5463
from FIAT.hdiv_trace import HDivTrace
55-
from FIAT.mixed import MixedElement # noqa: F401
56-
from FIAT.restricted import RestrictedElement # noqa: F401
57-
from FIAT.quadrature_element import QuadratureElement # noqa: F401
58-
from FIAT.kong_mulder_veldhuizen import KongMulderVeldhuizen # noqa: F401
64+
from FIAT.kong_mulder_veldhuizen import KongMulderVeldhuizen
5965
from FIAT.fdm_element import FDMLagrange, FDMDiscontinuousLagrange, FDMQuadrature, FDMBrokenH1, FDMBrokenL2, FDMHermite # noqa: F401
6066

61-
# Important functionality
62-
from FIAT.quadrature import make_quadrature # noqa: F401
63-
from FIAT.quadrature_schemes import create_quadrature # noqa: F401
64-
from FIAT.reference_element import ufc_cell, ufc_simplex # noqa: F401
65-
from FIAT.hdivcurl import Hdiv, Hcurl # noqa: F401
66-
67-
__version__ = pkg_resources.get_distribution("fenics-fiat").version
68-
6967
# List of supported elements and mapping to element classes
7068
supported_elements = {"Argyris": Argyris,
7169
"Bell": Bell,
@@ -112,8 +110,11 @@
112110
"BrokenElement": DiscontinuousElement,
113111
"HDiv Trace": HDivTrace,
114112
"Hellan-Herrmann-Johnson": HellanHerrmannJohnson,
113+
"Gopalakrishnan-Lederer-Schoberl 1st kind": GopalakrishnanLedererSchoberlFirstKind,
114+
"Gopalakrishnan-Lederer-Schoberl 2nd kind": GopalakrishnanLedererSchoberlSecondKind,
115115
"Conforming Arnold-Winther": ArnoldWinther,
116116
"Nonconforming Arnold-Winther": ArnoldWintherNC,
117+
"Hu-Zhang": HuZhang,
117118
"Mardal-Tai-Winther": MardalTaiWinther}
118119

119120
# List of extra elements

FIAT/arnold_winther.py

+98-130
Original file line numberDiff line numberDiff line change
@@ -9,157 +9,125 @@
99
# SPDX-License-Identifier: LGPL-3.0-or-later
1010

1111

12-
from FIAT.finite_element import CiarletElement
13-
from FIAT.dual_set import DualSet
14-
from FIAT.polynomial_set import ONSymTensorPolynomialSet, ONPolynomialSet
15-
from FIAT.functional import (
16-
PointwiseInnerProductEvaluation as InnerProduct,
17-
FrobeniusIntegralMoment as FIM,
18-
IntegralMomentOfTensorDivergence,
19-
IntegralLegendreNormalNormalMoment,
20-
IntegralLegendreNormalTangentialMoment)
21-
22-
from FIAT.quadrature import make_quadrature
12+
from FIAT import finite_element, dual_set, polynomial_set
13+
from FIAT.reference_element import TRIANGLE
14+
from FIAT.quadrature_schemes import create_quadrature
15+
from FIAT.functional import (ComponentPointEvaluation,
16+
TensorBidirectionalIntegralMoment,
17+
IntegralMomentOfTensorDivergence,
18+
IntegralLegendreNormalNormalMoment,
19+
IntegralLegendreNormalTangentialMoment)
2320

2421
import numpy
2522

2623

27-
class ArnoldWintherNCDual(DualSet):
28-
def __init__(self, cell, degree=2):
29-
if not degree == 2:
30-
raise ValueError("Nonconforming Arnold-Winther elements are"
31-
"only defined for degree 2.")
32-
dofs = []
33-
dof_ids = {}
34-
dof_ids[0] = {0: [], 1: [], 2: []}
35-
dof_ids[1] = {0: [], 1: [], 2: []}
36-
dof_ids[2] = {0: []}
24+
class ArnoldWintherNCDual(dual_set.DualSet):
25+
def __init__(self, ref_el, degree=2):
26+
if degree != 2:
27+
raise ValueError("Nonconforming Arnold-Winther elements are only defined for degree 2.")
28+
top = ref_el.get_topology()
29+
sd = ref_el.get_spatial_dimension()
30+
entity_ids = {dim: {entity: [] for entity in sorted(top[dim])} for dim in sorted(top)}
31+
nodes = []
3732

38-
dof_cur = 0
3933
# no vertex dofs
4034
# proper edge dofs now (not the contraints)
41-
# moments of normal . sigma against constants and linears.
42-
for entity_id in range(3): # a triangle has 3 edges
43-
for order in (0, 1):
44-
dofs += [IntegralLegendreNormalNormalMoment(cell, entity_id, order, 6),
45-
IntegralLegendreNormalTangentialMoment(cell, entity_id, order, 6)]
46-
dof_ids[1][entity_id] = list(range(dof_cur, dof_cur+4))
47-
dof_cur += 4
35+
# edge dofs: bidirectional nn and nt moments against P1.
36+
qdegree = degree + 2
37+
for entity in sorted(top[1]):
38+
cur = len(nodes)
39+
for order in range(2):
40+
nodes.append(IntegralLegendreNormalNormalMoment(ref_el, entity, order, qdegree))
41+
nodes.append(IntegralLegendreNormalTangentialMoment(ref_el, entity, order, qdegree))
42+
entity_ids[1][entity].extend(range(cur, len(nodes)))
4843

4944
# internal dofs: constant moments of three unique components
50-
Q = make_quadrature(cell, 2)
51-
52-
e1 = numpy.array([1.0, 0.0]) # euclidean basis 1
53-
e2 = numpy.array([0.0, 1.0]) # euclidean basis 2
54-
basis = [(e1, e1), (e1, e2), (e2, e2)] # basis for symmetric matrices
55-
for (v1, v2) in basis:
56-
v1v2t = numpy.outer(v1, v2)
57-
fatqp = numpy.zeros((2, 2, len(Q.pts)))
58-
for i, y in enumerate(v1v2t):
59-
for j, x in enumerate(y):
60-
for k in range(len(Q.pts)):
61-
fatqp[i, j, k] = x
62-
dofs.append(FIM(cell, Q, fatqp))
63-
dof_ids[2][0] = list(range(dof_cur, dof_cur + 3))
64-
dof_cur += 3
45+
cur = len(nodes)
46+
n = list(map(ref_el.compute_scaled_normal, sorted(top[sd-1])))
47+
Q = create_quadrature(ref_el, degree)
48+
phi = numpy.full(Q.get_weights().shape, 1/ref_el.volume())
49+
nodes.extend(TensorBidirectionalIntegralMoment(ref_el, n[i+1], n[j+1], Q, phi)
50+
for i in range(sd) for j in range(i, sd))
51+
entity_ids[2][0].extend(range(cur, len(nodes)))
6552

6653
# put the constraint dofs last.
67-
for entity_id in range(3):
68-
dof = IntegralLegendreNormalNormalMoment(cell, entity_id, 2, 6)
69-
dofs.append(dof)
70-
dof_ids[1][entity_id].append(dof_cur)
71-
dof_cur += 1
54+
for entity in sorted(top[1]):
55+
cur = len(nodes)
56+
nodes.append(IntegralLegendreNormalNormalMoment(ref_el, entity, 2, qdegree))
57+
entity_ids[1][entity].append(cur)
7258

73-
super().__init__(dofs, cell, dof_ids)
59+
super().__init__(nodes, ref_el, entity_ids)
7460

7561

76-
class ArnoldWintherNC(CiarletElement):
62+
class ArnoldWintherNC(finite_element.CiarletElement):
7763
"""The definition of the nonconforming Arnold-Winther element.
7864
"""
79-
def __init__(self, cell, degree=2):
80-
assert degree == 2, "Only defined for degree 2"
81-
Ps = ONSymTensorPolynomialSet(cell, degree)
82-
Ls = ArnoldWintherNCDual(cell, degree)
65+
def __init__(self, ref_el, degree=2):
66+
if ref_el.shape != TRIANGLE:
67+
raise ValueError(f"{type(self).__name__} only defined on triangles")
68+
Ps = polynomial_set.ONSymTensorPolynomialSet(ref_el, degree)
69+
Ls = ArnoldWintherNCDual(ref_el, degree)
70+
formdegree = ref_el.get_spatial_dimension() - 1
8371
mapping = "double contravariant piola"
72+
super().__init__(Ps, Ls, degree, formdegree, mapping=mapping)
8473

85-
super().__init__(Ps, Ls, degree, mapping=mapping)
8674

87-
88-
class ArnoldWintherDual(DualSet):
89-
def __init__(self, cell, degree=3):
90-
if not degree == 3:
91-
raise ValueError("Arnold-Winther elements are"
92-
"only defined for degree 3.")
93-
dofs = []
94-
dof_ids = {}
95-
dof_ids[0] = {0: [], 1: [], 2: []}
96-
dof_ids[1] = {0: [], 1: [], 2: []}
97-
dof_ids[2] = {0: []}
98-
99-
dof_cur = 0
75+
class ArnoldWintherDual(dual_set.DualSet):
76+
def __init__(self, ref_el, degree=3):
77+
if degree != 3:
78+
raise ValueError("Arnold-Winther elements are only defined for degree 3.")
79+
top = ref_el.get_topology()
80+
sd = ref_el.get_spatial_dimension()
81+
shp = (sd, sd)
82+
entity_ids = {dim: {entity: [] for entity in sorted(top[dim])} for dim in sorted(top)}
83+
nodes = []
10084

10185
# vertex dofs
102-
vs = cell.get_vertices()
103-
e1 = numpy.array([1.0, 0.0])
104-
e2 = numpy.array([0.0, 1.0])
105-
basis = [(e1, e1), (e1, e2), (e2, e2)]
106-
107-
dof_cur = 0
108-
109-
for entity_id in range(3):
110-
node = tuple(vs[entity_id])
111-
for (v1, v2) in basis:
112-
dofs.append(InnerProduct(cell, v1, v2, node))
113-
dof_ids[0][entity_id] = list(range(dof_cur, dof_cur + 3))
114-
dof_cur += 3
115-
116-
# edge dofs now
117-
# moments of normal . sigma against constants and linears.
118-
for entity_id in range(3):
119-
for order in (0, 1):
120-
dofs += [IntegralLegendreNormalNormalMoment(cell, entity_id, order, 6),
121-
IntegralLegendreNormalTangentialMoment(cell, entity_id, order, 6)]
122-
dof_ids[1][entity_id] = list(range(dof_cur, dof_cur+4))
123-
dof_cur += 4
124-
125-
# internal dofs: constant moments of three unique components
126-
Q = make_quadrature(cell, 3)
127-
128-
e1 = numpy.array([1.0, 0.0]) # euclidean basis 1
129-
e2 = numpy.array([0.0, 1.0]) # euclidean basis 2
130-
basis = [(e1, e1), (e1, e2), (e2, e2)] # basis for symmetric matrices
131-
for (v1, v2) in basis:
132-
v1v2t = numpy.outer(v1, v2)
133-
fatqp = numpy.zeros((2, 2, len(Q.pts)))
134-
for k in range(len(Q.pts)):
135-
fatqp[:, :, k] = v1v2t
136-
dofs.append(FIM(cell, Q, fatqp))
137-
dof_ids[2][0] = list(range(dof_cur, dof_cur + 3))
138-
dof_cur += 3
139-
140-
# Constraint dofs
141-
142-
Q = make_quadrature(cell, 5)
143-
144-
onp = ONPolynomialSet(cell, 2, (2,))
145-
pts = Q.get_points()
146-
onpvals = onp.tabulate(pts)[0, 0]
147-
148-
for i in list(range(3, 6)) + list(range(9, 12)):
149-
dofs.append(IntegralMomentOfTensorDivergence(cell, Q,
150-
onpvals[i, :, :]))
151-
152-
dof_ids[2][0] += list(range(dof_cur, dof_cur+6))
153-
154-
super().__init__(dofs, cell, dof_ids)
155-
156-
157-
class ArnoldWinther(CiarletElement):
86+
for v in sorted(top[0]):
87+
cur = len(nodes)
88+
pt, = ref_el.make_points(0, v, degree)
89+
nodes.extend(ComponentPointEvaluation(ref_el, (i, j), shp, pt)
90+
for i in range(sd) for j in range(i, sd))
91+
entity_ids[0][v].extend(range(cur, len(nodes)))
92+
93+
# edge dofs: bidirectional nn and nt moments against P_{k-2}
94+
max_order = degree - 2
95+
qdegree = degree + max_order
96+
for entity in sorted(top[1]):
97+
cur = len(nodes)
98+
for order in range(max_order+1):
99+
nodes.append(IntegralLegendreNormalNormalMoment(ref_el, entity, order, qdegree))
100+
nodes.append(IntegralLegendreNormalTangentialMoment(ref_el, entity, order, qdegree))
101+
entity_ids[1][entity].extend(range(cur, len(nodes)))
102+
103+
# internal dofs: moments of unique components against P_{k-3}
104+
n = list(map(ref_el.compute_scaled_normal, sorted(top[sd-1])))
105+
Q = create_quadrature(ref_el, 2*(degree-1))
106+
P = polynomial_set.ONPolynomialSet(ref_el, degree-3, scale="L2 piola")
107+
phis = P.tabulate(Q.get_points())[(0,)*sd]
108+
nodes.extend(TensorBidirectionalIntegralMoment(ref_el, n[i+1], n[j+1], Q, phi)
109+
for phi in phis for i in range(sd) for j in range(i, sd))
110+
111+
# constraint dofs: moments of divergence against P_{k-1} \ P_{k-2}
112+
P = polynomial_set.ONPolynomialSet(ref_el, degree-1, shape=(sd,))
113+
dimPkm1 = P.expansion_set.get_num_members(degree-1)
114+
dimPkm2 = P.expansion_set.get_num_members(degree-2)
115+
PH = P.take([i + j * dimPkm1 for j in range(sd) for i in range(dimPkm2, dimPkm1)])
116+
phis = PH.tabulate(Q.get_points())[(0,)*sd]
117+
nodes.extend(IntegralMomentOfTensorDivergence(ref_el, Q, phi) for phi in phis)
118+
119+
entity_ids[2][0].extend(range(cur, len(nodes)))
120+
super().__init__(nodes, ref_el, entity_ids)
121+
122+
123+
class ArnoldWinther(finite_element.CiarletElement):
158124
"""The definition of the conforming Arnold-Winther element.
159125
"""
160-
def __init__(self, cell, degree=3):
161-
assert degree == 3, "Only defined for degree 3"
162-
Ps = ONSymTensorPolynomialSet(cell, degree)
163-
Ls = ArnoldWintherDual(cell, degree)
126+
def __init__(self, ref_el, degree=3):
127+
if ref_el.shape != TRIANGLE:
128+
raise ValueError(f"{type(self).__name__} only defined on triangles")
129+
Ps = polynomial_set.ONSymTensorPolynomialSet(ref_el, degree)
130+
Ls = ArnoldWintherDual(ref_el, degree)
131+
formdegree = ref_el.get_spatial_dimension() - 1
164132
mapping = "double contravariant piola"
165-
super().__init__(Ps, Ls, degree, mapping=mapping)
133+
super().__init__(Ps, Ls, degree, formdegree, mapping=mapping)

FIAT/expansions.py

+7-6
Original file line numberDiff line numberDiff line change
@@ -279,16 +279,19 @@ def __init__(self, ref_el, scale=None, variant=None):
279279
self._dmats_cache = {}
280280
self._cell_node_map_cache = {}
281281

282-
def get_scale(self, cell=0):
282+
def get_scale(self, n, cell=0):
283283
scale = self.scale
284+
sd = self.ref_el.get_spatial_dimension()
284285
if isinstance(scale, str):
285-
sd = self.ref_el.get_spatial_dimension()
286286
vol = self.ref_el.volume_of_subcomplex(sd, cell)
287287
scale = scale.lower()
288288
if scale == "orthonormal":
289289
scale = math.sqrt(1.0 / vol)
290290
elif scale == "l2 piola":
291291
scale = 1.0 / vol
292+
elif n == 0 and sd > 1 and len(self.affine_mappings) == 1:
293+
# return 1 for n=0 to make regression tests pass
294+
scale = 1
292295
return scale
293296

294297
def get_num_members(self, n):
@@ -310,9 +313,7 @@ def _tabulate_on_cell(self, n, pts, order=0, cell=0, direction=None):
310313
ref_pts = numpy.add(numpy.dot(pts, A.T), b).T
311314
Jinv = A if direction is None else numpy.dot(A, direction)[:, None]
312315
sd = self.ref_el.get_spatial_dimension()
313-
314-
# Always return 1 for n=0 to make regression tests pass
315-
scale = 1.0 if n == 0 and len(self.affine_mappings) == 1 else self.get_scale(cell=cell)
316+
scale = self.get_scale(n, cell=cell)
316317
phi = dubiner_recurrence(sd, n, lorder, ref_pts, Jinv,
317318
scale, variant=self.variant)
318319
if self.continuity == "C0":
@@ -549,7 +550,7 @@ def _tabulate_on_cell(self, n, pts, order=0, cell=0, direction=None):
549550
Jinv = A[0, 0] if direction is None else numpy.dot(A, direction)
550551
xs = numpy.add(numpy.dot(pts, A.T), b)
551552
results = {}
552-
scale = self.get_scale(cell=cell) * numpy.sqrt(2 * numpy.arange(n+1) + 1)
553+
scale = self.get_scale(n, cell=cell) * numpy.sqrt(2 * numpy.arange(n+1) + 1)
553554
for k in range(order+1):
554555
v = numpy.zeros((n + 1, len(xs)), xs.dtype)
555556
if n >= k:

0 commit comments

Comments
 (0)