Skip to content

Commit c477bd0

Browse files
committed
FunctionalBlock: generate nodes on multiple entities
1 parent 312c447 commit c477bd0

5 files changed

Lines changed: 178 additions & 55 deletions

File tree

FIAT/brezzi_douglas_marini.py

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,6 @@
99
polynomial_set, nedelec)
1010
from FIAT.check_format_variant import check_format_variant
1111
from FIAT.quadrature_schemes import create_quadrature
12-
from FIAT.quadrature import FacetQuadratureRule
13-
import numpy
1412

1513

1614
class BDMDualSet(dual_set.DualSet):
@@ -33,14 +31,10 @@ def __init__(self, ref_el, degree, variant, interpolant_deg):
3331
Q_ref = create_quadrature(facet, interpolant_deg + degree)
3432
Pq = polynomial_set.ONPolynomialSet(facet, degree)
3533
Pq_at_qpts = Pq.tabulate(Q_ref.get_points())[(0,)*(sd - 1)]
34+
ells = functional.FacetNormalIntegralMomentBlock(ref_el, Q_ref, Pq_at_qpts)
3635
for f in top[sd - 1]:
3736
cur = len(nodes)
38-
Q = FacetQuadratureRule(ref_el, sd - 1, f, Q_ref)
39-
Jdet = Q.jacobian_determinant()
40-
n = ref_el.compute_scaled_normal(f) / Jdet
41-
phis = n[None, :, None] * Pq_at_qpts[:, None, :]
42-
nodes.extend(functional.FrobeniusIntegralMoment(ref_el, Q, phi)
43-
for phi in phis)
37+
nodes.extend(ells.nodes(sd-1, f))
4438
entity_ids[sd - 1][f] = list(range(cur, len(nodes)))
4539

4640
elif variant == "point":
@@ -61,13 +55,12 @@ def __init__(self, ref_el, degree, variant, interpolant_deg):
6155
cell = ref_el.construct_subelement(sd)
6256
Q_ref = create_quadrature(cell, interpolant_deg + degree - 1)
6357
Nedel = nedelec.Nedelec(cell, degree - 1, variant)
58+
mapping, = set(Nedel.mapping())
6459
Ned_at_qpts = Nedel.tabulate(0, Q_ref.get_points())[(0,) * sd]
60+
ells = functional.FacetIntegralMomentBlock(ref_el, Q_ref, Ned_at_qpts, mapping=mapping)
6561
for entity in top[sd]:
66-
Q = FacetQuadratureRule(ref_el, sd, entity, Q_ref)
67-
Jinv = numpy.linalg.inv(Q.jacobian())
68-
phis = numpy.tensordot(Jinv.T, Ned_at_qpts, (1, 1)).transpose((1, 0, 2))
6962
cur = len(nodes)
70-
nodes.extend(functional.FrobeniusIntegralMoment(ref_el, Q, phi) for phi in phis)
63+
nodes.extend(ells.nodes(sd, entity))
7164
entity_ids[sd][entity] = list(range(cur, len(nodes)))
7265

7366
super().__init__(nodes, ref_el, entity_ids)

FIAT/functional.py

Lines changed: 157 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
from itertools import chain
1616
import numpy
1717

18-
from FIAT import polynomial_set, jacobi, quadrature_schemes
18+
from FIAT import polynomial_set, jacobi, quadrature, quadrature_schemes
1919

2020

2121
def index_iterator(shp):
@@ -25,7 +25,162 @@ def index_iterator(shp):
2525
return numpy.ndindex(shp)
2626

2727

