def filter_kernel(self, kf, Gtype='spectral', k_kf=None, dtype=np.complex128): """ kf - input cutoff wavenumber for ensured isotropic filtering Gtype - (Default='spectral') filter kernel type k_kf - (Default=None) spectral-space wavenumber field pre- normalized by filter cutoff wavenumber. Pass this into FILTER_KERNEL for anisotropic filtering since this function generates isotropic filter kernels by default. If not None, kf is ignored. """ if k_kf is None: A = self.L / self.L.min() # domain size aspect ratios A.resize((3, 1, 1, 1)) # ensure proper array broadcasting kmag = np.sqrt(np.sum(np.square(self.K / A), axis=0)) k_kf = kmag / kf Ghat = np.empty(k_kf.shape, dtype=dtype) if Gtype == 'spectral': Ghat[:] = (np.abs(k_kf) < 1.0).astype(dtype) elif Gtype == 'tophat': Ghat[:] = np.sin(pi * k_kf) / (pi * k_kf**2) elif Gtype == 'comp_exp': # A Compact Exponential filter that: # 1) has compact support in _both_ physical and spectral space # 2) is strictly positive in _both_ spaces # 3) is smooth (infinitely differentiable) in _both_ spaces # 4) has simply-connected support in spectral space with # an outer radius kf, and # 5) has disconnected (lobed) support in physical space # with an outer radius of 2*pi/kf with np.errstate(divide='ignore'): Ghat[:] = np.exp(-k_kf**2 / (0.25 - k_kf**2), where=k_kf < 0.5, out=np.zeros_like(k_kf)).astype(dtype) G = irfft3(self.comm, Ghat) G[:] = np.square(G) rfft3(self.comm, G, Ghat) Ghat *= 1.0 / self.comm.allreduce(Ghat[0, 0, 0], op=MPI.MAX) Ghat -= 1j * np.imag(Ghat) elif Gtype == 'inv_comp_exp': # Same as 'comp_exp' but the physical-space and # spectral-space kernels are swapped so that the # physical-space support is a simply-connected ball raise ValueError('inv_comp_exp not yet implemented!') else: raise ValueError('did not understand filter type') return Ghat
def filter_kernel(self, kf, Gtype='spectral', k_kf=None, dtype=np.complex128): """ kf - input cutoff wavenumber for ensured isotropic filtering Gtype - (Default='spectral') filter kernel type k_kf - (Default=None) spectral-space wavenumber field pre- normalized by filter cutoff wavenumber. Pass this into FILTER_KERNEL for anisotropic filtering since this function generates isotropic filter kernels by default. If not None, kf is ignored. """ if k_kf is None: A = self.L/self.L.min() # domain size aspect ratios A.resize((3, 1, 1, 1)) # ensure proper array broadcasting kmag = np.sqrt(np.sum(np.square(self.K/A), axis=0)) k_kf = kmag/kf Ghat = np.empty(k_kf.shape, dtype=dtype) if Gtype == 'spectral': Ghat[:] = (np.abs(k_kf) < 1.0).astype(dtype) elif Gtype == 'comp_exp': # A 'COMPact EXPonential' filter which: # 1) has compact support in a ball of spectral (physical) # radius kf (1/kf) # 2) is strictly positive, and # 3) is smooth (infinitely differentiable) # in _both_ physical and spectral space! with np.errstate(divide='ignore'): Ghat[:] = np.exp(-k_kf**2/(0.25-k_kf**2), where=k_kf < 0.5, out=np.zeros_like(k_kf)).astype(dtype) G = irfft3(self.comm, Ghat) G[:] = np.square(G) rfft3(self.comm, G, Ghat) Ghat *= 1.0/self.comm.allreduce(Ghat[0, 0, 0], op=MPI.MAX) Ghat -= 1j*np.imag(Ghat) # elif Gtype == 'inv_comp_exp': # # Same as 'comp_exp' but the physical-space and spectral- # # space kernels are swapped so that the physical-space kernel # # has only a central lobe of support. # H = np.exp(-r_rf**2/(1.0-r_rf**2)) # G = np.where(r_rf < 1.0, H, 0.0) # rfft3(self.comm, G, Ghat) # Ghat[:] = Ghat**2 # G[:] = irfft3(self.comm, Ghat) # G /= self.comm.allreduce(psum(G), op=MPI.SUM) # rfft3(self.comm, G, Ghat) elif Gtype == 'tophat': Ghat[:] = np.sin(pi*k_kf)/(pi*k_kf**2) else: raise ValueError('did not understand filter type') return Ghat
def computeSource_ales244_SGS(self, H_244, **ignored): """ H_244 - ALES coefficients h_ij for 244-term Volterra series truncation. H_244.shape = (6, 244) """ tau_hat = self.tau_hat UU_hat = self.UU_hat irfft3(self.comm, self.les_filter * self.U_hat[0], self.W[0]) irfft3(self.comm, self.les_filter * self.U_hat[1], self.W[1]) irfft3(self.comm, self.les_filter * self.U_hat[2], self.W[2]) m = 0 for j in range(3): for i in range(j, 3): rfft3(self.comm, self.W[i] * self.W[j], UU_hat[m]) m += 1 # loop over 6 stress tensor components for m in range(6): tau_hat[5 - m] = H_244[m, 0] # constant coefficient # loop over 27 stencil points n = 1 for z in range(-1, 2): for y in range(-1, 2): for x in range(-1, 2): # compute stencil shift operator. # NOTE: dx = 2*pi/N for standard incompressible HIT # but really shift theorem needs 2*pi/N, not dx pos = np.array([z, y, x]) * self.dx pos.resize((3, 1, 1, 1)) shift = np.exp(1j * np.sum(self.K * pos, axis=0)) # 3 ui Volterra series components for i in range(2, 0, -1): tau_hat[5 - m] += H_244[m, n] * shift * self.U_hat[i] n += 1 # 6 uiuj collocated Volterra series components for p in range(6): tau_hat[5 - m] += H_244[m, n] * shift * UU_hat[5 - p] n += 1 self.W_hat[:] = 0.0 m = 0 for j in range(3): for i in range(j, 3): self.W_hat[i] += 1j * (1 + (i != j)) * self.K[j] * tau_hat[m] m += 1 self.dU += self.W_hat return
def computeSource_Smagorinsky_SGS(self, C1=-6.39e-2, **ignored): """ SPARSE SYMMETRIC TENSOR INDEXING: m == 0 -> ij == 00 m == 1 -> ij == 01 m == 2 -> ij == 02 m == 3 -> ij == 11 m == 4 -> ij == 12 m == 5 -> ij == 22 """ # -------------------------------------------------------------- # Explicitly filter the solution field self.W_hat[:] = self.les_filter*self.U_hat # -------------------------------------------------------------- # Compute S_ij and |S|^2 S_sqr = self.W[0] S_sqr[:] = 0.0 m = 0 for i in range(3): for j in range(i, 3): self.Aij[:] = 0.5j*self.K[j]*self.W_hat[i] if i == j: self.S[m] = irfft3(self.comm, 2*self.Aij) S_sqr += self.S[m]**2 else: self.Aji[:] = 0.5j*self.K[i]*self.W_hat[j] self.S[m] = irfft3(self.comm, self.Aij + self.Aji) S_sqr += 2*self.S[m]**2 m+=1 # -------------------------------------------------------------- # Compute C_1 Delta^2 |S| S_ij coef = self.W[1] coef[:] = C1*self.D_les**2*np.sqrt(2.0*S_sqr) # -------------------------------------------------------------- # Compute FFT{div(tau)} and add to RHS update m = 0 for i in range(3): for j in range(i, 3): rfft3(self.comm, coef*self.S[m], self.tau_ij_hat) self.dU[i] -= 1j*self.K[j]*self.tau_ij_hat if i != j: self.dU[j] -= 1j*self.K[i]*self.tau_ij_hat m+=1 return
def computeSource_Smagorinsky_SGS(self, Cs=1.2, **ignored): """ Smagorinsky Model (takes Cs as input) Takes one keyword argument: Cs: (float, optional), Smagorinsky constant """ # -------------------------------------------------------------- # Explicitly filter the solution field self.W_hat[:] = self.les_filter * self.U_hat for i in range(3): for j in range(3): self.S[i, j] = irfft3( self.comm, 1j * self.K[j] * self.W_hat[i] + 1j * self.K[i] * self.W_hat[j]) # -------------------------------------------------------------- # compute the leading coefficient, nu_T = 2|S|(Cs*D)**2 nuT = self.W[0] nuT[:] = np.sqrt(np.sum(np.square(self.S), axis=(0, 1))) nuT *= (Cs * self.D_les)**2 # -------------------------------------------------------------- # Compute FFT{div(tau)} and add to RHS update self.W_hat[:] = 0.0 for i in range(3): for j in range(3): self.W_hat[i] += 1j * self.K[j] * rfft3( self.comm, nuT * self.S[i, j]) self.dU += self.W_hat return
def computeSource_Smagorinksy_SGS(self, Cs=1.2, **ignored): """ Smagorinsky Model (takes Cs as input) Takes one keyword argument: Cs: (float, optional), Smagorinsky constant """ self.W_hat[:] = self.les_filter*self.U_hat for i in range(3): for j in range(3): self.A[j, i] = 0.5*irfft3(self.comm, 1j*(self.K[j]*self.W_hat[i] +self.K[i]*self.W_hat[j])) # compute SGS flux tensor, nuT = 2|S|(Cs*D)**2 nuT = self.W[0] nuT = np.sqrt(2.0*np.sum(np.square(self.A), axis=(0, 1))) nuT*= 2.0*(Cs*self.D_les)**2 self.W_hat[:] = 0.0 for i in range(3): for j in range(3): self.W_hat[i]+= 1j*self.K[j]*rfft3(self.comm, self.A[j, i]*nuT) self.dU += self.W_hat return
def compute_pressure(self): K = self.K U_hat = self.U_hat U = self.U omega = self.omega comm = self.comm # P_hat = np.empty_like(U_hat) # this is a vector field! # P = np.empty_like(U) # this is a vector field! # -------------------------------------------------------------- # take curl of velocity and inverse transform to get vorticity irfft3(comm, 1j * (K[1] * U_hat[2] - K[2] * U_hat[1]), omega[0]) irfft3(comm, 1j * (K[2] * U_hat[0] - K[0] * U_hat[2]), omega[1]) irfft3(comm, 1j * (K[0] * U_hat[1] - K[1] * U_hat[0]), omega[2]) # -------------------------------------------------------------- # compute the convective transport as the physical-space # cross-product of vorticity and velocity rfft3(comm, U[1] * omega[2] - U[2] * omega[1], self.dU[0]) rfft3(comm, U[2] * omega[0] - U[0] * omega[2], self.dU[1]) rfft3(comm, U[0] * omega[1] - U[1] * omega[0], self.dU[2]) P_hat = -1j * np.sum( self.dU * self.K_Ksq, axis=0 ) # this doesn't fill P_hat, it points the name "P_hat" to a new scalar field array! # irfft3(comm, P_hat, P) this is the wrong array size (see above)! return irfft3(comm, P_hat, self.Pres)
def computeAD_vorticity_form(self, **ignored): """ Computes right-hand-side (RHS) advection and diffusion term of the incompressible Navier-Stokes equations using a vorticity formulation for the advection term. This function overwrites the previous contents of self.dU. """ K = self.K U_hat = self.U_hat U = self.U omega = self.omega comm = self.comm # --------------------------------------------------------------# # take curl of velocity and inverse transform to get vorticity # # --------------------------------------------------------------# irfft3(comm, 1j * (K[1] * U_hat[2] - K[2] * U_hat[1]), omega[0]) irfft3(comm, 1j * (K[2] * U_hat[0] - K[0] * U_hat[2]), omega[1]) irfft3(comm, 1j * (K[0] * U_hat[1] - K[1] * U_hat[0]), omega[2]) # -------------------------------------------------------------- # compute the convective transport as the physical-space # cross-product of vorticity and velocity rfft3(comm, U[1] * omega[2] - U[2] * omega[1], self.dU[0]) rfft3(comm, U[2] * omega[0] - U[0] * omega[2], self.dU[1]) rfft3(comm, U[0] * omega[1] - U[1] * omega[0], self.dU[2]) # -------------------------------------------------------------- # add the diffusive transport term self.dU -= self.nu * self.Ksq * self.U_hat return
def initialize_HIT_random_spectrum(self, Einit=None, kexp=-5./6., kpeak=None, rseed=None): """ Generates a random, incompressible, velocity initial condition with a scaled Gamie-Ostriker isotropic turbulence spectrum """ if Einit is None: Einit = 0.72*(self.epsilon*self.L.max())**(2./3.) # the constant of 0.72 is empirically-based if kpeak is None: a = self.L/self.L.min() # domain size aspect ratios kpeak = np.max((self.nx//8)/a) # this gives kmax/4 self.compute_random_HIT_spectrum(kexp, kpeak, rseed) # Solenoidally-project, U_hat*(1-ki*kj/k^2) self.W_hat -= np.sum(self.W_hat*self.K_Ksq, axis=0)*self.K # - Third, scale to Einit irfft3(self.comm, self.W_hat[0], self.U[0]) irfft3(self.comm, self.W_hat[1], self.U[1]) irfft3(self.comm, self.W_hat[2], self.U[2]) Urms = sqrt(2.0*Einit) self.U *= Urms*sqrt(self.Nx/self.comm.allreduce(psum(self.U**2))) # transform to finish initial conditions rfft3(self.comm, self.U[0], self.U_hat[0]) rfft3(self.comm, self.U[1], self.U_hat[1]) rfft3(self.comm, self.U[2], self.U_hat[2]) return
def compute_pressure(self): K = self.K U_hat = self.U_hat U = self.U omega = self.omega comm = self.comm P_hat = np.empty_like(U_hat) P = np.empty_like(U) # -------------------------------------------------------------- # take curl of velocity and inverse transform to get vorticity irfft3(comm, 1j * (K[1] * U_hat[2] - K[2] * U_hat[1]), omega[0]) irfft3(comm, 1j * (K[2] * U_hat[0] - K[0] * U_hat[2]), omega[1]) irfft3(comm, 1j * (K[0] * U_hat[1] - K[1] * U_hat[0]), omega[2]) # -------------------------------------------------------------- # compute the convective transport as the physical-space # cross-product of vorticity and velocity rfft3(comm, U[1] * omega[2] - U[2] * omega[1], self.dU[0]) rfft3(comm, U[2] * omega[0] - U[0] * omega[2], self.dU[1]) rfft3(comm, U[0] * omega[1] - U[1] * omega[0], self.dU[2]) P_hat = -1j * np.sum(self.dU * self.K_Ksq, axis=0) irfft3(comm, P_hat, P) return P
def initialize_Taylor_Green_vortex(self): """ Generates the Taylor-Green vortex velocity initial condition """ self.U[0] = np.sin(self.X[0])*np.cos(self.X[1])*np.cos(self.X[2]) self.U[1] =-np.cos(self.X[0])*np.sin(self.X[1])*np.cos(self.X[2]) self.U[2] = 0.0 rfft3(self.comm, self.U[0], self.U_hat[0]) rfft3(self.comm, self.U[1], self.U_hat[1]) rfft3(self.comm, self.U[2], self.U_hat[2]) return
def computeSource_4termGEV_SGS(self, C=None, **ignored): """ Empty Docstring! """ # -------------------------------------------------------------- # Explicitly filter the solution field self.W_hat[:] = self.les_filter*self.U_hat # -------------------------------------------------------------- # Compute S_ij, R_ij, S_kl S_kl, R_kl R_kl, and |S| S = self.S R = self.R S_mod = self.W[0] S_sqr = self.W[1] R_sqr = self.W[2] S_sqr[:] = 0.0 R_sqr[:] = 0.0 for i in range(3): for j in range(3): self.Aij[:] = 0.5j*self.K[j]*self.W_hat[i] if i == j: S[i, j] = irfft3(self.comm, 2*self.Aij) R[i, j] = 0.0 else: self.Aji[:] = 0.5j*self.K[i]*self.W_hat[j] S[i, j] = irfft3(self.comm, self.Aij + self.Aji) R[i, j] = irfft3(self.comm, self.Aij - self.Aji) S_sqr += S[i, j]**2 R_sqr += R[i, j]**2 S_mod[:] = np.sqrt(2.0*S_sqr) # -------------------------------------------------------------- # Compute tau_ij = Delta**2 C_m G_ij^m and update RHS for i in range(3): for j in range(3): # G_ij^1 = |S| S_ij self.tau_ij[:] = C[0]*S_mod*S[i, j] # G_ij^2 = -(S_ik R_jk + R_ik S_jk) self.tau_ij -= C[1]*np.sum(S[i]*R[j] + S[j]*R[i], axis=0) # G_ij^3 = S_ik S_jk - 1/3 delta_ij S_kl S_kl self.tau_ij += C[2]*np.sum(S[i]*S[j], axis=0) if i == j: self.tau_ij -= C[2]*(1/3)*S_sqr # G_ij^4 = - R_ik R_jk - 1/3 delta_ij R_kl R_kl self.tau_ij -= C[3]*np.sum(R[i]*R[j], axis=0) if i == j: self.tau_ij -= C[3]*(1/3)*R_sqr rfft3(self.comm, self.tau_ij, self.tau_ij_hat) self.dU[i] -= 1j*self.K[j]*self.tau_ij_hat return
def computeSource_ales244_SGS(self, H_244, **ignored): """ h_ij Fortran column-major ordering: 11,12,13,22,23,33 equivalent ordering for spectralLES: 22,21,20,11,10,00 sparse tensor indexing for ales244_solver UU_hat and tau_hat: m == 0 -> ij == 22 m == 1 -> ij == 21 m == 2 -> ij == 20 m == 3 -> ij == 11 m == 4 -> ij == 10 m == 5 -> ij == 00 H_244 - ALES coefficients h_ij for 244-term Volterra series truncation. H_244.shape = (6, 244) """ tau_hat = self.tau_hat UU_hat = self.UU_hat W_hat = self.W_hat W_hat[:] = self.les_filter * self.U_hat irfft3(self.comm, W_hat[0], self.W[0]) irfft3(self.comm, W_hat[1], self.W[1]) irfft3(self.comm, W_hat[2], self.W[2]) m = 0 for i in range(2, -1, -1): for j in range(i, -1, -1): rfft3(self.comm, self.W[i] * self.W[j], UU_hat[m]) m += 1 # loop over 6 stress tensor components for m in range(6): tau_hat[m] = H_244[m, 0] # constant coefficient # loop over 27 stencil points n = 1 for z in range(-1, 2): for y in range(-1, 2): for x in range(-1, 2): # compute stencil shift operator. # NOTE: dx = 2*pi/N for standard incompressible HIT # but really shift theorem needs 2*pi/N, not dx pos = np.array([z, y, x]) * self.dx pos.resize((3, 1, 1, 1)) shift = np.exp(1j * np.sum(self.K * pos, axis=0)) # 3 ui Volterra series components for i in range(2, -1, -1): tau_hat[m] += H_244[m, n] * shift * W_hat[i] n += 1 # 6 uiuj collocated Volterra series components for p in range(6): tau_hat[m] += H_244[m, n] * shift * UU_hat[p] n += 1 m = 0 for i in range(2, -1, -1): for j in range(i, -1, -1): self.dU[i] -= 1j * self.K[j] * tau_hat[m] if i != j: self.dU[j] -= 1j * self.K[i] * tau_hat[m] m += 1 return