def shearstressGS(tau_order, data, submodule=None, friction='Roughness'): # Shear stress following the formulation of Schramkowski; only subtidal sf and subtidal second order friction jmax = data.v('grid', 'maxIndex', 'x') kmax = data.v('grid', 'maxIndex', 'z') fmax = data.v('grid', 'maxIndex', 'f') rho0 = data.v('RHO0') sf = data.v(friction, range(0, jmax+1), 0, 0) if submodule is None: submodule = (None, )*(tau_order+1) ## 1. bed shear stress # the bed shear stress is extended over fmax+1 frequency components to prevent inaccuracies in truncation ulist = [] for i in range(0, tau_order+1): if submodule[i] is None: u = data.v('u'+str(i), range(0, jmax+1), [kmax], range(0, fmax+1)) else: u = data.v('u'+str(i), submodule[i], range(0, jmax+1), [kmax], range(0, fmax+1)) if u is None: u = np.zeros((jmax+1, 1, fmax+1), dtype=complex) ulist.append(u) taub_abs = np.zeros((jmax+1, 1, fmax+1), dtype=complex) if tau_order == 0: # uabs0 = ny.absoluteU(ulist[0][:, 0, 1]+10**-6, 0) # uabs2 = ny.absoluteU(ulist[0][:, 0, 1]+10**-6, 2)+np.conj(ny.absoluteU(ulist[0][:, 0, 1]+10**-6, -2)) uabs0 = ny.absoluteU(ulist[0][:, 0, 1], 0) uabs2 = ny.absoluteU(ulist[0][:, 0, 1], 2) + np.conj(ny.absoluteU(ulist[0][:, 0, 1], -2)) taub_abs[:, 0, 0] = rho0*sf*uabs0 taub_abs[:, 0, 2] = rho0*sf*uabs2 elif tau_order ==1: signu = np.zeros((jmax+1, 1, np.maximum(fmax+1, 4)), dtype=complex) # signu[:, 0, 1] = ny.signU(ulist[0][:, 0, 1]+10**-6, 1) + np.conj(ny.signU(ulist[0][:, 0, 1]+10**-6, -1)) # signu[:, 0, 3] = ny.signU(ulist[0][:, 0, 1]+10**-6, 3) + np.conj(ny.signU(ulist[0][:, 0, 1]+10**-6, -3)) signu[:, 0, 1] = ny.signU(ulist[0][:, 0, 1], 1) + np.conj(ny.signU(ulist[0][:, 0, 1], -1)) signu[:, 0, 3] = ny.signU(ulist[0][:, 0, 1], 3) + np.conj(ny.signU(ulist[0][:, 0, 1], -3)) if fmax+1 < 4: ulist[1] = np.concatenate((ulist[1], np.zeros((jmax+1, 1, 4-fmax-1))), 2) taub_abs = rho0*sf.reshape((jmax+1, 1, 1))*ny.complexAmplitudeProduct(ulist[1], signu, 2) elif tau_order ==2: utid = ny.invfft2(ulist[0], 2, 90) ucomb = ny.invfft2(ulist[0]+ulist[1], 2, 90) uabs_tid = np.mean(np.abs(utid), axis=2) uabs_tot = np.mean(np.abs(ucomb), axis=2) uabs_eps = (uabs_tot - uabs_tid).reshape((jmax+1)) taub_abs[:, 0, 0] = rho0*sf*uabs_eps return taub_abs[:, :, :fmax+1]
def shearstress_truncated(tau_order, data, submodule=None, friction='Roughness'): # Shear stress derived using time series (truncated, only for standard forcing conditions) jmax = data.v('grid', 'maxIndex', 'x') kmax = data.v('grid', 'maxIndex', 'z') fmax = data.v('grid', 'maxIndex', 'f') rho0 = data.v('RHO0') sf = data.v(friction, range(0, jmax+1), 0, 0) if submodule is None: submodule = (None, )*(tau_order+1) ## 1. bed shear stress # the bed shear stress is extended over fmax+1 frequency components to prevent inaccuracies in truncation ulist = [] for i in range(0, 2): if submodule[i] is None: u = data.v('u'+str(i), range(0, jmax+1), [kmax], range(0, fmax+1)) else: u = data.v('u'+str(i), submodule[i], range(0, jmax+1), [kmax], range(0, fmax+1)) if u is None: u = np.zeros((jmax+1, 1, fmax+1), dtype=complex) ulist.append(u) u = sum(ulist) utim = ny.invfft2(u, 2, 90) utim = np.abs(utim) uabs = ny.fft(utim, 2)[:, :, :fmax+1] taub_abs = rho0*sf.reshape((jmax+1, 1, 1))*uabs if tau_order == 0: taub_abs[:, :, 1] =0 elif tau_order == 1: taub_abs[:, :, 0] =0 taub_abs[:, :, 2] =0 else: taub_abs[:, :, 1:] =0 return taub_abs
def positivity_correction(self, quantity, value_currentorder, order, include_vertical): """Correct 'quantity' so that its total over all orders does not become negative. Do this by reducing the time-varying components at the current order Parameters: quantity (str) - name of the quantity without order marking (assumes that leading order is not marked with a number, e.g. Av, Av1, Av2 ..) value_currentorder (int) - value of quantity at current order of computation order (int or None) - current order of computation (None=truncation) include_vertical (bool) - is there a vertical dependency? Returns: corrected result of 'quantity' only at the current order """ # Init jmax = self.input.v('grid', 'maxIndex', 'x') if include_vertical: kmax = self.input.v('grid', 'maxIndex', 'z') else: kmax = 0 value_currentorder = value_currentorder.reshape((value_currentorder.shape[0], 1, value_currentorder.shape[-1])) fmax = self.input.v('grid', 'maxIndex', 'f') # Add all eddy viscosity components if order is None: Av = value_currentorder # use value supplied in truncation method else: Av = 0 + 0*1j for i in range(0, order): # take all lower orders from DC, then add the value at current order if i==0: ordstr = quantity else: ordstr = quantity+str(order) Av += self.input.v(ordstr, range(0, jmax+1), range(0, kmax+1), range(0, fmax+1)) Av += value_currentorder # make a time series with 100 time steps Avt = ny.invfft2(Av, len(Av.shape)-1, 90) - Av[:, :, [0]] # correct by reducing time varying components if not np.min(np.real(np.minimum(1, abs(Av[:, :, 0])/(-np.min(Avt, axis=-1)+10**-10))))==1.: value_currentorder[:, :, 1:] = value_currentorder[:, :, 1:]*np.real(np.minimum(1, .95*abs(Av[:, :, 0])/(-np.min(Avt, axis=-1)+10**-10)).reshape((jmax+1, kmax+1, 1))) # value_currentorder = self.positivity_correction(quantity, value_currentorder, include_vertical) if not include_vertical: value_currentorder = value_currentorder.reshape((value_currentorder.shape[0], value_currentorder.shape[-1])) return value_currentorder
def run(self): ################################################################################################################ ## 1. Init ################################################################################################################ # self.timers[0].tic() ## prepare output message self.logger.info('Running MAW turbulence model') denstr = '' if self.betac ==0: denstr = '- not including density effects' self.logger.info('\tMAW rel. difference in Av in last iteration: %s %s' % (self.difference, denstr)) d = {} jmax = self.input.v('grid', 'maxIndex', 'x') kmax = self.input.v('grid', 'maxIndex', 'z') fmax = self.input.v('grid', 'maxIndex', 'f') G = self.input.v('G') rho0 = self.input.v('RHO0') uzmin = self.input.v('uzmin') Avold = self.input.v('Av', range(0, jmax+1), range(0, kmax+1), range(0, fmax+1)) Kvold = self.input.v('Kv', range(0, jmax+1), range(0, kmax+1), range(0, fmax+1)) sfold = self.input.v('Roughness', range(0, jmax+1), 0, 0) # self.timers[0].toc() ################################################################################################################ ## 2. KEFitted run ################################################################################################################ # self.timers[1].tic() d.update(self.kem.run()) self.input.merge(d) # load data resulting from KEFitted model Avmid = self.input.v('Av', range(0, jmax+1), range(0, kmax+1), range(0, fmax+1)) Kvmid = Avmid/self.input.v('sigma_rho', range(0, jmax+1), range(0, kmax+1), [0]) sfmid = self.input.v('Roughness', range(0, jmax+1), 0, 0) # self.timers[1].toc() ################################################################################################################ ## 3. Density effects ################################################################################################################ if self.betac == 0: # no density effect included, first let KEFitted spin up Av0 = Avmid[:, :, 0] Kv0 = Kvmid[:, :, 0] sf = sfmid Cd = 1. MA_av = 1. MA_kv = 1. Ri = 0. else: ## Load data # self.timers[2].tic() cz = self.input.d('c0', range(0, jmax+1), range(0, kmax+1), range(0, fmax+1), dim='z') + self.input.d('c1', range(0, jmax+1), range(0, kmax+1), range(0, fmax+1), dim='z') + self.input.d('c2', range(0, jmax+1), range(0, kmax+1), range(0, fmax+1), dim='z') uz = self.input.d('u0', range(0, jmax+1), range(0, kmax+1), range(0, fmax+1), dim='z') + self.input.d('u1', range(0, jmax+1), range(0, kmax+1), range(0, fmax+1), dim='z') zeta0 = self.input.v('zeta0', range(0, jmax+1), [0], range(0, fmax+1)) H = self.input.v('grid', 'low', 'z', range(0, jmax+1)) - self.input.v('grid', 'high', 'z', range(0, jmax+1)) # self.timers[2].tic() ## Convert to time series # self.timers[3].tic() cz = ny.invfft2(cz, 2, 90) uz = ny.invfft2(uz, 2, 90) zeta0 = ny.invfft2(zeta0, 2, 90) Avmid = ny.invfft2(Avmid, 2, 90) Kvmid = ny.invfft2(Kvmid, 2, 90) # self.timers[3].toc() # self.timers[4].tic() uzmin = np.ones(uz.shape)*uzmin ## Compute Richardson number Ri = -G*self.betac/rho0*cz/(uz**2+uzmin**2) Rida = 1./H.reshape((jmax+1, 1, 1))*ny.integrate(Ri.reshape((jmax+1, kmax+1, 1, Ri.shape[-1])), 'z', kmax, 0, self.input.slice('grid')).reshape((jmax+1, 1, Ri.shape[-1])) # depth-average Rida += zeta0*(Ri[:, [0], :]-Rida)/H.reshape((jmax+1, 1, 1)) # depth average continued Rida0 = np.maximum(Rida, 0.) # only accept positive Ri Ribed = np.maximum(Ri[:, [-1], :], 0.) # only accept positive Ri Ribedmax = self.input.v('Ribedmax') Ribed = np.minimum(Ribed, Ribedmax) # 5-3-2018 limit near-bed Ri ## relaxation on Rida if hasattr(self, 'Rida'): # Relaxation of Ri_da using previously saved value dRida = self.Rida - Rida0 Rida = np.max((Rida0, np.min((dRida * (1 - self.RELAX), dRida * (1. + self.RELAX)), axis=0) + Rida0), axis=0) Rida = np.min((Rida, np.max((dRida * (1 - self.RELAX), dRida * (1. + self.RELAX)), axis=0) + Rida0), axis=0) self.Rida = Rida else: # No value saved if the init found an available Ri. Then start with full signal computed here (do not use saved value, as this has been truncated to frequency components) Rida = Rida0 # self.timers[4].toc() ## Compute damping functions # self.timers[5].tic() # Av MA_av = (1+10*Rida)**(-0.5) dAv = ny.fft(Avmid*MA_av, 2)[:, :, :fmax+1] - Avold Av = Avold + (1-self.RELAX)*(self.LOCAL*dAv + .5*(1-self.LOCAL)*dAv[[0]+range(0, jmax), :, :] + .5*(1-self.LOCAL)*dAv[range(1, jmax+1)+[jmax], :, :]) # Av = Avold + dAv Av0 = Av[:, :, 0] # Kv MA_kv = (1+3.33*Rida)**(-1.5) dKv = ny.fft(Kvmid*MA_kv, 2)[:, :, :fmax+1] - Kvold Kv = Kvold + (1-self.RELAX)*(self.LOCAL*dKv + .5*(1-self.LOCAL)*dKv[[0]+range(0, jmax), :, :] + .5*(1-self.LOCAL)*dKv[range(1, jmax+1)+[jmax], :, :]) # Kv = Kvold + dKv Kv0 = Kv[:, :, 0] # Sf Rfmean = np.mean(Ribed[:, 0, :]*(Kvmid*MA_kv)[:, 0, :]/(Avmid*MA_av)[:, 0, :], axis=-1) Cd = (1+5.5*Rfmean)**-2. damp_sf = Cd sf = sfmid*damp_sf dsf = sf - sfold sf = sfold + (1-self.RELAX)*(self.LOCAL*dsf + .5*(1-self.LOCAL)*dsf[[0]+range(0, jmax)] + .5*(1-self.LOCAL)*dsf[range(1, jmax+1)+[jmax]]) # self.timers[5].toc() # process for output MA_av = ny.fft(MA_av, 2)[:, :, :fmax+1] MA_kv = ny.fft(MA_kv, 2)[:, :, :fmax+1] Ri = ny.fft(Ri, 2)[:, :, :fmax+1] ################################################################################################################ ## Reference level ################################################################################################################ if self.referenceLevel == 'True': self.input.merge({'Av': Av0, 'Roughness': sf}) d['R'] = self.kem.RL.run()['R'] ################################################################################################################ ## Compute difference ################################################################################################################ Av0s = ny.savitzky_golay(Av0[:, 0], self.filterlength, 1) difference = np.max(abs(Av0s-Avold[:, 0, 0])/abs(Av0s+10**-4)) self.difference = copy.copy(difference) ############################################################################################################### # DEBUG plots ############################################################################################################### # import matplotlib.pyplot as plt # import step as st # x = ny.dimensionalAxis(self.input.slice('grid'), 'x')[:, 0,0] # # if self.betac > 0 and np.mod(self.iteration, 3)==0: # and self.difference>0.15: # st.configure() # plt.figure(1, figsize=(2,2)) # plt.subplot(1,2,1) # plt.plot(x/1000., Avold[:, 0, 0], label='old') # plt.plot(x/1000., Av0[:, 0], label='new') # plt.ylim(0, np.maximum(np.max(Av0[:, 0]), np.max(Avold[:, 0, 0]))) # plt.legend() # # plt.subplot(1,2,2) # plt.plot(x/1000., Avold[:, 0, 0]-Av0[:, 0]) # plt.twinx() # plt.plot(x/1000., self.input.v('f', range(0, jmax+1)), color='grey') # plt.ylim(0, 1.) # # st.save('plot_'+str(len(x))+'_'+str(self.iteration)) # # plt.figure(2, figsize=(1, 2)) # ws = self.input.v('ws0', range(0, jmax+1), 0, 0) # plt.plot(x/1000., ws) # # st.save('ws_'+str(len(x))+'_'+str(self.iteration)) ################################################################################################################ ## Prepare Output ################################################################################################################ # self.timers[6].tic() x = ny.dimensionalAxis(self.input.slice('grid'),'x')[:, 0,0] nf = ny.functionTemplates.NumericalFunctionWrapper(ny.savitzky_golay(Av0[:, 0], self.filterlength, 1).reshape((jmax+1, 1)), self.input.slice('grid')) nf.addDerivative(ny.savitzky_golay(ny.derivative(Av0[:, 0], 'x', self.input), self.filterlength, 1).reshape((jmax+1, 1)), 'x') nf.addDerivative(ny.savitzky_golay(ny.secondDerivative(Av0[:, 0], 'x', self.input), self.filterlength, 1).reshape((jmax+1, 1)), 'xx') d['Av'] = nf.function nf2 = ny.functionTemplates.NumericalFunctionWrapper(ny.savitzky_golay(Kv0[:, 0], self.filterlength, 1).reshape((jmax+1, 1)), self.input.slice('grid')) nf2.addDerivative(ny.savitzky_golay(ny.derivative(Kv0[:, 0], 'x', self.input), self.filterlength, 1).reshape((jmax+1, 1)), 'x') nf2.addDerivative(ny.savitzky_golay(ny.secondDerivative(Kv0[:, 0], 'x', self.input), self.filterlength, 1).reshape((jmax+1, 1)), 'xx') d['Kv'] = nf2.function d['Roughness'] = sf d['skin_friction'] = sfmid d['dampingFunctions'] = {} d['dampingFunctions']['Roughness'] = Cd d['dampingFunctions']['Av'] = MA_av d['dampingFunctions']['Kv'] = MA_kv d['Ri'] = Ri # self.timers[6].toc() ## Timers # self.timers[0].disp('0 init MAW') # self.timers[1].disp('1 KEFitted') # self.timers[2].disp('2 load data') # self.timers[3].disp('3 invfft') # self.timers[4].disp('4 Ri') # self.timers[5].disp('5 Compute Av, Kv, sf') # self.timers[6].disp('6 Load in dict') return d
def profileSelection(self, uabs, uabsH, order): """ Go through a menu with turbulence profiles and roughness parameters. Selects the correct one based on the input Then determines the coefficients and prepares the functions for the eddy viscosity and Roughness Parameters: uabs (array) - approximation of the absolute depth-averaged velocity (at the current order) uabsH (array) - approximation of the absolute depth-averaged velocity * depth (at the current order) order (int or None) - current order of the calculation Returns: prepared functions for Av and the roughness parameter of choice. Also returns the type of boundary condition """ # Init jmax = self.input.v('grid', 'maxIndex', 'x') fmax = self.input.v('grid', 'maxIndex', 'f') if order < 1: Avmin = self.Avmin # Make a new data container with the roughness parameter with the depth-scaling param n incorporated data = self.input.slice('grid') data.addData('coef', self.input.v(self.roughnessParameter)) roughness = self.input.slice('grid') roughness.addData('Roughness', UniformX('x', data, self.n).function) ################################################################################################################ # Select the correct profile and roughness parameter: # 1. Uniform # 1a. uniform + sf0 (linear) # 1b. uniform + z0*(non-linear) ################################################################################################################ ################################################################################################################ ## case 1a: uniform + sf0 (linear) ################################################################################################################ if self.roughnessParameter == 'sf0': ## 1. Eddy viscosity Av0 = np.zeros((jmax + 1, fmax + 1), dtype=complex) # truncated model if order == None: depth = np.zeros((jmax+1, fmax+1), dtype=complex) depth[:, 0] = self.input.v('grid', 'low', 'z', range(0,jmax+1)) - self.input.v('grid', 'high', 'z', range(0,jmax+1)) i = 0 while self.input.v('zeta'+str(i)) and i <= self.truncationorder: depth += self.input.v('zeta'+str(i), range(0, jmax+1), 0, range(0, fmax+1)) for submod in self.ignoreSubmodule: try: depth -= self.input.v('zeta'+str(i), submod, range(0, jmax+1), 0, range(0, fmax+1)) except: pass i += 1 Av0[:, :] = 0.49 * roughness.v('Roughness', range(0, jmax + 1), 0, [0]) * depth # Leading order elif order == 0: depth = self.input.v('grid', 'low', 'z', x=0) - self.input.v('grid', 'high', 'z', x=0) Av0[:, 0] = 0.49 * roughness.v('Roughness', range(0, jmax + 1)) * depth # Higher orders else: depth = self.input.v('zeta'+str(order-1), range(0, jmax+1), 0, range(0, fmax+1)) for submod in self.ignoreSubmodule: try: depth -= self.input.v('zeta'+str(order-1), submod, range(0, jmax+1), 0, range(0, fmax+1)) except: pass Av0[:, :] = 0.49 * roughness.v('Roughness', range(0, jmax + 1)).reshape((jmax+1, 1)) * self.input.v('zeta'+str(order-1), range(0, jmax+1), 0, range(0, fmax+1)) # background eddy viscosity if order < 1: # i.e. None or 0 Av0[:, 0] = np.maximum(Av0[:, 0], Avmin) # adjust time dependence (NB no risk of negative eddy viscosity if zeta < H+R) Av0[:, 1:] = self.timedependence*Av0[:, 1:] # put data in output variables data = self.input.slice('grid') data.addData('coef', Av0) if order == 0: Av = UniformXF(['x', 'f'], data, 1.).function else: Av = UniformXF(['x', 'f'], data, 0.).function ## 2. Roughness if order == 0 or order==None: sf0 = np.zeros((jmax + 1, fmax + 1)) sf0[:, 0] = roughness.v('Roughness', range(0, jmax+1)) dataRough = self.input.slice('grid') dataRough.addData('coef', sf0) roughness = UniformXF(['x', 'f'], dataRough, 0.).function else: roughness = 0. ## 3. Boundary type BottomBC = 'PartialSlip' # No iteration required unless the reference level is computed if self.referenceLevel == 'False': self.difference = 0. ################################################################################################################ ## case 1b: constant + z0* (non-linear) ################################################################################################################ elif self.roughnessParameter in ['z0*']: # 1. prepare coefficients z0st = roughness.v('Roughness', range(0, jmax + 1)) Cd_div_k2 = (((1. + z0st) * np.log(1. / z0st + 1) - 1) ** -2.).reshape(jmax + 1, 1) Av0 = 0.10 / 0.636 * Cd_div_k2 * uabsH[:, 0, :] sf0 = 0.22 / 0.636 * Cd_div_k2 * uabs[:, 0, :] # background eddy viscosity if order < 1: # i.e. None or 0 Av0[:, 0] = np.maximum(Av0[:,0], self.Avmin) depth = self.input.v('grid', 'low', 'z', x=0) - self.input.v('grid', 'high', 'z', x=0) sf0[:, 0] = np.maximum(sf0[:,0], self.Avmin*2/depth) # minimum sf = 2*Avmin/H (relation from case 1a) # remove time dependence if required Av0[:, 1:] = self.timedependence*Av0[:, 1:] sf0[:, 1:] = self.timedependence*sf0[:, 1:] # correct possible negative eddy viscosity Av0 = self.positivity_correction('Av', Av0, order, False) sf0 = self.positivity_correction('Roughness', sf0, order, False) sf0t = ny.invfft2(sf0, 1, 90) Av0t = ny.invfft2(Av0, 1, 90) ind = 0 while (sf0t<0).any() and ind < 50: sf0 = self.positivity_correction('Roughness', sf0, order, False) sf0t = ny.invfft2(sf0, 1, 90) ind += 1 if ind == 50: raise KnownError('sf not sufficiently corrected for positivity') ind = 0 while (Av0t<0).any() and ind < 50: Av0 = self.positivity_correction('Av', Av0, order, False) Av0t = ny.invfft2(Av0, 1, 90) ind += 1 if ind == 50: raise KnownError('Av not sufficiently corrected for positivity') # 2. prepare smaller DataContainers data = self.input.slice('grid') data.addData('coef', Av0) dataRough = self.input.slice('grid') dataRough.addData('coef', sf0) # 3. prepare functions Av = UniformXF(['x', 'f'], data, 0.).function roughness = UniformXF(['x', 'f'], dataRough, 0.).function BottomBC = 'PartialSlip' else: raise KnownError('Combination of turbulence profile and roughness parameter is not implemented') return Av, roughness, BottomBC