Skip to content

Commit f6d1b38

Browse files
authored
Merge pull request #25 from mscroggs/cross_normal_nedelec
Correct higher order Nedelec
2 parents 5bf2a11 + 355a24e commit f6d1b38

25 files changed

+509
-245
lines changed

.github/workflows/run-tests.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ jobs:
2121
sudo apt-get update
2222
sudo apt-get install -y libeigen3-dev
2323
pip install --upgrade pip
24-
pip install scikit-build pytest numpy
24+
pip install scikit-build pytest numpy scipy
2525
pip install pytest-xdist
2626
name: Install dependencies
2727
- uses: actions/checkout@v2

symfem/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,8 @@
3131
and issubclass(_element, _FiniteElement)
3232
and _element != _FiniteElement
3333
):
34-
_elementlist.append(_element)
34+
if _element not in _elementlist:
35+
_elementlist.append(_element)
3536
for _n in _element.names:
3637
if _n in _elementmap:
3738
assert _element == _elementmap[_n]

symfem/core/finite_element.py

Lines changed: 25 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
"""Abstract finite element classes and functions."""
22

33
import sympy
4-
from .symbolic import x, zero, subs
4+
from .symbolic import x, zero, subs, sym_sum
5+
from . import mappings
56

67

78
class FiniteElement:
@@ -20,6 +21,10 @@ def __init__(self, reference, basis, dofs, domain_dim, range_dim,
2021
self._basis_functions = None
2122
self._reshaped_basis_functions = None
2223

24+
def entity_dofs(self, entity_dim, entity_number):
25+
"""Get the numbers of the DOFs associated with the given entity."""
26+
return [i for i, j in enumerate(self.dofs) if j.entity == (entity_dim, entity_number)]
27+
2328
def get_polynomial_basis(self, reshape=True):
2429
"""Get the polynomial basis for the element."""
2530
if reshape and self.range_shape is not None:
@@ -32,24 +37,26 @@ def get_polynomial_basis(self, reshape=True):
3237

3338
return self.basis
3439

40+
def get_dual_matrix(self):
41+
"""Get the dual matrix."""
42+
mat = []
43+
for b in self.basis:
44+
row = []
45+
for d in self.dofs:
46+
row.append(d.eval(b))
47+
mat.append(row)
48+
return sympy.Matrix(mat)
49+
3550
def get_basis_functions(self, reshape=True):
3651
"""Get the basis functions of the element."""
3752
if self._basis_functions is None:
38-
mat = []
39-
for b in self.basis:
40-
row = []
41-
for d in self.dofs:
42-
row.append(d.eval(b))
43-
mat.append(row)
44-
minv = sympy.Matrix(mat).inv("LU")
53+
minv = self.get_dual_matrix().inv("LU")
4554
self._basis_functions = []
4655
if self.range_dim == 1:
4756
# Scalar space
4857
for i, dof in enumerate(self.dofs):
49-
b = zero
50-
for c, d in zip(minv.row(i), self.basis):
51-
b += c * d
52-
self._basis_functions.append(b)
58+
self._basis_functions.append(
59+
sym_sum(c * d for c, d in zip(minv.row(i), self.basis)))
5360
else:
5461
# Vector space
5562
for i, dof in enumerate(self.dofs):
@@ -108,80 +115,15 @@ def tabulate_basis(self, points, order="xyzxyz"):
108115
return output
109116
raise ValueError(f"Unknown order: {order}")
110117

118+
def map_to_cell(self, f, vertices):
119+
"""Map a function onto a cell using the appropriate mapping for the element."""
120+
map = self.reference.get_map_to(vertices)
121+
inverse_map = self.reference.get_inverse_map_to(vertices)
122+
return getattr(mappings, self.mapping)(f, map, inverse_map, self.reference.tdim)
123+
111124
@property
112125
def name(self):
113126
"""Get the name of the element."""
114127
return self.names[0]
115128

116129
names = []
117-
118-
119-
def make_integral_moment_dofs(
120-
reference,
121-
vertices=None, edges=None, faces=None, volumes=None,
122-
cells=None, facets=None, ridges=None, peaks=None
123-
):
124-
"""Generate DOFs due to integral moments on sub entities.
125-
126-
Parameters
127-
----------
128-
reference: symfem.references.Reference
129-
The reference cell.
130-
vertices: tuple
131-
DOFs on dimension 0 entities.
132-
edges: tuple
133-
DOFs on dimension 1 entities.
134-
faces: tuple
135-
DOFs on dimension 2 entities.
136-
volumes: tuple
137-
DOFs on dimension 3 entities.
138-
cells: tuple
139-
DOFs on codimension 0 entities.
140-
facets: tuple
141-
DOFs on codimension 1 entities.
142-
ridges: tuple
143-
DOFs on codimension 2 entities.
144-
peaks: tuple
145-
DOFs on codimension 3 entities.
146-
"""
147-
from symfem import create_reference
148-
dofs = []
149-
150-
# DOFs per dimension
151-
for dim, moment_data in enumerate([vertices, edges, faces, volumes]):
152-
if moment_data is not None:
153-
IntegralMoment, SubElement, order = moment_data
154-
if order >= SubElement.min_order:
155-
sub_type = reference.sub_entity_types[dim]
156-
if sub_type is not None:
157-
assert dim > 0
158-
for i, vs in enumerate(reference.sub_entities(dim)):
159-
sub_ref = create_reference(
160-
sub_type,
161-
vertices=[reference.reference_vertices[v] for v in vs])
162-
sub_element = SubElement(sub_ref, order)
163-
for f, d in zip(
164-
sub_element.get_basis_functions(), sub_element.dofs
165-
):
166-
dofs.append(IntegralMoment(sub_ref, f, d))
167-
168-
# DOFs per codimension
169-
for _dim, moment_data in enumerate([peaks, ridges, facets, cells]):
170-
dim = reference.tdim - 3 + _dim
171-
if moment_data is not None:
172-
IntegralMoment, SubElement, order = moment_data
173-
if order >= SubElement.min_order:
174-
sub_type = reference.sub_entity_types[dim]
175-
if sub_type is not None:
176-
assert dim > 0
177-
for i, vs in enumerate(reference.sub_entities(dim)):
178-
sub_ref = create_reference(
179-
sub_type,
180-
vertices=[reference.reference_vertices[v] for v in vs])
181-
sub_element = SubElement(sub_ref, order)
182-
for f, d in zip(
183-
sub_element.get_basis_functions(), sub_element.dofs
184-
):
185-
dofs.append(IntegralMoment(sub_ref, f, d))
186-
187-
return dofs

symfem/core/functionals.py

Lines changed: 27 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@
77
class BaseFunctional:
88
"""A functional."""
99

10+
def __init__(self, entity=(None, None)):
11+
self.entity = entity
12+
1013
def eval(self, fun):
1114
"""Apply to the functional to a function."""
1215
raise NotImplementedError
@@ -21,17 +24,17 @@ def dof_direction(self):
2124

2225
def entity_dim(self):
2326
"""Get the dimension of the entitiy this DOF is associated with."""
24-
return None
27+
return self.entity[0]
2528

2629
name = None
2730

2831

2932
class PointEvaluation(BaseFunctional):
3033
"""A point evaluation."""
3134

32-
def __init__(self, point, entity_dim=None):
35+
def __init__(self, point, entity=(None, None)):
36+
super().__init__(entity)
3337
self.point = point
34-
self._entity_dim = entity_dim
3538

3639
def eval(self, function):
3740
"""Apply to the functional to a function."""
@@ -41,20 +44,16 @@ def dof_point(self):
4144
"""Get the location of the DOF in the cell."""
4245
return self.point
4346

44-
def entity_dim(self):
45-
"""Get the dimension of the entitiy this DOF is associated with."""
46-
return self._entity_dim
47-
4847
name = "Point evaluation"
4948

5049

5150
class PointDirectionalDerivativeEvaluation(BaseFunctional):
5251
"""A point evaluation of a derivative in a fixed direction."""
5352

54-
def __init__(self, point, direction, entity_dim=None):
53+
def __init__(self, point, direction, entity=(None, None)):
54+
super().__init__(entity)
5555
self.point = point
5656
self.dir = direction
57-
self._entity_dim = entity_dim
5857

5958
def eval(self, function):
6059
"""Apply to the functional to a function."""
@@ -68,18 +67,14 @@ def dof_direction(self):
6867
"""Get the direction of the DOF."""
6968
return self.dir
7069

71-
def entity_dim(self):
72-
"""Get the dimension of the entitiy this DOF is associated with."""
73-
return self._entity_dim
74-
7570
name = "Point evaluation of directional derivative"
7671

7772

7873
class PointNormalDerivativeEvaluation(PointDirectionalDerivativeEvaluation):
7974
"""A point evaluation of a normal derivative."""
8075

81-
def __init__(self, point, edge):
82-
super().__init__(point, edge.normal(), entity_dim=edge.tdim)
76+
def __init__(self, point, edge, entity=(None, None)):
77+
super().__init__(point, edge.normal(), entity=entity)
8378
self.reference = edge
8479

8580
name = "Point evaluation of normal derivative"
@@ -88,10 +83,10 @@ def __init__(self, point, edge):
8883
class PointComponentSecondDerivativeEvaluation(BaseFunctional):
8984
"""A point evaluation of a component of a second derivative."""
9085

91-
def __init__(self, point, component, entity_dim=None):
86+
def __init__(self, point, component, entity=(None, None)):
87+
super().__init__(entity)
9288
self.point = point
9389
self.component = component
94-
self._entity_dim = entity_dim
9590

9691
def eval(self, function):
9792
"""Apply to the functional to a function."""
@@ -101,20 +96,16 @@ def dof_point(self):
10196
"""Get the location of the DOF in the cell."""
10297
return self.point
10398

104-
def entity_dim(self):
105-
"""Get the dimension of the entitiy this DOF is associated with."""
106-
return self._entity_dim
107-
10899
name = "Point evaluation of Jacobian component"
109100

110101

111102
class PointInnerProduct(BaseFunctional):
112103
"""An evaluation of an inner product at a point."""
113104

114-
def __init__(self, point, vec, entity_dim=None):
105+
def __init__(self, point, vec, entity=(None, None)):
106+
super().__init__(entity)
115107
self.point = point
116108
self.vec = vec
117-
self._entity_dim = entity_dim
118109

119110
def eval(self, function):
120111
"""Apply to the functional to a function."""
@@ -132,20 +123,16 @@ def dof_direction(self):
132123
"""Get the location of the DOF in the cell."""
133124
return self.vec
134125

135-
def entity_dim(self):
136-
"""Get the dimension of the entitiy this DOF is associated with."""
137-
return self._entity_dim
138-
139126
name = "Point inner product"
140127

141128

142129
class DotPointEvaluation(BaseFunctional):
143130
"""A point evaluation in a given direction."""
144131

145-
def __init__(self, point, vector, entity_dim=None):
132+
def __init__(self, point, vector, entity=(None, None)):
133+
super().__init__(entity)
146134
self.point = point
147135
self.vector = vector
148-
self._entity_dim = entity_dim
149136

150137
def eval(self, function):
151138
"""Apply to the functional to a function."""
@@ -159,23 +146,22 @@ def dof_direction(self):
159146
"""Get the direction of the DOF."""
160147
return self.vector
161148

162-
def entity_dim(self):
163-
"""Get the dimension of the entitiy this DOF is associated with."""
164-
return self._entity_dim
165-
166149
name = "Dot point evaluation"
167150

168151

169152
class IntegralMoment(BaseFunctional):
170153
"""An integral moment."""
171154

172-
def __init__(self, reference, f, dof):
155+
def __init__(self, reference, f, dof, entity=(None, None)):
156+
super().__init__(entity)
173157
self.reference = reference
174158
self.dof = dof
175159
self.f = subs(f, x, t)
176160
if isinstance(self.f, tuple):
161+
# TODO: is this one of the mappings?
177162
self.f = tuple(
178-
sum(self.reference.scaled_axes()[j][i] * c for j, c in enumerate(self.f))
163+
sum(self.reference.axes[j][i] * c / self.reference.jacobian()
164+
for j, c in enumerate(self.f))
179165
for i, o in enumerate(self.reference.origin)
180166
)
181167

@@ -210,18 +196,14 @@ def dof_direction(self):
210196
for i in range(self.reference.gdim)
211197
)
212198

