mo_cloud_optics_rrtmgp.F90 Source File


Contents


Source Code

! This code is part of Radiative Transfer for Energetics (RTE)
!
! Contacts: Robert Pincus and Eli Mlawer
! email:  rrtmgp@aer.com
!
! Copyright 2015-2018,  Atmospheric and Environmental Research and
! Regents of the University of Colorado.  All right reserved.
!
! Use and duplication is permitted under the terms of the
!    BSD 3-clause license, see http://opensource.org/licenses/BSD-3-Clause
! -------------------------------------------------------------------------------------------------
! Provides cloud optical properties as a function of effective radius for the RRTMGP bands
!   or by g-point. Based on Mie calculations for liquid and results from Yang et al. (2013)
!     (doi:10.1175/JAS-D-12-039.1) for ice with variable surface roughness.
!   Can use either look-up tables by spectral band or by g-point.
!
! The class can be used as-is but is also intended as an example of how to extend the RTE framework
! -------------------------------------------------------------------------------------------------

module mo_cloud_optics_rrtmgp
  use mo_rte_kind,      only: wp, wl
  use mo_rte_config,    only: check_values, check_extents
  use mo_rte_util_array_validation,&
                        only: any_vals_less_than, any_vals_outside, extents_are
  use mo_optical_props, only: ty_optical_props,      &
                              ty_optical_props_arry, &
                              ty_optical_props_1scl, &
                              ty_optical_props_2str, &
                              ty_optical_props_nstr
  use mo_cloud_optics_rrtmgp_kernels, &
                        only: compute_cld_from_table
  implicit none
  private
  ! -----------------------------------------------------------------------------------
  type, extends(ty_optical_props), public :: ty_cloud_optics_rrtmgp
    private
    !
    ! Ice surface roughness category - needed for Yang (2013) ice optics parameterization
    !
    integer            :: icergh = 0  ! (1 = none, 2 = medium, 3 = high)
    !
    ! Lookup table information
    !
    ! Upper and lower limits of the tables
    real(wp) :: radliq_lwr = 0._wp, radliq_upr = 0._wp
    real(wp) :: diamice_lwr = 0._wp, diamice_upr = 0._wp
    ! How many steps in the table? (for convenience)
    integer  :: liq_nsteps = 0,        ice_nsteps = 0
    ! How big is each step in the table?
    real(wp) :: liq_step_size = 0._wp, ice_step_size = 0._wp
    !
    ! Cloud optics lookup tables  - by g-point or by band (with ngpt=nbnd)
    !
    real(wp), dimension(:,:  ), allocatable :: extliq, ssaliq, asyliq ! (nsize_liq, ngpt)
    real(wp), dimension(:,:,:), allocatable :: extice, ssaice, asyice ! (nsize_ice, ngpt, nrghice)
    !
    ! -----
  contains
    procedure, public :: load
    procedure, public :: finalize
    procedure, public :: cloud_optics
    procedure, public :: get_min_radius_liq
    procedure, public :: get_min_radius_ice
    procedure, public :: get_max_radius_liq
    procedure, public :: get_max_radius_ice
    procedure, public :: get_num_ice_roughness_types
    procedure, public :: set_ice_roughness
  end type ty_cloud_optics_rrtmgp

