9
9
10
10
import numpy
11
11
import math
12
- from FIAT import reference_element
13
- from FIAT import jacobi
12
+ from FIAT import reference_element , jacobi
14
13
15
14
16
15
def morton_index2 (p , q = 0 ):
@@ -24,11 +23,22 @@ def morton_index3(p, q=0, r=0):
24
23
def jrc (a , b , n ):
25
24
"""Jacobi recurrence coefficients"""
26
25
an = (2 * n + 1 + a + b )* (2 * n + 2 + a + b ) / (2 * (n + 1 )* (n + 1 + a + b ))
27
- bn = (a * a - b * b ) * (2 * n + 1 + a + b ) / (2 * (n + 1 )* (2 * n + a + b )* (n + 1 + a + b ))
26
+ bn = (a + b ) * ( a - b ) * (2 * n + 1 + a + b ) / (2 * (n + 1 )* (n + 1 + a + b )* (2 * n + a + b ))
28
27
cn = (n + a )* (n + b )* (2 * n + 2 + a + b ) / ((n + 1 )* (n + 1 + a + b )* (2 * n + a + b ))
29
28
return an , bn , cn
30
29
31
30
31
+ def integrated_jrc (a , b , n ):
32
+ """Integrated Jacobi recurrence coefficients"""
33
+ if n == 1 :
34
+ an = (a + b + 2 ) / 2
35
+ bn = (a - 3 * b - 2 ) / 2
36
+ cn = 0.0
37
+ else :
38
+ an , bn , cn = jrc (a - 1 , b + 1 , n - 1 )
39
+ return an , bn , cn
40
+
41
+
32
42
def pad_coordinates (ref_pts , embedded_dim ):
33
43
"""Pad reference coordinates by appending -1.0."""
34
44
return tuple (ref_pts ) + (- 1.0 , )* (embedded_dim - len (ref_pts ))
@@ -52,10 +62,31 @@ def jacobi_factors(x, y, z, dx, dy, dz):
52
62
return fa , fb , fc , dfa , dfb , dfc
53
63
54
64
55
- def dubiner_recurrence (dim , n , order , ref_pts , jacobian ):
56
- """Dubiner recurrence from (Kirby 2010)"""
65
+ def dubiner_recurrence (dim , n , order , ref_pts , Jinv , scale , variant = None ):
66
+ """Tabulate a Dubiner expansion set using the recurrence from (Kirby 2010).
67
+
68
+ :arg dim: The spatial dimension of the simplex.
69
+ :arg n: The polynomial degree.
70
+ :arg order: The maximum order of differenation.
71
+ :arg ref_pts: An ``ndarray`` with the coordinates on the default (-1, 1)^d simplex.
72
+ :arg Jinv: The inverse of the Jacobian of the coordinate mapping from the default simplex.
73
+ :arg scale: A scale factor that sets the first member of expansion set.
74
+ :arg variant: Choose between the default (None) orthogonal basis,
75
+ 'integral' for integrated Jacobi polynomials,
76
+ or 'dual' for the L2-duals of the integrated Jacobi polynomials.
77
+
78
+ :returns: A tuple with tabulations of the expansion set and its derivatives.
79
+ """
57
80
if order > 2 :
58
81
raise ValueError ("Higher order derivatives not supported" )
82
+ if variant not in [None , "integral" , "dual" ]:
83
+ raise ValueError (f"Invalid variant { variant } " )
84
+
85
+ if variant == "integral" :
86
+ scale = - scale
87
+ if n == 0 :
88
+ # Always return 1 for n=0 to make regression tests pass
89
+ scale = 1.0
59
90
60
91
num_members = math .comb (n + dim , dim )
61
92
results = tuple ([None ] * num_members for i in range (order + 1 ))
@@ -65,8 +96,8 @@ def dubiner_recurrence(dim, n, order, ref_pts, jacobian):
65
96
sym_outer = lambda x , y : outer (x , y ) + outer (y , x )
66
97
67
98
pad_dim = dim + 2
68
- dX = pad_jacobian (jacobian , pad_dim )
69
- phi [0 ] = sum ((ref_pts [i ] - ref_pts [i ] for i in range (dim )), 1. )
99
+ dX = pad_jacobian (Jinv , pad_dim )
100
+ phi [0 ] = sum ((ref_pts [i ] - ref_pts [i ] for i in range (dim )), scale )
70
101
if dphi is not None :
71
102
dphi [0 ] = (phi [0 ] - phi [0 ]) * dX [0 ]
72
103
if ddphi is not None :
@@ -76,9 +107,10 @@ def dubiner_recurrence(dim, n, order, ref_pts, jacobian):
76
107
if dim > 3 or dim < 0 :
77
108
raise ValueError ("Invalid number of spatial dimensions" )
78
109
110
+ beta = 1 if variant == "dual" else 0
111
+ coefficients = integrated_jrc if variant == "integral" else jrc
79
112
X = pad_coordinates (ref_pts , pad_dim )
80
113
idx = (lambda p : p , morton_index2 , morton_index3 )[dim - 1 ]
81
-
82
114
for codim in range (dim ):
83
115
# Extend the basis from codim to codim + 1
84
116
fa , fb , fc , dfa , dfb , dfc = jacobi_factors (* X [codim :codim + 3 ], * dX [codim :codim + 3 ])
@@ -87,9 +119,17 @@ def dubiner_recurrence(dim, n, order, ref_pts, jacobian):
87
119
# handle i = 1
88
120
icur = idx (* sub_index , 0 )
89
121
inext = idx (* sub_index , 1 )
90
- alpha = 2 * sum (sub_index ) + len (sub_index )
91
- b = 0.5 * alpha
92
- a = b + 1.0
122
+
123
+ if variant == "integral" :
124
+ alpha = 2 * sum (sub_index )
125
+ a = b = - 0.5
126
+ else :
127
+ alpha = 2 * sum (sub_index ) + len (sub_index )
128
+ if variant == "dual" :
129
+ alpha += 1 + len (sub_index )
130
+ a = 0.5 * (alpha + beta ) + 1.0
131
+ b = 0.5 * (alpha - beta )
132
+
93
133
factor = a * fa - b * fb
94
134
phi [inext ] = factor * phi [icur ]
95
135
if dphi is not None :
@@ -101,7 +141,7 @@ def dubiner_recurrence(dim, n, order, ref_pts, jacobian):
101
141
# general i by recurrence
102
142
for i in range (1 , n - sum (sub_index )):
103
143
iprev , icur , inext = icur , inext , idx (* sub_index , i + 1 )
104
- a , b , c = jrc (alpha , 0 , i )
144
+ a , b , c = coefficients (alpha , beta , i )
105
145
factor = a * fa - b * fb
106
146
phi [inext ] = factor * phi [icur ] - c * (fc * phi [iprev ])
107
147
if dphi is None :
@@ -115,11 +155,54 @@ def dubiner_recurrence(dim, n, order, ref_pts, jacobian):
115
155
c * (fc * ddphi [iprev ] + sym_outer (dphi [iprev ], dfc ) + phi [iprev ] * ddfc ))
116
156
117
157
# normalize
118
- for alpha in reference_element .lattice_iter (0 , n + 1 , codim + 1 ):
119
- icur = idx (* alpha )
120
- scale = math .sqrt (sum (alpha ) + 0.5 * len (alpha ))
158
+ d = codim + 1
159
+ shift = 1 if variant == "dual" else 0
160
+ for index in reference_element .lattice_iter (0 , n + 1 , d ):
161
+ icur = idx (* index )
162
+ if variant is not None :
163
+ p = index [- 1 ] + shift
164
+ alpha = 2 * (sum (index [:- 1 ]) + d * shift ) - 1
165
+ norm2 = 1.0
166
+ if p > 0 and p + alpha > 0 :
167
+ norm2 = (p + alpha ) * (2 * p + alpha ) / p
168
+ norm2 *= (2 * d + 1 ) / (2 * d )
169
+ else :
170
+ norm2 = (2 * sum (index ) + d ) / d
171
+ scale = math .sqrt (norm2 )
121
172
for result in results :
122
173
result [icur ] *= scale
174
+
175
+ # recover facet bubbles
176
+ if variant == "integral" :
177
+ icur = 0
178
+ for result in results :
179
+ result [icur ] *= - 1
180
+ for inext in range (1 , dim + 1 ):
181
+ for result in results :
182
+ result [icur ] -= result [inext ]
183
+
184
+ if dim == 2 :
185
+ for i in range (2 , n + 1 ):
186
+ icur = idx (0 , i )
187
+ iprev = idx (1 , i - 1 )
188
+ for result in results :
189
+ result [icur ] -= result [iprev ]
190
+
191
+ elif dim == 3 :
192
+ for i in range (2 , n + 1 ):
193
+ for j in range (0 , n + 1 - i ):
194
+ icur = idx (0 , i , j )
195
+ iprev = idx (1 , i - 1 , j )
196
+ for result in results :
197
+ result [icur ] -= result [iprev ]
198
+
199
+ icur = idx (0 , 0 , i )
200
+ iprev0 = idx (1 , 0 , i - 1 )
201
+ iprev1 = idx (0 , 1 , i - 1 )
202
+ for result in results :
203
+ result [icur ] -= result [iprev0 ]
204
+ result [icur ] -= result [iprev1 ]
205
+
123
206
return results
124
207
125
208
@@ -141,32 +224,42 @@ def xi_tetrahedron(eta):
141
224
142
225
143
226
class ExpansionSet (object ):
144
- def __new__ (cls , ref_el , * args , ** kwargs ):
227
+ def __new__ (cls , * args , ** kwargs ):
145
228
"""Returns an ExpansionSet instance appopriate for the given
146
229
reference element."""
147
230
if cls is not ExpansionSet :
148
231
return super (ExpansionSet , cls ).__new__ (cls )
149
- if ref_el .get_shape () == reference_element .POINT :
150
- return PointExpansionSet (ref_el )
151
- elif ref_el .get_shape () == reference_element .LINE :
152
- return LineExpansionSet (ref_el )
153
- elif ref_el .get_shape () == reference_element .TRIANGLE :
154
- return TriangleExpansionSet (ref_el )
155
- elif ref_el .get_shape () == reference_element .TETRAHEDRON :
156
- return TetrahedronExpansionSet (ref_el )
157
- else :
232
+ try :
233
+ ref_el = args [0 ]
234
+ expansion_set = {
235
+ reference_element .POINT : PointExpansionSet ,
236
+ reference_element .LINE : LineExpansionSet ,
237
+ reference_element .TRIANGLE : TriangleExpansionSet ,
238
+ reference_element .TETRAHEDRON : TetrahedronExpansionSet ,
239
+ }[ref_el .get_shape ()]
240
+ return expansion_set (* args , ** kwargs )
241
+ except KeyError :
158
242
raise ValueError ("Invalid reference element type." )
159
243
160
- def __init__ (self , ref_el ):
244
+ def __init__ (self , ref_el , scale = None , variant = None ):
161
245
self .ref_el = ref_el
246
+ self .variant = variant
162
247
dim = ref_el .get_spatial_dimension ()
163
248
self .base_ref_el = reference_element .default_simplex (dim )
164
249
v1 = ref_el .get_vertices ()
165
250
v2 = self .base_ref_el .get_vertices ()
166
251
self .A , self .b = reference_element .make_affine_mapping (v1 , v2 )
167
252
self .mapping = lambda x : numpy .dot (self .A , x ) + self .b
168
- self .scale = numpy .sqrt (numpy .linalg .det (self .A ))
169
253
self ._dmats_cache = {}
254
+ if scale is None :
255
+ scale = math .sqrt (1.0 / self .base_ref_el .volume ())
256
+ elif isinstance (scale , str ):
257
+ scale = scale .lower ()
258
+ if scale == "orthonormal" :
259
+ scale = math .sqrt (1.0 / ref_el .volume ())
260
+ elif scale == "l2 piola" :
261
+ scale = 1.0 / ref_el .volume ()
262
+ self .scale = scale
170
263
171
264
def get_num_members (self , n ):
172
265
D = self .ref_el .get_spatial_dimension ()
@@ -184,7 +277,7 @@ def _tabulate(self, n, pts, order=0):
184
277
"""A version of tabulate() that also works for a single point.
185
278
"""
186
279
D = self .ref_el .get_spatial_dimension ()
187
- return dubiner_recurrence (D , n , order , self ._mapping (pts ), self .A )
280
+ return dubiner_recurrence (D , n , order , self ._mapping (pts ), self .A , self . scale , variant = self . variant )
188
281
189
282
def get_dmats (self , degree ):
190
283
"""Returns a numpy array with the expansion coefficients dmat[k, j, i]
@@ -266,10 +359,10 @@ def tabulate_jet(self, n, pts, order=1):
266
359
267
360
class PointExpansionSet (ExpansionSet ):
268
361
"""Evaluates the point basis on a point reference element."""
269
- def __init__ (self , ref_el ):
362
+ def __init__ (self , ref_el , ** kwargs ):
270
363
if ref_el .get_spatial_dimension () != 0 :
271
364
raise ValueError ("Must have a point" )
272
- super (PointExpansionSet , self ).__init__ (ref_el )
365
+ super (PointExpansionSet , self ).__init__ (ref_el , ** kwargs )
273
366
274
367
def tabulate (self , n , pts ):
275
368
"""Returns a numpy array A[i,j] = phi_i(pts[j]) = 1.0."""
@@ -279,17 +372,20 @@ def tabulate(self, n, pts):
279
372
280
373
class LineExpansionSet (ExpansionSet ):
281
374
"""Evaluates the Legendre basis on a line reference element."""
282
- def __init__ (self , ref_el ):
375
+ def __init__ (self , ref_el , ** kwargs ):
283
376
if ref_el .get_spatial_dimension () != 1 :
284
377
raise Exception ("Must have a line" )
285
- super (LineExpansionSet , self ).__init__ (ref_el )
378
+ super (LineExpansionSet , self ).__init__ (ref_el , ** kwargs )
286
379
287
380
def _tabulate (self , n , pts , order = 0 ):
288
381
"""Returns a tuple of (vals, derivs) such that
289
382
vals[i,j] = phi_i(pts[j]), derivs[i,j] = D vals[i,j]."""
383
+ if self .variant is not None :
384
+ return super (LineExpansionSet , self )._tabulate (n , pts , order = order )
385
+
290
386
xs = self ._mapping (pts ).T
291
387
results = []
292
- scale = numpy .sqrt (0.5 + numpy .arange (n + 1 ))
388
+ scale = self . scale * numpy .sqrt (2 * numpy .arange (n + 1 ) + 1 )
293
389
for k in range (order + 1 ):
294
390
v = numpy .zeros ((n + 1 , len (xs )), xs .dtype )
295
391
if n >= k :
@@ -306,18 +402,18 @@ def _tabulate(self, n, pts, order=0):
306
402
class TriangleExpansionSet (ExpansionSet ):
307
403
"""Evaluates the orthonormal Dubiner basis on a triangular
308
404
reference element."""
309
- def __init__ (self , ref_el ):
405
+ def __init__ (self , ref_el , ** kwargs ):
310
406
if ref_el .get_spatial_dimension () != 2 :
311
407
raise Exception ("Must have a triangle" )
312
- super (TriangleExpansionSet , self ).__init__ (ref_el )
408
+ super (TriangleExpansionSet , self ).__init__ (ref_el , ** kwargs )
313
409
314
410
315
411
class TetrahedronExpansionSet (ExpansionSet ):
316
412
"""Collapsed orthonormal polynomial expansion on a tetrahedron."""
317
- def __init__ (self , ref_el ):
413
+ def __init__ (self , ref_el , ** kwargs ):
318
414
if ref_el .get_spatial_dimension () != 3 :
319
415
raise Exception ("Must be a tetrahedron" )
320
- super (TetrahedronExpansionSet , self ).__init__ (ref_el )
416
+ super (TetrahedronExpansionSet , self ).__init__ (ref_el , ** kwargs )
321
417
322
418
323
419
def polynomial_dimension (ref_el , degree ):
0 commit comments