1
# Written by Robert C. Kirby
2
# Copyright 2005 by The University of Chicago
3
# Distributed under the LGPL license
4
# This work is partially supported by the US Department of Energy
5
# under award number DE-FG02-04ER25650
7
# Last modified 4 Feb 2005 by RCK
9
import jacobi, shapes, math, Numeric
11
"""Principal expansion functions as defined by Karniadakis & Sherwin.
12
The main point of entry to this module is the function
13
make_expansions( shape , n ) which takes the simplex shape and the
14
polynomal degree n and returns the list of expansion functions up to
18
def psitilde_a( i , z ):
19
return jacobi.eval_jacobi( 0 , 0 , i , z )
21
def psitilde_b( i , j , z ):
23
return jacobi.eval_jacobi( 1 , 0 , j , z )
25
return (0.5 * ( 1 - z )) ** i * jacobi.eval_jacobi( 2*i+1 , 0 , j , z )
27
def psitilde_c( i , j , k , z ):
29
return jacobi.eval_jacobi( 2 , 0 , k , z )
31
return ( 0.5 * (1-z))**(i+j) \
32
* jacobi.eval_jacobi( 2*(i+j+1), 0 , k , z )
34
# coordinate changes from Karniadakis & Sherwin
36
def make_scalings( n , etas ):
37
scalings = Numeric.zeros( (n+1,len(etas)) ,"d")
40
scalings[1,:] = 0.5 * (1.0 - etas)
41
for k in range(2,n+1):
42
scalings[k,:] = scalings[k-1,:] * scalings[1,:]
46
def eta_triangle( xi ):
51
eta1 = 2.0 * ( 1. + xi1 ) / ( 1. - xi2 ) - 1
55
def xi_triangle( eta ):
57
xi1 = 0.5 * (1. + eta1) * (1. - eta2) - 1.
61
def eta_tetrahedron( xi ):
66
eta1 = -2. * ( 1. + xi1 ) / (xi2 + xi3) - 1.
70
eta2 = 2. * (1. + xi2) / (1. - xi3 ) - 1.
75
def xi_tetrahedron( eta ):
78
xi1 = 0.25 * ( 1. + eta1 ) * ( 1. - eta2 ) * ( 1. - eta3 ) - 1.
79
xi2 = 0.5 * ( 1. + eta2 ) * ( 1. - eta3 ) - 1.
84
coord_changes = { shapes.TRIANGLE: eta_triangle , \
85
shapes.TETRAHEDRON: eta_tetrahedron }
87
inverse_coord_changes = { shapes.TRIANGLE: xi_triangle , \
88
shapes.TETRAHEDRON: xi_tetrahedron }
90
def make_coordinate_change( shape ):
91
"""Maps from reference domain to rectangular reference domain."""
93
if coord_changes.has_key( shape ):
94
return coord_changes[shape]
96
raise RuntimeError, "Can't collapse coordinates"
98
def make_inverse_coordinate_change( shape ):
99
"""Maps from rectangular reference domain to reference domain."""
100
global inverse_coord_changes
101
if inverse_coord_changes.has_key( shape ):
102
return inverse_coord_changes[shape]
104
raise RuntimeError, "Can't collapse coordinates"
106
def phi_line( index , xi ):
109
return psitilde_a( p , eta )
111
def phi_triangle( index , xi ):
113
eta1,eta2 = eta_triangle( xi )
115
alpha = psitilde_a( p , eta1 )
116
beta = psitilde_b( p , q , eta2 )
120
def phi_tetrahedron( index , xi ):
122
eta1,eta2,eta3 = eta_tetrahedron( xi )
124
alpha = psitilde_a( p , eta1 )
125
beta = psitilde_b( p , q , eta2 )
126
gamma = psitilde_c( p , q , r , eta3 )
128
return alpha * beta * gamma
130
def tabulate_phis_line( n , xs ):
131
"""Tabulates all the basis functions over the line up to
132
degree in at points xs."""
133
# extract raw points from singletons
134
ys = Numeric.array( [ x for (x,) in xs ] )
135
phis = jacobi.eval_jacobi_batch(0,0,n,ys)
136
for i in range(0,len(phis)):
137
phis[i,:] *= math.sqrt(1.0*i+0.5)
140
def tabulate_phis_derivs_line( n , xs ):
141
ys = Numeric.array( [ x for (x,) in xs ] )
142
phi_derivs = jacobi.eval_jacobi_deriv_batch(0,0,n,ys)
143
for i in range(0,len(xs)):
144
phi_derivs[i,:] *= math.sqrt(1.0*i+0.5)
147
def tabulate_phis_triangle( n , xs ):
148
"""Tabulates all the basis functions over the triangle
152
etas = map( eta_triangle , xs )
153
eta1s = Numeric.array( [ eta1 for (eta1,eta2) in etas ] )
154
eta2s = Numeric.array( [ eta2 for (eta1,eta2) in etas ] )
156
# get Legendre functions for eta1 direction
157
psitilde_as = jacobi.eval_jacobi_batch(0,0,n,eta1s)
159
# scalings ( (1-eta2) / 2 ) ** i
160
scalings = make_scalings( n , eta2s )
162
# for i == 0, I can have j == 0...degree
163
# for i == 1, I can have j == 0...degree-1
165
# phi_{i,j} requires p_j^{2i+1,0}
166
psitilde_bs = [ jacobi.eval_jacobi_batch(2*i+1,0,n-i,eta2s) \
167
for i in range(0,n+1) ]
169
results = Numeric.zeros( (shapes.poly_dims[shapes.TRIANGLE](n),len(xs)) , \
172
for k in range(0,n+1):
173
for i in range(0,k+1):
176
results[cur,:] = psitilde_as[ii,:] * scalings[ii,:] \
177
* psitilde_bs[ii][jj,:]
178
results[cur,:] *= math.sqrt( (ii+0.5)*(ii+jj+1.0) )
183
def tabulate_phis_derivs_triangle(n,xs):
184
"""I'm not properly handling the singularity at the top,
185
so we can't differentiation for eta2 == 0."""
187
etas = map( eta_triangle , xs )
188
eta1s = Numeric.array( [ eta1 for (eta1,eta2) in etas ] )
189
eta2s = Numeric.array( [ eta2 for (eta1,eta2) in etas ] )
191
psitilde_as = jacobi.eval_jacobi_batch(0,0,n,eta1s)
192
psitilde_derivs_as = jacobi.eval_jacobi_deriv_batch(0,0,n,eta1s)
193
psitilde_bs = [ jacobi.eval_jacobi_batch(2*i+1,0,n-i,eta2s) \
194
for i in range(0,n+1) ]
195
psitilde_derivs_bs = [ jacobi.eval_jacobi_deriv_batch(2*i+1,0,n-i,eta2s) \
196
for i in range(0,n+1) ]
198
scalings = Numeric.zeros( (n+1,len(xs)) ,"d")
201
scalings[1,:] = 0.5 * (1.0 - eta2s)
202
for k in range(2,n+1):
203
scalings[k,:] = scalings[k-1,:] * scalings[1,:]
205
xderivs = Numeric.zeros( (shapes.poly_dims[shapes.TRIANGLE](n),len(xs)) , \
207
yderivs = Numeric.zeros( xderivs.shape , "d" )
208
tmp = Numeric.zeros( (len(xs),) , "d" )
211
for k in range(0,n+1):
212
for i in range(0,k+1):
216
xderivs[cur,:] = psitilde_derivs_as[ii,:] \
217
* psitilde_bs[ii][jj,:]
219
xderivs[cur,:] *= scalings[ii-1,:]
221
yderivs[cur,:] = psitilde_derivs_as[ii,:] \
222
* psitilde_bs[ii][jj,:] \
223
* 0.5 * (1.0+eta1s[:])
225
yderivs[cur,:] *= scalings[ii-1,:]
228
tmp[:] = psitilde_derivs_bs[ii][jj,:] \
231
tmp[:] -= 0.5 * ii * psitilde_bs[ii][jj,:] \
234
yderivs[cur,:] += psitilde_as[ii,:] * tmp
236
alpha = math.sqrt( (ii+0.5)*(ii+jj+1.0) )
237
xderivs[cur,:] *= alpha
238
yderivs[cur,:] *= alpha
241
return (xderivs,yderivs)
243
def tabulate_phis_tetrahedron( n , xs ):
244
"""Tabulates all the basis functions over the tetrahedron
247
etas = map( eta_tetrahedron , xs )
248
eta1s = Numeric.array( [ eta1 for (eta1,eta2,eta3) in etas ] )
249
eta2s = Numeric.array( [ eta2 for (eta1,eta2,eta3) in etas ] )
250
eta3s = Numeric.array( [ eta3 for (eta1,eta2,eta3) in etas ] )
252
# get Legendre functions for eta1 direction
253
psitilde_as = jacobi.eval_jacobi_batch(0,0,n,eta1s)
255
eta2_scalings = Numeric.zeros( (n+1,len(xs)) ,"d")
256
eta2_scalings[0,:] = 1.0
258
eta2_scalings[1,:] = 0.5 * (1.0 - eta2s)
259
for k in range(2,n+1):
260
eta2_scalings[k,:] = eta2_scalings[k-1,:] * eta2_scalings[1,:]
262
psitilde_bs = [ jacobi.eval_jacobi_batch(2*i+1,0,n-i,eta2s) \
263
for i in range(0,n+1) ]
265
# (0.5*(1-z))**i+j, since for k=0, we can have i+j up to n,
266
# we need same structure as eta2_scalings
267
eta3_scalings = Numeric.zeros( (n+1,len(xs)) ,"d" )
268
eta3_scalings[0,:] = 1.0
270
eta3_scalings[1,:] = 0.5 * (1.0 - eta3s)
271
for k in range(2,n+1):
272
eta3_scalings[k,:] = eta3_scalings[k-1,:] * eta3_scalings[1,:]
274
# I need psitilde_c[i][j][k]
277
psitilde_cs = [ [ jacobi.eval_jacobi_batch(2*(i+j+1),0,\
279
for j in range(0,n+1-i) ] for i in range(0,n+1) ]
281
results = Numeric.zeros( (shapes.poly_dims[shapes.TETRAHEDRON](n), \
285
for k in range(0,n+1): # loop over degree
286
for i in range(0,k+1):
287
for j in range(0,k-i+1):
291
results[cur,:] = psitilde_as[ii,:] * \
292
eta2_scalings[ii,:] \
293
* psitilde_bs[ii][jj,:] \
294
* eta3_scalings[ii+jj,:] \
295
* psitilde_cs[ii][jj][kk,:]
296
results[cur,:] *= math.sqrt( (ii+0.5) \
303
def tabulate_phis_derivs_tetrahedron(n,xs):
304
"""Tabulates all the derivatives of basis functions over the tetrahedron
305
up to degree n at points xs"""
307
etas = map( eta_tetrahedron , xs )
308
eta1s = Numeric.array( [ eta1 for (eta1,eta2,eta3) in etas ] )
309
eta2s = Numeric.array( [ eta2 for (eta1,eta2,eta3) in etas ] )
310
eta3s = Numeric.array( [ eta3 for (eta1,eta2,eta3) in etas ] )
312
psitilde_as = jacobi.eval_jacobi_batch(0,0,n,eta1s)
313
psitilde_as_derivs = jacobi.eval_jacobi_deriv_batch(0,0,n,eta1s)
314
psitilde_bs = [ jacobi.eval_jacobi_batch(2*i+1,0,n-i,eta2s) \
315
for i in range(0,n+1) ]
316
psitilde_bs_derivs = [ jacobi.eval_jacobi_deriv_batch(2*i+1,0,n-i,eta2s) \
317
for i in range(0,n+1) ]
318
psitilde_cs = [ [ jacobi.eval_jacobi_batch(2*(i+j+1),0,\
320
for j in range(0,n+1-i) ] for i in range(0,n+1) ]
321
psitilde_cs_derivs = [ [ jacobi.eval_jacobi_deriv_batch(2*(i+j+1),0,\
323
for j in range(0,n+1-i) ] for i in range(0,n+1) ]
325
eta2_scalings = Numeric.zeros( (n+1,len(xs)) ,"d")
326
eta2_scalings[0,:] = 1.0
328
eta2_scalings[1,:] = 0.5 * (1.0 - eta2s)
329
for k in range(2,n+1):
330
eta2_scalings[k,:] = eta2_scalings[k-1,:] * eta2_scalings[1,:]
332
eta3_scalings = Numeric.zeros( (n+1,len(xs)) ,"d" )
333
eta3_scalings[0,:] = 1.0
335
eta3_scalings[1,:] = 0.5 * (1.0 - eta3s)
336
for k in range(2,n+1):
337
eta3_scalings[k,:] = eta3_scalings[k-1,:] \
340
tmp = Numeric.zeros( (len(xs),) , "d" )
341
xderivs = Numeric.zeros( (shapes.poly_dims[shapes.TETRAHEDRON](n), \
344
yderivs = Numeric.zeros( xderivs.shape , "d" )
345
zderivs = Numeric.zeros( xderivs.shape , "d" )
348
for k in range(0,n+1): # loop over degree
349
for i in range(0,k+1):
350
for j in range(0,k-i+1):
355
xderivs[cur,:] = psitilde_as_derivs[ii,:]
356
xderivs[cur,:] *= psitilde_bs[ii][jj,:]
357
xderivs[cur,:] *= psitilde_cs[ii][jj][kk,:]
359
xderivs[cur,:] *= eta2_scalings[ii-1]
361
xderivs[cur,:] *= eta3_scalings[ii+jj-1]
363
#d/deta1 with scalings
364
yderivs[cur,:] = psitilde_as_derivs[ii,:]
365
yderivs[cur,:] *= psitilde_bs[ii][jj,:]
366
yderivs[cur,:] *= psitilde_cs[ii][jj][kk,:]
367
yderivs[cur,:] *= 0.5 * (1.0+eta1s[:])
369
yderivs[cur,:] *= eta2_scalings[ii-1]
371
yderivs[cur,:] *= eta3_scalings[ii+jj-1]
373
#tmp will hold d/deta2 term
374
tmp[:] = psitilde_bs_derivs[ii][jj,:]
375
tmp[:] *= eta2_scalings[ii,:]
377
tmp[:] -= 0.5*ii*eta2_scalings[ii-1]*psitilde_bs[ii][jj,:]
378
tmp[:] *= psitilde_as[ii,:]
379
tmp[:] *= psitilde_cs[ii][jj][kk,:]
381
tmp[:] *= eta3_scalings[ii+jj-1]
382
yderivs[cur,:] += tmp[:]
385
#d/deta1 with scalings
386
zderivs[cur,:] = psitilde_as_derivs[ii,:]
387
zderivs[cur,:] *= psitilde_bs[ii][jj,:]
388
zderivs[cur,:] *= psitilde_cs[ii][jj][kk,:]
389
zderivs[cur,:] *= 0.5 * (1.0+eta1s[:])
391
zderivs[cur,:] *= eta2_scalings[ii-1]
393
zderivs[cur,:] *= eta3_scalings[ii+jj-1]
395
#tmp will hold d/deta2 term
396
tmp[:] = psitilde_bs_derivs[ii][jj,:]
397
tmp[:] *= eta2_scalings[ii,:]
399
tmp[:] -= 0.5*ii*eta2_scalings[ii-1]*psitilde_bs[ii][jj,:]
400
tmp[:] *= psitilde_as[ii,:]
401
tmp[:] *= psitilde_cs[ii][jj][kk,:]
402
tmp[:] *= 0.5*(1.0+eta2s[:])
404
tmp[:] *= eta3_scalings[ii+jj-1]
405
zderivs[cur,:] += tmp[:]
407
#tmp will hold d/deta3 term
408
tmp[:] = psitilde_cs_derivs[ii][jj][kk,:]
409
tmp[:] *= eta3_scalings[ii+jj,:]
411
tmp[:] -= 0.5*(ii+jj)*psitilde_cs[ii][jj][kk,:] \
412
* eta3_scalings[ii+jj-1,:]
413
tmp[:] *= psitilde_as[ii,:]
414
tmp[:] *= psitilde_bs[ii][jj,:]
415
tmp[:] *= eta2_scalings[ii]
416
zderivs[cur,:] += tmp[:]
418
xderivs[cur,:] *= math.sqrt( (ii+0.5) \
422
yderivs[cur,:] *= math.sqrt( (ii+0.5) \
426
zderivs[cur,:] *= math.sqrt( (ii+0.5) \
433
return (xderivs,yderivs,zderivs)
435
def tabulate_phis_derivs_tetrahedron_old(n,xs):
436
"""Tabulates all the derivatives of basis functions over the tetrahedron
437
up to degree n at points xs"""
439
etas = map( eta_tetrahedron , xs )
440
eta1s = Numeric.array( [ eta1 for (eta1,eta2,eta3) in etas ] )
441
eta2s = Numeric.array( [ eta2 for (eta1,eta2,eta3) in etas ] )
442
eta3s = Numeric.array( [ eta3 for (eta1,eta2,eta3) in etas ] )
444
psitilde_as = jacobi.eval_jacobi_batch(0,0,n,eta1s)
445
psitilde_as_derivs = jacobi.eval_jacobi_deriv_batch(0,0,n,eta1s)
446
psitilde_bs = [ jacobi.eval_jacobi_batch(2*i+1,0,n-i,eta2s) \
447
for i in range(0,n+1) ]
448
psitilde_bs_derivs = [ jacobi.eval_jacobi_deriv_batch(2*i+1,0,n-i,eta2s) \
449
for i in range(0,n+1) ]
450
psitilde_cs = [ [ jacobi.eval_jacobi_batch(2*(i+j+1),0,\
452
for j in range(0,n+1-i) ] for i in range(0,n+1) ]
453
psitilde_cs_derivs = [ [ jacobi.eval_jacobi_deriv_batch(2*(i+j+1),0,\
455
for j in range(0,n+1-i) ] for i in range(0,n+1) ]
457
eta2_scalings = Numeric.zeros( (n+1,len(xs)) ,"d")
458
eta2_scalings[0,:] = 1.0
460
eta2_scalings[1,:] = 0.5 * (1.0 - eta2s)
461
for k in range(2,n+1):
462
eta2_scalings[k,:] = eta2_scalings[k-1,:] * eta2_scalings[1,:]
464
eta3_scalings = Numeric.zeros( (n+1,len(xs)) ,"d" )
465
eta3_scalings[0,:] = 1.0
467
eta3_scalings[1,:] = 0.5 * (1.0 - eta3s)
468
for k in range(2,n+1):
469
eta3_scalings[k,:] = eta3_scalings[k-1,:] \
472
tmp = Numeric.zeros( (len(xs),) , "d" )
473
xderivs = Numeric.zeros( (shapes.poly_dims[shapes.TETRAHEDRON](n), \
476
yderivs = Numeric.zeros( xderivs.shape , "d" )
477
zderivs = Numeric.zeros( xderivs.shape , "d" )
480
for k in range(0,n+1): # loop over degree
481
for i in range(0,k+1):
482
for j in range(0,k-i+1):
488
xderivs[cur,:] = psitilde_as_derivs[ii,:] \
489
* psitilde_bs[ii][jj,:] \
490
* psitilde_cs[ii][jj][kk,:]
492
xderivs[cur,:] *= eta2_scalings[ii-1,:]
494
xderivs[cur,:] *= eta3_scalings[ii+jj-1,:]
497
# eta1 derivative term
498
yderivs[cur,:] = 0.5 * (1.0+eta1s) * xderivs[cur,:]
500
# eta2 derivative term, start with the "internal"
501
# product rule on the eta2 term itself
502
tmp[:] = psitilde_bs_derivs[ii][jj,:] * eta2_scalings[ii,:]
504
tmp[:] -= 0.5 * ii * psitilde_bs[ii][jj,:] \
505
* eta2_scalings[ii-1,:]
506
tmp[:] *= psitilde_as[ii,:] * psitilde_cs[ii][jj][kk,:]
508
tmp[:] *= eta3_scalings[ii+jj-1,:]
509
yderivs[cur,:] += tmp
512
# eta1 derivative term
513
zderivs[cur,:] = 0.5 * (1.0+eta1s) * xderivs[cur,:]
515
# check this term here...
516
# eta2 derivative term
517
zderivs[cur,:] += tmp * 0.5 * (1.0+eta1s)
520
# eta3 derivative term
521
tmp[:] = psitilde_cs_derivs[ii][jj][kk,:] * eta3_scalings[ii+jj,:]
523
tmp[:] += psitilde_cs[ii][jj][kk,:] * (ii+jj) * eta3_scalings[ii+jj-1,:]
524
tmp[:] *= psitilde_as[ii,:] * psitilde_bs[ii][jj,:] \
525
* eta2_scalings[ii,:]
526
zderivs[cur,:] += tmp
528
xderivs[cur,:] *= math.sqrt( (ii+0.5) \
532
yderivs[cur,:] *= math.sqrt( (ii+0.5) \
536
zderivs[cur,:] *= math.sqrt( (ii+0.5) \
543
return (xderivs,yderivs,zderivs)
547
class ExpansionFunction( object ):
548
def __init__( self , indices , phi , alpha ):
549
self.indices, self.phi,self.alpha = indices , phi , alpha
551
def __call__( self , x ):
552
if len( x ) != len( self.indices ):
553
raise RuntimeError, "Illegal number of coordinates"
554
return self.alpha * self.phi( self.indices , x )
556
class PhiLine( ExpansionFunction ):
557
def __init__( self , i ):
558
ExpansionFunction.__init__( self , \
561
math.sqrt(1.0*i+0.5) )
563
class PhiTriangle( ExpansionFunction ):
564
def __init__( self , i , j ):
565
ExpansionFunction.__init__( self , \
568
math.sqrt( (i+0.5)*(i+j+1.0) ) )
571
class PhiTetrahedron( ExpansionFunction ):
572
def __init__( self , i , j , k ):
573
ExpansionFunction.__init__( self , (i,j,k) , \
575
math.sqrt( (i+0.5)*(i+j+1.0)*(i+j+k+1.5)))
578
def make_phis_line( n ):
579
return [ PhiLine( i ) \
580
for i in range(0,n+1) ]
582
def make_phis_triangle( n ):
583
return [ PhiTriangle( k - i , i ) \
584
for k in range( 0 , n + 1 ) \
585
for i in range( 0 , k + 1 ) ]
587
def make_phis_tetrahedron( n ):
588
return [ PhiTetrahedron( k - i - j , j , i ) \
589
for k in range( 0 , n + 1 ) \
590
for i in range( 0 , k + 1 ) \
591
for j in range( 0 , k - i + 1 ) ]
593
make_phis = { shapes.LINE : make_phis_line , \
594
shapes.TRIANGLE : make_phis_triangle , \
595
shapes.TETRAHEDRON : make_phis_tetrahedron }
597
def make_expansion( shape , n ):
598
"""Returns the orthogonal expansion basis on a given shape
599
for polynomials of degree n."""
602
return make_phis[shape]( n )
604
raise shapes.ShapeError, "expansions.make_expansion: Illegal shape"
606
tabulators = { shapes.LINE : tabulate_phis_line , \
607
shapes.TRIANGLE : tabulate_phis_triangle , \
608
shapes.TETRAHEDRON : tabulate_phis_tetrahedron }
610
deriv_tabulators = { shapes.LINE : tabulate_phis_derivs_line , \
611
shapes.TRIANGLE : tabulate_phis_derivs_triangle , \
612
shapes.TETRAHEDRON : tabulate_phis_derivs_tetrahedron }