Skip to content

Commit 69073c8

Browse files
committed
Hermite on 1D manifolds
1 parent 5b5f2b4 commit 69073c8

6 files changed

Lines changed: 51 additions & 50 deletions

File tree

FIAT/expansions.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -580,7 +580,7 @@ def __init__(self, ref_el, **kwargs):
580580
def _tabulate_on_cell(self, n, pts, order=0, cell=0, direction=None):
581581
"""Returns a dict of tabulations such that
582582
tabulations[alpha][i, j] = D^alpha phi_i(pts[j])."""
583-
if self.variant is not None:
583+
if self.variant is not None or self.ref_el.get_spatial_dimension() != 1:
584584
return super()._tabulate_on_cell(n, pts, order=order, cell=cell, direction=direction)
585585

586586
A, b = self.affine_mappings[cell]

FIAT/hermite.py

Lines changed: 19 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -9,60 +9,41 @@
99

1010

1111
class CubicHermiteDualSet(dual_set.DualSet):
12-
"""The dual basis for Lagrange elements. This class works for
13-
simplices of any dimension. Nodes are point evaluation at
14-
equispaced points."""
12+
"""The dual basis for Hermite elements. This class works for
13+
simplices of any dimension. Nodes are first order jet at
14+
vertices and point evaluation at barycenters of 2D entities."""
1515

1616
def __init__(self, ref_el):
17-
entity_ids = {}
18-
nodes = []
19-
cur = 0
20-
2117
# make nodes by getting points
2218
# need to do this dimension-by-dimension, facet-by-facet
2319
top = ref_el.get_topology()
2420
verts = ref_el.get_vertices()
25-
sd = ref_el.get_spatial_dimension()
26-
27-
# get jet at each vertex
21+
sd = ref_el.get_topological_dimension()
22+
entity_ids = {dim: {entity: [] for entity in top[dim]} for dim in top}
23+
nodes = []
2824

29-
entity_ids[0] = {}
25+
# get first order jet at each vertex
3026
for v in sorted(top[0]):
27+
cur = len(nodes)
3128
nodes.append(functional.PointEvaluation(ref_el, verts[v]))
32-
pd = functional.PointDerivative
33-
for i in range(sd):
34-
alpha = [0] * sd
35-
alpha[i] = 1
36-
37-
nodes.append(pd(ref_el, verts[v], alpha))
29+
if sd == 1:
30+
# in 1D use normal derivative to support manifolds
31+
nodes.append(functional.PointNormalDerivative(ref_el, v, verts[v]))
32+
else:
33+
nodes.extend(functional.PointDerivative(ref_el, verts[v], alpha)
34+
for alpha in polynomial_set.mis(sd, 1))
35+
entity_ids[0][v].extend(range(cur, len(nodes)))
3836

39-
entity_ids[0][v] = list(range(cur, cur + 1 + sd))
40-
cur += sd + 1
41-
42-
# now only have dofs at the barycenter, which is the
43-
# maximal dimension
4437
# no edge dof
45-
46-
entity_ids[1] = {}
47-
for i in top[1]:
48-
entity_ids
49-
entity_ids[1][i] = []
50-
38+
# now only add dofs at the barycenter of the 2D entities
5139
if sd > 1:
5240
# face dof
5341
# point evaluation at barycenter
54-
entity_ids[2] = {}
5542
for f in sorted(top[2]):
43+
cur = len(nodes)
5644
pt = ref_el.make_points(2, f, 3)[0]
57-
n = functional.PointEvaluation(ref_el, pt)
58-
nodes.append(n)
59-
entity_ids[2][f] = list(range(cur, cur + 1))
60-
cur += 1
61-
62-
for dim in range(3, sd + 1):
63-
entity_ids[dim] = {}
64-
for facet in top[dim]:
65-
entity_ids[dim][facet] = []
45+
nodes.append(functional.PointEvaluation(ref_el, pt))
46+
entity_ids[2][f].extend(range(cur, len(nodes)))
6647

6748
super().__init__(nodes, ref_el, entity_ids)
6849

