Skip to content

Commit b75d387

Browse files
committed
finat.ufl.HDivTraceElement, full support for tensor product elements
1 parent 2d802c9 commit b75d387

10 files changed

Lines changed: 161 additions & 32 deletions

File tree

FIAT/barycentric_interpolation.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ def __init__(self, ref_el, pts):
7575
x = pts[ibfs]
7676
if ref_el.is_trace():
7777
verts = ref_el.get_vertices_of_subcomplex(ref_el.topology[1][cell])
78-
A = numpy.diff(verts, axis=0)[0]/2
78+
A, = numpy.diff(verts, axis=0)
7979
A /= numpy.linalg.norm(A)
8080
b = -numpy.dot(numpy.sum(verts, axis=0)/2, A.T)
8181
self.affine_mappings[cell] = (A, b)

FIAT/expansions.py

Lines changed: 29 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -345,8 +345,6 @@ 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
350348
pts = numpy.asarray(pts)
351349
unique = self.continuity is not None and order == 0
352350
cell_point_map = compute_cell_point_map(self.ref_el, pts, unique=unique)
@@ -357,26 +355,7 @@ def _tabulate(self, n, pts, order=0):
357355
return phis[0]
358356

359357
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(())}
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)}
358+
phis = trace_tabulation(self.ref_el, cell_point_map, order, pts, phis)
380359

381360
if pts.dtype == object:
382361
# If binning is undefined, scale by the characteristic function of each subcell
@@ -402,7 +381,7 @@ def _tabulate(self, n, pts, order=0):
402381
result = {}
403382
base_phi = tuple(phis.values())[0]
404383
for alpha in base_phi:
405-
if isinstance(base_phi[alpha], TraceError):
384+
if isinstance(base_phi[alpha], Exception):
406385
result[alpha] = base_phi[alpha]
407386
continue
408387
dtype = base_phi[alpha].dtype
@@ -779,3 +758,30 @@ def compute_partition_of_unity(ref_el, pt, unique=True, tol=1E-12):
779758
mult = sum(masks)
780759
masks = [m / mult for m in masks]
781760
return masks
761+
762+
763+
def trace_tabulation(ref_el, cell_point_map, order, pts, phis):
764+
"""Lift trace tabulations into the cells and raise TraceError on invalid tabulations."""
765+
from FIAT.polynomial_set import mis
766+
from FIAT.hdiv_trace import TraceError
767+
parent = ref_el.get_parent()
768+
tdim = ref_el.get_spatial_dimension()
769+
gdim = parent.get_spatial_dimension()
770+
facet_key = (0, ) * tdim
771+
cell_key = (0, ) * gdim
772+
773+
for cell in phis:
774+
# Lift facet keys to cell keys
775+
phi = phis[cell][facet_key]
776+
# Raise TraceError on gradient tabulations
777+
msg = "Gradients on trace elements are not well-defined."
778+
phis[cell] = {alpha: phi if sum(alpha) == 0 else TraceError(msg)
779+
for i in range(order+1)
780+
for alpha in mis(gdim, i)}
781+
782+
if sum(len(cell_point_map[cell]) for cell in cell_point_map) < len(pts):
783+
# Raise TraceError when interior points fail to be binned on facets
784+
for cell in parent.topology[gdim]:
785+
msg = "The HDivTrace element can only be tabulated on facets."
786+
phis[cell] = {cell_key: TraceError(msg)}
787+
return phis

finat/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
from .cube import FlattenedDimensions # noqa: F401
3333
from .discontinuous import DiscontinuousElement # noqa: F401
3434
from .enriched import EnrichedElement # noqa: F401
35-
from .hdivcurl import HCurlElement, HDivElement # noqa: F401
35+
from .hdivcurl import HCurlElement, HDivElement, HDivTraceElement # noqa: F401
3636
from .mixed import MixedElement # noqa: F401
3737
from .nodal_enriched import NodalEnrichedElement # noqa: F401
3838
from .quadrature_element import QuadratureElement, make_quadrature_element # noqa: F401

finat/element_factory.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,12 @@ def convert_tensorproductelement(element, **kwargs):
287287
return finat.TensorProductElement(elements), deps
288288

289289

290+
@convert.register(finat.ufl.HDivTraceElement)
291+
def convert_hdivtraceelement(element, **kwargs):
292+
finat_elem, deps = _create_element(element._element, **kwargs)
293+
return finat.HDivTraceElement(finat_elem), deps
294+
295+
290296
@convert.register(finat.ufl.HDivElement)
291297
def convert_hdivelement(element, **kwargs):
292298
finat_elem, deps = _create_element(element._element, **kwargs)

