SUBROUTINE lpm_mean(nx,ny,n_ens,patch_nx,patch_ny,ovx, ovy, & filt_min,gauss_sigma,partweight, & var2d_ens,var2d_enmean,var2d_enmax, & var2d_lpm) ! !Author: Nate Snook (28 Apr. 2017) ! !Purpose: Calculate localized PM mean of a 2D field. Localized PM mean ! uses all data in a non-exclusive calculation area to obtain ! PM mean for a smaller patch within that region of dimensions ! (patch_nx, patch_ny). The size of the calculation area is ! controled by the overlap variables ovx and ovy. ! ! Here is a diagram of the design: ! ! +------------------------------------------------------------+ ! | full domain | ! | +---------------------------+ | ! | | calculation ^ | | ! | | area | | | ! | | ovy | | ! | | | | | ! | | v | | ! | | +-------+ | | ! | | | patch | | | ! | |<--ovx-->| |<--ovx-->| | ! | | +-------+ | | ! | | ^ | | ! | | | | | ! | | ovy | | ! | | | | | ! | | v | | ! | +---------------------------+ | ! | | ! | | ! | | ! | | ! +------------------------------------------------------------+ ! ! This calculation is repeated for patches in the x- and y-direction ! such that the entire domain is covered, and then a smoother is run ! on the resulting field to ensure a smooth 2D output. For best ! results, patch_nx and patch_ny of no larger than 8 are recommended. ! ! History ! ! 6/2/2017 Fanyou Kong ! - Code revision, bug fix, and clean up ! 6/7/2017 Fanyou Kong ! - Add a constrain with ensemble max (var2d_enmax) ! IMPLICIT NONE !Inputs INTEGER :: nx, ny, n_ens ! number of x, y gridpoints, ensemble members INTEGER :: patch_nx, patch_ny ! size of actual patches in gridpoints (recommended value <= 8) ! For HMT QPF, patch_nx = patch_ny = 6 works rather well. INTEGER :: ovx, ovy ! how many points of patch "overlap" to include in calculation area. ! Recommended value: 3 to 10 times patch_nx and patch_ny ! For HMT QPF, a value of ovx = ovy = 30 works quite well. REAL :: filt_min ! LPM is set to zero where enmean < filt_min -- this helps to remove ! areas of noise that can occur due to patch overlap. For QPF, ! filt_min = 0.1 mm for 3hr accumulated precip works well. REAL :: gauss_sigma ! Parameter sigma (standard deviation) to be passed to gauss_rad ! Recommended value = 2 (2 gridpoints) -- this needs some ! testing REAL :: partweight(nx*ny) ! Needed input parameter for gaussian_smooth REAL :: var2d_ens(nx, ny, n_ens) ! 2D (nx, ny) field for each member (n_ens) REAL :: var2d_enmean(nx, ny) ! 2D (nx, ny) ensemble mean field REAL :: var2d_enmax(nx, ny) ! 2D (nx, ny) ensemble max field !Outputs REAL :: var2d_lpm(nx, ny) ! 2D (nx, ny) LPM field !Calculated and used in this code INTEGER :: ipatches, jpatches ! number of patches in i, j directions INTEGER :: ipatch, jpatch ! iterator for patches in i, j directions INTEGER :: pe_west, pe_east, pe_north, pe_south ! patch edge locations (i or j point) INTEGER :: ce_west, ce_east, ce_north, ce_south ! calculation area edge locations INTEGER :: calc_nx, calc_ny ! The number of x, y gridpoints within the calculation area INTEGER :: offset_i, offset_j ! The distance of the patch edge from the W, S edge of calc area REAL, ALLOCATABLE :: lpm_smoothed(:,:) ! 2D (nx, ny) LPM field after gaussian smoothing REAL, ALLOCATABLE :: lpm_calc(:,:) !The LPM over the calculation area. Must be allocated since !we do not know ahead of time how big the calc area will be. REAL, ALLOCATABLE :: var0(:) ! The values from ALL ensemble members at all points on the 2D ! slice of interest crammed into a 1D array (needed for call to ! pm_mean) ! Since this is limited to calc area, size is unknown ahead of ! time. INTEGER :: ix,jy !---------------------------------------------------------------! !Determine whether specified patch_nx, patch_ny divide evenly into the domain !and if they do not, return an error. !!! Kong comment: This might not be necessary - need to loosen IF (MOD((ny - 3), patch_ny) /= 0) THEN CALL arpsstop('LPM MEAN ERROR: patches do not divide evenly into domain (y dir)', 1) END IF IF (MOD((nx - 3), patch_nx) /= 0) THEN CALL arpsstop('LPM MEAN ERROR: patches do not divide evenly into domain (x dir)', 1) END IF ALLOCATE(lpm_smoothed(nx,ny)) ipatches=(nx-3)/patch_nx jpatches=(ny-3)/patch_ny print *,'LPM -- ipatches,jpatches:',ipatches,jpatches !Now loop over each patch and perform the PM mean in that patch's calculation area ! NOTE: where calculation areas extend outside the domain boundary, they are clipped to fit DO ipatch=1, ipatches DO jpatch=1, jpatches !Calculate patch edge locations (i for pe_west, pe_east; j for pe_south, pe_north) ! NOTE: the "2 + " is to deal with the non-physical gridpoint on S, W arps domain edges ! (it excludes the westmost/southmost point (which has index "1" in FORTRAN) pe_west = 2 + ((ipatch-1) * patch_nx) pe_south= 2 + ((jpatch-1) * patch_ny) pe_east = pe_west + patch_nx pe_north= pe_south + patch_ny !Calculate nominal edges of calculation area (before clipping, if any is needed) ce_west = pe_west - ovx ce_east = pe_east + ovx ce_south = pe_south - ovx ce_north = pe_north + ovx !Catch edge cases: offset_i = ovx !If there is no clipping, offset_i is the same as ovx offset_j = ovy !If there is no clipping, offset_j is the same as ovy IF (ce_west < 2) THEN !West edge case offset_i = ovx - (2 - ce_west) ce_west = 2 END IF IF (ce_east > (nx - 2)) THEN !East edge case ce_east = nx - 2 !No need to change offset in this case (only cares about offset !from west side of calcuation area, not east side) END IF IF (ce_south < 2) THEN !South edge case offset_j = ovy - (2 - ce_south) ce_south = 2 END IF IF (ce_north > (ny - 2)) THEN !West edge case ce_north = ny - 2 !No need to change offset in this case (only cares about offset !from south side of calcuation area, not north side) END IF !Define calc_nx, calc_ny: calc_nx = ce_east + 1 - ce_west calc_ny = ce_north + 1 - ce_south !Allocate LPM storage for calculation area and var0 using calc_nx, calc_ny: ALLOCATE(lpm_calc(calc_nx, calc_ny)) ALLOCATE(var0(calc_nx*calc_ny*n_ens)) !Calculate PM mean over calculation area: !First, if all member values in calculation area are zero, then LPM is zero IF(maxval(var2d_ens(ce_west:ce_east, ce_south:ce_north, :)) <= 0 ) THEN lpm_calc = 0.0 ELSE var0 = RESHAPE(var2d_ens(ce_west:ce_east,ce_south:ce_north,:), & (/calc_nx*calc_ny*n_ens/)) !This call calculates PM mean over the calc area and stores it in lpm_calc: CALL pm_mean(calc_nx, calc_ny, n_ens, var0, & var2d_enmean(ce_west:ce_east, ce_south:ce_north), lpm_calc) !Take the result and store the patch into the array for var2d_lpm: var2d_lpm(pe_west:pe_east, pe_south:pe_north) = & lpm_calc(offset_i:offset_i + patch_nx, offset_j:offset_j + patch_ny) END IF !Deallocate values for this patch; will re-allocate again for the next one. DEALLOCATE(lpm_calc) DEALLOCATE(var0) END DO END DO if( filt_min > 0.0 ) then !Filter the LPM so that it is used only above a threshold value DO ix=1, nx DO jy=1, ny !IF(var2d_ens(ix, jy) < filt_min) THEN IF(var2d_enmean(ix, jy) < filt_min) THEN var2d_lpm(ix, jy) = 0.0 END IF END DO END DO endif ! Apply ensemble max as a ceiling (6/7/2017 - Fanyou Kong) var2d_lpm=min(var2d_lpm,var2d_enmax) if( gauss_sigma > 0 ) then !Apply a gaussian smoother to the filtered var2d_lpm to get rid of near-grid-scale noise CALL gaussian_smooth(nx,ny,var2d_lpm,gauss_sigma,partweight, & lpm_smoothed) var2d_lpm=lpm_smoothed endif DEALLOCATE(lpm_smoothed) END SUBROUTINE lpm_mean !---------------------------------------------------------------!