FIAT/reference_element.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1001,6 +1001,11 @@ def __init__(self):
10011001
1: edges}
10021002
super().__init__(LINE, verts, topology)
10031003

1004+
def compute_normal(self, i):
1005+
"UFC consistent normal"
1006+
n = self.compute_tangents(1, 0)[0]
1007+
return n / numpy.linalg.norm(n)
1008+
10041009

10051010
class DefaultTriangle(DefaultSimplex):
10061011
"""This is the reference triangle with vertices (-1.0,-1.0),

finat/hermite.py

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import FIAT
2-
from gem import ListTensor
2+
import numpy
3+
from gem import ListTensor, partial_indexed
34

45
from finat.citations import cite
56
from finat.fiat_elements import ScalarFiatElement
@@ -15,18 +16,23 @@ def basis_transformation(self, coordinate_mapping):
1516
Js = [coordinate_mapping.jacobian_at(vertex)
1617
for vertex in self.cell.get_vertices()]
1718

19+
pns = coordinate_mapping.physical_normals()
1820
h = coordinate_mapping.cell_size()
1921

20-
d = self.cell.get_dimension()
2122
M = identity(self.space_dimension())
2223

23-
cur = 0
24-
for i in range(d+1):
25-
cur += 1 # skip the vertex
24+
entity_ids = self.entity_dofs()
25+
for i in entity_ids[0]:
26+
# skip the PointEvaluation DOF
27+
vids = entity_ids[0][i][1:]
2628
J = Js[i]
27-
for j in range(d):
28-
for k in range(d):
29-
M[cur+j, cur+k] = J[j, k] / h[i]
30-
cur += d
29+
30+
gdim, tdim = J.shape
31+
if gdim != tdim:
32+
assert tdim == 1
33+
J = partial_indexed(pns, (i,)) @ J
34+
35+
Jnp = numpy.reshape([J[i] for i in numpy.ndindex(J.shape)], J.shape)
36+
M[numpy.ix_(vids, vids)] = Jnp * (1 / h[i])
3137

3238
return ListTensor(M)

test/finat/conftest.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -107,18 +107,19 @@ def scaled_simplex(dim, scale):
107107

108108
@pytest.fixture
109109
def ref_el():
110-
K = {dim: FIAT.ufc_simplex(dim) for dim in (2, 3)}
110+
K = {dim: FIAT.ufc_simplex(dim) for dim in (1, 2, 3)}
111111
return K
112112

113113

114114
@pytest.fixture
115115
def phys_el():
116-
K = {dim: FIAT.ufc_simplex(int(dim)) for dim in (2, 2.5, 3)}
116+
K = {dim: FIAT.ufc_simplex(int(dim)) for dim in (1.5, 2, 2.5, 3)}
117117
K[2].vertices = ((0.0, 0.1), (1.17, -0.09), (0.15, 1.84))
118118
K[3].vertices = ((0, 0, 0),
119119
(1., 0.1, -0.37),
120120
(0.01, 0.987, -.23),
121121
(-0.1, -0.2, 1.38))
122+
K[1.5].vertices = K[2].vertices[:-1]
122123
K[2.5].vertices = K[3].vertices[:-1]
123124
return K
124125

test/finat/test_zany_mapping.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,13 @@ def check_zany_mapping(element, ref_to_phys, *args, **kwargs):
119119
assert np.allclose(ref_vals_zany, phys_vals[:num_dofs]), pp.pformat((np.round(error, 8).tolist(), *inds))
120120

121121

122+
@pytest.mark.parametrize("element", [
123+
finat.Hermite,
124+
])
125+
def test_C1_interval(ref_to_phys, element):
126+
check_zany_mapping(element, ref_to_phys[1.5])
127+
128+
122129
@pytest.mark.parametrize("element", [
123130
finat.Morley,
124131
finat.Hermite,
@@ -132,6 +139,7 @@ def test_C1_triangle(ref_to_phys, element):
132139

133140
@pytest.mark.parametrize("element", [
134141
finat.Morley,
142+
finat.Hermite,
135143
finat.Walkington,
136144
])
137145
def test_C1_tetrahedron(ref_to_phys, element):

0 commit comments

Comments
 (0)