Skip to content

Commit 3d15fee

Browse files
committed
FunctionalBlock
1 parent 312c447 commit 3d15fee

5 files changed

Lines changed: 138 additions & 55 deletions

File tree

FIAT/brezzi_douglas_marini.py

Lines changed: 3 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):
@@ -35,12 +33,7 @@ def __init__(self, ref_el, degree, variant, interpolant_deg):
3533
Pq_at_qpts = Pq.tabulate(Q_ref.get_points())[(0,)*(sd - 1)]
3634
for f in top[sd - 1]:
3735
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)
36+
nodes.extend(functional.FacetNormalIntegralMomentBlock(ref_el, f, Q_ref, Pq_at_qpts))
4437
entity_ids[sd - 1][f] = list(range(cur, len(nodes)))
4538

4639
elif variant == "point":
@@ -61,13 +54,11 @@ def __init__(self, ref_el, degree, variant, interpolant_deg):
6154
cell = ref_el.construct_subelement(sd)
6255
Q_ref = create_quadrature(cell, interpolant_deg + degree - 1)
6356
Nedel = nedelec.Nedelec(cell, degree - 1, variant)
57+
mapping, = set(Nedel.mapping())
6458
Ned_at_qpts = Nedel.tabulate(0, Q_ref.get_points())[(0,) * sd]
6559
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))
6960
cur = len(nodes)
70-
nodes.extend(functional.FrobeniusIntegralMoment(ref_el, Q, phi) for phi in phis)
61+
nodes.extend(functional.FacetIntegralMomentBlock(ref_el, sd, entity, Q_ref, Ned_at_qpts, mapping=mapping))
7162
entity_ids[sd][entity] = list(range(cur, len(nodes)))
7263

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

FIAT/functional.py

Lines changed: 122 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,127 @@ 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, nodes):
64+
self.nodes = nodes
65+
66+
def __iter__(self):
67+
for ell in self.nodes:
68+
yield ell
69+
70+
def __len__(self):
71+
return len(self.nodes)
72+
73+
def completion(self):
74+
return self
75+
76+
77+
class FunctionalBlockView(FunctionalBlock):
78+
def __init__(self, block, index):
79+
self.block = block
80+
nodes = [block.nodes[index]]
81+
super().__init__(nodes)
82+
83+
def completion(self):
84+
return self.block
85+
86+
87+
class PointFunctionalBlock(FunctionalBlock):
88+
def __init__(self, ref_el, x, order):
89+
from FIAT.polynomial_set import mis
90+
sd = ref_el.get_spatial_dimension()
91+
nodes = [PointDerivative(ref_el, x, alpha) for alpha in mis(sd, order)]
92+
super().__init__(nodes)
93+
94+
95+
class PointGradient(PointFunctionalBlock):
96+
def __init__(self, ref_el, x):
97+
super().__init__(ref_el, x, 1)
98+
99+
100+
class PointHessian(PointFunctionalBlock):
101+
def __init__(self, ref_el, x):
102+
super().__init__(ref_el, x, 2)
103+
104+
105+
class PointDirectionalDerivativeBlock(PointFunctionalBlock):
106+
def __init__(self, ref_el, x, basis):
107+
nodes = [PointDirectionalDerivative(ref_el, x, e) for e in basis]
108+
super().__init__(nodes)
109+
110+
111+
class PointNormalTangentialDerivativeBlock(PointDirectionalDerivativeBlock):
112+
def __init__(self, ref_el, x, facet_id):
113+
sd = ref_el.get_spatial_dimension()
114+
basis = numpy.zeros((sd, sd))
115+
basis[0] = ref_el.compute_scaled_normal(facet_id)
116+
basis[1:] = ref_el.compute_tangents(sd-1, facet_id)
117+
super().__init__(self, x, basis)
118+
119+
120+
class PointNormalDerivativeView(FunctionalBlockView):
121+
def __init__(self, ref_el, x, facet_id):
122+
block = PointNormalTangentialDerivativeBlock(ref_el, x, facet_id)
123+
super().__init__(block, 0)
124+
125+
126+
class FacetIntegralMomentBlock(FunctionalBlock):
127+
def __init__(self, ref_el, entity_dim, entity_id, Q_ref, Phis, mapping="L2 piola"):
128+
Q = quadrature.FacetQuadratureRule(ref_el, entity_dim, entity_id, Q_ref)
129+
phis = pullback(mapping, Q.jacobian(), Q.jacobian_determinant(), Phis)
130+
nodes = [FrobeniusIntegralMoment(ref_el, Q, phi) for phi in phis]
131+
super().__init__(nodes)
132+
133+
134+
class FacetDirectionalIntegralMomentBlock(FacetIntegralMomentBlock):
135+
def __init__(self, ref_el, entity_dim, entity_id, Q_ref, Phis, direction):
136+
direction = numpy.asarray(direction)
137+
Phis = Phis[(slice(None), *(None for _ in range(direction.ndim)), slice(None))] * direction[(*(None for _ in range(Phis.ndim-1)), Ellipsis, None)]
138+
super().__init__(ref_el, entity_dim, entity_id, Q_ref, Phis)
139+
140+
141+
class FacetNormalIntegralMomentBlock(FacetDirectionalIntegralMomentBlock):
142+
def __init__(self, ref_el, facet_id, Q_ref, Phis):
143+
direction = ref_el.compute_scaled_normal(facet_id)
144+
sd = ref_el.get_spatial_dimension()
145+
super().__init__(ref_el, sd-1, facet_id, Q_ref, Phis, direction)
146+
147+
148+
class Functional(FunctionalBlock):
29149
r"""Abstract class representing a linear functional.
30150
All FIAT functionals are discrete in the sense that
31151
they are written as a weighted sum of (derivatives of components of) their

FIAT/nedelec.py

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -125,17 +125,10 @@ 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"
130129
for entity in top[dim]:
131130
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)
131+
nodes.extend(functional.FacetIntegralMomentBlock(ref_el, dim, entity, Q_ref, Phis, mapping=mapping))
139132
entity_ids[dim][entity] = list(range(cur, len(nodes)))
140133

141134
elif variant == "point":

FIAT/nedelec_second_kind.py

Lines changed: 10 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,29 @@ 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
145140

146141
# 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-
142+
for entity in range(num_facets):
143+
cur = offset + len(dofs)
159144
# Construct degrees of freedom as integral moments on this cell,
160145
# using the face quadrature weighted against the values
161146
# of the (physical) Raviart--Thomas'es on the face
162-
dofs.extend(IntegralMoment(cell, Q_facet, phi) for phi in phis)
147+
dofs.extend(FacetIntegralMomentBlock(cell, dim, entity, Q_ref, Phis, mapping=mapping))
163148

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

168152
return (dofs, ids)
169153

FIAT/raviart_thomas.py

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -79,12 +79,7 @@ def __init__(self, ref_el, degree, variant, interpolant_deg):
7979
Pq_at_qpts = Pq.tabulate(Q_ref.get_points())[(0,)*(sd - 1)]
8080
for f in top[sd - 1]:
8181
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)
82+
nodes.extend(functional.FacetNormalIntegralMomentBlock(ref_el, f, Q_ref, Pq_at_qpts))
8883
entity_ids[sd - 1][f] = list(range(cur, len(nodes)))
8984

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

0 commit comments

Comments
 (0)