contains
  ! ------------------------------------------------------------------------------
  !
  ! Routines to load lookup table data needed for cloud optics calculations either
  !    by spectral band or by g-point.
  !
  ! ------------------------------------------------------------------------------
  function load(this, band_lims_wvn, &
                radliq_lwr, radliq_upr, &
                diamice_lwr, diamice_upr, &
                extliq, ssaliq, asyliq, &
                extice, ssaice, asyice, &
                band_lims_gpt) result(error_msg)

    class(ty_cloud_optics_rrtmgp), intent(inout) :: this
    real(wp), dimension(:,:),   intent(in   ) :: band_lims_wvn ! beginning and ending wavenumbers for each band
    ! Lookup table interpolation constants
    ! Lower and upper bounds of the tables; also the constant for calculating interpolation indices for liquid
    real(wp),                   intent(in   ) :: radliq_lwr, radliq_upr
    real(wp),                   intent(in   ) :: diamice_lwr, diamice_upr
    ! LUT coefficients
    ! Extinction, single-scattering albedo, and asymmetry parameter for liquid and ice respectively
    real(wp), dimension(:,:),   intent(in   ) :: extliq, ssaliq, asyliq
    real(wp), dimension(:,:,:), intent(in   ) :: extice, ssaice, asyice
    integer,  dimension(:,:), optional,&
                                intent(in   ) :: band_lims_gpt ! beginning and ending g-points for each band
    character(len=128)    :: error_msg
    ! -------
    !
    ! Local variables
    !
    integer :: nrghice, nsize_liq, nsize_ice
    integer :: ngpt

    error_msg = this%init(band_lims_wvn, band_lims_gpt, name="RRTMGP cloud optics")
    !
    ! LUT coefficient dimensions
    !
    nsize_liq = size(extliq,dim=1)
    nsize_ice = size(extice,dim=1)
    nrghice   = size(extice,dim=3)
    ngpt     = this%get_ngpt() ! Same as the number of bands if defined by-band
    !
    ! Error checking
    !   Can we check for consistency between table bounds
    !
    if(.not. extents_are(extliq, nsize_liq, ngpt)) &
      error_msg = "cloud_optics%init(): array extliq isn't consistently sized"
    if(.not. extents_are(ssaliq, nsize_liq, ngpt)) &
      error_msg = "cloud_optics%init(): array ssaliq isn't consistently sized"
    if(.not. extents_are(asyliq, nsize_liq, ngpt)) &
      error_msg = "cloud_optics%init(): array asyliq isn't consistently sized"
    if(.not. extents_are(extice, nsize_ice, ngpt, nrghice)) &
      error_msg = "cloud_optics%init(): array extice isn't consistently sized"
    if(.not. extents_are(ssaice, nsize_ice, ngpt, nrghice)) &
      error_msg = "cloud_optics%init(): array ssaice isn't consistently sized"
    if(.not. extents_are(asyice, nsize_ice, ngpt, nrghice)) &
      error_msg = "cloud_optics%init(): array asyice isn't consistently sized"
    if(error_msg /= "") return

    this%liq_nsteps = nsize_liq
    this%ice_nsteps = nsize_ice
    this%liq_step_size = (radliq_upr - radliq_lwr)/real(nsize_liq-1,wp)
    this%ice_step_size = (diamice_upr - diamice_lwr)/real(nsize_ice-1,wp)
    ! Allocate LUT coefficients
    allocate(this%extliq(nsize_liq, ngpt), &
             this%ssaliq(nsize_liq, ngpt), &
             this%asyliq(nsize_liq, ngpt), &
             this%extice(nsize_ice, ngpt, nrghice), &
             this%ssaice(nsize_ice, ngpt, nrghice), &
             this%asyice(nsize_ice, ngpt, nrghice))

    !$acc enter data create(this)                                               &
    !$acc            create(this%extliq, this%ssaliq, this%asyliq)  &
    !$acc            create(this%extice, this%ssaice, this%asyice)
    !$omp target enter data &
    !$omp map(alloc:this%extliq, this%ssaliq, this%asyliq) &
    !$omp map(alloc:this%extice, this%ssaice, this%asyice)
    ! Load band LUT constants
    this%radliq_lwr = radliq_lwr
    this%radliq_upr = radliq_upr
    this%diamice_lwr = diamice_lwr
    this%diamice_upr = diamice_upr

    ! Load LUT coefficients
    !$acc kernels
    !$omp target
    this%extliq = extliq
    this%ssaliq = ssaliq
    this%asyliq = asyliq
    this%extice = extice
    this%ssaice = ssaice
    this%asyice = asyice
    !$acc end kernels
    !$omp end target
    !
    ! Set default ice roughness - min values
    !
    error_msg = this%set_ice_roughness(1)
  end function load
  !--------------------------------------------------------------------------------------------------------------------
  !
  ! Finalize
  !
  !--------------------------------------------------------------------------------------------------------------------
  subroutine finalize(this)
    class(ty_cloud_optics_rrtmgp), intent(inout) :: this

    this%radliq_lwr = 0._wp
    this%radliq_upr = 0._wp
    this%diamice_lwr = 0._wp
    this%diamice_upr = 0._wp

    ! Lookup table cloud optics coefficients
    if(allocated(this%extliq)) then

      !$acc exit data delete(this%extliq, this%ssaliq, this%asyliq)  &
      !$acc           delete(this%extice, this%ssaice, this%asyice)  &
      !$acc           delete(this)
      !$omp target exit data map(release:this%extliq, this%ssaliq, this%asyliq) &
      !$omp map(release:this%extice, this%ssaice, this%asyice)


      deallocate(this%extliq, this%ssaliq, this%asyliq, &
                 this%extice, this%ssaice, this%asyice)
      this%liq_nsteps = 0
      this%ice_nsteps = 0
      this%liq_step_size = 0._wp
      this%ice_step_size = 0._wp
    end if

  end subroutine finalize
  ! ------------------------------------------------------------------------------
  !
  ! Derive cloud optical properties from provided cloud physical properties for
  ! either band or g-point discretization
  !
  ! ------------------------------------------------------------------------------
  !
  ! Compute single-scattering properties
  !
  function cloud_optics(this,                     &
                        clwp, ciwp, reliq, reice, &
                        optical_props) result(error_msg)
    class(ty_cloud_optics_rrtmgp), &
              intent(in   ) :: this
    real(wp), intent(in   ) :: clwp  (:,:), &   ! cloud liquid water path (g/m2)
                               ciwp  (:,:), &   ! cloud ice water path    (g/m2)
                               reliq (:,:), &   ! cloud liquid particle effective size (microns)
                               reice (:,:)      ! cloud ice particle effective radius  (microns)
    class(ty_optical_props_arry), &
              intent(inout) :: optical_props
                                               ! Dimensions: (ncol,nlay,ngpt/nbnd)

    character(len=128)      :: error_msg
    ! ------- Local -------
    logical(wl), dimension(size(clwp,1), size(clwp,2)) :: liqmsk, icemsk
    real(wp),    dimension(size(clwp,1), size(clwp,2), this%get_ngpt()) :: &
                ltau, ltaussa, ltaussag, itau, itaussa, itaussag
                ! Optical properties: tau, tau*ssa, tau*ssa*g
                ! liquid and ice separately
    integer  :: ncol, nlay
    integer  :: nsizereg
    integer  :: icol, ilay, igpt
    ! scalars for total tau, tau*ssa
    real(wp) :: tau, taussa

    ! ----------------------------------------
    !
    ! Error checking
    !
    ! ----------------------------------------

    error_msg = ''
    if(.not.(allocated(this%extliq))) then
      error_msg = 'cloud optics: no data has been initialized'
      return
    end if

    ncol = size(clwp,1)
    nlay = size(clwp,2)
    !
    ! Array sizes
    !
    if (check_extents) then
      if(size(liqmsk,1) /= ncol .or. size(liqmsk,2) /= nlay) &
        error_msg = "cloud optics: liqmask has wrong extents"
      if(size(icemsk,1) /= ncol .or. size(icemsk,2) /= nlay) &
        error_msg = "cloud optics: icemsk has wrong extents"
      if(size(ciwp,  1) /= ncol .or. size(ciwp,  2) /= nlay) &
        error_msg = "cloud optics: ciwp has wrong extents"
      if(size(reliq, 1) /= ncol .or. size(reliq, 2) /= nlay) &
        error_msg = "cloud optics: reliq has wrong extents"
      if(size(reice, 1) /= ncol .or. size(reice, 2) /= nlay) &
        error_msg = "cloud optics: reice has wrong extents"
      if(optical_props%get_ncol() /= ncol .or. optical_props%get_nlay() /= nlay) &
        error_msg = "cloud optics: optical_props have wrong extents"
      if(error_msg /= "") return
    end if

    !
    ! Spectral consistency
    !
    if(check_values) then
      if(.not. this%bands_are_equal(optical_props)) &
        error_msg = "cloud optics: optical properties don't have the same band structure"
      if(error_msg /= "") return
    end if

    !$acc data copyin(clwp, ciwp, reliq, reice)                         &
    !$acc      create(ltau, ltaussa, ltaussag, itau, itaussa, itaussag) &
    !$acc      create(liqmsk,icemsk)
    !$omp target data map(to:clwp, ciwp, reliq, reice) &
    !$omp map(alloc:ltau, ltaussa, ltaussag, itau, itaussa, itaussag) &
    !$omp map(alloc:liqmsk, icemsk)
    !
    ! Cloud masks; don't need value re values if there's no cloud
    !
    !$acc parallel loop gang vector default(present) collapse(2)
    !$omp target teams distribute parallel do simd collapse(2)
    do ilay = 1, nlay
      do icol = 1, ncol
        liqmsk(icol,ilay) = clwp(icol,ilay) > 0._wp
        icemsk(icol,ilay) = ciwp(icol,ilay) > 0._wp
      end do
    end do

    !
    ! Particle size, liquid/ice water paths
    !
    if(check_values) then
      if(any_vals_outside(reliq, liqmsk, this%radliq_lwr, this%radliq_upr)) &
        error_msg = 'cloud optics: liquid effective radius is out of bounds'
      if(any_vals_outside(reice, icemsk, this%diamice_lwr, this%diamice_upr)) &
        error_msg = 'cloud optics: ice effective radius is out of bounds'
      if(any_vals_less_than(clwp, liqmsk, 0._wp) .or. any_vals_less_than(ciwp, icemsk, 0._wp)) &
        error_msg = 'cloud optics: negative clwp or ciwp where clouds are supposed to be'
    end if
    if(error_msg == "") then
      !
      !
      ! ----------------------------------------
      !
      ! The band or g-point tables determining extinction coeffient, single-scattering
      !   albedo, and asymmetry parameter (g) as a function of effective radius.
      ! We compute the optical depth tau (=exintinction coeff * condensed water path)
      !   and the products tau*ssa and tau*ssa*g for liquid and ice cloud separately.
      ! These are used to determine the optical properties of ice and water cloud together.
      ! We could compute the properties for liquid and ice separately and
      !    use ty_optical_props_arry%increment but this involves substantially more division.
      !
      if (allocated(this%extliq)) then
        !
        ! Cloud optical properties by spectral band or g-point
        !
        ! Liquid
        !
        call compute_cld_from_table(ncol, nlay, this%get_ngpt(), liqmsk, clwp, reliq,   &
                                    this%liq_nsteps,this%liq_step_size,this%radliq_lwr, &
                                    this%extliq, this%ssaliq, this%asyliq,              &
                                    ltau, ltaussa, ltaussag)
        !
        ! Ice
        !
        call compute_cld_from_table(ncol, nlay, this%get_ngpt(), icemsk, ciwp, reice,   &
                                    this%ice_nsteps,this%ice_step_size,this%diamice_lwr,&
                                    this%extice(:,:,this%icergh),                       &
                                    this%ssaice(:,:,this%icergh),                       &
                                    this%asyice(:,:,this%icergh),                       &
                                    itau, itaussa, itaussag)
      endif

      !
      ! Combine liquid and ice contributions into total cloud optical properties
      !   See also the increment routines in mo_optical_props_kernels
      !
      select type(optical_props)
      type is (ty_optical_props_1scl)
        !$acc parallel loop gang vector default(present) collapse(3) &
        !$acc               copyin(optical_props) copyout(optical_props%tau)
        !$omp target teams distribute parallel do simd collapse(3) &
        !$omp map(from:optical_props%tau)

        do igpt = 1, this%get_ngpt()
          do ilay = 1, nlay
            do icol = 1,ncol
              ! Absorption optical depth  = (1-ssa) * tau = tau - taussa
              optical_props%tau(icol,ilay,igpt) = (ltau(icol,ilay,igpt) - ltaussa(icol,ilay,igpt)) + &
                                                   (itau(icol,ilay,igpt) - itaussa(icol,ilay,igpt))
            end do
          end do
        end do
      type is (ty_optical_props_2str)
        !$acc parallel loop gang vector default(present) collapse(3) &
        !$acc               copyin(optical_props) copyout(optical_props%tau, optical_props%ssa, optical_props%g)
        !$omp target teams distribute parallel do simd collapse(3) &
        !$omp map(from:optical_props%tau, optical_props%ssa, optical_props%g)
        do igpt = 1, this%get_ngpt()
          do ilay = 1, nlay
            do icol = 1,ncol
              tau    = ltau    (icol,ilay,igpt) + itau   (icol,ilay,igpt)
              taussa = ltaussa (icol,ilay,igpt) + itaussa(icol,ilay,igpt)
              optical_props%g  (icol,ilay,igpt) = (ltaussag(icol,ilay,igpt) + itaussag(icol,ilay,igpt)) / &
                                                        max(epsilon(tau), taussa)
              optical_props%ssa(icol,ilay,igpt) = taussa/max(epsilon(tau), tau)
              optical_props%tau(icol,ilay,igpt) = tau
            end do
          end do
        end do
      type is (ty_optical_props_nstr)
        error_msg = "cloud optics: n-stream calculations not yet supported"
      end select
    end if
    !$acc end data
    !$omp end target data
  end function cloud_optics
  !--------------------------------------------------------------------------------------------------------------------
  !
  ! Inquiry functions
  !
  !--------------------------------------------------------------------------------------------------------------------
  function set_ice_roughness(this, icergh) result(error_msg)
    class(ty_cloud_optics_rrtmgp), intent(inout) :: this
    integer,                intent(in   ) :: icergh
    character(len=128)                    :: error_msg

    error_msg = ""
    if(.not. allocated(this%extice)) &
      error_msg = "cloud_optics%set_ice_roughness(): can't set before initialization"
    if (icergh < 1 .or. icergh > this%get_num_ice_roughness_types()) &
       error_msg = 'cloud optics: cloud ice surface roughness flag is out of bounds'
    if(error_msg /= "") return

    this%icergh = icergh
  end function set_ice_roughness
  !-----------------------------------------------
  function get_num_ice_roughness_types(this) result(i)
    class(ty_cloud_optics_rrtmgp), intent(in   ) :: this
    integer                               :: i

    i = 0
    if(allocated(this%extice)) i = size(this%extice, dim=3)
  end function get_num_ice_roughness_types
  !-----------------------------------------------
  function get_min_radius_liq(this) result(r)
    class(ty_cloud_optics_rrtmgp), intent(in   ) :: this
    real(wp)                              :: r

    r = this%radliq_lwr
  end function get_min_radius_liq
  !-----------------------------------------------
  function get_max_radius_liq(this) result(r)
    class(ty_cloud_optics_rrtmgp), intent(in   ) :: this
    real(wp)                              :: r

    r = this%radliq_upr
  end function get_max_radius_liq
  !-----------------------------------------------
  function get_min_radius_ice(this) result(r)
    class(ty_cloud_optics_rrtmgp), intent(in   ) :: this
    real(wp)                              :: r

    r = this%diamice_lwr
  end function get_min_radius_ice
  !-----------------------------------------------
  function get_max_radius_ice(this) result(r)
    class(ty_cloud_optics_rrtmgp), intent(in   ) :: this
    real(wp)                              :: r

    r = this%diamice_upr
  end function get_max_radius_ice
end module mo_cloud_optics_rrtmgp