finat/fiat_elements.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,8 @@ def basis_evaluation(self, order, ps, entity=None, coordinate_mapping=None):
112112
index_shape = (self._element.space_dimension(),)
113113
for alpha, fiat_table in fiat_result.items():
114114
if isinstance(fiat_table, Exception):
115-
result[alpha] = gem.Failure(self.index_shape + self.value_shape, fiat_table)
115+
shape = ps.points.shape[:-1] + self.index_shape + self.value_shape
116+
result[alpha] = gem.partial_indexed(gem.Failure(shape, fiat_table), ps.indices)
116117
continue
117118

118119
derivative = sum(alpha)

finat/hdivcurl.py

Lines changed: 50 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ def index_shape(self):
5858
def value_shape(self):
5959
return (self.cell.get_spatial_dimension(),)
6060

61-
def _transform_evaluation(self, core_eval):
61+
def _transform_evaluation(self, core_eval, entity=None):
6262
beta = self.get_indices()
6363
zeta = self.get_value_indices()
6464

@@ -72,11 +72,11 @@ def promote(table):
7272

7373
def basis_evaluation(self, order, ps, entity=None, coordinate_mapping=None):
7474
core_eval = self.wrappee.basis_evaluation(order, ps, entity)
75-
return self._transform_evaluation(core_eval)
75+
return self._transform_evaluation(core_eval, entity)
7676

7777
def point_evaluation(self, order, refcoords, entity=None, coordinate_mapping=None):
7878
core_eval = self.wrappee.point_evaluation(order, refcoords, entity)
79-
return self._transform_evaluation(core_eval)
79+
return self._transform_evaluation(core_eval, entity)
8080

8181
@property
8282
def dual_basis(self):
@@ -92,6 +92,53 @@ def dual_basis(self):
9292
return gem.ComponentTensor(Q[zeta], beta + zeta), x
9393

9494

95+
class HDivTraceElement(WrapperElementBase):
96+
"""HDiv Trace wrapper element for tensor product elements."""
97+
98+
def __init__(self, wrappee):
99+
assert isinstance(wrappee, TensorProductElement)
100+
if any(fe.formdegree is None for fe in wrappee.factors):
101+
raise ValueError("Form degree of subelement is None, cannot HDiv Trace!")
102+
103+
formdegree = sum(fe.formdegree for fe in wrappee.factors)
104+
if formdegree != wrappee.cell.get_spatial_dimension() - 1:
105+
raise ValueError("HDiv Trace requires (n-1)-form element!")
106+
107+
self.support_dim = tuple(fe.formdegree for fe in wrappee.factors)
108+
109+
super().__init__(wrappee, None)
110+
111+
def _transform_evaluation(self, core_eval, entity):
112+
if entity is None:
113+
entity = (self.cell.get_dimension(), 0)
114+
entity_dim, entity_id = entity
115+
if entity_dim == self.support_dim:
116+
return core_eval
117+
118+
# Create a zero tabulation with the same tensor-product structure
119+
return {alpha: table.zero() for alpha, table in core_eval.items()}
120+
121+
@property
122+
def formdegree(self):
123+
return self.wrappee.formdegree
124+
125+
@property
126+
def value_shape(self):
127+
return self.wrappee.value_shape
128+
129+
@cached_property
130+
def fiat_equivalent(self):
131+
return self.wrappee.fiat_equivalent
132+
133+
@property
134+
def mapping(self):
135+
return self.wrappee.mapping
136+
137+
@property
138+
def dual_basis(self):
139+
return self.wrappee.dual_basis
140+
141+
95142
class HDivElement(WrapperElementBase):
96143
"""H(div) wrapper element for tensor product elements."""
97144

finat/ufl/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
from finat.ufl.enrichedelement import EnrichedElement, NodalEnrichedElement # noqa: F401
1616
from finat.ufl.finiteelement import FiniteElement # noqa: F401
1717
from finat.ufl.finiteelementbase import FiniteElementBase # noqa: F401
18-
from finat.ufl.hdivcurl import HCurlElement, HDivElement, WithMapping, HDiv, HCurl # noqa: F401
18+
from finat.ufl.hdivcurl import HCurlElement, HDivElement, HDivTraceElement, WithMapping, HDiv, HCurl # noqa: F401
1919
from finat.ufl.mixedelement import MixedElement, TensorElement, VectorElement # noqa: F401
2020
from finat.ufl.restrictedelement import RestrictedElement # noqa: F401
2121
from finat.ufl.tensorproductelement import TensorProductElement # noqa: F401

