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 ADDED
@@ -0,0 +1,52 @@
1
+ from .blasius import Blasius_solution
2
+ from .composite_profile import composite_profile_CMN2009
3
+ from .confidence_interval import calc_var_bmbc, confidence_interval_unbiased
4
+ from .eas3 import eas3
5
+ from .eas4 import eas4
6
+ from .fig_ax_constructor import fig_ax_grid
7
+ from .freestream_parameters import freestream_parameters
8
+ from .gradient import fd_coeff_calculator, gradient
9
+ from .h5 import h5_print_contents
10
+ from .mvp import (
11
+ asymptotic_H12_Retheta,
12
+ asymptotic_H32_Retheta,
13
+ comp_transform_GFM,
14
+ comp_transform_TL,
15
+ comp_transform_VD,
16
+ comp_transform_VIPL,
17
+ )
18
+ from .rgd import rgd
19
+ from .set_mpl_env import set_mpl_env
20
+ from .spd import spd
21
+ from .utils import even_print, format_nbytes, format_time_string, step
22
+ from .ztmd import ztmd
23
+ from .ztmd_loader import ztmd_loader
24
+
25
+ __all__ = [
26
+ 'Blasius_solution',
27
+ 'asymptotic_H12_Retheta',
28
+ 'asymptotic_H32_Retheta',
29
+ 'calc_var_bmbc',
30
+ 'comp_transform_GFM',
31
+ 'comp_transform_TL',
32
+ 'comp_transform_VD',
33
+ 'comp_transform_VIPL',
34
+ 'composite_profile_CMN2009',
35
+ 'confidence_interval_unbiased',
36
+ 'eas3',
37
+ 'eas4',
38
+ 'even_print',
39
+ 'fd_coeff_calculator',
40
+ 'fig_ax_grid',
41
+ 'format_nbytes',
42
+ 'format_time_string',
43
+ 'freestream_parameters',
44
+ 'gradient',
45
+ 'h5_print_contents',
46
+ 'rgd',
47
+ 'set_mpl_env',
48
+ 'spd',
49
+ 'step',
50
+ 'ztmd',
51
+ 'ztmd_loader',
52
+ ]
turbx/bl.py ADDED
@@ -0,0 +1,620 @@
1
+ import numpy as np
2
+ import scipy as sp
3
+ from scipy.integrate import cumulative_trapezoid, trapezoid
4
+ from scipy.interpolate import interp1d
5
+ from tqdm import tqdm
6
+
7
+ from .gradient import gradient
8
+
9
+ # ======================================================================
10
+
11
+ def _generic_bl_integral(y, integrand, y_edge=None, j_edge=None, **kwargs):
12
+ '''
13
+ Compute a definite boundary-layer integral from y=0 to 'edge'
14
+ -----
15
+ The integration limit is given by j_edge (on-grid) or y_edge
16
+ (off-grid, using interpolation of the cumulative integral)
17
+ '''
18
+
19
+ interp_kind = kwargs.get('interp_kind','cubic')
20
+
21
+ ## Check coordinate [y] and integrand
22
+ if y.ndim != 1 or integrand.ndim != 1:
23
+ raise ValueError
24
+ if y.shape[0] != integrand.shape[0]:
25
+ raise ValueError
26
+ if not np.all(np.diff(y) > 0): ## check (+) monotonicity
27
+ raise ValueError("y must be monotonically increasing")
28
+
29
+ ## Assert y[0]~0.
30
+ if y[0] < -1e-4:
31
+ raise ValueError("y[0] < 0")
32
+ if not np.isclose(y[0], 0., atol=1e-4):
33
+ raise ValueError("y[0] not sufficiently close to 0")
34
+
35
+ ## Check distance 'y_edge' and/or index 'j_edge'
36
+ if j_edge is not None and not isinstance(j_edge,(int,np.integer)):
37
+ raise ValueError("j_edge must be int")
38
+ if y_edge is not None:
39
+ y_edge = float(y_edge)
40
+ if (y_edge is not None) and (j_edge is not None):
41
+ if not np.isclose(y[j_edge], y_edge, rtol=1e-4):
42
+ raise ValueError("y_edge and j_edge inconsistent")
43
+ if y_edge is None and j_edge is None: ## No edge defined; integrate full profile
44
+ j_edge = -1
45
+
46
+ if j_edge is not None: ## Index-defined 'edge'
47
+ integral_val = trapezoid(
48
+ integrand[:j_edge+1],
49
+ x=y[:j_edge+1],
50
+ )
51
+ else: ## 'edge' distance supplied, perform 1D interpolation
52
+ integral_cum = cumulative_trapezoid(
53
+ integrand,
54
+ x=y,
55
+ initial=0.0,
56
+ )
57
+ integral_val = interp1d(
58
+ y,
59
+ integral_cum,
60
+ kind=interp_kind,
61
+ bounds_error=True,
62
+ )(y_edge)
63
+
64
+ return integral_val
65
+
66
+ def calc_d1(y, u, rho=None, y_edge=None, j_edge=None, **kwargs):
67
+ '''
68
+ Displacement (mass-flux deficit) thickness δ1 = δ*
69
+ -----
70
+ δ1 = ∫ ( 1 - ρu / (ρe·ue) ) dy
71
+ '''
72
+ interp_kind = kwargs.get('interp_kind','cubic')
73
+
74
+ if y_edge is None and j_edge is None:
75
+ j_edge = -1 ## integrate over complete profile
76
+
77
+ if rho is None:
78
+ rho = np.ones_like(u)
79
+
80
+ if j_edge is None:
81
+ u_e = interp1d(y, u, kind=interp_kind, bounds_error=True)(y_edge)
82
+ rho_e = interp1d(y, rho, kind=interp_kind, bounds_error=True)(y_edge)
83
+ else:
84
+ u_e = u[j_edge]
85
+ rho_e = rho[j_edge]
86
+
87
+ integrand = 1. - (rho*u)/(rho_e*u_e) ## δ1
88
+
89
+ return _generic_bl_integral(
90
+ y,
91
+ integrand,
92
+ y_edge=y_edge,
93
+ j_edge=j_edge,
94
+ interp_kind=interp_kind,
95
+ )
96
+
97
+ def calc_d2(y, u, rho=None, y_edge=None, j_edge=None, **kwargs):
98
+ '''
99
+ Momentum deficit thickness δ2 = θ
100
+ -----
101
+ δ2 = ∫ ( ρu / (ρe·ue) ) ( 1 - u / ue ) dy
102
+ '''
103
+ interp_kind = kwargs.get('interp_kind','cubic')
104
+
105
+ if y_edge is None and j_edge is None:
106
+ j_edge = -1 ## integrate over complete profile
107
+
108
+ if rho is None:
109
+ rho = np.ones_like(u)
110
+
111
+ if j_edge is None:
112
+ u_e = interp1d(y, u, kind=interp_kind, bounds_error=True)(y_edge)
113
+ rho_e = interp1d(y, rho, kind=interp_kind, bounds_error=True)(y_edge)
114
+ else:
115
+ u_e = u[j_edge]
116
+ rho_e = rho[j_edge]
117
+
118
+ integrand = (rho*u)/(rho_e*u_e) * (1. - u/u_e) ## δ2
119
+
120
+ return _generic_bl_integral(
121
+ y,
122
+ integrand,
123
+ y_edge=y_edge,
124
+ j_edge=j_edge,
125
+ interp_kind=interp_kind,
126
+ )
127
+
128
+ def calc_d3(y, u, rho=None, y_edge=None, j_edge=None, **kwargs):
129
+ '''
130
+ Kinetic energy deficit thickness δ3
131
+ -----
132
+ δ3 = ∫ ( ρu / (ρe·ue) ) ( 1 - (u / ue)^2 ) dy
133
+ '''
134
+ interp_kind = kwargs.get('interp_kind','cubic')
135
+
136
+ if y_edge is None and j_edge is None:
137
+ j_edge = -1 ## integrate over complete profile
138
+
139
+ if rho is None:
140
+ rho = np.ones_like(u)
141
+
142
+ if j_edge is None:
143
+ u_e = interp1d(y, u, kind=interp_kind, bounds_error=True)(y_edge)
144
+ rho_e = interp1d(y, rho, kind=interp_kind, bounds_error=True)(y_edge)
145
+ else:
146
+ u_e = u[j_edge]
147
+ rho_e = rho[j_edge]
148
+
149
+ integrand = (rho*u)/(rho_e*u_e) * (1. - (u/u_e)**2) ## δ3
150
+
151
+ return _generic_bl_integral(
152
+ y,
153
+ integrand,
154
+ y_edge=y_edge,
155
+ j_edge=j_edge,
156
+ interp_kind=interp_kind,
157
+ )
158
+
159
+ def calc_dRC(y, u, u_tau, rho=None, rho_wall=None, y_edge=None, j_edge=None, **kwargs):
160
+ '''
161
+ Rotta-Clauser Δ = ∫W+dy = ∫(ρe+ue+ - ρ+u+) dy
162
+ -----
163
+ W = (ρe·ue - ρ·u)/(ρe·ue) = 1 - (ρu/(ρe·ue))
164
+ W+ = (ρe·ue - ρ·u)/(ρw·uτ) = ρe+ue+ - ρ+u+
165
+ '''
166
+ interp_kind = kwargs.get('interp_kind','cubic')
167
+
168
+ if y_edge is None and j_edge is None:
169
+ j_edge = -1 ## integrate over complete profile
170
+
171
+ if rho is None:
172
+ if rho_wall is not None:
173
+ raise ValueError("rho_wall provided but rho is None")
174
+ rho = np.ones_like(u)
175
+
176
+ if rho_wall is None:
177
+ rho_wall = rho[0]
178
+ else:
179
+ if not np.isclose(rho[0], rho_wall, rtol=1e-4):
180
+ raise ValueError("rho_wall inconsistent with rho[0]")
181
+
182
+ if j_edge is None:
183
+ u_e = interp1d(y, u, bounds_error=True)(y_edge)
184
+ rho_e = interp1d(y, rho, bounds_error=True)(y_edge)
185
+ else:
186
+ u_e = u[j_edge]
187
+ rho_e = rho[j_edge]
188
+
189
+ integrand = (rho_e*u_e - rho*u) / (rho_wall*u_tau) ## W+
190
+
191
+ ## Returned value is units of input [y]
192
+ return _generic_bl_integral(
193
+ y,
194
+ integrand,
195
+ y_edge=y_edge,
196
+ j_edge=j_edge,
197
+ interp_kind=interp_kind,
198
+ )
199
+
200
+ # ======================================================================
201
+
202
+ def calc_profile_edge_1d(y, u=None, ddy_u=None, **kwargs):
203
+ '''
204
+ Determine the edge location of a 1D boundary-layer profile
205
+
206
+ The edge is defined as the first location where |du+/dy+|<ϵ
207
+
208
+ Parameters
209
+ ----------
210
+ y : np.ndarray
211
+ 1D wall-normal coordinate (units arbitrary)
212
+ u : np.ndarray, optional
213
+ Profile used to compute du/dy internally
214
+ ddy_u : np.ndarray, optional
215
+ Precomputed du/dy. Exactly one of `u` or `ddy_u` must be provided
216
+
217
+ Keyword Arguments
218
+ -----------------
219
+ epsilon : float, default=5e-5
220
+ Threshold ϵ for |du+/dy+|
221
+ acc : int, default=6
222
+ Accuracy order for numerical differentiation
223
+ edge_stencil : str, default='half'
224
+ Gradient stencil near boundaries
225
+ interp_kind : {'linear','cubic'}, default='cubic'
226
+ Interpolation method when `ongrid=False`
227
+ ongrid : bool, default=True
228
+ Snap to first grid point if True; otherwise interpolate
229
+
230
+ Returns
231
+ -------
232
+ y_edge : float
233
+ Wall-normal location of the boundary-layer edge
234
+
235
+ Notes
236
+ -----
237
+ - Assumes |du+/dy+| at y=0 is O(1)
238
+ - ϵ is adaptively increased if no crossing is found
239
+ - Deprecated kwargs (`lchar`, `deriv`) are rejected
240
+ '''
241
+
242
+ epsilon = kwargs.get('epsilon',5e-5)
243
+ acc = kwargs.get('acc',6)
244
+ edge_stencil = kwargs.get('edge_stencil','half')
245
+ interp_kind = kwargs.get('interp_kind','cubic')
246
+ ongrid = kwargs.get('ongrid',True) ## Snap to grid point
247
+
248
+ ## These options were deprecated -- check that they are not passed
249
+ lchar = kwargs.get('lchar',None)
250
+ if (lchar is not None):
251
+ raise ValueError('lchar was deprecated as kwarg for turbx.calc_profile_edge_1d()')
252
+ deriv = kwargs.get('deriv',None)
253
+ if (deriv is not None):
254
+ raise ValueError('deriv was deprecated as kwarg for turbx.calc_profile_edge_1d()')
255
+
256
+ # do_debug_plot = kwargs.get('do_debug_plot',False) ## show a debug plot for tuning ϵ
257
+
258
+ ## Checks
259
+ if not isinstance(y, np.ndarray):
260
+ raise ValueError('y should be a numpy array')
261
+ if (y.ndim!=1):
262
+ raise ValueError
263
+
264
+ ## Make sure exactly one of u OR ddy_u was passed
265
+ if (u is None) and (ddy_u is None):
266
+ raise ValueError('pass one of either u or ddy_u')
267
+ if (u is not None) and (ddy_u is not None):
268
+ raise ValueError('either pass u or ddy_u (not both)')
269
+
270
+ ## Make sure dims of passed u | ddy_u are correct
271
+ if (u is not None):
272
+ if not isinstance(u, np.ndarray):
273
+ raise ValueError
274
+ if (u.ndim!=1):
275
+ raise ValueError
276
+ if (u.shape[0]!=y.shape[0]):
277
+ raise ValueError
278
+ if (ddy_u is not None):
279
+ if not isinstance(ddy_u, np.ndarray):
280
+ raise ValueError
281
+ if (ddy_u.ndim!=1):
282
+ raise ValueError
283
+ if (ddy_u.shape[0]!=y.shape[0]):
284
+ raise ValueError
285
+
286
+ if not any([(interp_kind=='linear'),(interp_kind=='cubic')]):
287
+ raise ValueError("'interp_kind' should be one of: 'linear','cubic'")
288
+
289
+ ny = y.shape[0]
290
+
291
+ ## ddy_u was NOT passed, need to calculate du/dy here
292
+ if (ddy_u is None):
293
+ ddy_u = gradient(u, y, axis=0, d=1, acc=acc, edge_stencil=edge_stencil)
294
+
295
+ ## Confirm that |d□/dy|_w is ~O(1)
296
+ np.testing.assert_allclose( ddy_u[0], 1., rtol=0.05) ## 5% rtol
297
+
298
+ ## Static ϵ
299
+ # ## get [y] of first intersection
300
+ # jtop = ny-1
301
+ # for j in range(ny):
302
+ # if ( np.abs(ddy_u[j]) < epsilon ): ## |d□/dy|<ϵ
303
+ # jtop = j
304
+ # break
305
+
306
+ ## Variable ϵ
307
+ ## Get [y] of first intersection
308
+ keep_going = True
309
+ while keep_going:
310
+ jtop = ny-1
311
+ for j in range(ny):
312
+ if ( np.abs(ddy_u[j]) < epsilon ): ## |d□/dy|<ϵ
313
+ jtop = j
314
+ break
315
+ if (jtop == ny-1): ## Got to end, recalibrate & keep going
316
+ epsilon *= 1.05
317
+ tqdm.write(f'recalibrating: ϵ={epsilon:0.3e}')
318
+ else:
319
+ keep_going = False
320
+
321
+ if (jtop == ny-1):
322
+ raise ValueError('jtop == ny-1')
323
+ if (jtop<3):
324
+ raise ValueError('jtop<3')
325
+
326
+ if ongrid: ## Grid-snapped, i.e. first point satisfying |d□/dy|<ϵ
327
+ y_edge = float( y[jtop] )
328
+
329
+ else: ## Find interpolated |d□/dy|==ϵ
330
+
331
+ #intrp_func = interp1d(y, ddy_u, kind=interp_kind, bounds_error=True) ## deprecated!
332
+
333
+ if (interp_kind=='cubic'):
334
+
335
+ #intrp_func = sp.interpolate.CubicSpline(y, ddy_u, axis=0, bc_type='natural', extrapolate=False)
336
+ intrp_func = sp.interpolate.PchipInterpolator(y, ddy_u, axis=0, extrapolate=False)
337
+ #intrp_func = sp.interpolate.Akima1DInterpolator(y, ddy_u, axis=0, method='akima', extrapolate=False)
338
+
339
+ elif (interp_kind=='linear'):
340
+ raise NotImplementedError
341
+ ## need to use np.interp() --> no longer retuns a function! needs to be wrapped.
342
+ else:
343
+ raise NotImplementedError
344
+
345
+ def __f_opt_edge_locator(y_test, intrp_func, epsilon):
346
+ ddy_u_test = intrp_func(y_test)
347
+ root = np.abs( ddy_u_test ) - epsilon
348
+ return root
349
+
350
+ sol = sp.optimize.least_squares(
351
+ fun=__f_opt_edge_locator,
352
+ args=(intrp_func,epsilon,),
353
+ x0 = y[0] + 0.99*(y[jtop]-y[0]),
354
+ #xtol=1e-15,
355
+ #ftol=1e-15,
356
+ #gtol=1e-15,
357
+ method='dogbox',
358
+ #method='trf',
359
+ #bounds=(y[jtop-2], y[jtop]),
360
+ bounds=(y[0], y[jtop]),
361
+ )
362
+ if not sol.success:
363
+ raise ValueError
364
+ if ( sol.x.shape[0] != 1 ):
365
+ raise ValueError
366
+
367
+ y_edge = float(sol.x[0])
368
+
369
+ if ( y_edge > y[jtop] ):
370
+ raise ValueError
371
+
372
+ # if do_debug_plot:
373
+ # plt.close('all')
374
+ # fig1 = plt.figure(figsize=(2*(16/9),2), dpi=300)
375
+ # ax1 = plt.gca()
376
+ # ax1.set_xscale('log', base=10)
377
+ # ax1.plot(
378
+ # ddy_u,
379
+ # y,
380
+ # lw=0.8,
381
+ # )
382
+ # #ax1.set_xlim(1e-30, 1e1)
383
+ # ax1.axhline(y=y_edge, linestyle='dashed', c='gray', zorder=1, lw=0.5)
384
+ # ax1.axvline(x=epsilon, linestyle='dashed', c='gray', zorder=1, lw=0.5)
385
+ # fig1.tight_layout(pad=0.25)
386
+ # fig1.tight_layout(pad=0.25)
387
+ # plt.show()
388
+
389
+ return y_edge
390
+
391
+ def calc_d99_1d(y, u, y_edge, u_edge=None, **kwargs):
392
+ '''
393
+ determine δ99 location of 1D profile
394
+ - [y] is a 1D coordinate vector
395
+ - [u] is some profile variable (streamwise velocity, pseudovelocity, etc.)
396
+ - [y_edge] is the edge location
397
+ - [u_edge] (optional) is the variable value at the edge location
398
+ '''
399
+
400
+ rtol = kwargs.get('rtol',1e-3) ## rtol for asserting u[y_edge]==u_edge
401
+ interp_kind = kwargs.get('interp_kind','cubic')
402
+ d95 = kwargs.get('d95',False) ## δ95 instead of δ99
403
+
404
+ if (y.ndim!=1):
405
+ raise ValueError
406
+ if (u.ndim!=1):
407
+ raise ValueError
408
+ if (u.shape[0]!=y.shape[0]):
409
+ raise ValueError
410
+ if not isinstance(y_edge, (float,np.floating)):
411
+ raise ValueError
412
+ if (u_edge is not None):
413
+ if not isinstance(u_edge, (float,np.floating)):
414
+ raise ValueError
415
+
416
+ if (y_edge>y.max()):
417
+ raise ValueError
418
+ if (y_edge<y.min()):
419
+ raise ValueError
420
+
421
+ ny = y.shape[0]
422
+
423
+ j_edge = np.abs(y-y_edge).argmin()
424
+ if ( y[j_edge] < y_edge ):
425
+ j_edge += 1
426
+ je = min(j_edge,ny)
427
+ je += 1 ## for numpy [:n]
428
+
429
+ intrp_func = interp1d(x=y[:je], y=u[:je], kind=interp_kind, bounds_error=True)
430
+
431
+ if (u_edge is not None): ## assert passed u_edge is == f(y_edge)
432
+ np.testing.assert_allclose(u_edge, intrp_func(y_edge), rtol=rtol)
433
+ else:
434
+ u_edge = intrp_func(y_edge)
435
+
436
+ def __f_opt_d99_locator(y_test, intrp_func, u_edge):
437
+ root = np.abs( 0.99*u_edge - intrp_func(y_test) )
438
+ return root
439
+
440
+ def __f_opt_d95_locator(y_test, intrp_func, u_edge):
441
+ root = np.abs( 0.95*u_edge - intrp_func(y_test) )
442
+ return root
443
+
444
+ if d95:
445
+ __f_opt = __f_opt_d95_locator
446
+ else:
447
+ __f_opt = __f_opt_d99_locator
448
+
449
+ sol = sp.optimize.least_squares(
450
+ fun=__f_opt,
451
+ args=(intrp_func,u_edge),
452
+ x0=0.1*y_edge,
453
+ xtol=1e-15,
454
+ ftol=1e-15,
455
+ gtol=1e-15,
456
+ method='dogbox',
457
+ bounds=(y.min(), y_edge),
458
+ )
459
+ if not sol.success:
460
+ raise ValueError
461
+ if ( sol.x.shape[0] != 1 ):
462
+ raise ValueError
463
+
464
+ delta = float(sol.x[0]) ## δ99 or δ95
465
+
466
+ if ( delta > y_edge ):
467
+ raise ValueError
468
+
469
+ return delta
470
+
471
+ def calc_bl_integral_quantities_1d(
472
+ y,
473
+ u,
474
+ rho,
475
+ u_tau,
476
+ d99,
477
+ y_edge,
478
+ rho_edge,
479
+ nu_edge,
480
+ u_edge,
481
+ rho_wall,
482
+ nu_wall,
483
+ **kwargs,
484
+ ):
485
+ '''
486
+ For 1D profile, get [δ1=δ*, δ2=θ, Reθ, Reτ] etc.
487
+ '''
488
+
489
+ rtol = kwargs.get('rtol',1e-6)
490
+ interp_kind = kwargs.get('interp_kind','cubic')
491
+
492
+ ## Checks
493
+ if y.ndim != 1:
494
+ raise ValueError
495
+ if u.ndim != 1 or u.shape[0] != y.shape[0]:
496
+ raise ValueError
497
+ if rho.ndim != 1 or rho.shape[0] != y.shape[0]:
498
+ raise ValueError
499
+
500
+ for val in (u_tau, d99, y_edge, rho_edge, nu_edge, u_edge, rho_wall, nu_wall):
501
+ if not isinstance(val, (float, np.floating)):
502
+ raise ValueError
503
+
504
+ if (y_edge > y.max()) or (y_edge < y.min()):
505
+ raise ValueError
506
+
507
+ ## Consistency checks for edge quantities
508
+ rho_edge_ = interp1d(y, rho, kind=interp_kind, bounds_error=True)(y_edge)
509
+ np.testing.assert_allclose(rho_edge, rho_edge_, rtol=rtol)
510
+
511
+ u_edge_ = interp1d(y, u, kind=interp_kind, bounds_error=True)(y_edge)
512
+ np.testing.assert_allclose(u_edge, u_edge_, rtol=rtol)
513
+
514
+ # === Compressible integrals (root names)
515
+
516
+ d1 = calc_d1(
517
+ y, u,
518
+ rho=rho,
519
+ y_edge=y_edge,
520
+ interp_kind=interp_kind,
521
+ )
522
+ dstar = d1
523
+
524
+ d2 = calc_d2(
525
+ y, u,
526
+ rho=rho,
527
+ y_edge=y_edge,
528
+ interp_kind=interp_kind,
529
+ )
530
+ theta = d2
531
+
532
+ d3 = calc_d3(
533
+ y, u,
534
+ rho=rho,
535
+ y_edge=y_edge,
536
+ interp_kind=interp_kind,
537
+ )
538
+
539
+ dRC = calc_dRC(
540
+ y, u,
541
+ u_tau=u_tau,
542
+ rho=rho,
543
+ rho_wall=rho_wall,
544
+ y_edge=y_edge,
545
+ interp_kind=interp_kind,
546
+ )
547
+
548
+ # === Kinetic (u-only) integrals
549
+
550
+ d1_k = calc_d1(
551
+ y, u,
552
+ rho=None,
553
+ y_edge=y_edge,
554
+ interp_kind=interp_kind,
555
+ )
556
+ dstar_k = d1_k
557
+
558
+ d2_k = calc_d2(
559
+ y, u,
560
+ rho=None,
561
+ y_edge=y_edge,
562
+ interp_kind=interp_kind,
563
+ )
564
+ theta_k = d2_k
565
+
566
+ d3_k = calc_d3(
567
+ y, u,
568
+ rho=None,
569
+ y_edge=y_edge,
570
+ interp_kind=interp_kind,
571
+ )
572
+
573
+ dRC_k = calc_dRC(
574
+ y, u,
575
+ u_tau=u_tau,
576
+ rho=None,
577
+ rho_wall=None,
578
+ y_edge=y_edge,
579
+ interp_kind=interp_kind,
580
+ )
581
+
582
+ ## Shape factors
583
+ H12 = d1 / d2
584
+ H12_k = d1_k / d2_k
585
+ H32 = d3 / d2
586
+ H32_k = d3_k / d2_k
587
+
588
+ ## Reynolds numbers
589
+ Re_tau = d99 * u_tau / nu_wall
590
+ Re_theta = theta * u_edge / nu_edge
591
+ Re_d99 = d99 * u_edge / nu_edge
592
+
593
+ dd = {
594
+ 'd1': d1,
595
+ 'd1_k': d1_k,
596
+ 'dstar': dstar,
597
+ 'dstar_k': dstar_k,
598
+
599
+ 'd2': d2,
600
+ 'd2_k': d2_k,
601
+ 'theta': theta,
602
+ 'theta_k': theta_k,
603
+
604
+ 'd3': d3,
605
+ 'd3_k': d3_k,
606
+
607
+ 'dRC': dRC,
608
+ 'dRC_k': dRC_k,
609
+
610
+ 'H12': H12,
611
+ 'H12_k': H12_k,
612
+ 'H32': H32,
613
+ 'H32_k': H32_k,
614
+
615
+ 'Re_tau': Re_tau,
616
+ 'Re_theta': Re_theta,
617
+ 'Re_d99': Re_d99,
618
+ }
619
+
620
+ return dd