! radiation_overlap.F90 - Module to compute cloud overlap quantities
!
! (C) Copyright 2014- ECMWF.
!
! This software is licensed under the terms of the Apache Licence Version 2.0
! which can be obtained at http://www.apache.org/licenses/LICENSE-2.0.
!
! In applying this licence, ECMWF does not waive the privileges and immunities
! granted to it by virtue of its status as an intergovernmental organisation
! nor does it submit to any jurisdiction.
!
! Author:  Robin Hogan
! Email:   r.j.hogan@ecmwf.int
!
! Modifications
!   2017-10-23  R. Hogan  Renamed single-character variables
!   2018-10-05  R. Hogan  Generalized alpha overlap for non-equal regions
!   2018-10-08  R. Hogan  Removed calc_region_fractions

module radiation_overlap

  implicit none

  public :: calc_overlap_matrices

contains


  ! This function now superceded by calc_region_properties in module
  ! radiation_regions
  ! !---------------------------------------------------------------------
  ! ! Return an array of length nreg containing the fraction of the
  ! ! gridbox occupied by each region for the specified cloud fraction.
  ! pure function calc_region_fractions(nreg, cloud_fraction)

  !   use parkind1, only : jprb

  !   integer,    intent(in)      :: nreg
  !   real(jprb), intent(in)      :: cloud_fraction

  !   real(jprb), dimension(nreg) :: calc_region_fractions
  !   integer :: jreg

  !   if (nreg == 1) then
  !     ! Only one region: must occupy all of gridbox
  !     calc_region_fractions(1) = 1.0_jprb
  !   else
  !     ! Two or more regions: the first is the cloud-free region
  !     calc_region_fractions(1) = 1.0_jprb - cloud_fraction

  !     do jreg = 2,nreg
  !       ! The cloudy regions are assumed to each have the same
  !       ! fraction - see Shonk and Hogan (2008) for justification
  !       calc_region_fractions(jreg) = cloud_fraction / (nreg - 1.0_jprb)
  !     end do
  !   end if

  ! end function calc_region_fractions

  !---------------------------------------------------------------------
  ! Calculate a matrix expressing the overlap of regions in adjacent
  ! layers, using the method of Shonk et al. (2010) in terms of their
  ! "beta" overlap parameter
  pure function calc_beta_overlap_matrix(nreg, op, frac_upper, frac_lower, &
       &  frac_threshold) result(overlap_matrix)

    use parkind1, only : jprb
    
    integer, intent(in) :: nreg ! Number of regions

    ! Overlap parameter for each region, and fraction of the gridbox
    ! occupied by each region in the upper and lower layers
    real(jprb), intent(in), dimension(nreg) :: op, frac_upper, frac_lower

    ! Cloud-fraction threshold below which cloud is deemed not to be
    ! present
    real(jprb), intent(in) :: frac_threshold

    ! Output overlap matrix
    real(jprb) :: overlap_matrix(nreg,nreg)

    ! Denominator and its reciprocal in computing the random part of
    ! the overlap matrix
    real(jprb) :: denominator, factor

    ! Beta overlap parameter multiplied by the minimum region fraction
    ! of the upper and lower layers
    real(jprb) :: op_x_frac_min(nreg)

    integer :: jupper, jlower, jreg

    ! In computing the random part of the overlap matrix we need
    ! to divide all elements by "denominator", or for efficiency
    ! multiply by "factor"
    denominator = 1.0_jprb
    do jreg = 1,nreg
      op_x_frac_min(jreg) = op(jreg) &
           &  * min(frac_upper(jreg), frac_lower(jreg))
      denominator = denominator - op_x_frac_min(jreg)
    end do
    ! In principle the denominator can be zero
    if (denominator >= frac_threshold) then
      factor = 1.0_jprb / denominator
      ! Create the random part of the overlap matrix
      do jupper = 1,nreg
        do jlower = 1,nreg
          overlap_matrix(jupper,jlower) = factor &
               &  * (frac_lower(jlower)-op_x_frac_min(jlower)) &
               &  * (frac_upper(jupper)-op_x_frac_min(jupper))
        end do
      end do
    else
      overlap_matrix = 0.0_jprb
    end if
    
    ! Add on the maximum part of the overlap matrix
    do jreg = 1,nreg
      overlap_matrix(jreg,jreg) = overlap_matrix(jreg,jreg) &
           &  + op_x_frac_min(jreg)
    end do

  end function calc_beta_overlap_matrix


  !---------------------------------------------------------------------
  ! Calculate a matrix expressing the overlap of regions in adjacent
  ! layers, using the Hogan and Illingworth (2000) "alpha" overlap
  ! parameter, but allowing for the two cloudy regions in the
  ! Tripleclouds assumption to have different areas
  pure function calc_alpha_overlap_matrix(nreg, op, op_inhom, &
       &  frac_upper, frac_lower) result(overlap_matrix)

    use parkind1, only : jprb
    
    integer, intent(in) :: nreg ! Number of regions

    ! Overlap parameter for cloud boundaries and for internal
    ! inhomogeneities
    real(jprb), intent(in) :: op, op_inhom

    ! Fraction of the gridbox occupied by each region in the upper and
    ! lower layers
    real(jprb), intent(in), dimension(nreg) :: frac_upper, frac_lower

    ! Output overlap matrix
    real(jprb) :: overlap_matrix(nreg,nreg)

    ! Combined cloud cover of pair of layers
    real(jprb) :: pair_cloud_cover

    ! Cloud fraction of upper and lower layers
    real(jprb) :: cf_upper, cf_lower

    ! One divided by cloud fraction
    real(jprb) :: one_over_cf

    ! Fraction of domain with cloud in both layers
    real(jprb) :: frac_both

    cf_upper = sum(frac_upper(2:nreg))
    cf_lower = sum(frac_lower(2:nreg))

    pair_cloud_cover = op*max(cf_upper,cf_lower) &
           &  + (1.0_jprb - op) &
           &  * (cf_upper+cf_lower-cf_upper*cf_lower)

    ! Clear in both layers
    overlap_matrix(1,1) = 1.0_jprb - pair_cloud_cover
    if (nreg == 2) then
      ! Clear in upper layer, cloudy in lower layer
      overlap_matrix(1,2) = pair_cloud_cover - cf_upper
      ! Clear in lower layer, cloudy in upper layer
      overlap_matrix(2,1) = pair_cloud_cover - cf_lower
      ! Cloudy in both layers
      overlap_matrix(2,2) = cf_upper + cf_lower - pair_cloud_cover
    else
       ! Clear in upper layer, cloudy in lower layer
      one_over_cf = 1.0_jprb / max(cf_lower, 1.0e-6_jprb)
      overlap_matrix(1,2) = (pair_cloud_cover - cf_upper) &
           &              * frac_lower(2) * one_over_cf
      overlap_matrix(1,3) = (pair_cloud_cover - cf_upper) &
           &              * frac_lower(3) * one_over_cf
      ! Clear in lower layer, cloudy in upper layer
      one_over_cf = 1.0_jprb / max(cf_upper, 1.0e-6_jprb)
      overlap_matrix(2,1) = (pair_cloud_cover - cf_lower) &
           &              * frac_upper(2) * one_over_cf
      overlap_matrix(3,1) = (pair_cloud_cover - cf_lower) &
           &              * frac_upper(3) * one_over_cf
      ! Cloudy in both layers: frac_both is the fraction of the
      ! gridbox with cloud in both layers
      frac_both = cf_upper + cf_lower - pair_cloud_cover
      ! Treat low and high optical-depth regions within frac_both as
      ! one treats clear and cloudy skies in the whole domain;
      ! redefine the following variables treating the high
      ! optical-depth region as the cloud
      cf_upper = frac_upper(3) / max(cf_upper, 1.0e-6_jprb)
      cf_lower = frac_lower(3) / max(cf_lower, 1.0e-6_jprb)
      pair_cloud_cover = op_inhom*max(cf_upper,cf_lower) &
           &  + (1.0_jprb - op_inhom) &
           &  * (cf_upper+cf_lower-cf_upper*cf_lower)
      ! Assign overlaps for this 2x2 section of the 3x3 matrix as for
      ! the 2-region case above, but multiplied by frac_both
      overlap_matrix(2,2) = frac_both * (1.0_jprb - pair_cloud_cover)
      overlap_matrix(2,3) = frac_both * (pair_cloud_cover - cf_upper)
      overlap_matrix(3,2) = frac_both * (pair_cloud_cover - cf_lower)
      overlap_matrix(3,3) = frac_both * (cf_upper+cf_lower-pair_cloud_cover)
    end if

  end function calc_alpha_overlap_matrix


  !---------------------------------------------------------------------
  ! Calculate a matrix expressing the overlap of regions in adjacent
  ! layers, using the Hogan and Illingworth (2000) "alpha" overlap
  ! parameter, and assuming the two cloudy regions in the Tripleclouds
  ! assumption have the same area
  pure function calc_alpha_overlap_matrix_simple(nreg, op, op_inhom, &
       &  cf_upper, cf_lower) result(overlap_matrix)

    use parkind1, only : jprb
    
    integer, intent(in) :: nreg ! Number of regions

    ! Overlap parameter for cloud boundaries and for internal
    ! inhomogeneities
    real(jprb), intent(in) :: op, op_inhom

    ! Cloud fraction in the upper and lower layers
    real(jprb), intent(in) :: cf_upper, cf_lower

    ! Output overlap matrix
    real(jprb) :: overlap_matrix(nreg,nreg)

    ! Combined cloud cover of pair of layers
    real(jprb) :: pair_cloud_cover

    real(jprb) :: cloud_unit

    pair_cloud_cover = op*max(cf_upper,cf_lower) &
           &  + (1.0_jprb - op) &
           &  * (cf_upper+cf_lower-cf_upper*cf_lower)

    ! Clear in both layers
    overlap_matrix(1,1) = 1.0_jprb - pair_cloud_cover
    if (nreg == 2) then
      ! Clear in upper layer, cloudy in lower layer
      overlap_matrix(1,2) = pair_cloud_cover - cf_upper
      ! Clear in lower layer, cloudy in upper layer
      overlap_matrix(2,1) = pair_cloud_cover - cf_lower
      ! Cloudy in both layers
      overlap_matrix(2,2) = cf_upper + cf_lower - pair_cloud_cover
    else
      ! The following assumes that the two cloudy regions are of equal area.
      ! Clear in upper layer, cloudy in lower layer
      overlap_matrix(1,2) = 0.5_jprb * (pair_cloud_cover - cf_upper)
      overlap_matrix(1,3) = overlap_matrix(1,2)
      ! Clear in lower layer, cloudy in upper layer
      overlap_matrix(2,1) = 0.5_jprb * (pair_cloud_cover - cf_lower)
      overlap_matrix(3,1) = overlap_matrix(2,1)
      ! Cloudy in both layers
      cloud_unit = 0.25_jprb * (cf_upper + cf_lower - pair_cloud_cover)
      overlap_matrix(2,2) = cloud_unit * (1.0_jprb + op_inhom)
      overlap_matrix(2,3) = cloud_unit * (1.0_jprb - op_inhom)
      overlap_matrix(3,3) = overlap_matrix(2,2)
      overlap_matrix(3,2) = overlap_matrix(2,3)
    end if

  end function calc_alpha_overlap_matrix_simple


  !---------------------------------------------------------------------
  ! Compute the upward and downward overlap matrices u_matrix and
  ! v_matrix, respectively, where u_matrix is defined such that
  ! y=u_matrix*x, where x is a vector of upwelling fluxes in each
  ! region just below an interface, and y is a vector of upwelling
  ! fluxes in each region just above that interface. For nlev model
  ! levels there are nlev+1 interfaces including the ground and
  ! top-of-atmosphere, and so that is one of the dimensions of
  ! u_matrix and v_matrix.
  subroutine calc_overlap_matrices(nlev,nreg,istartcol,iendcol, &
       &     region_fracs, overlap_param, u_matrix, v_matrix, decorrelation_scaling, &
       &     cloud_fraction_threshold, cloud_cover, use_beta_overlap)

    use parkind1,     only : jprb
    use yomhook,      only : lhook, dr_hook

    ! Number of levels and regions
    integer,  intent(in) :: nlev, nreg

    ! Range of columns to process (also outer dimensions of u_matrix
    ! and v_matrix)
    integer, intent(in) :: istartcol, iendcol

    ! Area fraction of each region: region 1 is clear sky, and 2+ are
    ! the cloudy regions (only one or two cloudy regions are
    ! supported)
    real(jprb), intent(in), dimension(1:nreg,nlev,istartcol:iendcol)  :: region_fracs

    ! The overlap parameter: either the "alpha" of Hogan & Illingworth
    ! (2000) or the "beta" of Shonk et al. (2010)
    real(jprb), intent(in), dimension(:,:)  :: overlap_param  ! (ncol,nlev-1)

    ! Output overlap matrices
    real(jprb), intent(out), dimension(nreg,nreg,nlev+1,istartcol:iendcol) &
         &  :: u_matrix, v_matrix

    ! For regions 2 and above, the overlap decorrelation length for
    ! cloud boundaries is scaled by this amount to obtain the overlap
    ! decorrelation length for cloud inhomogeneities. Typically this
    ! number is 0.5, but if omitted it will be assumed to be one (same
    ! decorrelation for cloud boundaries and in-cloud inhomogeneities)
    real(jprb), intent(in), optional :: decorrelation_scaling

    ! Regions smaller than this are ignored
    real(jprb), intent(in), optional :: cloud_fraction_threshold

    ! The diagnosed cloud cover is an optional output
    real(jprb), intent(out), optional :: cloud_cover(:)

    ! Do we use Shonk et al.'s (2010) "beta" overlap parameter?
    logical, intent(in), optional :: use_beta_overlap

    ! Loop indices for column, level, region and the regions in the
    ! upper and lower layers for an interface
    integer  :: jcol, jlev, jupper, jlower

    ! Overlap matrix (non-directional)
    real(jprb) :: overlap_matrix(nreg,nreg)

    ! Fraction of the gridbox occupied by each region in the upper and
    ! lower layers for an interface
    real(jprb) :: frac_upper(nreg), frac_lower(nreg)

    ! Beta overlap parameter for each region
    real(jprb) :: op(nreg)

    ! In case the user doesn't supply cloud_fraction_threshold we use
    ! a default value
    real(jprb) :: frac_threshold

    ! The decorrelation scaling to use, in case decorrelation_scaling
    ! was not provided
    real(jprb) :: used_decorrelation_scaling

    logical :: use_beta_overlap_param

    real(jprb) :: hook_handle

    if (lhook) call dr_hook('radiation_overlap:calc_overlap_matrices',0,hook_handle)

    if (present(decorrelation_scaling)) then
       used_decorrelation_scaling = decorrelation_scaling
    else
       used_decorrelation_scaling = 1.0_jprb
    end if

    if (present(cloud_fraction_threshold)) then
      frac_threshold = cloud_fraction_threshold
    else
      frac_threshold = 1.0e-20_jprb
    end if

    if (present(use_beta_overlap)) then
      use_beta_overlap_param = use_beta_overlap
    else
      use_beta_overlap_param = .false.
    end if

    ! Loop through each atmospheric column
    do jcol = istartcol, iendcol
      ! For this column, outer space is treated as one clear-sky
      ! region, so the fractions are assigned as such
      frac_upper(1) = 1.0_jprb
      frac_upper(2:nreg) = 0.0_jprb

      ! Overlap parameter is irrelevant when there is only one region
      ! in the upper layer
      op = 1.0_jprb

      ! Loop down through the atmosphere, where jlev indexes each
      ! half-level starting at 1 for the top-of-atmosphere, as well
      ! as indexing each level starting at 1 for the top-most level.
      do jlev = 1,nlev+1
        ! Fraction of each region just below the interface
        if (jlev > nlev) then
          ! We are at the surface: treat as a single clear-sky
          ! region
          frac_lower(1) = 1.0_jprb
          frac_lower(2:nreg) = 0.0_jprb
        else
          frac_lower = region_fracs(1:nreg,jlev,jcol)
        end if
   
        ! Compute the overlap parameter of the interface just below
        ! the current full level
        if (jlev == 1 .or. jlev > nlev) then
          ! We are at the surface or top-of-atmosphere: overlap
          ! parameter is irrelevant
          op = 1.0_jprb
        else
          ! We are not at the surface
          op(1) = overlap_param(jcol,jlev-1)
          ! For cloudy regions, scale the cloud-boundary overlap
          ! parameter to obtain the cloud-inhomogeneity overlap
          ! parameter as follows
          if (op(1) >= 0.0_jprb) then
            op(2:nreg) = op(1)**(1.0_jprb/used_decorrelation_scaling)
          else
            op(2:nreg) = op(1)
          end if
        end if
     
        if (use_beta_overlap_param) then
          overlap_matrix = calc_beta_overlap_matrix(nreg, op, &
               &  frac_upper, frac_lower, frac_threshold)
        else
          ! Simpler scheme assuming the two cloudy regions have the
          ! same fraction
          !overlap_matrix = calc_alpha_overlap_matrix_simple(nreg, &
          !     &  op(1), op(2), &
          !     &  1.0_jprb - frac_upper(1), 1.0_jprb - frac_lower(1))
          ! More general scheme
          overlap_matrix = calc_alpha_overlap_matrix(nreg, &
               &  op(1), op(2), frac_upper, frac_lower)
        end if

        ! Convert to directional overlap matrices
        do jupper = 1,nreg
          do jlower = 1,nreg
            if (frac_lower(jlower) >= frac_threshold) then
              u_matrix(jupper,jlower,jlev,jcol) = overlap_matrix(jupper,jlower) &
                   &  / frac_lower(jlower)
            else
              u_matrix(jupper,jlower,jlev,jcol) = 0.0_jprb
            end if
            if (frac_upper(jupper) >= frac_threshold) then
              v_matrix(jlower,jupper,jlev,jcol) = overlap_matrix(jupper,jlower) &
                   &  / frac_upper(jupper)
            else
              v_matrix(jlower,jupper,jlev,jcol) = 0.0_jprb
            end if
          end do
        end do
        frac_upper = frac_lower
        
      end do ! levels

      ! Compute cloud cover from one of the directional overlap matrices
      if (present(cloud_cover)) then
        cloud_cover(jcol) = 1.0_jprb - product(v_matrix(1,1,:,jcol))
      end if

    end do ! columns

    if (lhook) call dr_hook('radiation_overlap:calc_overlap_matrices',1,hook_handle)

  end subroutine calc_overlap_matrices
  
end module radiation_overlap