finat/ufl/finiteelement.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ def __new__(cls,
3939
from finat.ufl.enrichedelement import EnrichedElement
4040
from finat.ufl.hdivcurl import HCurlElement as HCurl
4141
from finat.ufl.hdivcurl import HDivElement as HDiv
42+
from finat.ufl.hdivcurl import HDivTraceElement as HDivTrace
4243
from finat.ufl.tensorproductelement import TensorProductElement
4344

4445
family, short_name, degree, reference_value_shape, sobolev_space, mapping, embedded_degree = \
@@ -106,8 +107,8 @@ def __new__(cls,
106107
dg_h = FiniteElement("DG", cell_h, degree, variant=variant)
107108
dg_v = FiniteElement("DG", cell_v, degree, variant=variant)
108109

109-
return EnrichedElement(TensorProductElement(tr_h, dg_v, cell=cell),
110-
TensorProductElement(dg_h, tr_v, cell=cell))
110+
return EnrichedElement(HDivTrace(TensorProductElement(tr_h, dg_v, cell=cell)),
111+
HDivTrace(TensorProductElement(dg_h, tr_v, cell=cell)))
111112

112113
elif family == "Q":
113114
return TensorProductElement(*[FiniteElement("CG", c, degree, variant=variant)

finat/ufl/hdivcurl.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,62 @@ def __call__(self, element):
3535
HDiv = CallableSobolevSpace(HDivSobolevSpace.name, HDivSobolevSpace.parents)
3636

3737

38+
class HDivTraceElement(FiniteElementBase):
39+
"""A HDiv Trace version of an outer product element, assuming this makes mathematical sense."""
40+
__slots__ = ("_element", )
41+
42+
def __init__(self, element):
43+
"""Doc."""
44+
self._element = element
45+
46+
family = "TensorProductElement"
47+
cell = element.cell
48+
degree = element.degree()
49+
quad_scheme = element.quadrature_scheme()
50+
reference_value_shape = ()
51+
52+
super().__init__(family, cell, degree, quad_scheme, reference_value_shape)
53+
54+
def __repr__(self):
55+
"""Doc."""
56+
return f"HDivTraceElement({repr(self._element)})"
57+
58+
def mapping(self):
59+
"""Doc."""
60+
return "affine"
61+
62+
@property
63+
def sobolev_space(self):
64+
"""Return the underlying Sobolev space."""
65+
return L2
66+
67+
def reconstruct(self, **kwargs):
68+
"""Doc."""
69+
return HDivTraceElement(self._element.reconstruct(**kwargs))
70+
71+
def variant(self):
72+
"""Doc."""
73+
return self._element.variant()
74+
75+
def __str__(self):
76+
"""Doc."""
77+
return f"HDivTraceElement({repr(self._element)})"
78+
79+
def shortstr(self):
80+
"""Format as string for pretty printing."""
81+
return f"HDivTraceElement({self._element.shortstr()})"
82+
83+
@property
84+
def embedded_subdegree(self):
85+
"""Return embedded subdegree."""
86+
return self._element.embedded_subdegree
87+
88+
@property
89+
def embedded_superdegree(self):
90+
"""Return embedded superdegree."""
91+
return self._element.embedded_superdegree
92+
93+
3894
class HDivElement(FiniteElementBase):
3995
"""A div-conforming version of an outer product element, assuming this makes mathematical sense."""
4096
__slots__ = ("_element", )

gem/gem.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,9 @@ def inherit_dtype_from_children(children):
166166
else:
167167
return numpy.result_type(*(c.dtype for c in children))
168168

169+
def zero(self):
170+
return type(self)(*(c.zero() for c in self.children))
171+
169172

170173
class Terminal(Node):
171174
"""Abstract class for terminal GEM nodes."""
@@ -197,6 +200,9 @@ def dtype(self):
197200
else:
198201
raise AttributeError(f"Must set _dtype on terminal node, {type(self)}")
199202

203+
def zero(self):
204+
return Literal(numpy.zeros(self.shape, dtype=self.dtype))
205+
200206

201207
class Scalar(Node):
202208
"""Abstract class for scalar-valued GEM nodes."""
@@ -717,6 +723,9 @@ def index_ordering(self):
717723
free_indices.extend(i.expression.free_indices)
718724
return tuple(free_indices)
719725

726+
def zero(self):
727+
return type(self)(*(c.zero() for c in self.children), self.multiindex)
728+
720729

721730
class FlexiblyIndexed(Scalar):
722731
"""Flexible indexing of :py:class:`Variable`s to implement views and
@@ -847,6 +856,9 @@ def __new__(cls, expression, multiindex):
847856

848857
return self
849858

859+
def zero(self):
860+
return type(self)(*(c.zero() for c in self.children), self.multiindex)
861+
850862

851863
class IndexSum(Scalar):
852864
__slots__ = ('children', 'multiindex')

0 commit comments

Comments
 (0)