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/spd_wall_ci.py ADDED
@@ -0,0 +1,406 @@
1
+ import os
2
+ import sys
3
+ import timeit
4
+ import traceback
5
+ from pathlib import Path, PurePosixPath
6
+
7
+ import h5py
8
+ import numpy as np
9
+ from tqdm import tqdm
10
+
11
+ from .confidence_interval import calc_var_bmbc, confidence_interval_unbiased
12
+ from .h5 import h5_print_contents
13
+ from .utils import even_print, format_time_string
14
+
15
+ # ======================================================================
16
+
17
+ def _calc_mean_uncertainty_BMBC(self, **kwargs):
18
+ '''
19
+ calculate the uncertainty of the mean using the
20
+ "Batch Means and Batch Correlations" (BMBC) method outlined in
21
+ §4 of https://doi.org/10.1016/j.jcp.2017.07.005
22
+ '''
23
+
24
+ if (self.rank==0):
25
+ verbose = True
26
+ else:
27
+ verbose = False
28
+
29
+ if verbose: print('\n'+'spd.calc_mean_uncertainty_BMBC()'+'\n'+72*'-')
30
+ t_start_func = timeit.default_timer()
31
+
32
+ if not self.usingmpi:
33
+ raise NotImplementedError('function is not implemented for non-MPI usage')
34
+
35
+ h5py_is_mpi_build = h5py.h5.get_config().mpi
36
+ if not h5py_is_mpi_build:
37
+ if verbose: print('h5py was not compiled for parallel usage! exiting.')
38
+ sys.exit(1)
39
+
40
+ ri = kwargs.get('ri',1)
41
+ rj = kwargs.get('rj',1)
42
+ rt = kwargs.get('rt',1)
43
+
44
+ ## #n_threads = kwargs.get('n_threads',1)
45
+ ## try:
46
+ ## n_threads = int(os.environ.get('OMP_NUM_THREADS'))
47
+ ## except TypeError: ## not set
48
+ ## n_threads = os.cpu_count()
49
+
50
+ fn_h5_out = kwargs.get('fn_h5_out',None) ## filename for output HDF5 (.h5) file
51
+
52
+ confidence = kwargs.get('confidence',0.99)
53
+ M = kwargs.get('M',100) ## batch size
54
+
55
+ if not isinstance(M, (int,np.int32,np.int64)):
56
+ raise ValueError("'M' should be an int")
57
+ if (M < 1):
58
+ raise ValueError('M < 1')
59
+
60
+ if (self.nt%M!=0):
61
+ if verbose: print(f'nt = {self.nt:d}')
62
+ if verbose: print(f'M = {M:d}')
63
+ raise ValueError('nt%M!=0')
64
+
65
+ if not isinstance(confidence, (float,np.float32,np.float64)):
66
+ raise ValueError("'confidence' should be a float")
67
+ if (confidence <= 0.) or (confidence >= 1.):
68
+ raise ValueError('confidence should be between 0,1')
69
+
70
+ ## check data distribution
71
+ if (rj!=1):
72
+ raise AssertionError('rj!=1')
73
+ if (rt!=1):
74
+ raise AssertionError('rt!=1')
75
+ if (ri*rj*rt != self.n_ranks):
76
+ raise AssertionError('ri*rj*rt != self.n_ranks')
77
+ if (ri>self.ni):
78
+ raise AssertionError('ri>self.ni')
79
+ if (self.ni%ri!=0):
80
+ raise AssertionError('ni currently needs to be divisible by the n ranks')
81
+
82
+ ## distribute data over i/[x]
83
+ ril_ = np.array_split(np.arange(self.ni,dtype=np.int64),min(ri,self.ni))
84
+ ril = [[b[0],b[-1]+1] for b in ril_ ]
85
+ ri1,ri2 = ril[self.rank]
86
+ nir = ri2 - ri1
87
+
88
+ ## output filename : HDF5 (.h5)
89
+ if (fn_h5_out is None): ## automatically determine name
90
+ fname_path = os.path.dirname(self.fname)
91
+ fname_base = os.path.basename(self.fname)
92
+ fname_root, fname_ext = os.path.splitext(fname_base)
93
+ #fname_root = re.findall(r'io\S+_mpi_[0-9]+', fname_root)[0]
94
+ fname_out_h5_base = fname_root+'_uncertainty_bmbc.h5'
95
+ fn_h5_out = str(PurePosixPath(fname_path, fname_out_h5_base))
96
+ if (Path(fn_h5_out).suffix != '.h5'):
97
+ raise ValueError(f"fn_h5_out='{str(fn_h5_out)}' must end in .h5")
98
+ if os.path.isfile(fn_h5_out):
99
+ if (fn_h5_out == self.fname):
100
+ raise ValueError(f"fn_h5_out='{str(fn_h5_out)}' cannot be same as input filename.")
101
+
102
+ if verbose: even_print( 'fn_h5' , self.fname )
103
+ if verbose: even_print( 'fn_h5_out' , fn_h5_out )
104
+ if verbose: print(72*'-')
105
+ if verbose: even_print( 'ni' , f'{self.ni:d}' )
106
+ if verbose: even_print( 'nj' , f'{self.nj:d}' )
107
+ if verbose: even_print( 'nt' , f'{self.nt:d}' )
108
+ if verbose: print(72*'-')
109
+ if verbose: even_print('n ranks', f'{self.n_ranks:d}' )
110
+ #if verbose: even_print('n threads', f'{n_threads:d}' )
111
+ if verbose: print(72*'-')
112
+ if verbose: even_print('M', f'{M:d}' )
113
+ if verbose: even_print('confidence level', f'{confidence:0.4f}' )
114
+ if verbose: print(72*'-')
115
+ self.comm.Barrier()
116
+
117
+ # ===
118
+
119
+ ## the data dictionary to be pickled or written to .h5 later
120
+ data = {}
121
+
122
+ ## freestream data
123
+ data['lchar'] = self.lchar
124
+ data['U_inf'] = self.U_inf
125
+ data['rho_inf'] = self.rho_inf
126
+ data['T_inf'] = self.T_inf
127
+ data['mu_inf'] = self.mu_inf
128
+ data['p_inf'] = self.p_inf
129
+ data['Ma'] = self.Ma
130
+ data['Pr'] = self.Pr
131
+
132
+ ## read in 1D coordinate arrays & re-dimensionalize
133
+ x = np.copy( self['dims/x'][()] * self.lchar )
134
+ z = np.copy( self['dims/z'][()] * self.lchar )
135
+ t = np.copy( self['dims/t'][()] * self.tchar )
136
+ data['x'] = x
137
+ data['z'] = z
138
+ data['t'] = t
139
+
140
+ nx = self.ni
141
+ ni = self.ni
142
+ nz = self.nj
143
+ nj = self.nj
144
+ data['ni'] = ni
145
+ data['nx'] = nx
146
+ data['nj'] = nj
147
+ data['nz'] = nz
148
+
149
+ nt = self.nt
150
+ data['nt'] = nt
151
+
152
+ ## assert constant Δz
153
+ dz0 = np.diff(z)[0]
154
+ if not np.all(np.isclose(np.diff(z), dz0, rtol=1e-6)):
155
+ raise NotImplementedError('Δz not constant')
156
+
157
+ ## get Δt, dimensional [s]
158
+ if hasattr(self,'dt'):
159
+ dt = self.dt * self.tchar
160
+ np.testing.assert_allclose(dt, t[1]-t[0], rtol=1e-12, atol=1e-12)
161
+ else:
162
+ dt = t[1] - t[0] ## already dimensional
163
+
164
+ if hasattr(self,'duration'):
165
+ t_meas = self.duration * self.tchar
166
+ np.testing.assert_allclose(t_meas, t.max()-t.min(), rtol=1e-12, atol=1e-12)
167
+ else:
168
+ t_meas = t[-1] - t[0] ## already dimensional
169
+ self.duration = t_meas / self.tchar ## non-dimensionalize for attribute
170
+
171
+ zrange = z.max() - z.min()
172
+
173
+ data['t'] = t
174
+ data['dt'] = dt
175
+ data['dz'] = dz0
176
+ data['zrange'] = zrange
177
+
178
+ if verbose: even_print( 'Δt/tchar' , f'{dt/self.tchar:0.8f}' )
179
+ if verbose: even_print( 'Δt' , f'{dt:0.3e} [s]' )
180
+ if verbose: even_print( 'duration/tchar' , f'{self.duration:0.1f}' )
181
+ if verbose: even_print( 'duration' , f'{self.duration*self.tchar:0.3e} [s]' )
182
+ if verbose: print(72*'-')
183
+
184
+ ## report
185
+ if verbose:
186
+ even_print('Δt' , f'{dt :0.5e} [s]' )
187
+ even_print('t_meas' , f'{t_meas:0.5e} [s]' )
188
+ even_print('Δz' , f'{dz0 :0.5e} [m]' )
189
+ even_print('zrange' , f'{zrange:0.5e} [m]' )
190
+ print(72*'-')
191
+
192
+ # ==============================================================
193
+ # prepare buffers, maps, etc.
194
+ # ==============================================================
195
+
196
+ # n_lags = 2*nt-1
197
+
198
+ data['M'] = M
199
+ data['confidence'] = confidence
200
+
201
+ ## HARDCODE
202
+ scalars = [ 'tau_uy', 'u_tau', ]
203
+ #scalars = [ 'tau_uy', ]
204
+
205
+ K_full = nt // M
206
+
207
+ if (K_full<10):
208
+ raise ValueError('nt//M<10')
209
+
210
+ #Ks = np.arange(3,K_full+1,dtype=np.int64)
211
+ Ks = np.arange(10,K_full+1,dtype=np.int64)
212
+ #Ks = np.copy(Ks[-1:]) ## debug, take last N (full time series)
213
+ Ns = M * Ks
214
+ #if verbose: print(Ns)
215
+ nN = Ns.shape[0]
216
+
217
+ ## report
218
+ if verbose:
219
+ even_print('n time durations (N)' , f'{nN:d}' )
220
+ #print(72*'-')
221
+
222
+ data['Ks'] = Ks
223
+ data['Ns'] = Ns
224
+
225
+ ## rank-local buffers
226
+ data_mean = np.zeros(shape=(nir,nN) , dtype={'names':scalars , 'formats':[ np.float64 for sss in scalars ]})
227
+ data_ci = np.zeros(shape=(nir,nN,2) , dtype={'names':scalars , 'formats':[ np.float64 for sss in scalars ]})
228
+ data_Nsig2 = np.zeros(shape=(nir,nN,) , dtype={'names':scalars , 'formats':[ np.float64 for sss in scalars ]})
229
+ #data_acor = np.zeros(shape=(nir,n_lags) , dtype={'names':scalars , 'formats':[ np.float64 for sss in scalars ]})
230
+
231
+ # ==============================================================
232
+ # main loop
233
+ # ==============================================================
234
+
235
+ if verbose:
236
+ progress_bar = tqdm(
237
+ total=len(scalars)*nir,
238
+ ncols=100,
239
+ desc='BMBC',
240
+ leave=True,
241
+ file=sys.stdout,
242
+ mininterval=0.1,
243
+ smoothing=0.,
244
+ #bar_format="\033[B{l_bar}{bar}| {n}/{total} [{percentage:.1f}%] {elapsed}/{remaining}\033[A\n\b",
245
+ bar_format="{l_bar}{bar}| {n}/{total} [{percentage:.1f}%] {elapsed}/{remaining}",
246
+ ascii="░█",
247
+ colour='#FF6600',
248
+ )
249
+
250
+ for scalar in scalars:
251
+
252
+ dset = self[f'data/{scalar}']
253
+ if verbose: tqdm.write(72*'-')
254
+
255
+ for ii in range(ri1,ri2):
256
+
257
+ iii = ii - ri1
258
+
259
+ ## COLLECTIVE read
260
+ self.comm.Barrier()
261
+ t_start = timeit.default_timer()
262
+ if self.usingmpi:
263
+ with dset.collective:
264
+ dd = np.copy( dset[ii,:,:] )
265
+ else:
266
+ dd = np.copy( dset[ii,:,:] )
267
+ self.comm.Barrier()
268
+ t_delta = timeit.default_timer() - t_start
269
+ data_gb = self.n_ranks * 1 * self.nj * self.nt * dset.dtype.itemsize / 1024**3
270
+ if verbose:
271
+ tqdm.write(even_print(f'read: {scalar}', '%0.3f [GB] %0.3f [s] %0.3f [GB/s]'%(data_gb,t_delta,(data_gb/t_delta)), s=True))
272
+
273
+ ## assert shape [nz,nt]
274
+ if ( dd.shape != (nj,nt) ):
275
+ raise ValueError
276
+ print(f'rank {self.rank:d}: shape violation')
277
+ self.comm.Abort(1)
278
+
279
+ ## cast to double
280
+ dd = np.copy( dd.astype(np.float64) )
281
+
282
+ ## re-dimensionalize
283
+ if scalar in ['tau_uy','tau_vy','tau_wy',]:
284
+ dd *= self.rho_inf * self.U_inf**2
285
+ elif scalar in ['u_tau','v_tau','w_tau',]:
286
+ dd *= self.U_inf
287
+ elif scalar in ['T',]:
288
+ dd *= self.T_inf
289
+ elif scalar in ['rho',]:
290
+ dd *= self.rho_inf
291
+ elif scalar in ['p',]:
292
+ dd *= self.rho_inf * self.U_inf**2
293
+ else:
294
+ raise ValueError(f"condition needed for redimensionalizing '{scalar}'")
295
+
296
+ # ======================================================
297
+
298
+ for iN,N in enumerate(Ns):
299
+
300
+ ddN = np.copy( dd[:,:N] )
301
+
302
+ ## mean in [z], leave [t]
303
+ ddN = np.mean( ddN , axis=0 )
304
+
305
+ ## assert shape
306
+ if ( ddN.shape != (N,) ):
307
+ raise ValueError
308
+ print(f'rank {self.rank:d}: shape violation')
309
+ self.comm.Abort(1)
310
+
311
+ d_mean = np.mean( ddN , dtype=np.float64 ) ## [t] mean, was already avged over [z]
312
+ #dI = np.copy( ddN - d_mean )
313
+
314
+ data_mean[scalar][iii,iN] = d_mean
315
+
316
+ try:
317
+ #Nsig2_ , S1ovS0_ = calc_var_bmbc( dI , M=M )
318
+ Nsig2_ , S1ovS0_ = calc_var_bmbc( ddN , M=M )
319
+ except Exception as ee:
320
+ print(f"error occurred on rank {self.rank:d}: {ee}")
321
+ traceback.print_exc()
322
+ #print('exception_traceback : \n'+traceback.format_exc().rstrip())
323
+ self.comm.Abort(1)
324
+
325
+ data_Nsig2[scalar][iii,iN] = Nsig2_
326
+ data_ci[scalar][iii,iN,:] = confidence_interval_unbiased(mean=d_mean, N_sigma2=Nsig2_, N=N, confidence=confidence )
327
+
328
+ #self.comm.Barrier()
329
+ if verbose: progress_bar.update()
330
+ #break ## debug
331
+
332
+ self.comm.Barrier() ## per scalar Barrier
333
+
334
+ self.comm.Barrier() ## full loop Barrier
335
+ if verbose:
336
+ progress_bar.close()
337
+ print(72*'-')
338
+
339
+ # ==============================================================
340
+ # write HDF5 (.h5) file
341
+ # ==============================================================
342
+
343
+ ## open on rank 0 and write attributes, dimensions, etc.
344
+ if (self.rank==0):
345
+ with h5py.File(fn_h5_out, 'w') as hfw:
346
+
347
+ ## write floats,ints as top-level attributes
348
+ for key,val in data.items():
349
+ if isinstance(data[key], (int,np.int32,np.int64)):
350
+ hfw.attrs[key] = val
351
+ elif isinstance(data[key], (float,np.float32,np.float64)):
352
+ hfw.attrs[key] = val
353
+ elif isinstance(data[key], np.ndarray):
354
+ pass
355
+ else:
356
+ print(f'key {key} is type {str(type(data[key]))}')
357
+ self.comm.Abort(1)
358
+
359
+ ## write numpy arrays
360
+ hfw.create_dataset( 'dims/x' , data=x ) ## [m]
361
+ hfw.create_dataset( 'dims/z' , data=z ) ## [m]
362
+ hfw.create_dataset( 'dims/t' , data=t ) ## [s]
363
+
364
+ ## initialize datasets
365
+ for scalar in scalars:
366
+ hfw.create_dataset( f'mean/{scalar}' , shape=(ni,nN,) , dtype=np.float64, chunks=( min(nir,64),nN, ) )
367
+ hfw.create_dataset( f'Nsig2/{scalar}' , shape=(ni,nN,) , dtype=np.float64, chunks=( min(nir,64),nN, ) )
368
+ hfw.create_dataset( f'ci/{scalar}' , shape=(ni,nN,2,) , dtype=np.float64, chunks=( min(nir,64),nN,2 ) )
369
+
370
+ ## independently write data
371
+ hfw.create_dataset( 'Ks' , data=Ks )
372
+ hfw.create_dataset( 'Ns' , data=Ns )
373
+
374
+ self.comm.Barrier()
375
+
376
+ with h5py.File(fn_h5_out, 'a', driver='mpio', comm=self.comm) as hfw:
377
+
378
+ ## collectively write data
379
+ for scalar in scalars:
380
+
381
+ dset = hfw[f'mean/{scalar}']
382
+ with dset.collective:
383
+ dset[ri1:ri2,:] = data_mean[scalar][:,:]
384
+ dset = hfw[f'Nsig2/{scalar}']
385
+ with dset.collective:
386
+ dset[ri1:ri2,:] = data_Nsig2[scalar][:,:]
387
+ dset = hfw[f'ci/{scalar}']
388
+ with dset.collective:
389
+ dset[ri1:ri2,:,:] = data_ci[scalar][:,:,:]
390
+
391
+ ## report file size
392
+ if verbose:
393
+ even_print( os.path.basename(fn_h5_out), f'{(os.path.getsize(fn_h5_out)/1024**2):0.2f} [MB]' )
394
+ print(72*'-')
395
+
396
+ ## report file contents
397
+ self.comm.Barrier()
398
+ if (self.rank==0):
399
+ with h5py.File(fn_h5_out,'r') as hfr:
400
+ h5_print_contents(hfr)
401
+ self.comm.Barrier()
402
+
403
+ if verbose: print(72*'-')
404
+ if verbose: print('total time : spd.calc_mean_uncertainty_BMBC() : %s'%format_time_string((timeit.default_timer() - t_start_func)))
405
+ if verbose: print(72*'-')
406
+ return