MODULE tropopause_m IMPLICIT NONE PRIVATE PUBLIC :: dyn_tropopause REAL, PARAMETER :: DynPTrMin = 8.E+3 !--- Dyn tropopause pressures < DynPTrMin are set to DynPTrMin (Pa) REAL, PARAMETER :: DynPTrMax = 4.E+4 !--- Dyn tropopause pressures > DynPTrMax are set to DynPTrMax (Pa) REAL, PARAMETER :: theta0 = 380. !--- Default threshold for theta-defined tropopause (K) REAL, PARAMETER :: pVort0 = 2.0 !--- Default threshold for PV-defined tropopause (PVU) REAL, PARAMETER :: sg0 = 0.75 !--- Bottom->top PV=pv0e search loop starts at sigma=sg0 level INTEGER, PARAMETER :: nadj = 3 !--- Threshold must be exceeded on nadj adjacent levels INTEGER, PARAMETER :: ns = 2 !--- Number of neighbours used each side for vertical smoothing CONTAINS !=============================================================================================================================== FUNCTION dyn_tropopause(t, ts, paprs, pplay, rot, itrop, thet0, potV0) RESULT(pTrop) USE assert_m, ONLY: assert USE assert_eq_m, ONLY: assert_eq USE dimphy, ONLY: klon, klev USE geometry_mod, ONLY: latitude USE strings_mod, ONLY: maxlen USE yomcst_mod_h, ONLY: ROMEGA, RKAPPA, RG USE vertical_layers_mod, ONLY: aps, bps, preff USE lmdz_reprobus_wrappers, ONLY: itroprep USE lmdz_cppkeys_wrapper, ONLY: CPPKEY_REPROBUS USE mod_phys_lmdz_para, ONLY: is_master USE mod_phys_lmdz_transfert_para, ONLY : bcast IMPLICIT NONE REAL :: pTrop(klon) !--- Pressure at dynamical tropopause (Pa) REAL, INTENT(IN) :: t(:,:) !--- Temperature at layers centers (K) REAL, INTENT(IN) :: ts(:) !--- Temperature on surface layer interface (K) REAL, INTENT(IN) :: paprs(:,:) !--- Pressure at layers interfaces (Pa) REAL, INTENT(IN) :: pplay(:,:) !--- Pressure at layers centers (Pa) REAL, INTENT(IN) :: rot(:,:) !--- Relative vorticity at layers centers (s-1) INTEGER, OPTIONAL, INTENT(OUT) :: itrop(klon) !--- Last tropospheric layer idx REAL, OPTIONAL, INTENT(IN) :: thet0 !--- Potential temperature at the tropopause (tropical region) (K) REAL, OPTIONAL, INTENT(IN) :: potV0 !--- Potential vorticity at the tropopause (rest of globe) (PVU) !------------------------------------------------------------------------------------------------------------------------------ CHARACTER(LEN=maxlen) :: modname !--- Current routine name REAL :: Temp_edg(klon,klev) !--- Regular temperature at layers interfaces (except last one)(K) REAL :: potTemp_edg(klon,klev) !--- Potential temperature at layers interfaces (except last one)(K) REAL :: potTemp_cen(klon,klev) !--- Potential temperature at layers centers (K) REAL :: potVort_cen(klon,klev) !--- Potential vorticity at layers centers (K) REAL :: p_th0(klon) !--- Pressures at theta=380K (Pa) REAL :: p_pv0(klon) !--- Pressures at PV=2PVU (Pa) REAL :: al, th0, pv0 !--- Interpolation coefficient + potential temp. and PV thresholds INTEGER :: i, k, kb, kt, kp, ib, ie, nw, n INTEGER :: ith(klon) !--- Indices of first TH=380K layers (top -> bottom search) INTEGER :: ipv(klon) !--- Indices of first PV=2PVU layers (top -> bottom search) INTEGER :: ipv0(klon) !--- Indices of first PV=2PVU layers (bottom -> top search) INTEGER :: ncons(klon) !--- Number of consecutive matching values found in vertical loops INTEGER :: itr(klon) !--- Index of last layer with a center pressure lower than pTrop INTEGER :: co(2*ns+1) !--- Binomial coefficients used compute smoothing weights "w(:,:)" INTEGER, SAVE :: k0 !--- Start index (sigma=sg0) for 2PVU bottom->top search loop REAL, ALLOCATABLE, SAVE :: fac(:) !--- Coriolis parameter: 2*ROMEGA*SIN(cells centers latitudes) (s-1) REAL, ALLOCATABLE, SAVE :: w(:,:) !--- Coefficients for vertical smoothing froutine "smooth" LOGICAL, SAVE :: lFirst = .TRUE. !$OMP THREADPRIVATE(k0, fac, w, lFirst) !------------------------------------------------------------------------------------------------------------------------------ modname = 'dyn_tropopause' CALL assert(SIZE(t, DIM=1) == klon, TRIM(modname)//" t klon") CALL assert(SIZE(t, DIM=2) == klev, TRIM(modname)//" t klev") CALL assert(SIZE(ts, DIM=1) == klon, TRIM(modname)//" ts klon") CALL assert(SHAPE(paprs) == [klon, klev+1], TRIM(modname)//" paprs shape") CALL assert(SHAPE(pplay) == [klon, klev ], TRIM(modname)//" pplay shape") CALL assert(SHAPE(rot) == [klon, klev ], TRIM(modname)//" rot shape") !--- MODIFY THE THRESHOLDS FOR THE DYNAMICAL TROPOPAUSE DEFINITION IN CASE THE CORRESPONDING OPTIONAL ARGUMENTS ARE USED th0 = theta0; IF(PRESENT(thet0)) th0 = thet0 !--- Potential temperature at the tropopause (tropical region) (K) pv0 = pVort0; IF(PRESENT(potV0)) pv0 = potV0 !--- Potential vorticity at the tropopause (rest of globe) (PVU) IF(lFirst) THEN ALLOCATE(fac(klon), w(ns+1, ns+1)) !--- COMPUTE THE CORIOLIS PARAMETER FOR PV ALCULATION ROUTINE "potentialVorticity" DO i = 1, klon fac(i) = 2. * ROMEGA * SIN(latitude(i)) END DO !$OMP BARRIER IF(is_master) THEN !--- GET THE INDEX "k0" OF THE FIRST LOWER INTERFACE LAYER WITH SIGMA COORDINATE LOWER THAN "sg0" !--- NOTE: "k0" DEPENDS ON VERTICAL DISCRETIZATION ONLY (VIA HYBRID COEFFS aps, bps) AND IS NOT SIMULATION-DEPENDENT DO k0 = 1, klev; IF( aps(k0) / preff + bps(k0) < sg0 ) EXIT; END DO !--- START INDEX FOR BOTTOM->TOP PV SEARCH LOOP !--- COMPUTE THE WEIGHTS FOR THE VERTICAL SMOOTHING ROUTINE "smooth" co(:) = 0; w(:, :) = 0. co(1) = 1; w(1, 1) = 1. DO i = 1, ns co(2:2*ns+1) = co(2:2*ns+1) + co(1:2*ns) !--- C(n+1,p+1) = C(n,p+1) + C(n,p) co(2:2*ns+1) = co(2:2*ns+1) + co(1:2*ns) !--- C(n+1,p+1) = C(n,p+1) + C(n,p) AGAIN w(i+1, 1:i+1) = REAL(co(i+1:2*i+1))/REAL(SUM(co(i+1:2*i+1))) END DO lFirst=.FALSE. END IF CALL bcast(k0) CALL bcast(w) CALL bcast(lFirst) END IF !=== DETERMINE THE PRESSURE AT WHICH THETA = th0 ============================================================================ CALL potentialTemperature(pplay, t, potTemp_cen) !--- POTENTIAL TEMPERATURE @ LAYERS CENTERS !--- INDEX OF FIRST LAYERS WITH THETA<380K @ CENTER ON "nadj" CONSECUTIVE LAYERS CALL getLayerIdx(potTemp_cen, th0, -1, nadj, ith) !--- FROM TOP TO BOTTOM CALL getPressure(potTemp_cen, th0, ith, pplay, paprs, p_th0) !--- PRESSURE @ THETA = th0 SURFACE !=== DETERMINE THE PRESSURE AT WHICH PV = pv0 =============================================================================== CALL cen2edg(t, ts, pplay, paprs(:,1:klev), temp_edg) !--- TEMP @ LAYERS INTERFACES (EXCEPT LAST ONE) CALL potentialTemperature (paprs(:,1:klev), temp_edg, potTemp_edg) !--- TPOT @ LAYERS INTERFACES (EXCEPT LAST ONE) CALL potentialVorticity(rot, potTemp_edg, paprs(:,1:klev), potVort_cen) !--- ERTEL POTENTIAL VORTICITY @ LAYERS CENTERS !--- INDEX OF FIRST LAYERS WITH PV<=2PVU @ CENTER ON "nadj" CONSECUTIVE LAYERS CALL getLayerIdx(potVort_cen, pv0, -1, nadj, ipv) !--- FROM TOP TO BOTTOM CALL getLayerIdx(potVort_cen, pv0, k0, nadj, ipv0) !--- FROM LAYER @ sig=sig0 TO TOP DO i = 1, klon; n = 0 !--- CHOOSE BETWEEN BOTTOM AND TOP INDEX IF(ipv0(i) == k0-1 .OR. ipv0(i) > ipv(i)) CYCLE !--- ipv0 CAN'T BE USED DO k = ipv0(i), ipv(i); IF(potVort_cen(i, k) > pv0) n = n+1; END DO !--- NUMBER OF POINTS WITH PV>2PVU IF(2 * n >= ipv(i)-ipv0(i)+1) ipv(i) = ipv0(i) !--- MORE THAN 50% > pv0 => LOWER POINT KEPT END DO CALL getPressure(potVort_cen, pv0, ipv, pplay, paprs, p_pv0) !--- PRESSURE @ PV = pv0 SURFACE !=== DETERMINE THE UNFILTERED DYNAMICAL TROPOPAUSE PRESSURE FIELD (LOWER POINT BETWEEN THETA=380K AND PV=2PVU) ============== DO i = 1, klon pTrop(i) = MAX(p_th0(i), p_pv0(i)) END DO !=== FILTER THE PRESSURE FIELD: TOO HIGH AND TOO LOW VALUES ARE CLIPPED ===================================================== DO i = 1, klon IF(pTrop(i) < DynPTrMin) pTrop(i) = DynPTrMin IF(pTrop(i) > DynPTrMax) pTrop(i) = DynPTrMax END DO !=== LAST VERTICAL INDEX WITH A PRESSURE HIGHER THAN TROPOPAUSE PRESSURE ==================================================== IF(.NOT.(PRESENT(itrop) .OR. CPPKEY_REPROBUS)) RETURN DO i = 1, klon DO k = 1, klev IF(pplay(i,k+1) <= pTrop(i)) EXIT END DO IF(PRESENT(itrop )) itrop(i) = k IF(CPPKEY_REPROBUS) itroprep(i) = k END DO CONTAINS !=============================================================================================================================== SUBROUTINE cen2edg(v_cen, v0_edg, p_cen, p_edg, v_edg) IMPLICIT NONE REAL, DIMENSION(klon, klev), INTENT(IN) :: v_cen, p_cen, p_edg REAL, DIMENSION(klon), INTENT(IN) :: v0_edg REAL, DIMENSION(klon, klev), INTENT(OUT) :: v_edg INTEGER :: i, k DO i = 1, klon v_edg(i, 1) = v0_edg(i) END DO DO k = 2, klev DO i = 1, klon al = LOG(p_edg(i, k-1)/p_cen(i, k)) / LOG(p_cen(i, k-1)/p_cen(i, k)) !--- CENTER -> INTERFACE INTERPOLATION COEFF v_edg(i, k) = v_cen(i, k-1) + al * (v_cen(i, k) - v_cen(i, k-1)) !--- FIELD AT LAYER INTERFACE END DO END DO END SUBROUTINE cen2edg !=============================================================================================================================== SUBROUTINE getPressure(v_cen, v0, ix, p_cen, p_int, pre_v0) IMPLICIT NONE REAL, INTENT(IN) :: v_cen(klon, klev), v0 INTEGER, INTENT(IN) :: ix(klon) REAL, INTENT(IN) :: p_cen(klon, klev), p_int(klon, klev+1) REAL, INTENT(OUT) :: pre_v0(klon) REAL :: al INTEGER :: i, k DO i = 1, klon; k = ix(i) IF(k == 0) THEN pre_v0(i) = p_int(i,1) ELSE IF(k == klev) THEN pre_v0(i) = p_int(i,klev+1) ELSE al = (v0 - v_cen(i, k+1)) / (v_cen(i, k) - v_cen(i, k+1)) pre_v0(i) = p_cen(i, k+1) * (p_cen(i, k) / p_cen(i, k+1))**al END IF END DO END SUBROUTINE getPressure !=============================================================================================================================== SUBROUTINE getLayerIdx(v, v0, k0, nadj, ix) ! Purpose: Search for the index of the last layer ix(i) with a value v(i,k) lower than or equal to v0. ! At least nadj adjacent layers must satisfy the criterium (less - as much as possible - near top or bottom). ! The search is done from: * top to bottom if k0 < 0 (from k=klev to k=|k0|) ! * bottom to top if k0 > 0 (from k=k0 to k=klev) ! - nominal case: k0 <= ix(i) < klev ! - special case: ix(i) == klev: ALL(v(i,k0:klev) <= v0) ! - special case: ix(i) == |k0|-1: ALL(v(i,k0:klev) > v0) IMPLICIT NONE REAL, INTENT(IN) :: v(klon, klev), v0 INTEGER, INTENT(IN) :: k0, nadj INTEGER, INTENT(OUT) :: ix(klon) INTEGER :: i, k, nc(klon) nc(:) = 0 ix(:) = 0 IF(k0 < 0) THEN !=== SEARCH FROM TOP TO BOTTOM: klev -> -k0 !--- ix(i) depends on nc(i), the number of adjacent layers with v(i,:) <= v0 (k is the index of the last tested layer) !--- * nc(i) == nadj nominal case: enough matching values => ix(i) = k+nadj-1 (|k0|+nadj-1 <= k <= klev-nadj+1) !--- particular case: all values are matching => ix(i) = klev (k = klev-nadj+1) !--- * 0 < nc(i) < nadj bottom reached: nc ix(i) = k+nc(i)-1 (k = |k0|) !--- * nc(i) == 0 bottom reached: no matching values => ix(i) = k (k = |k0|-1) !--- So ix(i) = MAX(k, k+nc(i)-1) fits for each case. DO k = klev, -1, -k0 DO i = 1, klon IF(ix(i) /= 0) CYCLE !--- ADEQUATE LAYER ALREADY FOUND nc(i) = nc(i) + 1 IF(ABS(v(i, k)) > v0) nc(i) = 0 IF(nc(i) /= nadj) CYCLE !--- nc klev !--- ix(i) depends on nc(i), the number of adjacent layers with v(i,:) > v0 (k is the index of the last tested layer) !--- * nc(i) == nadj nominal case: enough matching values => ix(i) = k-nadj ( k0 +nadj-1 <= k <= klev-nadj+1) !--- particular case: all values are matching => ix(i) = k0-1 (k = k0+nadj-1) !--- * 0 < nc(i) < nadj top reached: nc ix(i) = k-nc(i) (k = klev) !--- * nc(i) == 0 top reached: no matching values => ix(i) = k (k = klev) !--- So ix(i) = k-nc(i) fits for each case. DO k = k0, klev DO i = 1, klon IF(ix(i) /= 0) CYCLE !--- ADEQUATE LAYER ALREADY FOUND nc(i) = nc(i) + 1 IF(ABS(v(i, k)) <= v0) nc(i) = 0 IF(nc(i) /= nadj) CYCLE !--- nc klev-ns), a narrower set of weights (n in particular, first and last levels are left untouched. IMPLICIT NONE REAL, INTENT(IN) :: v (klon, klev) REAL, INTENT(OUT) :: vs(klon, klev) INTEGER :: i, j, k vs(:, :) = 0. DO k = 1, klev n = MIN(k-1, klev-k, ns) DO j = k-n, k+n DO i = 1, klon vs(i, k) = vs(i, k) + v(i, j) * w(n+1, 1+ABS(j-k)) END DO END DO END DO END SUBROUTINE smooth END FUNCTION dyn_tropopause END MODULE tropopause_m