Skip to content

Commit 7a51a2b

Browse files
authored
Enable macroelement variants for most elements (#152)
* Macroize all the non-zany elements * SimplicialComplex.get_facet_element
1 parent 369bda5 commit 7a51a2b

18 files changed

Lines changed: 559 additions & 205 deletions

FIAT/argyris.py

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -53,13 +53,17 @@ def __init__(self, ref_el, degree, variant, interpolant_deg):
5353
# interior dofs
5454
q = degree - 6
5555
if q >= 0:
56-
Q = create_quadrature(ref_el, interpolant_deg + q)
57-
Pq = polynomial_set.ONPolynomialSet(ref_el, q, scale=1)
58-
phis = Pq.tabulate(Q.get_points())[(0,) * sd]
59-
scale = ref_el.volume()
60-
cur = len(nodes)
61-
nodes.extend(IntegralMoment(ref_el, Q, phi/scale) for phi in phis)
62-
entity_ids[sd][0] = list(range(cur, len(nodes)))
56+
cell = ref_el.construct_subelement(sd)
57+
Q_ref = create_quadrature(cell, interpolant_deg + q)
58+
Pq = polynomial_set.ONPolynomialSet(cell, q, scale=1)
59+
Phis = Pq.tabulate(Q_ref.get_points())[(0,) * sd]
60+
for entity in sorted(top[sd]):
61+
Q = FacetQuadratureRule(ref_el, sd, entity, Q_ref)
62+
scale = 1 / Q.jacobian_determinant()
63+
phis = scale * Phis
64+
cur = len(nodes)
65+
nodes.extend(IntegralMoment(ref_el, Q, phi) for phi in phis)
66+
entity_ids[sd][entity] = list(range(cur, len(nodes)))
6367

6468
elif variant == "point":
6569
# edge dofs
@@ -77,9 +81,10 @@ def __init__(self, ref_el, degree, variant, interpolant_deg):
7781
# interior dofs
7882
if degree > 5:
7983
cur = len(nodes)
80-
internalpts = ref_el.make_points(2, 0, degree - 3)
81-
nodes.extend(PointEvaluation(ref_el, pt) for pt in internalpts)
82-
entity_ids[2][0] = list(range(cur, len(nodes)))
84+
for entity in sorted(top[sd]):
85+
internalpts = ref_el.make_points(sd, entity, degree - 3)
86+
nodes.extend(PointEvaluation(ref_el, pt) for pt in internalpts)
87+
entity_ids[sd][entity] = list(range(cur, len(nodes)))
8388
else:
8489
raise ValueError("Invalid variant for Argyris")
8590
super().__init__(nodes, ref_el, entity_ids)
@@ -103,7 +108,9 @@ class Argyris(finite_element.CiarletElement):
103108

104109
def __init__(self, ref_el, degree=5, variant=None):
105110

106-
variant, interpolant_deg = check_format_variant(variant, degree)
111+
splitting, variant, interpolant_deg = check_format_variant(variant, degree)
112+
if splitting is not None:
113+
raise NotImplementedError(f"{type(self).__name__} is not implemented as a macroelement.")
107114

108115
poly_set = polynomial_set.ONPolynomialSet(ref_el, degree, variant="bubble")
109116
dual = ArgyrisDualSet(ref_el, degree, variant, interpolant_deg)

FIAT/brezzi_douglas_marini.py

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from FIAT.check_format_variant import check_format_variant
1111
from FIAT.quadrature_schemes import create_quadrature
1212
from FIAT.quadrature import FacetQuadratureRule
13+
import numpy
1314

1415

1516
class BDMDualSet(dual_set.DualSet):
@@ -26,7 +27,7 @@ def __init__(self, ref_el, degree, variant, interpolant_deg):
2627
entity_ids[dim][entity] = []
2728

2829
if variant == "integral":
29-
facet = ref_el.get_facet_element()
30+
facet = ref_el.construct_subelement(sd-1)
3031
# Facet nodes are \int_F v\cdot n p ds where p \in P_{q}
3132
# degree is q
3233
Q_ref = create_quadrature(facet, interpolant_deg + degree)
@@ -56,14 +57,18 @@ def __init__(self, ref_el, degree, variant, interpolant_deg):
5657
if degree > 1:
5758
if interpolant_deg is None:
5859
interpolant_deg = degree
59-
cur = len(nodes)
60-
Q = create_quadrature(ref_el, interpolant_deg + degree - 1)
61-
Nedel = nedelec.Nedelec(ref_el, degree - 1, variant)
62-
Nedfs = Nedel.get_nodal_basis()
63-
Ned_at_qpts = Nedfs.tabulate(Q.get_points())[(0,) * sd]
64-
nodes.extend(functional.FrobeniusIntegralMoment(ref_el, Q, phi)
65-
for phi in Ned_at_qpts)
66-
entity_ids[sd][0] = list(range(cur, len(nodes)))
60+
61+
cell = ref_el.construct_subelement(sd)
62+
Q_ref = create_quadrature(cell, interpolant_deg + degree - 1)
63+
Nedel = nedelec.Nedelec(cell, degree - 1, variant)
64+
Ned_at_qpts = Nedel.tabulate(0, Q_ref.get_points())[(0,) * sd]
65+
for entity in top[sd]:
66+
Q = FacetQuadratureRule(ref_el, sd, entity, Q_ref)
67+
Jinv = numpy.linalg.inv(Q.jacobian())
68+
phis = numpy.tensordot(Jinv.T, Ned_at_qpts, (1, 1)).transpose((1, 0, 2))
69+
cur = len(nodes)
70+
nodes.extend(functional.FrobeniusIntegralMoment(ref_el, Q, phi) for phi in phis)
71+
entity_ids[sd][entity] = list(range(cur, len(nodes)))
6772

6873
super().__init__(nodes, ref_el, entity_ids)
6974

@@ -90,7 +95,9 @@ class BrezziDouglasMarini(finite_element.CiarletElement):
9095

9196
def __init__(self, ref_el, degree, variant=None):
9297

93-
variant, interpolant_deg = check_format_variant(variant, degree)
98+
splitting, variant, interpolant_deg = check_format_variant(variant, degree)
99+
if splitting is not None:
100+
ref_el = splitting(ref_el)
94101

95102
if degree < 1:
96103
raise Exception("BDM_k elements only valid for k >= 1")

FIAT/check_format_variant.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@
2727

2828

2929
def check_format_variant(variant, degree):
30+
splitting, variant = parse_lagrange_variant(variant, integral=True)
31+
3032
if variant is None:
3133
variant = "integral"
3234

@@ -44,7 +46,7 @@ def check_format_variant(variant, degree):
4446
raise ValueError('Choose either variant="point" or variant="integral"'
4547
'or variant="integral(q)"')
4648

47-
return variant, interpolant_degree
49+
return splitting, variant, interpolant_degree
4850

4951

5052
def parse_lagrange_variant(variant, discontinuous=False, integral=False):
@@ -61,7 +63,7 @@ def parse_lagrange_variant(variant, discontinuous=False, integral=False):
6163

6264
default = "integral" if integral else "spectral"
6365
if integral:
64-
supported_point_variants = {"integral": None}
66+
supported_point_variants = {"integral": None, "point": "point"}
6567
elif discontinuous:
6668
supported_point_variants = supported_dg_variants
6769
else:
@@ -81,6 +83,8 @@ def parse_lagrange_variant(variant, discontinuous=False, integral=False):
8183
k, = match.groups()
8284
call_split = IsoSplit
8385
splitting_args = (int(k),)
86+
elif opt.startswith("integral"):
87+
point_variant = opt
8488
elif opt in supported_point_variants:
8589
point_variant = supported_point_variants[opt]
8690
else:

FIAT/crouzeix_raviart.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -81,12 +81,13 @@ class CrouzeixRaviart(finite_element.CiarletElement):
8181
"""
8282

8383
def __init__(self, ref_el, degree, variant=None):
84-
85-
variant, interpolant_deg = check_format_variant(variant, degree)
86-
8784
if degree % 2 != 1:
8885
raise ValueError("Crouzeix-Raviart only defined for odd degree")
8986

90-
space = polynomial_set.ONPolynomialSet(ref_el, degree, variant="bubble")
87+
splitting, variant, interpolant_deg = check_format_variant(variant, degree)
88+
if splitting is not None:
89+
ref_el = splitting(ref_el)
90+
91+
poly_set = polynomial_set.ONPolynomialSet(ref_el, degree)
9192
dual = CrouzeixRaviartDualSet(ref_el, degree, variant, interpolant_deg)
92-
super().__init__(space, dual, degree)
93+
super().__init__(poly_set, dual, degree)

FIAT/expansions.py

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -274,11 +274,18 @@ def __init__(self, ref_el, scale=None, variant=None):
274274
if scale is None:
275275
scale = math.sqrt(1.0 / base_ref_el.volume())
276276
self.scale = scale
277+
self.variant = variant
277278
self.continuity = "C0" if variant == "bubble" else None
278279
self.recurrence_order = 2
279280
self._dmats_cache = {}
280281
self._cell_node_map_cache = {}
281282

283+
def reconstruct(self, ref_el=None, scale=None, variant=None):
284+
"""Reconstructs this ExpansionSet with modified arguments."""
285+
return ExpansionSet(ref_el or self.ref_el,
286+
scale=scale or self.scale,
287+
variant=variant or self.variant)
288+
282289
def get_scale(self, n, cell=0):
283290
scale = self.scale
284291
sd = self.ref_el.get_spatial_dimension()
@@ -371,7 +378,7 @@ def _tabulate(self, n, pts, order=0):
371378
phi[alpha] /= mult[None, ipts]
372379

373380
# Insert subcell tabulations into the corresponding submatrices
374-
idx = lambda *args: args if args[1] is Ellipsis else numpy.ix_(*args)
381+
idx = lambda *args: args if args[-1] is Ellipsis else numpy.ix_(*args)
375382
num_phis = self.get_num_members(n)
376383
cell_node_map = self.get_cell_node_map(n)
377384
result = {}
@@ -599,7 +606,10 @@ def polynomial_dimension(ref_el, n, continuity=None):
599606
raise ValueError("Only degree zero polynomials supported on point elements.")
600607
return 1
601608
top = ref_el.get_topology()
602-
if continuity == "C0":
609+
610+
if isinstance(continuity, dict):
611+
space_dimension = sum(len(continuity[dim][0]) * len(top[dim]) for dim in top)
612+
elif continuity == "C0":
603613
space_dimension = sum(math.comb(n - 1, dim) * len(top[dim]) for dim in top)
604614
else:
605615
dim = ref_el.get_spatial_dimension()
@@ -620,7 +630,9 @@ def polynomial_entity_ids(ref_el, n, continuity=None):
620630
entity_ids = {}
621631
cur = 0
622632
for dim in sorted(top):
623-
if continuity == "C0":
633+
if isinstance(continuity, dict):
634+
dofs, = set(len(continuity[dim][entity]) for entity in continuity[dim])
635+
elif continuity == "C0":
624636
dofs = math.comb(n - 1, dim)
625637
else:
626638
# DG numbering

FIAT/finite_element.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
from FIAT.dual_set import DualSet
1616
from FIAT.polynomial_set import PolynomialSet
1717
from FIAT.quadrature_schemes import create_quadrature
18+
from FIAT.macro import MacroPolynomialSet
1819

1920

2021
class FiniteElement(object):
@@ -134,6 +135,11 @@ def __init__(self, poly_set, dual, order, formdegree=None, mapping="affine", ref
134135
ref_complex = ref_complex or poly_set.get_reference_element()
135136
super().__init__(ref_el, dual, order, formdegree, mapping, ref_complex)
136137

138+
# Tile the poly_set
139+
if ref_complex.is_macrocell() and len(poly_set) > len(dual):
140+
base_element = type(self)(ref_el, order)
141+
poly_set = MacroPolynomialSet(ref_complex, base_element)
142+
137143
if len(poly_set) != len(dual):
138144
raise ValueError(f"Dimension of function space is {len(poly_set)}, but got {len(dual)} nodes.")
139145

Lines changed: 40 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from FIAT import finite_element, dual_set, polynomial_set, expansions
2+
from FIAT.check_format_variant import check_format_variant
23
from FIAT.functional import TensorBidirectionalIntegralMoment as BidirectionalMoment
34
from FIAT.quadrature_schemes import create_quadrature
45
from FIAT.quadrature import FacetQuadratureRule
@@ -12,34 +13,29 @@ def __init__(self, ref_el, degree):
1213
nodes = []
1314
entity_ids = {dim: {entity: [] for entity in sorted(top[dim])} for dim in sorted(top)}
1415

15-
# Face dofs: moments of normal-tangential components against a basis for Pk
16-
ref_facet = ref_el.construct_subelement(sd-1)
17-
Qref = create_quadrature(ref_facet, 2*degree)
18-
P = polynomial_set.ONPolynomialSet(ref_facet, degree)
19-
phis = P.tabulate(Qref.get_points())[(0,) * (sd-1)]
20-
for f in sorted(top[sd-1]):
21-
cur = len(nodes)
22-
Q = FacetQuadratureRule(ref_el, sd-1, f, Qref)
23-
Jdet = Q.jacobian_determinant()
24-
normal = ref_el.compute_scaled_normal(f)
25-
tangents = ref_el.compute_tangents(sd-1, f)
26-
n = normal / Jdet
27-
nodes.extend(BidirectionalMoment(ref_el, t, n, Q, phi)
28-
for phi in phis for t in tangents)
29-
entity_ids[sd-1][f].extend(range(cur, len(nodes)))
30-
16+
# Face dofs: moments of normal-tangential components against a basis for P_k
3117
# Interior dofs: moments of normal-tangential components against a basis for P_{k-1}
32-
if degree > 0:
33-
cur = len(nodes)
34-
Q = create_quadrature(ref_el, 2*degree-1)
35-
P = polynomial_set.ONPolynomialSet(ref_el, degree-1, scale="L2 piola")
36-
phis = P.tabulate(Q.get_points())[(0,) * sd]
37-
for f in sorted(top[sd-1]):
38-
n = ref_el.compute_scaled_normal(f)
39-
tangents = ref_el.compute_tangents(sd-1, f)
40-
nodes.extend(BidirectionalMoment(ref_el, t, n, Q, phi)
41-
for phi in phis for t in tangents)
42-
entity_ids[sd][0].extend(range(cur, len(nodes)))
18+
for dim in (sd-1, sd):
19+
q = degree + sd-1 - dim
20+
if q < 0:
21+
continue
22+
23+
ref_facet = ref_el.construct_subelement(dim)
24+
Q_ref = create_quadrature(ref_facet, degree + q)
25+
P = polynomial_set.ONPolynomialSet(ref_facet, q, scale=1)
26+
phis = P.tabulate(Q_ref.get_points())[(0,) * dim]
27+
28+
for entity in sorted(top[dim]):
29+
cur = len(nodes)
30+
Q = FacetQuadratureRule(ref_el, dim, entity, Q_ref)
31+
Jdet = Q.jacobian_determinant()
32+
for f in ref_el.get_connectivity()[(dim, sd-1)][entity]:
33+
normal = ref_el.compute_scaled_normal(f)
34+
tangents = ref_el.compute_tangents(sd-1, f)
35+
n = normal / Jdet
36+
nodes.extend(BidirectionalMoment(ref_el, t, n, Q, phi)
37+
for phi in phis for t in tangents)
38+
entity_ids[dim][entity].extend(range(cur, len(nodes)))
4339

4440
super().__init__(nodes, ref_el, entity_ids)
4541

@@ -59,7 +55,14 @@ class GopalakrishnanLedererSchoberlSecondKind(finite_element.CiarletElement):
5955
the weak symmetry constraint.
6056
6157
"""
62-
def __init__(self, ref_el, degree):
58+
def __init__(self, ref_el, degree, variant=None):
59+
60+
splitting, variant, interpolant_deg = check_format_variant(variant, degree)
61+
assert variant == "integral"
62+
63+
if splitting is not None:
64+
ref_el = splitting(ref_el)
65+
6366
poly_set = polynomial_set.TracelessTensorPolynomialSet(ref_el, degree)
6467
dual = GLSDual(ref_el, degree)
6568
sd = ref_el.get_spatial_dimension()
@@ -68,7 +71,7 @@ def __init__(self, ref_el, degree):
6871
super().__init__(poly_set, dual, degree, formdegree, mapping=mapping)
6972

7073

71-
def GopalakrishnanLedererSchoberlFirstKind(ref_el, degree):
74+
def GopalakrishnanLedererSchoberlFirstKind(ref_el, degree, variant=None):
7275
"""The GLS element used for the Mass-Conserving mixed Stress (MCS)
7376
formulation for Stokes flow.
7477
@@ -77,12 +80,16 @@ def GopalakrishnanLedererSchoberlFirstKind(ref_el, degree):
7780
7881
Reference: https://doi.org/10.1093/imanum/drz022
7982
"""
80-
fe = GopalakrishnanLedererSchoberlSecondKind(ref_el, degree)
83+
fe = GopalakrishnanLedererSchoberlSecondKind(ref_el, degree, variant=variant)
8184
entity_dofs = fe.entity_dofs()
8285
sd = ref_el.get_spatial_dimension()
83-
dimPkm1 = (sd-1)*expansions.polynomial_dimension(ref_el.construct_subelement(sd-1), degree-1)
86+
facet = ref_el.construct_subelement(sd-1)
87+
dimPkm1 = (sd-1)*expansions.polynomial_dimension(facet, degree-1)
88+
8489
indices = []
85-
for f in entity_dofs[sd-1]:
90+
for f in sorted(entity_dofs[sd-1]):
8691
indices.extend(entity_dofs[sd-1][f][:dimPkm1])
87-
indices.extend(entity_dofs[sd][0])
92+
for cell in sorted(entity_dofs[sd]):
93+
indices.extend(entity_dofs[sd][cell])
94+
8895
return RestrictedElement(fe, indices=indices)

0 commit comments

Comments
 (0)