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/__init__.py +52 -0
- turbx/bl.py +620 -0
- turbx/blasius.py +64 -0
- turbx/cli.py +19 -0
- turbx/composite_profile.py +243 -0
- turbx/confidence_interval.py +64 -0
- turbx/eas3.py +420 -0
- turbx/eas4.py +567 -0
- turbx/fig_ax_constructor.py +52 -0
- turbx/freestream_parameters.py +268 -0
- turbx/gradient.py +391 -0
- turbx/grid_metric.py +272 -0
- turbx/h5.py +236 -0
- turbx/mvp.py +385 -0
- turbx/rgd.py +2693 -0
- turbx/rgd_mean.py +523 -0
- turbx/rgd_testing.py +354 -0
- turbx/rgd_xpln_ccor.py +701 -0
- turbx/rgd_xpln_coh.py +992 -0
- turbx/rgd_xpln_mean_dim.py +336 -0
- turbx/rgd_xpln_spectrum.py +940 -0
- turbx/rgd_xpln_stats.py +738 -0
- turbx/rgd_xpln_turb_budget.py +1193 -0
- turbx/set_mpl_env.py +85 -0
- turbx/signal.py +277 -0
- turbx/spd.py +1206 -0
- turbx/spd_wall_ccor.py +629 -0
- turbx/spd_wall_ci.py +406 -0
- turbx/spd_wall_import.py +676 -0
- turbx/spd_wall_spectrum.py +638 -0
- turbx/spd_wall_stats.py +618 -0
- turbx/utils.py +84 -0
- turbx/ztmd.py +2224 -0
- turbx/ztmd_analysis.py +2337 -0
- turbx/ztmd_loader.py +56 -0
- turbx-1.0.2.dist-info/LICENSE +21 -0
- turbx-1.0.2.dist-info/METADATA +120 -0
- turbx-1.0.2.dist-info/RECORD +41 -0
- turbx-1.0.2.dist-info/WHEEL +5 -0
- turbx-1.0.2.dist-info/entry_points.txt +2 -0
- turbx-1.0.2.dist-info/top_level.txt +1 -0
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
|