1515from itertools import chain
1616import numpy
1717
18- from FIAT import polynomial_set , jacobi , quadrature_schemes
18+ from FIAT import polynomial_set , jacobi , quadrature , quadrature_schemes
1919
2020
2121def 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
0 commit comments