28-
class Functional(object):
28+
def pullback(mapping, J, detJ, phi):
29+
try:
30+
formdegree = {
31+
"identity": (0,),
32+
"L2 piola": (3,),
33+
"covariant piola": (1,),
34+
"contravariant piola": (2,),
35+
"double covariant piola": (1, 1),
36+
"double contravariant piola": (2, 2),
37+
"covariant contravariant piola": (1, 2),
38+
"contravariant covariant piola": (2, 1), }[mapping]
39+
except KeyError:
40+
raise ValueError(f"Unrecognized mapping {mapping}")
41+
42+
F1 = numpy.linalg.pinv(J).T
43+
F2 = J / detJ
44+
45+
perm = [*range(1, phi.ndim-1), 0, -1]
46+
phi = phi.transpose(perm)
47+
48+
for i, k in enumerate(formdegree):
49+
if k == 0:
50+
continue
51+
elif k == 3:
52+
phi = phi / detJ
53+
else:
54+
F = F1 if k == 1 else F2
55+
phi = numpy.tensordot(F, phi, (1, i))
56+
57+
perm = [-2, *reversed(range(phi.ndim-2)), -1]
58+
phi = phi.transpose(perm)
59+
return phi
60+
61+
62+
class FunctionalBlock:
63+
def __init__(self, ref_el):
64+
self.ref_el = ref_el
65+
66+
def completion(self):
67+
return self
68+
69+
def nodes(self, entity_dim, entity_id):
70+
raise NotImplementedError
71+
72+
73+
class FunctionalBlockView(FunctionalBlock):
74+
def __init__(self, block, subset):
75+
self.subset = subset
76+
self.block = block
77+
super().__init__(block.ref_el)
78+
79+
def completion(self):
80+
return self.block
81+
82+
def nodes(self, entity_dim, entity_id):
83+
nodes = list(self.block.nodes(entity_dim, entity_id))
84+
for i in self.subset:
85+
yield nodes[i]
86+
87+
88+
class PointFunctionalBlock(FunctionalBlock):
89+
def __init__(self, ref_el, order):
90+
self.order = order
91+
super().__init__(ref_el)
92+
93+
def nodes(self, entity_dim, entity_id):
94+
from FIAT.polynomial_set import mis
95+
sd = self.ref_el.get_spatial_dimension()
96+
for pt in self.ref_el.make_points(entity_dim, entity_id, 0):
97+
for alpha in mis(sd, self.order):
98+
yield PointDerivative(self.ref_el, pt, alpha)
99+
100+
101+
class PointGradient(PointFunctionalBlock):
102+
def __init__(self, ref_el, x):
103+
super().__init__(ref_el, 1)
104+
105+
106+
class PointHessian(PointFunctionalBlock):
107+
def __init__(self, ref_el, x):
108+
super().__init__(ref_el, 2)
109+
110+
111+
class PointDirectionalDerivativeBlock(PointFunctionalBlock):
112+
def __init__(self, ref_el, basis):
113+
self.basis = basis
114+
super().__init__(ref_el)
115+
116+
def get_basis(self, entity_dim, entity_id):
117+
return self.basis
118+
119+
def nodes(self, entity_dim, entity_id):
120+
for pt in self.ref_el.make_points(entity_dim, entity_id, 0):
121+
for e in self.get_basis(entity_dim, entity_id):
122+
yield PointDirectionalDerivative(self.ref_el, pt, e)
123+
124+
125+
class PointNormalTangentialDerivativeBlock(PointDirectionalDerivativeBlock):
126+
def __init__(self, ref_el, facet_id):
127+
super().__init__(ref_el, None)
128+
129+
def get_basis(self, entity_dim, entity_id):
130+
sd = self.ref_el.get_spatial_dimension()
131+
basis = numpy.zeros((sd, sd))
132+
basis[0] = self.ref_el.compute_scaled_normal(entity_id)
133+
basis[1:] = self.ref_el.compute_tangents(sd-1, entity_id)
134+
return basis
135+
136+
137+
class PointNormalDerivativeView(FunctionalBlockView):
138+
def __init__(self, ref_el):
139+
block = PointNormalTangentialDerivativeBlock(ref_el)
140+
super().__init__(block, [0])
141+
142+
143+
class FacetIntegralMomentBlock(FunctionalBlock):
144+
def __init__(self, ref_el, Q_ref, Phis, mapping="L2 piola"):
145+
self.Q_ref = Q_ref
146+
self.Phis = Phis
147+
self.mapping = mapping
148+
super().__init__(ref_el)
149+
150+
def get_functions(self, entity_dim, entity_id):
151+
return self.Phis
152+
153+
def nodes(self, entity_dim, entity_id):
154+
Q = quadrature.FacetQuadratureRule(self.ref_el, entity_dim, entity_id, self.Q_ref)
155+
Phis = self.get_functions(entity_dim, entity_id)
156+
phis = pullback(self.mapping, Q.jacobian(), Q.jacobian_determinant(), Phis)
157+
for phi in phis:
158+
yield FrobeniusIntegralMoment(self.ref_el, Q, phi)
159+
160+
161+
class FacetDirectionalIntegralMomentBlock(FacetIntegralMomentBlock):
162+
def __init__(self, ref_el, Q_ref, Phis, direction):
163+
self.direction = direction
164+
super().__init__(ref_el, Q_ref, Phis)
165+
166+
def get_direction(self, entity_dim, entity_id):
167+
return self.direction
168+
169+
def get_functions(self, entity_dim, entity_id):
170+
udir = self.get_direction(entity_dim, entity_id)
171+
Phis = self.Phis
172+
return Phis[(slice(None), *(None for _ in range(udir.ndim)), slice(None))] * udir[(*(None for _ in range(Phis.ndim-1)), Ellipsis, None)]
173+
174+
175+
class FacetNormalIntegralMomentBlock(FacetDirectionalIntegralMomentBlock):
176+
def __init__(self, ref_el, Q_ref, Phis):
177+
super().__init__(ref_el, Q_ref, Phis, "normal")
178+
179+
def get_direction(self, entity_dim, entity_id):
180+
return self.ref_el.compute_scaled_normal(entity_id)
181+
182+
183+
class Functional(FunctionalBlock):
29184
r"""Abstract class representing a linear functional.
30185
All FIAT functionals are discrete in the sense that
31186
they are written as a weighted sum of (derivatives of components of) their

FIAT/nedelec.py

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -125,17 +125,11 @@ def __init__(self, ref_el, degree, variant, interpolant_deg):
125125
Q_ref = create_quadrature(facet, interpolant_deg + phi_deg)
126126
Pqmd = polynomial_set.ONPolynomialSet(facet, phi_deg, (dim,))
127127
Phis = Pqmd.tabulate(Q_ref.get_points())[(0,) * dim]
128-
Phis = numpy.transpose(Phis, (0, 2, 1))
129-
128+
mapping = "contravariant piola"
129+
ells = functional.FacetIntegralMomentBlock(ref_el, Q_ref, Phis, mapping=mapping)
130130
for entity in top[dim]:
131131
cur = len(nodes)
132-
Q = FacetQuadratureRule(ref_el, dim, entity, Q_ref)
133-
Jdet = Q.jacobian_determinant()
134-
R = numpy.array(ref_el.compute_tangents(dim, entity))
135-
phis = numpy.dot(Phis, R / Jdet)
136-
phis = numpy.transpose(phis, (0, 2, 1))
137-
nodes.extend(functional.FrobeniusIntegralMoment(ref_el, Q, phi)
138-
for phi in phis)
132+
nodes.extend(ells.nodes(dim, entity))
139133
entity_ids[dim][entity] = list(range(cur, len(nodes)))
140134

141135
elif variant == "point":

FIAT/nedelec_second_kind.py

Lines changed: 11 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,12 @@
55
#
66
# SPDX-License-Identifier: LGPL-3.0-or-later
77

8-
import numpy
9-
108
from FIAT.finite_element import CiarletElement
119
from FIAT.dual_set import DualSet
1210
from FIAT.polynomial_set import ONPolynomialSet
1311
from FIAT.functional import PointEdgeTangentEvaluation as Tangent
14-
from FIAT.functional import FrobeniusIntegralMoment as IntegralMoment
12+
from FIAT.functional import FacetIntegralMomentBlock
1513
from FIAT.raviart_thomas import RaviartThomas
16-
from FIAT.quadrature import FacetQuadratureRule
1714
from FIAT.quadrature_schemes import create_quadrature
1815
from FIAT.check_format_variant import check_format_variant
1916

@@ -128,42 +125,30 @@ def _generate_facet_dofs(self, dim, cell, degree, offset, variant, interpolant_d
128125
# Construct quadrature scheme for the reference facet
129126
ref_facet = cell.construct_subelement(dim)
130127
Q_ref = create_quadrature(ref_facet, interpolant_deg + rt_degree)
128+
129+
# Construct Raviart-Thomas on the reference facet
131130
if dim == 1:
132-
Phi = ONPolynomialSet(ref_facet, rt_degree, (dim,))
131+
Phi = ONPolynomialSet(ref_facet, rt_degree, shape=(dim,))
132+
mapping = "contravariant piola"
133133
else:
134-
# Construct Raviart-Thomas on the reference facet
135134
RT = RaviartThomas(ref_facet, rt_degree, variant)
136135
Phi = RT.get_nodal_basis()
136+
mapping, = set(RT.mapping())
137137

138138
# Evaluate basis functions at reference quadrature points
139139
Phis = Phi.tabulate(Q_ref.get_points())[(0,) * dim]
140-
# Note: Phis has dimensions:
141-
# num_basis_functions x num_components x num_quad_points
142-
Phis = numpy.transpose(Phis, (0, 2, 1))
143-
# Note: Phis has dimensions:
144-
# num_basis_functions x num_quad_points x num_components
140+
ells = FacetIntegralMomentBlock(cell, Q_ref, Phis, mapping=mapping)
145141

146142
# Iterate over the facets
147-
cur = offset
148-
for facet in range(num_facets):
149-
# Get the quadrature and Jacobian on this facet
150-
Q_facet = FacetQuadratureRule(cell, dim, facet, Q_ref)
151-
J = Q_facet.jacobian()
152-
detJ = Q_facet.jacobian_determinant()
153-
154-
# Map Phis -> phis (reference values to physical values)
155-
piola_map = J / detJ
156-
phis = numpy.dot(Phis, piola_map.T)
157-
phis = numpy.transpose(phis, (0, 2, 1))
158-
143+
for entity in range(num_facets):
144+
cur = offset + len(dofs)
159145
# Construct degrees of freedom as integral moments on this cell,
160146
# using the face quadrature weighted against the values
161147
# of the (physical) Raviart--Thomas'es on the face
162-
dofs.extend(IntegralMoment(cell, Q_facet, phi) for phi in phis)
148+
dofs.extend(ells.nodes(dim, entity))
163149

164150
# Assign identifiers (num RTs per face + previous edge dofs)
165-
ids[facet].extend(range(cur, cur + len(phis)))
166-
cur += len(phis)
151+
ids[entity].extend(range(cur, offset + len(dofs)))
167152

168153
return (dofs, ids)
169154

FIAT/raviart_thomas.py

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -77,14 +77,10 @@ def __init__(self, ref_el, degree, variant, interpolant_deg):
7777
Q_ref = create_quadrature(facet, interpolant_deg + q)
7878
Pq = polynomial_set.ONPolynomialSet(facet, q if sd > 1 else 0)
7979
Pq_at_qpts = Pq.tabulate(Q_ref.get_points())[(0,)*(sd - 1)]
80+
ells = functional.FacetNormalIntegralMomentBlock(ref_el, Q_ref, Pq_at_qpts)
8081
for f in top[sd - 1]:
8182
cur = len(nodes)
82-
Q = FacetQuadratureRule(ref_el, sd-1, f, Q_ref)
83-
Jdet = Q.jacobian_determinant()
84-
n = ref_el.compute_scaled_normal(f) / Jdet
85-
phis = n[None, :, None] * Pq_at_qpts[:, None, :]
86-
nodes.extend(functional.FrobeniusIntegralMoment(ref_el, Q, phi)
87-
for phi in phis)
83+
nodes.extend(ells.nodes(sd-1, f))
8884
entity_ids[sd - 1][f] = list(range(cur, len(nodes)))
8985

9086
# internal nodes. These are \int_T v \cdot p dx where p \in P_{q-1}^d

0 commit comments

Comments
 (0)