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
ubuntu2204import numpy as np1import matplotlib.pyplot as plt2from matplotlib import cm3import warnings4import subprocess56check_tol = 1e-107pi = np.pi8cos = np.cos9sin = np.sin1011class Msh:12"""Base grid class containing physical and wavenumber grids."""1314def __init__(self,Lx=2*np.pi,Lz=2*np.pi,nx=64,nz=64):15# Physical16self.Lx = Lx17self.Lz = Lz18self.nx = nx19self.nz = nz20self.dx = Lx / nx21self.fz = Lz / nz22self.x = np.linspace(0,Lx-self.dx,nx)23self.z = np.linspace(0,Lz-self.fz,nz)24(self.X, self.Z) = np.meshgrid(self.x, self.z, indexing='ij')2526# Wavenumber27self.kx_max = np.pi / self.dx28self.kz_max = np.pi / self.fz29self.dkx = 2*np.pi / Lx30self.dkz = 2*np.pi / Lz31self.nkx = nx32self.nkz = nz//2 + 133self.kx = 2 * self.kx_max * np.fft.fftfreq(nx)34self.kz = np.linspace(0,self.kz_max,self.nkz)35(self.KX, self.KZ) = np.meshgrid(self.kx, self.kz, indexing='ij')3637def one(self):38return np.ones((self.nx, self.nz))3940class Var:41"""Base solution variable class."""4243def __init__(self,msh,val=None,hat=None,name='var',ls='None',mrk='o',clr='k'):44self.msh = msh45self.name = name46self.ls = ls47self.mrk = mrk48self.clr = clr4950if (np.shape(val) == (msh.nx, msh.nz)):51self.setval(val)52elif (np.shape(hat) == (msh.nkx, msh.nkz)):53self.sethat(hat)54else:55self.setval(np.zeros((msh.nx,msh.nz)))56self.sethat(np.zeros((msh.nkx,msh.nkz),dtype=complex))57self.hasval = False58self.hashat = False5960def copy(self, name=None, ls=None, mrk=None, clr=None):61new = Var(self.msh, self.name, self.ls, self.mrk, self.clr)62if(self.hasval):63new.val = np.array(self.val)64new.hasval = True65if(self.hashat):66new.hat = np.array(self.hat,dtype=complex)67new.hashat = True68if(name):69new.name = name70if(ls):71new.ls = ls72if(mrk):73new.mrk = mrk74if(clr):75new.clr = clr76return new7778def setval(self, val_array):79self.val = np.array(val_array)80self.hasval = True81self.hashat = False8283def sethat(self, hat_array):84self.hat = np.array(hat_array,dtype=complex)85self.hashat = True86self.hasval = False8788def set_constant(self, const):89self.val = np.array(const*np.ones((self.msh.nx, self.msh.nz)))90self.hasval = True91self.hashat = False92self.fft()9394def randn(self,sigma=1.):95randns = sigma*np.random.randn(self.msh.nx*self.msh.nz)96self.setval(np.reshape(randns,(self.msh.nx,self.msh.nz)))97self.fft()9899def fft(self):100if(not self.hashat):101if(self.hasval):102self.hat = np.fft.rfftn(self.val) / (self.msh.nx*self.msh.nz)103self.hashat = True104else:105raise Exception("Called fft for variable with hasval = False")106107108def ifft(self):109if(not self.hasval):110if(self.hashat):111self.val = np.fft.irfftn(self.hat) * (self.msh.nx*self.msh.nz)112self.hasval = True113else:114raise Exception("Called ifft for variable with hashat = False")115116117def smult(self, scalar):118if(self.hashat):119return Var(self.msh, hat=scalar*self.hat)120elif(self.hasval):121return Var(self.msh, val=scalar*self.val)122else:123raise Exception("Called smult with neither hashat nor hasval")124125def mult(self, other):126if(self.hasval and other.hasval):127return Var(self.msh, val=self.val * other.val)128elif(self.hashat and other.hashat):129new = Var(self.msh)130self.ifft()131other.ifft()132new.setval(self.val * other.val)133new.fft()134return new135else:136print('self.hashat = ', self.hashat)137print('self.hasval = ', self.hasval)138print('other.hashat = ', other.hashat)139print('other.hasval = ', other.hasval)140raise Exception('mult called with neither hashat nor hasval')141142def sqrt(self):143if(self.hasval):144return Var(self.msh, val=np.sqrt(np.abs(self.val)))145elif(self.hashat):146self.ifft()147new = Var(self.msh, val=np.sqrt(np.abs(self.val)))148new.fft()149return new150else:151raise Exception('sqrt called with neither hashat nor hasval')152153def sadd(self, scalar):154if(self.hashat):155new = Var(self.msh, val=scalar*np.ones((self.msh.nx,self.msh.nz)))156new.fft()157return self.add(new)158elif (self.hasval):159new = Var(self.msh, val=scalar*np.ones((self.msh.nx,self.msh.nz)))160return self.add(new)161else:162raise Exception('sadd called with neither hashat nor hasval')163164def add(self, other):165if(self.hashat and other.hashat):166return Var(self.msh, hat=self.hat + other.hat)167elif(self.hasval and other.hasval):168return Var(self.msh, val=self.val + other.val)169else:170print('self.hashat = ', self.hashat)171print('self.hasval = ', self.hasval)172print('other.hashat = ', other.hashat)173print('other.hasval = ', other.hasval)174raise Exception('add called with neither hashat nor hasval')175176def diff_x(self):177if(not self.hashat):178warnings.warn("Had to call fft within diff_x")179self.fft()180deriv = Var(self.msh, self.name+'_dx', self.ls, self.mrk, self.clr)181deriv.hat = 1j * self.msh.KX * self.hat182deriv.hashat = True183deriv.filter_kxmax()184return deriv185186def diff_z(self):187if(not self.hashat):188warnings.warn("Had to call fft within diff_z")189self.fft()190deriv = Var(self.msh, self.name+'_fz', self.ls, self.mrk, self.clr)191deriv.hat = 1j * self.msh.KZ * self.hat192deriv.hashat = True193deriv.filter_kzmax()194return deriv195196def laplacian(self):197if(not self.hashat):198warnings.warn("Had to call fft within lapl")199self.fft()200lapl = Var(self.msh, self.name+'_lapl', self.ls, self.mrk, self.clr)201lapl.hat = - (self.msh.KX**2 + self.msh.KZ**2) * self.hat202lapl.hashat = True203lapl.filter_max()204return lapl205206def viscous_damp(self, nu, dt):207k2 = self.msh.KX**2 + self.msh.KZ**2208self.hat = self.hat * np.exp(-k2**2*nu*dt)209210def check(self):211if(self.hashat and self.hasval):212val = np.fft.irfftn(self.hat) * (self.msh.nx*self.msh.nz)213if(np.linalg.norm(self.val - val) > check_tol):214raise Exception('Check zero: ' + str(np.linalg.norm(self.val - val)))215hat = np.fft.rfftn(self.val) / (self.msh.nx*self.msh.nz)216if(np.linalg.norm(self.hat - hat) > check_tol):217raise Exception('Check zero: ' + str(np.linalg.norm(self.hat - hat)))218else:219raise Exception('Check failed: hashat = ' + str(self.hashat) \220+ '; hasval = ' + str(self.hasval))221222def cp_hat(self,other):223if(self.msh.nkz <= other.msh.nkz and self.msh.nkx <= other.msh.nkx):224# if self is smaller in x and z, spectral cutoff filter225self.hat[:self.msh.nkx//2,:] \226= other.hat[:self.msh.nkx//2,:self.msh.nkz]227self.hat[self.msh.nkx//2:,:] \228= other.hat[(other.msh.nkx-self.msh.nkx//2):,:self.msh.nkz]229elif(self.msh.nkz > other.msh.nkz and self.msh.nkx > other.msh.nkx):230# if self is larger in x and z, pad with zeros231self.hat = np.zeros((self.msh.nkx,self.msh.nkz),dtype=complex)232self.hat[:other.msh.nkx//2,:other.msh.nkz] \233= other.hat[:other.msh.nkx//2,:]234self.hat[(self.msh.nkx-other.msh.nkx//2):,:other.msh.nkz]\235= other.hat[other.msh.nkx//2:,:]236else:237raise Exception("Not implemented yet: cp_hat()")238239def filter(self,kx_cut,kz_cut):240k_tol = 1e-9241self.hat[np.abs(self.msh.KX) >= (kx_cut - k_tol)] = 0. + 0.j242self.hat[np.abs(self.msh.KZ) >= (kz_cut - k_tol)] = 0. + 0.j243self.hasval = False244245def filter_max(self):246self.filter(self.msh.kx_max,self.msh.kz_max)247248def filter_kxmax(self):249self.filter(self.msh.kx_max,2*self.msh.kz_max)250251def filter_kzmax(self):252self.filter(2*self.msh.kx_max,self.msh.kz_max)253254def filter_third(self):255self.filter(2*self.msh.kx_max/3,2*self.msh.kz_max/3)256257def filter_half(self):258self.filter(self.msh.kx_max/2, self.msh.kz_max/2)259260def filter_all(self):261self.filter(self.msh.kx[1],self.msh.kz[1])262263264def mean(self):265if(self.hashat):266return np.real(self.hat[0,0])267elif(self.hasval):268return np.mean(self.val)269270def var(self):271if(self.hashat):272spectrum = self.spectrum()273spectrum[0,0] = 0.274return np.sum(spectrum)275elif(self.hasval):276return np.var(self.val)277278def pdf(self, nbins=100):279if(self.hashat and not self.hasval):280self.ifft()281(pdf, edges) = np.histogram(self.val,bins=nbins,density=True)282bins = edges[:-1] + np.diff(edges)/2283return (pdf, bins)284285def spectrum(self):286if(not self.hashat):287self.fft()288return np.abs(self.hat)**2289290def spectrum_x(self):291spectrum = self.spectrum()292return np.sum(spectrum,axis=1)293294def spectrum_z(self):295spectrum = self.spectrum()296return np.sum(spectrum,axis=0)297298def report(self, name='var'):299if(self.hashat and not self.hasval):300self.ifft()301report = (np.min(self.val), np.mean(self.val), np.max(self.val))302print('min/mean/max of '+name+' is:', report)303304def vorticity(u, w):305if(not u.hashat):306warnings.warn("Had to call fft within vorticity()")307u.fft()308if(not w.hashat):309warnings.warn("Had to call fft within vorticity()")310w.fft()311vort = Var(u.msh)312vort.sethat(1.j*u.msh.KX*w.hat - 1.j*u.msh.KZ*u.hat)313vort.filter_max()314return vort315316def filter_Gauss(f, ell):317if not f.hashat:318f.fft()319ell2 = ell*ell320kx = f.msh.KX321kz = f.msh.KZ322k2 = kx*kx + kz*kz + 1e-99323g = Var(f.msh)324g.sethat( f.hat * np.exp(-0.5*ell2*k2) )325g.filter_max()326return g327328def filter_Gauss_highpass(f, ell):329if not f.hashat:330f.fft()331ell2 = ell*ell332kx = f.msh.KX333kz = f.msh.KZ334k2 = kx*kx + kz*kz + 1e-99335g = Var(f.msh)336g.sethat( f.hat * (1-np.exp(-0.5*ell2*k2)) )337g.filter_max()338return g339340def filter_highpass(f, kx_cut, kz_cut):341if not f.hashat:342f.fft()343k_tol = 1e-9344g = Var(f.msh, hat=f.hat)345g.hat[np.abs(g.msh.KZ) <= (kz_cut + k_tol)] = 0. + 0.j346return g347348def projection(f_x, f_z):349# (delta_{ij} - k_i k_j/k^2) f_j350if(not f_x.hashat):351f_x.fft()352if(not f_z.hashat):353f_z.fft()354msh = f_x.msh355kx = msh.KX356kz = msh.KZ357k2 = kx*kx + kz*kz + 1e-99358Pxx = kx*kx/k2359Pxz = kx*kz/k2360Pzz = kz*kz/k2361Pxx[k2==0.] = 0.362Pxz[k2==0.] = 0.363Pzz[k2==0.] = 0.364r_x = Var(msh)365r_z = Var(msh)366r_x.sethat( (1. - Pxx)*f_x.hat - Pxz*f_z.hat )367r_z.sethat( (1. - Pzz)*f_z.hat - Pxz*f_x.hat )368r_x.filter_max()369r_z.filter_max()370return (r_x, r_z)371372def div(f_x, f_z):373# df_x/dx + df_z/fz374if(f_x.hashat and f_z.hashat):375return div_hat(f_x, f_z)376elif(f_x.hasval and f_z.hasval):377return div_val(f_x, f_z)378else:379print('f_x.hashat = ', f_x.hashat)380print('f_x.hasval = ', f_x.hasval)381print('f_z.hashat = ', f_z.hashat)382print('f_z.hasval = ', f_z.hasval)383raise Exception('Called div with neither hashat nor hasval')384385def div_val(f_x, f_z):386if(f_x.hasval and f_z.hasval):387f_x.fft()388f_z.fft()389return div_hat(f_x, f_z)390else:391raise Exception('Called div_val with hasval=False')392393def div_hat(f_x, f_z):394if(f_x.hashat and f_z.hashat):395msh = f_x.msh396kx = msh.KX397kz = msh.KZ398div = Var(msh, hat=1.j*kx*f_x.hat + 1.j*kz*f_z.hat)399div.filter_max()400return div401else:402raise Exception('Called div_hat with hashat=False')403404def friction(u, w, u_avg, pwr):405fx = Var(u.msh)406fz = Var(w.msh)407u.ifft()408w.ifft()409u_mag = np.sqrt(u.val**2 + w.val**2)+1e-99410e_x = u.val / (u_mag)411e_z = w.val / (u_mag)412friction = (u_mag/u_avg)**pwr413fx.setval(friction*e_x)414fz.setval(friction*e_z)415fx.fft()416fz.fft()417fx.filter_third()418fz.filter_third()419return (fx,fz)420421422423