A collection of diagnostic and interpolation routines for use with output from the Weather Research and Forecasting (WRF-ARW) Model.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

983 lines
37 KiB

!======================================================================
!
! !IROUTINE: VIRTUAL -- Calculate virtual temperature (K)
!
! !DESCRIPTION:
!
! This function returns a single value of virtual temperature in
! K, given temperature in K and mixing ratio in kg/kg. For an
! array of virtual temperatures, use subroutine VIRTUAL_TEMP.
!
! !INPUT:
! RATMIX - water vapor mixing ratio (kg/kg)
! TEMP - temperature (K)
!
! !OUTPUT:
! TV - Virtual temperature (K)
!
! NCLFORTSTART
REAL(KIND=8) FUNCTION TVIRTUAL(temp, ratmix)
USE wrf_constants, ONLY : EPS
!f2py threadsafe
IMPLICIT NONE
REAL(KIND=8), INTENT(IN) :: temp, ratmix
! NCLEND
TVIRTUAL = temp*(EPS + ratmix)/(EPS*(1.D0 + ratmix))
RETURN
END FUNCTION TVIRTUAL
! NCLFORTSTART
REAL(KIND=8) FUNCTION TONPSADIABAT(thte, prs, psadithte, psadiprs, psaditmk, gamma,&
errstat, errmsg)
USE wrf_constants, ONLY : ALGERR
!!$OMP DECLARE SIMD (TONPSADIABAT)
!!uniform(thte,prs,psadithte,psadiprs,psaditmk)
!f2py threadsafe
!f2py intent(in,out) :: cape, cin
IMPLICIT NONE
REAL(KIND=8), INTENT(IN) :: thte
REAL(KIND=8), INTENT(IN) :: prs
REAL(KIND=8), DIMENSION(150), INTENT(IN) :: psadithte
REAL(KIND=8), DIMENSION(150), INTENT(IN) :: psadiprs
REAL(KIND=8), DIMENSION(150,150), INTENT(IN) :: psaditmk
REAL(KIND=8), INTENT(IN) :: gamma
INTEGER, INTENT(INOUT) :: errstat
CHARACTER(LEN=*), INTENT(INOUT) :: errmsg
! NCLEND
REAL(KIND=8) :: fracjt
REAL(KIND=8) :: fracjt2
REAL(KIND=8) :: fracip
REAL(KIND=8) :: fracip2
INTEGER :: l1, h1, mid1, rang1, l2, h2, mid2, rang2
INTEGER :: ip, jt
! This function gives the temperature (in K) on a moist adiabat
! (specified by thte in K) given pressure in hPa. It uses a
! lookup table, with data that was generated by the Bolton (1980)
! formula for theta_e.
! First check if pressure is less than min pressure in lookup table.
! If it is, assume parcel is so dry that the given theta-e value can
! be interpretted as theta, and get temperature from the simple dry
! theta formula.
IF (prs .LE. psadiprs(150)) THEN
TONPSADIABAT = thte * (prs/1000.D0)**gamma
RETURN
END IF
! Otherwise, look for the given thte/prs point in the lookup table.
jt = -1
l1 = 1
h1 = 149
rang1 = h1 - l1
mid1 = (h1 + l1) / 2
DO WHILE(rang1 .GT. 1)
if(thte .GE. psadithte(mid1)) then
l1 = mid1
else
h1 = mid1
end if
rang1 = h1 - l1
mid1 = (h1 + l1) / 2
END DO
jt = l1
! DO jtch = 1, 150-1
! IF (thte .GE. psadithte(jtch) .AND. thte .LT. psadithte(jtch+1)) THEN
! jt = jtch
! EXIT
! !GO TO 213
! END IF
! END DO
ip = -1
l2 = 1
h2 = 149
rang2 = h2 - l2
mid2 = (h2 + l2) / 2
DO WHILE(rang2 .GT. 1)
if(prs .LE. psadiprs(mid2)) then
l2 = mid2
else
h2 = mid2
end if
rang2 = h2 - l2
mid2 = (h2 + l2) / 2
END DO
ip = l2
! ip = -1
! DO ipch = 1, 150-1
! IF (prs .LE. psadiprs(ipch) .AND. prs .GT. psadiprs(ipch+1)) THEN
! ip = ipch
! EXIT
! !GO TO 215
! END IF
! END DO
IF (jt .EQ. -1 .OR. ip .EQ. -1) THEN
! Set the error and return
TONPSADIABAT = -1
errstat = ALGERR
WRITE(errmsg, *) "capecalc3d: Outside of lookup table bounds. prs,thte=", prs, thte
RETURN
END IF
fracjt = (thte-psadithte(jt)) / (psadithte(jt+1)-psadithte(jt))
fracjt2 = 1.D0 - fracjt
fracip = (psadiprs(ip)-prs) / (psadiprs(ip)-psadiprs(ip+1))
fracip2 = 1.D0 - fracip
IF (psaditmk(ip,jt) .GT. 1D9 .OR. psaditmk(ip+1,jt) .GT. 1D9 .OR. &
psaditmk(ip,jt+1) .GT. 1D9 .OR. psaditmk(ip+1,jt+1) .GT. 1D9) THEN
! Set the error and return
TONPSADIABAT = -1
errstat = ALGERR
WRITE(errmsg, *) "capecalc3d: Tried to access missing temperature in lookup table. ", &
"Prs and Thte probably unreasonable. prs,thte=", prs, thte
RETURN
END IF
TONPSADIABAT = fracip2*fracjt2*psaditmk(ip,jt) + fracip*fracjt2*psaditmk(ip+1,jt) + &
fracip2*fracjt*psaditmk(ip,jt+1) + fracip*fracjt*psaditmk(ip+1,jt+1)
RETURN
END FUNCTION TONPSADIABAT
!NCLFORTSTART
SUBROUTINE DLOOKUP_TABLE(psadithte, psadiprs, psaditmk, fname, errstat, errmsg)
USE wrf_constants, ONLY : ALGERR
!f2py threadsafe
REAL(KIND=8), DIMENSION(150), INTENT(INOUT) :: psadithte, psadiprs
REAL(KIND=8), DIMENSION(150,150), INTENT(INOUT) :: psaditmk
CHARACTER(LEN=*), INTENT(IN) :: fname
INTEGER, INTENT(INOUT) :: errstat
CHARACTER(LEN=*), INTENT(INOUT) :: errmsg
!NCLEND
! Locals
INTEGER :: iustnlist, i, nthte, nprs, ip, jt
! FNAME = 'psadilookup.dat'
iustnlist = 33
OPEN (UNIT=iustnlist, FILE=fname, FORM='formatted', STATUS='old')
DO i = 1,14
READ (iustnlist, FMT=*)
END DO
READ (iustnlist, FMT=*) nthte, nprs
IF (nthte .NE. 150 .OR. nprs .NE. 150) THEN
errstat = ALGERR
errmsg = "Number of pressure or theta_e levels in lookup table file not 150"
RETURN
END IF
READ (iustnlist, FMT="(5D15.7)") (psadithte(jt),jt=1,nthte)
READ (iustnlist, FMT="(5D15.7)") (psadiprs(ip),ip=1,nprs)
READ (iustnlist, FMT="(5D15.7)") ((psaditmk(ip,jt),ip=1,nprs),jt=1,nthte)
CLOSE (iustnlist)
RETURN
END SUBROUTINE DLOOKUP_TABLE
! Historically, this routine calculated the pressure at full sigma
! levels when RIP was specifically designed for MM4/MM5 output.
! With the new generalized RIP (Feb '02), this routine is still
! intended to calculate a set of pressure levels that bound the
! layers represented by the vertical grid points, although no such
! layer boundaries are assumed to be defined. The routine simply
! uses the midpoint between the pressures of the vertical grid
! points as the bounding levels. The array only contains mkzh
! levels, so the pressure of the top of the uppermost layer is
! actually excluded. The kth value of pf is the lower bounding
! pressure for the layer represented by kth data level. At the
! lower bounding level of the lowest model layer, it uses the
! surface pressure, unless the data set is pressure-level data, in
! which case it assumes the lower bounding pressure level is as far
! below the lowest vertical level as the upper bounding pressure
! level is above.
SUBROUTINE DPFCALC(prs, sfp, pf, mix, mjy, mkzh, ter_follow)
REAL(KIND=8), DIMENSION(mkzh,mix,mjy), INTENT(IN) :: prs
REAL(KIND=8), DIMENSION(mix,mjy), INTENT(IN) :: sfp
REAL(KIND=8), DIMENSION(mkzh,mix,mjy), INTENT(OUT) :: pf
INTEGER, INTENT(IN) :: ter_follow,mix,mjy,mkzh
INTEGER :: i,j,k
DO j = 1,mjy
DO i = 1,mix
DO k = 1,mkzh
IF (k .EQ. mkzh) THEN
! terrain-following data
IF (ter_follow .EQ. 1) THEN
pf(k,i,j) = sfp(i,j)
! pressure-level data
ELSE
pf(k,i,j) = .5D0 * (3.D0*prs(k,i,j) - prs(k-1,i,j))
END IF
ELSE
pf(k,i,j) = .5D0 * (prs(k+1,i,j) + prs(k,i,j))
END IF
END DO
END DO
END DO
RETURN
END SUBROUTINE DPFCALC
!======================================================================
!
! !IROUTINE: capecalc3d -- Calculate CAPE and CIN
!
! !DESCRIPTION:
!
! This routine calculates CAPE and CIN (in m**2/s**2,
! or J/kg) for every grid point in the entire 3D domain (treating
! each grid point as a parcel).
!
! Important! The z-indexes must be arranged so that mkzh (max z-index) is the
! surface pressure. So, pressure must be ordered in ascending order before
! calling this routine. Other variables must be ordered the same (p,tk,q,z).
! Also, be advised that missing data values are not checked during the computation.
! Also also, Pressure must be hPa
! NCLFORTSTART
SUBROUTINE DCAPECALC3D(prs,tmk,qvp,ght,ter,sfp,cape,cin,&
cmsg,mix,mjy,mkzh,ter_follow,&
psafile, errstat, errmsg)
USE wrf_constants, ONLY : ALGERR, CELKEL, G, EZERO, ESLCON1, ESLCON2, &
EPS, RD, CP, GAMMA, CPMD, RGASMD, GAMMAMD, TLCLC1, &
TLCLC2, TLCLC3, TLCLC4, THTECON1, THTECON2, THTECON3
!USE omp_lib
IMPLICIT NONE
!f2py threadsafe
!f2py intent(in,out) :: cape, cin
INTEGER, INTENT(IN) :: mix, mjy, mkzh, ter_follow
REAL(KIND=8), DIMENSION(mix,mjy,mkzh), INTENT(IN) :: prs
REAL(KIND=8), DIMENSION(mix,mjy,mkzh), INTENT(IN) :: tmk
REAL(KIND=8), DIMENSION(mix,mjy,mkzh), INTENT(IN) :: qvp
REAL(KIND=8), DIMENSION(mix,mjy,mkzh), INTENT(IN) :: ght
REAL(KIND=8), DIMENSION(mix,mjy), INTENT(IN) :: ter
REAL(KIND=8), DIMENSION(mix,mjy), INTENT(IN) ::sfp
REAL(KIND=8), DIMENSION(mix,mjy,mkzh), INTENT(OUT) :: cape
REAL(KIND=8), DIMENSION(mix,mjy,mkzh), INTENT(OUT) :: cin
REAL(KIND=8), INTENT(IN) :: cmsg
CHARACTER(LEN=*), INTENT(IN) :: psafile
INTEGER, INTENT(INOUT) :: errstat
CHARACTER(LEN=*), INTENT(INOUT) :: errmsg
! NCLFORTEND
! local variables
INTEGER :: i, j, k, ilcl, kel, kk, klcl, klev, klfc, kmax, kpar
REAL(KIND=8) :: tlcl, zlcl
REAL(KIND=8) :: ethpari, qvppari, tmkpari
REAL(KIND=8) :: facden, qvplift, tmklift, tvenv, tvlift, ghtlift
REAL(KIND=8) :: eslift, tmkenv, qvpenv, tonpsadiabat
REAL(KIND=8) :: benamin, dz
REAL(KIND=8), DIMENSION(150) :: buoy, zrel, benaccum
REAL(KIND=8), DIMENSION(mkzh,mix,mjy) :: prsf
REAL(KIND=8), DIMENSION(150) :: psadithte, psadiprs
REAL(KIND=8), DIMENSION(150,150) :: psaditmk
LOGICAL :: elfound
REAL :: t1,t2
REAL(KIND=8), DIMENSION(mkzh,mix,mjy) :: prs_new
REAL(KIND=8), DIMENSION(mkzh,mix,mjy) :: tmk_new
REAL(KIND=8), DIMENSION(mkzh,mix,mjy) :: qvp_new
REAL(KIND=8), DIMENSION(mkzh,mix,mjy) :: ght_new
! To remove compiler warnings
tmkpari = 0
qvppari = 0
klev = 0
klcl = 0
kel = 0
! the comments were taken from a mark stoelinga email, 23 apr 2007,
! in response to a user getting the "outside of lookup table bounds"
! error message.
! tmkpari - initial temperature of parcel, k
! values of 300 okay. (not sure how much from this you can stray.)
! prspari - initial pressure of parcel, hpa
! values of 980 okay. (not sure how much from this you can stray.)
! thtecon1, thtecon2, thtecon3
! these are all constants, the first in k and the other two have
! no units. values of 3376, 2.54, and 0.81 were stated as being
! okay.
! tlcl - the temperature at the parcel's lifted condensation level, k
! should be a reasonable atmospheric temperature around 250-300 k
! (398 is "way too high")
! qvppari - the initial water vapor mixing ratio of the parcel,
! kg/kg (should range from 0.000 to 0.025)
!
! calculated the pressure at full sigma levels (a set of pressure
! levels that bound the layers represented by the vertical grid points)
!CALL cpu_time(t1)
!CALL OMP_SET_NUM_THREADS(16)
!$OMP PARALLEL DO
DO j = 1,mjy
DO i = 1,mix
DO k = 1,mkzh
prs_new(k,i,j) = prs(i,j,k)
tmk_new(k,i,j) = tmk(i,j,k)
qvp_new(k,i,j) = qvp(i,j,k)
ght_new(k,i,j) = ght(i,j,k)
END DO
END DO
END DO
!$OMP END PARALLEL DO
CALL DPFCALC(prs_new, sfp, prsf, mix, mjy, mkzh, ter_follow)
! before looping, set lookup table for getting temperature on
! a pseudoadiabat.
CALL DLOOKUP_TABLE(psadithte, psadiprs, psaditmk, psafile, errstat, errmsg)
IF (errstat .NE. 0) THEN
RETURN
END IF
!$OMP PARALLEL DO COLLAPSE(2) PRIVATE(tlcl, ethpari, &
!$OMP zlcl, kk, ilcl, klcl, tmklift, tvenv, tvlift, ghtlift, &
!$OMP facden, tmkenv, qvpenv, eslift, qvplift, buoy, benamin, &
!$OMP benaccum, zrel, kmax, dz, elfound, &
!$OMP kel, klfc, &
!$OMP i,j,k,kpar)
DO j = 1,mjy
DO i = 1,mix
cape(i,j,1) = 0.d0
cin(i,j,1) = 0.d0
!!$OMP SIMD
DO kpar = 2, mkzh
! Calculate temperature and moisture properties of parcel
! (note, qvppari and tmkpari already calculated above for 2d case.)
tlcl = TLCLC1/(LOG(tmk_new(kpar,i,j)**TLCLC2/(MAX(1.D-20,qvp_new(kpar,i,j)*prs_new(kpar,i,j)/ &
(EPS + qvp_new(kpar,i,j))))) - TLCLC3) + TLCLC4
ethpari = tmk_new(kpar,i,j)*(1000.D0/prs_new(kpar,i,j))**(GAMMA*(1.D0 + GAMMAMD*qvp_new(kpar,i,j)))* &
EXP((THTECON1/tlcl - THTECON2)*qvp_new(kpar,i,j)*(1.D0 + THTECON3*qvp_new(kpar,i,j)))
zlcl = ght_new(kpar,i,j) + (tmk_new(kpar,i,j) - tlcl)/(G/CP * (1.D0 + CPMD*qvp_new(kpar,i,j)))
! DO k = kpar,1,-1
! tmklift_new(k) = TONPSADIABAT(ethpari, prs_new(k,i,j), psadithte, psadiprs,&
! psaditmk, GAMMA, errstat, errmsg)
! END DO
! Calculate buoyancy and relative height of lifted parcel at
! all levels, and store in bottom up arrays. add a level at the lcl,
! and at all points where buoyancy is zero.
!
! For arrays that go bottom to top
kk = 0
ilcl = 0
IF (ght_new(kpar,i,j) .GE. zlcl) THEN
! Initial parcel already saturated or supersaturated.
ilcl = 2
klcl = 1
END IF
!!$OMP SIMD lastprivate(qvplift,tmklift,ghtlift,tvlift,tmkenv,qvpenv,tvenv,eslift,facden)
DO k = kpar,1,-1
! For arrays that go bottom to top
kk = kk + 1
! Model level is below lcl
IF (ght_new(k,i,j) .LT. zlcl) THEN
tmklift = tmk_new(kpar,i,j) - G/(CP * (1.D0 + CPMD*qvp_new(kpar,i,j))) * (ght_new(k,i,j) - ght_new(kpar,i,j))
tvenv = tmk_new(k,i,j)*(EPS + qvp_new(k,i,j))/(EPS*(1.D0 + qvp_new(k,i,j)))
tvlift = tmklift*(EPS + qvp_new(kpar,i,j))/(EPS*(1.D0 + qvp_new(kpar,i,j)))
ghtlift = ght_new(k,i,j)
ELSE IF (ght(i,j,k) .GE. zlcl .AND. ilcl .EQ. 0) THEN
! This model level and previous model level straddle the lcl,
! so first create a new level in the bottom-up array, at the lcl.
facden = 1.0/(ght_new(k,i,j) - ght_new(k+1,i,j))
tmkenv = tmk_new(k+1,i,j)*((ght_new(k,i,j)-zlcl)*facden) + tmk_new(k,i,j)*((zlcl-ght_new(k+1,i,j))*facden)
qvpenv = qvp_new(k+1,i,j)*((ght_new(k,i,j)-zlcl)*facden) + qvp_new(k,i,j)*((zlcl-ght_new(k+1,i,j))*facden)
tvenv = tmkenv* (EPS + qvpenv) / (EPS * (1.D0 + qvpenv))
tvlift = tlcl* (EPS + qvp_new(kpar,i,j)) / (EPS *(1.D0 + qvp_new(kpar,i,j)))
ghtlift = zlcl
ilcl = 1
ELSE
tmklift = TONPSADIABAT(ethpari, prs_new(k,i,j), psadithte, psadiprs,&
psaditmk, GAMMA, errstat, errmsg)
eslift = EZERO*EXP(ESLCON1*(tmklift - CELKEL)/(tmklift - ESLCON2))
qvplift = EPS*eslift/(prs_new(k,i,j) - eslift)
tvenv = tmk_new(k,i,j) * (EPS + qvp_new(k,i,j)) / (EPS * (1.D0 + qvp_new(k,i,j)))
tvlift = tmklift*(EPS + qvplift) / (EPS * (1.D0 + qvplift))
ghtlift = ght_new(k,i,j)
END IF
! Buoyancy
buoy(kk) = G*(tvlift - tvenv)/tvenv
zrel(kk) = ghtlift - ght_new(kpar,i,j)
IF ((kk .GT. 1) .AND. (buoy(kk)*buoy(kk-1) .LT. 0.0D0)) THEN
! Parcel ascent curve crosses sounding curve, so create a new level
! in the bottom-up array at the crossing.
kk = kk + 1
buoy(kk) = buoy(kk-1)
zrel(kk) = zrel(kk-1)
buoy(kk-1) = 0.D0
zrel(kk-1) = zrel(kk-2) + buoy(kk-2)/&
(buoy(kk-2) - buoy(kk))*(zrel(kk) - zrel(kk-2))
END IF
IF (ilcl .EQ. 1) THEN
klcl = kk
ilcl = 2
CYCLE
END IF
END DO
kmax = kk
! IF (kmax .GT. 150) THEN
! print *,'kmax got too big'
! errstat = ALGERR
! WRITE(errmsg, *) 'capecalc3d: kmax got too big. kmax=',kmax
! RETURN
! END IF
! If no lcl was found, set klcl to kmax. it is probably not really
! at kmax, but this will make the rest of the routine behave
! properly.
IF (ilcl .EQ. 0) klcl=kmax
! Get the accumulated buoyant energy from the parcel's starting
! point, at all levels up to the top level.
benaccum(1) = 0.0D0
benamin = 9d9
DO k = 2,kmax
dz = zrel(k) - zrel(k-1)
benaccum(k) = benaccum(k-1) + .5D0*dz*(buoy(k-1) + buoy(k))
IF (benaccum(k) .LT. benamin) THEN
benamin = benaccum(k)
END IF
END DO
! Determine equilibrium level (el), which we define as the highest
! level of non-negative buoyancy above the lcl. note, this may be
! the top level if the parcel is still buoyant there.
elfound = .FALSE.
DO k = kmax,klcl,-1
IF (buoy(k) .GE. 0.D0) THEN
! k of equilibrium level
kel = k
elfound = .TRUE.
EXIT
END IF
END DO
! If we got through that loop, then there is no non-negative
! buoyancy above the lcl in the sounding. in these situations,
! both cape and cin will be set to -0.1 j/kg. (see below about
! missing values in v6.1.0). also, where cape is
! non-zero, cape and cin will be set to a minimum of +0.1 j/kg, so
! that the zero contour in either the cin or cape fields will
! circumscribe regions of non-zero cape.
! In v6.1.0 of ncl, we added a _fillvalue attribute to the return
! value of this function. at that time we decided to change -0.1
! to a more appropriate missing value, which is passed into this
! routine as cmsg.
IF (.NOT. elfound) THEN
!print *,'el not found'
cape(i,j,kpar) = cmsg
cin(i,j,kpar) = cmsg
klfc = kmax
CYCLE
END IF
! If there is an equilibrium level, then cape is positive. we'll
! define the level of free convection (lfc) as the point below the
! el, but at or above the lcl, where accumulated buoyant energy is a
! minimum. the net positive area (accumulated buoyant energy) from
! the lfc up to the el will be defined as the cape, and the net
! negative area (negative of accumulated buoyant energy) from the
! parcel starting point to the lfc will be defined as the convective
! inhibition (cin).
! First get the lfc according to the above definition.
benamin = 9D9
klfc = kmax
DO k = klcl,kel
IF (benaccum(k) .LT. benamin) THEN
benamin = benaccum(k)
klfc = k
END IF
END DO
! Now we can assign values to cape and cin
cape(i,j,kpar) = MAX(benaccum(kel)-benamin, 0.1D0)
cin(i,j,kpar) = MAX(-benamin, 0.1D0)
! cin is uninteresting when cape is small (< 100 j/kg), so set
! cin to -0.1 (see note about missing values in v6.1.0) in
! that case.
! In v6.1.0 of ncl, we added a _fillvalue attribute to the return
! value of this function. at that time we decided to change -0.1
! to a more appropriate missing value, which is passed into this
! routine as cmsg.
IF (cape(i,j,kpar) .LT. 100.D0) cin(i,j,kpar) = cmsg
END DO
END DO
END DO
!$OMP END PARALLEL DO
!CALL cpu_time(t2)
!print *,'Time taken in seconds ',(t2-t1)
RETURN
END SUBROUTINE DCAPECALC3D
!======================================================================
!
! !IROUTINE: capecalc2d -- Calculate CAPE and CIN
!
! !DESCRIPTION:
!
! Calculates CAPE and CIN only for the parcel with max theta-e in
! the column, (i.e. something akin to Colman's MCAPE). By "parcel",
! we mean a 500-m deep parcel, with actual temperature and moisture
! averaged over that depth.
!
! CAPE and CIN are 2D fields that are placed in the k=mkzh slabs of
! the cape and cin arrays. Also, LCL and LFC heights
! are put in the k=mkzh-1 and k=mkzh-2 slabs of the cin array.
!
! Important! The z-indexes must be arranged so that mkzh (max z-index) is the
! surface pressure. So, pressure must be ordered in ascending order before
! calling this routine. Other variables must be ordered the same (p,tk,q,z).
! Also, be advised that missing data values are not checked during the
! computation.
! Also also, Pressure must be hPa
! NCLFORTSTART
SUBROUTINE DCAPECALC2D(prs,tmk,qvp,ght,ter,sfp,cape,cin,&
cmsg,mix,mjy,mkzh,ter_follow,&
psafile, errstat, errmsg)
USE wrf_constants, ONLY : ALGERR, CELKEL, G, EZERO, ESLCON1, ESLCON2, &
EPS, RD, CP, GAMMA, CPMD, RGASMD, GAMMAMD, TLCLC1, &
TLCLC2, TLCLC3, TLCLC4, THTECON1, THTECON2, THTECON3
!USE omp_lib
IMPLICIT NONE
!f2py threadsafe
!f2py intent(in,out) :: cape, cin
INTEGER, INTENT(IN) :: mix, mjy, mkzh, ter_follow
REAL(KIND=8), DIMENSION(mix,mjy,mkzh), INTENT(IN) :: prs
REAL(KIND=8), DIMENSION(mix,mjy,mkzh), INTENT(IN) :: tmk
REAL(KIND=8), DIMENSION(mix,mjy,mkzh), INTENT(IN) :: qvp
REAL(KIND=8), DIMENSION(mix,mjy,mkzh), INTENT(IN) :: ght
REAL(KIND=8), DIMENSION(mix,mjy), INTENT(IN) :: ter
REAL(KIND=8), DIMENSION(mix,mjy), INTENT(IN) ::sfp
REAL(KIND=8), DIMENSION(mix,mjy,mkzh), INTENT(OUT) :: cape
REAL(KIND=8), DIMENSION(mix,mjy,mkzh), INTENT(OUT) :: cin
REAL(KIND=8), INTENT(IN) :: cmsg
CHARACTER(LEN=*), INTENT(IN) :: psafile
INTEGER, INTENT(INOUT) :: errstat
CHARACTER(LEN=*), INTENT(INOUT) :: errmsg
! NCLFORTEND
! local variables
INTEGER :: i, j, k, ilcl, kel, kk, klcl, klev, klfc, kmax, kpar, kpar1, kpar2
REAL(KIND=8) :: ethmax, q, p, e, tlcl, zlcl
REAL(KIND=8) :: pavg, tvirtual, p1, p2, pp1, pp2, th, totthe, totqvp, totprs
REAL(KIND=8) :: cpm, deltap, ethpari, gammam, qvppari, tmkpari
REAL(KIND=8) :: facden, qvplift, tmklift, tvenv, tvlift, ghtlift, fac1, fac2
REAL(KIND=8) :: eslift, tmkenv, qvpenv, tonpsadiabat
REAL(KIND=8) :: benamin, dz, pup, pdn
REAL(KIND=8), DIMENSION(150) :: buoy, zrel, benaccum
REAL(KIND=8), DIMENSION(mkzh,mix,mjy) :: prsf
REAL(KIND=8), DIMENSION(150) :: psadithte, psadiprs
REAL(KIND=8), DIMENSION(150,150) :: psaditmk
LOGICAL :: elfound
INTEGER :: nthreads
REAL(KIND=8), DIMENSION(mkzh) :: eth_temp
REAL(KIND=8), DIMENSION(mkzh,mix,mjy) :: prs_new
REAL(KIND=8), DIMENSION(mkzh,mix,mjy) :: tmk_new
REAL(KIND=8), DIMENSION(mkzh,mix,mjy) :: qvp_new
REAL(KIND=8), DIMENSION(mkzh,mix,mjy) :: ght_new
! To remove compiler warnings
errstat = 0
tmkpari = 0
qvppari = 0
klev = 0
klcl = 0
kel = 0
deltap = 0
! the comments were taken from a mark stoelinga email, 23 apr 2007,
! in response to a user getting the "outside of lookup table bounds"
! error message.
! tmkpari - initial temperature of parcel, k
! values of 300 okay. (not sure how much from this you can stray.)
! prspari - initial pressure of parcel, hpa
! values of 980 okay. (not sure how much from this you can stray.)
! thtecon1, thtecon2, thtecon3
! these are all constants, the first in k and the other two have
! no units. values of 3376, 2.54, and 0.81 were stated as being
! okay.
! tlcl - the temperature at the parcel's lifted condensation level, k
! should be a reasonable atmospheric temperature around 250-300 k
! (398 is "way too high")
! qvppari - the initial water vapor mixing ratio of the parcel,
! kg/kg (should range from 0.000 to 0.025)
!
!$OMP PARALLEL DO COLLAPSE(3)
DO j = 1,mjy
DO i = 1,mix
DO k = 1,mkzh
prs_new(k,i,j) = prs(i,j,k)
tmk_new(k,i,j) = tmk(i,j,k)
qvp_new(k,i,j) = qvp(i,j,k)
ght_new(k,i,j) = ght(i,j,k)
END DO
END DO
END DO
!$OMP END PARALLEL DO
! calculated the pressure at full sigma levels (a set of pressure
! levels that bound the layers represented by the vertical grid points)
CALL DPFCALC(prs_new, sfp, prsf, mix, mjy, mkzh, ter_follow)
! before looping, set lookup table for getting temperature on
! a pseudoadiabat.
CALL DLOOKUP_TABLE(psadithte, psadiprs, psaditmk, psafile, errstat, errmsg)
IF (errstat .NE. 0) THEN
RETURN
END IF
!CALL OMP_SET_NUM_THREADS(16)
!nthreads = omp_get_num_threads()
!$OMP PARALLEL DO COLLAPSE(2) PRIVATE(tlcl, ethpari, &
!$OMP zlcl, kk, ilcl, klcl, tmklift, tvenv, tvlift, ghtlift, &
!$OMP facden, tmkenv, qvpenv, eslift, qvplift, buoy, benamin, &
!$OMP benaccum, zrel, kmax, dz, elfound, &
!$OMP kel, klfc, pavg, p2, p1, totthe, totqvp, totprs, &
!$OMP i,j,k,kpar, kpar1, kpar2, qvppari, tmkpari,p, pup, pdn, q, th, &
!$OMP pp1, pp2, ethmax, eth_temp, klev)
DO j = 1,mjy
DO i = 1,mix
cape(i,j,1) = 0.d0
cin(i,j,1) = 0.d0
! find parcel with max theta-e in lowest 3 km agl.
ethmax = -1.d0
eth_temp = -1.d0
DO k = 1, mkzh
IF (ght_new(k,i,j)-ter(i,j) .LT. 3000.d0) THEN
tlcl = TLCLC1 / (LOG(tmk_new(k,i,j)**TLCLC2/&
(MAX(qvp_new(k,i,j), 1.d-15)*prs_new(k,i,j)/(EPS+MAX(qvp_new(k,i,j), 1.d-15))))-TLCLC3)+&
TLCLC4
eth_temp(k) = tmk_new(k,i,j) * (1000.d0/prs_new(k,i,j))**&
(GAMMA*(1.d0 + GAMMAMD*(MAX(qvp_new(k,i,j), 1.d-15))))*&
EXP((THTECON1/tlcl - THTECON2)*(MAX(qvp_new(k,i,j), 1.d-15))*&
(1.d0 + THTECON3*(MAX(qvp_new(k,i,j), 1.d-15))))
END IF
END DO
klev = mkzh
DO k = 1,mkzh
IF (eth_temp(k) .GT. ethmax) THEN
klev = k
ethmax = eth_temp(k)
END IF
END DO
kpar1 = klev
kpar2 = klev
! Establish average properties of that parcel
! (over depth of approximately davg meters)
!davg = 500.d0
pavg = 500.d0 * prs_new(kpar1,i,j)*&
G/(RD*tvirtual(tmk_new(kpar1,i,j), qvp_new(kpar1,i,j)))
p2 = MIN(prs_new(kpar1,i,j)+.5d0*pavg, prsf(mkzh,i,j))
p1 = p2 - pavg
totthe = 0.D0
totqvp = 0.D0
totprs = 0.D0
DO k = mkzh,2,-1
IF (prsf(k,i,j) .LE. p1) EXIT !GOTO 35
IF (prsf(k-1,i,j) .GE. p2) CYCLE !GOTO 34
p = prs_new(k,i,j)
pup = prsf(k,i,j)
pdn = prsf(k-1,i,j)
!q = MAX(qvp_new(k,i,j),1.D-15)
th = tmk_new(k,i,j)*(1000.D0/prs_new(k,i,j))**(GAMMA*(1.D0 + GAMMAMD*MAX(qvp_new(k,i,j),1.D-15)))
pp1 = MAX(p1,pdn)
pp2 = MIN(p2,pup)
IF (pp2 .GT. pp1) THEN
! deltap = pp2 - pp1
totqvp = totqvp + MAX(qvp_new(k,i,j),1.D-15)*(pp2 - pp1)
totthe = totthe + th*(pp2 - pp1)
totprs = totprs + (pp2 - pp1)
END IF
END DO
qvppari = totqvp/totprs
tmkpari = (totthe/totprs)*&
(prs_new(kpar1,i,j)/1000.D0)**(GAMMA*(1.D0+GAMMAMD*qvp_new(kpar1,i,j)))
!CALL CPU_TIME(t3)
DO kpar = kpar1, kpar2
! Calculate temperature and moisture properties of parcel
! (note, qvppari and tmkpari already calculated above for 2d
! case.)
!prspari = prs_new(kpar,i,j)
!ghtpari = ght_new(kpar,i,j)
gammam = GAMMA * (1.D0 + GAMMAMD*qvppari)
cpm = CP * (1.D0 + CPMD*qvppari)
e = MAX(1.D-20,qvppari*prs_new(kpar,i,j)/(EPS + qvppari))
tlcl = TLCLC1/(LOG(tmkpari**TLCLC2/e) - TLCLC3) + TLCLC4
ethpari = tmkpari*(1000.D0/prs_new(kpar,i,j))**(GAMMA*(1.D0 + GAMMAMD*qvppari))*&
EXP((THTECON1/tlcl - THTECON2)*qvppari*(1.D0 + THTECON3*qvppari))
zlcl = ght_new(kpar,i,j) + (tmkpari - tlcl)/(G/cpm)
! Calculate buoyancy and relative height of lifted parcel at
! all levels, and store in bottom up arrays. add a level at the
! lcl,
! and at all points where buoyancy is zero.
!
!
! For arrays that go bottom to top
kk = 0
ilcl = 0
IF (ght_new(kpar,i,j) .GE. zlcl) THEN
! Initial parcel already saturated or supersaturated.
ilcl = 2
klcl = 1
END IF
k = kpar
DO k = kpar,1,-1
! For arrays that go bottom to top
kk = kk + 1
! Model level is below lcl
IF (ght_new(k,i,j) .LT. zlcl) THEN
tmklift = tmk_new(kpar,i,j) - G/(CP * (1.D0 + CPMD*qvp_new(kpar,i,j))) * (ght_new(k,i,j) - ght_new(kpar,i,j))
tvenv = tmk_new(k,i,j)*(EPS + qvp_new(k,i,j))/(EPS*(1.D0 + qvp_new(k,i,j)))
tvlift = tmklift*(EPS + qvp_new(kpar,i,j))/(EPS*(1.D0 + qvp_new(kpar,i,j)))
ghtlift = ght_new(k,i,j)
ELSE IF (ght(i,j,k) .GE. zlcl .AND. ilcl .EQ. 0) THEN
! This model level and previous model level straddle the lcl,
! so first create a new level in the bottom-up array, at the lcl.
facden = 1/(ght_new(k,i,j) - ght_new(k+1,i,j))
tmkenv = tmk_new(k+1,i,j)*((ght_new(k,i,j)-zlcl)*facden) + tmk_new(k,i,j)*((zlcl-ght_new(k+1,i,j))*facden)
qvpenv = qvp_new(k+1,i,j)*((ght_new(k,i,j)-zlcl)*facden) + qvp_new(k,i,j)*((zlcl-ght_new(k+1,i,j))*facden)
tvenv = tmkenv* (EPS + qvpenv) / (EPS * (1.D0 + qvpenv))
tvlift = tlcl* (EPS + qvp_new(kpar,i,j)) / (EPS *(1.D0 + qvp_new(kpar,i,j)))
ghtlift = zlcl
ilcl = 1
ELSE
tmklift = TONPSADIABAT(ethpari, prs_new(k,i,j), psadithte, psadiprs,&
psaditmk, GAMMA, errstat, errmsg)
eslift = EZERO*EXP(ESLCON1*(tmklift - CELKEL)/(tmklift - ESLCON2))
qvplift = EPS*eslift/(prs_new(k,i,j) - eslift)
tvenv = tmk_new(k,i,j) * (EPS + qvp_new(k,i,j)) / (EPS * (1.D0 + qvp_new(k,i,j)))
tvlift = tmklift*(EPS + qvplift) / (EPS * (1.D0 + qvplift))
ghtlift = ght_new(k,i,j)
END IF
! Buoyancy
buoy(kk) = G*(tvlift - tvenv)/tvenv
zrel(kk) = ghtlift - ght_new(kpar,i,j)
IF ((kk .GT. 1) .AND. (buoy(kk)*buoy(kk-1) .LT. 0.0D0)) THEN
! Parcel ascent curve crosses sounding curve, so create a new level
! in the bottom-up array at the crossing.
kk = kk + 1
buoy(kk) = buoy(kk-1)
zrel(kk) = zrel(kk-1)
buoy(kk-1) = 0.D0
zrel(kk-1) = zrel(kk-2) + buoy(kk-2)/&
(buoy(kk-2) - buoy(kk))*(zrel(kk) - zrel(kk-2))
END IF
IF (ilcl .EQ. 1) THEN
klcl = kk
ilcl = 2
CYCLE
END IF
END DO
kmax = kk
! IF (kmax .GT. 150) THEN
! errstat = ALGERR
! WRITE(errmsg, *) 'capecalc3d: kmax got too big. kmax=',kmax
! RETURN
! END IF
! If no lcl was found, set klcl to kmax. it is probably not
! really
! at kmax, but this will make the rest of the routine behave
! properly.
IF (ilcl .EQ. 0) klcl=kmax
! Get the accumulated buoyant energy from the parcel's starting
! point, at all levels up to the top level.
benaccum(1) = 0.0D0
benamin = 9d9
DO k = 2,kmax
dz = zrel(k) - zrel(k-1)
benaccum(k) = benaccum(k-1) + .5D0*dz*(buoy(k-1) + buoy(k))
IF (benaccum(k) .LT. benamin) THEN
benamin = benaccum(k)
END IF
END DO
! Determine equilibrium level (el), which we define as the highest
! level of non-negative buoyancy above the lcl. note, this may be
! the top level if the parcel is still buoyant there.
elfound = .FALSE.
DO k = kmax,klcl,-1
IF (buoy(k) .GE. 0.D0) THEN
! k of equilibrium level
kel = k
elfound = .TRUE.
EXIT
END IF
END DO
! If we got through that loop, then there is no non-negative
! buoyancy above the lcl in the sounding. in these situations,
! both cape and cin will be set to -0.1 j/kg. (see below about
! missing values in v6.1.0). also, where cape is
! non-zero, cape and cin will be set to a minimum of +0.1 j/kg, so
! that the zero contour in either the cin or cape fields will
! circumscribe regions of non-zero cape.
! In v6.1.0 of ncl, we added a _fillvalue attribute to the return
! value of this function. at that time we decided to change -0.1
! to a more appropriate missing value, which is passed into this
! routine as cmsg.
IF (.NOT. elfound) THEN
cape(i,j,kpar) = cmsg
cin(i,j,kpar) = cmsg
klfc = kmax
CYCLE
END IF
! If there is an equilibrium level, then cape is positive.
! we'll
! define the level of free convection (lfc) as the point below
! the
! el, but at or above the lcl, where accumulated buoyant energy
! is a
! minimum. the net positive area (accumulated buoyant energy)
! from
! the lfc up to the el will be defined as the cape, and the net
! negative area (negative of accumulated buoyant energy) from
! the
! parcel starting point to the lfc will be defined as the
! convective
! inhibition (cin).
! First get the lfc according to the above definition.
benamin = 9D9
klfc = kmax
DO k = klcl,kel
IF (benaccum(k) .LT. benamin) THEN
benamin = benaccum(k)
klfc = k
END IF
END DO
! Now we can assign values to cape and cin
cape(i,j,kpar) = MAX(benaccum(kel)-benamin, 0.1D0)
cin(i,j,kpar) = MAX(-benamin, 0.1D0)
! cin is uninteresting when cape is small (< 100 j/kg), so set
! cin to -0.1 (see note about missing values in v6.1.0) in
! that case.
! In v6.1.0 of ncl, we added a _fillvalue attribute to the return
! value of this function. at that time we decided to change -0.1
! to a more appropriate missing value, which is passed into this
! routine as cmsg.
IF (cape(i,j,kpar) .LT. 100.D0) cin(i,j,kpar) = cmsg
END DO
cape(i,j,mkzh) = cape(i,j,kpar1)
cin(i,j,mkzh) = cin(i,j,kpar1)
! meters agl
cin(i,j,mkzh-1) = zrel(klcl) + ght_new(kpar,i,j) - ter(i,j)
! meters agl
cin(i,j,mkzh-2) = zrel(klfc) + ght_new(kpar,i,j) - ter(i,j)
END DO
END DO
!$OMP END PARALLEL DO
RETURN
END SUBROUTINE DCAPECALC2D