def Nsq_forcing_from_Q(E,datetime_in=None,debug=False,hostname='taurus'): """ Birner (2010) used the thermodynamic equation in the TEM form to derive an expression for the rate of change of static stability (N2) due to residual motion and diabatic heating. This subroutine compares the term due to diabatic heating, i.e.: g d(Q/theta)dz INPUTS: E: a DART experiment dictionary. Relevant fields are: E['exp_name'] - the experiment name E['daterange'] - helps to choose which date to load in case this isn't specifically given E['variable'] - this determines what kind of diabatic heating we use: the value of E['variable'] should be a string like 'Nsq_forcing_XXXXX' where XXXXX is the model variable corresponding to whatever diabatic heating type we are looking for. For example, in WACCM, 'QRL_TOT' is the total longwave heating, so to get the N2 forcing from that, just set E['variable']='Nsq_forcing_QRL_TOT' datetime_in: the date for which we want to compute this diagnostic. default is None -- in this case, just choose the fist date in E['daterange'] OUTPUTS: N2_forcing: Nsquared forcing term in s^2/day lev lat """ # necessary constants H=7000.0 # scale height in m p0=1000.0 # reference pressure in hPa g=9.8 # acceleration of gravity # load the desired diabatic heating term # this is not typically part of the DART output, so load from model history files # (right now this really only works for WACCM/CAM) Qstring = E['variable'].strip('Nsq_forcing_') EQ = E.copy() EQ['variable']=Qstring Q2,lat,lon,lev = DSS.compute_DART_diagn_from_model_h_files(EQ,datetime_in,verbose=debug) # remove the time dimension, which should have length 1 Q = np.squeeze(Q2) # also load potential temperature ET = E.copy() ET['variable']='theta' lev,lat,lon,theta2,P0,hybm,hyam = dart.load_DART_diagnostic_file(ET,datetime_in,hostname=hostname,debug=debug) # squeeze out extra dims, which we get if we load single copies (e.g. ensemble mean) theta = np.squeeze(theta2) # now find the longitude dimension and average over it # for both Q and theta nlon=len(lon) Mean_arrays = [] for A in [Q,theta]: for idim,s in enumerate(A.shape): if s == nlon: londim=idim Mean_arrays.append(np.average(A,axis=londim)) Q_mean=Mean_arrays[0] theta_mean=Mean_arrays[1] # if the shapes don't match up, might have to transpose one of them # if Mean_arrays[1].shape[0] != Q_mean.shape[0]: # theta_mean=np.transpose(Mean_arrays[1]) # else: # theta_mean=Mean_arrays[1] # Q_mean should come out as copy x lev x lat, whereas theta_mean is copy x lat x lev # to manually transpose Q_mean Q_mean2 = np.zeros(shape=theta_mean.shape) if Q_mean2.ndim==3: for icopy in range(theta_mean.shape[0]): for ilat in range(theta_mean.shape[1]): for ilev in range(theta_mean.shape[2]): Q_mean2[icopy,ilat,ilev]=Q_mean[icopy,ilev,ilat] else: for ilat in range(theta_mean.shape[0]): for ilev in range(theta_mean.shape[1]): Q_mean2[ilat,ilev]=Q_mean[ilev,ilat] # divide Q by theta X = Q_mean2/theta_mean # convert pressure levels to approximate altitude and take the vertical gradient zlev = H*np.log(p0/lev) dZ = np.gradient(zlev) # gradient of vertical levels in m # now X *should* have shape (copy x lat x lev) OR (lat x lev) # so need to copy dZ to look like this if X.ndim==3: dZm = dZ[None,None,:] levdim=2 if X.ndim==2: dZm = dZ[None,:] levdim=1 dZ3 = np.broadcast_to(dZm,X.shape) dXdZ_3D = np.gradient(X,dZ3) dxdz = dXdZ_3D[levdim] # this is the vertical gradient with respect to height # the above calculation yields a quantity in units s^-2/s, but it makes more sense # in the grand scheme of things to look at buoyancy forcing per day, so here # is a conversion factor. seconds_per_day = 60.*60.*24.0 # now loop over ensemble members and compute the n2 forcing for each one N2_forcing = g*dxdz*seconds_per_day return N2_forcing,lat,lev
def ano(E,climatology_option = 'NODA',hostname='taurus',verbose=False): """ Compute anomaly fields relative to some climatology Inputs allowed for climatology_option: 'NODA': take the ensemble mean of the corresponding no-DA experiment as a 40-year climatology 'F_W4_L66': daily climatology of a CESM+WACCM simulation with realistic forcings, 1951-2010 None: don't subtract out anything -- just return the regular fields in the same shape as other "anomalies" """ # load climatology Xclim,lat,lon,lev,DR = load_climatology(E,climatology_option,hostname) # change the daterange in the anomalies to suit what was found for climatology if len(DR) != len(E['daterange']): print('Changing the experiment daterange to the dates found for the requested climatology') E['daterange'] = DR d1 = DR[0].strftime("%Y-%m-%d") d2 = DR[len(E['daterange'])-1].strftime("%Y-%m-%d") print('new daterange goes from '+d1+' to '+d2) # some climatologies are only available at daily resolution, so # in that case we have to change the daterange in E to be daily if (climatology_option == 'F_W4_L66'): d0 = E['daterange'][0] df = E['daterange'][len(E['daterange'])-1] days = df-d0 DRnew = dart.daterange(date_start=d0, periods=days.days+1, DT='1D') E['daterange'] = DRnew # load the desired model fields for the experiment Xlist = [] # empty list to hold the fields we retrieve for every day for date in E['daterange']: X,lat0,lon0,lev0 = DSS.compute_DART_diagn_from_model_h_files(E,date,hostname=hostname,verbose=verbose) if X is not None: Xs = np.squeeze(X) Xlist.append(Xs) lat = lat0 lon = lon0 lev = lev0 # check that the right vertical levels were loaded if verbose: print('------computing daily anomalies for the following vertical levels and variable:-------') print(lev) print(E['variable']) # compute anomalies: # for this we turn the model fields into a matrix and subtract from the climatology XX = np.concatenate([X[..., np.newaxis] for X in Xlist], axis=len(Xs.shape)) if climatology_option == None: AA = XX else: # if the climatology does not have shape lat x lon x lev x time, # run swapaxes 2x to get it as such # NOTE: this is still a kludge and probably wont work with all datasets - check this carefully # with your own data XclimS = np.squeeze(Xclim) nT = len(DRnew) lastdim = len(XclimS.shape)-1 for s,ii in zip(XclimS.shape,range(len(XclimS.shape))): if s == nT: time_dim = ii # if only retrieveing a single date, don't need to do any reshaping # but might need to squeeze out a length-one time dimension if nT == 1: XclimR = XclimS XX = np.squeeze(XX) else: # if time is the last dimension, don't need to reshape Xclim if time_dim == lastdim: XclimR = XclimS # if time is the first dimension, need to reshape Xclim if time_dim == 0: Xclim2 = XclimS.swapaxes(0,lastdim) XclimR = Xclim2.swapaxes(0,1) AA = XX-XclimR return AA,XclimR,lat,lon,lev,DR
def Nsq_forcing_from_Q(E, datetime_in=None, debug=False, hostname='taurus'): """ Birner (2010) used the thermodynamic equation in the TEM form to derive an expression for the rate of change of static stability (N2) due to residual motion and diabatic heating. This subroutine compares the term due to diabatic heating, i.e.: g d(Q/theta)dz INPUTS: E: a DART experiment dictionary. Relevant fields are: E['exp_name'] - the experiment name E['daterange'] - helps to choose which date to load in case this isn't specifically given E['variable'] - this determines what kind of diabatic heating we use: the value of E['variable'] should be a string like 'Nsq_forcing_XXXXX' where XXXXX is the model variable corresponding to whatever diabatic heating type we are looking for. For example, in WACCM, 'QRL_TOT' is the total longwave heating, so to get the N2 forcing from that, just set E['variable']='Nsq_forcing_QRL_TOT' datetime_in: the date for which we want to compute this diagnostic. default is None -- in this case, just choose the fist date in E['daterange'] OUTPUTS: N2_forcing: Nsquared forcing term in s^2/day lev lat """ # necessary constants H = 7000.0 # scale height in m p0 = 1000.0 # reference pressure in hPa g = 9.8 # acceleration of gravity # load the desired diabatic heating term # this is not typically part of the DART output, so load from model history files # (right now this really only works for WACCM/CAM) Qstring = E['variable'].strip('Nsq_forcing_') EQ = E.copy() EQ['variable'] = Qstring DQ = DSS.compute_DART_diagn_from_model_h_files(EQ, datetime_in, verbose=debug) # remove the time dimension, which should have length 1 DQ['data'] = np.squeeze(DQ['data']) # also load potential temperature ET = E.copy() ET['variable'] = 'theta' Dtheta = dart.load_DART_diagnostic_file(ET, datetime_in, hostname=hostname, debug=debug) # squeeze out extra dims, which we get if we load single copies (e.g. ensemble mean) Dtheta['data'] = np.squeeze(Dtheta['data']) # now find the longitude dimension and average over it # for both Q and theta Q_mean = DSS.average_over_named_dimension(DQ['data'], DQ['lon']) theta_mean = DSS.average_over_named_dimension(Dtheta['data'], Dtheta['lon']) # if the shapes don't match up, might have to transpose one of them # if Mean_arrays[1].shape[0] != Q_mean.shape[0]: # theta_mean=np.transpose(Mean_arrays[1]) # else: # theta_mean=Mean_arrays[1] # Q_mean should come out as copy x lev x lat, whereas theta_mean is copy x lat x lev # to manually transpose Q_mean Q_mean2 = np.zeros(shape=theta_mean.shape) if Q_mean2.ndim == 3: for icopy in range(theta_mean.shape[0]): for ilat in range(theta_mean.shape[1]): for ilev in range(theta_mean.shape[2]): Q_mean2[icopy, ilat, ilev] = Q_mean[icopy, ilev, ilat] else: for ilat in range(theta_mean.shape[0]): for ilev in range(theta_mean.shape[1]): Q_mean2[ilat, ilev] = Q_mean[ilev, ilat] # divide Q by theta X = Q_mean2 / theta_mean # convert pressure levels to approximate altitude and take the vertical gradient lev = DQ['lev'] zlev = H * np.log(p0 / lev) dZ = np.gradient(zlev) # gradient of vertical levels in m # now X *should* have shape (copy x lat x lev) OR (lat x lev) # so need to copy dZ to look like this if X.ndim == 3: dZm = dZ[None, None, :] levdim = 2 if X.ndim == 2: dZm = dZ[None, :] levdim = 1 dZ3 = np.broadcast_to(dZm, X.shape) dXdZ_3D = np.gradient(X, dZ3) dxdz = dXdZ_3D[ levdim] # this is the vertical gradient with respect to height # the above calculation yields a quantity in units s^-2/s, but it makes more sense # in the grand scheme of things to look at buoyancy forcing per day, so here # is a conversion factor. seconds_per_day = 60. * 60. * 24.0 # now loop over ensemble members and compute the n2 forcing for each one N2_forcing = g * dxdz * seconds_per_day D = dict() D['data'] = N2_forcing D['lev'] = DQ['lev'] D['lat'] = DQ['lat'] D['units'] = 's^{-2}/day' D['long_name'] = 'N^{2} Forcing' return D