Skip to content

Commit 0cb3808

Browse files
authored
Legendre and Lobatto elements (#231)
* Implement Legendre and Lobatto Lagrange elements * isort * flake8 * pydocstyle * mypy * flake * isort * setup.py * fix tests * remove print * fix DPc element * cache date? * isort * fix tests
1 parent 088158f commit 0cb3808

18 files changed

+788
-551
lines changed

CHANGELOG_SINCE_LAST_VERSION.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,4 @@
55
- Corrected C1 and higher order C tests
66
- Allow element creation on non-default references
77
- Corrected Bell element
8+
- Corrected legendre and lobatto variants of Lagrange

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
author_email="[email protected]",
3535
maintainer_email="[email protected]",
3636
url="https://github.com/mscroggs/symfem",
37-
packages=["symfem", "symfem.elements"],
37+
packages=["symfem", "symfem.elements", "symfem.polynomials"],
3838
package_data={"symfem": ["py.typed"]},
3939
include_package_data=True,
4040
data_files=data_files,

symfem/basis_functions.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -353,8 +353,6 @@ def with_floats(self) -> AnyFunction:
353353
def __iter__(self) -> typing.Iterator[AnyFunction]:
354354
"""Iterate through components of vector function."""
355355
f = self.get_function()
356-
if not hasattr(f, "__iter__"):
357-
raise TypeError(f"'{self.__class__.__name__}' object is not iterable")
358356
return f.__iter__()
359357

360358

symfem/elements/dpc.py

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import sympy
77

88
from ..finite_element import CiarletElement
9-
from ..functionals import DotPointEvaluation, ListOfFunctionals, PointEvaluation
9+
from ..functionals import DotPointEvaluation, IntegralAgainst, ListOfFunctionals, PointEvaluation
1010
from ..functions import FunctionInput
1111
from ..polynomials import polynomial_set_1d, polynomial_set_vector
1212
from ..references import NonDefaultReferenceError, Reference
@@ -24,20 +24,28 @@ def __init__(self, reference: Reference, order: int, variant: str = "equispaced"
2424
order: The polynomial order
2525
variant: The variant of the element
2626
"""
27+
dofs: ListOfFunctionals = []
2728
if reference.name == "interval":
28-
points = [d.dof_point() for d in Lagrange(reference, order, variant).dofs]
29-
elif order == 0:
30-
points = [reference.get_point(tuple(
31-
sympy.Rational(1, 2) for _ in range(reference.tdim)))]
29+
for d in Lagrange(reference, order, variant).dofs:
30+
if isinstance(d, PointEvaluation):
31+
dofs.append(PointEvaluation(reference, d.point, entity=(reference.tdim, 0)))
32+
elif isinstance(d, IntegralAgainst):
33+
dofs.append(IntegralAgainst(
34+
reference, d.f * reference.jacobian(), entity=(reference.tdim, 0)))
3235
else:
33-
points = [
34-
reference.get_point(tuple(sympy.Rational(j, order) for j in i[::-1]))
35-
for i in product(range(order + 1), repeat=reference.tdim)
36-
if sum(i) <= order
37-
]
36+
if order == 0:
37+
points = [reference.get_point(tuple(
38+
sympy.Rational(1, 2) for _ in range(reference.tdim)))]
39+
else:
40+
points = [
41+
reference.get_point(tuple(sympy.Rational(j, order) for j in i[::-1]))
42+
for i in product(range(order + 1), repeat=reference.tdim)
43+
if sum(i) <= order
44+
]
45+
46+
dofs = [
47+
PointEvaluation(reference, d, entity=(reference.tdim, 0)) for d in points]
3848

39-
dofs: ListOfFunctionals = [
40-
PointEvaluation(reference, d, entity=(reference.tdim, 0)) for d in points]
4149
poly: typing.List[FunctionInput] = []
4250
poly += polynomial_set_1d(reference.tdim, order)
4351
poly = reference.map_polyset_from_default(poly)
@@ -59,7 +67,7 @@ def init_kwargs(self) -> typing.Dict[str, typing.Any]:
5967
references = ["interval", "quadrilateral", "hexahedron"]
6068
min_order = 0
6169
continuity = "L2"
62-
last_updated = "2023.06"
70+
last_updated = "2023.07.1"
6371

6472

6573
class VectorDPC(CiarletElement):
@@ -107,4 +115,4 @@ def init_kwargs(self) -> typing.Dict[str, typing.Any]:
107115
references = ["quadrilateral", "hexahedron"]
108116
min_order = 0
109117
continuity = "L2"
110-
last_updated = "2023.05"
118+
last_updated = "2023.07"

symfem/elements/lagrange.py

Lines changed: 36 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,10 @@
66
import sympy
77

88
from ..finite_element import CiarletElement
9-
from ..functionals import DotPointEvaluation, ListOfFunctionals, PointEvaluation
9+
from ..functionals import DotPointEvaluation, IntegralAgainst, ListOfFunctionals, PointEvaluation
1010
from ..functions import FunctionInput
11-
from ..polynomials import polynomial_set_1d, polynomial_set_vector
11+
from ..polynomials import (lobatto_dual_basis, orthonormal_basis, polynomial_set_1d,
12+
polynomial_set_vector)
1213
from ..quadrature import get_quadrature
1314
from ..references import Reference
1415

@@ -25,21 +26,34 @@ def __init__(self, reference: Reference, order: int, variant: str = "equispaced"
2526
variant: The variant of the element
2627
"""
2728
dofs: ListOfFunctionals = []
28-
if order == 0:
29-
dofs = [
29+
if variant == "legendre":
30+
basis = orthonormal_basis(reference.name, order, 0)[0]
31+
for f in basis:
32+
dofs.append(IntegralAgainst(reference, f, (reference.tdim, 0)))
33+
elif order == 0:
34+
dofs.append(
3035
PointEvaluation(
3136
reference, reference.get_point(tuple(
3237
sympy.Rational(1, reference.tdim + 1)
3338
for i in range(reference.tdim)
3439
)),
3540
entity=(reference.tdim, 0)
3641
)
37-
]
42+
)
43+
elif variant == "lobatto":
44+
for v_n, v in enumerate(reference.vertices):
45+
dofs.append(PointEvaluation(reference, v, entity=(0, v_n)))
46+
for edim in range(1, 4):
47+
for e_n in range(reference.sub_entity_count(edim)):
48+
entity = reference.sub_entity(edim, e_n)
49+
basis = lobatto_dual_basis(entity.name, order, False)
50+
for f in basis:
51+
dofs.append(IntegralAgainst(reference, f, (edim, e_n)))
3852
else:
3953
points, _ = get_quadrature(variant, order + 1)
4054

41-
for v_n, v in enumerate(reference.reference_vertices):
42-
dofs.append(PointEvaluation(reference, reference.get_point(v), entity=(0, v_n)))
55+
for v_n, v in enumerate(reference.vertices):
56+
dofs.append(PointEvaluation(reference, v, entity=(0, v_n)))
4357
for edim in range(1, 4):
4458
for e_n in range(reference.sub_entity_count(edim)):
4559
entity = reference.sub_entity(edim, e_n)
@@ -66,7 +80,7 @@ def init_kwargs(self) -> typing.Dict[str, typing.Any]:
6680
references = ["interval", "triangle", "tetrahedron"]
6781
min_order = 0
6882
continuity = "C0"
69-
last_updated = "2023.06"
83+
last_updated = "2023.07"
7084

7185

7286
class VectorLagrange(CiarletElement):
@@ -85,7 +99,11 @@ def __init__(self, reference: Reference, order: int, variant: str = "equispaced"
8599
poly: typing.List[FunctionInput] = []
86100
if reference.tdim == 1:
87101
for p in scalar_space.dofs:
88-
dofs.append(PointEvaluation(reference, p.dof_point(), entity=p.entity))
102+
if isinstance(p, PointEvaluation):
103+
dofs.append(PointEvaluation(reference, p.dof_point(), entity=p.entity))
104+
elif isinstance(p, IntegralAgainst):
105+
dofs.append(IntegralAgainst(
106+
reference, p.f * reference.jacobian(), entity=p.entity))
89107

90108
poly += polynomial_set_1d(reference.tdim, order)
91109
else:
@@ -95,9 +113,15 @@ def __init__(self, reference: Reference, order: int, variant: str = "equispaced"
95113
]
96114
for p in scalar_space.dofs:
97115
for d in directions:
98-
dofs.append(DotPointEvaluation(reference, p.dof_point(), d, entity=p.entity))
116+
if isinstance(p, PointEvaluation):
117+
dofs.append(DotPointEvaluation(
118+
reference, p.dof_point(), d, entity=p.entity))
119+
elif isinstance(p, IntegralAgainst):
120+
dofs.append(IntegralAgainst(
121+
reference, tuple(p.f * i for i in d), entity=p.entity))
99122

100123
poly += polynomial_set_vector(reference.tdim, reference.tdim, order)
124+
101125
super().__init__(reference, order, poly, dofs, reference.tdim, reference.tdim)
102126
self.variant = variant
103127

@@ -113,7 +137,7 @@ def init_kwargs(self) -> typing.Dict[str, typing.Any]:
113137
references = ["interval", "triangle", "tetrahedron"]
114138
min_order = 0
115139
continuity = "C0"
116-
last_updated = "2023.05"
140+
last_updated = "2023.07.1"
117141

118142

119143
class MatrixLagrange(CiarletElement):
@@ -159,7 +183,7 @@ def init_kwargs(self) -> typing.Dict[str, typing.Any]:
159183
references = ["triangle", "tetrahedron"]
160184
min_order = 0
161185
continuity = "L2"
162-
last_updated = "2023.05"
186+
last_updated = "2023.07"
163187

164188

165189
class SymmetricMatrixLagrange(CiarletElement):

symfem/elements/lagrange_prism.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@
66
import sympy
77

88
from ..finite_element import CiarletElement
9-
from ..functionals import DotPointEvaluation, ListOfFunctionals, PointEvaluation
9+
from ..functionals import DotPointEvaluation, IntegralAgainst, ListOfFunctionals, PointEvaluation
1010
from ..functions import FunctionInput
11-
from ..polynomials import prism_polynomial_set_1d, prism_polynomial_set_vector
11+
from ..polynomials import orthonormal_basis, prism_polynomial_set_1d, prism_polynomial_set_vector
1212
from ..quadrature import get_quadrature
1313
from ..references import NonDefaultReferenceError, Reference
1414

@@ -28,7 +28,11 @@ def __init__(self, reference: Reference, order: int, variant: str = "equispaced"
2828
raise NonDefaultReferenceError()
2929

3030
dofs: ListOfFunctionals = []
31-
if order == 0:
31+
if variant == "legendre":
32+
basis = orthonormal_basis(reference.name, order, 0)[0]
33+
for f in basis:
34+
dofs.append(IntegralAgainst(reference, f, (reference.tdim, 0)))
35+
elif order == 0:
3236
dofs = [
3337
PointEvaluation(
3438
reference, tuple(
@@ -38,6 +42,8 @@ def __init__(self, reference: Reference, order: int, variant: str = "equispaced"
3842
entity=(reference.tdim, 0)
3943
)
4044
]
45+
elif variant == "lobatto":
46+
raise NotImplementedError()
4147
else:
4248
points, _ = get_quadrature(variant, order + 1)
4349

@@ -91,7 +97,7 @@ def init_kwargs(self) -> typing.Dict[str, typing.Any]:
9197
references = ["prism"]
9298
min_order = 0
9399
continuity = "C0"
94-
last_updated = "2023.05"
100+
last_updated = "2023.07"
95101

96102

97103
class VectorLagrange(CiarletElement):

symfem/elements/lagrange_pyramid.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@
1010
import sympy
1111

1212
from ..finite_element import CiarletElement
13-
from ..functionals import ListOfFunctionals, PointEvaluation
13+
from ..functionals import IntegralAgainst, ListOfFunctionals, PointEvaluation
1414
from ..functions import FunctionInput
15-
from ..polynomials import pyramid_polynomial_set_1d
15+
from ..polynomials import orthonormal_basis, pyramid_polynomial_set_1d
1616
from ..quadrature import get_quadrature
1717
from ..references import NonDefaultReferenceError, Reference
1818

@@ -32,7 +32,11 @@ def __init__(self, reference: Reference, order: int, variant: str = "equispaced"
3232
raise NonDefaultReferenceError()
3333

3434
dofs: ListOfFunctionals = []
35-
if order == 0:
35+
if variant == "legendre":
36+
basis = orthonormal_basis(reference.name, order, 0)[0]
37+
for f in basis:
38+
dofs.append(IntegralAgainst(reference, f, (reference.tdim, 0)))
39+
elif order == 0:
3640
dofs = [
3741
PointEvaluation(
3842
reference, tuple(
@@ -42,6 +46,8 @@ def __init__(self, reference: Reference, order: int, variant: str = "equispaced"
4246
entity=(reference.tdim, 0)
4347
)
4448
]
49+
elif variant == "lobatto":
50+
raise NotImplementedError()
4551
else:
4652
points, _ = get_quadrature(variant, order + 1)
4753

@@ -97,4 +103,4 @@ def init_kwargs(self) -> typing.Dict[str, typing.Any]:
97103
references = ["pyramid"]
98104
min_order = 0
99105
continuity = "C0"
100-
last_updated = "2023.05"
106+
last_updated = "2023.07"

symfem/elements/q.py

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,12 @@
66
import sympy
77

88
from ..finite_element import CiarletElement, FiniteElement
9-
from ..functionals import (DotPointEvaluation, IntegralMoment, ListOfFunctionals,
9+
from ..functionals import (DotPointEvaluation, IntegralAgainst, IntegralMoment, ListOfFunctionals,
1010
NormalIntegralMoment, PointEvaluation, TangentIntegralMoment)
1111
from ..functions import FunctionInput
1212
from ..moments import make_integral_moment_dofs
13-
from ..polynomials import (Hcurl_quolynomials, Hdiv_quolynomials, quolynomial_set_1d,
14-
quolynomial_set_vector)
13+
from ..polynomials import (Hcurl_quolynomials, Hdiv_quolynomials, lobatto_dual_basis,
14+
orthonormal_basis, quolynomial_set_1d, quolynomial_set_vector)
1515
from ..quadrature import get_quadrature
1616
from ..references import NonDefaultReferenceError, Reference
1717

@@ -28,11 +28,26 @@ def __init__(self, reference: Reference, order: int, variant: str = "equispaced"
2828
variant: The variant of the element
2929
"""
3030
dofs: ListOfFunctionals = []
31-
if order == 0:
31+
if variant == "legendre":
32+
basis = orthonormal_basis(reference.name, order, 0)[0]
33+
for f in basis:
34+
dofs.append(IntegralAgainst(reference, f, (reference.tdim, 0)))
35+
elif order == 0:
3236
dofs = [PointEvaluation(
3337
reference,
3438
reference.get_point(tuple(sympy.Rational(1, 2) for i in range(reference.tdim))),
3539
entity=(reference.tdim, 0))]
40+
elif variant == "lobatto":
41+
if reference != reference.default_reference():
42+
raise NonDefaultReferenceError()
43+
for v_n, v in enumerate(reference.vertices):
44+
dofs.append(PointEvaluation(reference, v, entity=(0, v_n)))
45+
for edim in range(1, 4):
46+
for e_n in range(reference.sub_entity_count(edim)):
47+
entity = reference.sub_entity(edim, e_n)
48+
basis = lobatto_dual_basis(entity.name, order, False)
49+
for f in basis:
50+
dofs.append(IntegralAgainst(reference, f, (edim, e_n)))
3651
else:
3752
points, _ = get_quadrature(variant, order + 1)
3853

@@ -64,6 +79,8 @@ def get_tensor_factorisation(
6479
Returns:
6580
The tensor factorisation
6681
"""
82+
if self.variant == "lobatto":
83+
return super().get_tensor_factorisation()
6784
from symfem import create_element
6885
interval_q = create_element("interval", "Lagrange", self.order)
6986

@@ -110,7 +127,7 @@ def init_kwargs(self) -> typing.Dict[str, typing.Any]:
110127
references = ["quadrilateral", "hexahedron"]
111128
min_order = 0
112129
continuity = "C0"
113-
last_updated = "2023.06"
130+
last_updated = "2023.07.1"
114131

115132

116133
class VectorQ(CiarletElement):

symfem/elements/serendipity.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ def init_kwargs(self) -> typing.Dict[str, typing.Any]:
103103
references = ["quadrilateral", "hexahedron"]
104104
min_order = 1
105105
continuity = "H(curl)"
106-
last_updated = "2023.06"
106+
last_updated = "2023.07"
107107

108108

109109
class SerendipityDiv(CiarletElement):
@@ -146,4 +146,4 @@ def init_kwargs(self) -> typing.Dict[str, typing.Any]:
146146
references = ["quadrilateral", "hexahedron"]
147147
min_order = 1
148148
continuity = "H(div)"
149-
last_updated = "2023.06.1"
149+
last_updated = "2023.07"

symfem/functions.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -340,6 +340,10 @@ def with_floats(self) -> AnyFunction:
340340
"""
341341
pass
342342

343+
def __iter__(self) -> typing.Iterator[AnyFunction]:
344+
"""Iterate through components of vector function."""
345+
raise TypeError(f"'{self.__class__.__name__}' object is not iterable")
346+
343347
def integrate(self, *limits: typing.Tuple[
344348
sympy.core.symbol.Symbol,
345349
typing.Union[int, sympy.core.expr.Expr],

0 commit comments

Comments
 (0)