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,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
0 commit comments