213-
def entity_dim(self):
214-
"""Get the dimension of the entitiy this DOF is associated with."""
215-
return self.reference.tdim
216-
217199
name = "Integral moment"
218200

219201

220202
class VecIntegralMoment(IntegralMoment):
221203
"""An integral moment applied to a component of a vector."""
222204

223-
def __init__(self, reference, f, dot_with, dof):
224-
super().__init__(reference, f, dof)
205+
def __init__(self, reference, f, dot_with, dof, entity=(None, None)):
206+
super().__init__(reference, f, dof, entity=entity)
225207
self.dot_with = dot_with
226208

227209
def dot(self, function):
@@ -238,16 +220,16 @@ def dof_direction(self):
238220
class TangentIntegralMoment(VecIntegralMoment):
239221
"""An integral moment in the tangential direction."""
240222

241-
def __init__(self, reference, f, dof):
242-
super().__init__(reference, f, reference.tangent(), dof)
223+
def __init__(self, reference, f, dof, entity=(None, None)):
224+
super().__init__(reference, f, reference.tangent(), dof, entity=entity)
243225

244226
name = "Tangential integral moment"
245227

246228

247229
class NormalIntegralMoment(VecIntegralMoment):
248230
"""An integral moment in the normal direction."""
249231

250-
def __init__(self, reference, f, dof):
251-
super().__init__(reference, f, reference.normal(), dof)
232+
def __init__(self, reference, f, dof, entity=(None, None)):
233+
super().__init__(reference, f, reference.normal(), dof, entity=entity)
252234

253235
name = "Normal integral moment"

0 commit comments

Comments
 (0)