From 340f2e849f46e65d1e49c42ab7b00f05ffc61bbc Mon Sep 17 00:00:00 2001 From: Quentin Rodier <quentin.rodier@meteo.fr> Date: Thu, 21 Dec 2023 14:06:26 +0100 Subject: [PATCH] Quentin 21/12/2023: add files from ecrad-1-4-0 official repo to ecrad-1-4-0_mnh before being modified --- .../radiation/radiation_cloud_optics.F90 | 491 ++++ .../radiation/radiation_config.F90 | 1980 +++++++++++++++++ .../radiation/radiation_delta_eddington.h | 93 + 3 files changed, 2564 insertions(+) create mode 100644 src/LIB/RAD/ecrad-1.4.0_mnh/radiation/radiation_cloud_optics.F90 create mode 100644 src/LIB/RAD/ecrad-1.4.0_mnh/radiation/radiation_config.F90 create mode 100644 src/LIB/RAD/ecrad-1.4.0_mnh/radiation/radiation_delta_eddington.h diff --git a/src/LIB/RAD/ecrad-1.4.0_mnh/radiation/radiation_cloud_optics.F90 b/src/LIB/RAD/ecrad-1.4.0_mnh/radiation/radiation_cloud_optics.F90 new file mode 100644 index 000000000..0fa20eb82 --- /dev/null +++ b/src/LIB/RAD/ecrad-1.4.0_mnh/radiation/radiation_cloud_optics.F90 @@ -0,0 +1,491 @@ +! radiation_cloud_optics.F90 - Computing cloud optical properties +! +! (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-07-22 R. Hogan Added Yi et al. ice optics model + +module radiation_cloud_optics + + implicit none + public + +contains + + ! Provides elemental function "delta_eddington_scat_od" +#include "radiation_delta_eddington.h" + + !--------------------------------------------------------------------- + ! Load cloud scattering data; this subroutine delegates to one + ! in radiation_cloud_optics_data.F90, but checks the size of + ! what is returned + subroutine setup_cloud_optics(config) + + use parkind1, only : jprb + use yomhook, only : lhook, dr_hook + + use radiation_io, only : nulerr, radiation_abort + use radiation_config, only : config_type, IIceModelFu, IIceModelBaran, & + & IIceModelBaran2016, IIceModelBaran2017, & + & IIceModelYi, & + & ILiquidModelSOCRATES, ILiquidModelSlingo + use radiation_cloud_optics_data, only : cloud_optics_type + use radiation_ice_optics_fu, only : NIceOpticsCoeffsFuSW, & + & NIceOpticsCoeffsFuLW + use radiation_ice_optics_baran, only : NIceOpticsCoeffsBaran, & + & NIceOpticsCoeffsBaran2016 + use radiation_ice_optics_baran2017, only : NIceOpticsCoeffsBaran2017, & + & NIceOpticsGeneralCoeffsBaran2017 + use radiation_ice_optics_yi, only : NIceOpticsCoeffsYiSW, & + & NIceOpticsCoeffsYiLW + use radiation_liquid_optics_socrates, only : NLiqOpticsCoeffsSOCRATES + use radiation_liquid_optics_slingo, only : NLiqOpticsCoeffsSlingoSW, & + & NLiqOpticsCoeffsLindnerLiLW + + type(config_type), intent(inout) :: config + + real(jprb) :: hook_handle + + if (lhook) call dr_hook('radiation_cloud_optics:setup_cloud_optics',0,hook_handle) + + call config%cloud_optics%setup(trim(config%liq_optics_file_name), & + & trim(config%ice_optics_file_name), iverbose=config%iverbosesetup) + + ! Check liquid coefficients + if (size(config%cloud_optics%liq_coeff_lw, 1) /= config%n_bands_lw) then + write(nulerr,'(a,i0,a,i0,a)') & + & '*** Error: number of longwave bands for droplets (', & + & size(config%cloud_optics%liq_coeff_lw, 1), & + & ') does not match number for gases (', config%n_bands_lw, ')' + call radiation_abort() + end if + if (size(config%cloud_optics%liq_coeff_sw, 1) /= config%n_bands_sw) then + write(nulerr,'(a,i0,a,i0,a)') & + & '*** Error: number of shortwave bands for droplets (', & + & size(config%cloud_optics%liq_coeff_sw, 1), & + & ') does not match number for gases (', config%n_bands_sw, ')' + call radiation_abort() + end if + + if (config%i_liq_model == ILiquidModelSOCRATES) then + if (size(config%cloud_optics%liq_coeff_lw, 2) /= NLiqOpticsCoeffsSOCRATES) then + write(nulerr,'(a,i0,a,i0,a,i0,a)') & + & '*** Error: number of liquid cloud optical coefficients (', & + & size(config%cloud_optics%liq_coeff_lw, 2), & + & ') does not match number expected (', NLiqOpticsCoeffsSOCRATES,')' + call radiation_abort() + end if + else if (config%i_liq_model == ILiquidModelSlingo) then + if (size(config%cloud_optics%liq_coeff_sw, 2) /= NLiqOpticsCoeffsSlingoSW) then + write(nulerr,'(a,i0,a,i0,a,i0,a)') & + & '*** Error: number of shortwave liquid cloud optical coefficients (', & + & size(config%cloud_optics%liq_coeff_sw, 2), & + & ') does not match number expected (', NLiqOpticsCoeffsSlingoSW,')' + call radiation_abort() + end if + if (size(config%cloud_optics%liq_coeff_lw, 2) /= NLiqOpticsCoeffsLindnerLiLW) then + write(nulerr,'(a,i0,a,i0,a,i0,a)') & + & '*** Error: number of longwave liquid cloud optical coefficients (', & + & size(config%cloud_optics%liq_coeff_lw, 2), & + & ') does not match number expected (', NLiqOpticsCoeffsLindnerLiLw,')' + call radiation_abort() + end if + end if + + ! Check ice coefficients + if (size(config%cloud_optics%ice_coeff_lw, 1) /= config%n_bands_lw) then + write(nulerr,'(a,i0,a,i0,a)') & + & '*** Error: number of longwave bands for ice particles (', & + & size(config%cloud_optics%ice_coeff_lw, 1), & + & ') does not match number for gases (', config%n_bands_lw, ')' + call radiation_abort() + end if + if (size(config%cloud_optics%ice_coeff_sw, 1) /= config%n_bands_sw) then + write(nulerr,'(a,i0,a,i0,a)') & + & '*** Error: number of shortwave bands for ice particles (', & + & size(config%cloud_optics%ice_coeff_sw, 1), & + & ') does not match number for gases (', config%n_bands_sw, ')' + call radiation_abort() + end if + + if (config%i_ice_model == IIceModelFu) then + if (size(config%cloud_optics%ice_coeff_lw, 2) & + & /= NIceOpticsCoeffsFuLW) then + write(nulerr,'(a,i0,a,i0,a,i0,a)') & + & '*** Error: number of LW ice-particle optical coefficients (', & + & size(config%cloud_optics%ice_coeff_lw, 2), & + & ') does not match number expected (', NIceOpticsCoeffsFuLW,')' + call radiation_abort() + end if + if (size(config%cloud_optics%ice_coeff_sw, 2) & + & /= NIceOpticsCoeffsFuSW) then + write(nulerr,'(a,i0,a,i0,a,i0,a)') & + & '*** Error: number of SW ice-particle optical coefficients (', & + & size(config%cloud_optics%ice_coeff_sw, 2), & + & ') does not match number expected (', NIceOpticsCoeffsFuSW,')' + call radiation_abort() + end if + else if (config%i_ice_model == IIceModelBaran & + & .and. size(config%cloud_optics%ice_coeff_lw, 2) & + & /= NIceOpticsCoeffsBaran) then + write(nulerr,'(a,i0,a,i0,a,i0,a)') & + & '*** Error: number of ice-particle optical coefficients (', & + & size(config%cloud_optics%ice_coeff_lw, 2), & + & ') does not match number expected (', NIceOpticsCoeffsBaran,')' + call radiation_abort() + else if (config%i_ice_model == IIceModelBaran2016 & + & .and. size(config%cloud_optics%ice_coeff_lw, 2) & + & /= NIceOpticsCoeffsBaran2016) then + write(nulerr,'(a,i0,a,i0,a,i0,a)') & + & '*** Error: number of ice-particle optical coefficients (', & + & size(config%cloud_optics%ice_coeff_lw, 2), & + & ') does not match number expected (', NIceOpticsCoeffsBaran2016,')' + call radiation_abort() + else if (config%i_ice_model == IIceModelBaran2017) then + if (size(config%cloud_optics%ice_coeff_lw, 2) & + & /= NIceOpticsCoeffsBaran2017) then + write(nulerr,'(a,i0,a,i0,a,i0,a)') & + & '*** Error: number of ice-particle optical coefficients (', & + & size(config%cloud_optics%ice_coeff_lw, 2), & + & ') does not match number expected (', NIceOpticsCoeffsBaran2017,')' + call radiation_abort() + else if (.not. allocated(config%cloud_optics%ice_coeff_gen)) then + write(nulerr,'(a)') & + & '*** Error: coeff_gen needed for Baran-2017 ice optics parameterization' + call radiation_abort() + else if (size(config%cloud_optics%ice_coeff_gen) & + & /= NIceOpticsGeneralCoeffsBaran2017) then + write(nulerr,'(a,i0,a,i0,a,i0,a)') & + & '*** Error: number of general ice-particle optical coefficients (', & + & size(config%cloud_optics%ice_coeff_gen), & + & ') does not match number expected (', NIceOpticsGeneralCoeffsBaran2017,')' + call radiation_abort() + end if + else if (config%i_ice_model == IIceModelYi) then + if (size(config%cloud_optics%ice_coeff_lw, 2) & + & /= NIceOpticsCoeffsYiLW) then + write(nulerr,'(a,i0,a,i0,a,i0,a)') & + & '*** Error: number of LW ice-particle optical coefficients (', & + & size(config%cloud_optics%ice_coeff_lw, 2), & + & ') does not match number expected (', NIceOpticsCoeffsYiLW,')' + call radiation_abort() + end if + if (size(config%cloud_optics%ice_coeff_sw, 2) & + & /= NIceOpticsCoeffsYiSW) then + write(nulerr,'(a,i0,a,i0,a,i0,a)') & + & '*** Error: number of SW ice-particle optical coefficients (', & + & size(config%cloud_optics%ice_coeff_sw, 2), & + & ') does not match number expected (', NIceOpticsCoeffsYiSW,')' + call radiation_abort() + end if + end if + + if (lhook) call dr_hook('radiation_cloud_optics:setup_cloud_optics',1,hook_handle) + + end subroutine setup_cloud_optics + + + !--------------------------------------------------------------------- + ! Compute cloud optical properties + subroutine cloud_optics(nlev,istartcol,iendcol, & + & config, thermodynamics, cloud, & + & od_lw_cloud, ssa_lw_cloud, g_lw_cloud, & + & od_sw_cloud, ssa_sw_cloud, g_sw_cloud) + + use parkind1, only : jprb + use yomhook, only : lhook, dr_hook + + use radiation_io, only : nulout, nulerr, radiation_abort + use radiation_config, only : config_type, IIceModelFu, IIceModelBaran, & + & IIceModelBaran2016, IIceModelBaran2017, & + & IIceModelYi, & + & ILiquidModelSOCRATES, ILiquidModelSlingo + use radiation_thermodynamics, only : thermodynamics_type + use radiation_cloud, only : cloud_type + use radiation_constants, only : AccelDueToGravity + use radiation_cloud_optics_data, only : cloud_optics_type + use radiation_ice_optics_fu, only : calc_ice_optics_fu_sw, & + & calc_ice_optics_fu_lw + use radiation_ice_optics_baran, only : calc_ice_optics_baran, & + & calc_ice_optics_baran2016 + use radiation_ice_optics_baran2017, only : calc_ice_optics_baran2017 + use radiation_ice_optics_yi, only : calc_ice_optics_yi_sw, & + & calc_ice_optics_yi_lw + use radiation_liquid_optics_socrates, only:calc_liq_optics_socrates + use radiation_liquid_optics_slingo, only:calc_liq_optics_slingo, & + & calc_liq_optics_lindner_li + + integer, intent(in) :: nlev ! number of model levels + integer, intent(in) :: istartcol, iendcol ! range of columns to process + type(config_type), intent(in), target :: config + type(thermodynamics_type),intent(in) :: thermodynamics + type(cloud_type), intent(in) :: cloud + + ! Layer optical depth, single scattering albedo and g factor of + ! clouds in each longwave band, where the latter two + ! variables are only defined if cloud longwave scattering is + ! enabled (otherwise both are treated as zero). + real(jprb), dimension(config%n_bands_lw,nlev,istartcol:iendcol), intent(out) :: & + & od_lw_cloud + real(jprb), dimension(config%n_bands_lw_if_scattering,nlev,istartcol:iendcol), & + & intent(out) :: ssa_lw_cloud, g_lw_cloud + + ! Layer optical depth, single scattering albedo and g factor of + ! clouds in each shortwave band + real(jprb), dimension(config%n_bands_sw,nlev,istartcol:iendcol), intent(out) :: & + & od_sw_cloud, ssa_sw_cloud, g_sw_cloud + + ! Longwave and shortwave optical depth, scattering optical depth + ! and asymmetry factor, for liquid and ice in all bands but a + ! single cloud layer + real(jprb), dimension(config%n_bands_lw) :: & + & od_lw_liq, scat_od_lw_liq, g_lw_liq, & + & od_lw_ice, scat_od_lw_ice, g_lw_ice + real(jprb), dimension(config%n_bands_sw) :: & + & od_sw_liq, scat_od_sw_liq, g_sw_liq, & + & od_sw_ice, scat_od_sw_ice, g_sw_ice + + ! In-cloud water path of cloud liquid or ice (i.e. liquid or ice + ! gridbox-mean water path divided by cloud fraction); kg m-2 + real(jprb) :: lwp_in_cloud, iwp_in_cloud + + ! Full-level temperature (K) + real(jprb) :: temperature + + ! Factor to convert gridbox-mean mixing ratio to in-cloud water + ! path + real(jprb) :: factor + + ! Pointer to the cloud optics coefficients for brevity of + ! access + type(cloud_optics_type), pointer :: ho + + integer :: jcol, jlev + + real(jprb) :: hook_handle + + if (lhook) call dr_hook('radiation_cloud_optics:cloud_optics',0,hook_handle) + + if (config%iverbose >= 2) then + write(nulout,'(a)') 'Computing cloud absorption/scattering properties' + end if + + ho => config%cloud_optics + + ! Array-wise assignment + od_lw_cloud = 0.0_jprb + od_sw_cloud = 0.0_jprb + ssa_sw_cloud = 0.0_jprb + g_sw_cloud = 0.0_jprb + if (config%do_lw_cloud_scattering) then + ssa_lw_cloud = 0.0_jprb + g_lw_cloud = 0.0_jprb + end if + + do jlev = 1,nlev + do jcol = istartcol,iendcol + ! Only do anything if cloud is present (assume that + ! cloud%crop_cloud_fraction has already been called) + if (cloud%fraction(jcol,jlev) > 0.0_jprb) then + + ! Compute in-cloud liquid and ice water path + if (config%is_homogeneous) then + ! Homogeneous solvers assume cloud fills the box + ! horizontally, so we don't divide by cloud fraction + factor = ( thermodynamics%pressure_hl(jcol,jlev+1) & + & -thermodynamics%pressure_hl(jcol,jlev ) ) & + & / AccelDueToGravity + else + factor = ( thermodynamics%pressure_hl(jcol,jlev+1) & + & -thermodynamics%pressure_hl(jcol,jlev ) ) & + & / (AccelDueToGravity * cloud%fraction(jcol,jlev)) + end if + lwp_in_cloud = factor * cloud%q_liq(jcol,jlev) + iwp_in_cloud = factor * cloud%q_ice(jcol,jlev) + + ! Only compute liquid properties if liquid cloud is + ! present + if (lwp_in_cloud > 0.0_jprb) then + if (config%i_liq_model == ILiquidModelSOCRATES) then + ! Compute longwave properties + call calc_liq_optics_socrates(config%n_bands_lw, & + & config%cloud_optics%liq_coeff_lw, & + & lwp_in_cloud, cloud%re_liq(jcol,jlev), & + & od_lw_liq, scat_od_lw_liq, g_lw_liq) + ! Compute shortwave properties + call calc_liq_optics_socrates(config%n_bands_sw, & + & config%cloud_optics%liq_coeff_sw, & + & lwp_in_cloud, cloud%re_liq(jcol,jlev), & + & od_sw_liq, scat_od_sw_liq, g_sw_liq) + else if (config%i_liq_model == ILiquidModelSlingo) then + ! Compute longwave properties + call calc_liq_optics_lindner_li(config%n_bands_lw, & + & config%cloud_optics%liq_coeff_lw, & + & lwp_in_cloud, cloud%re_liq(jcol,jlev), & + & od_lw_liq, scat_od_lw_liq, g_lw_liq) + ! Compute shortwave properties + call calc_liq_optics_slingo(config%n_bands_sw, & + & config%cloud_optics%liq_coeff_sw, & + & lwp_in_cloud, cloud%re_liq(jcol,jlev), & + & od_sw_liq, scat_od_sw_liq, g_sw_liq) + else + write(nulerr,*) '*** Error: Unknown liquid model with code', & + & config%i_liq_model + call radiation_abort() + end if + + if (.not. config%do_sw_delta_scaling_with_gases) then + ! Delta-Eddington scaling in the shortwave only + call delta_eddington_scat_od(od_sw_liq, scat_od_sw_liq, g_sw_liq) + end if + else + ! Liquid not present: set properties to zero + od_lw_liq = 0.0_jprb + scat_od_lw_liq = 0.0_jprb + g_lw_liq = 0.0_jprb + + od_sw_liq = 0.0_jprb + scat_od_sw_liq = 0.0_jprb + g_sw_liq = 0.0_jprb + end if ! Liquid present + + ! Only compute ice properties if ice cloud is present + if (iwp_in_cloud > 0.0_jprb) then + if (config%i_ice_model == IIceModelBaran) then + ! Compute longwave properties + call calc_ice_optics_baran(config%n_bands_lw, & + & config%cloud_optics%ice_coeff_lw, & + & iwp_in_cloud, cloud%q_ice(jcol,jlev), & + & od_lw_ice, scat_od_lw_ice, g_lw_ice) + ! Compute shortwave properties + call calc_ice_optics_baran(config%n_bands_sw, & + & config%cloud_optics%ice_coeff_sw, & + & iwp_in_cloud, cloud%q_ice(jcol,jlev), & + & od_sw_ice, scat_od_sw_ice, g_sw_ice) + else if (config%i_ice_model == IIceModelBaran2016) then + temperature = 0.5_jprb * (thermodynamics%temperature_hl(jcol,jlev) & + & +thermodynamics%temperature_hl(jcol,jlev+1)) + ! Compute longwave properties + call calc_ice_optics_baran2016(config%n_bands_lw, & + & config%cloud_optics%ice_coeff_lw, & + & iwp_in_cloud, cloud%q_ice(jcol,jlev), & + & temperature, & + & od_lw_ice, scat_od_lw_ice, g_lw_ice) + ! Compute shortwave properties + call calc_ice_optics_baran2016(config%n_bands_sw, & + & config%cloud_optics%ice_coeff_sw, & + & iwp_in_cloud, cloud%q_ice(jcol,jlev), & + & temperature, & + & od_sw_ice, scat_od_sw_ice, g_sw_ice) + else if (config%i_ice_model == IIceModelBaran2017) then + temperature = 0.5_jprb * (thermodynamics%temperature_hl(jcol,jlev) & + & +thermodynamics%temperature_hl(jcol,jlev+1)) + ! Compute longwave properties + call calc_ice_optics_baran2017(config%n_bands_lw, & + & config%cloud_optics%ice_coeff_gen, & + & config%cloud_optics%ice_coeff_lw, & + & iwp_in_cloud, cloud%q_ice(jcol,jlev), & + & temperature, & + & od_lw_ice, scat_od_lw_ice, g_lw_ice) + ! Compute shortwave properties + call calc_ice_optics_baran2017(config%n_bands_sw, & + & config%cloud_optics%ice_coeff_gen, & + & config%cloud_optics%ice_coeff_sw, & + & iwp_in_cloud, cloud%q_ice(jcol,jlev), & + & temperature, & + & od_sw_ice, scat_od_sw_ice, g_sw_ice) + else if (config%i_ice_model == IIceModelFu) then + ! Compute longwave properties + call calc_ice_optics_fu_lw(config%n_bands_lw, & + & config%cloud_optics%ice_coeff_lw, & + & iwp_in_cloud, cloud%re_ice(jcol,jlev), & + & od_lw_ice, scat_od_lw_ice, g_lw_ice) + if (config%do_fu_lw_ice_optics_bug) then + ! Reproduce bug in old IFS scheme + scat_od_lw_ice = od_lw_ice - scat_od_lw_ice + end if + ! Compute shortwave properties + call calc_ice_optics_fu_sw(config%n_bands_sw, & + & config%cloud_optics%ice_coeff_sw, & + & iwp_in_cloud, cloud%re_ice(jcol,jlev), & + & od_sw_ice, scat_od_sw_ice, g_sw_ice) + else if (config%i_ice_model == IIceModelYi) then + ! Compute longwave properties + call calc_ice_optics_yi_lw(config%n_bands_lw, & + & config%cloud_optics%ice_coeff_lw, & + & iwp_in_cloud, cloud%re_ice(jcol,jlev), & + & od_lw_ice, scat_od_lw_ice, g_lw_ice) + ! Compute shortwave properties + call calc_ice_optics_yi_sw(config%n_bands_sw, & + & config%cloud_optics%ice_coeff_sw, & + & iwp_in_cloud, cloud%re_ice(jcol,jlev), & + & od_sw_ice, scat_od_sw_ice, g_sw_ice) + else + write(nulerr,*) '*** Error: Unknown ice model with code', & + & config%i_ice_model + call radiation_abort() + end if + + if (.not. config%do_sw_delta_scaling_with_gases) then + ! Delta-Eddington scaling in both longwave and shortwave + ! (assume that particles are larger than wavelength even + ! in longwave) + call delta_eddington_scat_od(od_sw_ice, scat_od_sw_ice, g_sw_ice) + end if + + call delta_eddington_scat_od(od_lw_ice, scat_od_lw_ice, g_lw_ice) + else + ! Ice not present: set properties to zero + od_lw_ice = 0.0_jprb + scat_od_lw_ice = 0.0_jprb + g_lw_ice = 0.0_jprb + + od_sw_ice = 0.0_jprb + scat_od_sw_ice = 0.0_jprb + g_sw_ice = 0.0_jprb + end if ! Ice present + + ! Combine liquid and ice + if (config%do_lw_cloud_scattering) then + od_lw_cloud(:,jlev,jcol) = od_lw_liq + od_lw_ice + where (scat_od_lw_liq+scat_od_lw_ice > 0.0_jprb) + g_lw_cloud(:,jlev,jcol) = (g_lw_liq * scat_od_lw_liq & + & + g_lw_ice * scat_od_lw_ice) & + & / (scat_od_lw_liq+scat_od_lw_ice) + elsewhere + g_lw_cloud(:,jlev,jcol) = 0.0_jprb + end where + ssa_lw_cloud(:,jlev,jcol) = (scat_od_lw_liq + scat_od_lw_ice) & + & / (od_lw_liq + od_lw_ice) + else + ! If longwave scattering is to be neglected then the + ! best approximation is to set the optical depth equal + ! to the absorption optical depth + od_lw_cloud(:,jlev,jcol) = od_lw_liq - scat_od_lw_liq & + & + od_lw_ice - scat_od_lw_ice + end if + od_sw_cloud(:,jlev,jcol) = od_sw_liq + od_sw_ice + g_sw_cloud(:,jlev,jcol) = (g_sw_liq * scat_od_sw_liq & + & + g_sw_ice * scat_od_sw_ice) & + & / (scat_od_sw_liq + scat_od_sw_ice) + ssa_sw_cloud(:,jlev,jcol) & + & = (scat_od_sw_liq + scat_od_sw_ice) / (od_sw_liq + od_sw_ice) + end if ! Cloud present + end do ! Loop over column + end do ! Loop over level + + if (lhook) call dr_hook('radiation_cloud_optics:cloud_optics',1,hook_handle) + + end subroutine cloud_optics + +end module radiation_cloud_optics diff --git a/src/LIB/RAD/ecrad-1.4.0_mnh/radiation/radiation_config.F90 b/src/LIB/RAD/ecrad-1.4.0_mnh/radiation/radiation_config.F90 new file mode 100644 index 000000000..2a77f3a3e --- /dev/null +++ b/src/LIB/RAD/ecrad-1.4.0_mnh/radiation/radiation_config.F90 @@ -0,0 +1,1980 @@ +! radiation_config.F90 - Derived type to configure the radiation scheme +! +! (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-07-22 R. Hogan Added Yi et al. ice optics model +! 2017-10-23 R. Hogan Renamed single-character variables +! 2018-03-15 R. Hogan Added logicals controlling surface spectral treatment +! 2018-08-29 R. Hogan Added monochromatic single-scattering albedo / asymmetry factor +! 2018-09-03 R. Hogan Added min_cloud_effective_size +! 2018-09-04 R. Hogan Added encroachment_scaling +! 2018-09-13 R. Hogan Added IEncroachmentFractal +! 2019-01-02 R. Hogan Added Cloudless solvers +! 2019-01-14 R. Hogan Added out_of_bounds_[1,2,3]d for checker routines +! 2019-01-18 R. Hogan Added albedo weighting +! 2019-02-03 R. Hogan Added ability to fix out-of-physical-bounds inputs +! 2019-02-10 R. Hogan Renamed "encroachment" to "entrapment" +! +! Note: The aim is for ecRad in the IFS to be as similar as possible +! to the offline version, so if you make any changes to this or any +! files in this directory, please inform Robin Hogan. +! + +module radiation_config + + use parkind1, only : jprb + + use radiation_cloud_optics_data, only : cloud_optics_type + use radiation_aerosol_optics_data, only : aerosol_optics_type + use radiation_pdf_sampler, only : pdf_sampler_type + use radiation_cloud_cover, only : OverlapName, & + & IOverlapMaximumRandom, IOverlapExponentialRandom, IOverlapExponential + + implicit none + public + + ! Configuration codes: use C-style enumerators to avoid having to + ! remember the numbers + + ! Solvers: can be specified for longwave and shortwave + ! independently, except for "Homogeneous", which must be the same + ! for both + enum, bind(c) + enumerator ISolverCloudless, ISolverHomogeneous, ISolverMcICA, & + & ISolverSpartacus, ISolverTripleclouds + end enum + character(len=*), parameter :: SolverName(0:4) = (/ 'Cloudless ', & + & 'Homogeneous ', & + & 'McICA ', & + & 'SPARTACUS ', & + & 'Tripleclouds' /) + + ! SPARTACUS shortwave solver can treat the reflection of radiation + ! back up into different regions in various ways + enum, bind(c) + enumerator & + & IEntrapmentZero, & ! No entrapment, as Tripleclouds + & IEntrapmentEdgeOnly, & ! Only radiation passed through cloud edge is horizontally homogenized + & IEntrapmentExplicit, & ! Estimate horiz migration dist, account for fractal clouds + & IEntrapmentExplicitNonFractal, & ! As above but ignore fractal nature of clouds + & IEntrapmentMaximum ! Complete horizontal homogenization within regions (old SPARTACUS assumption) + + end enum + + ! Names available in the radiation namelist for variable + ! sw_entrapment_name + character(len=*), parameter :: EntrapmentName(0:4) = [ 'Zero ', & + & 'Edge-only ', & + & 'Explicit ', & + & 'Non-fractal', & + & 'Maximum ' ] + ! For backwards compatibility, the radiation namelist also supports + ! the equivalent variable sw_encroachment_name with the following + ! names + character(len=*), parameter :: EncroachmentName(0:4) = [ 'Zero ', & + & 'Minimum ', & + & 'Fractal ', & + & 'Computed', & + & 'Maximum ' ] + + ! Two-stream models + ! This is not configurable at run-time + + ! Gas models + enum, bind(c) + enumerator IGasModelMonochromatic, IGasModelIFSRRTMG + end enum + character(len=*), parameter :: GasModelName(0:1) = (/ 'Monochromatic', & + & 'RRTMG-IFS ' /) + + ! Hydrometeor scattering models + enum, bind(c) + enumerator ILiquidModelMonochromatic, & + & ILiquidModelSOCRATES, ILiquidModelSlingo + end enum + character(len=*), parameter :: LiquidModelName(0:2) = (/ 'Monochromatic', & + & 'SOCRATES ', & + & 'Slingo ' /) + + enum, bind(c) + enumerator IIceModelMonochromatic, IIceModelFu, & + & IIceModelBaran, IIceModelBaran2016, IIceModelBaran2017, & + & IIceModelYi + end enum + character(len=*), parameter :: IceModelName(0:5) = (/ 'Monochromatic', & + & 'Fu-IFS ', & + & 'Baran ', & + & 'Baran2016 ', & + & 'Baran2017 ', & + & 'Yi ' /) + + ! Cloud PDF distribution shapes + enum, bind(c) + enumerator IPdfShapeLognormal, IPdfShapeGamma + end enum + character(len=*), parameter :: PdfShapeName(0:1) = (/ 'Lognormal', & + & 'Gamma ' /) + + ! Maximum number of different aerosol types that can be provided + integer, parameter :: NMaxAerosolTypes = 256 + + ! Maximum number of shortwave albedo and longwave emissivity + ! intervals + integer, parameter :: NMaxAlbedoIntervals = 256 + + ! Length of string buffer for printing config information + integer, parameter :: NPrintStringLen = 60 + + !--------------------------------------------------------------------- + ! Derived type containing all the configuration information needed + ! to run the radiation scheme. The intention is that this is fixed + ! for a given model run. The parameters are to list first those + ! quantities that can be set directly by the user, for example using a + ! namelist, and second those quantities that are computed afterwards + ! from the user-supplied numbers, especially the details of the gas + ! optics model. + type config_type + ! USER-CONFIGURABLE PARAMETERS + + ! Override default solar spectrum + logical :: use_spectral_solar_scaling = .false. + + ! Directory in which gas, cloud and aerosol data files are to be + ! found + character(len=511) :: directory_name = '.' + + ! Cloud is deemed to be present in a layer if cloud fraction + ! exceeds this value + real(jprb) :: cloud_fraction_threshold = 1.0e-6_jprb + ! ...and total cloud water mixing ratio exceeds this value + real(jprb) :: cloud_mixing_ratio_threshold = 1.0e-9_jprb + + ! Overlap scheme + integer :: i_overlap_scheme = IOverlapExponentialRandom + + ! Use the Shonk et al. (2010) "beta" overlap parameter, rather + ! than the "alpha" overlap parameter of Hogan and Illingworth + ! (2000)? + logical :: use_beta_overlap = .false. + + ! Shape of sub-grid cloud water PDF + integer :: i_cloud_pdf_shape = IPdfShapeGamma + + ! The ratio of the overlap decorrelation length for cloud + ! inhomogeneities to the overlap decorrelation length for cloud + ! boundaries. Observations suggest this has a value of 0.5 + ! (e.g. from the decorrelation lengths of Hogan and Illingworth + ! 2003 and Hogan and Illingworth 2000). + real(jprb) :: cloud_inhom_decorr_scaling = 0.5_jprb + + ! Factor controlling how much of the cloud edge length interfaces + ! directly between the clear-sky region (region a) and the + ! optically thick cloudy region (region c). If Lxy is the length + ! of the interfaces between regions x and y, and Lab and Lbc have + ! been computed already, then + ! Lac=clear_to_thick_fraction*min(Lab,Lbc). + real(jprb) :: clear_to_thick_fraction = 0.0_jprb + + ! Factor allowing lateral transport when the sun is close to + ! overhead; consider atand(overhead_sun_factor) to be the number + ! of degrees that the sun angle is perturbed from zenith for the + ! purposes of computing lateral transport. A value of up to 0.1 + ! seems to be necessary to account for the fact that some forward + ! scattered radiation is treated as unscattered by delta-Eddington + ! scaling; therefore it ought to have the chance to escape. + real(jprb) :: overhead_sun_factor = 0.0_jprb + + ! Minimum gas optical depth in a single layer at any wavelength, + ! for stability + real(jprb) :: min_gas_od_lw = 1.0e-15_jprb + real(jprb) :: min_gas_od_sw = 0.0_jprb + + ! Maximum gas optical depth in a layer before that g-point will + ! not be considered for 3D treatment: a limit is required to avoid + ! expensive computation of matrix exponentials on matrices with + ! large elements + real(jprb) :: max_gas_od_3d = 8.0_jprb + + ! Maximum total optical depth of a cloudy region for stability: + ! optical depth will be capped at this value in the SPARTACUS + ! solvers + real(jprb) :: max_cloud_od = 16.0_jprb + + ! How much longwave scattering is included? + logical :: do_lw_cloud_scattering = .true. + logical :: do_lw_aerosol_scattering = .true. + + ! Number of regions used to describe clouds and clear skies. A + ! value of 2 means one clear and one cloudy region, so clouds are + ! horizontally homogeneous, while a value of 3 means two cloudy + ! regions with different optical depth, thereby representing + ! inhomogeneity via the Shonk & Hogan (2008) "Tripleclouds" + ! method. + integer :: nregions = 3 + + ! Code specifying the solver to be used: use the enumerations + ! defined above + integer :: i_solver_sw = ISolverMcICA + integer :: i_solver_lw = ISolverMcICA + + ! Do shortwave delta-Eddington scaling on the cloud-aerosol-gas + ! mixture (as in the original IFS scheme), rather than the more + ! correct approach of separately scaling the cloud and aerosol + ! scattering properties before merging with gases. Note that + ! .true. is not compatible with the SPARTACUS solver. + logical :: do_sw_delta_scaling_with_gases = .false. + + ! Codes describing the gas and cloud scattering models to use, the + ! latter of which is currently not used + integer :: i_gas_model = IGasModelIFSRRTMG + ! integer :: i_cloud_model + + ! Optics if i_gas_model==IGasModelMonochromatic. + ! The wavelength to use for the Planck function in metres. If this + ! is positive then the output longwave fluxes will be in units of + ! W m-2 um-1. If this is zero or negative (the default) then + ! sigma*T^4 will be used and the output longwave fluxes will be in + ! W m-2. + real(jprb) :: mono_lw_wavelength = -1.0_jprb + ! Total zenith optical depth of the atmosphere in the longwave and + ! shortwave, distributed vertically according to the pressure. + ! Default is zero. + real(jprb) :: mono_lw_total_od = 0.0_jprb + real(jprb) :: mono_sw_total_od = 0.0_jprb + ! Single-scattering albedo and asymmetry factor: values typical + ! for liquid clouds with effective radius of 10 microns, at (SW) + ! 0.55 micron wavelength and (LW) 10.7 microns wavelength + real(jprb) :: mono_sw_single_scattering_albedo = 0.999999_jprb + real(jprb) :: mono_sw_asymmetry_factor = 0.86_jprb + real(jprb) :: mono_lw_single_scattering_albedo = 0.538_jprb + real(jprb) :: mono_lw_asymmetry_factor = 0.925_jprb + + ! Codes describing particle scattering models + integer :: i_liq_model = ILiquidModelSOCRATES + integer :: i_ice_model = IIceModelBaran + + ! The mapping from albedo/emissivity intervals to SW/LW bands can + ! either be done by finding the interval containing the central + ! wavenumber of the band (nearest neighbour), or by a weighting + ! according to the spectral overlap of each interval with each + ! band + logical :: do_nearest_spectral_sw_albedo = .true. + logical :: do_nearest_spectral_lw_emiss = .true. + + ! User-defined monotonically increasing wavelength bounds (m) + ! between input surface albedo/emissivity intervals. Implicitly + ! the first interval starts at zero and the last ends at infinity. + real(jprb) :: sw_albedo_wavelength_bound(NMaxAlbedoIntervals-1) = -1.0_jprb + real(jprb) :: lw_emiss_wavelength_bound( NMaxAlbedoIntervals-1) = -1.0_jprb + + ! The index to the surface albedo/emissivity intervals for each of + ! the wavelength bounds specified in sw_albedo_wavelength_bound + ! and lw_emiss_wavelength_bound + integer :: i_sw_albedo_index(NMaxAlbedoIntervals) = 0 + integer :: i_lw_emiss_index (NMaxAlbedoIntervals) = 0 + + ! Do we compute longwave and/or shortwave radiation? + logical :: do_lw = .true. + logical :: do_sw = .true. + + ! Do we compute clear-sky fluxes and/or solar direct fluxes? + logical :: do_clear = .true. + logical :: do_sw_direct = .true. + + ! Do we include 3D effects? + logical :: do_3d_effects = .true. + + ! To what extent do we include "entrapment" effects in the + ! SPARTACUS solver? This essentially means that in a situation + ! like this + ! + ! 000111 + ! 222222 + ! + ! Radiation downwelling from region 1 may be reflected back into + ! region 0 due to some degree of homogenization of the radiation + ! in region 2. Hogan and Shonk (2013) referred to this as + ! "anomalous horizontal transport" for a 1D model, although for 3D + ! calculations it is desirable to include at least some of it. The + ! options are described by the IEntrapment* parameters above. + integer :: i_3d_sw_entrapment = IEntrapmentExplicit + + ! In the longwave, the equivalent process it either "on" (like + ! maximum entrapment) or "off" (like zero entrapment): + logical :: do_3d_lw_multilayer_effects = .false. + + ! Do we account for the effective emissivity of the side of + ! clouds? + logical :: do_lw_side_emissivity = .true. + + ! The 3D transfer rate "X" is such that if transport out of a + ! region was the only process occurring then by the base of a + ! layer only exp(-X) of the original flux would remain in that + ! region. The transfer rate computed geometrically can be very + ! high for the clear-sky regions in layers with high cloud + ! fraction. For stability reasons it is necessary to provide a + ! maximum possible 3D transfer rate. + real(jprb) :: max_3d_transfer_rate = 10.0_jprb + + ! It has also sometimes been found necessary to set a minimum + ! cloud effective size for stability (metres) + real(jprb) :: min_cloud_effective_size = 100.0_jprb + + ! Given a horizontal migration distance, there is still + ! uncertainty about how much entrapment occurs associated with how + ! one assumes cloud boundaries line up in adjacent layers. This + ! factor can be varied between 0.0 (the boundaries line up to the + ! greatest extent possible given the overlap parameter) and 1.0 + ! (the boundaries line up to the minimum extent possible). In the + ! Hogan et al. entrapment paper it is referred to as the overhang + ! factor zeta, and a value of 0 matches the Monte Carlo + ! calculations best. + real(jprb) :: overhang_factor = 0.0_jprb + + ! By default, the Meador & Weaver (1980) expressions are used + ! instead of the matrix exponential whenever 3D effects can be + ! neglected (e.g. cloud-free layers or clouds with infinitely + ! large effective cloud size), but setting the following to true + ! uses the matrix exponential everywhere, enabling the two + ! methods to be compared. Note that Meador & Weaver will still be + ! used for very optically thick g points where the matrix + ! exponential can produce incorrect results. + logical :: use_expm_everywhere = .false. + + ! Aerosol descriptors: aerosol_type_mapping must be of length + ! n_aerosol_types, and contains 0 if that type is to be ignored, + ! positive numbers to map on to the indices of hydrophobic + ! aerosols in the aerosol optics configuration file, and negative + ! numbers to map on to (the negative of) the indices of + ! hydrophilic aerosols in the configuration file. + logical :: use_aerosols = .false. + integer :: n_aerosol_types = 0 + integer :: i_aerosol_type_map(NMaxAerosolTypes) + + ! Save the gas and cloud optical properties for each g point in + ! "radiative_properties.nc"? + logical :: do_save_radiative_properties = .false. + + ! Save the flux profiles in each band? + logical :: do_save_spectral_flux = .false. + + ! Save the surface downwelling shortwave fluxes in each band? + logical :: do_surface_sw_spectral_flux = .true. + + ! Compute the longwave derivatives needed to apply the approximate + ! radiation updates of Hogan and Bozzo (2015) + logical :: do_lw_derivatives = .false. + + ! Save the flux profiles in each g-point (overrides + ! do_save_spectral_flux if TRUE)? + logical :: do_save_gpoint_flux = .false. + + ! In the IFS environment, setting up RRTM has already been done + ! so not needed to do it again + logical :: do_setup_ifsrrtm = .true. + + ! In the IFS environment the old scheme has a bug in the Fu + ! longwave ice optics whereby the single scattering albedo is one + ! minus what it should be. Unfortunately fixing it makes + ! forecasts worse. Setting the following to true reproduces the + ! bug. + logical :: do_fu_lw_ice_optics_bug = .false. + + ! Control verbosity: 0=none (no output to standard output; write + ! to standard error only if an error occurs), 1=warning, 2=info, + ! 3=progress, 4=detailed, 5=debug. Separate settings for the + ! setup of the scheme and the execution of it. + integer :: iverbosesetup = 2 + integer :: iverbose = 1 + + ! Are we doing radiative transfer in complex surface canopies + ! (streets/vegetation), in which case tailored downward fluxes are + ! needed at the top of the canopy? + logical :: do_canopy_fluxes_sw = .false. + logical :: do_canopy_fluxes_lw = .false. + ! If so, do we use the full spectrum as in the atmosphere, or just + ! the reduced spectrum in which the shortwave albedo and longwave + ! emissivity are provided? + logical :: use_canopy_full_spectrum_sw = .false. + logical :: use_canopy_full_spectrum_lw = .false. + ! Do we treat gas radiative transfer in streets/vegetation? + logical :: do_canopy_gases_sw = .false. + logical :: do_canopy_gases_lw = .false. + + ! Optics file names for overriding the ones generated from the + ! other options. If these remain empty then the generated names + ! will be used (see the "consolidate_config" routine below). If + ! the user assigns one of these and it starts with a '/' character + ! then that will be used instead. If the user assigns one and it + ! doesn't start with a '/' character then it will be prepended by + ! the contents of directory_name. + character(len=511) :: ice_optics_override_file_name = '' + character(len=511) :: liq_optics_override_file_name = '' + character(len=511) :: aerosol_optics_override_file_name = '' + + ! Optionally override the look-up table file for the cloud-water + ! PDF used by the McICA solver + character(len=511) :: cloud_pdf_override_file_name = '' + + ! Has "consolidate" been called? + logical :: is_consolidated = .false. + + ! COMPUTED PARAMETERS + ! Users of this library should not edit these parameters directly; + ! they are set by the "consolidate" routine + + ! Wavenumber range for each band, in cm-1, which will be allocated + ! to be of length n_bands_sw or n_bands_lw + real(jprb), allocatable, dimension(:) :: wavenumber1_sw + real(jprb), allocatable, dimension(:) :: wavenumber2_sw + real(jprb), allocatable, dimension(:) :: wavenumber1_lw + real(jprb), allocatable, dimension(:) :: wavenumber2_lw + + ! If the nearest surface albedo/emissivity interval is to be used + ! for each SW/LW band then the following arrays will be allocated + ! to the length of the number of bands and contain the index to + ! the relevant interval + integer, allocatable, dimension(:) :: i_albedo_from_band_sw + integer, allocatable, dimension(:) :: i_emiss_from_band_lw + + ! ...alternatively, this matrix dimensioned + ! (n_albedo_intervals,n_bands_sw) providing the weights needed for + ! computing the albedo in each ecRad band from the albedo in each + ! native albedo band - see radiation_single_level.F90 + real(jprb), allocatable, dimension(:,:) :: sw_albedo_weights + ! ...and similarly in the longwave, dimensioned + ! (n_emiss_intervals,n_bands_lw) + real(jprb), allocatable, dimension(:,:) :: lw_emiss_weights + + ! Arrays of length the number of g-points that convert from + ! g-point to the band index + integer, allocatable, dimension(:) :: i_band_from_g_lw + integer, allocatable, dimension(:) :: i_band_from_g_sw + + ! We allow for the possibility for g-points to be ordered in terms + ! of likely absorption (weakest to strongest) across the shortwave + ! or longwave spectrum, in order that in SPARTACUS we select only + ! the first n g-points that will not have too large an absorption, + ! and therefore matrix exponentials that are both finite and not + ! too expensive to compute. The following two arrays map the + ! reordered g-points to the original ones. + integer, allocatable, dimension(:) :: i_g_from_reordered_g_lw + integer, allocatable, dimension(:) :: i_g_from_reordered_g_sw + + ! The following map the reordered g-points to the bands + integer, allocatable, dimension(:) :: i_band_from_reordered_g_lw + integer, allocatable, dimension(:) :: i_band_from_reordered_g_sw + + ! The following map the reordered g-points to the spectral + ! information being saved: if do_save_gpoint_flux==TRUE then this + ! will map on to the original g points, but if only + ! do_save_spectral_flux==TRUE then this will map on to the bands + integer, pointer, dimension(:) :: i_spec_from_reordered_g_lw + integer, pointer, dimension(:) :: i_spec_from_reordered_g_sw + + ! Number of spectral intervals used for the canopy radiative + ! transfer calculation; they are either equal to + ! n_albedo_intervals/n_emiss_intervals or n_g_sw/n_g_lw + integer :: n_canopy_bands_sw = 1 + integer :: n_canopy_bands_lw = 1 + + ! Data structure containing cloud scattering data + type(cloud_optics_type) :: cloud_optics + + ! Data structure containing aerosol scattering data + type(aerosol_optics_type) :: aerosol_optics + + ! Object for sampling from a gamma or lognormal distribution + type(pdf_sampler_type) :: pdf_sampler + + ! Optics file names + character(len=511) :: ice_optics_file_name, & + & liq_optics_file_name, & + & aerosol_optics_file_name + + ! McICA PDF look-up table file name + character(len=511) :: cloud_pdf_file_name + + ! Number of gpoints and bands in the shortwave and longwave - set + ! to zero as will be set properly later + integer :: n_g_sw = 0, n_g_lw = 0 + integer :: n_bands_sw = 0, n_bands_lw = 0 + + ! Number of spectral points to save (equal either to the number of + ! g points or the number of bands + integer :: n_spec_sw = 0, n_spec_lw = 0 + + ! Dimensions to store variables that are only needed if longwave + ! scattering is included. "n_g_lw_if_scattering" is equal to + ! "n_g_lw" if aerosols are allowed to scatter in the longwave, + ! and zero otherwise. "n_bands_lw_if_scattering" is equal to + ! "n_bands_lw" if clouds are allowed to scatter in the longwave, + ! and zero otherwise. + integer :: n_g_lw_if_scattering = 0, n_bands_lw_if_scattering = 0 + + ! Treat clouds as horizontally homogeneous within the gribox + logical :: is_homogeneous = .false. + + ! If the solvers are both "Cloudless" then we don't need to do any + ! cloud processing + logical :: do_clouds = .true. + + contains + procedure :: read => read_config_from_namelist + procedure :: consolidate => consolidate_config + procedure :: set => set_config + procedure :: print => print_config + procedure :: get_sw_weights + procedure :: define_sw_albedo_intervals + procedure :: define_lw_emiss_intervals + procedure :: consolidate_intervals + + end type config_type + +! procedure, private :: print_logical, print_real, print_int + +contains + + + !--------------------------------------------------------------------- + ! This subroutine reads configuration data from a namelist file, and + ! anything that is not in the namelists will be set to default + ! values. If optional output argument "is_success" is present, then + ! on error (e.g. missing file) it will be set to .false.; if this + ! argument is missing then on error the program will be aborted. You + ! may either specify the file_name or the unit of an open file to + ! read, but not both. + subroutine read_config_from_namelist(this, file_name, unit, is_success) + + use yomhook, only : lhook, dr_hook + use radiation_io, only : nulout, nulerr, nulrad, radiation_abort + + class(config_type), intent(inout) :: this + character(*), intent(in), optional :: file_name + integer, intent(in), optional :: unit + logical, intent(out), optional :: is_success + + integer :: iosopen, iosread ! Status after calling open and read + + ! The following variables are read from the namelists and map + ! directly onto members of the config_type derived type + + ! To be read from the radiation_config namelist + logical :: do_sw, do_lw, do_clear, do_sw_direct + logical :: do_3d_effects, use_expm_everywhere, use_aerosols + logical :: do_lw_side_emissivity + logical :: do_3d_lw_multilayer_effects, do_fu_lw_ice_optics_bug + logical :: do_lw_aerosol_scattering, do_lw_cloud_scattering + logical :: do_save_radiative_properties, do_save_spectral_flux + logical :: do_save_gpoint_flux, do_surface_sw_spectral_flux + logical :: use_beta_overlap, do_lw_derivatives + logical :: do_sw_delta_scaling_with_gases + logical :: do_canopy_fluxes_sw, do_canopy_fluxes_lw + logical :: use_canopy_full_spectrum_sw, use_canopy_full_spectrum_lw + logical :: do_canopy_gases_sw, do_canopy_gases_lw + integer :: n_regions, iverbose, iverbosesetup, n_aerosol_types + real(jprb):: mono_lw_wavelength, mono_lw_total_od, mono_sw_total_od + real(jprb):: mono_lw_single_scattering_albedo, mono_sw_single_scattering_albedo + real(jprb):: mono_lw_asymmetry_factor, mono_sw_asymmetry_factor + real(jprb):: cloud_inhom_decorr_scaling, cloud_fraction_threshold + real(jprb):: clear_to_thick_fraction, max_gas_od_3d, max_cloud_od + real(jprb):: cloud_mixing_ratio_threshold, overhead_sun_factor + real(jprb):: max_3d_transfer_rate, min_cloud_effective_size + real(jprb):: overhang_factor, encroachment_scaling + character(511) :: directory_name, aerosol_optics_override_file_name + character(511) :: liq_optics_override_file_name, ice_optics_override_file_name + character(511) :: cloud_pdf_override_file_name + character(63) :: liquid_model_name, ice_model_name, gas_model_name + character(63) :: sw_solver_name, lw_solver_name, overlap_scheme_name + character(63) :: sw_entrapment_name, sw_encroachment_name, cloud_pdf_shape_name + integer :: i_aerosol_type_map(NMaxAerosolTypes) ! More than 256 is an error + + logical :: do_nearest_spectral_sw_albedo = .true. + logical :: do_nearest_spectral_lw_emiss = .true. + real(jprb) :: sw_albedo_wavelength_bound(NMaxAlbedoIntervals-1) + real(jprb) :: lw_emiss_wavelength_bound( NMaxAlbedoIntervals-1) + integer :: i_sw_albedo_index(NMaxAlbedoIntervals) + integer :: i_lw_emiss_index (NMaxAlbedoIntervals) + + integer :: iunit ! Unit number of namelist file + + namelist /radiation/ do_sw, do_lw, do_sw_direct, & + & do_3d_effects, do_lw_side_emissivity, do_clear, & + & do_save_radiative_properties, sw_entrapment_name, sw_encroachment_name, & + & do_3d_lw_multilayer_effects, do_fu_lw_ice_optics_bug, & + & do_save_spectral_flux, do_save_gpoint_flux, & + & do_surface_sw_spectral_flux, do_lw_derivatives, & + & do_lw_aerosol_scattering, do_lw_cloud_scattering, & + & n_regions, directory_name, gas_model_name, & + & ice_optics_override_file_name, liq_optics_override_file_name, & + & aerosol_optics_override_file_name, cloud_pdf_override_file_name, & + & liquid_model_name, ice_model_name, max_3d_transfer_rate, & + & min_cloud_effective_size, overhang_factor, encroachment_scaling, & + & use_canopy_full_spectrum_sw, use_canopy_full_spectrum_lw, & + & do_canopy_fluxes_sw, do_canopy_fluxes_lw, & + & do_canopy_gases_sw, do_canopy_gases_lw, & + & do_sw_delta_scaling_with_gases, overlap_scheme_name, & + & sw_solver_name, lw_solver_name, use_beta_overlap, & + & use_expm_everywhere, iverbose, iverbosesetup, & + & cloud_inhom_decorr_scaling, cloud_fraction_threshold, & + & clear_to_thick_fraction, max_gas_od_3d, max_cloud_od, & + & cloud_mixing_ratio_threshold, overhead_sun_factor, & + & n_aerosol_types, i_aerosol_type_map, use_aerosols, & + & mono_lw_wavelength, mono_lw_total_od, mono_sw_total_od, & + & mono_lw_single_scattering_albedo, mono_sw_single_scattering_albedo, & + & mono_lw_asymmetry_factor, mono_sw_asymmetry_factor, & + & cloud_pdf_shape_name, & + & do_nearest_spectral_sw_albedo, do_nearest_spectral_lw_emiss, & + & sw_albedo_wavelength_bound, lw_emiss_wavelength_bound, & + & i_sw_albedo_index, i_lw_emiss_index + + real(jprb) :: hook_handle + + if (lhook) call dr_hook('radiation_config:read',0,hook_handle) + + ! Copy default values from the original structure + do_sw = this%do_sw + do_lw = this%do_lw + do_sw_direct = this%do_sw_direct + do_3d_effects = this%do_3d_effects + do_3d_lw_multilayer_effects = this%do_3d_lw_multilayer_effects + do_lw_side_emissivity = this%do_lw_side_emissivity + do_clear = this%do_clear + do_lw_aerosol_scattering = this%do_lw_aerosol_scattering + do_lw_cloud_scattering = this%do_lw_cloud_scattering + do_sw_delta_scaling_with_gases = this%do_sw_delta_scaling_with_gases + do_fu_lw_ice_optics_bug = this%do_fu_lw_ice_optics_bug + do_canopy_fluxes_sw = this%do_canopy_fluxes_sw + do_canopy_fluxes_lw = this%do_canopy_fluxes_lw + use_canopy_full_spectrum_sw = this%use_canopy_full_spectrum_sw + use_canopy_full_spectrum_lw = this%use_canopy_full_spectrum_lw + do_canopy_gases_sw = this%do_canopy_gases_sw + do_canopy_gases_lw = this%do_canopy_gases_lw + n_regions = this%nregions + directory_name = this%directory_name + cloud_pdf_override_file_name = this%cloud_pdf_override_file_name + liq_optics_override_file_name = this%liq_optics_override_file_name + ice_optics_override_file_name = this%ice_optics_override_file_name + aerosol_optics_override_file_name = this%aerosol_optics_override_file_name + use_expm_everywhere = this%use_expm_everywhere + use_aerosols = this%use_aerosols + do_save_radiative_properties = this%do_save_radiative_properties + do_save_spectral_flux = this%do_save_spectral_flux + do_save_gpoint_flux = this%do_save_gpoint_flux + do_lw_derivatives = this%do_lw_derivatives + do_surface_sw_spectral_flux = this%do_surface_sw_spectral_flux + iverbose = this%iverbose + iverbosesetup = this%iverbosesetup + cloud_fraction_threshold = this%cloud_fraction_threshold + cloud_mixing_ratio_threshold = this%cloud_mixing_ratio_threshold + use_beta_overlap = this%use_beta_overlap + cloud_inhom_decorr_scaling = this%cloud_inhom_decorr_scaling + clear_to_thick_fraction = this%clear_to_thick_fraction + overhead_sun_factor = this%overhead_sun_factor + max_gas_od_3d = this%max_gas_od_3d + max_cloud_od = this%max_cloud_od + max_3d_transfer_rate = this%max_3d_transfer_rate + min_cloud_effective_size = this%min_cloud_effective_size + overhang_factor = this%overhang_factor + encroachment_scaling = -1.0_jprb + gas_model_name = '' !DefaultGasModelName + liquid_model_name = '' !DefaultLiquidModelName + ice_model_name = '' !DefaultIceModelName + sw_solver_name = '' !DefaultSwSolverName + lw_solver_name = '' !DefaultLwSolverName + sw_entrapment_name = '' + sw_encroachment_name = '' + overlap_scheme_name = '' + cloud_pdf_shape_name = '' + n_aerosol_types = this%n_aerosol_types + mono_lw_wavelength = this%mono_lw_wavelength + mono_lw_total_od = this%mono_lw_total_od + mono_sw_total_od = this%mono_sw_total_od + mono_lw_single_scattering_albedo = this%mono_lw_single_scattering_albedo + mono_sw_single_scattering_albedo = this%mono_sw_single_scattering_albedo + mono_lw_asymmetry_factor = this%mono_lw_asymmetry_factor + mono_sw_asymmetry_factor = this%mono_sw_asymmetry_factor + i_aerosol_type_map = this%i_aerosol_type_map + do_nearest_spectral_sw_albedo = this%do_nearest_spectral_sw_albedo + do_nearest_spectral_lw_emiss = this%do_nearest_spectral_lw_emiss + sw_albedo_wavelength_bound = this%sw_albedo_wavelength_bound + lw_emiss_wavelength_bound = this%lw_emiss_wavelength_bound + i_sw_albedo_index = this%i_sw_albedo_index + i_lw_emiss_index = this%i_lw_emiss_index + + if (present(file_name) .and. present(unit)) then + write(nulerr,'(a)') '*** Error: cannot specify both file_name and unit in call to config_type%read' + call radiation_abort('Radiation configuration error') + else if (.not. present(file_name) .and. .not. present(unit)) then + write(nulerr,'(a)') '*** Error: neither file_name nor unit specified in call to config_type%read' + call radiation_abort('Radiation configuration error') + end if + + if (present(file_name)) then + ! Open the namelist file + iunit = nulrad + open(unit=iunit, iostat=iosopen, file=trim(file_name)) + else + ! Assume that iunit represents and open file + iosopen = 0 + iunit = unit + end if + + if (iosopen /= 0) then + ! An error occurred opening the file + if (present(is_success)) then + is_success = .false. + ! We now continue the subroutine so that the default values + ! are placed in the config structure + else + write(nulerr,'(a,a,a)') '*** Error: namelist file "', & + & trim(file_name), '" not found' + call radiation_abort('Radiation configuration error') + end if + else + read(unit=iunit, iostat=iosread, nml=radiation) + if (iosread /= 0) then + ! An error occurred reading the file + if (present(is_success)) then + is_success = .false. + ! We now continue the subroutine so that the default values + ! are placed in the config structure + else if (present(file_name)) then + write(nulerr,'(a,a,a)') '*** Error reading namelist "radiation" from file "', & + & trim(file_name), '"' + close(unit=iunit) + call radiation_abort('Radiation configuration error') + else + write(nulerr,'(a,i0)') '*** Error reading namelist "radiation" from unit ', & + & iunit + call radiation_abort('Radiation configuration error') + end if + end if + + if (present(file_name)) then + close(unit=iunit) + end if + end if + + ! Copy namelist data into configuration object + + ! Start with verbosity levels, which should be within limits + if (iverbose < 0) then + iverbose = 0 + end if + this%iverbose = iverbose + + if (iverbosesetup < 0) then + iverbosesetup = 0 + end if + this%iverbosesetup = iverbosesetup + + this%do_lw = do_lw + this%do_sw = do_sw + this%do_clear = do_clear + this%do_sw_direct = do_sw_direct + this%do_3d_effects = do_3d_effects + this%do_3d_lw_multilayer_effects = do_3d_lw_multilayer_effects + this%do_lw_side_emissivity = do_lw_side_emissivity + this%use_expm_everywhere = use_expm_everywhere + this%use_aerosols = use_aerosols + this%do_lw_cloud_scattering = do_lw_cloud_scattering + this%do_lw_aerosol_scattering = do_lw_aerosol_scattering + this%nregions = n_regions + this%do_surface_sw_spectral_flux = do_surface_sw_spectral_flux + this%do_sw_delta_scaling_with_gases = do_sw_delta_scaling_with_gases + this%do_fu_lw_ice_optics_bug = do_fu_lw_ice_optics_bug + this%do_canopy_fluxes_sw = do_canopy_fluxes_sw + this%do_canopy_fluxes_lw = do_canopy_fluxes_lw + this%use_canopy_full_spectrum_sw = use_canopy_full_spectrum_sw + this%use_canopy_full_spectrum_lw = use_canopy_full_spectrum_lw + this%do_canopy_gases_sw = do_canopy_gases_sw + this%do_canopy_gases_lw = do_canopy_gases_lw + this%mono_lw_wavelength = mono_lw_wavelength + this%mono_lw_total_od = mono_lw_total_od + this%mono_sw_total_od = mono_sw_total_od + this%mono_lw_single_scattering_albedo = mono_lw_single_scattering_albedo + this%mono_sw_single_scattering_albedo = mono_sw_single_scattering_albedo + this%mono_lw_asymmetry_factor = mono_lw_asymmetry_factor + this%mono_sw_asymmetry_factor = mono_sw_asymmetry_factor + this%use_beta_overlap = use_beta_overlap + this%cloud_inhom_decorr_scaling = cloud_inhom_decorr_scaling + this%clear_to_thick_fraction = clear_to_thick_fraction + this%overhead_sun_factor = overhead_sun_factor + this%max_gas_od_3d = max_gas_od_3d + this%max_cloud_od = max_cloud_od + this%max_3d_transfer_rate = max_3d_transfer_rate + this%min_cloud_effective_size = max(1.0e-6_jprb, min_cloud_effective_size) + if (encroachment_scaling >= 0.0_jprb) then + this%overhang_factor = encroachment_scaling + if (iverbose >= 1) then + write(nulout, '(a)') 'Warning: radiation namelist parameter "encroachment_scaling" is deprecated: use "overhang_factor"' + end if + else + this%overhang_factor = overhang_factor + end if + this%directory_name = directory_name + this%cloud_pdf_override_file_name = cloud_pdf_override_file_name + this%liq_optics_override_file_name = liq_optics_override_file_name + this%ice_optics_override_file_name = ice_optics_override_file_name + this%aerosol_optics_override_file_name = aerosol_optics_override_file_name + this%cloud_fraction_threshold = cloud_fraction_threshold + this%cloud_mixing_ratio_threshold = cloud_mixing_ratio_threshold + this%n_aerosol_types = n_aerosol_types + this%do_save_radiative_properties = do_save_radiative_properties + this%do_lw_derivatives = do_lw_derivatives + this%do_save_spectral_flux = do_save_spectral_flux + this%do_save_gpoint_flux = do_save_gpoint_flux + this%do_nearest_spectral_sw_albedo = do_nearest_spectral_sw_albedo + this%do_nearest_spectral_lw_emiss = do_nearest_spectral_lw_emiss + this%sw_albedo_wavelength_bound = sw_albedo_wavelength_bound + this%lw_emiss_wavelength_bound = lw_emiss_wavelength_bound + this%i_sw_albedo_index = i_sw_albedo_index + this%i_lw_emiss_index = i_lw_emiss_index + + if (do_save_gpoint_flux) then + ! Saving the fluxes every g-point overrides saving as averaged + ! in a band, but this%do_save_spectral_flux needs to be TRUE as + ! it is tested inside the solver routines to decide whether to + ! save anything + this%do_save_spectral_flux = .true. + end if + + ! Determine liquid optics model + call get_enum_code(liquid_model_name, LiquidModelName, & + & 'liquid_model_name', this%i_liq_model) + + ! Determine ice optics model + call get_enum_code(ice_model_name, IceModelName, & + & 'ice_model_name', this%i_ice_model) + + ! Determine gas optics model + call get_enum_code(gas_model_name, GasModelName, & + & 'gas_model_name', this%i_gas_model) + + ! Determine solvers + call get_enum_code(sw_solver_name, SolverName, & + & 'sw_solver_name', this%i_solver_sw) + call get_enum_code(lw_solver_name, SolverName, & + & 'lw_solver_name', this%i_solver_lw) + + if (len_trim(sw_encroachment_name) > 1) then + call get_enum_code(sw_encroachment_name, EncroachmentName, & + & 'sw_encroachment_name', this%i_3d_sw_entrapment) + write(nulout, '(a)') 'Warning: radiation namelist string "sw_encroachment_name" is deprecated: use "sw_entrapment_name"' + else + call get_enum_code(sw_entrapment_name, EntrapmentName, & + & 'sw_entrapment_name', this%i_3d_sw_entrapment) + end if + + ! Determine overlap scheme + call get_enum_code(overlap_scheme_name, OverlapName, & + & 'overlap_scheme_name', this%i_overlap_scheme) + + ! Determine cloud PDF shape + call get_enum_code(cloud_pdf_shape_name, PdfShapeName, & + & 'cloud_pdf_shape_name', this%i_cloud_pdf_shape) + + this%i_aerosol_type_map = 0 + if (this%use_aerosols) then + this%i_aerosol_type_map(1:n_aerosol_types) & + & = i_aerosol_type_map(1:n_aerosol_types) + end if + + ! Will clouds be used at all? + if ((this%do_sw .and. this%i_solver_sw /= ISolverCloudless) & + & .or. (this%do_lw .and. this%i_solver_lw /= ISolverCloudless)) then + this%do_clouds = .true. + else + this%do_clouds = .false. + end if + + ! Normal subroutine exit + if (present(is_success)) then + is_success = .true. + end if + + if (lhook) call dr_hook('radiation_config:read',1,hook_handle) + + end subroutine read_config_from_namelist + + + !--------------------------------------------------------------------- + ! This routine is called by radiation_interface:setup_radiation and + ! it converts the user specified options into some more specific + ! data such as data file names + subroutine consolidate_config(this) + + use yomhook, only : lhook, dr_hook + use radiation_io, only : nulout, nulerr, radiation_abort + + class(config_type), intent(inout) :: this + + real(jprb) :: hook_handle + + if (lhook) call dr_hook('radiation_config:consolidate',0,hook_handle) + + ! Check consistency of models + if (this%do_canopy_fluxes_sw .and. .not. this%do_surface_sw_spectral_flux) then + if (this%iverbosesetup >= 1) then + write(nulout,'(a)') 'Warning: turning on do_surface_sw_spectral_flux as required by do_canopy_fluxes_sw' + end if + this%do_surface_sw_spectral_flux = .true. + end if + + ! Will clouds be used at all? + if ((this%do_sw .and. this%i_solver_sw /= ISolverCloudless) & + & .or. (this%do_lw .and. this%i_solver_lw /= ISolverCloudless)) then + this%do_clouds = .true. + else + this%do_clouds = .false. + end if + + ! SPARTACUS only works with Exp-Ran overlap scheme + if (( this%i_solver_sw == ISolverSPARTACUS & + & .or. this%i_solver_lw == ISolverSPARTACUS & + & .or. this%i_solver_sw == ISolverTripleclouds & + & .or. this%i_solver_lw == ISolverTripleclouds) & + & .and. this%i_overlap_scheme /= IOverlapExponentialRandom) then + write(nulerr,'(a)') '*** Error: SPARTACUS/Tripleclouds solvers can only do Exponential-Random overlap' + call radiation_abort('Radiation configuration error') + + end if + + ! Set aerosol optics file name + if (len_trim(this%aerosol_optics_override_file_name) > 0) then + if (this%aerosol_optics_override_file_name(1:1) == '/') then + this%aerosol_optics_file_name = trim(this%aerosol_optics_override_file_name) + else + this%aerosol_optics_file_name = trim(this%directory_name) & + & // '/' // trim(this%aerosol_optics_override_file_name) + end if + else + ! In the IFS, the aerosol optics file should be specified in + ! ifs/module/radiation_setup.F90, not here + this%aerosol_optics_file_name & + & = trim(this%directory_name) // "/aerosol_ifs_rrtm_45R2.nc" + end if + + ! Set liquid optics file name + if (len_trim(this%liq_optics_override_file_name) > 0) then + if (this%liq_optics_override_file_name(1:1) == '/') then + this%liq_optics_file_name = trim(this%liq_optics_override_file_name) + else + this%liq_optics_file_name = trim(this%directory_name) & + & // '/' // trim(this%liq_optics_override_file_name) + end if + else if (this%i_liq_model == ILiquidModelSOCRATES) then + this%liq_optics_file_name & + & = trim(this%directory_name) // "/socrates_droplet_scattering_rrtm.nc" + else if (this%i_liq_model == ILiquidModelSlingo) then + this%liq_optics_file_name & + & = trim(this%directory_name) // "/slingo_droplet_scattering_rrtm.nc" + end if + + ! Set ice optics file name + if (len_trim(this%ice_optics_override_file_name) > 0) then + if (this%ice_optics_override_file_name(1:1) == '/') then + this%ice_optics_file_name = trim(this%ice_optics_override_file_name) + else + this%ice_optics_file_name = trim(this%directory_name) & + & // '/' // trim(this%ice_optics_override_file_name) + end if + else if (this%i_ice_model == IIceModelFu) then + this%ice_optics_file_name & + & = trim(this%directory_name) // "/fu_ice_scattering_rrtm.nc" + else if (this%i_ice_model == IIceModelBaran) then + this%ice_optics_file_name & + & = trim(this%directory_name) // "/baran_ice_scattering_rrtm.nc" + else if (this%i_ice_model == IIceModelBaran2016) then + this%ice_optics_file_name & + & = trim(this%directory_name) // "/baran2016_ice_scattering_rrtm.nc" + else if (this%i_ice_model == IIceModelBaran2017) then + this%ice_optics_file_name & + & = trim(this%directory_name) // "/baran2017_ice_scattering_rrtm.nc" + else if (this%i_ice_model == IIceModelYi) then + this%ice_optics_file_name & + & = trim(this%directory_name) // "/yi_ice_scattering_rrtm.nc" + end if + + ! Set cloud-water PDF look-up table file name + if (len_trim(this%cloud_pdf_override_file_name) > 0) then + if (this%cloud_pdf_override_file_name(1:1) == '/') then + this%cloud_pdf_file_name = trim(this%cloud_pdf_override_file_name) + else + this%cloud_pdf_file_name = trim(this%directory_name) & + & // '/' // trim(this%cloud_pdf_override_file_name) + end if + elseif (this%i_cloud_pdf_shape == IPdfShapeLognormal) then + this%cloud_pdf_file_name = trim(this%directory_name) // "/mcica_lognormal.nc" + else + this%cloud_pdf_file_name = trim(this%directory_name) // "/mcica_gamma.nc" + end if + + ! Aerosol data + if (this%n_aerosol_types < 0 & + & .or. this%n_aerosol_types > NMaxAerosolTypes) then + write(nulerr,'(a,i0)') '*** Error: number of aerosol types must be between 0 and ', & + & NMaxAerosolTypes + call radiation_abort('Radiation configuration error') + end if + + if (this%use_aerosols .and. this%n_aerosol_types == 0) then + if (this%iverbosesetup >= 2) then + write(nulout, '(a)') 'Aerosols on but n_aerosol_types=0: optical properties to be computed outside ecRad' + end if + end if + + ! In the monochromatic case we need to override the liquid, ice + ! and aerosol models to ensure compatibility + if (this%i_gas_model == IGasModelMonochromatic) then + this%i_liq_model = ILiquidModelMonochromatic + this%i_ice_model = IIceModelMonochromatic + this%use_aerosols = .false. + end if + + ! McICA solver currently can't store full profiles of spectral fluxes + if (this%i_solver_sw == ISolverMcICA) then + this%do_save_spectral_flux = .false. + end if + + if (this%i_solver_sw == ISolverSPARTACUS .and. this%do_sw_delta_scaling_with_gases) then + write(nulerr,'(a)') '*** Error: SW delta-Eddington scaling with gases not possible with SPARTACUS solver' + call radiation_abort('Radiation configuration error') + end if + + if ((this%do_lw .and. this%do_sw) .and. & + & ( ( this%i_solver_sw == ISolverHomogeneous & + & .and. this%i_solver_lw /= ISolverHomogeneous) & + & .or. ( this%i_solver_sw /= ISolverHomogeneous & + & .and. this%i_solver_lw == ISolverHomogeneous) & + & ) ) then + write(nulerr,'(a)') '*** Error: if one solver is "Homogeneous" then the other must be' + call radiation_abort('Radiation configuration error') + end if + + ! Set is_homogeneous if the active solvers are homogeneous, since + ! this affects how "in-cloud" water contents are computed + if ( (this%do_sw .and. this%i_solver_sw == ISolverHomogeneous) & + & .or. (this%do_lw .and. this%i_solver_lw == ISolverHomogeneous)) then + this%is_homogeneous = .true. + end if + + this%is_consolidated = .true. + + if (lhook) call dr_hook('radiation_config:consolidate',1,hook_handle) + + end subroutine consolidate_config + + + !--------------------------------------------------------------------- + ! This subroutine sets members of the configuration object via + ! optional arguments, and any member not specified is left + ! untouched. Therefore, this should be called after taking data from + ! the namelist. + subroutine set_config(config, directory_name, & + & do_lw, do_sw, & + & do_lw_aerosol_scattering, do_lw_cloud_scattering, & + & do_sw_direct) + + class(config_type), intent(inout):: config + character(len=*), intent(in), optional :: directory_name + logical, intent(in), optional :: do_lw, do_sw + logical, intent(in), optional :: do_lw_aerosol_scattering + logical, intent(in), optional :: do_lw_cloud_scattering + logical, intent(in), optional :: do_sw_direct + + if (present(do_lw)) then + config%do_lw = do_lw + end if + + if(present(do_sw)) then + config%do_sw = do_sw + end if + + if (present(do_sw_direct)) then + config%do_sw_direct = do_sw_direct + end if + + if (present(directory_name)) then + config%directory_name = trim(directory_name) + end if + + if (present(do_lw_aerosol_scattering)) then + config%do_lw_aerosol_scattering = .true. + end if + + if (present(do_lw_cloud_scattering)) then + config%do_lw_cloud_scattering = .true. + end if + + end subroutine set_config + + + !--------------------------------------------------------------------- + ! Print configuration information to standard output + subroutine print_config(this, iverbose) + + use radiation_io, only : nulout + + class(config_type), intent(in) :: this + + integer, optional, intent(in) :: iverbose + integer :: i_local_verbose + + if (present(iverbose)) then + i_local_verbose = iverbose + else + i_local_verbose = this%iverbose + end if + + if (i_local_verbose >= 2) then + !--------------------------------------------------------------------- + write(nulout, '(a)') 'General settings:' + write(nulout, '(a,a,a)') ' Data files expected in "', & + & trim(this%directory_name), '"' + call print_logical(' Clear-sky calculations are', 'do_clear', this%do_clear) + call print_logical(' Saving intermediate radiative properties', & + & 'do_save_radiative_properties', this%do_save_radiative_properties) + call print_logical(' Saving spectral flux profiles', & + & 'do_save_spectral_flux', this%do_save_spectral_flux) + call print_enum(' Gas model is', GasModelName, 'i_gas_model', & + & this%i_gas_model) + call print_logical(' Aerosols are', 'use_aerosols', this%use_aerosols) + call print_logical(' Clouds are', 'do_clouds', this%do_clouds) + + !--------------------------------------------------------------------- + write(nulout, '(a)') 'Surface settings:' + if (this%do_sw) then + call print_logical(' Saving surface shortwave spectral fluxes', & + & 'do_surface_sw_spectral_flux', this%do_surface_sw_spectral_flux) + call print_logical(' Saving surface shortwave fluxes in abledo bands', & + & 'do_canopy_fluxes_sw', this%do_canopy_fluxes_sw) + end if + if (this%do_lw) then + call print_logical(' Saving surface longwave fluxes in emissivity bands', & + & 'do_canopy_fluxes_lw', this%do_canopy_fluxes_lw) + call print_logical(' Longwave derivative calculation is', & + & 'do_lw_derivatives',this%do_lw_derivatives) + end if + if (this%do_sw) then + call print_logical(' Nearest-neighbour spectral albedo mapping', & + & 'do_nearest_spectral_sw_albedo', this%do_nearest_spectral_sw_albedo) + end if + if (this%do_lw) then + call print_logical(' Nearest-neighbour spectral emissivity mapping', & + & 'do_nearest_spectral_lw_emiss', this%do_nearest_spectral_lw_emiss) + end if + !--------------------------------------------------------------------- + if (this%do_clouds) then + write(nulout, '(a)') 'Cloud settings:' + call print_real(' Cloud fraction threshold', & + & 'cloud_fraction_threshold', this%cloud_fraction_threshold) + call print_real(' Cloud mixing-ratio threshold', & + & 'cloud_mixing_ratio_threshold', this%cloud_mixing_ratio_threshold) + call print_enum(' Liquid optics scheme is', LiquidModelName, & + & 'i_liq_model',this%i_liq_model) + call print_enum(' Ice optics scheme is', IceModelName, & + & 'i_ice_model',this%i_ice_model) + if (this%i_ice_model == IIceModelFu) then + call print_logical(' Longwave ice optics bug in Fu scheme is', & + & 'do_fu_lw_ice_optics_bug',this%do_fu_lw_ice_optics_bug) + end if + call print_enum(' Cloud overlap scheme is', OverlapName, & + & 'i_overlap_scheme',this%i_overlap_scheme) + call print_logical(' Use "beta" overlap parameter is', & + & 'use_beta_overlap', this%use_beta_overlap) + call print_enum(' Cloud PDF shape is', PdfShapeName, & + & 'i_cloud_pdf_shape',this%i_cloud_pdf_shape) + call print_real(' Cloud inhom decorrelation scaling', & + & 'cloud_inhom_decorr_scaling', this%cloud_inhom_decorr_scaling) + end if + + !--------------------------------------------------------------------- + write(nulout, '(a)') 'Solver settings:' + if (this%do_sw) then + call print_enum(' Shortwave solver is', SolverName, & + & 'i_solver_sw', this%i_solver_sw) + + if (this%i_gas_model == IGasModelMonochromatic) then + call print_real(' Shortwave atmospheric optical depth', & + & 'mono_sw_total_od', this%mono_sw_total_od) + call print_real(' Shortwave particulate single-scattering albedo', & + & 'mono_sw_single_scattering_albedo', & + & this%mono_sw_single_scattering_albedo) + call print_real(' Shortwave particulate asymmetry factor', & + & 'mono_sw_asymmetry_factor', & + & this%mono_sw_asymmetry_factor) + end if + call print_logical(' Shortwave delta scaling after merge with gases', & + & 'do_sw_delta_scaling_with_gases', & + & this%do_sw_delta_scaling_with_gases) + else + call print_logical(' Shortwave calculations are','do_sw',this%do_sw) + end if + + if (this%do_lw) then + call print_enum(' Longwave solver is', SolverName, 'i_solver_lw', & + & this%i_solver_lw) + + if (this%i_gas_model == IGasModelMonochromatic) then + if (this%mono_lw_wavelength > 0.0_jprb) then + call print_real(' Longwave effective wavelength (m)', & + & 'mono_lw_wavelength', this%mono_lw_wavelength) + else + write(nulout,'(a)') ' Longwave fluxes are broadband (mono_lw_wavelength<=0)' + end if + call print_real(' Longwave atmospheric optical depth', & + & 'mono_lw_total_od', this%mono_lw_total_od) + call print_real(' Longwave particulate single-scattering albedo', & + & 'mono_lw_single_scattering_albedo', & + & this%mono_lw_single_scattering_albedo) + call print_real(' Longwave particulate asymmetry factor', & + & 'mono_lw_asymmetry_factor', & + & this%mono_lw_asymmetry_factor) + end if + call print_logical(' Longwave cloud scattering is', & + & 'do_lw_cloud_scattering',this%do_lw_cloud_scattering) + call print_logical(' Longwave aerosol scattering is', & + & 'do_lw_aerosol_scattering',this%do_lw_aerosol_scattering) + else + call print_logical(' Longwave calculations are','do_lw', this%do_lw) + end if + + if (this%i_solver_sw == ISolverSpartacus & + & .or. this%i_solver_lw == ISolverSpartacus) then + write(nulout, '(a)') ' SPARTACUS options:' + call print_integer(' Number of regions', 'n_regions', this%nregions) + call print_real(' Max cloud optical depth per layer', & + & 'max_cloud_od', this%max_cloud_od) + call print_enum(' Shortwave entrapment is', EntrapmentName, & + & 'i_3d_sw_entrapment', this%i_3d_sw_entrapment) + call print_logical(' Multilayer longwave horizontal transport is', & + 'do_3d_lw_multilayer_effects', this%do_3d_lw_multilayer_effects) + call print_logical(' Use matrix exponential everywhere is', & + & 'use_expm_everywhere', this%use_expm_everywhere) + call print_logical(' 3D effects are', 'do_3d_effects', & + & this%do_3d_effects) + + if (this%do_3d_effects) then + call print_logical(' Longwave side emissivity parameterization is', & + & 'do_lw_side_emissivity', this%do_lw_side_emissivity) + call print_real(' Clear-to-thick edge fraction is', & + & 'clear_to_thick_fraction', this%clear_to_thick_fraction) + call print_real(' Overhead sun factor is', & + & 'overhead_sun_factor', this%overhead_sun_factor) + call print_real(' Max gas optical depth for 3D effects', & + & 'max_gas_od_3d', this%max_gas_od_3d) + call print_real(' Max 3D transfer rate', & + & 'max_3d_transfer_rate', this%max_3d_transfer_rate) + call print_real(' Min cloud effective size (m)', & + & 'min_cloud_effective_size', this%min_cloud_effective_size) + call print_real(' Overhang factor', & + & 'overhang_factor', this%overhang_factor) + end if + end if + + end if + + end subroutine print_config + + + + !--------------------------------------------------------------------- + ! In order to estimate UV and photosynthetically active radiation, + ! we need weighted sum of fluxes considering wavelength range + ! required. This routine returns information for how to correctly + ! weight output spectral fluxes for a range of input wavelengths. + ! Note that this is approximate; internally it may be assumed that + ! the energy is uniformly distributed in wavenumber space, for + ! example. If the character string "weighting_name" is present, and + ! iverbose>=2, then information on the weighting will be provided on + ! nulout. + subroutine get_sw_weights(this, wavelength1, wavelength2, & + & nweights, iband, weight, weighting_name) + + use parkind1, only : jprb + use radiation_io, only : nulout, nulerr, radiation_abort + + class(config_type), intent(in) :: this + ! Range of wavelengths to get weights for (m) + real(jprb), intent(in) :: wavelength1, wavelength2 + ! Output number of weights needed + integer, intent(out) :: nweights + ! Only write to the first nweights of these arrays: they contain + ! the indices to the non-zero bands, and the weight in each of + ! those bands + integer, intent(out) :: iband(:) + real(jprb), intent(out) :: weight(:) + character(len=*), optional, intent(in) :: weighting_name + + ! Internally we deal with wavenumber + real(jprb) :: wavenumber1, wavenumber2 ! cm-1 + + integer :: jband ! Loop index for spectral band + + if (this%n_bands_sw <= 0) then + write(nulerr,'(a)') '*** Error: get_sw_weights called before number of shortwave bands set' + call radiation_abort() + end if + + ! Convert wavelength range (m) to wavenumber (cm-1) + wavenumber1 = 0.01_jprb / wavelength2 + wavenumber2 = 0.01_jprb / wavelength1 + + nweights = 0 + + do jband = 1,this%n_bands_sw + if (wavenumber1 < this%wavenumber2_sw(jband) & + & .and. wavenumber2 > this%wavenumber1_sw(jband)) then + nweights = nweights+1 + iband(nweights) = jband + weight(nweights) = (min(wavenumber2,this%wavenumber2_sw(jband)) & + & - max(wavenumber1,this%wavenumber1_sw(jband))) & + & / (this%wavenumber2_sw(jband) - this%wavenumber1_sw(jband)) + end if + end do + + if (nweights == 0) then + write(nulerr,'(a,e8.4,a,e8.4,a)') '*** Error: wavelength range ', & + & wavelength1, ' to ', wavelength2, ' m is outside shortwave band' + call radiation_abort() + else if (this%iverbosesetup >= 2 .and. present(weighting_name)) then + write(nulout,'(a,a,a,f6.0,a,f6.0,a)') 'Spectral weights for ', & + & weighting_name, ' (', wavenumber1, ' to ', & + & wavenumber2, ' cm-1):' + do jband = 1, nweights + write(nulout, '(a,i0,a,f6.0,a,f6.0,a,f8.4)') ' Shortwave band ', & + & iband(jband), ' (', this%wavenumber1_sw(iband(jband)), ' to ', & + & this%wavenumber2_sw(iband(jband)), ' cm-1): ', weight(jband) + end do + end if + + end subroutine get_sw_weights + + + !--------------------------------------------------------------------- + ! The input shortwave surface albedo coming in is likely to be in + ! different spectral intervals to the gas model in the radiation + ! scheme. We assume that the input albedo is defined within + ! "ninterval" spectral intervals covering the wavelength range 0 to + ! infinity, but allow for the possibility that two intervals may be + ! indexed back to the same albedo band. + subroutine define_sw_albedo_intervals(this, ninterval, wavelength_bound, & + & i_intervals, do_nearest) + + use radiation_io, only : nulerr, radiation_abort + + class(config_type), intent(inout) :: this + ! Number of spectral intervals in which albedo is defined + integer, intent(in) :: ninterval + ! Monotonically increasing wavelength bounds between intervals, + ! not including the outer bounds (which are assumed to be zero and + ! infinity) + real(jprb), intent(in) :: wavelength_bound(ninterval-1) + ! The albedo indices corresponding to each interval + integer, intent(in) :: i_intervals(ninterval) + logical, optional, intent(in) :: do_nearest + + if (ninterval > NMaxAlbedoIntervals) then + write(nulerr,'(a,i0,a,i0)') '*** Error: ', ninterval, & + & ' albedo intervals exceeds maximum of ', NMaxAlbedoIntervals + call radiation_abort(); + end if + + if (present(do_nearest)) then + this%do_nearest_spectral_sw_albedo = do_nearest + else + this%do_nearest_spectral_sw_albedo = .false. + end if + this%sw_albedo_wavelength_bound(1:ninterval-1) = wavelength_bound(1:ninterval-1) + this%sw_albedo_wavelength_bound(ninterval:) = -1.0_jprb + this%i_sw_albedo_index(1:ninterval) = i_intervals(1:ninterval) + this%i_sw_albedo_index(ninterval+1:) = 0 + + if (this%is_consolidated) then + call this%consolidate_intervals(.true., & + & this%do_nearest_spectral_sw_albedo, & + & this%sw_albedo_wavelength_bound, this%i_sw_albedo_index, & + & this%wavenumber1_sw, this%wavenumber2_sw, & + & this%i_albedo_from_band_sw, this%sw_albedo_weights) + end if + + end subroutine define_sw_albedo_intervals + + + !--------------------------------------------------------------------- + ! As define_sw_albedo_intervals but for longwave emissivity + subroutine define_lw_emiss_intervals(this, ninterval, wavelength_bound, & + & i_intervals, do_nearest) + + use radiation_io, only : nulerr, radiation_abort + + class(config_type), intent(inout) :: this + ! Number of spectral intervals in which emissivity is defined + integer, intent(in) :: ninterval + ! Monotonically increasing wavelength bounds between intervals, + ! not including the outer bounds (which are assumed to be zero and + ! infinity) + real(jprb), intent(in) :: wavelength_bound(ninterval-1) + ! The emissivity indices corresponding to each interval + integer, intent(in) :: i_intervals(ninterval) + logical, optional, intent(in) :: do_nearest + + if (ninterval > NMaxAlbedoIntervals) then + write(nulerr,'(a,i0,a,i0)') '*** Error: ', ninterval, & + & ' emissivity intervals exceeds maximum of ', NMaxAlbedoIntervals + call radiation_abort(); + end if + + if (present(do_nearest)) then + this%do_nearest_spectral_lw_emiss = do_nearest + else + this%do_nearest_spectral_lw_emiss = .false. + end if + this%lw_emiss_wavelength_bound(1:ninterval-1) = wavelength_bound(1:ninterval-1) + this%lw_emiss_wavelength_bound(ninterval:) = -1.0_jprb + this%i_lw_emiss_index(1:ninterval) = i_intervals(1:ninterval) + this%i_lw_emiss_index(ninterval+1:) = 0 + + if (this%is_consolidated) then + call this%consolidate_intervals(.false., & + & this%do_nearest_spectral_lw_emiss, & + & this%lw_emiss_wavelength_bound, this%i_lw_emiss_index, & + & this%wavenumber1_lw, this%wavenumber2_lw, & + & this%i_emiss_from_band_lw, this%lw_emiss_weights) + end if + + end subroutine define_lw_emiss_intervals + + + !--------------------------------------------------------------------- + ! This routine consolidates either the input shortwave albedo + ! intervals with the shortwave bands, or the input longwave + ! emissivity intervals with the longwave bands, depending on the + ! arguments provided. + subroutine consolidate_intervals(this, is_sw, do_nearest, & + & wavelength_bound, i_intervals, wavenumber1, wavenumber2, & + & i_mapping, weights) + + use radiation_io, only : nulout, nulerr, radiation_abort + + class(config_type), intent(inout) :: this + ! Is this the shortwave? Otherwise longwave + logical, intent(in) :: is_sw + ! Do we find the nearest albedo interval to the centre of each + ! band, or properly weight the contributions? This can be modified + ! if there is only one albedo intervals. + logical, intent(inout) :: do_nearest + ! Monotonically increasing wavelength bounds between intervals, + ! not including the outer bounds (which are assumed to be zero and + ! infinity) + real(jprb), intent(in) :: wavelength_bound(NMaxAlbedoIntervals-1) + ! The albedo band indices corresponding to each interval + integer, intent(in) :: i_intervals(NMaxAlbedoIntervals) + ! Start and end wavenumber bounds for the ecRad bands (cm-1) + real(jprb), intent(in) :: wavenumber1(:), wavenumber2(:) + + ! if do_nearest is TRUE then the result is expressed in i_mapping, + ! which will be allocated to have the same length as wavenumber1, + ! and contain the index of the albedo interval corresponding to + ! that band + integer, allocatable, intent(inout) :: i_mapping(:) + ! ...otherwise the result is expressed in "weights", of + ! size(n_intervals, n_bands) containing how much of each interval + ! contributes to each band. + real(jprb), allocatable, intent(inout) :: weights(:,:) + + ! Number and loop index of ecRad bands + integer :: nband, jband + ! Number and index of albedo/emissivity intervals + integer :: ninterval, iinterval + ! Sometimes an albedo or emissivity value will be used in more + ! than one interval, so nvalue indicates how many values will + ! actually be provided + integer :: nvalue + ! Wavenumber bounds of the albedo/emissivity interval + real(jprb) :: wavenumber1_albedo, wavenumber2_albedo + ! Reciprocal of the wavenumber range of the ecRad band + real(jprb) :: recip_dwavenumber ! cm + ! Midpoint/bound of wavenumber band + real(jprb) :: wavenumber_mid, wavenumber_bound ! cm-1 + + nband = size(wavenumber1) + + ! Count the number of albedo/emissivity intervals + ninterval = 0 + do iinterval = 1,NMaxAlbedoIntervals + if (i_intervals(iinterval) > 0) then + ninterval = iinterval + else + exit + end if + end do + + if (ninterval < 2) then + ! Zero or one albedo/emissivity intervals found, so we index all + ! bands to one interval + if (allocated(i_mapping)) then + deallocate(i_mapping) + end if + allocate(i_mapping(nband)) + i_mapping(:) = 1 + do_nearest = .true. + ninterval = 1 + nvalue = 1 + else + ! Check wavelength is monotonically increasing + do jband = 2,ninterval-1 + if (wavelength_bound(jband) <= wavelength_bound(jband-1)) then + if (is_sw) then + write(nulerr, '(a,a)') '*** Error: wavelength bounds for shortwave albedo intervals ', & + & 'must be monotonically increasing' + else + write(nulerr, '(a,a)') '*** Error: wavelength bounds for longwave emissivity intervals ', & + & 'must be monotonically increasing' + end if + call radiation_abort() + end if + end do + + ! What is the maximum index, indicating the number of + ! albedo/emissivity values to expect? + nvalue = maxval(i_intervals(1:ninterval)) + + if (do_nearest) then + ! Simpler nearest-neighbour mapping from band to + ! albedo/emissivity interval + if (allocated(i_mapping)) then + deallocate(i_mapping) + end if + allocate(i_mapping(nband)) + + ! Loop over bands + do jband = 1,nband + ! Compute mid-point of band in wavenumber space (cm-1) + wavenumber_mid = 0.5_jprb * (wavenumber1(jband) & + & + wavenumber2(jband)) + iinterval = 1 + ! Convert wavelength (m) into wavenumber (cm-1) at the lower + ! bound of the albedo interval + wavenumber_bound = 0.01_jprb / wavelength_bound(iinterval) + ! Find the albedo interval that has the largest overlap with + ! the band; this approach assumes that the albedo intervals + ! are larger than the spectral bands + do while (wavenumber_bound >= wavenumber_mid & + & .and. iinterval < ninterval) + iinterval = iinterval + 1 + if (iinterval < ninterval) then + wavenumber_bound = 0.01_jprb / wavelength_bound(iinterval) + else + ! For the last interval there is no lower bound + wavenumber_bound = 0.0_jprb + end if + end do + ! Save the index of the band corresponding to the albedo + ! interval and move onto the next band + i_mapping(jband) = i_intervals(iinterval) + end do + else + ! More accurate weighting + if (allocated(weights)) then + deallocate(weights) + end if + allocate(weights(nvalue,nband)) + weights(:,:) = 0.0_jprb + + ! Loop over bands + do jband = 1,nband + recip_dwavenumber = 1.0_jprb / (wavenumber2(jband) & + & - wavenumber1(jband)) + ! Find the first overlapping albedo band + iinterval = 1 + ! Convert wavelength (m) into wavenumber (cm-1) at the lower + ! bound (in wavenumber space) of the albedo/emissivty interval + wavenumber1_albedo = 0.01_jprb / wavelength_bound(iinterval) + do while (wavenumber1_albedo >= wavenumber2(jband) & + & .and. iinterval < ninterval) + iinterval = iinterval + 1 + wavenumber1_albedo = 0.01_jprb / wavelength_bound(iinterval) + end do + + wavenumber2_albedo = wavenumber2(jband) + + ! Add all overlapping bands + do while (wavenumber2_albedo > wavenumber1(jband) & + & .and. iinterval <= ninterval) + weights(i_intervals(iinterval),jband) & + & = weights(i_intervals(iinterval),jband) & + & + recip_dwavenumber & + & * (min(wavenumber2_albedo,wavenumber2(jband)) & + & - max(wavenumber1_albedo,wavenumber1(jband))) + wavenumber2_albedo = wavenumber1_albedo + iinterval = iinterval + 1 + if (iinterval < ninterval) then + wavenumber1_albedo = 0.01_jprb / wavelength_bound(iinterval) + else + wavenumber1_albedo = 0.0_jprb + end if + end do + end do + end if + end if + + ! Define how many bands to use for reporting surface downwelling + ! fluxes for canopy radiation scheme + if (is_sw) then + if (this%use_canopy_full_spectrum_sw) then + this%n_canopy_bands_sw = this%n_g_sw + else + this%n_canopy_bands_sw = nvalue + end if + else + if (this%use_canopy_full_spectrum_lw) then + this%n_canopy_bands_lw = this%n_g_lw + else + this%n_canopy_bands_lw = nvalue + end if + end if + + if (this%iverbosesetup >= 2) then + if (.not. do_nearest) then + if (is_sw) then + write(nulout, '(a,i0,a,i0,a)') 'Weighting of ', nvalue, ' albedo values in ', & + & nband, ' shortwave bands (wavenumber ranges in cm-1):' + else + write(nulout, '(a,i0,a,i0,a)') 'Weighting of ', nvalue, ' emissivity values in ', & + & nband, ' longwave bands (wavenumber ranges in cm-1):' + end if + do jband = 1,nband + write(nulout,'(i6,a,i6,a)',advance='no') nint(wavenumber1(jband)), ' to', & + & nint(wavenumber2(jband)), ':' + do iinterval = 1,nvalue + write(nulout,'(f5.2)',advance='no') weights(iinterval,jband) + end do + write(nulout, '()') + end do + else if (ninterval <= 1) then + if (is_sw) then + write(nulout, '(a)') 'All shortwave bands will use the same albedo' + else + write(nulout, '(a)') 'All longwave bands will use the same emissivty' + end if + else + if (is_sw) then + write(nulout, '(a,i0,a)',advance='no') 'Mapping from ', nband, & + & ' shortwave bands to albedo intervals:' + else + write(nulout, '(a,i0,a)',advance='no') 'Mapping from ', nband, & + & ' longwave bands to emissivity intervals:' + end if + do jband = 1,nband + write(nulout,'(a,i0)',advance='no') ' ', i_mapping(jband) + end do + write(nulout, '()') + end if + end if + + end subroutine consolidate_intervals + + + !--------------------------------------------------------------------- + ! Return the 0-based index for str in enum_str, or abort if it is + ! not found + subroutine get_enum_code(str, enum_str, var_name, icode) + + use radiation_io, only : nulerr, radiation_abort + + character(len=*), intent(in) :: str + character(len=*), intent(in) :: enum_str(0:) + character(len=*), intent(in) :: var_name + integer, intent(out) :: icode + + integer :: jc + logical :: is_not_found + + ! If string is empty then we don't modify icode but assume it has + ! a sensible default value + if (len_trim(str) > 1) then + is_not_found = .true. + + do jc = 0,size(enum_str)-1 + if (trim(str) == trim(enum_str(jc))) then + icode = jc + is_not_found = .false. + exit + end if + end do + if (is_not_found) then + write(nulerr,'(a,a,a,a,a)',advance='no') '*** Error: ', trim(var_name), & + & ' must be one of: "', enum_str(0), '"' + do jc = 1,size(enum_str)-1 + write(nulerr,'(a,a,a)',advance='no') ', "', trim(enum_str(jc)), '"' + end do + write(nulerr,'(a)') '' + call radiation_abort('Radiation configuration error') + end if + end if + + end subroutine get_enum_code + + + !--------------------------------------------------------------------- + ! Print one line of information: logical + subroutine print_logical(message_str, name, val) + use radiation_io, only : nulout + character(len=*), intent(in) :: message_str + character(len=*), intent(in) :: name + logical, intent(in) :: val + character(4) :: on_or_off + character(NPrintStringLen) :: str + if (val) then + on_or_off = ' ON ' + else + on_or_off = ' OFF' + end if + write(str, '(a,a4)') message_str, on_or_off + write(nulout,'(a,a,a,a,l1,a)') str, ' (', name, '=', val,')' + end subroutine print_logical + + + !--------------------------------------------------------------------- + ! Print one line of information: integer + subroutine print_integer(message_str, name, val) + use radiation_io, only : nulout + character(len=*), intent(in) :: message_str + character(len=*), intent(in) :: name + integer, intent(in) :: val + character(NPrintStringLen) :: str + write(str, '(a,a,i0)') message_str, ' = ', val + write(nulout,'(a,a,a,a)') str, ' (', name, ')' + end subroutine print_integer + + + !--------------------------------------------------------------------- + ! Print one line of information: real + subroutine print_real(message_str, name, val) + use parkind1, only : jprb + use radiation_io, only : nulout + character(len=*), intent(in) :: message_str + character(len=*), intent(in) :: name + real(jprb), intent(in) :: val + character(NPrintStringLen) :: str + write(str, '(a,a,g8.3)') message_str, ' = ', val + write(nulout,'(a,a,a,a)') str, ' (', name, ')' + end subroutine print_real + + + !--------------------------------------------------------------------- + ! Print one line of information: enum + subroutine print_enum(message_str, enum_str, name, val) + use radiation_io, only : nulout + character(len=*), intent(in) :: message_str + character(len=*), intent(in) :: enum_str(0:) + character(len=*), intent(in) :: name + integer, intent(in) :: val + character(NPrintStringLen) :: str + write(str, '(a,a,a,a)') message_str, ' "', trim(enum_str(val)), '"' + write(nulout,'(a,a,a,a,i0,a)') str, ' (', name, '=', val,')' + end subroutine print_enum + + + !--------------------------------------------------------------------- + ! Return .true. if 1D allocatable array "var" is out of physical + ! range specified by boundmin and boundmax, and issue a warning. + ! "do_fix" determines whether erroneous values are fixed to lie + ! within the physical range. To check only a subset of the array, + ! specify i1 and i2 for the range. + function out_of_bounds_1d(var, var_name, boundmin, boundmax, do_fix, i1, i2) result (is_bad) + + use radiation_io, only : nulout + + real(jprb), allocatable, intent(inout) :: var(:) + character(len=*), intent(in) :: var_name + real(jprb), intent(in) :: boundmin, boundmax + logical, intent(in) :: do_fix + integer, optional, intent(in) :: i1, i2 + + logical :: is_bad + + real(jprb) :: varmin, varmax + + is_bad = .false. + + if (allocated(var)) then + + if (present(i1) .and. present(i2)) then + varmin = minval(var(i1:i2)) + varmax = maxval(var(i1:i2)) + else + varmin = minval(var) + varmax = maxval(var) + end if + + if (varmin < boundmin .or. varmax > boundmax) then + write(nulout,'(a,a,a,g12.4,a,g12.4,a,g12.4,a,g12.4)',advance='no') & + & '*** Warning: ', var_name, ' range', varmin, ' to', varmax, & + & ' is out of physical range', boundmin, 'to', boundmax + is_bad = .true. + if (do_fix) then + if (present(i1) .and. present(i2)) then + var(i1:i2) = max(boundmin, min(boundmax, var(i1:i2))) + else + var = max(boundmin, min(boundmax, var)) + end if + write(nulout,'(a)') ': corrected' + else + write(nulout,'(1x)') + end if + end if + + end if + + end function out_of_bounds_1d + + + !--------------------------------------------------------------------- + ! Return .true. if 2D allocatable array "var" is out of physical + ! range specified by boundmin and boundmax, and issue a warning. To + ! check only a subset of the array, specify i1 and i2 for the range + ! of the first dimension and j1 and j2 for the range of the second. + function out_of_bounds_2d(var, var_name, boundmin, boundmax, do_fix, & + & i1, i2, j1, j2) result (is_bad) + + use radiation_io, only : nulout + + real(jprb), allocatable, intent(inout) :: var(:,:) + character(len=*), intent(in) :: var_name + real(jprb), intent(in) :: boundmin, boundmax + logical, intent(in) :: do_fix + integer, optional, intent(in) :: i1, i2, j1, j2 + + ! Local copies of indices + integer :: ii1, ii2, jj1, jj2 + + logical :: is_bad + + real(jprb) :: varmin, varmax + + is_bad = .false. + + if (allocated(var)) then + + if (present(i1) .and. present(i2)) then + ii1 = i1 + ii2 = i2 + else + ii1 = lbound(var,1) + ii2 = ubound(var,1) + end if + if (present(j1) .and. present(j2)) then + jj1 = j1 + jj2 = j2 + else + jj1 = lbound(var,2) + jj2 = ubound(var,2) + end if + varmin = minval(var(ii1:ii2,jj1:jj2)) + varmax = maxval(var(ii1:ii2,jj1:jj2)) + + if (varmin < boundmin .or. varmax > boundmax) then + write(nulout,'(a,a,a,g12.4,a,g12.4,a,g12.4,a,g12.4)',advance='no') & + & '*** Warning: ', var_name, ' range', varmin, ' to', varmax,& + & ' is out of physical range', boundmin, 'to', boundmax + is_bad = .true. + if (do_fix) then + var(ii1:ii2,jj1:jj2) = max(boundmin, min(boundmax, var(ii1:ii2,jj1:jj2))) + write(nulout,'(a)') ': corrected' + else + write(nulout,'(1x)') + end if + end if + + end if + + end function out_of_bounds_2d + + + !--------------------------------------------------------------------- + ! Return .true. if 3D allocatable array "var" is out of physical + ! range specified by boundmin and boundmax, and issue a warning. To + ! check only a subset of the array, specify i1 and i2 for the range + ! of the first dimension, j1 and j2 for the second and k1 and k2 for + ! the third. + function out_of_bounds_3d(var, var_name, boundmin, boundmax, do_fix, & + & i1, i2, j1, j2, k1, k2) result (is_bad) + + use radiation_io, only : nulout + + real(jprb), allocatable, intent(inout) :: var(:,:,:) + character(len=*), intent(in) :: var_name + real(jprb), intent(in) :: boundmin, boundmax + logical, intent(in) :: do_fix + integer, optional, intent(in) :: i1, i2, j1, j2, k1, k2 + + ! Local copies of indices + integer :: ii1, ii2, jj1, jj2, kk1, kk2 + + logical :: is_bad + + real(jprb) :: varmin, varmax + + is_bad = .false. + + if (allocated(var)) then + + if (present(i1) .and. present(i2)) then + ii1 = i1 + ii2 = i2 + else + ii1 = lbound(var,1) + ii2 = ubound(var,1) + end if + if (present(j1) .and. present(j2)) then + jj1 = j1 + jj2 = j2 + else + jj1 = lbound(var,2) + jj2 = ubound(var,2) + end if + if (present(k1) .and. present(k2)) then + kk1 = k1 + kk2 = k2 + else + kk1 = lbound(var,3) + kk2 = ubound(var,3) + end if + varmin = minval(var(ii1:ii2,jj1:jj2,kk1:kk2)) + varmax = maxval(var(ii1:ii2,jj1:jj2,kk1:kk2)) + + if (varmin < boundmin .or. varmax > boundmax) then + write(nulout,'(a,a,a,g12.4,a,g12.4,a,g12.4,a,g12.4)',advance='no') & + & '*** Warning: ', var_name, ' range', varmin, ' to', varmax,& + & ' is out of physical range', boundmin, 'to', boundmax + is_bad = .true. + if (do_fix) then + var(ii1:ii2,jj1:jj2,kk1:kk2) = max(boundmin, min(boundmax, & + & var(ii1:ii2,jj1:jj2,kk1:kk2))) + write(nulout,'(a)') ': corrected' + else + write(nulout,'(1x)') + end if + end if + + end if + + end function out_of_bounds_3d + + +end module radiation_config diff --git a/src/LIB/RAD/ecrad-1.4.0_mnh/radiation/radiation_delta_eddington.h b/src/LIB/RAD/ecrad-1.4.0_mnh/radiation/radiation_delta_eddington.h new file mode 100644 index 000000000..04f43bafd --- /dev/null +++ b/src/LIB/RAD/ecrad-1.4.0_mnh/radiation/radiation_delta_eddington.h @@ -0,0 +1,93 @@ +! radiation_delta_eddington.h - Delta-Eddington scaling +! +! (C) Copyright 2015- 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 +! +! This file is intended to be included inside a module to ensure that +! these simple functions may be inlined + +!--------------------------------------------------------------------- +! Perform in-place delta-Eddington scaling of the phase function +elemental subroutine delta_eddington(od, ssa, g) + + use parkind1, only : jprb + + ! Total optical depth, single scattering albedo and asymmetry + ! factor + real(jprb), intent(inout) :: od, ssa, g + + ! Fraction of the phase function deemed to be in the forward lobe + ! and therefore treated as if it is not scattered at all + real(jprb) :: f + + f = g*g + od = od * (1.0_jprb - ssa*f) + ssa = ssa * (1.0_jprb - f) / (1.0_jprb - ssa*f) + g = g / (1.0_jprb + g) + +end subroutine delta_eddington + + +!--------------------------------------------------------------------- +! Perform in-place delta-Eddington scaling of the phase function, but +! using extensive variables (i.e. the scattering optical depth, +! scat_od, rather than the single-scattering albedo, and the +! scattering-optical-depth-multiplied-by-asymmetry-factor, scat_od_g, +! rather than the asymmetry factor. +elemental subroutine delta_eddington_extensive(od, scat_od, scat_od_g) + + use parkind1, only : jprb + + ! Total optical depth, scattering optical depth and asymmetry factor + ! multiplied by the scattering optical depth + real(jprb), intent(inout) :: od, scat_od, scat_od_g + + ! Fraction of the phase function deemed to be in the forward lobe + ! and therefore treated as if it is not scattered at all + real(jprb) :: f, g + + if (scat_od > 0.0_jprb) then + g = scat_od_g / scat_od + else + g = 0.0 + end if + + f = g*g + od = od - scat_od * f + scat_od = scat_od * (1.0_jprb - f) + scat_od_g = scat_od * g / (1.0_jprb + g) + +end subroutine delta_eddington_extensive + + +!--------------------------------------------------------------------- +! Perform in-place delta-Eddington scaling of the phase function, +! using the scattering optical depth rather than the single scattering +! albedo +elemental subroutine delta_eddington_scat_od(od, scat_od, g) + + use parkind1, only : jprb + + ! Total optical depth, scattering optical depth and asymmetry factor + real(jprb), intent(inout) :: od, scat_od, g + + ! Fraction of the phase function deemed to be in the forward lobe + ! and therefore treated as if it is not scattered at all + real(jprb) :: f + + f = g*g + od = od - scat_od * f + scat_od = scat_od * (1.0_jprb - f) + g = g / (1.0_jprb + g) + +end subroutine delta_eddington_scat_od + -- GitLab