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.
unlisted
ubuntu2204from periodic2d import *1import subprocess2import glob3import scipy.special as spec4from PIL import Image5import glob67plt.rcParams.update({'lines.linewidth': 1, 'lines.markersize': 6,'lines.markeredgewidth': 0.2})8plt.rcParams.update({'font.size': 12, 'legend.fontsize': 8})9plt.rc('font', family='serif')10plt.rc('text',usetex=True)1112# sine integral function13def Si(x):14return spec.sici(x)[0]1516def Ci(x):17return spec.sici(x)[1]1819# input parameters20class Params:21"""Data structure for storing and passing input parameters for TRIS closures."""2223def __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.):24self.kappa = kappa25self.B = B26self.Re = Re27self.Pi_ref = Pi_ref28self.Cuv = Cuv29self.Cs0 = Cs030self.Cs1 = Cs131self.Cp = Cp32self.Cn = Cn33self.n = n34self.Ctau = Ctau35self.CPi = CPi36self.linearize = linearize37self.flow_angle = flow_angle3839class Animation:40"""Data structure for creating an animation of a given quantity."""4142def __init__(self, vname, vdir='animation', fmt='png', dpi=300):43self.name = vname44self.dir = vdir45self.fmt = fmt46self.dpi = dpi47self.filename = self.dir+'vid_'+self.name48self.N = 04950def update(self, i, field):51Lx = field.msh.Lx52Lz = field.msh.Lz53colormap = plt.get_cmap('RdBu_r')54plt.figure(1, figsize=(6,1.5))55im = plt.imshow((field.val.T - np.mean(field.val.T))/np.std(field.val.T), interpolation='bicubic', origin='lower',\56extent=(0,Lx,0,Lz), cmap=colormap, vmin=-3, vmax=3)57cb = plt.colorbar(im)58cb.set_ticks(np.linspace(-3, 3, num=3)) # Adjust number of ticks59plt.xticks(np.linspace(0, Lx, 5), [r"$"+pi_formatter(val, None)+r"$" for val in np.linspace(0, Lx, 5)],fontsize=12)60plt.yticks(np.linspace(0, Lz, 4), [r"$"+pi_formatter(val, None)+r"$" for val in np.linspace(0, Lz, 4)],fontsize=12)61if self.name == 'u0':62plt.title(r'$\left< \widetilde{u} \right>_0^s$')63if self.name == 'u1':64plt.title(r'$\left< \widetilde{u} \right>_1^s$')65if self.name == 'w0':66plt.title(r'$\left< \widetilde{w} \right>_0^s$')67if self.name == 'w1':68plt.title(r'$\left< \widetilde{w} \right>_1^s$')69if self.name == 'v0':70plt.title(r'$\left< \widetilde{v} \right>_0^s$')71if self.name == 'p0':72plt.title(r'$\left< \widetilde{p} \right>_0^s$')73if self.name == 'p0':74plt.title(r'$\left< \widetilde{p} \right>_1^s$')75plt.savefig(self.filename+'_'+str(i)+'.'+self.fmt, format=self.fmt, bbox_inches='tight', dpi=self.dpi)76plt.close(1)77self.N += 17879def compile(self,animation_variable):80image_files = sorted(glob.glob('animation/*.png'))81images = [Image.open(image) for image in image_files]8283# Save as GIF84gif_path = 'animation/'+animation_variable+'.gif'85images[0].save(86gif_path,87save_all=True,88append_images=images[1:],89duration=100,90loop=0)9192cmd = ['rm']93files = glob.glob(self.filename+'_*.'+self.fmt)94subprocess.run(cmd+files)9596def calc_v0(u1, w1):97return div(u1, w1).smult(0.5)9899def calc_rhs(u0, w0, u1, w1, params, verbose=False):100## 0. calculate v0 field101v0 = calc_v0(u1, w1)102103## obtain u,v,w fields in physical space104for field in [u0, w0, u1, w1, v0]:105field.ifft()106107## 1. calculate assumed profile parameters from moments108(Re, thRe, Pi, thPi) = profile_params(u0.val, w0.val, u1.val, w1.val, params)109110## 2. closure models for nonlinear flux terms111(r0xx,r0zz,r0xz,r1xx,r1zz,r1xz) = structural_model(Re, thRe, Pi, thPi, params)112# place it back on the mesh113uu0 = Var(u0.msh, val=r0xx)114uw0 = Var(u0.msh, val=r0xz)115ww0 = Var(w0.msh, val=r0zz)116uu1 = Var(u1.msh, val=r1xx)117uw1 = Var(u1.msh, val=r1xz)118ww1 = Var(w1.msh, val=r1zz)119#120for field in [uu0, uw0, ww0, uu1, uw1, ww1]:121field.fft()122# negative divergence on RHS123rhs_u0 = div(uu0, uw0).smult(-1.)124rhs_w0 = div(uw0, ww0).smult(-1.)125rhs_p0 = div(rhs_u0, rhs_w0)126rhs_u1 = div(uu1, uw1).smult(-1.)127rhs_w1 = div(uw1, ww1).smult(-1.)128rhs_p1 = div(rhs_u1, rhs_w1)129130## 3. calculate resolved wall-normal momentum fluxes131uv0 = u0.mult(v0)132wv0 = w0.mult(v0)133for field in [uv0, wv0]:134field.fft()135rhs_u1 = rhs_u1.add(uv0.smult(2.))136rhs_w1 = rhs_w1.add(wv0.smult(2.))137rhs_p1 = rhs_p1.add(div(uv0,wv0).smult(4.))138139## 4. calculate wall shear stress model140(taux, tauz, ztaux, ztauz) = stress_model(Re, thRe, Pi, thPi, params)141# put it back on the mesh142tx = Var(u0.msh, val=taux)143tz = Var(w0.msh, val=tauz)144ztx = Var(u0.msh, val=ztaux)145ztz = Var(w0.msh, val=ztauz)146147# return to wavenumber space148for field in [tx, tz, ztx, ztz]:149field.fft()150# subtract from RHS151rhs_u0 = rhs_u0.add(tx.smult(-1.))152rhs_w0 = rhs_w0.add(tz.smult(-1.))153rhs_p0 = rhs_p0.add(div(tx, tz).smult(-1.))154# integral of the viscous and unresolved turbulent stress155rhs_u1 = rhs_u1.add(ztx.smult(-1.))156rhs_w1 = rhs_w1.add(ztz.smult(-1.))157rhs_p1 = rhs_p1.add(div(ztx, ztz).smult(-2.))158159## 4b. calculate viscous stress integral160(utopx, utopz) = top_velocity_model(Re, thRe, Pi, thPi, params)161visc_x = Var(u0.msh, val=-2.*utopx/params.Re)162visc_z = Var(u0.msh, val=-2.*utopz/params.Re)163for field in [visc_x, visc_z]:164field.fft()165rhs_u1 = rhs_u1.add(visc_x)166rhs_w1 = rhs_w1.add(visc_z)167168## 5. add body force driving the flow169rhs_u0 = rhs_u0.sadd(np.cos(params.flow_angle))170rhs_u1 = rhs_u1.sadd(np.cos(params.flow_angle))171rhs_w0 = rhs_w0.sadd(np.sin(params.flow_angle))172rhs_w1 = rhs_w1.sadd(np.sin(params.flow_angle))173174## 6. wall-parallel eddy viscosity175(S0xx, S0zz, S0xz, S02) = strain_rate(u0, w0)176(S1xx, S1zz, S1xz, S12) = strain_rate(u1, w1)177if params.linearize:178Lx = u0.msh.Lx179Lz = u0.msh.Lz180nx = u0.msh.nx181nz = u0.msh.nz182dx = Lx/nx183dz = Lz/nz184Smag = S02.sqrt()185nuT0 = Smag.smult(params.Cs0).smult(dx).smult(dz)186Smag = S12.sqrt()187nuT1 = Smag.smult(params.Cs1).smult(dx).smult(dz)188else:189nuT0 = eddy_viscosity_model(S02, S12, params)190nuT1 = nuT0191tau0xx = S0xx.mult(nuT0)192tau0zz = S0zz.mult(nuT0)193tau0xz = S0xz.mult(nuT0)194tau1xx = S1xx.mult(nuT1)195tau1zz = S1zz.mult(nuT1)196tau1xz = S1xz.mult(nuT1)197f0x = div(tau0xx, tau0xz)198f0z = div(tau0xz, tau0zz)199f1x = div(tau1xx, tau1xz)200f1z = div(tau1xz, tau1zz)201rhs_u0 = rhs_u0.add(f0x)202rhs_w0 = rhs_w0.add(f0z)203rhs_u1 = rhs_u1.add(f1x)204rhs_w1 = rhs_w1.add(f1z)205rhs_p0 = rhs_p0.add(div(f0x, f0z))206rhs_p1 = rhs_p1.add(div(f1x, f1z))207208nu_eff = (1./params.Re) # molecular viscosity209rhs_u0 = rhs_u0.add(u0.laplacian().smult(nu_eff))210rhs_w0 = rhs_w0.add(w0.laplacian().smult(nu_eff))211rhs_u1 = rhs_u1.add(u1.laplacian().smult(nu_eff))212rhs_w1 = rhs_w1.add(w1.laplacian().smult(nu_eff))213214# project RHS to make divergence-free215p0 = calc_p0(rhs_p0)216rhs_u0 = rhs_u0.add(p0.diff_x().smult(-1.))217rhs_w0 = rhs_w0.add(p0.diff_z().smult(-1.))218p1 = new_calc_p1(rhs_p1, p0, params)219rhs_u1 = rhs_u1.add(p1.diff_x().smult(-1.))220rhs_w1 = rhs_w1.add(p1.diff_z().smult(-1.))221222return (rhs_u0, rhs_w0, rhs_u1, rhs_w1, p0, p1)223224def profile_params(U0, W0, U1, W1, params, verbose=False):225# unpack model parameters226kappa = params.kappa227B = params.B228#229# first vector for obtaining Re_* and theta_*230q1 = (1 + np.pi**2/4)*U0 - np.pi**2/4*U1231q2 = (1 + np.pi**2/4)*W0 - np.pi**2/4*W1232qmag = np.sqrt(q1**2 + q2**2)233Re = np.exp(kappa*qmag - kappa*B + np.pi**2/8 + 1)234thRe = np.sign(q2)*np.arccos(q1/qmag)235#236# second vector for obtaining Pi and theta_Pi237q1 = np.pi**2/4*kappa*(U1 - U0) - np.pi**2/8*np.cos(thRe)238q2 = np.pi**2/4*kappa*(W1 - W0) - np.pi**2/8*np.sin(thRe)239Pi = np.sqrt(q1**2 + q2**2)240thPi = np.sign(q2)*np.arccos(q1/Pi)241#242return (Re, thRe, Pi, thPi)243244# solve pressure Poisson for <p>_0245def calc_p0(rhs):246msh = rhs.msh247kx = msh.KX248kz = msh.KZ249k2 = kx*kx + kz*kz + 1e-99250p0_hat = -rhs.hat/k2251p0_hat[k2==0.] = 0. # take the mean pressure out252return Var(msh, hat=p0_hat)253254# solve pressure Helmholtz for <p>_1255def old_calc_p1(rhs, p0, params):256# unpack model parameters257Cp = params.Cp258# setup Helmholtz equation259rhs = rhs.add(p0.smult(-2.*Cp))260msh = rhs.msh261kx = msh.KX262kz = msh.KZ263k2 = kx*kx + kz*kz# + 1e-99264p1_hat = -rhs.hat/(k2+2*Cp)265return Var(msh, hat=p1_hat)266267def calc_p1(rhs):268msh = rhs.msh269kx = msh.KX270kz = msh.KZ271k2 = kx*kx + kz*kz + 1e-99272p1_hat = -rhs.hat/k2273p1_hat[k2==0.] = 0.274return Var(msh, hat=p1_hat)275276# solve pressure Helmholtz for <p>_1 + large scale relax to zero divergence277def new_calc_p1(rhs, p0, params):278# unpack model parameters279Cp = params.Cp280# setup Helmholtz equation281rhs = rhs.add(p0.smult(-2.*Cp))282msh = rhs.msh283kx = msh.KX284kz = msh.KZ285k2 = kx*kx + kz*kz# + 1e-99286return Var(msh, hat=-rhs.hat/(k2+2*Cp))287288# return nonlinear for zeroth and first moments289def structural_model(Re, thRe, Pi, thPi, params, verbose=False):290# unpack model parameters291kappa = params.kappa292B = params.B293Re_ref = params.Re294Pi_ref = params.Pi_ref295lin = params.linearize296#297ff = np.log(Re)/kappa + B298## zeroth moment closure299ff_ref = np.log(Re_ref)/kappa + B300r0RR = (2*ff_ref - 2/kappa)*ff + (2/kappa**2 - ff_ref**2)301r0RP = Pi/kappa*ff_ref + Pi_ref/kappa*(ff-ff_ref) - Pi/(kappa**2)*(1-Si(np.pi)/np.pi)302r0PP = 3*Pi_ref / (kappa**2) * (Pi - 0.5*Pi_ref)303#304# i=1, j=1305r0xx = r0RR*np.cos(thRe)**2306r0xx += r0RP*(2*np.cos(thRe)*np.cos(thPi))307r0xx += r0PP*np.cos(thPi)**2308# i=2, j=2309r0zz = r0RR*np.sin(thRe)**2310r0zz += r0RP*(2*np.sin(thRe)*np.sin(thPi))311r0zz += r0PP*np.sin(thPi)**2312# i=1, j=2 OR i=2, j=1 (symmetric)313r0xz = r0RR*np.cos(thRe)*np.sin(thRe)314r0xz += r0RP*(np.cos(thRe)*np.sin(thPi) + np.sin(thRe)*np.cos(thPi))315r0xz += r0PP*np.cos(thPi)*np.sin(thPi)316#317# first moment closure318r1RR = (2*ff_ref - 1/kappa)*ff + (0.5/(kappa**2) - ff_ref**2)319r1RP = (1 + 4/np.pi**2)*((Pi/kappa)*ff_ref + Pi_ref/kappa*(ff - ff_ref))320r1RP -= 2*Pi/kappa**2*(0.25-2/np.pi**2-(Ci(np.pi)-np.euler_gamma-np.log(np.pi))/np.pi**2)321r1PP = (3 + 16/np.pi)*Pi_ref/(kappa**2) * (Pi - 0.5*Pi_ref)322#323# i=1, j=1324r1xx = r1RR*np.cos(thRe)**2325r1xx += r1RP*(2*np.cos(thRe)*np.cos(thPi))326r1xx += r1PP*np.cos(thPi)**2327# i=2, j=2328r1zz = r1RR*np.sin(thRe)**2329r1zz += r1RP*(2*np.sin(thRe)*np.sin(thPi))330r1zz += r1PP*np.sin(thPi)**2331# i=1, j=2 OR i=2, j=1 (symmetric)332r1xz = r1RR*np.cos(thRe)*np.sin(thRe)333r1xz += r1RP*(np.cos(thRe)*np.sin(thPi) + np.sin(thRe)*np.cos(thPi))334r1xz += r1PP*np.cos(thPi)*np.sin(thPi)335#336return (r0xx, r0zz, r0xz, r1xx, r1zz, r1xz)337338def stress_model(Re, thRe, Pi, thPi, params, verbose=False):339# unpack model parameters340kappa = params.kappa341B = params.B342lin = params.linearize343Re_ref = params.Re344Pi_ref = params.Pi_ref345Cuv = params.Cuv346Ctau = params.Ctau347CPi = params.CPi348Cn = params.Cn349n = params.n350# calculate wall shear stress in local reference frame351tmag = (Re/Re_ref)**(2*Ctau)352tX = tmag * np.cos(thRe)353tZ = tmag * np.sin(thRe)354355# viscous + unresolved turbulent stress integral for moment of momentum356ttR = 1. + (1-CPi)*Pi_ref357ttP = 1.*CPi*Pi358ttref = 1. + Pi_ref359ttX = Cuv * (ttR*np.cos(thRe) + ttP*np.cos(thPi))/ttref360ttZ = Cuv * (ttR*np.sin(thRe) + ttP*np.sin(thPi))/ttref361362return (tX, tZ, ttX, ttZ)363364def top_velocity_model(Re, thRe, Pi, thPi, params):365kappa = params.kappa366B = params.B367Re_ref = params.Re368Pi_ref = params.Pi_ref369utR = np.log(Re)/kappa+B370utP = 2*Pi/kappa371utx = utR*np.cos(thRe) + utP*np.cos(thPi)372utz = utR*np.sin(thRe) + utP*np.sin(thPi)373return (utx, utz)374375def ref_zeroth_moment(Re, thRe, Pi, thPi, params, verbose=False):376# unpack model parameters377kappa = params.kappa378B = params.B379Re_ref = params.Re380Pi_ref = params.Pi_ref381R0_ref = (np.log(Re_ref)-1.)/kappa + B382P0_ref = Pi_ref / kappa383u0_ref = R0_ref * np.cos(thRe) + P0_ref * np.cos(thPi)384w0_ref = R0_ref * np.sin(thRe) + P0_ref * np.sin(thPi)385return (u0_ref, w0_ref)386387def strain_rate(u, w):388dudx = u.diff_x()389dudz = u.diff_z()390dwdx = w.diff_x()391dwdz = w.diff_z()392Sxx = dudx393Sxz = dudz.add(dwdx)394Sxz = Sxz.smult(0.5)395Szz = dwdz396Smag2 = Sxx.mult(Sxx)397Smag2 = Smag2.add(Szz.mult(Szz))398Smag2 = Smag2.add(Sxz.mult(Sxz.smult(2.)))399return (Sxx, Szz, Sxz, Smag2)400401def eddy_viscosity_model(S02, S12, params):402# unpack model parameters403Cs = params.Cs0404Smag = S02.add(S12)405Smag = Smag.sqrt()406return Smag.smult(Cs)407408# Pi formatter function409def pi_formatter(x, pos):410N = int(np.round(x / np.pi))411if N == 0:412return "0"413elif N == 1:414return r"\pi"415elif N == -1:416return r"-\pi"417elif N % 2 == 0:418return r"{0}\pi".format(N)419else:420return r"{0}\pi".format(N)421422423424