Skip to content

Commit be3bd4e

Browse files
committed
Implement HDivTrace as a macroelement
1 parent e04a204 commit be3bd4e

13 files changed

Lines changed: 254 additions & 442 deletions

FIAT/bubble.py

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
# SPDX-License-Identifier: LGPL-3.0-or-later
88

99
from FIAT.lagrange import Lagrange
10-
from FIAT.hierarchical import IntegratedLegendre
1110
from FIAT.restricted import RestrictedElement
1211
from itertools import chain
1312

@@ -16,11 +15,7 @@ class CodimBubble(RestrictedElement):
1615
"""Bubbles of a certain codimension."""
1716

1817
def __init__(self, ref_el, degree, codim, variant=None):
19-
if variant and variant.startswith("integral"):
20-
CG = IntegratedLegendre
21-
else:
22-
CG = Lagrange
23-
element = CG(ref_el, degree, variant=variant)
18+
element = Lagrange(ref_el, degree, variant=variant)
2419

2520
cell_dim = ref_el.get_dimension()
2621
assert cell_dim == max(element.entity_dofs().keys())

FIAT/discontinuous_lagrange.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
from FIAT.barycentric_interpolation import LagrangePolynomialSet, get_lagrange_points
1515
from FIAT.polynomial_set import mis
1616
from FIAT.check_format_variant import parse_lagrange_variant
17+
from FIAT.hierarchical import Legendre
1718

1819

