def write_les_profiles(les): U, V, T, SH, QL, QI, Pf, Ph, A, Zgfull, Zghalf = (getattr( les, varname, None) for varname in gcm_vars) Zf = les.gcm_Zf # note: gcm Zf varies in time and space - must get it again after every step, for every column h = les.get_zf() u_d = les.get_profile_U() v_d = les.get_profile_V() sp_d = les.get_presf() thl_d = les.get_profile_THL() qt_d = les.get_profile_QT() ql_d = les.get_profile_QL() ql_ice_d = les.get_profile_QL_ice() # ql_ice is the ice part of QL ql_water_d = ql_d - ql_ice_d # ql_water is the water part of ql qr_d = les.get_profile_QR() A_d = get_cloud_fraction(les) # dales state # dales.cdf.variables['presh'][gcm.step] = dales.get_presh().value_in(units.Pa) # todo associate with zh in netcdf # calculate real temperature from Dales' thl, qt, using the pressures from openIFS pf = sputils.interp(h, Zf[::-1], Pf[::-1]) t = thl_d * sputils.exner(pf) + sputils.rlv * ql_d / sputils.cp # get real temperature from Dales - note it is calculated internally from thl and ql t_d = les.get_profile_T() spio.write_les_data(les, u=u_d.value_in(units.m / units.s), v=v_d.value_in(units.m / units.s), presf=sp_d.value_in(units.Pa), qt=qt_d.value_in(units.mfu), ql=ql_d.value_in(units.mfu), ql_ice=ql_ice_d.value_in(units.mfu), ql_water=ql_water_d.value_in(units.mfu), thl=thl_d.value_in(units.K), t=t.value_in(units.K), t_=t_d.value_in(units.K), qr=qr_d.value_in(units.mfu))
def variability_nudge(les, gcm, write=True): # this cannot be used before the LES has been stepped - otherwise qsat and ql are not defined. qsat = les.get_field("Qsat") qt = les.get_field("QT") ql2 = les.get_profile("QL") qt_av = les.get_profile("QT") itot, jtot = les.get_itot(), les.get_jtot() ql = (qt - qsat).maximum(0 | units.mfu).sum(axis=(0, 1)) / (itot * jtot) # strangely, this doesn't have a unit # the part before / has the unit mfu # mfu is dimensionless - might be the reason. # use mean instead of sum / size ? # ql_ref has unit mfu # print('---', les.lat, les.lon, '---') # print(les.QL) # print(les.ql_ref) # print(ql) # print(ql2) # print(les.get_itot(), les.get_jtot()) # print ('---------') # get ql difference # note the implicit k, qt, qt_av, qsat variables def get_ql_diff(beta): result = (beta * (qt[:, :, k] - qt_av[k]) + qt_av[k] - qsat[:, :, k]).maximum( 0 | units.mfu).sum() / (itot * jtot) - les.ql_ref[k] return result.number beta_min = 0 # search interval beta_max = 2000 beta = numpy.ones(les.k) for k in range(0, les.k): current_ql_diff = get_ql_diff(1) if les.ql_ref[ k] > 1e-9: # significant amount of clouds in the GCM. Nudge towards this amount. # print (k, 'significant ql_ref') q_min = get_ql_diff(beta_min) q_max = get_ql_diff(beta_max) if q_min > 0 or q_max < 0: log.info( "k:%d didn't bracket a zero. qmin:%f, qmax:%f, qt_avg:%f, stdev(qt):%f " % (k, q_min, q_max, numpy.mean( qt[:, :, k]).number, numpy.std(qt[:, :, k]).number)) # seems to happen easily in the sponge layer, where the variability is kept small continue beta[k] = brentq(get_ql_diff, beta_min, beta_max) elif ql[k] > les.ql_ref[ k]: # The GCM says no clouds, or very little, and the LES has more than this. # Nudge towards barely unsaturated. i, j = numpy.unravel_index( numpy.argmax(qt[:, :, k] - qsat[:, :, k]), qt[:, :, k].shape) beta[k] = (qsat[i, j, k] - qt_av[k]) / (qt[i, j, k] - qt_av[k]) # print (qt[i,j,k].value_in(units.mfu)) # print (qsat[i,j,k].value_in(units.mfu)) # print(qt_av[k].value_in(units.mfu)) # print(ql[k].value_in(units.mfu)) # print(les.ql_ref[k].value_in(units.mfu)) log.info( '%d nudging towards non-saturation. Max at (%d,%d). qt:%f, qsat:%f, qt_av[k]:%f, beta:%f, ql_avg:%f, ' 'ql_ref:%f' % (k, i, j, qt[i, j, k].value_in( units.mfu), qsat[i, j, k].value_in( units.mfu), qt_av[k].value_in(units.mfu), beta[k], ql[k], les.ql_ref[k].value_in(units.mfu))) if beta[k] < 0: # this happens when qt_av > qsat log.info(' beta<0, setting beta=1 ') beta[k] = 1 else: continue # no nudge - don't print anything # print (k, current_ql_diff, les.ql_ref[k], beta[k]) alpha = numpy.log(beta) / gcm.get_timestep() les.set_qt_variability_factor(alpha) qt_std = qt.std(axis=(0, 1)) if write: spio.write_les_data(les, qt_alpha=alpha.value_in(1 / units.s)) spio.write_les_data(les, qt_beta=beta, qt_std=qt_std.value_in(units.mfu))
def set_gcm_tendencies(gcm, les, factor=1, write=True): U, V, T, SH, QL, QI, Pf, Ph, A, Zgfull, Zghalf = (getattr( les, varname, None) for varname in gcm_vars) Zf = les.gcm_Zf # note: gcm Zf varies in time and space - must get it again after every step, for every column h = les.get_zf() u_d = les.get_profile_U() v_d = les.get_profile_V() sp_d = les.get_presf() rhof_d = les.get_rhof() rhobf_d = les.get_rhobf() thl_d = les.get_profile_THL() qt_d = les.get_profile_QT() ql_d = les.get_profile_QL() ql_ice_d = les.get_profile_QL_ice() # ql_ice is the ice part of QL ql_water_d = ql_d - ql_ice_d # ql_water is the water part of ql qr_d = les.get_profile_QR() A_d = get_cloud_fraction(les) # dales state # dales.cdf.variables['presh'][gcm.step] = dales.get_presh().value_in(units.Pa) # todo associate with zh in netcdf # calculate real temperature from Dales' thl, qt, using the pressures from openIFS pf = sputils.interp(h, Zf[::-1], Pf[::-1]) t = thl_d * sputils.exner(pf) + sputils.rlv * ql_d / sputils.cp # get real temperature from Dales - note it is calculated internally from thl and ql t_d = les.get_profile_T() if write: spio.write_les_data(les, u=u_d.value_in(units.m / units.s), v=v_d.value_in(units.m / units.s), presf=sp_d.value_in(units.Pa), rhof=rhof_d.value_in(units.kg / units.m**3), rhobf=rhobf_d.value_in(units.kg / units.m**3), qt=qt_d.value_in(units.mfu), ql=ql_d.value_in(units.mfu), ql_ice=ql_ice_d.value_in(units.mfu), ql_water=ql_water_d.value_in(units.mfu), thl=thl_d.value_in(units.K), t=t.value_in(units.K), t_=t_d.value_in(units.K), qr=qr_d.value_in(units.mfu)) # forcing ft = gcm.get_timestep() # should be the length of the NEXT time step # interpolate to GCM heights t_d = sputils.interp(Zf, h, t_d) qt_d = sputils.interp(Zf, h, qt_d) ql_d = sputils.interp(Zf, h, ql_d) ql_water_d = sputils.interp(Zf, h, ql_water_d) ql_ice_d = sputils.interp(Zf, h, ql_ice_d) u_d = sputils.interp(Zf, h, u_d) v_d = sputils.interp(Zf, h, v_d) # log.info("Height of LES system: %f" % h[-1]) # first index in the openIFS colum which is inside the Dales system start_index = sputils.searchsorted(-Zf, -h[-1]) # log.info("start_index: %d" % start_index) f_T = factor * (t_d - T) / ft f_SH = factor * ( (qt_d - ql_d) - SH) / ft # !!!!! -ql_d here - SH is vapour only. f_QL = factor * (ql_water_d - QL) / ft # condensed liquid water f_QI = factor * (ql_ice_d - QI) / ft # condensed water as ice # f_QL = factor * (ql_d - (QL+QI)) / ft dales QL is both liquid and ice - f_QL is liquid only. this conserves # water mass but makes an error in latent heat. f_U = factor * (u_d - U) / ft f_V = factor * (v_d - V) / ft f_A = factor * (A_d - A) / ft f_T[0:start_index] *= 0 # zero out the forcings above the Dales system f_SH[0:start_index] *= 0 # TODO : taper off smoothly instead f_QL[0:start_index] *= 0 f_QI[0:start_index] *= 0 f_U[0:start_index] *= 0 f_V[0:start_index] *= 0 f_A[0:start_index] *= 0 # careful with double coriolis gcm.set_profile_tendency("U", les.grid_index, f_U) gcm.set_profile_tendency("V", les.grid_index, f_V) gcm.set_profile_tendency("T", les.grid_index, f_T) gcm.set_profile_tendency("SH", les.grid_index, f_SH) gcm.set_profile_tendency("QL", les.grid_index, f_QL) gcm.set_profile_tendency("QI", les.grid_index, f_QI) gcm.set_profile_tendency("A", les.grid_index, f_A) # store forcings on GCM in the statistics in the corresponding LES group if write: spio.write_les_data(les, f_U=f_U.value_in(units.m / units.s**2), f_V=f_V.value_in(units.m / units.s**2), f_T=f_T.value_in(units.K / units.s), f_SH=f_SH.value_in(units.shu / units.s), A=A.value_in(units.ccu), A_d=A_d.value_in(units.ccu), f_QL=f_QL.value_in(units.mfu / units.s), f_QI=f_QI.value_in(units.mfu / units.s), f_A=f_A.value_in(units.ccu / units.s))
def set_les_forcings(les, gcm, dt_gcm, factor, couple_surface, qt_forcing='sp', write=True): u, v, thl, qt, ps, ql = convert_profiles(les) # get dales slab averages u_d = les.get_profile_U() v_d = les.get_profile_V() thl_d = les.get_profile_THL() qt_d = les.get_profile_QT() ql_d = les.get_profile_QL() ps_d = les.get_surface_pressure() try: rain_last = les.rain except: rain_last = 0 | units.kg / units.m**2 rain = les.get_rain() les.rain = rain rainrate = (rain - rain_last) / dt_gcm # ft = dt # forcing time constant # forcing f_u = factor * (u - u_d) / dt_gcm f_v = factor * (v - v_d) / dt_gcm f_thl = factor * (thl - thl_d) / dt_gcm f_qt = factor * (qt - qt_d) / dt_gcm f_ps = factor * (ps - ps_d) / dt_gcm f_ql = factor * (ql - ql_d) / dt_gcm # log.info("RMS forcings at %d during time step" % les.grid_index) # dt_gcm = gcm.get_timestep().value_in(units.s) # log.info(" u : %f" % (sputils.rms(f_u)*dt_gcm)) # log.info(" v : %f" % (sputils.rms(f_v)*dt_gcm)) # log.info(" thl: %f" % (sputils.rms(f_thl)*dt_gcm)) # log.info(" qt : %f" % (sputils.rms(f_qt)*dt_gcm)) # store forcings on dales in the statistics if write: spio.write_les_data( les, f_u=f_u.value_in(units.m / units.s**2), f_v=f_v.value_in(units.m / units.s**2), f_thl=f_thl.value_in(units.K / units.s), f_qt=f_qt.value_in(units.mfu / units.s), rain=rain.value_in(units.kg / units.m**2), rainrate=rainrate.value_in(units.kg / units.m**2 / units.s) * 3600) # set tendencies for Dales les.set_tendency_U(f_u) les.set_tendency_V(f_v) les.set_tendency_THL(f_thl) les.set_tendency_QT(f_qt) les.set_tendency_surface_pressure(f_ps) les.set_tendency_QL(f_ql) # used in experimental local qt nudging les.set_ref_profile_QL(ql) # used in experimental variability nudging les.ql_ref = ql # store ql profile from GCM, interpolated to the LES levels # for another variant of variability nudging # transfer surface quantities if couple_surface: z0m, z0h, wt, wq = convert_surface_fluxes(les) les.set_z0m_surf(z0m) les.set_z0h_surf(z0h) les.set_wt_surf(wt) les.set_wq_surf(wq) if write: spio.write_les_data(les, z0m=z0m.value_in(units.m), z0h=z0h.value_in(units.m), wthl=wt.value_in(units.m * units.s**-1 * units.K), wqt=wq.value_in(units.m / units.s)) spio.write_les_data( les, TLflux=les.TLflux.value_in(units.W / units.m**2), TSflux=les.TSflux.value_in(units.W / units.m**2), SHflux=les.SHflux.value_in(units.kg / units.m**2 / units.s), QLflux=les.QLflux.value_in(units.kg / units.m**2 / units.s), QIflux=les.QIflux.value_in(units.kg / units.m**2 / units.s)) if qt_forcing == 'variance': if les.get_model_time() > 0 | units.s: starttime = time.time() variability_nudge(les, gcm) walltime = time.time() - starttime log.info("variability nudge took %6.2f s" % walltime)
def convert_profiles(les, write=True): U, V, T, SH, QL, QI, Pf, Ph, A, Zgfull, Zghalf = (getattr( les, varname, None) for varname in gcm_vars) # virtual temperature - used to get heights c = sputils.rv / sputils.rd - 1 # epsilon^(-1) -1 = 0.61 Tv = T * (1 + c * SH - (QL + QI)) # is it correct to include QI here? # like liquid water, ice contributes to the density but not (much) to pressure # dP = Ph[1:] - Ph[:-1] # dP - pressure difference over one cell # dZ = sputils.rd * Tv / (sputils.grav * Pf) * dP # dZ - height of one cell # sum up dZ to get Z at half-levels. # 0 is at the end of the list, therefore reverse lists before and after. # Zh_local = numpy.cumsum(dZ[::-1])[::-1] # Zh_local.append(0 | units.m) # append a 0 for ground # height of full levels - simply average half levels (for now) # better: use full level pressure to calculate height? # Zf_local = (Zh[1:] + Zh[:-1]) * .5 # use Zgfull, Zghalf from IFS directly instead of calculating from pressure # these values seem close to what we used before. # 2% difference at top, 0.2% difference close to ground. Zh = (Zghalf - Zghalf[-1]) / sputils.grav Zf = (Zgfull - Zghalf[-1]) / sputils.grav les.gcm_Zf = Zf # save height levels in the les object for re-use les.gcm_Zh = Zh #print ('Zf ', Zf[-5:]) #print ('Zf_local', Zf_local[-5:]) #print ('Zh ', Zh[-5:]) #print ('Zh_local', Zh_local[-5:]) #print ('Zf relative diff\n', (Zf_local-Zf)/Zf) #print ('Zh relative diff\n', (Zh_local[:-1]-Zh[:-1])/Zh[:-1]) # Convert from OpenIFS quantities to les # note - different from modtestbed - iexner multiplied with both terms # could include QI as well. thl_ = (T - (sputils.rlv * (QL + QI)) / sputils.cp) * sputils.iexner(Pf) qt_ = SH + QL + QI # interpolate to les' heights # quirks: # Zf must be increasing, so reverse the gcm arrays # outside the range of Zf, interp returns the first or the last point of the range h = les.get_zf() thl = sputils.interp(h, Zf[::-1], thl_[::-1]) qt = sputils.interp(h, Zf[::-1], qt_[::-1]) ql = sputils.interp(h, Zf[::-1], QL[::-1]) u = sputils.interp(h, Zf[::-1], U[::-1]) v = sputils.interp(h, Zf[::-1], V[::-1]) if write: spio.write_les_data( les, U=U.value_in(units.m / units.s), V=V.value_in(units.m / units.s), T=T.value_in(units.K), SH=SH.value_in(units.shu), QL=QL.value_in(units.mfu), QI=QI.value_in(units.mfu), # A.value_in(units.ccu) Pf=Pf.value_in(units.Pa), Ph=Ph[1:].value_in(units.Pa), Zf=Zf.value_in(units.m), Zh=Zh[1:].value_in(units.m), Psurf=Ph[-1].value_in(units.Pa), Tv=Tv.value_in(units.K), THL=thl_.value_in(units.K), QT=qt_.value_in(units.mfu)) return u, v, thl, qt, Ph[-1], ql
def set_gcm_tendencies(gcm, les, factor=1): U, V, T, SH, QL, QI, Pf, Ph, A = (getattr(les, varname, None) for varname in gcm_vars) Zf = les.gcm_Zf # note: gcm Zf varies in time and space - must get it again after every step, for every column h = les.zf.value_in(units.m) u_d = les.get_profile_U().value_in(units.m / units.s) v_d = les.get_profile_V().value_in(units.m / units.s) sp_d = les.get_presf().value_in(units.Pa) thl_d = les.get_profile_THL().value_in(units.K) qt_d = les.get_profile_QT() ql_d = les.get_profile_QL() ql_ice_d = les.get_profile_QL_ice() # ql_ice is the ice part of QL ql_water_d = ql_d - ql_ice_d # ql_water is the water part of ql qr_d = les.get_profile_QR() A_d = get_cloud_fraction(les) # dales state # dales.cdf.variables['presh'][gcm.step] = dales.get_presh().value_in(units.Pa) # todo associate with zh in netcdf # calculate real temperature from Dales' thl, qt, using the pressures from openIFS pf = numpy.interp(h, Zf[::-1], Pf[::-1]) t = thl_d * sputils.exner(pf) + sputils.rlv * ql_d / sputils.cp # get real temperature from Dales - note it is calculated internally from thl and ql t_d = les.get_profile_T().value_in(units.K) spio.write_les_data(les, u=u_d, v=v_d, presf=sp_d, qt=qt_d, ql=ql_d, ql_ice=ql_ice_d, ql_water=ql_water_d, thl=thl_d, t=t, t_=t_d, qr=qr_d) # forcing ft = gcm.get_timestep().value_in( units.s) # should be the length of the NEXT time step # interpolate to GCM heights t_d = numpy.interp(Zf, h, t_d) qt_d = numpy.interp(Zf, h, qt_d) ql_d = numpy.interp(Zf, h, ql_d) ql_water_d = numpy.interp(Zf, h, ql_water_d) ql_ice_d = numpy.interp(Zf, h, ql_ice_d) u_d = numpy.interp(Zf, h, u_d) v_d = numpy.interp(Zf, h, v_d) les_height = h[-1] # log.info("Height of LES system: %f" % les_height) i = 0 for i in range(0, len(Zf)): if Zf[i] < les_height: break start_index = i # first index in the openIFS column which is inside the Dales system # log.info("start_index: %d" % start_index) f_T = factor * (t_d - T) / ft f_SH = factor * ( (qt_d - ql_d) - SH) / ft # !!!!! -ql_d here - SH is vapour only. f_QL = factor * (ql_water_d - QL) / ft # condensed liquid water f_QI = factor * (ql_ice_d - QI) / ft # condensed water as ice # f_QL = factor * (ql_d - (QL+QI)) / ft dales QL is both liquid and ice - f_QL is liquid only. this conserves # water mass but makes an error in latent heat. f_U = factor * (u_d - U) / ft f_V = factor * (v_d - V) / ft f_A = factor * (A_d - A) / ft f_T[0:start_index] = 0 # zero out the forcings above the Dales system f_SH[0:start_index] = 0 # TODO : taper off smoothly instead f_QL[0:start_index] = 0 f_QI[0:start_index] = 0 f_U[0:start_index] = 0 f_V[0:start_index] = 0 f_A[0:start_index] = 0 gcm.set_profile_tendency("U", les.grid_index, f_U) gcm.set_profile_tendency("V", les.grid_index, f_V) gcm.set_profile_tendency("T", les.grid_index, f_T) gcm.set_profile_tendency("SH", les.grid_index, f_SH) gcm.set_profile_tendency("QL", les.grid_index, f_QL) gcm.set_profile_tendency("QI", les.grid_index, f_QI) gcm.set_profile_tendency("A", les.grid_index, f_A) # store forcings on GCM in the statistics in the corresponding LES group spio.write_les_data(les, f_U=f_U, f_V=f_V, f_T=f_T, f_SH=f_SH, A=A, f_QL=f_QL, f_QI=f_QI)
def convert_profiles(les, write=True): U, V, T, SH, QL, QI, Pf, Ph, A = (getattr(les, varname, None) for varname in gcm_vars) # virtual temperature - used to get heights c = sputils.rv / sputils.rd - 1 # epsilon^(-1) -1 = 0.61 Tv = T * (1 + c * SH - (QL + QI)) # is it correct to include QI here? # like liquid water, ice contributes to the density but not (much) to pressure dP = Ph[1:] - Ph[:-1] # dP - pressure difference over one cell dZ = sputils.rd * Tv / (sputils.grav * Pf) * dP # dZ - height of one cell # sum up dZ to get Z at half-levels. # 0 is at the end of the list, therefore reverse lists before and after. Zh = numpy.cumsum(dZ[::-1])[::-1] Zh = numpy.append(Zh, 0) # append a 0 for ground # height of full levels - simply average half levels (for now) # better: use full level pressure to calculate height? Zf = (Zh[1:] + Zh[:-1]) * .5 les.gcm_Zf = Zf # save height levels in the les object for re-use les.gcm_Zh = Zh # Convert from OpenIFS quantities to les # note - different from modtestbed - iexner multiplied with both terms # could include QI as well. thl_ = (T - (sputils.rlv * (QL + QI)) / sputils.cp) * sputils.iexner(Pf) qt_ = SH + QL + QI # interpolate to les' heights # quirks: # Zf must be increasing, so reverse the gcm arrays # outside the range of Zf, interp returns the first or the last point of the range h = les.zf.value_in(units.m) thl = numpy.interp(h, Zf[::-1], thl_[::-1]) qt = numpy.interp(h, Zf[::-1], qt_[::-1]) ql = numpy.interp(h, Zf[::-1], QL[::-1]) u = numpy.interp(h, Zf[::-1], U[::-1]) v = numpy.interp(h, Zf[::-1], V[::-1]) if write: spio.write_les_data(les, U=U, V=V, T=T, SH=SH, QL=QL, QI=QI, Pf=Pf, Ph=Ph[1:], Zf=Zf, Zh=Zh[1:], Psurf=Ph[-1], Tv=Tv, THL=thl_, QT=qt_) return u, v, thl, qt, Ph[-1] | units.Pa, ql