turbx 1.0.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
turbx/eas4.py ADDED
@@ -0,0 +1,567 @@
1
+ import os
2
+
3
+ import h5py
4
+ import numpy as np
5
+ from mpi4py import MPI
6
+
7
+ from .utils import even_print
8
+
9
+ # ======================================================================
10
+
11
+ class eas4(h5py.File):
12
+ '''
13
+ Interface class for EAS4 files
14
+ ------------------------------
15
+ - super()'ed h5py.File class
16
+ - EAS4 is the HDF5-based output format from the flow solver NS3D
17
+ - 3D dataset storage ([x,y,z] per [t])
18
+ '''
19
+
20
+ def __init__(self, *args, **kwargs):
21
+
22
+ ## if grid MUST be read as 3D, i.e. the GMODE=(5,5,5), only read if this is TRUE
23
+ ## this can lead to huge RAM usage in MPI mode, so OFF by default
24
+ ##
25
+ ## if a single dimension has GMODE=5 but not ALL, i.e. (5,5,2), this allows for
26
+ ## grid to be read as some combination of 2D&1D
27
+ self.read_3d_grid = kwargs.pop('read_3d_grid', False)
28
+
29
+ self.fname, openMode = args
30
+
31
+ self.fname_path = os.path.dirname(self.fname)
32
+ self.fname_base = os.path.basename(self.fname)
33
+ self.fname_root, self.fname_ext = os.path.splitext(self.fname_base)
34
+
35
+ ## default to libver='latest' if none provided
36
+ if ('libver' not in kwargs):
37
+ kwargs['libver'] = 'latest'
38
+
39
+ if (openMode!='r'):
40
+ raise ValueError('turbx.eas4(): opening EAS4 in anything but read mode \'r\' is not allowed!')
41
+
42
+ ## catch possible user error --> user tries to open non-EAS4 with turbx.eas4()
43
+ if (self.fname_ext!='.eas'):
44
+ raise ValueError('turbx.eas4() should not be used to open non-EAS4 files')
45
+
46
+ ## determine if using mpi
47
+ if ('driver' in kwargs) and (kwargs['driver']=='mpio'):
48
+ if ('comm' not in kwargs):
49
+ raise ValueError("if driver='mpio', then comm should be provided")
50
+ self.usingmpi = True
51
+ else:
52
+ self.usingmpi = False
53
+
54
+ ## determine communicator & rank info
55
+ if self.usingmpi:
56
+ self.comm = kwargs['comm']
57
+ self.n_ranks = self.comm.Get_size()
58
+ self.rank = self.comm.Get_rank()
59
+ else:
60
+ self.comm = None
61
+ self.n_ranks = 1
62
+ self.rank = 0
63
+ if ('comm' in kwargs):
64
+ del kwargs['comm']
65
+
66
+ ## set library version to latest (if not otherwise set)
67
+ if ('libver' not in kwargs):
68
+ kwargs['libver']='latest'
69
+
70
+ ## unique kwargs (not h5py.File kwargs) --> pop() rather than get()
71
+ no_indep_rw = kwargs.pop('no_indep_rw', False)
72
+ if not isinstance(no_indep_rw, bool):
73
+ raise ValueError('no_indep_rw must be type bool')
74
+
75
+ ## set MPI hints, passed through 'mpi_info' dict
76
+ if self.usingmpi:
77
+ if ('info' in kwargs):
78
+ self.mpi_info = kwargs['info']
79
+ else:
80
+ mpi_info = MPI.Info.Create()
81
+ ##
82
+ mpi_info.Set('romio_ds_write' , 'disable' )
83
+ mpi_info.Set('romio_ds_read' , 'disable' )
84
+ #mpi_info.Set('romio_cb_read' , 'automatic' )
85
+ #mpi_info.Set('romio_cb_write' , 'automatic' )
86
+ mpi_info.Set('romio_cb_read' , 'enable' )
87
+ mpi_info.Set('romio_cb_write' , 'enable' )
88
+
89
+ ## ROMIO -- collective buffer size
90
+ mpi_info.Set('cb_buffer_size' , str(int(round(1024**3))) ) ## 1 [GB]
91
+
92
+ ## ROMIO -- force collective I/O
93
+ if no_indep_rw:
94
+ mpi_info.Set('romio_no_indep_rw' , 'true' )
95
+
96
+ ## ROMIO -- N Aggregators
97
+ #mpi_info.Set('cb_nodes' , str(min(16,self.n_ranks//2)) )
98
+ mpi_info.Set('cb_nodes' , str(min(16,self.n_ranks)) )
99
+
100
+ kwargs['info'] = mpi_info
101
+ self.mpi_info = mpi_info
102
+
103
+ # === HDF5 tuning factors (independent of MPI I/O driver)
104
+
105
+ ## rdcc_w0 : preemption policy (weight) for HDF5's raw data chunk cache
106
+ ## - influences how HDF5 evicts chunks from the per-process chunk cache
107
+ ## - 1.0 favors retaining fully-read chunks (good for read-heavy access)
108
+ ## - 0.0 favors recently-used chunks (better for partial writes)
109
+ if ('rdcc_w0' not in kwargs):
110
+ kwargs['rdcc_w0'] = 0.75
111
+
112
+ ## rdcc_nbytes : maximum total size of the HDF5 raw chunk cache per dataset per process
113
+ if ('rdcc_nbytes' not in kwargs):
114
+ kwargs['rdcc_nbytes'] = int(1*1024**3) ## 1 [GB]
115
+
116
+ ## rdcc_nslots : number of hash table slots in the raw data chunk cache
117
+ ## - should be ~= ( rdcc_nbytes / chunk size )
118
+ if ('rdcc_nslots' not in kwargs):
119
+ #kwargs['rdcc_nslots'] = 16381 ## prime
120
+ kwargs['rdcc_nslots'] = kwargs['rdcc_nbytes'] // (2*1024**2) ## assume 2 [MB] chunks
121
+ #kwargs['rdcc_nslots'] = kwargs['rdcc_nbytes'] // (128*1024**2) ## assume 128 [MB] chunks
122
+
123
+ self.domainName = 'DOMAIN_000000' ## turbx only handles one domain for now
124
+
125
+ ## eas4() unique kwargs (not h5py.File kwargs) --> pop() rather than get()
126
+ verbose = kwargs.pop('verbose',False)
127
+ self.verbose = verbose
128
+ ## force = kwargs.pop('force',False) ## --> dont need, always read-only!
129
+
130
+ ## call actual h5py.File.__init__()
131
+ super(eas4, self).__init__(*args, **kwargs)
132
+ self.get_header(verbose=verbose)
133
+
134
+ def get_header(self, **kwargs):
135
+
136
+ EAS4_NO_G=1
137
+ EAS4_X0DX_G=2
138
+ #EAS4_UDEF_G=3
139
+ EAS4_ALL_G=4
140
+ EAS4_FULL_G=5
141
+ gmode_dict = {1:'EAS4_NO_G', 2:'EAS4_X0DX_G', 3:'EAS4_UDEF_G', 4:'EAS4_ALL_G', 5:'EAS4_FULL_G'}
142
+
143
+ self.bform = self['Kennsatz'].attrs['bform'] ## binary format : 1=single , 2=double
144
+ self.dform = self['Kennsatz'].attrs['dform'] ## data format : 1=C-order , 2=Fortran-order
145
+
146
+ # === characteristic values
147
+
148
+ if self.verbose: print(72*'-')
149
+ Ma = self['Kennsatz']['FLOWFIELD_PROPERTIES'].attrs['Ma'][0]
150
+ Re = self['Kennsatz']['FLOWFIELD_PROPERTIES'].attrs['Re'][0]
151
+ Pr = self['Kennsatz']['FLOWFIELD_PROPERTIES'].attrs['Pr'][0]
152
+ kappa = self['Kennsatz']['FLOWFIELD_PROPERTIES'].attrs['kappa'][0]
153
+ R = self['Kennsatz']['FLOWFIELD_PROPERTIES'].attrs['R'][0]
154
+ p_inf = self['Kennsatz']['FLOWFIELD_PROPERTIES'].attrs['p_inf'][0]
155
+ T_inf = self['Kennsatz']['FLOWFIELD_PROPERTIES'].attrs['T_inf'][0]
156
+
157
+ ## !!! import what is called 'C_Suth' in NS3D as 'S_Suth' !!!
158
+ S_Suth = self['Kennsatz']['VISCOUS_PROPERTIES'].attrs['C_Suth'][0]
159
+
160
+ mu_Suth_ref = self['Kennsatz']['VISCOUS_PROPERTIES'].attrs['mu_Suth_ref'][0]
161
+ T_Suth_ref = self['Kennsatz']['VISCOUS_PROPERTIES'].attrs['T_Suth_ref'][0]
162
+
163
+ C_Suth = mu_Suth_ref/(T_Suth_ref**(3/2))*(T_Suth_ref + S_Suth) ## [kg/(m·s·√K)]
164
+
165
+ if self.verbose: even_print('Ma' , '%0.2f [-]' % Ma )
166
+ if self.verbose: even_print('Re' , '%0.1f [-]' % Re )
167
+ if self.verbose: even_print('Pr' , '%0.3f [-]' % Pr )
168
+ if self.verbose: even_print('T_inf' , '%0.3f [K]' % T_inf )
169
+ if self.verbose: even_print('p_inf' , '%0.1f [Pa]' % p_inf )
170
+ if self.verbose: even_print('kappa' , '%0.3f [-]' % kappa )
171
+ if self.verbose: even_print('R' , '%0.3f [J/(kg·K)]' % R )
172
+ if self.verbose: even_print('mu_Suth_ref' , '%0.6E [kg/(m·s)]' % mu_Suth_ref )
173
+ if self.verbose: even_print('T_Suth_ref' , '%0.2f [K]' % T_Suth_ref )
174
+ if self.verbose: even_print('S_Suth' , '%0.2f [K]' % S_Suth )
175
+ if self.verbose: even_print('C_Suth' , '%0.5e [kg/(m·s·√K)]' % C_Suth ) ## actually derived
176
+
177
+ # === characteristic values : derived
178
+
179
+ # mu_inf_1 = 14.58e-7*T_inf**1.5/(T_inf+110.4)
180
+ # mu_inf_2 = mu_Suth_ref*(T_inf/T_Suth_ref)**(3/2) * ((T_Suth_ref+S_Suth)/(T_inf+S_Suth))
181
+ # mu_inf_3 = C_Suth*T_inf**(3/2)/(T_inf+S_Suth)
182
+ # if not np.isclose(mu_inf_1, mu_inf_2, rtol=1e-14):
183
+ # raise AssertionError('inconsistency in Sutherland calc --> check')
184
+ # if not np.isclose(mu_inf_2, mu_inf_3, rtol=1e-14):
185
+ # raise AssertionError('inconsistency in Sutherland calc --> check')
186
+ # mu_inf = mu_inf_3
187
+
188
+ if self.verbose: print(72*'-')
189
+ mu_inf = mu_Suth_ref*(T_inf/T_Suth_ref)**(3/2) * ((T_Suth_ref+S_Suth)/(T_inf+S_Suth))
190
+ rho_inf = p_inf / ( R * T_inf )
191
+ nu_inf = mu_inf/rho_inf
192
+ a_inf = np.sqrt(kappa*R*T_inf)
193
+ U_inf = Ma*a_inf
194
+ cp = R*kappa/(kappa-1.)
195
+ cv = cp/kappa
196
+ recov_fac = Pr**(1/3)
197
+ Taw = T_inf + recov_fac*U_inf**2/(2*cp)
198
+ lchar = Re * nu_inf / U_inf
199
+
200
+ if self.verbose: even_print('rho_inf' , '%0.3f [kg/m³]' % rho_inf )
201
+ if self.verbose: even_print('mu_inf' , '%0.6E [kg/(m·s)]' % mu_inf )
202
+ if self.verbose: even_print('nu_inf' , '%0.6E [m²/s]' % nu_inf )
203
+ if self.verbose: even_print('a_inf' , '%0.6f [m/s]' % a_inf )
204
+ if self.verbose: even_print('U_inf' , '%0.6f [m/s]' % U_inf )
205
+ if self.verbose: even_print('cp' , '%0.3f [J/(kg·K)]' % cp )
206
+ if self.verbose: even_print('cv' , '%0.3f [J/(kg·K)]' % cv )
207
+ if self.verbose: even_print('recovery factor' , '%0.6f [-]' % recov_fac )
208
+ if self.verbose: even_print('Taw' , '%0.3f [K]' % Taw )
209
+ if self.verbose: even_print('lchar' , '%0.6E [m]' % lchar )
210
+ if self.verbose: print(72*'-'+'\n')
211
+
212
+ # ===
213
+
214
+ self.Ma = Ma
215
+ self.Re = Re
216
+ self.Pr = Pr
217
+ self.kappa = kappa
218
+ self.R = R
219
+ self.p_inf = p_inf
220
+ self.T_inf = T_inf
221
+ self.C_Suth = C_Suth
222
+ self.S_Suth = S_Suth
223
+ self.mu_Suth_ref = mu_Suth_ref
224
+ self.T_Suth_ref = T_Suth_ref
225
+
226
+ self.rho_inf = rho_inf
227
+ self.mu_inf = mu_inf
228
+ self.nu_inf = nu_inf
229
+ self.a_inf = a_inf
230
+ self.U_inf = U_inf
231
+ self.cp = cp
232
+ self.cv = cv
233
+ self.recov_fac = recov_fac
234
+ self.Taw = Taw
235
+ self.lchar = lchar
236
+
237
+ # === check if this a 2D average file like 'mean_flow_mpi.eas'
238
+
239
+ if self.verbose: print(72*'-')
240
+ if ('/Kennsatz/AUXILIARY/AVERAGING' in self):
241
+ self.total_avg_time = self['/Kennsatz/AUXILIARY/AVERAGING'].attrs['total_avg_time'][0]
242
+ self.total_avg_iter_count = self['/Kennsatz/AUXILIARY/AVERAGING'].attrs['total_avg_iter_count'][0]
243
+ if self.verbose: even_print('total_avg_time', '%0.2f'%self.total_avg_time)
244
+ if self.verbose: even_print('total_avg_iter_count', '%i'%self.total_avg_iter_count)
245
+ self.measType = 'mean'
246
+ else:
247
+ self.measType = 'unsteady'
248
+ if self.verbose: even_print('meas type', '\'%s\''%self.measType)
249
+ if self.verbose: print(72*'-'+'\n')
250
+
251
+ # === grid info
252
+
253
+ ndim1 = int( self['Kennsatz/GEOMETRY/%s'%self.domainName].attrs['DOMAIN_SIZE'][0] )
254
+ ndim2 = int( self['Kennsatz/GEOMETRY/%s'%self.domainName].attrs['DOMAIN_SIZE'][1] )
255
+ ndim3 = int( self['Kennsatz/GEOMETRY/%s'%self.domainName].attrs['DOMAIN_SIZE'][2] )
256
+
257
+ nx = self.nx = ndim1
258
+ ny = self.ny = ndim2
259
+ nz = self.nz = ndim3
260
+
261
+ if (self.measType=='mean'):
262
+ nz = self.nz = 1
263
+
264
+ ngp = self.ngp = nx*ny*nz
265
+
266
+ if self.verbose: print('grid info\n'+72*'-')
267
+ if self.verbose: even_print('nx', '%i'%nx )
268
+ if self.verbose: even_print('ny', '%i'%ny )
269
+ if self.verbose: even_print('nz', '%i'%nz )
270
+ if self.verbose: even_print('ngp', '%i'%ngp )
271
+
272
+ gmode_dim1 = self.gmode_dim1 = self['Kennsatz/GEOMETRY/%s'%self.domainName].attrs['DOMAIN_GMODE'][0]
273
+ gmode_dim2 = self.gmode_dim2 = self['Kennsatz/GEOMETRY/%s'%self.domainName].attrs['DOMAIN_GMODE'][1]
274
+ gmode_dim3 = self.gmode_dim3 = self['Kennsatz/GEOMETRY/%s'%self.domainName].attrs['DOMAIN_GMODE'][2]
275
+
276
+ ## the original gmode (pre-conversion)
277
+ #gmode_dim1_orig = self.gmode_dim1_orig = gmode_dim1
278
+ #gmode_dim2_orig = self.gmode_dim2_orig = gmode_dim2
279
+ #gmode_dim3_orig = self.gmode_dim3_orig = gmode_dim3
280
+
281
+ if self.verbose: even_print( 'gmode dim1' , '%i / %s'%(gmode_dim1,gmode_dict[gmode_dim1]) )
282
+ if self.verbose: even_print( 'gmode dim2' , '%i / %s'%(gmode_dim2,gmode_dict[gmode_dim2]) )
283
+ if self.verbose: even_print( 'gmode dim3' , '%i / %s'%(gmode_dim3,gmode_dict[gmode_dim3]) )
284
+ if self.verbose: print(72*'-')
285
+
286
+ # === (automatic) grid read
287
+
288
+ ## can fail if >2[GB] and using driver=='mpio' and using one process
289
+ ## https://github.com/h5py/h5py/issues/1052
290
+
291
+ if True:
292
+
293
+ ## dset object handles, no read yet
294
+ dset1 = self[f'Kennsatz/GEOMETRY/{self.domainName}/dim01']
295
+ dset2 = self[f'Kennsatz/GEOMETRY/{self.domainName}/dim02']
296
+ dset3 = self[f'Kennsatz/GEOMETRY/{self.domainName}/dim03']
297
+
298
+ #float_bytes1 = dset1.dtype.itemsize
299
+ #float_bytes2 = dset2.dtype.itemsize
300
+ #float_bytes3 = dset3.dtype.itemsize
301
+
302
+ #data_gb_dim1 = float_bytes1*np.prod(dset1.shape) / 1024**3
303
+ #data_gb_dim2 = float_bytes2*np.prod(dset2.shape) / 1024**3
304
+ #data_gb_dim3 = float_bytes3*np.prod(dset3.shape) / 1024**3
305
+
306
+ ## no 3D datasets, because no dim's GMODE is 5
307
+ ## in this case, the dsets are just 0D/1D, so just read completely to all ranks
308
+ if not any([ (gmode_dim1==5) , (gmode_dim2==5) , (gmode_dim3==5) ]):
309
+
310
+ self.is_curvilinear = False
311
+ self.is_rectilinear = True
312
+
313
+ ## check just to be sure that the coord dsets in the HDF5 are indeed not 3D
314
+ if (dset1.ndim>2):
315
+ raise ValueError
316
+ if (dset2.ndim>2):
317
+ raise ValueError
318
+ if (dset2.ndim>2):
319
+ raise ValueError
320
+
321
+ ## read 1D datasets completely... this should never be a problem for RAM in MPI mode
322
+ dim1_data = np.copy(dset1[()])
323
+ dim2_data = np.copy(dset2[()])
324
+ dim3_data = np.copy(dset3[()])
325
+
326
+ else:
327
+
328
+ '''
329
+ at least one dim has GMODE=5, so 3D datasets exist, BUT unless GMODE==(5,5,5)
330
+ it is still possible to read as combo of 1D/2D, which is safe for MPI
331
+ '''
332
+
333
+ self.is_curvilinear = True
334
+ self.is_rectilinear = False
335
+
336
+ if (gmode_dim1==5) and (gmode_dim2==5) and (gmode_dim3==5): ## MUST read as 3D... only do here if read_3d_grid=True explicitly set
337
+
338
+ ## this becomes very RAM intensive in MPI mode (all rannks are reading full 3D coord data)
339
+ if self.read_3d_grid:
340
+ dim1_data = np.copy(dset1[()])
341
+ dim2_data = np.copy(dset2[()])
342
+ dim3_data = np.copy(dset3[()])
343
+ else:
344
+ dim1_data = None
345
+ dim2_data = None
346
+ dim3_data = None
347
+
348
+ elif (gmode_dim1==5) and (gmode_dim2==5) and (gmode_dim3==1): ## 551
349
+ dim1_data = np.copy(dset1[:,:,0][:,:,np.newaxis]) ## [x] is (nx,ny,1)
350
+ dim2_data = np.copy(dset2[:,:,0][:,:,np.newaxis]) ## [y] is (nx,ny,1)
351
+ dim3_data = np.copy(dset3[()]) ## [z]
352
+
353
+ elif (gmode_dim1==5) and (gmode_dim2==5) and (gmode_dim3==2): ## 552
354
+ dim1_data = np.copy(dset1[:,:,0][:,:,np.newaxis]) ## [x] is (nx,ny,1)
355
+ dim2_data = np.copy(dset2[:,:,0][:,:,np.newaxis]) ## [y] is (nx,ny,1)
356
+ dim3_data = np.copy(dset3[()]) ## [z]
357
+
358
+ elif (gmode_dim1==5) and (gmode_dim2==5) and (gmode_dim3==4): ## 554
359
+ dim1_data = np.copy(dset1[:,:,0][:,:,np.newaxis]) ## [x] is (nx,ny,1)
360
+ dim2_data = np.copy(dset2[:,:,0][:,:,np.newaxis]) ## [y] is (nx,ny,1)
361
+ dim3_data = np.copy(dset3[()]) ## [z]
362
+
363
+ else:
364
+
365
+ print(f'gmode combo ({gmode_dim1:d},{gmode_dim2:d},{gmode_dim3:d}) does not yet have instructions in eas4.get_header()')
366
+ raise NotImplementedError
367
+
368
+ ## old snippet for getting around HDF5 / h5py bug if >2[GB] and using driver=='mpio' and using one process
369
+ ## keeping here for now
370
+ ## https://github.com/h5py/h5py/issues/1052
371
+ #except OSError:
372
+ if False:
373
+
374
+ if (gmode_dim1 == EAS4_FULL_G):
375
+ dim1_data = np.zeros((nx,ny,nz), dtype = self['Kennsatz/GEOMETRY/%s/dim01'%self.domainName].dtype)
376
+ for i in range(nx):
377
+ dim1_data[i,:,:] = self['Kennsatz/GEOMETRY/%s/dim01'%self.domainName][i,:,:]
378
+ else:
379
+ dim1_data = self['Kennsatz/GEOMETRY/%s/dim01'%self.domainName][:]
380
+
381
+ if (gmode_dim2 == EAS4_FULL_G):
382
+ dim2_data = np.zeros((nx,ny,nz), dtype = self['Kennsatz/GEOMETRY/%s/dim02'%self.domainName].dtype)
383
+ for i in range(nx):
384
+ dim2_data[i,:,:] = self['Kennsatz/GEOMETRY/%s/dim02'%self.domainName][i,:,:]
385
+ else:
386
+ dim2_data = self['Kennsatz/GEOMETRY/%s/dim02'%self.domainName][:]
387
+
388
+ if (gmode_dim3 == EAS4_FULL_G):
389
+ dim3_data = np.zeros((nx,ny,nz), dtype = self['Kennsatz/GEOMETRY/%s/dim03'%self.domainName].dtype)
390
+ for i in range(nx):
391
+ dim3_data[i,:,:] = self['Kennsatz/GEOMETRY/%s/dim03'%self.domainName][i,:,:]
392
+ else:
393
+ dim3_data = self['Kennsatz/GEOMETRY/%s/dim03'%self.domainName][:]
394
+
395
+ ## check grid for span avg
396
+ if False:
397
+ if (self.measType == 'mean'):
398
+ if (gmode_dim1 == EAS4_FULL_G):
399
+ if not np.allclose(dim1_data[:,:,0], dim1_data[:,:,1], rtol=1e-08):
400
+ raise AssertionError('check')
401
+ if (gmode_dim2 == EAS4_FULL_G):
402
+ if not np.allclose(dim2_data[:,:,0], dim2_data[:,:,1], rtol=1e-08):
403
+ raise AssertionError('check')
404
+
405
+ # === expand gmodes 1,2 --> 4 (always ok, even for MPI mode)
406
+
407
+ ## convert EAS4_NO_G to EAS4_ALL_G (1 --> 4) --> always do this
408
+ ## dim_n_data are already numpy arrays of shape (1,) --> no conversion necessary, just update 'gmode_dimn' attr
409
+ if (gmode_dim1 == EAS4_NO_G):
410
+ gmode_dim1 = self.gmode_dim1 = EAS4_ALL_G
411
+ if (gmode_dim2 == EAS4_NO_G):
412
+ gmode_dim2 = self.gmode_dim2 = EAS4_ALL_G
413
+ if (gmode_dim3 == EAS4_NO_G):
414
+ gmode_dim3 = self.gmode_dim3 = EAS4_ALL_G
415
+
416
+ ## convert EAS4_X0DX_G to EAS4_ALL_G (2 --> 4) --> always do this
417
+ if (gmode_dim1 == EAS4_X0DX_G):
418
+ dim1_data = np.linspace(dim1_data[0],dim1_data[0]+dim1_data[1]*(ndim1-1), ndim1, dtype=np.float64)
419
+ gmode_dim1 = self.gmode_dim1 = EAS4_ALL_G
420
+ if (gmode_dim2 == EAS4_X0DX_G):
421
+ dim2_data = np.linspace(dim2_data[0],dim2_data[0]+dim2_data[1]*(ndim2-1), ndim2, dtype=np.float64)
422
+ gmode_dim2 = self.gmode_dim2 = EAS4_ALL_G
423
+ if (gmode_dim3 == EAS4_X0DX_G):
424
+ dim3_data = np.linspace(dim3_data[0],dim3_data[0]+dim3_data[1]*(ndim3-1), ndim3, dtype=np.float64)
425
+ gmode_dim3 = self.gmode_dim3 = EAS4_ALL_G
426
+
427
+ # ===
428
+
429
+ x = self.x = np.copy(dim1_data)
430
+ y = self.y = np.copy(dim2_data)
431
+ z = self.z = np.copy(dim3_data)
432
+
433
+ # ## bug check
434
+ # if (z.size > 1):
435
+ # if np.all(np.isclose(z,z[0],rtol=1e-12)):
436
+ # raise AssertionError('z has size > 1 but all grid coords are identical!')
437
+
438
+ if self.verbose: even_print('x_min', '%0.2f'%x.min())
439
+ if self.verbose: even_print('x_max', '%0.2f'%x.max())
440
+ if self.is_rectilinear:
441
+ if (self.nx>2):
442
+ if self.verbose: even_print('dx begin : end', '%0.3E : %0.3E'%( (x[1]-x[0]), (x[-1]-x[-2]) ))
443
+ if self.verbose: even_print('y_min', '%0.2f'%y.min())
444
+ if self.verbose: even_print('y_max', '%0.2f'%y.max())
445
+ if self.is_rectilinear:
446
+ if (self.ny>2):
447
+ if self.verbose: even_print('dy begin : end', '%0.3E : %0.3E'%( (y[1]-y[0]), (y[-1]-y[-2]) ))
448
+ if self.verbose: even_print('z_min', '%0.2f'%z.min())
449
+ if self.verbose: even_print('z_max', '%0.2f'%z.max())
450
+ if self.is_rectilinear:
451
+ if (self.nz>2):
452
+ if self.verbose: even_print('dz begin : end', '%0.3E : %0.3E'%( (z[1]-z[0]), (z[-1]-z[-2]) ))
453
+ if self.verbose: print(72*'-'+'\n')
454
+
455
+ # === time & scalar info
456
+
457
+ if self.verbose: print('time & scalar info\n'+72*'-')
458
+
459
+ n_scalars = self['Kennsatz/PARAMETER'].attrs['PARAMETER_SIZE'][0]
460
+
461
+ if ('Kennsatz/PARAMETER/PARAMETERS_ATTRS' in self):
462
+ scalars = [ s.decode('utf-8').strip() for s in self['Kennsatz/PARAMETER/PARAMETERS_ATTRS'][()] ]
463
+ else:
464
+ ## this is the older gen structure
465
+ scalars = [ self['Kennsatz/PARAMETER'].attrs['PARAMETERS_ATTR_%06d'%i][0].decode('utf-8').strip() for i in range(n_scalars) ]
466
+
467
+ scalar_n_map = dict(zip(scalars, range(n_scalars)))
468
+
469
+ self.scalars_dtypes = []
470
+ for scalar in scalars:
471
+ dset_path = 'Data/%s/ts_%06d/par_%06d'%(self.domainName,0,scalar_n_map[scalar])
472
+ if (dset_path in self):
473
+ self.scalars_dtypes.append(self[dset_path].dtype)
474
+ else:
475
+ #self.scalars_dtypes.append(np.float32)
476
+ raise AssertionError('dset not found: %s'%dset_path)
477
+
478
+ self.scalars_dtypes_dict = dict(zip(scalars, self.scalars_dtypes)) ## dict {<<scalar>>: <<dtype>>}
479
+
480
+ nt = self['Kennsatz/TIMESTEP'].attrs['TIMESTEP_SIZE'][0]
481
+ gmode_time = self['Kennsatz/TIMESTEP'].attrs['TIMESTEP_MODE'][0]
482
+
483
+ ## a baseflow will not have a TIMEGRID
484
+ if ('Kennsatz/TIMESTEP/TIMEGRID' in self):
485
+ t = self['Kennsatz/TIMESTEP/TIMEGRID'][:]
486
+ else:
487
+ t = np.array( [0.] , dtype=np.float64 )
488
+
489
+ if (gmode_time==EAS4_X0DX_G): ## =2 --> i.e. more than one timestep
490
+ t = np.linspace(t[0],t[0]+t[1]*(nt - 1), nt )
491
+ gmode_time = EAS4_ALL_G
492
+ else:
493
+ #print('gmode_time : '+str(gmode_time))
494
+ pass
495
+
496
+ if (t.size>1):
497
+ dt = t[1] - t[0]
498
+ duration = t[-1] - t[0]
499
+ else:
500
+ dt = 0.
501
+ duration = 0.
502
+
503
+ if self.verbose: even_print('nt', '%i'%nt )
504
+ if self.verbose: even_print('dt', '%0.6f'%dt)
505
+ if self.verbose: even_print('duration', '%0.2f'%duration )
506
+
507
+ # === attach to instance
508
+
509
+ self.n_scalars = n_scalars
510
+ self.scalars = scalars
511
+ self.scalar_n_map = scalar_n_map
512
+ self.t = t
513
+ self.dt = dt
514
+ self.nt = nt
515
+ self.duration = duration
516
+
517
+ self.ti = np.arange(self.nt, dtype=np.float64)
518
+
519
+ if self.verbose: print(72*'-'+'\n')
520
+
521
+ # === udef dictionary attached to instance
522
+
523
+ # udef_char = [ 'Ma', 'Re', 'Pr', 'kappa', 'R', 'p_inf', 'T_inf', 'C_Suth', 'S_Suth', 'mu_Suth_ref', 'T_Suth_ref' ]
524
+ # udef_real = [ self.Ma , self.Re , self.Pr , self.kappa, self.R, self.p_inf, self.T_inf, self.C_Suth, self.S_Suth, self.mu_Suth_ref, self.T_Suth_ref ]
525
+ # self.udef = dict(zip(udef_char, udef_real))
526
+
527
+ self.udef = { 'Ma':self.Ma,
528
+ 'Re':self.Re,
529
+ 'Pr':self.Pr,
530
+ 'kappa':self.kappa,
531
+ 'R':self.R,
532
+ 'p_inf':self.p_inf,
533
+ 'T_inf':self.T_inf,
534
+ #'C_Suth':self.C_Suth, ## technically a derived variable
535
+ 'S_Suth':self.S_Suth,
536
+ 'mu_Suth_ref':self.mu_Suth_ref,
537
+ 'T_Suth_ref':self.T_Suth_ref,
538
+ }
539
+
540
+ return
541
+
542
+ def get_mean(self, **kwargs):
543
+ '''
544
+ get spanwise mean of 2D EAS4 file
545
+ '''
546
+ axis = kwargs.get('axis',(2,)) ## default: avg over [z]
547
+
548
+ if (self.measType!='mean'):
549
+ raise NotImplementedError('get_mean() not yet valid for measType=\'%s\''%self.measType)
550
+
551
+ ## numpy structured array
552
+ data_mean = np.zeros(shape=(self.nx,self.ny), dtype={'names':self.scalars, 'formats':self.scalars_dtypes})
553
+
554
+ for si, scalar in enumerate(self.scalars):
555
+ scalar_dtype = self.scalars_dtypes[si]
556
+ dset_path = 'Data/%s/ts_%06d/par_%06d'%(self.domainName,0,self.scalar_n_map[scalar])
557
+ data = np.copy(self[dset_path][()])
558
+ ## perform np.mean() with float64 accumulator!
559
+ scalar_mean = np.mean(data, axis=axis, dtype=np.float64).astype(scalar_dtype)
560
+ data_mean[scalar] = scalar_mean
561
+
562
+ return data_mean
563
+
564
+ def make_xdmf(self, **kwargs):
565
+ print('make_xdmf() not yet implemented for turbx class EAS4')
566
+ raise NotImplementedError
567
+ return
@@ -0,0 +1,52 @@
1
+ import matplotlib.pyplot as plt
2
+ import numpy as np
3
+
4
+ # ======================================================================
5
+
6
+ def fig_ax_grid(
7
+ width_in=5.31,
8
+ aspect_fig=3.0,
9
+ sfac=1.0,
10
+ nx=1,
11
+ ny=1,
12
+ ax_aspect=1.5,
13
+ ax_x_in = 2.0,
14
+ x1in=0.52,
15
+ x2in=0.04,
16
+ y1in=0.10,
17
+ y2in=0.30,
18
+ center_x=False,
19
+ dpi=150):
20
+ '''
21
+ Figure & Axis maker for grid of axes
22
+ '''
23
+
24
+ figsize = (width_in*sfac, width_in*sfac/aspect_fig)
25
+ fig = plt.figure(figsize=figsize, dpi=dpi/sfac)
26
+ fig_x_in, fig_y_in = fig.get_size_inches()
27
+ np.testing.assert_allclose(fig_x_in/fig_y_in, aspect_fig, rtol=1e-5)
28
+
29
+ ## Scale by global scale factor
30
+ x1in *= sfac
31
+ x2in *= sfac
32
+ y1in *= sfac
33
+ y2in *= sfac
34
+ ax_x_in *= sfac
35
+
36
+ ax_y_in = ax_x_in/ax_aspect
37
+
38
+ ## Make axes
39
+ axs = []
40
+ for j in range(ny):
41
+ for i in range(nx):
42
+ if center_x:
43
+ ws_x_in = fig_x_in - nx*ax_x_in ## total [x] whitespace [in]
44
+ x0 = ( (i+1)*(ws_x_in/(nx+1)) + i*ax_x_in ) / fig_x_in
45
+ else:
46
+ x0 = x1in/fig_x_in + i*(1/nx) - i*x2in/fig_x_in
47
+ y0 = ( fig_y_in - (j+1)*ax_y_in - (j+1)*y1in - j*y2in ) / fig_y_in
48
+ dx = ax_x_in/fig_x_in
49
+ dy = ax_y_in/fig_y_in
50
+ axs.append( fig.add_axes([x0,y0,dx,dy]) )
51
+
52
+ return fig,axs