1920
def make_entity_permutations(dim, npoints):
@@ -215,6 +216,8 @@ class DiscontinuousLagrange(finite_element.CiarletElement):
215216
macroelement for Scott-Vogelius.
216217
"""
217218
def __new__(cls, ref_el, degree, variant="equispaced"):
219+
if variant and variant.startswith("integral"):
220+
return Legendre(ref_el, degree, variant=variant)
218221
if degree == 0:
219222
splitting, _ = parse_lagrange_variant(variant, discontinuous=True)
220223
if splitting is None and not ref_el.is_macrocell():
@@ -230,7 +233,7 @@ def __init__(self, ref_el, degree, variant="equispaced"):
230233
dual = BrokenLagrangeDualSet(ref_el, degree, point_variant=point_variant)
231234
else:
232235
dual = DiscontinuousLagrangeDualSet(ref_el, degree, point_variant=point_variant)
233-
if ref_el.shape == LINE:
236+
if ref_el.shape == LINE and not ref_el.is_trace():
234237
# In 1D we can use the primal basis as the expansion set,
235238
# avoiding any round-off coming from a basis transformation
236239
points = get_lagrange_points(dual)

FIAT/dual_set.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -277,15 +277,15 @@ def merge_entities(nodes, ref_el, entity_ids, entity_permutations):
277277
parent_cell = ref_el.get_parent()
278278
if parent_cell is None:
279279
return nodes, ref_el, entity_ids, entity_permutations
280-
parent_ids = {}
280+
parent_top = parent_cell.get_topology()
281+
parent_ids = {dim: {entity: [] for entity in parent_top[dim]} for dim in parent_top}
281282
parent_permutations = None
282283
parent_to_children = ref_el.get_parent_to_children()
283284

284-
if all(isinstance(node, functional.PointEvaluation) for node in nodes):
285+
if all(isinstance(node, functional.PointEvaluation) for node in nodes) and not ref_el.is_trace():
285286
# Merge Lagrange dual with lexicographical reordering
286287
parent_nodes = []
287288
for dim in sorted(parent_to_children):
288-
parent_ids[dim] = {}
289289
for entity in sorted(parent_to_children[dim]):
290290
cur = len(parent_nodes)
291291
for child_dim, child_entity in parent_to_children[dim][entity]:
@@ -296,9 +296,7 @@ def merge_entities(nodes, ref_el, entity_ids, entity_permutations):
296296
# Merge everything else with the same node ordering
297297
parent_nodes = nodes
298298
for dim in sorted(parent_to_children):
299-
parent_ids[dim] = {}
300299
for entity in sorted(parent_to_children[dim]):
301-
parent_ids[dim][entity] = []
302300
for child_dim, child_entity in parent_to_children[dim][entity]:
303301
parent_ids[dim][entity].extend(entity_ids[child_dim][child_entity])
304302

FIAT/expansions.py

Lines changed: 34 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -345,6 +345,8 @@ def distance(alpha, beta):
345345

346346
def _tabulate(self, n, pts, order=0):
347347
"""A version of tabulate() that also works for a single point."""
348+
from FIAT.hdiv_trace import TraceError
349+
from FIAT.polynomial_set import mis
348350
pts = numpy.asarray(pts)
349351
unique = self.continuity is not None and order == 0
350352
cell_point_map = compute_cell_point_map(self.ref_el, pts, unique=unique)
@@ -354,6 +356,28 @@ def _tabulate(self, n, pts, order=0):
354356
if not self.ref_el.is_macrocell():
355357
return phis[0]
356358

359+
if self.ref_el.is_trace():
360+
parent = self.ref_el.get_parent()
361+
tdim = self.ref_el.get_spatial_dimension()
362+
gdim = parent.get_spatial_dimension()
363+
for cell in phis:
364+
# Promote facet keys to cell keys
365+
phi = phis[cell][(0,) * tdim]
366+
# Raise TraceError on gradient tabulations
367+
msg = "Gradients on trace elements are not well-defined."
368+
phis[cell] = {alpha: phi if sum(alpha) == 0 else TraceError(msg)
369+
for i in range(order+1)
370+
for alpha in mis(gdim, i)}
371+
if tdim == 0 and len(phis) == 0:
372+
# Hack for TensorProduct HDivTrace: do not raise TraceError on the interval
373+
for cell in parent.topology[gdim]:
374+
phis[cell] = {(0,)*gdim: numpy.zeros((1, 1))}
375+
elif sum(len(cell_point_map[cell]) for cell in cell_point_map) < len(pts):
376+
# Raise TraceError when interior points fail to be binned on facets
377+
for cell in parent.topology[gdim]:
378+
msg = "The HDivTrace element can only be tabulated on facets."
379+
phis[cell] = {(0,)*gdim: TraceError(msg)}
380+
357381
if pts.dtype == object:
358382
# If binning is undefined, scale by the characteristic function of each subcell
359383
Xi = compute_partition_of_unity(self.ref_el, pts, unique=unique)
@@ -362,13 +386,14 @@ def _tabulate(self, n, pts, order=0):
362386
phi[alpha] *= Xi[cell]
363387
elif not unique:
364388
# If binning is not unique, divide by the multiplicity of each point
365-
mult = numpy.zeros(pts.shape[:-1])
389+
mult = numpy.zeros(pts.shape[:-1], dtype=int)
366390
for cell, ipts in cell_point_map.items():
367391
mult[ipts] += 1
368-
for cell, ipts in cell_point_map.items():
369-
phi = phis[cell]
370-
for alpha in phi:
371-
phi[alpha] /= mult[None, ipts]
392+
if (mult != 1).any():
393+
for cell, ipts in cell_point_map.items():
394+
phi = phis[cell]
395+
for alpha in phi:
396+
phi[alpha] /= mult[None, ipts]
372397

373398
# Insert subcell tabulations into the corresponding submatrices
374399
idx = lambda *args: args if args[1] is Ellipsis else numpy.ix_(*args)
@@ -377,6 +402,9 @@ def _tabulate(self, n, pts, order=0):
377402
result = {}
378403
base_phi = tuple(phis.values())[0]
379404
for alpha in base_phi:
405+
if isinstance(base_phi[alpha], TraceError):
406+
result[alpha] = base_phi[alpha]
407+
continue
380408
dtype = base_phi[alpha].dtype
381409
result[alpha] = numpy.zeros((num_phis, *pts.shape[:-1]), dtype=dtype)
382410
for cell in cell_point_map:
@@ -497,8 +525,7 @@ def get_dmats(self, degree, cell=0):
497525
def tabulate(self, n, pts):
498526
if len(pts) == 0:
499527
return numpy.array([])
500-
sd = self.ref_el.get_spatial_dimension()
501-
return self._tabulate(n, pts)[(0,) * sd]
528+
return tuple(self._tabulate(n, pts).values())[0]
502529

503530
def tabulate_derivatives(self, n, pts):
504531
from FIAT.polynomial_set import mis
@@ -597,7 +624,6 @@ def polynomial_dimension(ref_el, n, continuity=None):
597624
if ref_el.get_shape() == reference_element.POINT:
598625
if n > 0:
599626
raise ValueError("Only degree zero polynomials supported on point elements.")
600-
return 1
601627
top = ref_el.get_topology()
602628
if continuity == "C0":
603629
space_dimension = sum(math.comb(n - 1, dim) * len(top[dim]) for dim in top)

0 commit comments

Comments
 (0)