c c c THERMODYNAMIC SUBROUTINES AND FUNCTIONS c c Richard Carpenter c c Univ. of Oklahoma, 1992 c c c c ANALYZSND - Analyze a sounding c ANALYZSND2 - Analyze a sounding (does not recompute quantities) c GETCAPE - Computes CAPE given T(v) of sounding, parcel c PARCEL - Compute T,LWC of lifted parcel. c MODEL2RAW - Convert (z,Th,Qv) sounding to (p,T,Td). c SATVAPOR - es(T) using Bolton's (1980) best fit. c THQ2T - Compute T given ThQ. c THE2T - Compute T given ThE. c c c c c c ######################################## c ######################################## c ######################################## c ######## ######## c ######## ANALYZSND2 ######## c ######## ######## c ######################################## c ######################################## c ######################################## c c Subroutine AnalyzSnd2 (n,pres,temp,tdew, irev, 3,4 > z,qv,qs,rh,tv,theta, > presLCL,tempLCL,zLCL,presLNB,zLNB, > alwcP,tempP,tvP,tvwlP, & CINtvwl,CAPEtvwl, CINtvnowl,CAPEtvnowl, CINt,CAPEt, ifCB) c ! 12/08/93 Added 2 more CIN/CAPE parameters Implicit None Include 'thermo.consts' c Integer k,n, irev, maxp, ifCB, k1 Parameter (maxp=1000) Real pres(n), temp(n), tdew(n), z(n), alwcP(n), > qv(n), qs(n), rh(n), tv(n), theta(n), tempP(n), > tvP(n), tvwlP(n), > dCAPE(maxp) Real zLCL,presLCL, presLNB,zLNB, tempLCL, > e,es,qP,qvP,th,CINtvwl,CAPEtvwl, > CINt,CAPEt, presLNBt,zLNBt, CINtvnowl, CAPEtvnowl Include 'thermo.stfunc' c c c Input: c n = number of points c pres = pressure [Pa] c temp,tdew = temperature, dew point [K] c irev = 0 for pseudoadabatic ascent, 1 for reversible c ifCB = 0: surface-based parcel c 1: cloud base parcel. presLCL,tempLCL are inputs. c z = height c qv = water vapor [kg/kg] c qs = saturation water vapor [kg/kg] c rh = relative humidity c tv = virtual temp [K] c theta = potential temp c c Note for ifCB=1 (cloud base parcel): The cloud base lies between k=1,2. c c Output: c alwcP = parcel adiabatic liquid water content [kg/kg] c tempP = parcel temperature [K] c tvP = parcel virt temp [K] c tvwlP = parcel virt temp, incl water loading [K] c CINtvwl = negative CAPE [J/kg] c CAPEp = positive CAPE [J/kg] c tempLCL = parcel temperature at LCL c presLCL = pressure at LCL c c c If (n.GT.maxp) Then Print *, '% AnalyzSnd2: maxp too small. maxp,n: ', maxp,n Stop End If c c Find LCL c If (ifCB.EQ.0) Then th = Ftheta(pres(1),temp(1)) e = Fsvpres(tdew(1)-tfrz) qP = Fmixrat(pres(1),e) k1 = 1 tempLCL = Ftlcl(temp(1),tdew(1)) presLCL = Fplcl(pres(1),temp(1),tempLCL,qP) zLCL = Fzlcl(temp(1),tempLCL,qP) Print '(1x,a,3f10.2)', '% AnalyzSnd2: LCL: pres,temp,z: ', > presLCL*1.e-2,tempLCL-tfrz,zLCL Else th = Ftheta(presLCL,tempLCL) e = Fsvpres(tempLCL-tfrz) qP = Fmixrat(presLCL,e) k1 = 2 zLCL = -99. End If c c Print *, '% AnalyzSnd2: ifCB:', ifCB c Print *, '% AnalyzSnd2: temp(1),tempLCL:', temp(1),tempLCL c c print*,'% AnalyzSnd2:pres(1),temp(1),qP:', c > pres(1)/100.,temp(1)-tfrz,qP*1000. c c c Compute Lifted Parcel quantities c For cloud base parcel, only want to recompute parcel properties: c alwcP,tempP(2:n),tvP,tvwlP. c Do k=k1,n es = Fsvpres(temp(k)-tfrz) e = Fsvpres(tdew(k)-tfrz) c c ...parcel stuff c If (pres(k).LT.presLCL) Then Call Parcel (presLCL,tempLCL,pres(k),irev, tempP(k),alwcP(k)) Else tempP(k)= th * (pres(k)/p00)**rcp alwcP(k)= 0. End If qvP = qP - alwcP(k) tvP(k) = Ftvirtnowl(tempP(k),qvP) tvwlP(k) = Ftvirtwl(tempP(k),qvP,alwcP(k)) c If (ifCB.NE.0) Print *, k,pres(k)/100.,tempP(k)-tfrz, c > qvP*1000.,alwcP(k)*1000. c End Do c c c Supply values for k=1 for cloud base parcel c If (ifCB.EQ.1) Then k = 1 qvP = qP tempP(k) = tempLCL tvP(k) = Ftvirtnowl(tempP(k),qvP) tvwlP(k) = Ftvirtwl(tempP(k),qvP,0.) End If c c c c c compute heights c If (ifCB.NE.0) Then zLCL = z(1) + (z(2)-z(1)) * > (Log(pres(1))-Log(presLCL)) / (Log(pres(1))-Log(pres(2))) Print *, '% AnalyzSnd2: zLCL: ', zLCL End If c c c c Compute CAPE ! 1. Using Tv(wl). 2. Using Tv. 3. Using T. c ! c Call GetCAPE (n,maxp,ifCB,tv,tvP,z,pres, !Tv > dCAPE,CAPEtvnowl,CINtvnowl,presLNB,zLNB,zLCL) c Call GetCAPE (n,maxp,ifCB,tv,tvwlP,z,pres, !Tv(wl) > dCAPE,CAPEtvwl,CINtvwl,presLNB,zLNB,zLCL) c Call GetCAPE (n,maxp,ifCB,temp,tempP,z,pres, !T > dCAPE,CAPEt,CINt,presLNBt,zLNBt,zLCL) c 9901 Format (1x,a,3f8.0) Print 9901, '% AnalyzSnd2: CAPE (Tvwl,Tv,T):', & CAPEtvwl, CAPEtvnowl, CAPEt Print 9901, '% AnalyzSnd2: CIN (Tvwl,Tv,T): ', & CINtvwl, CINtvnowl, CINt Print 9901, '% AnalyzSnd2: pLNB:', presLNB*1.e-2, presLNBt*1.e-2 Print 9901, '% AnalyzSnd2: zLNB:', zLNB, zLNBt c End c c c c c ######################################## c ######################################## c ######################################## c ######## ######## c ######## ANALYZSND ######## c ######## ######## c ######################################## c ######################################## c ######################################## c c Subroutine AnalyzSnd (n,pres,temp,tdew, irev, ,3 > z,qv,qs,rh,tv,theta, > presLCL,tempLCL,zLCL,presLNB,zLNB, > alwcP,tempP,tvP,tvwlP,capem,capep, capemt,capept, ifCB) c Implicit None Include 'thermo.consts' c Integer k,n, irev, maxp, ifCB, k1 Parameter (maxp=1000) Real pres(n), temp(n), tdew(n), z(n), alwcP(n), > qv(n), qs(n), rh(n), tv(n), theta(n), tempP(n), > tvP(n), tvwlP(n), dcape(maxp) Real dz,tvavg,zLCL,presLCL, presLNB,zLNB,tempLCL, > e,es,qP,qvP,th,capem,capep, dtvPavg, > capemt,capept, presLNBt,zLNBt Include 'thermo.stfunc' c c c Input: c n = number of points c pres = pressure [Pa] c temp,tdew = temperature, dew point [K] c irev = 0 for pseudoadabatic ascent, 1 for reversible c ifCB = 0: surface-based parcel c 1: cloud base parcel. presLCL,tempLCL are inputs. c c Note for ifCB=1 (cloud base parcel): The cloud base lies between k=1,2. c c Output: c z = height c qv = water vapor [kg/kg] c qs = saturation water vapor [kg/kg] c rh = relative humidity c tv = virtual temp [K] c theta = potential temp c alwcP = parcel adiabatic liquid water content [kg/kg] c tempP = parcel temperature [K] c tvP = parcel virt temp [K] c tvwlP = parcel virt temp, incl water loading [K] c capem = negative cape [J/kg] c capep = positive cape [J/kg] c tempLCL = parcel temperature at LCL c presLCL = pressure at LCL c c c If (n.GT.maxp) Then Print *, '% AnalyzSnd: maxp too small. maxp,n: ', maxp,n Stop End If c c Find LCL c If (ifCB.EQ.0) Then th = Ftheta(pres(1),temp(1)) e = Fsvpres(tdew(1)-tfrz) qP = Fmixrat(pres(1),e) k1 = 1 tempLCL = Ftlcl(temp(1),tdew(1)) presLCL = Fplcl(pres(1),temp(1),tempLCL,qP) zLCL = Fzlcl(temp(1),tempLCL,qP) Print '(1x,a,3f10.2)', '% AnalyzSnd: LCL: pres,temp,z: ', > presLCL*1.e-2,tempLCL-tfrz,zLCL Else th = Ftheta(presLCL,tempLCL) e = Fsvpres(tempLCL-tfrz) qP = Fmixrat(presLCL,e) k1 = 2 zLCL = -99. End If c c Print *, '% AnalyzSnd: ifCB:', ifCB c Print *, '% AnalyzSnd: temp(1),tempLCL:', temp(1),tempLCL c c print*,'% AnalyzSnd:pres(1),temp(1),qP:', c > pres(1)/100.,temp(1)-tfrz,qP*1000. c c c For cloud base parcel, only want to recompute parcel properties: c alwcP,tempP(2:n),tvP,tvwlP. c Do k=k1,n es = Fsvpres(temp(k)-tfrz) e = Fsvpres(tdew(k)-tfrz) If (ifCB.EQ.0) Then theta(k) = temp(k) * (p00/pres(k)) ** rcp qv(k) = Fmixrat(pres(k),e) qs(k) = Fmixrat(pres(k),es) rh(k) = Frh_q(qv(k),qs(k)) tv(k) = Ftvirtnowl(temp(k),qv(k)) End If c c ...parcel stuff c If (pres(k).LT.presLCL) Then Call Parcel (presLCL,tempLCL,pres(k),irev, tempP(k),alwcP(k)) Else tempP(k)= th * (pres(k)/p00)**rcp alwcP(k)= 0. End If qvP = qP - alwcP(k) tvP(k) = Ftvirtnowl(tempP(k),qvP) tvwlP(k) = Ftvirtwl(tempP(k),qvP,alwcP(k)) c If (ifCB.NE.0) Print *, k,pres(k),tempP(k)-tfrz, c > qvP*1000.,alwcP(k)*1000. c End Do c c c Supply values for k=1 for cloud base parcel c If (ifCB.EQ.1) Then k = 1 qvP = qP tempP(k) = tempLCL tvP(k) = Ftvirtnowl(tempP(k),qvP) tvwlP(k) = Ftvirtwl(tempP(k),qvP,0.) End If c c c c c compute heights c If (ifCB.EQ.0) Then z(1) = 0. c Do k=2,n tvavg = .5 * (tv(k-1) + tv(k)) dz = rd/grav*tvavg * Log(pres(k-1)/pres(k)) z(k) = z(k-1) + dz End Do c Else zLCL = z(1) + (z(2)-z(1)) * > (Log(pres(1))-Log(presLCL)) / (Log(pres(1))-Log(pres(2))) Print *, '% AnalyzSnd: zLCL: ', zLCL End If c c c c Compute CAPE c c Call GetCAPE (n,maxp,ifCB,tv,tvwlP,z,pres, > dcape,capep,capem,presLNB,zLNB,zLCL) c Call GetCAPE (n,maxp,ifCB,temp,tempP,z,pres, > dcape,capept,capemt,presLNBt,zLNBt,zLCL) c 9901 Format (1x,a,2f8.0) Print 9901, '% AnalyzSnd: capep:', capep, capept Print 9901, '% AnalyzSnd: capem:', capem, capemt Print 9901, '% AnalyzSnd: pLNB:', presLNB*1.e-2, presLNBt*1.e-2 Print 9901, '% AnalyzSnd: zLNB:', zLNB, zLNBt c End c c c c c ######################################## c ######################################## c ######################################## c ######## ######## c ######## GETCAPE ######## c ######## ######## c ######################################## c ######################################## c ######################################## c c Subroutine GetCAPE (n,maxp,ifCB,t,tP,z,pres, 5 > dcape,capep,capem,presLNB,zLNB,zLCL) Implicit None Include 'thermo.consts' c c c INPUT: (all SI units) c n = number of points in sounding c maxp = size of arrays c ifCB = Compute relative to cloud base (given by zLCL) c t = array of (virtual) temperatures from sounding c tP = array of (virtual) temperatures of lifted parcel c z,pres = array of heights,pressures c zLCL = Hgt of cloud base (only if ifCB=1) c c OUTPUT: c dcape = array of CAPE for each level c zLNB,presLNB = hgt and pres of LNB c capep = CAPE from LFC to LNB (positive area) c capem = CAPE from sfc to LFC (negative area) c c Integer n, maxp, k, ifCB Real t(maxp), tP(maxp), z(maxp), pres(maxp), dcape(maxp) Real t0avg,t1avg,dz,zLNB,capep,capem, presLNB, zLCL Logical lnbflag c capem = 0. capep = 0. lnbflag = .False. presLNB = pres(1) zLNB = z(1) c c c Compute CAPE in each level c ...account for the special case of (k=1 && ifCB=1). c Do k=1,n-1 If (ifCB.EQ.1 .AND. k.EQ.1) Then t0avg = t(k+1) t1avg = .5 * (tP(k+1)-t(k+1)) dz = z(k+1) - zLCL Else t0avg = .5 * (t(k) + t(k+1)) t1avg = .5 * (tP(k)-t(k) + tP(k+1)-t(k+1)) dz = z(k+1) - z(k) End If dcape(k) = dz * grav * t1avg / t0avg End Do c c c Find LNB, total CAPE c lnbflag = .False. Do k=n-1,1,-1 c c ...find LNB c If (.NOT.lnbflag .AND. dcape(k).GT.0.) Then presLNB = pres(k+1) zLNB = z(k+1) lnbflag = .True. End If c c ...accumulate CAPE below LNB c If (lnbflag) Then If (dcape(k).LT.0.) Then capem = capem + dcape(k) Else capep = capep + dcape(k) End If End If c End Do c End c c c c c ######################################## c ######################################## c ######################################## c ######## ######## c ######## PARCEL ######## c ######## ######## c ######################################## c ######################################## c ######################################## c c Subroutine Parcel (presLCL, tempLCL, pres, irev, temp, alwc) 2,2 Implicit None Include 'thermo.consts' Integer irev Real presLCL, tempLCL, pres, temp, alwc, thetae, qsLCL, qv,es Include 'thermo.stfunc' c c c Input: c presLCL = pressure at LCL [mb] c tempLCL = temperature at LCL [C] c pres = pressure level to compute parcel at [mb] c irev = 0 for pseudoadabatic ascent, 1 for reversible c Output: c temp = temperature of parcel [C] c alwc = adiabatic liquid water content of parcel [g/kg] c c c I don't know if this works below the LCL (that is, for unsat parcels) c c We must find not only the temp but also the mix ratio c c c Reference thetae/q c c es = Fsvpres(tempLCL-tfrz) qsLCL = Fmixrat(presLCL,es) If (irev.EQ.0) Then thetae = Fthetae(presLCL,tempLCL,tempLCL,qsLCL) Else thetae = Fthetaq(presLCL,tempLCL,qsLCL,Fthq_cwcptrm(qsLCL)) End If c c Print *, 'Parcel: presLCL,tempLCL,qsLCL: ',presLCL,tempLCL,qsLCL c Print *, 'Parcel: thetae/q: ', thetae c Find T c If (irev.EQ.0) Then Call ThE2T (temp, thetae, pres) Else Call ThQ2T (temp, thetae, pres, qsLCL, 0) End If c c get ALWC c es = Fsvpres(temp-tfrz) qv = Fmixrat(pres,es) alwc = qsLCL - qv c c End c c c c c ######################################## c ######################################## c ######################################## c ######## ######## c ######## MODEL2RAW ######## c ######## ######## c ######################################## c ######################################## c ######################################## c c Subroutine Model2Raw (presmb,tempc,tdewc, n,dz,zsfc,psfc) Implicit None Include 'thermo.consts' Integer k,n Real presmb(n), tempc(n), tdewc(n), > dz,zsfc,psfc,pres,theta,qv,tvavg,pkp1, > tempkp1,thetakp1,qvkp1,tvkp1 Include 'thermo.stfunc' c c c Input: c tempc = theta c tdewc = qv [kg/kg] c n = number of points c dz = grid spacing c zsfc = station elevation (not used) c psfc = surface pres c c Output: c presmb = pressure [mb] c tempc,tdewc = temperature, dew point [deg C] c c pkp1 = psfc Do k=1,n pres = pkp1 presmb(k) = pres / 100. theta = tempc(k) qv = tdewc(k) tempc(k) = theta * (pres / p00) ** rcp - tfrz tdewc(k) = Ftdewc(Fvpres(pres,qv)) tvavg = (tempc(k)+tfrz) * (1. + cp608*qv) !should be layer avg pkp1 = pres * Exp (-grav*dz*rdi/tvavg) c c compute Tv at k+1 to get more accurate integration of pres. c thetakp1 = tempc(k+1) qvkp1 = tdewc(k+1) tempkp1 = theta * (pkp1 / p00) ** rcp tvkp1 = Ftvirtnowl(tempkp1,qvkp1) tvavg = .5 * (tvavg + tvkp1) pkp1 = pres * Exp (-grav*dz*rdi/tvavg) c End Do c End c c c c c ######################################## c ######################################## c ######################################## c ######## ######## c ######## SATVAPOR ######## c ######## ######## c ######################################## c ######################################## c ######################################## c c c Computes saturation vapor pressure c SI units c See Bolton (1980) c Real Function SatVapor (temp) Implicit None Real temp, g(0:7) Integer i Data g /-2.9912729e3, -6.0170128e3, 1.887643854e1,-2.8354721e-2, > 1.7838301e-5,-8.4150417e-10,4.4412543e-13, 2.858487e0 / c SatVapor = g(7) * Log (temp) Do i=0,6 SatVapor = SatVapor + g(i) * temp ** (i-2) End Do SatVapor = Exp (SatVapor) End c c c c c ######################################## c ######################################## c ######################################## c ######## ######## c ######## THQ2T ######## c ######## ######## c ######################################## c ######################################## c ######################################## c c Subroutine ThQ2T (temp, thetaq, pres, q, iqflag) 1 Implicit None Include 'thermo.consts' c Integer iter, iqflag Real temp, thetaq, pres, tempc, es, qs, thetaq1, err, > delta, tempold, q, qv, qtotal, > deltaold, stepfrac Include 'thermo.stfunc' c c NOTE: THIS ASSUMES THE PARCEL IS JUST SATURATED. c c Given ThetaQ and Pres, find Temp c Note that, for fixed pres, ThetaQ varies with both temp and q. c c Input: c temp = temperature (K) (first guess) c thetaq = ThetaQ c pres = pressure (Pa) c q = Mixing ratio (kg/kg) (iqflag=0 only) c Output: c temp = temperature (K) c qtotal = Mixing ratio (kg/kg) c iqflag = 0: Hold q const. 1: Set q to satd value. c c If (temp.LT.200.) temp = 300. stepfrac = 0.5 c print *, 'ThQ2T: temp,thetaq,pres: ', temp,thetaq,pres c Do iter=1,40 tempc = temp - tfrz es = Fsvpres(tempc) qs = Fmixrat(pres,es) If (iqflag.EQ.0) Then qv = Min(qs,q) qtotal = q Else qv = qs qtotal = qs End If thetaq1 = Fthetaq(pres,temp,qv,Fthq_cwcptrm(qtotal)) c print *, 'ThQ2T: tempc,es,qs,thetaq1: ', tempc,es,qs,thetaq1 err = thetaq1 - thetaq If (Abs(err).LT.0.001) Go To 9000 c c limit the step to 20 K or 1/2 the error in ThE c deltaold = delta delta = Min (40., stepfrac*Abs(err)) delta = Sign (delta, err) If (iter.GT.2 .AND. Sign(1.,delta).NE.Sign(1.,deltaold)) Then stepfrac = stepfrac * .5 c Print *, 'ThQ2T: reducing stepfrac to :', stepfrac delta = Min (40., stepfrac*Abs(err)) delta = Sign (delta, err) End If tempold = temp temp = temp - delta c Print 9999,'ThQ2T: iter,temp,err,Err:',iter,temp,err,temp-tempold End Do c Print *, 'ThQ2T: did not converge. iter,temp: ', iter,temp c 9000 Continue c Print *, 'ThQ2T: temp,err: ', temp,temp-tempold End c c c c c ######################################## c ######################################## c ######################################## c ######## ######## c ######## THE2T ######## c ######## ######## c ######################################## c ######################################## c ######################################## c c Subroutine ThE2T (temp, thetae, pres) 5,1 Implicit None Include 'thermo.consts' c Integer iter Real temp, thetae, pres, tempc, es, qs, thetae1, err, > delta, deltaold, stepfrac, tempold, tlcl Include 'thermo.stfunc' c c NOTE: THIS ASSUMES THE PARCEL IS JUST SATURATED. c c Given ThetaE and Pres, find Temp c Note that, for fixed pres, ThetaE varies with both temp and q. c c Input: c temp = temperature (K) (first guess) c thetae = ThetaE c pres = pressure (Pa) c Output: c temp = temperature (K) c c If (temp.LT.200.) temp = 300. stepfrac = 0.5 c print *, 'ThE2T: temp,thetae,pres: ', temp,thetae,pres c Do iter=1,30 tempc = temp - tfrz es = Fsvpres(tempc) qs = Fmixrat(pres,es) tlcl = Ftlcl2(temp,es) thetae1 = Fthetae(pres,temp,tlcl,qs) c print *, 'ThE2T: tempc,es,qs,thetae1: ', tempc,es,qs,thetae1 err = thetae1 - thetae If (Abs(err).LT.0.001) Go To 9000 c c limit the step to 20 K or 1/2 the error in ThE c deltaold = delta delta = Min (40., stepfrac*Abs(err)) delta = Sign (delta, err) If (iter.GT.2 .AND. Sign(1.,delta).NE.Sign(1.,deltaold)) Then stepfrac = stepfrac * .5 c Print *, 'ThE2T: reducing stepfrac to :', stepfrac delta = Min (40., stepfrac*Abs(err)) delta = Sign (delta, err) End If tempold = temp temp = temp - delta c Print 9999,'ThE2T: iter,temp,err,Err:',iter,temp,err,temp-tempold End Do c Print 9999, 'ThE2T: did not converge. iter,temp,err: ', > iter,temp,err,temp-tempold c 9999 Format (1x,a,i5,8f10.3) 9000 Continue c Print *, 'ThE2T: temp,err: ', temp,temp-tempold End