Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Download

Turbulence-Resolving Integral Simulations (TRIS) applies a moment-of-momentum integral approach, derived from Navier-Stokes, to run the time evolution of the integrated velocity and pressure fields. The Python code here provides an animation of a chosen field depending on the user input.

71 views
unlisted
ubuntu2204
1
from periodic2d import *
2
import subprocess
3
import glob
4
import scipy.special as spec
5
from PIL import Image
6
import glob
7
8
plt.rcParams.update({'lines.linewidth': 1, 'lines.markersize': 6,'lines.markeredgewidth': 0.2})
9
plt.rcParams.update({'font.size': 12, 'legend.fontsize': 8})
10
plt.rc('font', family='serif')
11
plt.rc('text',usetex=True)
12
13
# sine integral function
14
def Si(x):
15
return spec.sici(x)[0]
16
17
def Ci(x):
18
return spec.sici(x)[1]
19
20
# input parameters
21
class Params:
22
"""Data structure for storing and passing input parameters for TRIS closures."""
23
24
def __init__(self,kappa=0.41,B=5.2,Re=300.,Pi_ref=0.15,Cuv=0.6,Cs0=0.04,Cs1=0.04,Cp=6.0,Cn=0.0,n=0.0,Ctau=1.0,CPi=1.0,linearize=True,flow_angle=0.):
25
self.kappa = kappa
26
self.B = B
27
self.Re = Re
28
self.Pi_ref = Pi_ref
29
self.Cuv = Cuv
30
self.Cs0 = Cs0
31
self.Cs1 = Cs1
32
self.Cp = Cp
33
self.Cn = Cn
34
self.n = n
35
self.Ctau = Ctau
36
self.CPi = CPi
37
self.linearize = linearize
38
self.flow_angle = flow_angle
39
40
class Animation:
41
"""Data structure for creating an animation of a given quantity."""
42
43
def __init__(self, vname, vdir='animation', fmt='png', dpi=300):
44
self.name = vname
45
self.dir = vdir
46
self.fmt = fmt
47
self.dpi = dpi
48
self.filename = self.dir+'vid_'+self.name
49
self.N = 0
50
51
def update(self, i, field):
52
Lx = field.msh.Lx
53
Lz = field.msh.Lz
54
colormap = plt.get_cmap('RdBu_r')
55
plt.figure(1, figsize=(6,1.5))
56
im = plt.imshow((field.val.T - np.mean(field.val.T))/np.std(field.val.T), interpolation='bicubic', origin='lower',\
57
extent=(0,Lx,0,Lz), cmap=colormap, vmin=-3, vmax=3)
58
cb = plt.colorbar(im)
59
cb.set_ticks(np.linspace(-3, 3, num=3)) # Adjust number of ticks
60
plt.xticks(np.linspace(0, Lx, 5), [r"$"+pi_formatter(val, None)+r"$" for val in np.linspace(0, Lx, 5)],fontsize=12)
61
plt.yticks(np.linspace(0, Lz, 4), [r"$"+pi_formatter(val, None)+r"$" for val in np.linspace(0, Lz, 4)],fontsize=12)
62
if self.name == 'u0':
63
plt.title(r'$\left< \widetilde{u} \right>_0^s$')
64
if self.name == 'u1':
65
plt.title(r'$\left< \widetilde{u} \right>_1^s$')
66
if self.name == 'w0':
67
plt.title(r'$\left< \widetilde{w} \right>_0^s$')
68
if self.name == 'w1':
69
plt.title(r'$\left< \widetilde{w} \right>_1^s$')
70
if self.name == 'v0':
71
plt.title(r'$\left< \widetilde{v} \right>_0^s$')
72
if self.name == 'p0':
73
plt.title(r'$\left< \widetilde{p} \right>_0^s$')
74
if self.name == 'p0':
75
plt.title(r'$\left< \widetilde{p} \right>_1^s$')
76
plt.savefig(self.filename+'_'+str(i)+'.'+self.fmt, format=self.fmt, bbox_inches='tight', dpi=self.dpi)
77
plt.close(1)
78
self.N += 1
79
80
def compile(self,animation_variable):
81
image_files = sorted(glob.glob('animation/*.png'))
82
images = [Image.open(image) for image in image_files]
83
84
# Save as GIF
85
gif_path = 'animation/'+animation_variable+'.gif'
86
images[0].save(
87
gif_path,
88
save_all=True,
89
append_images=images[1:],
90
duration=100,
91
loop=0)
92
93
cmd = ['rm']
94
files = glob.glob(self.filename+'_*.'+self.fmt)
95
subprocess.run(cmd+files)
96
97
def calc_v0(u1, w1):
98
return div(u1, w1).smult(0.5)
99
100
def calc_rhs(u0, w0, u1, w1, params, verbose=False):
101
## 0. calculate v0 field
102
v0 = calc_v0(u1, w1)
103
104
## obtain u,v,w fields in physical space
105
for field in [u0, w0, u1, w1, v0]:
106
field.ifft()
107
108
## 1. calculate assumed profile parameters from moments
109
(Re, thRe, Pi, thPi) = profile_params(u0.val, w0.val, u1.val, w1.val, params)
110
111
## 2. closure models for nonlinear flux terms
112
(r0xx,r0zz,r0xz,r1xx,r1zz,r1xz) = structural_model(Re, thRe, Pi, thPi, params)
113
# place it back on the mesh
114
uu0 = Var(u0.msh, val=r0xx)
115
uw0 = Var(u0.msh, val=r0xz)
116
ww0 = Var(w0.msh, val=r0zz)
117
uu1 = Var(u1.msh, val=r1xx)
118
uw1 = Var(u1.msh, val=r1xz)
119
ww1 = Var(w1.msh, val=r1zz)
120
#
121
for field in [uu0, uw0, ww0, uu1, uw1, ww1]:
122
field.fft()
123
# negative divergence on RHS
124
rhs_u0 = div(uu0, uw0).smult(-1.)
125
rhs_w0 = div(uw0, ww0).smult(-1.)
126
rhs_p0 = div(rhs_u0, rhs_w0)
127
rhs_u1 = div(uu1, uw1).smult(-1.)
128
rhs_w1 = div(uw1, ww1).smult(-1.)
129
rhs_p1 = div(rhs_u1, rhs_w1)
130
131
## 3. calculate resolved wall-normal momentum fluxes
132
uv0 = u0.mult(v0)
133
wv0 = w0.mult(v0)
134
for field in [uv0, wv0]:
135
field.fft()
136
rhs_u1 = rhs_u1.add(uv0.smult(2.))
137
rhs_w1 = rhs_w1.add(wv0.smult(2.))
138
rhs_p1 = rhs_p1.add(div(uv0,wv0).smult(4.))
139
140
## 4. calculate wall shear stress model
141
(taux, tauz, ztaux, ztauz) = stress_model(Re, thRe, Pi, thPi, params)
142
# put it back on the mesh
143
tx = Var(u0.msh, val=taux)
144
tz = Var(w0.msh, val=tauz)
145
ztx = Var(u0.msh, val=ztaux)
146
ztz = Var(w0.msh, val=ztauz)
147
148
# return to wavenumber space
149
for field in [tx, tz, ztx, ztz]:
150
field.fft()
151
# subtract from RHS
152
rhs_u0 = rhs_u0.add(tx.smult(-1.))
153
rhs_w0 = rhs_w0.add(tz.smult(-1.))
154
rhs_p0 = rhs_p0.add(div(tx, tz).smult(-1.))
155
# integral of the viscous and unresolved turbulent stress
156
rhs_u1 = rhs_u1.add(ztx.smult(-1.))
157
rhs_w1 = rhs_w1.add(ztz.smult(-1.))
158
rhs_p1 = rhs_p1.add(div(ztx, ztz).smult(-2.))
159
160
## 4b. calculate viscous stress integral
161
(utopx, utopz) = top_velocity_model(Re, thRe, Pi, thPi, params)
162
visc_x = Var(u0.msh, val=-2.*utopx/params.Re)
163
visc_z = Var(u0.msh, val=-2.*utopz/params.Re)
164
for field in [visc_x, visc_z]:
165
field.fft()
166
rhs_u1 = rhs_u1.add(visc_x)
167
rhs_w1 = rhs_w1.add(visc_z)
168
169
## 5. add body force driving the flow
170
rhs_u0 = rhs_u0.sadd(np.cos(params.flow_angle))
171
rhs_u1 = rhs_u1.sadd(np.cos(params.flow_angle))
172
rhs_w0 = rhs_w0.sadd(np.sin(params.flow_angle))
173
rhs_w1 = rhs_w1.sadd(np.sin(params.flow_angle))
174
175
## 6. wall-parallel eddy viscosity
176
(S0xx, S0zz, S0xz, S02) = strain_rate(u0, w0)
177
(S1xx, S1zz, S1xz, S12) = strain_rate(u1, w1)
178
if params.linearize:
179
Lx = u0.msh.Lx
180
Lz = u0.msh.Lz
181
nx = u0.msh.nx
182
nz = u0.msh.nz
183
dx = Lx/nx
184
dz = Lz/nz
185
Smag = S02.sqrt()
186
nuT0 = Smag.smult(params.Cs0).smult(dx).smult(dz)
187
Smag = S12.sqrt()
188
nuT1 = Smag.smult(params.Cs1).smult(dx).smult(dz)
189
else:
190
nuT0 = eddy_viscosity_model(S02, S12, params)
191
nuT1 = nuT0
192
tau0xx = S0xx.mult(nuT0)
193
tau0zz = S0zz.mult(nuT0)
194
tau0xz = S0xz.mult(nuT0)
195
tau1xx = S1xx.mult(nuT1)
196
tau1zz = S1zz.mult(nuT1)
197
tau1xz = S1xz.mult(nuT1)
198
f0x = div(tau0xx, tau0xz)
199
f0z = div(tau0xz, tau0zz)
200
f1x = div(tau1xx, tau1xz)
201
f1z = div(tau1xz, tau1zz)
202
rhs_u0 = rhs_u0.add(f0x)
203
rhs_w0 = rhs_w0.add(f0z)
204
rhs_u1 = rhs_u1.add(f1x)
205
rhs_w1 = rhs_w1.add(f1z)
206
rhs_p0 = rhs_p0.add(div(f0x, f0z))
207
rhs_p1 = rhs_p1.add(div(f1x, f1z))
208
209
nu_eff = (1./params.Re) # molecular viscosity
210
rhs_u0 = rhs_u0.add(u0.laplacian().smult(nu_eff))
211
rhs_w0 = rhs_w0.add(w0.laplacian().smult(nu_eff))
212
rhs_u1 = rhs_u1.add(u1.laplacian().smult(nu_eff))
213
rhs_w1 = rhs_w1.add(w1.laplacian().smult(nu_eff))
214
215
# project RHS to make divergence-free
216
p0 = calc_p0(rhs_p0)
217
rhs_u0 = rhs_u0.add(p0.diff_x().smult(-1.))
218
rhs_w0 = rhs_w0.add(p0.diff_z().smult(-1.))
219
p1 = new_calc_p1(rhs_p1, p0, params)
220
rhs_u1 = rhs_u1.add(p1.diff_x().smult(-1.))
221
rhs_w1 = rhs_w1.add(p1.diff_z().smult(-1.))
222
223
return (rhs_u0, rhs_w0, rhs_u1, rhs_w1, p0, p1)
224
225
def profile_params(U0, W0, U1, W1, params, verbose=False):
226
# unpack model parameters
227
kappa = params.kappa
228
B = params.B
229
#
230
# first vector for obtaining Re_* and theta_*
231
q1 = (1 + np.pi**2/4)*U0 - np.pi**2/4*U1
232
q2 = (1 + np.pi**2/4)*W0 - np.pi**2/4*W1
233
qmag = np.sqrt(q1**2 + q2**2)
234
Re = np.exp(kappa*qmag - kappa*B + np.pi**2/8 + 1)
235
thRe = np.sign(q2)*np.arccos(q1/qmag)
236
#
237
# second vector for obtaining Pi and theta_Pi
238
q1 = np.pi**2/4*kappa*(U1 - U0) - np.pi**2/8*np.cos(thRe)
239
q2 = np.pi**2/4*kappa*(W1 - W0) - np.pi**2/8*np.sin(thRe)
240
Pi = np.sqrt(q1**2 + q2**2)
241
thPi = np.sign(q2)*np.arccos(q1/Pi)
242
#
243
return (Re, thRe, Pi, thPi)
244
245
# solve pressure Poisson for <p>_0
246
def calc_p0(rhs):
247
msh = rhs.msh
248
kx = msh.KX
249
kz = msh.KZ
250
k2 = kx*kx + kz*kz + 1e-99
251
p0_hat = -rhs.hat/k2
252
p0_hat[k2==0.] = 0. # take the mean pressure out
253
return Var(msh, hat=p0_hat)
254
255
# solve pressure Helmholtz for <p>_1
256
def old_calc_p1(rhs, p0, params):
257
# unpack model parameters
258
Cp = params.Cp
259
# setup Helmholtz equation
260
rhs = rhs.add(p0.smult(-2.*Cp))
261
msh = rhs.msh
262
kx = msh.KX
263
kz = msh.KZ
264
k2 = kx*kx + kz*kz# + 1e-99
265
p1_hat = -rhs.hat/(k2+2*Cp)
266
return Var(msh, hat=p1_hat)
267
268
def calc_p1(rhs):
269
msh = rhs.msh
270
kx = msh.KX
271
kz = msh.KZ
272
k2 = kx*kx + kz*kz + 1e-99
273
p1_hat = -rhs.hat/k2
274
p1_hat[k2==0.] = 0.
275
return Var(msh, hat=p1_hat)
276
277
# solve pressure Helmholtz for <p>_1 + large scale relax to zero divergence
278
def new_calc_p1(rhs, p0, params):
279
# unpack model parameters
280
Cp = params.Cp
281
# setup Helmholtz equation
282
rhs = rhs.add(p0.smult(-2.*Cp))
283
msh = rhs.msh
284
kx = msh.KX
285
kz = msh.KZ
286
k2 = kx*kx + kz*kz# + 1e-99
287
return Var(msh, hat=-rhs.hat/(k2+2*Cp))
288
289
# return nonlinear for zeroth and first moments
290
def structural_model(Re, thRe, Pi, thPi, params, verbose=False):
291
# unpack model parameters
292
kappa = params.kappa
293
B = params.B
294
Re_ref = params.Re
295
Pi_ref = params.Pi_ref
296
lin = params.linearize
297
#
298
ff = np.log(Re)/kappa + B
299
## zeroth moment closure
300
ff_ref = np.log(Re_ref)/kappa + B
301
r0RR = (2*ff_ref - 2/kappa)*ff + (2/kappa**2 - ff_ref**2)
302
r0RP = Pi/kappa*ff_ref + Pi_ref/kappa*(ff-ff_ref) - Pi/(kappa**2)*(1-Si(np.pi)/np.pi)
303
r0PP = 3*Pi_ref / (kappa**2) * (Pi - 0.5*Pi_ref)
304
#
305
# i=1, j=1
306
r0xx = r0RR*np.cos(thRe)**2
307
r0xx += r0RP*(2*np.cos(thRe)*np.cos(thPi))
308
r0xx += r0PP*np.cos(thPi)**2
309
# i=2, j=2
310
r0zz = r0RR*np.sin(thRe)**2
311
r0zz += r0RP*(2*np.sin(thRe)*np.sin(thPi))
312
r0zz += r0PP*np.sin(thPi)**2
313
# i=1, j=2 OR i=2, j=1 (symmetric)
314
r0xz = r0RR*np.cos(thRe)*np.sin(thRe)
315
r0xz += r0RP*(np.cos(thRe)*np.sin(thPi) + np.sin(thRe)*np.cos(thPi))
316
r0xz += r0PP*np.cos(thPi)*np.sin(thPi)
317
#
318
# first moment closure
319
r1RR = (2*ff_ref - 1/kappa)*ff + (0.5/(kappa**2) - ff_ref**2)
320
r1RP = (1 + 4/np.pi**2)*((Pi/kappa)*ff_ref + Pi_ref/kappa*(ff - ff_ref))
321
r1RP -= 2*Pi/kappa**2*(0.25-2/np.pi**2-(Ci(np.pi)-np.euler_gamma-np.log(np.pi))/np.pi**2)
322
r1PP = (3 + 16/np.pi)*Pi_ref/(kappa**2) * (Pi - 0.5*Pi_ref)
323
#
324
# i=1, j=1
325
r1xx = r1RR*np.cos(thRe)**2
326
r1xx += r1RP*(2*np.cos(thRe)*np.cos(thPi))
327
r1xx += r1PP*np.cos(thPi)**2
328
# i=2, j=2
329
r1zz = r1RR*np.sin(thRe)**2
330
r1zz += r1RP*(2*np.sin(thRe)*np.sin(thPi))
331
r1zz += r1PP*np.sin(thPi)**2
332
# i=1, j=2 OR i=2, j=1 (symmetric)
333
r1xz = r1RR*np.cos(thRe)*np.sin(thRe)
334
r1xz += r1RP*(np.cos(thRe)*np.sin(thPi) + np.sin(thRe)*np.cos(thPi))
335
r1xz += r1PP*np.cos(thPi)*np.sin(thPi)
336
#
337
return (r0xx, r0zz, r0xz, r1xx, r1zz, r1xz)
338
339
def stress_model(Re, thRe, Pi, thPi, params, verbose=False):
340
# unpack model parameters
341
kappa = params.kappa
342
B = params.B
343
lin = params.linearize
344
Re_ref = params.Re
345
Pi_ref = params.Pi_ref
346
Cuv = params.Cuv
347
Ctau = params.Ctau
348
CPi = params.CPi
349
Cn = params.Cn
350
n = params.n
351
# calculate wall shear stress in local reference frame
352
tmag = (Re/Re_ref)**(2*Ctau)
353
tX = tmag * np.cos(thRe)
354
tZ = tmag * np.sin(thRe)
355
356
# viscous + unresolved turbulent stress integral for moment of momentum
357
ttR = 1. + (1-CPi)*Pi_ref
358
ttP = 1.*CPi*Pi
359
ttref = 1. + Pi_ref
360
ttX = Cuv * (ttR*np.cos(thRe) + ttP*np.cos(thPi))/ttref
361
ttZ = Cuv * (ttR*np.sin(thRe) + ttP*np.sin(thPi))/ttref
362
363
return (tX, tZ, ttX, ttZ)
364
365
def top_velocity_model(Re, thRe, Pi, thPi, params):
366
kappa = params.kappa
367
B = params.B
368
Re_ref = params.Re
369
Pi_ref = params.Pi_ref
370
utR = np.log(Re)/kappa+B
371
utP = 2*Pi/kappa
372
utx = utR*np.cos(thRe) + utP*np.cos(thPi)
373
utz = utR*np.sin(thRe) + utP*np.sin(thPi)
374
return (utx, utz)
375
376
def ref_zeroth_moment(Re, thRe, Pi, thPi, params, verbose=False):
377
# unpack model parameters
378
kappa = params.kappa
379
B = params.B
380
Re_ref = params.Re
381
Pi_ref = params.Pi_ref
382
R0_ref = (np.log(Re_ref)-1.)/kappa + B
383
P0_ref = Pi_ref / kappa
384
u0_ref = R0_ref * np.cos(thRe) + P0_ref * np.cos(thPi)
385
w0_ref = R0_ref * np.sin(thRe) + P0_ref * np.sin(thPi)
386
return (u0_ref, w0_ref)
387
388
def strain_rate(u, w):
389
dudx = u.diff_x()
390
dudz = u.diff_z()
391
dwdx = w.diff_x()
392
dwdz = w.diff_z()
393
Sxx = dudx
394
Sxz = dudz.add(dwdx)
395
Sxz = Sxz.smult(0.5)
396
Szz = dwdz
397
Smag2 = Sxx.mult(Sxx)
398
Smag2 = Smag2.add(Szz.mult(Szz))
399
Smag2 = Smag2.add(Sxz.mult(Sxz.smult(2.)))
400
return (Sxx, Szz, Sxz, Smag2)
401
402
def eddy_viscosity_model(S02, S12, params):
403
# unpack model parameters
404
Cs = params.Cs0
405
Smag = S02.add(S12)
406
Smag = Smag.sqrt()
407
return Smag.smult(Cs)
408
409
# Pi formatter function
410
def pi_formatter(x, pos):
411
N = int(np.round(x / np.pi))
412
if N == 0:
413
return "0"
414
elif N == 1:
415
return r"\pi"
416
elif N == -1:
417
return r"-\pi"
418
elif N % 2 == 0:
419
return r"{0}\pi".format(N)
420
else:
421
return r"{0}\pi".format(N)
422
423
424