From 028140cea77186ae7e604485e2617c8458acc552 Mon Sep 17 00:00:00 2001 From: AMLattanzi Date: Fri, 10 Jan 2025 13:20:23 -0800 Subject: [PATCH 01/13] First pass at merging port into development. --- .gitmodules | 6 +- CMake/BuildERFExe.cmake | 28 +- CMakeLists.txt | 14 +- Exec/Make.ERF | 57 +- Source/ERF.H | 2 +- Source/ERF.cpp | 8 +- Source/ERF_Config.H.in | 14 - Source/Radiation/ERF_AeroRadProps.H | 135 -- Source/Radiation/ERF_AeroRadProps.cpp | 701 ------ Source/Radiation/ERF_Albedo.H | 13 - Source/Radiation/ERF_Albedo.cpp | 19 - Source/Radiation/ERF_CloudRadProps.H | 96 - Source/Radiation/ERF_CloudRadProps.cpp | 373 ---- Source/Radiation/ERF_EbertCurry.H | 157 -- Source/Radiation/ERF_FinalizeRRTMGP.cpp | 19 - Source/Radiation/ERF_InitRRTMGP.cpp | 39 - Source/Radiation/ERF_LinearInterpolate.H | 342 --- Source/Radiation/ERF_M2005EffRadius.H | 126 -- Source/Radiation/ERF_Mam4Aero.H | 756 ------- Source/Radiation/ERF_Mam4Constituents.H | 1210 ----------- Source/Radiation/ERF_ModalAeroWaterUptake.H | 455 ---- Source/Radiation/ERF_Optics.H | 98 - Source/Radiation/ERF_Optics.cpp | 467 ---- Source/Radiation/ERF_OrbCosZenith.H | 43 + Source/Radiation/ERF_OrbCosZenith.cpp | 573 +++++ Source/Radiation/ERF_Parameterizations.H | 43 - Source/Radiation/ERF_PhysProp.H | 864 -------- Source/Radiation/ERF_RRTMGP.H | 125 -- Source/Radiation/ERF_RRTMGP_Interface.H | 145 ++ Source/Radiation/ERF_RRTMGP_Interface.cpp | 1171 ++++++++++ Source/Radiation/ERF_RRTMGP_Utils.H | 144 ++ Source/Radiation/ERF_RadConstants.H | 264 --- Source/Radiation/ERF_Radiation.H | 506 ++--- Source/Radiation/ERF_Radiation.cpp | 1880 ++++++----------- Source/Radiation/ERF_RunLongWaveRRTMGP.cpp | 146 -- Source/Radiation/ERF_RunShortWaveRRTMGP.cpp | 150 -- Source/Radiation/ERF_Slingo.H | 157 -- Source/Radiation/Make.package | 44 +- .../TimeIntegration/ERF_AdvanceRadiation.cpp | 32 +- 39 files changed, 3082 insertions(+), 8340 deletions(-) delete mode 100644 Source/ERF_Config.H.in delete mode 100644 Source/Radiation/ERF_AeroRadProps.H delete mode 100644 Source/Radiation/ERF_AeroRadProps.cpp delete mode 100644 Source/Radiation/ERF_Albedo.H delete mode 100644 Source/Radiation/ERF_Albedo.cpp delete mode 100644 Source/Radiation/ERF_CloudRadProps.H delete mode 100644 Source/Radiation/ERF_CloudRadProps.cpp delete mode 100644 Source/Radiation/ERF_EbertCurry.H delete mode 100644 Source/Radiation/ERF_FinalizeRRTMGP.cpp delete mode 100644 Source/Radiation/ERF_InitRRTMGP.cpp delete mode 100644 Source/Radiation/ERF_LinearInterpolate.H delete mode 100644 Source/Radiation/ERF_M2005EffRadius.H delete mode 100644 Source/Radiation/ERF_Mam4Aero.H delete mode 100644 Source/Radiation/ERF_Mam4Constituents.H delete mode 100644 Source/Radiation/ERF_ModalAeroWaterUptake.H delete mode 100644 Source/Radiation/ERF_Optics.H delete mode 100644 Source/Radiation/ERF_Optics.cpp create mode 100644 Source/Radiation/ERF_OrbCosZenith.H create mode 100644 Source/Radiation/ERF_OrbCosZenith.cpp delete mode 100644 Source/Radiation/ERF_Parameterizations.H delete mode 100644 Source/Radiation/ERF_PhysProp.H delete mode 100644 Source/Radiation/ERF_RRTMGP.H create mode 100644 Source/Radiation/ERF_RRTMGP_Interface.H create mode 100644 Source/Radiation/ERF_RRTMGP_Interface.cpp create mode 100644 Source/Radiation/ERF_RRTMGP_Utils.H delete mode 100644 Source/Radiation/ERF_RadConstants.H delete mode 100644 Source/Radiation/ERF_RunLongWaveRRTMGP.cpp delete mode 100644 Source/Radiation/ERF_RunShortWaveRRTMGP.cpp delete mode 100644 Source/Radiation/ERF_Slingo.H diff --git a/.gitmodules b/.gitmodules index ba9b4da82..16611622c 100644 --- a/.gitmodules +++ b/.gitmodules @@ -9,11 +9,11 @@ [submodule "Submodules/RRTMGP"] path = Submodules/RRTMGP url = https://github.com/E3SM-Project/rte-rrtmgp - shallow = true + shallow = true [submodule "Submodules/NOAH-MP"] path = Submodules/NOAH-MP - url = https://github.com/AIEADA/noahmp.git - shallow=true + url = https://github.com/AIEADA/noahmp.git + shallow=true [submodule "Submodules/WW3"] path = Submodules/WW3 url = https://github.com/erf-model/WW3 diff --git a/CMake/BuildERFExe.cmake b/CMake/BuildERFExe.cmake index aa6c6ab12..995296402 100644 --- a/CMake/BuildERFExe.cmake +++ b/CMake/BuildERFExe.cmake @@ -66,17 +66,19 @@ function(build_erf_lib erf_lib_name) if(ERF_ENABLE_RRTMGP) target_sources(${erf_lib_name} PRIVATE - ${SRC_DIR}/Utils/ERF_Orbit.cpp - ${SRC_DIR}/Radiation/ERF_InitRRTMGP.cpp - ${SRC_DIR}/Radiation/ERF_FinalizeRRTMGP.cpp - ${SRC_DIR}/Radiation/ERF_RunLongWaveRRTMGP.cpp - ${SRC_DIR}/Radiation/ERF_RunShortWaveRRTMGP.cpp - ${SRC_DIR}/Radiation/ERF_CloudRadProps.cpp - ${SRC_DIR}/Radiation/ERF_AeroRadProps.cpp - ${SRC_DIR}/Radiation/ERF_Optics.cpp + ${SRC_DIR}/Radiation/ERF_RRTMGP_Interface.cpp + ${SRC_DIR}/Radiation/ERF_RRTMGP_Utils.cpp ${SRC_DIR}/Radiation/ERF_Radiation.cpp - ${SRC_DIR}/Radiation/ERF_Albedo.cpp + ${SRC_DIR}/Radiation/ERF_OrbCosZenith.cpp + ${CMAKE_SOURCE_DIR}/Submodules/RRTMGP/cpp/rrtmgp/mo_rrtmgp_util_reorder.cpp + ${CMAKE_SOURCE_DIR}/Submodules/RRTMGP/cpp/rrtmgp/kernels/mo_gas_optics_kernels.cpp + ${CMAKE_SOURCE_DIR}/Submodules/RRTMGP/cpp/rte/expand_and_transpose.cpp + ${CMAKE_SOURCE_DIR}/Submodules/RRTMGP/cpp/rte/kernels/mo_fluxes_broadband_kernels.cpp + ${CMAKE_SOURCE_DIR}/Submodules/RRTMGP/cpp/rte/kernels/mo_optical_props_kernels.cpp + ${CMAKE_SOURCE_DIR}/Submodules/RRTMGP/cpp/rte/kernels/mo_rte_solver_kernels.cpp ${CMAKE_SOURCE_DIR}/Submodules/RRTMGP/cpp/examples/mo_load_coefficients.cpp + ${CMAKE_SOURCE_DIR}/Submodules/RRTMGP/cpp/examples/all-sky/mo_garand_atmos_io.cpp + ${CMAKE_SOURCE_DIR}/Submodules/RRTMGP/cpp/examples/all-sky/mo_load_cloud_coefficients.cpp ${CMAKE_SOURCE_DIR}/Submodules/RRTMGP/cpp/extensions/fluxes_byband/mo_fluxes_byband_kernels.cpp ) @@ -84,9 +86,13 @@ function(build_erf_lib erf_lib_name) target_compile_definitions(${erf_lib_name} PUBLIC ERF_USE_RRTMGP) target_include_directories(${erf_lib_name} SYSTEM PUBLIC - ${CMAKE_SOURCE_DIR}/Submodules/RRTMGP/cpp/extensions/fluxes_byband - ${CMAKE_SOURCE_DIR}/Submodules/RRTMGP/cpp/extensions/cloud_optics + ${CMAKE_SOURCE_DIR}/Submodules/RRTMGP/cpp/rrtmgp + ${CMAKE_SOURCE_DIR}/Submodules/RRTMGP/cpp/rrtmgp/kernels + ${CMAKE_SOURCE_DIR}/Submodules/RRTMGP/cpp/rte + ${CMAKE_SOURCE_DIR}/Submodules/RRTMGP/cpp/rte/kernels ${CMAKE_SOURCE_DIR}/Submodules/RRTMGP/cpp/examples + ${CMAKE_SOURCE_DIR}/Submodules/RRTMGP/cpp/examples/all-sky + ${CMAKE_SOURCE_DIR}/Submodules/RRTMGP/cpp/extensions/cloud_optics ) endif() diff --git a/CMakeLists.txt b/CMakeLists.txt index e7a740936..67fb0bfe4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -187,12 +187,6 @@ if(ERF_ENABLE_RRTMGP) set(RRTMGP_BIN ${CMAKE_BINARY_DIR}/rrtmgp) add_subdirectory(${CMAKE_SOURCE_DIR}/Submodules/RRTMGP/cpp ${RRTMGP_BIN}) - # Find the radiation data files - if (DEFINED ENV{ERF_RADIATION_DATA_DIR}) - set(ERF_RADIATION_DATA_DIR "$ENV{ERF_RADIATION_DATA_DIR}") - else() - message(FATAL_ERROR "Environment variable ERF_RADIATION_DATA_DIR not set!") - endif() endif() ########################### ERF ##################################### @@ -201,12 +195,6 @@ if(ERF_ENABLE_MPI) find_package(MPI REQUIRED) endif() -configure_file( - ${CMAKE_SOURCE_DIR}/Source/ERF_Config.H.in - ${CMAKE_BINARY_DIR}/ERF_Config.H - @ONLY -) - # General information about machine, compiler, and build type message(STATUS "ERF Information:") message(STATUS "CMAKE_SYSTEM_NAME = ${CMAKE_SYSTEM_NAME}") @@ -245,7 +233,7 @@ target_link_libraries(erf_api PUBLIC erf_srclib) add_library(${PROJECT_NAME}::erf_api ALIAS erf_srclib) # Collect all headers and make them installable with the target -set(ERF_INCLUDES "Source/ERF.H;Source/ERF_Constants.H;Source/WindFarmParametrization/SimpleActuatorDisk/ERF_SimpleAD.H;Source/WindFarmParametrization/EWP/ERF_EWP.H;Source/WindFarmParametrization/Null/ERF_NullWindFarm.H;Source/WindFarmParametrization/ERF_WindFarm.H;Source/WindFarmParametrization/Fitch/ERF_Fitch.H;Source/BoundaryConditions/ERF_PhysBCFunct.H;Source/BoundaryConditions/ERF_MOSTAverage.H;Source/BoundaryConditions/ERF_MOSTRoughness.H;Source/BoundaryConditions/ERF_ABLMost.H;Source/BoundaryConditions/ERF_FillPatcher.H;Source/BoundaryConditions/ERF_MOSTStress.H;Source/BoundaryConditions/ERF_TimeInterpolatedData.H;Source/Utils/ERF_Interpolation.H;Source/Utils/ERF_TileNoZ.H;Source/Utils/ERF_PlaneAverage.H;Source/Utils/ERF_Interpolation_WENO.H;Source/Utils/ERF_DirectionSelector.H;Source/Utils/ERF_ParFunctions.H;Source/Utils/ERF_Wstar.H;Source/Utils/ERF_MicrophysicsUtils.H;Source/Utils/ERF_SatMethods.H;Source/Utils/ERF_Interpolation_1D.H;Source/Utils/ERF_Interpolation_UPW.H;Source/Utils/ERF_TerrainMetrics.H;Source/Utils/ERF_Interpolation_WENO_Z.H;Source/Utils/ERF_Thetav.H;Source/Utils/ERF_WaterVaporSaturation.H;Source/Utils/ERF_Utils.H;Source/Utils/ERF_Orbit.H;Source/Utils/ERF_EOS.H;Source/Utils/ERF_HSEUtils.H;Source/EB/ERF_TerrainIF.H;Source/EB/ERF_EBIF.H;Source/Particles/ERFPC.H;Source/Particles/ERF_ParticleData.H;Source/Prob/ERF_InitDensityHSEDry.H;Source/Prob/ERF_InitRayleighDamping.H;Source/Prob/ERF_InitConstantDensityHSE.H;Source/ERF_ProbCommon.H;Source/ERF_Derive.H;Source/Radiation/ERF_Mam4Constituents.H;Source/Radiation/ERF_Mam4Aero.H;Source/Radiation/ERF_Optics.H;Source/Radiation/ERF_ModalAeroWaterUptake.H;Source/Radiation/ERF_CloudRadProps.H;Source/Radiation/ERF_PhysProp.H;Source/Radiation/ERF_Radiation.H;Source/Radiation/ERF_Albedo.H;Source/Radiation/ERF_Parameterizations.H;Source/Radiation/ERF_RadConstants.H;Source/Radiation/ERF_AeroRadProps.H;Source/Radiation/ERF_M2005EffRadius.H;Source/Radiation/ERF_LinearInterpolate.H;Source/Radiation/ERF_Slingo.H;Source/Radiation/ERF_RRTMGP.H;Source/Radiation/ERF_EbertCurry.H;Source/SourceTerms/ERF_NumericalDiffusion.H;Source/SourceTerms/ERF_SrcHeaders.H;Source/SourceTerms/ERF_ForestDrag.H;Source/SourceTerms/ERF_TerrainDrag.H;Source/IO/ERF_SampleData.H;Source/IO/ERF_NCInterface.H;Source/IO/ERF_NCWpsFile.H;Source/IO/ERF_NCPlotFile.H;Source/IO/ERF_ReadBndryPlanes.H;Source/IO/ERF_WriteBndryPlanes.H;Source/PBL/ERF_MYNNStruct.H;Source/PBL/ERF_PBLModels.H;Source/PBL/ERF_PBLHeight.H;Source/TimeIntegration/ERF_TI_substep_fun.H;Source/TimeIntegration/ERF_TI_slow_headers.H;Source/TimeIntegration/ERF_TI_slow_rhs_fun.H;Source/TimeIntegration/ERF_TI_fast_headers.H;Source/TimeIntegration/ERF_TI_utils.H;Source/TimeIntegration/ERF_MRI.H;Source/TimeIntegration/ERF_TI_no_substep_fun.H;Source/LandSurfaceModel/Null/ERF_NullSurf.H;Source/LandSurfaceModel/ERF_LandSurface.H;Source/LandSurfaceModel/MM5/ERF_MM5.H;Source/LandSurfaceModel/SLM/ERF_SLM.H;Source/ERF_IndexDefines.H;Source/Advection/ERF_AdvectionSrcForMom_N.H;Source/Advection/ERF_AdvectionSrcForScalars.H;Source/Advection/ERF_AdvectionSrcForMom_T.H;Source/Advection/ERF_Advection.H;Source/MultiBlock/ERF_MultiBlockContainer.H;Source/Initialization/ERF_MetgridUtils.H;Source/Diffusion/ERF_EddyViscosity.H;Source/Diffusion/ERF_Diffusion.H;Source/Microphysics/Null/ERF_NullMoistLagrangian.H;Source/Microphysics/Null/ERF_NullMoist.H;Source/Microphysics/ERF_Microphysics.H;Source/Microphysics/ERF_LagrangianMicrophysics.H;Source/Microphysics/ERF_EulerianMicrophysics.H;Source/Microphysics/Kessler/ERF_Kessler.H;Source/Microphysics/SAM/ERF_SAM.H;Source/Microphysics/SatAdj/ERF_SatAdj.H;Source/DataStructs/ERF_InputSpongeData.H;Source/DataStructs/ERF_TurbPertStruct.H;Source/DataStructs/ERF_SpongeStruct.H;Source/DataStructs/ERF_AdvStruct.H;Source/DataStructs/ERF_DataStruct.H;Source/DataStructs/ERF_InputSoundingData.H;Source/DataStructs/ERF_DiffStruct.H;Source/DataStructs/ERF_TurbStruct.H;Source/LinearSolvers/ERF_TerrainPoisson.H;Source/LinearSolvers/ERF_FFTUtils.H") +set(ERF_INCLUDES "Source/ERF.H;Source/ERF_Constants.H;Source/WindFarmParametrization/SimpleActuatorDisk/ERF_SimpleAD.H;Source/WindFarmParametrization/EWP/ERF_EWP.H;Source/WindFarmParametrization/Null/ERF_NullWindFarm.H;Source/WindFarmParametrization/ERF_WindFarm.H;Source/WindFarmParametrization/Fitch/ERF_Fitch.H;Source/BoundaryConditions/ERF_PhysBCFunct.H;Source/BoundaryConditions/ERF_MOSTAverage.H;Source/BoundaryConditions/ERF_MOSTRoughness.H;Source/BoundaryConditions/ERF_ABLMost.H;Source/BoundaryConditions/ERF_FillPatcher.H;Source/BoundaryConditions/ERF_MOSTStress.H;Source/BoundaryConditions/ERF_TimeInterpolatedData.H;Source/Utils/ERF_Interpolation.H;Source/Utils/ERF_TileNoZ.H;Source/Utils/ERF_PlaneAverage.H;Source/Utils/ERF_Interpolation_WENO.H;Source/Utils/ERF_DirectionSelector.H;Source/Utils/ERF_ParFunctions.H;Source/Utils/ERF_Wstar.H;Source/Utils/ERF_MicrophysicsUtils.H;Source/Utils/ERF_SatMethods.H;Source/Utils/ERF_Interpolation_1D.H;Source/Utils/ERF_Interpolation_UPW.H;Source/Utils/ERF_TerrainMetrics.H;Source/Utils/ERF_Interpolation_WENO_Z.H;Source/Utils/ERF_Thetav.H;Source/Utils/ERF_WaterVaporSaturation.H;Source/Utils/ERF_Utils.H;Source/Utils/ERF_Orbit.H;Source/Utils/ERF_EOS.H;Source/Utils/ERF_HSEUtils.H;Source/EB/ERF_TerrainIF.H;Source/EB/ERF_EBIF.H;Source/Particles/ERFPC.H;Source/Particles/ERF_ParticleData.H;Source/Prob/ERF_InitDensityHSEDry.H;Source/Prob/ERF_InitRayleighDamping.H;Source/Prob/ERF_InitConstantDensityHSE.H;Source/ERF_ProbCommon.H;Source/ERF_Derive.H;Source/Radiation/ERF_RRTMGP_Interface.H;Source/Radiation/ERF_RRTMGP_Utils.H;Source/Radiation/ERF_Radiation.H;Source/Radiation/ERF_OrbCosZenith.H;Source/SourceTerms/ERF_NumericalDiffusion.H;Source/SourceTerms/ERF_SrcHeaders.H;Source/SourceTerms/ERF_ForestDrag.H;Source/SourceTerms/ERF_TerrainDrag.H;Source/IO/ERF_SampleData.H;Source/IO/ERF_NCInterface.H;Source/IO/ERF_NCWpsFile.H;Source/IO/ERF_NCPlotFile.H;Source/IO/ERF_ReadBndryPlanes.H;Source/IO/ERF_WriteBndryPlanes.H;Source/PBL/ERF_MYNNStruct.H;Source/PBL/ERF_PBLModels.H;Source/PBL/ERF_PBLHeight.H;Source/TimeIntegration/ERF_TI_substep_fun.H;Source/TimeIntegration/ERF_TI_slow_headers.H;Source/TimeIntegration/ERF_TI_slow_rhs_fun.H;Source/TimeIntegration/ERF_TI_fast_headers.H;Source/TimeIntegration/ERF_TI_utils.H;Source/TimeIntegration/ERF_MRI.H;Source/TimeIntegration/ERF_TI_no_substep_fun.H;Source/LandSurfaceModel/Null/ERF_NullSurf.H;Source/LandSurfaceModel/ERF_LandSurface.H;Source/LandSurfaceModel/MM5/ERF_MM5.H;Source/LandSurfaceModel/SLM/ERF_SLM.H;Source/ERF_IndexDefines.H;Source/Advection/ERF_AdvectionSrcForMom_N.H;Source/Advection/ERF_AdvectionSrcForScalars.H;Source/Advection/ERF_AdvectionSrcForMom_T.H;Source/Advection/ERF_Advection.H;Source/MultiBlock/ERF_MultiBlockContainer.H;Source/Initialization/ERF_MetgridUtils.H;Source/Diffusion/ERF_EddyViscosity.H;Source/Diffusion/ERF_Diffusion.H;Source/Microphysics/Null/ERF_NullMoistLagrangian.H;Source/Microphysics/Null/ERF_NullMoist.H;Source/Microphysics/ERF_Microphysics.H;Source/Microphysics/ERF_LagrangianMicrophysics.H;Source/Microphysics/ERF_EulerianMicrophysics.H;Source/Microphysics/Kessler/ERF_Kessler.H;Source/Microphysics/SAM/ERF_SAM.H;Source/Microphysics/SatAdj/ERF_SatAdj.H;Source/DataStructs/ERF_InputSpongeData.H;Source/DataStructs/ERF_TurbPertStruct.H;Source/DataStructs/ERF_SpongeStruct.H;Source/DataStructs/ERF_AdvStruct.H;Source/DataStructs/ERF_DataStruct.H;Source/DataStructs/ERF_InputSoundingData.H;Source/DataStructs/ERF_DiffStruct.H;Source/DataStructs/ERF_TurbStruct.H;Source/LinearSolvers/ERF_TerrainPoisson.H;Source/LinearSolvers/ERF_FFTUtils.H") set_target_properties( erf_srclib PROPERTIES PUBLIC_HEADER "${ERF_INCLUDES}") diff --git a/Exec/Make.ERF b/Exec/Make.ERF index f9ce7d172..fa3cd8a4f 100644 --- a/Exec/Make.ERF +++ b/Exec/Make.ERF @@ -178,18 +178,24 @@ include $(ERF_LSM_SLM_DIR)/Make.package VPATH_LOCATIONS += $(ERF_LSM_SLM_DIR) INCLUDE_LOCATIONS += $(ERF_LSM_SLM_DIR) +ERF_LSM_MM5_DIR = $(ERF_SOURCE_DIR)/LandSurfaceModel/MM5 +include $(ERF_LSM_MM5_DIR)/Make.package +VPATH_LOCATIONS += $(ERF_LSM_MM5_DIR) +INCLUDE_LOCATIONS += $(ERF_LSM_MM5_DIR) + + # If using NOAH-MP model, then compile relevant source and headers ifeq ($(USE_NOAH), TRUE) ifneq ($(USE_NETCDF), TRUE) $(error USE_NETCDF must be true for using NOAH-MP interface) else - DEFINES += -DERF_USE_NOAH - includes += $(shell pkg-config --cflags netcdf-fortran) + DEFINES += -DERF_USE_NOAH + includes += $(shell pkg-config --cflags netcdf-fortran) LIBRARIES += $(shell pkg-config --libs netcdf-fortran) - NOAH_HOME ?= $(ERF_HOME)/Submodules/NOAH-MP - VPATH_LOCATIONS += $(NOAH_HOME)/drivers/hrldas + NOAH_HOME ?= $(ERF_HOME)/Submodules/NOAH-MP + VPATH_LOCATIONS += $(NOAH_HOME)/drivers/hrldas INCLUDE_LOCATIONS += $(NOAH_HOME)/drivers/hrldas - ERF_LSM_NOAH_DIR = $(ERF_SOURCE_DIR)/LandSurfaceModel/NOAH + ERF_LSM_NOAH_DIR = $(ERF_SOURCE_DIR)/LandSurfaceModel/NOAH include $(ERF_LSM_NOAH_DIR)/Make.package VPATH_LOCATIONS += $(ERF_LSM_NOAH_DIR) INCLUDE_LOCATIONS += $(ERF_LSM_NOAH_DIR) @@ -197,11 +203,38 @@ ifeq ($(USE_NOAH), TRUE) endif -ERF_LSM_MM5_DIR = $(ERF_SOURCE_DIR)/LandSurfaceModel/MM5 -include $(ERF_LSM_MM5_DIR)/Make.package -VPATH_LOCATIONS += $(ERF_LSM_MM5_DIR) -INCLUDE_LOCATIONS += $(ERF_LSM_MM5_DIR) - +# If using RRTMGP Radiation, compile relevant source and headers +#ifeq ($(USE_RRTMGP), TRUE) +# ifneq ($(USE_NETCDF), TRUE) +# $(error USE_NETCDF must be true for using RRTMGP interface) +# else ifneq ($(USE_YAKL), TRUE) +# $(error USE_YAKL must be true for using RRTMGP interface) +# else +# DEFINES += -DERF_USE_RRTMGP +# +# ERF_RAD_DIR = $(ERF_SOURCE_DIR)/Radiation +# include $(ERF_RAD_DIR)/Make.package +# VPATH_LOCATIONS += $(ERF_RAD_DIR) +# INCLUDE_LOCATIONS += $(ERF_RAD_DIR) +# +# RRTMGP_HOME ?= $(ERF_HOME)/Submodules/RRTMGP +# VPATH_LOCATIONS += $(RRTMGP_HOME)/cpp/rrtmgp +# VPATH_LOCATIONS += $(RRTMGP_HOME)/cpp/rrtmgp/kernels +# VPATH_LOCATIONS += $(RRTMGP_HOME)/cpp/rte +# VPATH_LOCATIONS += $(RRTMGP_HOME)/cpp/rte/kernels +# VPATH_LOCATIONS += $(RRTMGP_HOME)/cpp/examples +# VPATH_LOCATIONS += $(RRTMGP_HOME)/cpp/examples/all-sky +# VPATH_LOCATIONS += $(RRTMGP_HOME)/cpp/extensions/fluxes_byband +# INCLUDE_LOCATIONS += $(RRTMGP_HOME)/cpp/rrtmgp +# INCLUDE_LOCATIONS += $(RRTMGP_HOME)/cpp/rrtmgp/kernels +# INCLUDE_LOCATIONS += $(RRTMGP_HOME)/cpp/rte +# INCLUDE_LOCATIONS += $(RRTMGP_HOME)/cpp/rte/kernels +# INCLUDE_LOCATIONS += $(RRTMGP_HOME)/cpp/examples +# INCLUDE_LOCATIONS += $(RRTMGP_HOME)/cpp/examples/all-sky +# INCLUDE_LOCATIONS += $(RRTMGP_HOME)/cpp/extensions/cloud_optics +# endif +#endif + include $(AMREX_HOME)/Src/Base/Make.package AMReXdirs := Base Boundary AmrCore @@ -243,10 +276,10 @@ ifeq ($(USE_NETCDF), TRUE) DEFINES += -DERF_USE_NETCDF has_netcdf_mpi := $(shell pkg-config --cflags netcdf-mpi > /dev/null 2>&1; echo $$?) ifeq ($(has_netcdf_mpi),0) - includes += $(shell pkg-config --cflags netcdf-mpi) + includes += $(shell pkg-config --cflags netcdf-mpi) LIBRARIES += $(shell pkg-config --libs netcdf-mpi) else - includes += $(shell pkg-config --cflags netcdf) + includes += $(shell pkg-config --cflags netcdf) LIBRARIES += $(shell pkg-config --libs netcdf) endif endif diff --git a/Source/ERF.H b/Source/ERF.H index 03f55334c..be8cb3905 100644 --- a/Source/ERF.H +++ b/Source/ERF.H @@ -789,7 +789,7 @@ private: amrex::Vector> lsm_flux; // (lev,ncomp) Components: theta, q1, q2 #if defined(ERF_USE_RRTMGP) - Radiation rad; + amrex::Vector> rad; amrex::Vector> qheating_rates; // radiation heating rate source terms // Containers for additional SLM inputs diff --git a/Source/ERF.cpp b/Source/ERF.cpp index 6decc4a2c..25b981616 100644 --- a/Source/ERF.cpp +++ b/Source/ERF.cpp @@ -148,7 +148,6 @@ ERF::ERF_shared () m_terrain_drag.resize(nlevs_max); for (int lev = 0; lev < max_level; ++lev) { m_terrain_drag[lev] = nullptr;} - ReadParameters(); initializeMicrophysics(nlevs_max); @@ -156,6 +155,11 @@ ERF::ERF_shared () initializeWindFarm(nlevs_max); #endif +#ifdef ERF_USE_RRTMGP + rad.resize(nlevs_max); + for (int lev = 0; lev < max_level; ++lev) { rad[lev] = std::make_unique(solverChoice); } +#endif + const std::string& pv1 = "plot_vars_1"; setPlotVariables(pv1,plot_var_names_1); const std::string& pv2 = "plot_vars_2"; setPlotVariables(pv2,plot_var_names_2); @@ -1428,7 +1432,7 @@ ERF::ReadParameters () for (int lev = 1; lev <= max_level; lev++) { - fixed_dt[lev] = fixed_dt[lev-1] / static_cast(MaxRefRatio(lev-1)); + fixed_dt[lev] = fixed_dt[lev-1] / static_cast(MaxRefRatio(lev-1)); fixed_fast_dt[lev] = fixed_fast_dt[lev-1] / static_cast(MaxRefRatio(lev-1)); } diff --git a/Source/ERF_Config.H.in b/Source/ERF_Config.H.in deleted file mode 100644 index dd31d2780..000000000 --- a/Source/ERF_Config.H.in +++ /dev/null @@ -1,14 +0,0 @@ -#ifndef ERF_CONFIG_H_ -#define ERF_CONFIG_H_ - -// define the radiation data directory -#define RADIATION_DATA_DIR @ERF_RADIATION_DATA_DIR@ - -// functions to get the radiation data directory -inline -std::string getRadiationDataDir () -{ - return "@ERF_RADIATION_DATA_DIR@"; -} - -#endif // ERF_CONFIG_H diff --git a/Source/Radiation/ERF_AeroRadProps.H b/Source/Radiation/ERF_AeroRadProps.H deleted file mode 100644 index f6af4b542..000000000 --- a/Source/Radiation/ERF_AeroRadProps.H +++ /dev/null @@ -1,135 +0,0 @@ -// -// cloud radiation properties class -// -#ifndef ERF_AER_RAD_PROPS_H_ -#define ERF_AER_RAD_PROPS_H_ - -#include -#include "ERF_Mam4_aero.H" -#include "YAKL_netcdf.h" -#include "rrtmgp_const.h" -#include "ERF_Rad_constants.H" -#include "ERF_Mam4_constituents.H" - -// -// AerRadProps class converts the aerosol mass to bulk optical properties for short wave -// and long wave radiation computations -// -class AerRadProps { - public: - AerRadProps () = default; - ~AerRadProps () = default; - - // initialization - void initialize (int num_gas, int num_modes, int naeroes, - int nswbands_, int nlwbands_, - int ncoloum, int nlevel, int num_rh, int top_levels, - const std::vector& aerosol_names, - const real2d& zint, const real2d& pmiddle, const real2d& pdel, - const real2d& temperature, const real2d& qtotal, - const real2d& geom_rad); - - void aer_rad_props_sw (const int& list_idx, - const real& dt, - const int& nnite, - const int1d& idxnite, - const bool is_cmip6_volc, - const real3d& tau, - const real3d& tau_w, - const real3d& tau_w_g, - const real3d& tau_w_f, - const real2d& clear_rh); - - void aer_rad_props_lw (const bool& is_cmip6_volc, - const int& list_idx, - const real& dt, - const real2d& zi, - const real3d& odap_aer, - const real2d& clear_rh); - - - void get_hygro_rad_props (const int& ncol, - const int2d& krh, - const real2d& wrh, - const real2d& mass, - const real2d& ext, - const real2d& ssa, - const real2d& assm, - const real3d& tau, - const real3d& tau_w, - const real3d& tau_w_g, - const real3d& tau_w_f); - - void get_nonhygro_rad_props (const int& ncol, - const real2d& mass, - const real1d& ext, - const real1d& ssa, - const real1d& assm, - const real3d& tau, - const real3d& tau_w, - const real3d& tau_w_g, - const real3d& tau_w_f); - - void get_volcanic_radius_rad_props (const int& ncol, - const real2d& mass, - const real2d& r_ext, - const real2d& r_scat, - const real2d& r_ascat, - const real1d& r_mu, - const real3d& tau, - const real3d& tau_w, - const real3d& tau_w_g, - const real3d& tau_w_f); - - void volcanic_cmip_sw (const int1d& trop_level, - const real2d& zi, // zone interface - const real3d& ext_cmip6_sw_inv_m, - const real3d& ssa_cmip6_sw, // ssa from the volcanic inputs - const real3d& af_cmip6_sw, // asymmetry factor (af) from volcanic inputs - const real3d& tau, - const real3d& tau_w, - const real3d& tau_w_g, - const real3d& tau_w_f); - - void get_volcanic_rad_props (const int& ncol, - const real2d& mass, - const real1d& ext, - const real1d& scat, - const real1d& ascat, - const real3d& tau, - const real3d& tau_w, - const real3d& tau_w_g, - const real3d& tau_w_f); - - void aer_vis_diag_out (const int& ncol, - const int& nnite, - const int1d& idxnite, - const int& iaer, - const real2d& tau, - const int& diag_idx); - - private: - int nmodes, ngas, num_aeroes; - std::vector aero_names; - - int nswbands, nlwbands; - int ncol, nlev, nrh, top_lev; - - // aero constituents - MamConstituents mam_consti; - - // aero - Mam4_aer mam_aer; - - // pdel dry - real2d pdeldry; - // pmid - real2d pmid, temp, qt; - // geometric radius - real2d geometric_radius; - // vertical grid - real2d zi; - // cmip6 short wave - real3d ext_cmip6_sw, ssa_cmip6_sw, af_cmip6_sw; -}; -#endif diff --git a/Source/Radiation/ERF_AeroRadProps.cpp b/Source/Radiation/ERF_AeroRadProps.cpp deleted file mode 100644 index 01139c6ee..000000000 --- a/Source/Radiation/ERF_AeroRadProps.cpp +++ /dev/null @@ -1,701 +0,0 @@ -// -// get the cloud radiation props data -// -#include - -#include "ERF_Constants.H" -#include "ERF_Aero_rad_props.H" -#include "ERF_Water_vapor_saturation.H" -using yakl::fortran::parallel_for; -using yakl::fortran::SimpleBounds; - -void AerRadProps::initialize (int num_gas, int num_modes, int naeroes, - int nswbands_, int nlwbands_, - int ncoloum, int nlevel, int num_rh, int top_levels, - const std::vector& aerosol_names, - const real2d& zint, const real2d& pmiddle, const real2d& pdel, - const real2d& temperature, const real2d& qtotal, - const real2d& geom_rad) -{ - nmodes = num_modes; - ngas = num_gas; - num_aeroes = naeroes; - aero_names = aerosol_names; - - nswbands = nswbands_; - nlwbands = nlwbands_; - ncol = ncoloum; - nlev = nlevel; - nrh = num_rh; - top_lev = top_levels; - - // NOTE: pmid is absolute pressure but pdeldry is the vertical - // change in pressure (analog to mass per area with HSE balance) - pmid = pmiddle; - pdeldry = pdel; - /* - // This will overwrite pmid - pdeldry = pmiddle; - parallel_for(SimpleBounds<2>(ncol, nlev), YAKL_LAMBDA (int icol, int ilev) - { - // Pressure max at bottom of column - pdeldry(icol,ilev) = pmid(icol,ilev) - pmid(icol,ilev+1); - }); - */ - - temp = temperature; - qt = qtotal; - // geometric radius - geometric_radius = geom_rad; - // vertical grid - zi = zint; - // initialize mam4 aero model - mam_aer.initialize(ncol, nlev, top_lev, nswbands, nlwbands); -} - -void AerRadProps::aer_rad_props_sw (const int& list_idx, const real& dt, const int& nnite, - const int1d& idxnite, const bool is_cmip6_volc, const real3d& tau, const real3d& tau_w, - const real3d& tau_w_g, const real3d& tau_w_f, const real2d& clear_rh) -{ - // for cmip6 style volcanic file - int1d trop_level("trop_level", ncol); - real3d ext_cmip6_sw_inv_m("ext_cmip6_sw_inv_m", ncol, nlev, nswbands); // short wave extinction in the units of 1/m - - // compute mixing ratio to mass conversion - real2d mmr_to_mass("mmr_to_mass", ncol, nlev); // conversion factor for mmr to mass - parallel_for(SimpleBounds<2>(ncol, nlev) , YAKL_LAMBDA (int icol, int ilev) - { - mmr_to_mass(icol,ilev) = rga * pdeldry(icol,ilev); - }); - - // initialize to conditions that would cause failure - yakl::memset(tau , -100.); - yakl::memset(tau_w , -100.); - yakl::memset(tau_w_g, -100.); - yakl::memset(tau_w_f, -100.); - yakl::memset(ext_cmip6_sw_inv_m, 1.0e-3); - - // top layer (ilev = 0) has no aerosol (ie tau = 0) - // also initialize rest of layers to accumulate od's - parallel_for(SimpleBounds<2>(ncol, nswbands) , YAKL_LAMBDA (int icol, int isw) - { - tau (icol,1,isw) = 0.; - tau_w (icol,1,isw) = 0.; - tau_w_g(icol,1,isw) = 0.; - tau_w_f(icol,1,isw) = 0.; - }); - - // local variables - real2d wrh("wrh", ncol, nlev); - int2d krh("krh", ncol, nlev); - // calculate relative humidity for table lookup into rh grid - parallel_for(SimpleBounds<2>(ncol, nlev), YAKL_LAMBDA (int icol, int ilev) - { - real es, qs, rh, rhtrunc; - WaterVaporSat::qsat(temp(icol,ilev), pmid(icol,ilev), es, qs); - rh = qt(icol,ilev)/qs; - rhtrunc = std::min(rh,1.); - krh(icol, ilev) = std::min(std::floor(rhtrunc*nrh )+1., nrh - 1.); // index into rh mesh - wrh(icol, ilev) = rhtrunc*nrh-krh(icol, ilev); // (-) weighting on left side values - }); - - // Special treatment for CMIP6 volcanic aerosols, where extinction, ssa - // and af are directly read from the prescribed volcanic aerosol file - - // Point ext_cmip6_sw to null so that the code breaks if they are used for is_cmip6_volc=.false. case - // This is done to avoid having optional arguments in modal_aero_sw call - yakl::memset(trop_level, 10000); - - // I was thinking whether we have to include volcanic effects on radiation? (xyuan) - if (is_cmip6_volc) { - // get extinction so as to supply to modal_aero_sw routine for computing EXTINCT variable - // converting it from 1/km to 1/m - // call pbuf_get_field(pbuf, idx_ext_sw, ext_cmip6_sw) - // call outfld('extinct_sw_inp',ext_cmip6_sw(:,:,idx_sw_diag), pcols, lchnk) - parallel_for(SimpleBounds<3>(ncol, nlev, nswbands), YAKL_LAMBDA (int icol, int ilev, int isw) - { - ext_cmip6_sw_inv_m(icol, ilev, isw) = 1.0e-3; //*ext_cmip6_sw(icol, ilev, isw); //convert from 1/km to 1/m - }); - - //Find tropopause as extinction should be applied only above tropopause - //trop_level has value for tropopause for each column - //tropopause_find(state, trop_level) - // Iterate over all of the columns to find the troppause - real1d strop("strop",1); - parallel_for(SimpleBounds<1>(ncol), YAKL_LAMBDA (int i) - { - // Search from the bottom up, looking for the first minimum. - int tlev = -1; - for (auto k = nlev-1; k > 0; --k) { - real stobie = 0.03 * temp(i,k) - std::log10(pmid(i, k)); - if (pmid(i, k) <= 4000.) break; - if (pmid(i, k) >= 55000.) continue; - if ((tlev == -1) || (stobie < strop(1))) { - tlev = k; - strop(1) = stobie; - } - } - if (tlev != -1) trop_level(i) = tlev; - }); - - //Quit if tropopause is not found - if (yakl::intrinsics::any(yakl::componentwise::operator==(trop_level, -1))) { - amrex::Print() << "aer_rad_props.F90: subr aer_rad_props_sw: tropopause not found\n"; - } - } - - // get number of bulk aerosols and number of modes in current list - mam_consti.get_nmodes(list_idx, nmodes); - mam_consti.get_ngas(list_idx, ngas); - mam_consti.get_naero(list_idx, num_aeroes); - - // Contributions from modal aerosols. - if (nmodes > 0) { - real2d ext_cmip6_sw_2d("ext_cmip6_sw",ncol,nlev); - parallel_for(SimpleBounds<3>(ncol, nlev, nswbands), YAKL_LAMBDA (int icol, int ilev, int isw) - { - ext_cmip6_sw_2d(icol,ilev) = ext_cmip6_sw_inv_m(icol,ilev,RadConstants::idx_sw_diag); - }); - - mam_aer.modal_aero_sw(list_idx, dt, nnite, idxnite, is_cmip6_volc, - pdeldry, pmid, temp, qt, ext_cmip6_sw_2d, - trop_level, tau, tau_w, tau_w_g, tau_w_f, clear_rh); - } else { - yakl::memset(tau, 0.); - yakl::memset(tau_w, 0.); - yakl::memset(tau_w_g, 0.); - yakl::memset(tau_w_f, 0.); - } - -#if 1 - if (is_cmip6_volc) { - //update tau, tau_w, tau_w_g, and tau_w_f with the read in values of extinction, ssa and asymmetry factors - volcanic_cmip_sw (trop_level, zi, ext_cmip6_sw_inv_m, ssa_cmip6_sw, af_cmip6_sw, - tau, tau_w, tau_w_g, tau_w_f); - } - - // Contributions from bulk aerosols. - for (auto iaero = 0; iaero < num_aeroes; ++iaero) { - // aerosol masses - real2d aermmr("aermmr", ncol, nlev); // mass mixing ratio of aerosols - real2d aermass("aermass", ncol, nlev); // mass of aerosols - - yakl::memset(aermmr, 1.0e-2); - // get bulk aerosol mass mixing ratio - // mam_consti.rad_cnst_get_aer_mmr(list_idx, iaero, aermmr); - parallel_for(SimpleBounds<2>(ncol, nlev), YAKL_LAMBDA (int icol, int ilev) - { - if (ilev < top_lev) { - aermass(icol,ilev) = 0.; - } else { - aermass(icol,ilev) = aermmr(icol,ilev) * mmr_to_mass(icol,ilev); - } - }); - - std::string opticstype; // hygro or nonhygro - // get optics type - mam_consti.get_aer_opticstype(list_idx, iaero, opticstype); - - // radiative properties for each aerosol - real3d ta("ta", ncol, nlev, nswbands); - real3d tw("tw", ncol, nlev, nswbands); - real3d twf("twf", ncol, nlev, nswbands); - real3d twg("twg", ncol, nlev, nswbands); - - if (opticstype == "hygro") { - // get optical properties for hygroscopic aerosols - // optical props for each aerosol hygroscopic - real2d h_ext("h_ext", ncol, nlev); - real2d h_ssa("h_ssa", ncol, nlev); - real2d h_asm("h_asm", ncol, nlev); - mam_consti.get_aer_sw_hygro_ext(list_idx, iaero, h_ext); - mam_consti.get_aer_sw_hygro_ssa(list_idx, iaero, h_ssa); - mam_consti.get_aer_sw_hygro_asm(list_idx, iaero, h_asm); - get_hygro_rad_props(ncol, krh, wrh, aermass, h_ext, h_ssa, h_asm, ta, tw, twg, twf); - parallel_for(SimpleBounds<3>(ncol, nlev, nswbands), YAKL_LAMBDA (int icol, int ilev, int isw) - { - tau (icol,ilev,isw) = tau (icol,ilev,isw) + ta (icol,ilev,isw); - tau_w (icol,ilev,isw) = tau_w (icol,ilev,isw) + tw (icol,ilev,isw); - tau_w_g(icol,ilev,isw) = tau_w_g(icol,ilev,isw) + twg(icol,ilev,isw); - tau_w_f(icol,ilev,isw) = tau_w_f(icol,ilev,isw) + twf(icol,ilev,isw); - }); - } else if (opticstype == "nonhygro") { - // get optical properties for non-hygroscopic aerosols - // non-hygroscopic - real1d n_ext("n_ext", ncol); - real1d n_ssa("n_ssa", ncol); - real1d n_asm("n_asm", ncol); - mam_consti.get_aer_sw_nonhygro_ext(list_idx, iaero, n_ext); - mam_consti.get_aer_sw_nonhygro_ssa(list_idx, iaero, n_ssa); - mam_consti.get_aer_sw_nonhygro_asm(list_idx, iaero, n_asm); - get_nonhygro_rad_props(ncol, aermass, n_ext, n_ssa, n_asm, ta, tw, twg, twf); - parallel_for(SimpleBounds<3>(ncol, nlev, nswbands), YAKL_LAMBDA (int icol, int ilev, int isw) - { - tau (icol,ilev,isw) = tau (icol,ilev,isw) + ta (icol,ilev,isw); - tau_w (icol,ilev,isw) = tau_w (icol,ilev,isw) + tw (icol,ilev,isw); - tau_w_g(icol,ilev,isw) = tau_w_g(icol,ilev,isw) + twg(icol,ilev,isw); - tau_w_f(icol,ilev,isw) = tau_w_f(icol,ilev,isw) + twf(icol,ilev,isw); - }); - } else if (opticstype == "volcanic") { - // get optical properties for volcanic aerosols - real1d n_ext("n_ext", ncol); - real1d n_scat("n_scat", ncol); - real1d n_ascat("n_ascat", ncol); - mam_consti.get_aer_sw_nonhygro_ext(list_idx, iaero, n_ext); - mam_consti.get_aer_sw_nonhygro_scat(list_idx, iaero, n_scat); - mam_consti.get_aer_sw_nonhygro_ascat(list_idx, iaero, n_ascat); - get_volcanic_rad_props(ncol, aermass, n_ext, n_scat, n_ascat, ta, tw, twg, twf); - parallel_for(SimpleBounds<3>(ncol, nlev, nswbands), YAKL_LAMBDA (int icol, int ilev, int isw) - { - tau (icol,ilev,isw) = tau (icol,ilev,isw) + ta (icol,ilev,isw); - tau_w (icol,ilev,isw) = tau_w (icol,ilev,isw) + tw (icol,ilev,isw); - tau_w_g(icol,ilev,isw) = tau_w_g(icol,ilev,isw) + twg(icol,ilev,isw); - tau_w_f(icol,ilev,isw) = tau_w_f(icol,ilev,isw) + twf(icol,ilev,isw); - }); - } else if (opticstype == "volcanic_radius") { - // get optical properties for volcanic aerosols - real2d r_ext("r_ext",ncol,nlev); - real2d r_scat("r_scat",ncol,nlev); - real2d r_ascat("r_ascat",ncol,nlev); - real1d r_mu("r_mu", ncol); - mam_consti.get_aer_r_sw_ext(list_idx, iaero, r_ext); - mam_consti.get_aer_r_sw_scat(list_idx, iaero, r_scat); - mam_consti.get_aer_r_sw_ascat(list_idx, iaero, r_ascat); - mam_consti.get_aer_mu(list_idx, iaero, r_mu); - get_volcanic_radius_rad_props(ncol, aermass, r_ext, r_scat, r_ascat, r_mu, ta, tw, twg, twf); - //get_volcanic_radius_rad_props(const real2d& mass, - // const real2d& r_ext, - // const real2d& r_scat, - // const real2d& r_ascat, - // const real1d& r_mu, - // real3d& tau, - // real3d& tau_w, - // real3d& tau_w_g, - // real3d& tau_w_f) - - parallel_for(SimpleBounds<3>(ncol, nlev, nswbands), YAKL_LAMBDA (int icol, int ilev, int isw) - { - tau (icol,ilev,isw) = tau (icol,ilev,isw) + ta (icol,ilev,isw); - tau_w (icol,ilev,isw) = tau_w (icol,ilev,isw) + tw (icol,ilev,isw); - tau_w_g(icol,ilev,isw) = tau_w_g(icol,ilev,isw) + twg(icol,ilev,isw); - tau_w_f(icol,ilev,isw) = tau_w_f(icol,ilev,isw) + twf(icol,ilev,isw); - }); - } else { - amrex::Print() << "aer_rad_props_sw: unsupported opticstype\n"; - } - - // diagnostic output of individual aerosol optical properties - // currently implemented for climate list only - // call aer_vis_diag_out(lchnk, ncol, nnite, idxnite, iaero, ta(:,:,idx_sw_diag), list_idx) - } - - // diagnostic output of total aerosol optical properties - // currently implemented for climate list only - // call aer_vis_diag_out(lchnk, ncol, nnite, idxnite, 0, tau(:,:,idx_sw_diag), list_idx) -#endif -} - -// Purpose: Compute aerosol transmissions needed in absorptivity/ -// emissivity calculations -// -// lw extinction is the same representation for all -// species. If this changes, this routine will need to do something -// similar to the sw with routines like get_hygro_lw_abs -void AerRadProps::aer_rad_props_lw (const bool& is_cmip6_volc, - const int& list_idx, - const real& dt, - const real2d& zi, - const real3d& odap_aer, - const real2d& clear_rh) -{ - int numaerosols; // number of bulk aerosols in climate/diagnostic list - int nmodes; // number of aerosol modes in climate/diagnostic list - std::string opticstype; // hygro or nonhygro - - // optical props for each aerosol - real1d lw_abs("lw_abs", ncol); - real2d lw_hygro_abs("lw_hygro", ncol, nlev); - real2d geometric_radius("geometric_radius", ncol, nlev); - - // volcanic lookup table - real2d r_lw_abs("r_lw_abs", ncol, nlev); // radius dependent mass-specific absorption coefficient - real1d r_mu("r_mu", ncol); // log(geometric_mean_radius) domain samples of r_lw_abs(:,:) - real2d mu("mu", ncol, nlev); // log(geometric_radius) - //real r_mu_min, r_mu_max, wmu, mutrunc; - //int nmu, kmu; - - // for table lookup into rh grid - real2d es("es", ncol, nlev); // saturation vapor pressure - real2d qs("qs", ncol, nlev); // saturation specific humidity - real2d rh("rh", ncol, nlev); - real2d rhtrunc("rhtrunc", ncol, nlev); - real2d wrh("wrh", ncol, nlev); - int2d krh("krh", ncol, nlev); - - // aerosol (vertical) mass path and extinction aerosol masses - real2d aermmr("aermmr", ncol, nlev); // mass mixing ratio of aerosols - real2d mmr_to_mass("mmr_to_mass", ncol, nlev); // conversion factor for mmr to mass - real2d aermass("aermass", ncol, nlev); // mass of aerosols - - //For cmip6 volcanic file - int1d trop_level("trop_level", ncol); - //real lyr_thk; - real3d ext_cmip6_lw("ext_cmip6_lw", ncol, nlev, nlwbands); - real3d ext_cmip6_lw_inv_m("ext_cmip6_lw_inv_m",ncol, nlev, nlwbands); //long wave extinction in the units of 1/m - - const real km_inv_to_m_inv = 0.001; - - // get number of bulk aerosols and number of modes in current list - mam_consti.get_nmodes(list_idx, nmodes); - mam_consti.get_naero(list_idx, numaerosols); - - // Contributions from modal aerosols. - if (nmodes > 0) { - mam_aer.modal_aero_lw(list_idx, dt, pdeldry, pmid, temp, qt, odap_aer,clear_rh); - } else { - yakl::memset(odap_aer, 0.); - } - - // Contributions from bulk aerosols. - if (numaerosols > 0) { - - // compute mixing ratio to mass conversion - parallel_for(SimpleBounds<2>(ncol, nlev), YAKL_LAMBDA (int icol, int ilev) - { - mmr_to_mass(icol,ilev) = rga * pdeldry(icol,ilev); - }); - - // calculate relative humidity for table lookup into rh grid - parallel_for(SimpleBounds<2>(ncol, nlev), YAKL_LAMBDA (int icol, int ilev) - { - WaterVaporSat::qsat(temp(icol,ilev), pmid(icol,ilev), es(icol,ilev), qs(icol,ilev)); - rh(icol,ilev) = qt(icol,ilev) / qs(icol,ilev); - - rhtrunc(icol, ilev) = std::min(rh(icol, ilev),1.); - krh(icol, ilev) = std::min(std::floor(rhtrunc(icol, ilev) * nrh ) + 1, nrh - 1.); // index into rh mesh - wrh(icol, ilev) = rhtrunc(icol, ilev) * nrh - krh(icol, ilev); // (-) weighting on left side values - }); - } - - yakl::memset(ext_cmip6_lw, 0.); - if(is_cmip6_volc) { - // Logic: - // Update odap_aer with the read in volcanic aerosol extinction (1/km). - // It needs to be converted to 1/m and multiplied by layer thickness first. - - // Obtain read in values for ext from the volcanic input file - // call pbuf_get_field(pbuf, idx_ext_lw, ext_cmip6_lw) - // call outfld('extinct_lw_inp',ext_cmip6_lw(:,:,idx_lw_diag), pcols, lchnk) - // ext_cmip6_lw_inv_m = ext_cmip6_lw * km_inv_to_m_inv !convert from 1/km to 1/m - // - // Above the tropopause, the read in values from the file include both the stratospheric - // and volcanic aerosols. Therefore, we need to zero out odap_aer above the tropopause - // and populate it exclusively from the read in values. - - // Find tropopause - // trop_level has value for tropopause for each column - // tropopause_find(state, trop_level) - real1d strop("strop",1); - parallel_for(SimpleBounds<1>(ncol), YAKL_LAMBDA (int i) - { - // Search from the bottom up, looking for the first minimum. - int tlev = -1; - for (auto k = nlev-1; k > 0; --k) { - real stobie = 0.03 * temp(i,k) - std::log10(pmid(i, k)); - if (pmid(i, k) <= 4000.) break; - if (pmid(i, k) >= 55000.) continue; - if ((tlev == -1) || (stobie < strop(0))) { - tlev = k; - strop(0) = stobie; - } - } - if (tlev != -1) trop_level(i) = tlev; - }); - - // Quit if tropopause is not found - if (yakl::intrinsics::any(yakl::componentwise::operator==(trop_level, -1))) - amrex::Print() << "aer_rad_props_lw: tropopause not found\n"; - - // If tropopause is found, update taus with 50% contributuions from the volcanic input - // file and 50% from the existing model computed values - // First handle the case of tropopause layer itself: - parallel_for(SimpleBounds<2>(ncol, nlwbands), YAKL_LAMBDA (int icol, int ilw) - { - int ilev_tropp = trop_level(icol); //tropopause level - auto lyr_thk = zi(icol,ilev_tropp) - zi(icol,ilev_tropp+1); //in meters - auto ext_cmip6_inv_m = km_inv_to_m_inv * ext_cmip6_lw_inv_m(icol,ilev_tropp,ilw); - odap_aer(icol,ilev_tropp,ilw) = 0.5 * (odap_aer(icol,ilev_tropp,ilw) + lyr_thk * ext_cmip6_inv_m); - }); - - // As it will be more efficient for FORTRAN to loop over levels and then columns, the following loops - // are nested keeping that in mind - parallel_for(SimpleBounds<3>(ncol, nlev, nlwbands), YAKL_LAMBDA (int icol, int ilev, int ilw) - { - int ilev_tropp = trop_level(icol); //tropopause level - if (ilev < ilev_tropp) { - //auto lyr_thk = zi(icol,ilev) - zi(icol,ilev+1); - // odap_aer(icol,ilev,ilw) = lyr_thk * ext_cmip6_lw_inv_m(icol,ilev,ilw); - } - }); - } - - - // Loop over bulk aerosols in list. - for (auto iaerosol = 0; iaerosol < numaerosols; ++iaerosol) { - - // get aerosol mass mixing ratio - // mam_consti.rad_cnst_get_aer_mmr(list_idx, iaerosol, aermmr); - parallel_for(SimpleBounds<2>(ncol, nlev), YAKL_LAMBDA (int icol, int ilev) - { - if (ilev < top_lev) { - aermass(icol,ilev) = 0.; - } else { - aermass(icol,ilev) = aermmr(icol,ilev) * mmr_to_mass(icol,ilev); - } - }); - - // get optics type - mam_consti.get_aer_opticstype(list_idx, iaerosol, opticstype); - - if (opticstype == "hygroscopic") { - // get optical properties for hygroscopic aerosols - mam_consti.get_aer_lw_hygro_abs(list_idx, iaerosol, lw_hygro_abs); - parallel_for(SimpleBounds<3>(ncol, nlev, nlwbands), YAKL_LAMBDA (int icol, int ilev, int bnd_idx) - { - odap_aer(icol, ilev, bnd_idx) = odap_aer(icol, ilev, bnd_idx) + aermass(icol, ilev) * - ((1 + wrh(icol,ilev)) * lw_hygro_abs(krh(icol, ilev)+1,bnd_idx) - - wrh(icol, ilev) * lw_hygro_abs(krh(icol, ilev), bnd_idx)); - }); - } else if (opticstype == "insoluble" || - opticstype == "nonhygro" || - opticstype == "hygro" || - opticstype == "volcanic") { - // get optical properties for hygroscopic aerosols - mam_consti.get_aer_lw_abs(list_idx, iaerosol, lw_abs); - parallel_for(SimpleBounds<3>(ncol, nlev, nlwbands), YAKL_LAMBDA (int icol, int ilev, int bnd_idx) - { - odap_aer(icol,ilev,bnd_idx) = odap_aer(icol,ilev,bnd_idx) + lw_abs(bnd_idx)*aermass(icol,ilev); - }); - } else if (opticstype == "volcanic_radius") { - // get optical properties for hygroscopic aerosols - mam_consti.get_aer_r_lw_abs(list_idx, iaerosol, r_lw_abs); - mam_consti.get_aer_mu(list_idx, iaerosol, r_mu); - // get microphysical properties for volcanic aerosols - // idx = pbuf_get_index('VOLC_RAD_GEOM') - // call pbuf_get_field(pbuf, idx, geometric_radius ) - - // interpolate in radius - // caution: clip the table with no warning when outside bounds - parallel_for(SimpleBounds<2>(ncol, nlev), YAKL_LAMBDA (int icol, int ilev) - { - auto nmu = r_mu.extent(0); - auto r_mu_max = r_mu(nmu); - auto r_mu_min = r_mu(1); - - if(geometric_radius(icol,ilev) > 0.) { - mu(icol,ilev) = std::log(geometric_radius(icol,ilev)); - } else { - mu(icol,ilev) = 0.; - } - auto mutrunc = std::max(std::min(mu(icol,ilev),r_mu_max),r_mu_min); - auto kmu = std::max(std::min(1 + (mutrunc-r_mu_min)/(r_mu_max-r_mu_min)*(nmu-1),nmu-1.),1.); - auto wmu = std::max(std::min( (mutrunc -r_mu(kmu)) / (r_mu(kmu+1) - r_mu(kmu)) ,1.),0.); - for (auto bnd_idx = 0; bnd_idx < nlwbands; ++bnd_idx) { - odap_aer(icol,ilev,bnd_idx) = odap_aer(icol,ilev,bnd_idx) + - aermass(icol,ilev) * ((1. - wmu) * r_lw_abs(bnd_idx, kmu) + - (wmu) * r_lw_abs(bnd_idx, kmu+1)); - } - }); - } - } -} - - void AerRadProps::get_hygro_rad_props (const int& ncol, - const int2d& krh, - const real2d& wrh, - const real2d& mass, - const real2d& extb, - const real2d& ssab, - const real2d& asmb, - const real3d& tau, - const real3d& tau_w, - const real3d& tau_w_g, - const real3d& tau_w_f) -{ - parallel_for(SimpleBounds<3>(ncol,nlev,nswbands) , YAKL_LAMBDA (int icol, int ilev, int iswband) - { - auto ext1 = (1 + wrh(icol,ilev)) * extb(krh(icol,ilev)+1,iswband) - wrh(icol,ilev) * extb(krh(icol,ilev),iswband); - auto ssa1 = (1 + wrh(icol,ilev)) * ssab(krh(icol,ilev)+1,iswband) - wrh(icol,ilev) * ssab(krh(icol,ilev),iswband); - auto asm1 = (1 + wrh(icol,ilev)) * asmb(krh(icol,ilev)+1,iswband) - wrh(icol,ilev) * asmb(krh(icol,ilev),iswband); - - tau (icol, ilev, iswband) = mass(icol, ilev) * ext1; - tau_w (icol, ilev, iswband) = mass(icol, ilev) * ext1 * ssa1; - tau_w_g(icol, ilev, iswband) = mass(icol, ilev) * ext1 * ssa1 * asm1; - tau_w_f(icol, ilev, iswband) = mass(icol, ilev) * ext1 * ssa1 * asm1 * asm1; - }); -} - -void AerRadProps::get_nonhygro_rad_props (const int& ncol, - const real2d& mass, - const real1d& extb, - const real1d& ssab, - const real1d& asmb, - const real3d& tau, - const real3d& tau_w, - const real3d& tau_w_g, - const real3d& tau_w_f) -{ - parallel_for(SimpleBounds<3>(ncol,nlev,nswbands) , YAKL_LAMBDA (int icol, int ilev, int iswband) - { - auto ext1 = extb(iswband); - auto ssa1 = ssab(iswband); - auto asm1 = asmb(iswband); - tau (icol,ilev,iswband) = mass(icol,ilev) * ext1; - tau_w (icol,ilev,iswband) = mass(icol,ilev) * ext1 * ssa1; - tau_w_g(icol,ilev,iswband) = mass(icol,ilev) * ext1 * ssa1 * asm1; - tau_w_f(icol,ilev,iswband) = mass(icol,ilev) * ext1 * ssa1 * asm1 * asm1; - }); -} - -void AerRadProps::get_volcanic_radius_rad_props (const int& ncol, - const real2d& mass, - const real2d& r_ext, - const real2d& r_scat, - const real2d& r_ascat, - const real1d& r_mu, - const real3d& tau, - const real3d& tau_w, - const real3d& tau_w_g, - const real3d& tau_w_f) -{ - yakl::memset(tau, 0.); - yakl::memset(tau_w, 0.); - yakl::memset(tau_w_g, 0.); - yakl::memset(tau_w_f, 0.); - - // get microphysical properties for volcanic aerosols - //idx = pbuf_get_index('VOLC_RAD_GEOM') - //call pbuf_get_field(pbuf, idx, geometric_radius ) - - // interpolate in radius - // caution: clip the table with no warning when outside bounds - auto nmu = r_mu.extent(0); - auto r_mu_max = r_mu(nmu-1); - auto r_mu_min = r_mu(0); - - // interpolated values from table - real1d ext("ext", nswbands); - real1d scat("scat", nswbands); - real1d ascat("ascat", nswbands); - real2d mu("mu", ncol, nlev); - - parallel_for(SimpleBounds<3>(ncol,nlev,nswbands), YAKL_LAMBDA (int icol, int ilev, int iswband) - { - if(geometric_radius(icol,ilev) > 0.) - mu(icol,ilev) = std::log(geometric_radius(icol,ilev)); - else - mu(icol,ilev) = 0.; - - auto mutrunc = std::max(std::min(mu(icol,ilev),r_mu_max),r_mu_min); - auto kmu = std::max(std::min(1 + (mutrunc-r_mu_min)/(r_mu_max-r_mu_min)*(nmu-1),nmu-1.),1.); - auto wmu = std::max(std::min((mutrunc -r_mu(kmu)) / (r_mu(kmu+1) - r_mu(kmu)),1.),0.); - - ext(iswband) = ((1. - wmu) * r_ext(iswband, kmu ) + - (wmu) * r_ext(iswband, kmu+1)); - scat(iswband) = ((1. - wmu) * r_scat(iswband, kmu ) + - (wmu) * r_scat(iswband, kmu+1)); - ascat(iswband) = ((1. - wmu) * r_ascat(iswband, kmu ) + - (wmu) * r_ascat(iswband, kmu+1)); - real g = 0; - if (scat(iswband) > 0.) - g = ascat(iswband)/scat(iswband); - - tau (icol,ilev,iswband) = mass(icol,ilev) * ext(iswband); - tau_w (icol,ilev,iswband) = mass(icol,ilev) * scat(iswband); - tau_w_g(icol,ilev,iswband) = mass(icol,ilev) * ascat(iswband); - tau_w_f(icol,ilev,iswband) = mass(icol,ilev) * g * ascat(iswband); - }); -} - - // Logic: - // Update taus, tau_w, tau_w_g and tau_w_f with the read in volcanic - // aerosol extinction (1/km), single scattering albedo and asymmtry factors. - - void AerRadProps::volcanic_cmip_sw (const int1d& trop_level, - const real2d& zi, // zone interface - const real3d& ext_cmip6_sw_inv_m, - const real3d& ssa_cmip6_sw, // ssa from the volcanic inputs - const real3d& af_cmip6_sw, // asymmetry factor (af) from volcanic inputs - const real3d& tau, - const real3d& tau_w, - const real3d& tau_w_g, - const real3d& tau_w_f) { - // Above the tropopause, the read in values from the file include both the stratospheric - // and volcanic aerosols. Therefore, we need to zero out taus above the tropopause - // and populate them exclusively from the read in values. - - // If tropopause is found, update taus with 50% contributuions from the volcanic input - // file and 50% from the existing model computed values - real1d ext_unitless("ext_unitless", nswbands), - asym_unitless("asym_unitless",nswbands), - ext_ssa("ext_ssa",nswbands), - ext_ssa_asym("ext_ssa_asym",nswbands); - - // First handle the case of tropopause layer itself: - parallel_for(SimpleBounds<2>(ncol,nswbands), YAKL_LAMBDA (int icol, int isw) { - auto ilev_tropp = trop_level(icol); //tropopause level - auto lyr_thk = zi(icol,ilev_tropp) - zi(icol,ilev_tropp+1); - - ext_unitless(isw) = lyr_thk * ext_cmip6_sw_inv_m(icol,ilev_tropp,isw); - asym_unitless(isw) = af_cmip6_sw (icol,ilev_tropp,isw); - ext_ssa(isw) = ext_unitless(isw) * ssa_cmip6_sw(icol,ilev_tropp,isw); - ext_ssa_asym(isw) = ext_ssa(isw) * asym_unitless(isw); - - tau (icol,ilev_tropp,isw) = 0.5 * ( tau (icol,ilev_tropp,isw) + ext_unitless(isw) ); - tau_w (icol,ilev_tropp,isw) = 0.5 * ( tau_w (icol,ilev_tropp,isw) + ext_ssa(isw)); - tau_w_g(icol,ilev_tropp,isw) = 0.5 * ( tau_w_g(icol,ilev_tropp,isw) + ext_ssa_asym(isw)); - tau_w_f(icol,ilev_tropp,isw) = 0.5 * ( tau_w_f(icol,ilev_tropp,isw) + ext_ssa_asym(isw) * asym_unitless(isw)); - }); - - // As it will be more efficient for FORTRAN to loop over levels and then columns, the following loops - // are nested keeping that in mind - parallel_for(SimpleBounds<3>(ncol,nlev, nswbands), YAKL_LAMBDA (int icol, int ilev, int isw) { - auto ilev_tropp = trop_level(icol); //tropopause level - - if (ilev < ilev_tropp) { //BALLI: see if this is right! - auto lyr_thk = zi(icol,ilev) - zi(icol,ilev+1); - - ext_unitless(isw) = lyr_thk * ext_cmip6_sw_inv_m(icol,ilev,isw); - asym_unitless(isw) = af_cmip6_sw(icol,ilev,isw); - ext_ssa(isw) = ext_unitless(isw) * ssa_cmip6_sw(icol,ilev,isw); - ext_ssa_asym(isw) = ext_ssa(isw) * asym_unitless(isw); - - tau (icol,ilev,isw) = ext_unitless(isw); - tau_w (icol,ilev,isw) = ext_ssa(isw); - tau_w_g(icol,ilev,isw) = ext_ssa_asym(isw); - tau_w_f(icol,ilev,isw) = ext_ssa_asym(isw) * asym_unitless(isw); - } - }); -} - -void AerRadProps::get_volcanic_rad_props(const int& ncol, - const real2d& mass, - const real1d& ext, - const real1d& scat, - const real1d& ascat, - const real3d& tau, - const real3d& tau_w, - const real3d& tau_w_g, - const real3d& tau_w_f) { - //int nswbands; - parallel_for(SimpleBounds<3>(nswbands, ncol,nlev), YAKL_LAMBDA (int iswband, int icol, int ilev) { - real g = 0; - if (scat(iswband) > 0.) - g = ascat(iswband)/scat(iswband); - - tau (icol, ilev, iswband) = mass(icol, ilev) * ext(iswband); - tau_w (icol, ilev, iswband) = mass(icol, ilev) * scat(iswband); - tau_w_g(icol, ilev, iswband) = mass(icol, ilev) * ascat(iswband); - tau_w_f(icol, ilev, iswband) = mass(icol, ilev) * g * ascat(iswband); - }); -} diff --git a/Source/Radiation/ERF_Albedo.H b/Source/Radiation/ERF_Albedo.H deleted file mode 100644 index 253547e39..000000000 --- a/Source/Radiation/ERF_Albedo.H +++ /dev/null @@ -1,13 +0,0 @@ -/*------------------------------------------------------------ - Computes surface albedos - For more details , see Briegleb, Bruce P., 1992: Delta-Eddington - Approximation for Solar Radiation in the NCAR Community Climate Model, - Journal of Geophysical Research, Vol 97, D7, pp7603-7612). - - NOTE: this is the simplest formula for albedo calculation, we have to - update to the latest implementation from microphysics output. -*/ -#ifndef ERF_ALBEDO_H_ -#define ERF_ALBEDO_H_ -void set_albedo (const real1d& coszrs, real2d& albedo_dir, real2d& albedo_dif); -#endif diff --git a/Source/Radiation/ERF_Albedo.cpp b/Source/Radiation/ERF_Albedo.cpp deleted file mode 100644 index 08422db4f..000000000 --- a/Source/Radiation/ERF_Albedo.cpp +++ /dev/null @@ -1,19 +0,0 @@ - -#include -#include "ERF_Albedo.H" - -void set_albedo (const real1d& coszrs, real2d& albedo_dir, real2d& albedo_dif) -{ - // Albedos for land type I (Briegleb) - auto nswbands = albedo_dir.extent(0); - auto ncol = albedo_dif.extent(1); - using yakl::fortran::parallel_for; - using yakl::fortran::SimpleBounds; - - parallel_for(SimpleBounds<2>(nswbands,ncol), YAKL_LAMBDA (int ibnd, int icol) - { - albedo_dir(ibnd, icol) = 1.4 * 0.24 / ( 1. + 0.8 * coszrs(icol)); - albedo_dif(ibnd, icol) = 1.2 * 0.24; - }); -} - diff --git a/Source/Radiation/ERF_CloudRadProps.H b/Source/Radiation/ERF_CloudRadProps.H deleted file mode 100644 index e1e94a943..000000000 --- a/Source/Radiation/ERF_CloudRadProps.H +++ /dev/null @@ -1,96 +0,0 @@ -// -// cloud radiation properties class -// -#ifndef ERF_CLOUD_RAD_PROPS_H_ -#define ERF_CLOUD_RAD_PROPS_H_ - -#include -#include "ERF_Constants.H" -#include "ERF_Config.H" -#include "rrtmgp_const.h" -#include "ERF_WaterVaporSaturation.H" -#include "ERF_LinearInterpolate.H" - -class CloudRadProps { - public: - // constructors and descturctors - CloudRadProps () = default; - ~CloudRadProps () = default; - - // initialization - void initialize (); - - // liquid optics - void gammadist_liq_optics_sw (const int& ncol, - const int& nlev, - const real2d& iclwpth, - const real2d& lamc, - const real2d& pgam, - real3d& tau, - real3d& tau_w, - real3d& tau_w_g, - real3d& tau_w_f); - - void gammadist_liq_optics_lw (const int& ncol, - const int& nlev, - const real2d& iclwpth, - const real2d& lamc, - const real2d& pgam, - real3d& abs_od); - - void mitchell_ice_optics_sw (const int& ncol, - const int& nlev, - const real2d& iciwpth, - const real2d& dei, - real3d& tau, - real3d& tau_w, - real3d& tau_w_g, - real3d& tau_w_f); - - void mitchell_ice_optics_lw (const int& ncol, - const int& nlev, - const real2d& iciwpth, - const real2d& dei, - real3d& abs_od); - - void gam_liquid_lw (const real& clwptn, - const real& lamc, - const real& pgam, - real1d abs_od); - - - // liquid optics (short wave) - void gam_liquid_sw (const real& clwptn, - const real& lamc, - const real& pgam, - real1d tau, - real1d tau_w, - real1d tau_w_g, - real1d tau_w_f); - - // lambda weight - void get_mu_lambda_weights (const real& lamc, - const real& pgam, - LinInterp::InterpType& mu_wgts, - LinInterp::InterpType& lambda_wgts); - private: - std::string name{"CloudRadProps"}; - std::string liquid_file; - std::string ice_file; - - int nlwbands, nswbands, nlambda, nmu, n_g_d; - - real1d g_mu; // mu samples on grid - real2d g_lambda; // lambda scale samples on grid - real3d ext_sw_liq; - real3d ssa_sw_liq; - real3d asm_sw_liq; - real3d abs_lw_liq; - - real1d g_d_eff; // radiative effective diameter samples on grid - real2d ext_sw_ice; - real2d ssa_sw_ice; - real2d asm_sw_ice; - real2d abs_lw_ice; -}; -#endif // ERF_CLOUDRADPROPS_H diff --git a/Source/Radiation/ERF_CloudRadProps.cpp b/Source/Radiation/ERF_CloudRadProps.cpp deleted file mode 100644 index c31d90868..000000000 --- a/Source/Radiation/ERF_CloudRadProps.cpp +++ /dev/null @@ -1,373 +0,0 @@ -// -// get the cloud radiation props data -// -#include -#include "YAKL_netcdf.h" -#include "ERF_CloudRadProps.H" - -using yakl::fortran::parallel_for; -using yakl::fortran::SimpleBounds; - -void CloudRadProps::initialize () -{ - realHost1d g_mu_h; // mu samples on grid - realHost2d g_lambda_h; // lambda scale samples on grid - realHost3d ext_sw_liq_h; - realHost3d ssa_sw_liq_h; - realHost3d asm_sw_liq_h; - realHost3d abs_lw_liq_h; - - realHost1d g_d_eff_h; // radiative effective diameter samples on grid - realHost2d ext_sw_ice_h; - realHost2d ssa_sw_ice_h; - realHost2d asm_sw_ice_h; - realHost2d abs_lw_ice_h; - - auto erf_rad_data_dir = getRadiationDataDir() + "/"; - liquid_file = erf_rad_data_dir + "F_nwvl200_mu20_lam50_res64_t298_c080428.nc"; - ice_file = erf_rad_data_dir + "iceoptics_c080917.nc"; - - yakl::SimpleNetCDF liquid, ice; - liquid.open(liquid_file, yakl::NETCDF_MODE_READ); - ice.open(ice_file, yakl::NETCDF_MODE_READ); - - // read the dimensions - nlwbands = liquid.getDimSize( "lw_band" ); - nswbands = liquid.getDimSize( "sw_band" ); - nmu = liquid.getDimSize( "mu" ); - nlambda = liquid.getDimSize( "lambda_scale" ); - - liquid.read( g_mu_h, "mu"); - liquid.read( g_lambda_h, "lambda"); - liquid.read( ext_sw_liq_h, "k_ext_sw"); - liquid.read( ssa_sw_liq_h, "ssa_sw"); - liquid.read( asm_sw_liq_h, "asm_sw"); - liquid.read( abs_lw_liq_h, "k_abs_lw"); - - g_mu = real1d("gmu", nmu); - g_lambda = real2d("g_lambda", nmu, nlambda); - ext_sw_liq = real3d("ext_sw_liq", nmu, nlambda, nswbands); - ssa_sw_liq = real3d("ssa_sw_liq", nmu, nlambda, nswbands); - asm_sw_liq = real3d("asm_sw_liq", nmu, nlambda, nswbands); - abs_lw_liq = real3d("abs_lw_liq", nmu, nlambda, nlwbands); - - g_mu_h.deep_copy_to(g_mu); - g_lambda_h.deep_copy_to(g_lambda); - ext_sw_liq_h.deep_copy_to(ext_sw_liq); - ssa_sw_liq_h.deep_copy_to(ssa_sw_liq); - asm_sw_liq_h.deep_copy_to(asm_sw_liq); - abs_lw_liq_h.deep_copy_to(abs_lw_liq); - - // I forgot to convert kext from m^2/Volume to m^2/Kg - parallel_for(SimpleBounds<3>(nmu,nlambda,nswbands) , YAKL_LAMBDA (int i, int j, int k) - { - ext_sw_liq(i,j,k) = ext_sw_liq(i,j,k) / 0.9970449e3; - }); - - parallel_for(SimpleBounds<3>(nmu,nlambda,nlwbands) , YAKL_LAMBDA (int i, int j, int k) - { - abs_lw_liq(i,j,k) = abs_lw_liq(i,j,k) / 0.9970449e3; - }); - - // read ice cloud optics - nlwbands = ice.getDimSize( "lw_band" ); - nswbands = ice.getDimSize( "sw_band" ); - n_g_d = ice.getDimSize( "d_eff" ); - - ice.read( g_d_eff_h, "d_eff"); - ice.read( ext_sw_ice_h, "sw_ext"); - ice.read( ssa_sw_ice_h, "sw_ssa"); - ice.read( asm_sw_ice_h, "sw_asm"); - ice.read( abs_lw_ice_h, "lw_abs"); - - g_d_eff = real1d("g_d_eff",n_g_d); - ext_sw_ice = real2d("ext_sw_ice", n_g_d, nswbands); - ssa_sw_ice = real2d("ssa_sw_ice", n_g_d, nswbands); - asm_sw_ice = real2d("asm_sw_ice", n_g_d, nswbands); - abs_lw_ice = real2d("abs_lw_ice", n_g_d, nlwbands); - - g_d_eff_h.deep_copy_to(g_d_eff); - ext_sw_ice_h.deep_copy_to(ext_sw_ice); - ssa_sw_ice_h.deep_copy_to(ssa_sw_ice); - asm_sw_ice_h.deep_copy_to(asm_sw_ice); - abs_lw_ice_h.deep_copy_to(abs_lw_ice); -} - -void CloudRadProps::gammadist_liq_optics_sw (const int& ncol, - const int& nlev, - const real2d& iclwpth, - const real2d& lamc, - const real2d& pgam, - real3d& tau, - real3d& tau_w, - real3d& tau_w_g, - real3d& tau_w_f) -{ - real1d tau1d("tau1d", nswbands); - real1d tauw1d("tauw1d", nswbands); - real1d tauwg1d("tauwg1d", nswbands); - real1d tauwf1d("tauwf1d", nswbands); - parallel_for(SimpleBounds<2>(ncol, nlev), YAKL_LAMBDA (int i, int k) - { - if(lamc(i,k) > 0.) { // This seems to be clue from microphysics of no cloud - for (auto iband=1; iband<=nswbands; ++iband) { - tau1d(iband) = tau(iband,i,k); - tauw1d(iband) = tau_w(iband,i,k); - tauwg1d(iband) = tau_w_g(iband,i,k); - tauwf1d(iband) = tau_w_f(iband,i,k); - } - gam_liquid_sw(iclwpth(i,k), lamc(i,k), pgam(i,k), tau1d, tauw1d, tauwg1d, tauwf1d); - } - else { - for (auto iband=1; iband<=nswbands; ++iband) { - tau(iband,i,k) = 0.; - tau_w(iband,i,k) = 0.; - tau_w_g(iband,i,k) = 0.; - tau_w_f(iband,i,k) = 0.; - } - } - }); -} - -void CloudRadProps::gammadist_liq_optics_lw (const int& ncol, - const int& nlev, - const real2d& iclwpth, - const real2d& lamc, - const real2d& pgam, - real3d& abs_od) -{ - auto abs_od_1d = real1d("abs_od_1d", nlwbands); - parallel_for(SimpleBounds<2>(ncol, nlev), YAKL_LAMBDA (int i, int k) - { - if(lamc(i,k) > 0.) { // This seems to be the clue for no cloud from microphysics formulation - for (auto ib=1; ib<=nlwbands; ++ib) abs_od_1d(ib) = abs_od(ib,i,k); - gam_liquid_lw(iclwpth(i,k), lamc(i,k), pgam(i,k), abs_od_1d); - } - else { - for(auto j=1; j<=nlwbands; ++j) abs_od(j,i,k) = 0.; - } - }); -} - - -void CloudRadProps::mitchell_ice_optics_sw (const int& ncol, - const int& nlev, - const real2d& iciwpth, - const real2d& dei, - real3d& tau, - real3d& tau_w, - real3d& tau_w_g, - real3d& tau_w_f) -{ - real1d ext("ext", nswbands), - ssa("ssa", nswbands), - assm("assm", nswbands); - - real1d ext_sw_ice_1d("ext_sw_ice_1d", n_g_d), - ssa_sw_ice_1d("ssa_sw_ice_1d", n_g_d), - asm_sw_ice_1d("asm_sw_ice_1d", n_g_d); - - real1d dei_1d("dei_1d",1); - auto ext_1d = real1d("ext_1d",1); - auto ssa_1d = real1d("ssa_1d",1); - auto assm_1d = real1d("assm_1d",1); - - parallel_for(SimpleBounds<2>(ncol, nlev), YAKL_LAMBDA (int i, int k) - { - if( iciwpth(i,k) < 1.e-80 || dei(i,k) == 0.) { - // if ice water path is too small - for(auto ib=1; ib<=nswbands; ++ib) { - tau (ib,i,k) = 0.; - tau_w (ib,i,k) = 0.; - tau_w_g(ib,i,k) = 0.; - tau_w_f(ib,i,k) = 0.; - } - } - else { - // for each cell interpolate to find weights in g_d_eff grid. - dei_1d(1) = dei(i,k); - LinInterp::InterpType dei_wgts; - LinInterp::init(g_d_eff, n_g_d, dei_1d, 1, LinInterp::extrap_method_bndry, dei_wgts); - // interpolate into grid and extract radiative properties - for (auto is=1; is<=nswbands; ++is) { - for(auto ig=1; ig<=n_g_d; ++ig) { - ext_sw_ice_1d(ig) = ext_sw_ice(ig, is); - ssa_sw_ice_1d(ig) = ssa_sw_ice(ig, is); - asm_sw_ice_1d(ig) = asm_sw_ice(ig, is); - } - ext_1d(1) = ext(is); - ssa_1d(1) = ssa(is); - assm_1d(1) = assm(is); - LinInterp::interp1d(ext_sw_ice_1d, n_g_d, ext_1d, 1, dei_wgts); - LinInterp::interp1d(ssa_sw_ice_1d, n_g_d, ssa_1d, 1, dei_wgts); - LinInterp::interp1d(asm_sw_ice_1d, n_g_d, assm_1d, 1, dei_wgts); - } - for (auto is=1; is<=nswbands; ++is) { - tau (is,i,k) = iciwpth(i,k) * ext(is); - tau_w (is,i,k) = tau(is,i,k) * ssa(is); - tau_w_g(is,i,k) = tau_w(is,i,k) * assm(is); - tau_w_f(is,i,k) = tau_w_g(is,i,k) * assm(is); - } - } - }); -} - -void CloudRadProps::mitchell_ice_optics_lw (const int& ncol, - const int& nlev, - const real2d& iciwpth, - const real2d& dei, - real3d& abs_od) -{ - real1d absor("absor", nlwbands); - real1d dei_1d("dei_1d",1); - real1d abs_lw_ice_1d("abs_lw_ice_1d",n_g_d); - - real1d absor_1d("absor_1d",1); - - parallel_for(SimpleBounds<2>(ncol, nlev), YAKL_LAMBDA (int i, int k) - { - // if ice water path is too small, OD := 0 - if(iciwpth(i,k) < 1.e-80 || dei(i,k) == 0.) { - for (auto lb=1; lb<=nlwbands; ++lb) { - abs_od (lb,i,k) = 0.; - } - } - else { - // for each cell interpolate to find weights in g_d_eff grid. - dei_1d(1) = dei(i,k); - LinInterp::InterpType dei_wgts; - LinInterp::init(g_d_eff, n_g_d, dei_1d, 1, LinInterp::extrap_method_bndry, dei_wgts); - // interpolate into grid and extract radiative properties - for(auto lwband = 1; lwband <= nlwbands; ++lwband) { - absor_1d(1) = absor(lwband); - LinInterp::interp1d(abs_lw_ice_1d, n_g_d, absor_1d, 1, dei_wgts); - absor(lwband) = absor_1d(1); - } - for (auto lwband = 1; lwband <= nlwbands; ++lwband) { - abs_od(lwband,i,k) = iciwpth(i,k) * absor(lwband); - } - } - }); -} - - -void CloudRadProps::gam_liquid_lw (const real& clwptn, - const real& lamc, - const real& pgam, - real1d abs_od) -{ - LinInterp::InterpType mu_wgts; - LinInterp::InterpType lambda_wgts; - - real2d abs_lw_liq_2d("abs_lw_liq_2d", nmu, nlambda); - real1d abs_od_1d("abs_od_1d", 1); - - if (clwptn < 1.e-80) { - yakl::memset(abs_od, 0.); - return; - } - - get_mu_lambda_weights(lamc, pgam, mu_wgts, lambda_wgts); - - parallel_for(SimpleBounds<1>(nlwbands), YAKL_LAMBDA (int lwband) - { - for (auto imu=1; imu<=nmu; ++imu) { - for (auto ilb=1; ilb<=nlambda; ++ilb) { - abs_lw_liq_2d(imu, ilb) = abs_lw_liq(imu, ilb, lwband); - } - } - abs_od_1d(1) = abs_od(lwband); - LinInterp::interp2d1d(abs_lw_liq_2d, nmu, nlambda, abs_od_1d, 1, mu_wgts, lambda_wgts); - abs_od(lwband) = clwptn*abs_od_1d(1); - }); - - parallel_for(SimpleBounds<1>(nlwbands) , YAKL_LAMBDA (int iband) - { - abs_od(iband) = clwptn * abs_od(iband); - }); -} - -void CloudRadProps::gam_liquid_sw (const real& clwptn, - const real& lamc, - const real& pgam, - real1d tau, - real1d tau_w, - real1d tau_w_g, - real1d tau_w_f) -{ - real1d extb("ext", nswbands), - ssab("ssa", nswbands), - asmb("asm", nswbands); - - real2d ext_sw_liq_2d("ext_sw_liq_2d", nmu, nlambda), - ssa_sw_liq_2d("ssa_sw_liq_2d", nmu, nlambda), - asm_sw_liq_2d("asm_sw_liq_2d", nmu, nlambda); - - LinInterp::InterpType mu_wgts; - LinInterp::InterpType lambda_wgts; - - if (clwptn < 1.e-80) { - yakl::memset(tau, 0.); - yakl::memset(tau_w, 0.); - yakl::memset(tau_w_g, 0.); - yakl::memset(tau_w_f, 0.); - return; - } - - get_mu_lambda_weights(lamc, pgam, mu_wgts, lambda_wgts); - - parallel_for(SimpleBounds<1>(nswbands), YAKL_LAMBDA (int swband) - { - for (auto imu=1; imu<=nmu; ++imu) { - for (auto lb=1; lb<=nlambda; ++lb) { - ext_sw_liq_2d(imu,lb) = ext_sw_liq(imu,lb,swband); - ssa_sw_liq_2d(imu,lb) = ssa_sw_liq(imu,lb,swband); - asm_sw_liq_2d(imu,lb) = asm_sw_liq(imu,lb,swband); - } - } - LinInterp::interp2d1d(ext_sw_liq_2d, nmu, nlambda, extb, 1, mu_wgts, lambda_wgts); - LinInterp::interp2d1d(ssa_sw_liq_2d, nmu, nlambda, ssab, 1, mu_wgts, lambda_wgts); - LinInterp::interp2d1d(asm_sw_liq_2d, nmu, nlambda, asmb, 1, mu_wgts, lambda_wgts); - }); - - // compute radiative properties - parallel_for(SimpleBounds<1>(nswbands), YAKL_LAMBDA (int iband) - { - tau(iband) = clwptn * extb(iband); - tau_w(iband) = tau(iband) * ssab(iband); - tau_w_g(iband) = tau_w(iband) * asmb(iband); - tau_w_f(iband) = tau_w_g(iband) * asmb(iband); - }); -} - - -void CloudRadProps::get_mu_lambda_weights (const real& lamc, - const real& pgam, - LinInterp::InterpType& mu_wgts, - LinInterp::InterpType& lambda_wgts) -{ - real1d g_lambda_interp("g_lambda_interp", nlambda); - real1d pgam1d("pgam1d", nmu); - real1d lamc1d("lam1d", nmu); - real1d g_lambda1d("g_lambda1d", nmu); - - yakl::memset(pgam1d, pgam); - yakl::memset(lamc1d, lamc); - - real1d g_mu0 = real1d("g_mu0",nmu); - - // Make interpolation weights for mu. - // (Put pgam in a temporary array for this purpose.) - LinInterp::init(g_mu0, nmu, pgam1d, 1, LinInterp::extrap_method_bndry, mu_wgts); - - // Use mu weights to interpolate to a row in the lambda table. - parallel_for(SimpleBounds<1>(nlambda), YAKL_LAMBDA (int i) - { - for (auto im=1; im<=nmu; ++im) g_lambda1d(im) = g_lambda(im, i); - LinInterp::interp1d(g_lambda1d, nmu, g_lambda_interp, 1, mu_wgts); - }); - - // Make interpolation weights for lambda. - LinInterp::init(g_lambda_interp, nlambda, lamc1d, 1, LinInterp::extrap_method_bndry, lambda_wgts); -} - diff --git a/Source/Radiation/ERF_EbertCurry.H b/Source/Radiation/ERF_EbertCurry.H deleted file mode 100644 index 6e2173b53..000000000 --- a/Source/Radiation/ERF_EbertCurry.H +++ /dev/null @@ -1,157 +0,0 @@ - -#ifndef ERF_EBERT_CURRY_H_ -#define ERF_EBERT_CURRY_H_ -using yakl::fortran::parallel_for; -using yakl::fortran::SimpleBounds; - -class EbertCurry { - public: - static constexpr real scalefactor = 1.; //500._r8/917._r8 - - static void ec_ice_optics_sw (int ncol, int nlev, int nswbands, - const real2d& cldn, const real2d& cicewp, const real2d& rei, - const real3d& ice_tau, const real3d& ice_tau_w, - const real3d& ice_tau_w_g, const real3d& ice_tau_w_f) - { - real1d wavmin("wavmin", nswbands); - real1d wavmax("wavmax", nswbands); - - // ice water coefficients (Ebert and Curry,1992, JGR, 97, 3831-3836) - real1d abari("abari", 4); // a coefficient for extinction optical depth - real1d bbari("bbari", 4); // b coefficient for extinction optical depth - real1d cbari("cbari", 4); // c coefficient for single scat albedo - real1d dbari("dbari", 4); // d coefficient for single scat albedo - real1d ebari("ebari", 4); // e coefficient for asymmetry parameter - real1d fbari("fbari", 4); // f coefficient for asymmetry parameter - - parallel_for(SimpleBounds<1>(1), YAKL_LAMBDA (int i) - { - abari(1) = 3.448e-03; - abari(2) = 3.448e-03; - abari(3) = 3.448e-03; - abari(4) = 3.448e-03; - bbari(1) = 2.431; - bbari(2) = 2.431; - bbari(3) = 2.431; - bbari(4) = 2.431; - cbari(1) = 1.00e-05; - cbari(2) = 1.10e-04; - cbari(3) = 1.861e-02; - cbari(4) = 0.46658; - dbari(1) = 0.0; - dbari(2) = 1.405e-05; - dbari(3) = 8.328e-04; - dbari(4) = 2.05e-05; - ebari(1) = 0.7661; - ebari(2) = 0.7730; - ebari(3) = 0.794; - ebari(4) = 0.9595; - fbari(1) = 5.851e-04; - fbari(2) = 5.665e-04; - fbari(3) = 7.267e-04; - fbari(4) = 1.076e-04; - }); - - // Minimum cloud amount (as a fraction of the grid-box area) to - // distinguish from clear sky - constexpr real cldmin = 1.0e-80; - - // Decimal precision of cloud amount (0 -> preserve full resolution; - // 10^-n -> preserve n digits of cloud amount) - constexpr real cldeps = 0.0; - - // Optical properties for ice are valid only in the range of - // 13 < rei < 130 micron (Ebert and Curry 92) - constexpr real rei_min = 13.; - constexpr real rei_max = 130.; - - // integer :: ns, i, k, indxsl - int indxsl; - - RadConstants::get_sw_spectral_boundaries(wavmin,wavmax,RadConstants::micrometer); - - for (auto ns=0; ns 2.38) { - indxsl = 4; - } - parallel_for(SimpleBounds<2>(nlev, ncol), YAKL_LAMBDA (int k, int i) - { - real tmp1i, tmp2i, tmp3i, g; - auto abarii = abari(indxsl); - auto bbarii = bbari(indxsl); - auto cbarii = cbari(indxsl); - auto dbarii = dbari(indxsl); - auto ebarii = ebari(indxsl); - auto fbarii = fbari(indxsl); - - // note that optical properties for ice valid only - // in range of 13 > rei > 130 micron (Ebert and Curry 92) - if (cldn(i,k) >= cldmin && cldn(i,k) >= cldeps) { - tmp1i = abarii + bbarii/std::max(rei_min,std::min(scalefactor*rei(i,k),rei_max)); - ice_tau(ns,i,k) = 1000. * cicewp(i,k) * tmp1i; - } else { - ice_tau(ns,i,k) = 0.0; - } - - tmp2i = 1. - cbarii - dbarii*std::min(std::max(rei_min,scalefactor*rei(i,k)),rei_max); - tmp3i = fbarii*std::min(std::max(rei_min,scalefactor*rei(i,k)),rei_max); - // Do not let single scatter albedo be 1. Delta-eddington solution - // for non-conservative case has different analytic form from solution - // for conservative case, and raddedmx is written for non-conservative case. - ice_tau_w(ns,i,k) = ice_tau(ns,i,k) * std::min(tmp2i,.999999); - g = ebarii + tmp3i; - ice_tau_w_g(ns,i,k) = ice_tau_w(ns,i,k) * g; - ice_tau_w_f(ns,i,k) = ice_tau_w(ns,i,k) * g * g; - }); - } // nswbands - } - - - static void ec_ice_optics_lw (int ncol, int nlev, int nlwbands, - const real2d& cldn, const real2d& iclwpth, - const real2d& iciwpth, const real2d& rei, const real3d& abs_od) - { - real2d ficemr("ficemr",ncol,nlev); - real2d cwp("cwp",ncol,nlev); - real2d cldtau("cldtau",ncol,nlev); - - // longwave liquid absorption coeff (m**2/g) - //const real kabsl = 0.090361; - - // Optical properties for ice are valid only in the range of - // 13 < rei < 130 micron (Ebert and Curry 92) - const real rei_min = 13.; - const real rei_max = 130.; - - parallel_for(SimpleBounds<2>(nlev, ncol), YAKL_LAMBDA (int k, int i) - { - cwp(i,k) = 1000.0 *iciwpth(i,k) + 1000.0 *iclwpth(i,k); - ficemr(i,k) = 1000.0*iciwpth(i,k)/(std::max(1.e-18,cwp(i,k))); - }); - - parallel_for(SimpleBounds<2>(nlev, ncol), YAKL_LAMBDA (int k, int i) - { - // Note from Andrew Conley: - // Optics for RK no longer supported, This is constructed to get - // close to bit for bit. Otherwise we could simply use ice water path - // note that optical properties for ice valid only - // in range of 13 > rei > 130 micron (Ebert and Curry 92) - auto kabsi = 0.005 + 1./std::min(std::max(rei_min,scalefactor*rei(i,k)),rei_max); - auto kabs = kabsi*ficemr(i,k); // kabsl*(1.-ficemr(i,k)) + kabsi*ficemr(i,k); - cldtau(i,k) = kabs*cwp(i,k); - }); - - parallel_for(SimpleBounds<3>(nlwbands, ncol, nlev), YAKL_LAMBDA (int lwband, int icol, int ilev) - { - abs_od(lwband,icol,ilev)=cldtau(icol,ilev); - }); - } -}; -#endif - diff --git a/Source/Radiation/ERF_FinalizeRRTMGP.cpp b/Source/Radiation/ERF_FinalizeRRTMGP.cpp deleted file mode 100644 index 4ff4fa732..000000000 --- a/Source/Radiation/ERF_FinalizeRRTMGP.cpp +++ /dev/null @@ -1,19 +0,0 @@ -#include "mo_gas_concentrations.h" -#include "mo_gas_optics_rrtmgp.h" -#include "mo_load_coefficients.h" -#include "mo_rte_sw.h" -#include "mo_rte_lw.h" -#include "mo_optical_props.h" -#include "rrtmgp_const.h" -#include "mo_fluxes_byband.h" - -// Rrtmgp -#include "ERF_RRTMGP.H" - -void Rrtmgp::finalize () -{ - k_dist_sw.finalize(); - k_dist_lw.finalize(); - yakl::finalize(); -} - diff --git a/Source/Radiation/ERF_InitRRTMGP.cpp b/Source/Radiation/ERF_InitRRTMGP.cpp deleted file mode 100644 index 9188064d8..000000000 --- a/Source/Radiation/ERF_InitRRTMGP.cpp +++ /dev/null @@ -1,39 +0,0 @@ -#include "mo_gas_concentrations.h" -#include "mo_gas_optics_rrtmgp.h" -#include "mo_load_coefficients.h" -#include "mo_rte_sw.h" -#include "mo_rte_lw.h" -#include "mo_optical_props.h" -#include "rrtmgp_const.h" -#include "mo_fluxes_byband.h" - -// RRTMGP -#include "ERF_RRTMGP.H" - -void Rrtmgp::initialize (int num_gas, const std::vector& active_gas_names, - const char* rrtmgp_coefficients_file_sw, - const char* rrtmgp_coefficients_file_lw) -{ - // Read gas optics coefficients from file - // Need to initialize available_gases here! The only field of the - // available_gases type that is used in the kdist initialize is - // available_gases%gas_name, which gives the name of each gas that would be - // present in the ty_gas_concs object. So, we can just set this here, rather - // than trying to fully populate the ty_gas_concs object here, which would be - // impossible from this initialization routine because I do not think the - // rad_cnst objects are setup yet. - // the other tasks! - ngas = num_gas; - coefficients_file_sw = rrtmgp_coefficients_file_sw; - coefficients_file_lw = rrtmgp_coefficients_file_lw; - - active_gases = string1d("active_gases", ngas); - for (int igas=0; igas(n_in-1), YAKL_LAMBDA (int j) - { - if(yin(j) > yin(j+1)) { - printf("init: inputs are not monotonic!\n"); - return; - } - }); - - // - // Initialize index arrays for later checking - // - parallel_for(SimpleBounds<1>(nout), YAKL_LAMBDA (int j) - { - interp_wgts.jjm(j) = 0; - interp_wgts.jjp(j) = 0; - }); - - switch (extrap_method) { - case extrap_method_zero: - // - // For values which extend beyond boundaries, set weights - // such that values will be 0. - // - if(increasing) { - parallel_for(SimpleBounds<1>(nout), YAKL_LAMBDA (int j) - { - if (yout(j) < yin(1)) { - interp_wgts.jjm(j) = 1; - interp_wgts.jjp(j) = 1; - interp_wgts.wgts(j) = 0.; - interp_wgts.wgtn(j) = 0.; - } - else if (yout(j) > yin(n_in)) { - interp_wgts.jjm(j) = n_in; - interp_wgts.jjp(j) = n_in; - interp_wgts.wgts(j) = 0.; - interp_wgts.wgtn(j) = 0.; - } - }); - } - else { - parallel_for(SimpleBounds<1>(nout), YAKL_LAMBDA (int j) - { - if (yout(j) > yin(1)) { - interp_wgts.jjm(j) = 1; - interp_wgts.jjp(j) = 1; - interp_wgts.wgts(j) = 0.; - interp_wgts.wgtn(j) = 0.; - } - else if (yout(j) < yin(n_in)) { - interp_wgts.jjm(j) = n_in; - interp_wgts.jjp(j) = n_in; - interp_wgts.wgts(j) = 0.; - interp_wgts.wgtn(j) = 0.; - } - }); - } - break; - case extrap_method_bndry: - // - // For values which extend beyond boundaries, set weights - // such that values will just be copied. - // - if(increasing) { - parallel_for(SimpleBounds<1>(nout), YAKL_LAMBDA (int j) - { - if (yout(j) <= yin(1)) { - interp_wgts.jjm(j) = 1; - interp_wgts.jjp(j) = 1; - interp_wgts.wgts(j) = 1.; - interp_wgts.wgtn(j) = 0.; - } - else if (yout(j) > yin(n_in)) { - interp_wgts.jjm(j) = n_in; - interp_wgts.jjp(j) = n_in; - interp_wgts.wgts(j) = 1.; - interp_wgts.wgtn(j) = 0.; - } - }); - } - else { - parallel_for(SimpleBounds<1>(nout), YAKL_LAMBDA (int j) - { - if (yout(j) > yin(1)) { - interp_wgts.jjm(j) = 1; - interp_wgts.jjp(j) = 1; - interp_wgts.wgts(j) = 1.; - interp_wgts.wgtn(j) = 0.; - } - else if (yout(j) <= yin(n_in)) { - interp_wgts.jjm(j) = n_in; - interp_wgts.jjp(j) = n_in; - interp_wgts.wgts(j) = 1.; - interp_wgts.wgtn(j) = 0.; - } - }); - } - break; - case extrap_method_cycle: - // - // For values which extend beyond boundaries, set weights - // for circular boundaries - // - dyinwrap = yin(1) + (cmax-cmin) - yin(n_in); - avgdyin = abs(yin(n_in)-yin(1))/(n_in-1.); - auto ratio = dyinwrap/avgdyin; - if (ratio < 0.9 || ratio > 1.1) { - printf("ratio is too large, dyinwrap= %13.6e, avgdyin= %13.6e, yin(1) = %13.6e, yin(n_in)= %13.6e\n", - dyinwrap, avgdyin, yin(1), yin(n_in)); - } - - if(increasing) { - parallel_for(SimpleBounds<1>(nout), YAKL_LAMBDA (int j) - { - if (yout(j) <= yin(1)) { - interp_wgts.jjm(j) = n_in; - interp_wgts.jjp(j) = 1; - interp_wgts.wgts(j) = (yin(1)-yout(j))/dyinwrap; - interp_wgts.wgtn(j) = (yout(j)+(cmax-cmin) - yin(n_in))/dyinwrap; - } - else if (yout(j) > yin(n_in)) { - interp_wgts.jjm(j) = n_in; - interp_wgts.jjp(j) = 1; - interp_wgts.wgts(j) = (yin(1)+(cmax-cmin)-yout(j))/dyinwrap; - interp_wgts.wgtn(j) = (yout(j)-yin(n_in))/dyinwrap; - } - }); - } - else { - parallel_for(SimpleBounds<1>(nout), YAKL_LAMBDA (int j) - { - if (yout(j) > yin(1)) { - interp_wgts.jjm(j) = n_in; - interp_wgts.jjp(j) = 1; - interp_wgts.wgts(j) = (yin(1)-yout(j))/dyinwrap; - interp_wgts.wgtn(j) = (yout(j)+(cmax-cmin) - yin(n_in))/dyinwrap; - } - else if (yout(j) <= yin(n_in)) { - interp_wgts.jjm(j) = n_in; - interp_wgts.jjp(j) = 1; - interp_wgts.wgts(j) = (yin(1)+(cmax-cmin)-yout(j))/dyinwrap; - interp_wgts.wgtn(j) = (yout(j)+(cmax-cmin)-yin(n_in))/dyinwrap; - } - }); - } - break; - } - // - // Loop though output indices finding input indices and weights - // - if(increasing) { - parallel_for(SimpleBounds<2>(nout, n_in-1), YAKL_LAMBDA (int j, int jj) - { - if (yout(j) > yin(jj) && yout(j) <= yin(jj+1)) { - interp_wgts.jjm(j) = jj; - interp_wgts.jjp(j) = jj + 1; - interp_wgts.wgts(j) = (yin(jj+1)-yout(j))/(yin(jj+1)-yin(jj)); - interp_wgts.wgtn(j) = (yout(j)-yin(jj))/(yin(jj+1)-yin(jj)); - return; - } - }); - } - else { - parallel_for(SimpleBounds<2>(nout, n_in-1), YAKL_LAMBDA (int j, int jj) - { - if (yout(j) <= yin(jj) && yout(j) > yin(jj+1)) { - interp_wgts.jjm(j) = jj; - interp_wgts.jjp(j) = jj + 1; - interp_wgts.wgts(j) = (yin(jj+1)-yout(j))/(yin(jj+1)-yin(jj)); - interp_wgts.wgtn(j) = (yout(j)-yin(jj))/(yin(jj+1)-yin(jj)); - return; - } - }); - } - - // - // Check that interp/extrap points have been found for all outputs - // - parallel_for(SimpleBounds<1>(nout), YAKL_LAMBDA (int j) - { - int count = 0; - amrex::ignore_unused(count); - if (interp_wgts.jjm(j) == 0 || interp_wgts.jjp(j) == 0) count += 1; - auto frac = interp_wgts.wgts(j) + interp_wgts.wgtn(j); - if ((frac < 0.9 || frac > 1.1) && extrap_method != 0) - printf("interpolation error!\n"); - }); - - } - - // - // Purpose: Do a linear interpolation from input mesh to output - // mesh with weights as set in lininterp_init. - YAKL_INLINE - static void interp1d (const real1d& arrin, - const int& n1, - const real1d& arrout, - const int& m1, - const InterpType& interp_wgts) - { - // - // Do the interpolation - // - parallel_for(SimpleBounds<1>(m1), YAKL_LAMBDA (int j) - { - arrout(j) = arrin(interp_wgts.jjm(j))*interp_wgts.wgts(j) + - arrin(interp_wgts.jjp(j))*interp_wgts.wgtn(j); - }); - } - - YAKL_INLINE - static void interp2d2d (const real2d& arrin, - const int& n1, - const int& n2, - const real2d& arrout, - const int& m1, - const int& m2, - const InterpType& wgt1, - const InterpType& wgt2) - { - - real2d arrtmp("arrtmp",n1,m2); - - parallel_for(SimpleBounds<2>(n1, m2), YAKL_LAMBDA (int i, int j) - { - arrtmp(i,j) = arrin(i,wgt2.jjm(j))*wgt2.wgts(j) + arrin(i,wgt2.jjp(j))*wgt2.wgtn(j); - }); - - parallel_for(SimpleBounds<2>(n1, m2), YAKL_LAMBDA (int i, int j) - { - arrout(i,j) = arrtmp(wgt1.jjm(i),j)*wgt1.wgts(i) + arrtmp(wgt1.jjp(i),j)*wgt1.wgtn(i); - }); - } - - YAKL_INLINE - static void interp2d1d (const real2d& arrin, - const int& n1, - const int& n2, - const real1d& arrout, - const int& m1, - const InterpType& wgt1, - const InterpType& wgt2) - { - parallel_for(SimpleBounds<1>(m1), YAKL_LAMBDA (int i) - { - arrout(i) = arrin(wgt1.jjm(i),wgt2.jjm(i))*wgt1.wgts(i)*wgt2.wgts(i)+arrin(wgt1.jjp(i),wgt2.jjm(i))*wgt1.wgtn(i)*wgt2.wgts(i) - + arrin(wgt1.jjm(i),wgt2.jjp(i))*wgt1.wgtn(i)*wgt2.wgtn(i)+arrin(wgt1.jjp(i),wgt2.jjp(i))*wgt1.wgtn(i)*wgt2.wgtn(i); - }); - } - - YAKL_INLINE - static void interp3d2d (const real3d& arrin, - const int& n1, - const int& n2, - const int& n3, - const real2d& arrout, - const int& m1, - const int& len1, - const InterpType& wgt1, - const InterpType& wgt2) - { - parallel_for(SimpleBounds<2>(m1, n3), YAKL_LAMBDA (int i, int k) - { - arrout(i,k) = arrin(wgt1.jjm(i),wgt2.jjm(i),k)*wgt1.wgts(i)*wgt2.wgts(i)+arrin(wgt1.jjp(i),wgt2.jjm(i),k)*wgt1.wgtn(i)*wgt2.wgts(i) + - arrin(wgt1.jjm(i),wgt2.jjp(i),k)*wgt1.wgts(i)*wgt2.wgtn(i)+arrin(wgt1.jjp(i),wgt2.jjp(i),k)*wgt1.wgtn(i)*wgt2.wgtn(i); - }); - } -}; -#endif diff --git a/Source/Radiation/ERF_M2005EffRadius.H b/Source/Radiation/ERF_M2005EffRadius.H deleted file mode 100644 index 4a24b222c..000000000 --- a/Source/Radiation/ERF_M2005EffRadius.H +++ /dev/null @@ -1,126 +0,0 @@ -/*------------------------------------------------------------------------------------------- - This subroutine is used to calculate droplet and ice crystal effective radius, which will be - used in the CAM radiation code. The method to calculate effective radius is taken out of the - Morrision two moment scheme from M2005MICRO_GRAUPEL. It is also very similar to the subroutine - effradius in the module of cldwat2m in the CAM source codes. - Adopted by Minghuai Wang (Minghuai.Wang@pnl.gov). - Calculate effective radius for radiation code - If no cloud water, default value is: - 10 micron for droplets, - 25 micron for cloud ice. - Be careful of the unit of effective radius : [micro meter] - - NOTE: this code is modified from E3SM --------------------------------------------------------------------------------------------*/ -#include "ERF_Constants.H" -#include "ERF_RadConstants.H" -#include - -using namespace amrex; -using yakl::intrinsics::size; -using yakl::fortran::parallel_for; -using yakl::fortran::SimpleBounds; - -YAKL_INLINE -void m2005_effradius (const real2d& ql, const real2d& nl, const real2d& qi, - const real2d& ni, const real2d& qs, const real2d& ns, - const real2d& cld, const real2d& pres, const real2d& tk, - const real2d& effl, const real2d& effi, - const real2d& deffi, const real2d& lamcrad, - const real2d& pgamrad, const real2d& des) -{ - // Main computation - const real pi = 3.1415926535897932384626434; - const real qsmall = 1.0e-14; // in the SAM source code (module_mp_graupel) - const real rhow = 997.; // in module_mp_graupel, SAM - const real rhoi = 500.; // in both CAM and SAM - const real dcs = 125.e-6; // in module_mp_graupel, SAM - const real ci = rhoi*pi/6.; - const real di = 3.; - - // for snow water - const real rhos = 100.; - const real cs = rhos*pi/6.; - const real ds = 3.; - const real mincld = 0.0001; - - int ncol = pres.extent(0); - int nlev = pres.extent(1); - // Effective diameters of snow crystals - parallel_for (SimpleBounds<2> (ncol, nlev), YAKL_LAMBDA (int i, int j) - { - auto rho = pres(i,j)/(287.15*tk(i,j)); // air density [kg/m3] - auto cldm = std::max(cld(i,j), mincld); - auto qlic = std::min(5.e-3, std::max(0., ql(i,j)/cldm)); - auto qiic = std::min(5.e-3, std::max(0., qi(i,j)/cldm)); - auto nlic = std::max(nl(i,j), 0.)/cldm; - auto niic = std::max(ni(i,j), 0.)/cldm; - - real res; - if(qs(i,j) > 1.0e-7) { - auto lammaxs=1./10.e-6; - auto lammins=1./2000.e-6; - auto lams = std::pow((std::tgamma(1.+ds)*cs*ns(i,j)/qs(i,j)), (1./ds)); - lams = std::min(lammaxs, std::max(lams,lammins)); - res = 1.5/lams*1.0e6; - } - else { - res = 500.; - } - - // from Hugh Morrision: rhos/917 accounts for assumptions about - // ice density in the Mitchell optics. - des(i,j) = res*rhos/917.*2.; - - // Effective radius of cloud ice droplet - if( qiic >= qsmall ) { - niic = std::min(niic, qiic*1.e20); - auto lammax = 1./1.e-6; // in module_mp_graupel, SAM - auto lammin = 1./(2.*dcs+100.e-6); // in module_mp_graupel, SAM - auto lami = std::pow((std::tgamma(1.+di)*ci*niic/qiic), (1./di)); - lami = std::min(lammax, std::max(lami, lammin)); - effi(i,j) = 1.5/lami*1.e6; - } - else { - effi(i,j) = 25.; - } - - // hm ice effective radius for david mitchell's optics - // ac morrison indicates that this is effective diameter - // ac morrison indicates 917 (for the density of pure ice..) - deffi(i,j) = effi(i,j)*rhoi/917.*2.; - - // Effective radius of cloud liquid droplet - if( qlic >= qsmall ) { - // Matin et al., 1994 (JAS) formula for pgam (the same is used in both CAM and SAM). - // See also Morrison and Grabowski (2007, JAS, Eq. (2)) - nlic = std::min(nlic, qlic*1.e20); - - // set the minimum droplet number as 20/cm3. - // nlic = max(nlic,20.e6_r8/rho) ! sghan minimum in #/cm3 - //auto tempnc = nlic/rho/1.0e6; // #/kg --> #/cm3 - - // Should be the in-cloud dropelt number calculated as nlic*rho/1.0e6_r8 ????!!!! +++mhwang - auto pgam = 0.0005714*(nlic*rho/1.e6) + 0.2714; - pgam = 1./std::pow(pgam, 2.0)-1.; - pgam = std::min(10., std::max(pgam,2.)); - auto laml = std::pow((pi/6.*rhow*nlic*std::tgamma(pgam+4.)/(qlic*std::tgamma(pgam+1.))), (1./3.)); - auto lammin = (pgam+1.)/50.e-6; // in cldwat2m, CAM - auto lammax = (pgam+1.)/2.e-6; // if lammax is too large, this will lead to crash in - // src/physics/rrtmg/cloud_rad_props.F90 because - // klambda-1 can be zero in gam_liquid_lw and gam_liquid_sw - // and g_lambda(kmu,klambda-1) will not be defined. - laml = std::min(std::max(laml, lammin),lammax); - effl(i,j) = std::tgamma(pgam+4.)/std::tgamma(pgam+3.)/laml/2.*1.e6; - lamcrad(i,j) = laml; - pgamrad(i,j) = pgam; - } - else { - // we chose 10. over 25, since 10 is a more reasonable value for liquid droplet. +++mhwang - effl(i,j) = 10.; // in cldwat2m, CAM - lamcrad(i,j) = 0.0; - pgamrad(i,j) = 0.0; - } - }); - } // m2005_effradius - diff --git a/Source/Radiation/ERF_Mam4Aero.H b/Source/Radiation/ERF_Mam4Aero.H deleted file mode 100644 index 01ab3433b..000000000 --- a/Source/Radiation/ERF_Mam4Aero.H +++ /dev/null @@ -1,756 +0,0 @@ -// -// Mam4 aerosol model class -// -// parameterizes aerosol coefficients using chebychev polynomial -// parameterize aerosol radiative properties in terms of -// surface mode wet radius and wet refractive index -// Ghan and Zaveri, JGR 2007. -// -#ifndef ERF_MAM4_AERO_H_ -#define ERF_MAM4_AERO_H_ - -#include "YAKL_netcdf.h" -#include "rrtmgp_const.h" -#include "ERF_Mam4Constituents.H" -#include "ERF_RadConstants.H" -#include "ERF_Constants.H" -#include "ERF_Config.H" -#include "ERF_ModalAeroWaterUptake.H" - -using yakl::fortran::parallel_for; -using yakl::fortran::SimpleBounds; - -class Mam4_aer { - public: - int ncol, nlev, nspec, top_lev; - int nswbands, nlwbands; - std::string modal_optics_file; // full pathname for modal optics dataset - std::string water_refindex_file; // full pathname for water refractive index dataset - - // Dimension sizes in coefficient arrays used to parameterize aerosol radiative properties - // in terms of refractive index and wet radius - int ncoef = 5; - int prefr = 7; - int prefi = 10; - int nmodes = 4; - int n_diag = 1; - bool clim_modal_aero = true; // true when radiatively constituents present (nmodes>0) - - real xrmin, xrmax; - // refractive index for water read in read_water_refindex - real1d crefwswr; //(nswbands) complex refractive index for water visible - real1d crefwswi; - real1d crefwlwr; // complex refractive index for water infrared - real1d crefwlwi; - - // These are defined as module level variables to avoid allocation-deallocation in a loop - real3d dgnumdry_m; // number mode dry diameter for all modes - real3d dgnumwet_m; // number mode wet diameter for all modes - real3d qaerwat_m; // aerosol water (g/g) for all modes - real3d wetdens_m; - - // aerosol constituents - MamConstituents mam_consti; - public: - // initialization - inline - void initialize (int ncols, int nlevs, int top_levs, int nsw_bands, int nlw_bands) - { - ncol = ncols; - nlev = nlevs; - top_lev = top_levs; - nswbands = nsw_bands; - nlwbands = nlw_bands; - clim_modal_aero = true; - - const real rmmin = 0.01e-6; - const real rmmax = 25.e-6; - xrmin = std::log(rmmin); - xrmax = std::log(rmmax); - - // Check that dimension sizes in the coefficient arrays used to - // parameterize aerosol radiative properties are consistent between this - // module and the mode physprop files. - for(auto ilist = 0; ilist < n_diag; ++ilist) { - mam_consti.get_nmodes(ilist, nmodes); - for(auto m = 0; m < nmodes; ++m) { - mam_consti.get_mode_props(ilist, m, ncoef, prefr, prefi); - } - } - - auto erf_rad_data_dir = getRadiationDataDir() + "/"; - water_refindex_file = erf_rad_data_dir + "water_refindex_rrtmg_c080910.nc"; - read_water_refindex(water_refindex_file); - - // Allocate dry and wet size variables - dgnumdry_m = real3d("dgnumdry", ncol, nlev, nmodes); - dgnumwet_m = real3d("dgnumwet", ncol, nlev, nmodes); - qaerwat_m = real3d("qaerwat" , ncol, nlev, nmodes); - wetdens_m = real3d("wetdens" , ncol, nlev, nmodes); - - crefwswr = real1d("crefwswr", nswbands); - crefwswi = real1d("crefwswi", nswbands); - crefwlwr = real1d("crefwlwr", nlwbands); - crefwlwi = real1d("crefwlwi", nlwbands); - } - - // - // read water refractive index file and set module data - // - inline - void read_water_refindex (std::string infilename) - { - yakl::SimpleNetCDF water_ref_file; - water_ref_file.open(infilename, yakl::NETCDF_MODE_READ); - - // read the dimensions - int nlw_bands = water_ref_file.getDimSize( "lw_band" ); - int nsw_bands = water_ref_file.getDimSize( "sw_band" ); - - if (nswbands != nsw_bands || nlwbands != nlw_bands) - amrex::Print() << "ERROR - file and bandwidth values do not match: " - << "\n nswbands: " << nswbands << "; nsw_bands: " << nsw_bands - << "\n nlwbands: " << nlwbands << "; nlw_bands: " << nlw_bands << "\n"; - - // Local variables - real1d refrwsw("refrwsw", nsw_bands), refiwsw("refiwsw", nsw_bands); // real, imaginary ref index for water visible - real1d refrwlw("refrwlw", nlw_bands), refiwlw("refiwlw", nlw_bands); // real, imaginary ref index for water infrared - - // read variables - water_ref_file.read( refrwsw, "refindex_real_water_sw"); - water_ref_file.read( refiwsw, "refindex_im_water_sw"); - water_ref_file.read( refrwlw, "refindex_real_water_lw"); - water_ref_file.read( refiwlw, "refindex_im_water_lw"); - } - - // - // modal size parameters - // - inline - void modal_size_parameters (real sigma_logr_aer, - const real2d& dgnumwet, const real2d& radsurf, - const real2d& logradsurf, const real3d& cheb) - { - real1d xrad("xrad", ncol); - - auto alnsg_amode = std::log(sigma_logr_aer); - auto explnsigma = std::exp(2.0*alnsg_amode*alnsg_amode); - top_lev = 1; - - for(auto k = top_lev; k <= nlev; ++k) { - for(auto i = 1; i <= ncol; ++i) { - // convert from number mode diameter to surface area - radsurf(i,k) = 0.5*dgnumwet(i,k)*explnsigma; - logradsurf(i,k) = std::log(radsurf(i,k)); - - // normalize size parameter - xrad(i) = std::max(logradsurf(i,k),xrmin); - xrad(i) = std::min(xrad(i),xrmax); - xrad(i) = (2.*xrad(i)-xrmax-xrmin)/(xrmax-xrmin); - - // chebyshev polynomials - cheb(1,i,k) = 1.; - cheb(2,i,k) = xrad(i); - for(auto nc = 3; nc <= ncoef; ++nc) { - cheb(nc,i,k) = 2.*xrad(i)*cheb(nc-1,i,k)-cheb(nc-2,i,k); - } - } - } - } - - // - // bilinear interpolation of table - // - inline - void binterp (const real3d& table, int ncol, int km, int im, int jm, - const real1d& x, const real1d& y, const real1d& xtab, const real1d& ytab, - const int1d& ix, const int1d& jy, const real1d& t, const real1d& u, const real2d& out) - { - // local variables - real1d tu("tu", ncol), tuc("tuc", ncol), tcu("tcu", ncol), tcuc("tcuc", ncol); - - if(ix(1) <= 0) { - if(im > 1) { - parallel_for (SimpleBounds<1>(ncol), YAKL_LAMBDA (int ic) - { - int i; - for (i = 1; i <= im; ++i) { - if(x(ic) < xtab(i)) break; - } - - ix(ic) = std::max(i-1,1); - auto ip1 = std::min(ix(ic)+1,im); - auto dx = (xtab(ip1)-xtab(ix(ic))); - - if(abs(dx) > 1.0-20) { - t(ic)=(x(ic)-xtab(ix(ic)))/dx; - } else { - t(ic)=0.; - } - }); - } else { - parallel_for (SimpleBounds<1>(ncol), YAKL_LAMBDA (int i) - { - ix(i) = 1; - t(i) = 0.; - }); - } - - if(jm > 1) { - parallel_for(SimpleBounds<1>(ncol), YAKL_LAMBDA (int ic) - { - int j; - for (j = 1; j <= jm; ++j) { - if(y(ic) < ytab(j)) break; - } - - jy(ic) = std::max(j-1,1); - auto jp1 = std::min(jy(ic)+1,jm); - auto dy = (ytab(jp1)-ytab(jy(ic))); - - if(std::abs(dy) > 1.e-20) { - u(ic) = (y(ic)-ytab(jy(ic)))/dy; - if(u(ic) < 0. || u(ic) > 1.) - amrex::Print() << "u= " << u(ic) << "; y= " << y(ic) << "; ytab= " - << ytab(jy(ic)) << "; dy= " << dy << std::endl; - } else { - u(ic)=0.; - } - }); - } else { - parallel_for (SimpleBounds<1>(ncol), YAKL_LAMBDA (int i) - { - jy(i) = 1; - u(i) = 0.; - }); - } - } - - parallel_for (SimpleBounds<2>(ncol, km), YAKL_LAMBDA (int ic, int k) - { - tu(ic) = t(ic)*u(ic); - tuc(ic) = t(ic)-tu(ic); - tcuc(ic) = 1.-tuc(ic)-u(ic); - tcu(ic) = u(ic)-tu(ic); - auto jp1 = std::min(jy(ic)+1,jm); - auto ip1 = std::min(ix(ic)+1,im); - out(ic,k) = tcuc(ic)*table(k,ix(ic),jy(ic))+tuc(ic)*table(k,ip1 ,jy(ic)) - +tu(ic)*table(k,ip1 ,jp1 )+tcu(ic)*table(k,ix(ic),jp1 ); - }); - } // subroutine binterp - - inline - void modal_aero_calcsize_diag (int list_idx, const real3d& dgnum_m) - { - real2d dgncur_a("dgncur",ncol,nlev); // (pcols,pver) - real2d mode_num("mode_num",ncol,nlev); // mode number mixing ratio - real2d specmmr("specmmr",ncol,nlev); // specie mmr - real2d dryvol_a("dryvol_a",ncol,nlev); // interstital aerosol dry volume (cm^3/mol_air) - - for (auto n = 1; n <= nmodes; ++n) { - parallel_for(SimpleBounds<2>(ncol, nlev), YAKL_LAMBDA (int i, int k) - { - dgncur_a(i,k) = dgnum_m(i,k,n); - }); - - // get mode properties - real dgnum, dgnumhi, dgnumlo, sigmag; - mam_consti.get_mode_props(list_idx, n-1, dgnum, dgnumhi, dgnumlo, sigmag); - - // get mode number mixing ratio - mam_consti.rad_cnst_get_mode_num(list_idx, n-1, "a", mode_num); - - yakl::memset(dgncur_a, dgnum); - yakl::memset(dryvol_a, 0.0); - - // compute dry volume mixrats = - // sum_over_components{ component_mass mixrat / density } - mam_consti.get_mode_nspec(list_idx, n-1, nspec); - for (auto l1 = 0; l1 < nspec; ++l1) { - real specdens = 1.0e-20; - mam_consti.rad_cnst_get_mam_mmr_by_idx(list_idx, n-1, l1, "a", specmmr); - mam_consti.get_mam_props(list_idx, n-1, l1, specdens); - - // need qmass*dummwdens = (kg/kg-air) * [1/(kg/m3)] = m3/kg-air - real dummwdens = 1.0 / specdens; - top_lev = 1; - - for (auto k=top_lev; k <= nlev; ++k) { - for (auto i=1; i<=ncol; ++i) { - dryvol_a(i,k) = dryvol_a(i,k) + std::max(0.0, specmmr(i,k))*dummwdens; - } - } - } - - auto alnsg = std::log(sigmag); - auto dumfac = std::exp(4.5*std::pow(alnsg, 2))*PI/6.0; - auto voltonumblo = 1./((PI/6.)*(std::pow(dgnumlo, 3))*std::exp(4.5*std::pow(alnsg, 2))); - auto voltonumbhi = 1./((PI/6.)*(std::pow(dgnumhi, 3))*std::exp(4.5*std::pow(alnsg, 2))); - auto v2nmin = voltonumbhi; - auto v2nmax = voltonumblo; - auto dgnxx = dgnumhi; - auto dgnyy = dgnumlo; - - top_lev = 1; - for (auto k = top_lev; k <= nlev; ++k) { - for (auto i = 1; i <= ncol; ++i) { - auto drv_a = dryvol_a(i,k); - auto num_a0 = mode_num(i,k); - auto num_a = std::max(0.0, num_a0); - - if (drv_a > 0.0) { - if (num_a <= drv_a*v2nmin) - dgncur_a(i,k) = dgnxx; - else if (num_a >= drv_a*v2nmax) - dgncur_a(i,k) = dgnyy; - else - dgncur_a(i,k) = std::pow((drv_a/(dumfac*num_a)), 1./3.); - } - } - } - parallel_for(SimpleBounds<2>(ncol, nlev), YAKL_LAMBDA (int i, int k) - { - dgnum_m(i,k,n) = dgncur_a(i,k); - }); - } - } - - // - // calculates aerosol sw radiative properties - // - void modal_aero_sw (int list_idx, real dt, int nnite, - const int1d& idxnite, const bool& is_cmip6_volc, - const real2d& pdeldry, const real2d& pmid, const real2d& temperature, const real2d& qt, - const real2d& ext_cmip6_sw, const int1d& trop_level, - const real3d& tauxar, const real3d& wa, const real3d& ga, - const real3d& fa, const real2d& clear_rh) - { - // local variables - real2d mass("mass",ncol,nlev); // layer mass - real2d air_density("air_density",ncol,nlev); // (kg/m3) - - real2d specmmr("specmmr",ncol,nlev); // species mass mixing ratio - real1d specrefindex_real("specrefindex_real",nswbands); - real1d specrefindex_im("specrefindex_im",nswbands); - std::string spectype; // species type - real hygro_aer; - real volf; - amrex::ignore_unused(volf); - - real2d dgnumwet("dgnumwet", ncol,nlev); // number mode wet diameter - real2d qaerwat("qaerwat", ncol,nlev); // aerosol water (g/g) - real2d wetdens("wetdens", ncol, nlev); - - real sigma_logr_aer; // geometric standard deviation of number distribution - real2d radsurf("radsurf", ncol, nlev); // aerosol surface mode radius - real2d logradsurf("logradsurf", ncol, nlev); // log(aerosol surface mode radius) - real3d cheb("cheb", ncoef, ncol, nlev); - - real1d refr("refr", ncol); // real part of refractive index - real1d refi("refi", ncol); // imaginary part of refractive index - real1d crefin_real("crefin_real",ncol); - real1d crefin_im("crefin_im",ncol); - - real2d refitabsw("refitabsw",prefi,nswbands); - real2d refrtabsw("refrtabsw",prefr,nswbands); - - real4d extpsw("extpsw",ncoef,prefr,prefi,nswbands); - real4d abspsw("abspsw",ncoef,prefr,prefi,nswbands); - real4d asmpsw("asmpsw",ncoef,prefr,prefi,nswbands); - - real1d vol("vol", ncol); // volume concentration of aerosol specie (m3/kg) - real1d dryvol("dryvol", ncol); // volume concentration of aerosol mode (m3/kg) - real1d watervol("watervol", ncol); // volume concentration of water in each mode (m3/kg) - real1d wetvol("wetvol", ncol); // volume concentration of wet mode (m3/kg) - - int1d itab("itab", ncol), jtab("jtab", ncol); - real1d ttab("ttab",ncol), utab("utab", ncol); - real2d cext("cext", ncol, ncoef), cabs("cabs", ncol,ncoef), casm("casm",ncol,ncoef); - real1d pext("pext",ncol); // parameterized specific extinction (m2/kg) - real1d specpext("specpext",ncol); // specific extinction (m2/kg) - real1d dopaer("dopaer",ncol); // aerosol optical depth in layer - real1d pabs("pabs",ncol); // parameterized specific absorption (m2/kg) - real1d pasm("pasm",ncol); // parameterized asymmetry factor - real1d palb("palb",ncol); // parameterized single scattering albedo - - // initialize output variables - yakl::memset(tauxar, 0.); - yakl::memset(wa, 0.); - yakl::memset(ga, 0.); - yakl::memset(fa, 0.); - - // zero'th layer does not contain aerosol - // parallel_for(SimpleBounds<2>(ncol, nswbands), YAKL_LAMBDA (int i, int ibnd) { - // wa(i,1,ibnd) = 0.925; - // ga(i,1,ibnd) = 0.850; - // fa(i,1,ibnd) = 0.7225; - // }); - - parallel_for(SimpleBounds<2>(ncol, nlev), YAKL_LAMBDA (int i, int k) - { - mass(i,k) = pdeldry(i,k)*rga; - air_density(i,k) = pmid(i,k)/(rair*temperature(i,k)); - }); - - // Calculate aerosol size distribution parameters and aerosol water uptake - if (clim_modal_aero) { - // radiation diagnostics are not supported for prescribed aerosols cases - if(list_idx != 0) { - amrex::Print() << "Radiation diagnostic calls are not supported for prescribed aerosols\n"; - exit(0); - } - // diagnostic aerosol size calculations - modal_aero_calcsize_diag(list_idx, dgnumdry_m); - } - - // clear_rh provides alternate estimate non-cloudy relative humidity - ModalAeroWateruptake::modal_aero_wateruptake_dr(list_idx, ncol, nlev, nmodes, top_lev, - mam_consti, qt, temperature, pmid, dgnumdry_m, dgnumwet_m, - qaerwat_m, wetdens_m, clear_rh); - - // loop over all aerosol modes - for(auto m = 1; m <= nmodes; ++m) { - parallel_for(SimpleBounds<2>(ncol, nlev), YAKL_LAMBDA (int icol, int ilev) - { - dgnumwet(icol,ilev) = dgnumwet_m(icol,ilev,m); - qaerwat(icol,ilev) = qaerwat_m(icol,ilev,m); - }); - - // get mode properties - mam_consti.get_mode_props(list_idx, m-1, sigma_logr_aer, refrtabsw, - refitabsw, extpsw, abspsw, asmpsw); - - // get mode info - mam_consti.get_mode_nspec(list_idx, m-1, nspec); - - // calc size parameter for all columns - modal_size_parameters(sigma_logr_aer, dgnumwet, radsurf, logradsurf, cheb); - - for(auto isw = 1; isw <= nswbands; ++isw) { - for(auto k = top_lev; k <= nlev; ++k) { - // form bulk refractive index - yakl::memset(crefin_real, 0.); - yakl::memset(crefin_im , 0.); - yakl::memset(dryvol , 0.); - - // aerosol species loop - for(auto l = 0; l < nspec; ++l) { - real specdens = 1.0e-20; - mam_consti.rad_cnst_get_mam_mmr_by_idx(list_idx, m-1, l, "a", specmmr); - mam_consti.get_mam_props(list_idx, m-1, l, specdens, spectype, hygro_aer, - specrefindex_real, specrefindex_im); - for(auto i = 1; i <= ncol; ++i) { - vol(i) = specmmr(i,k)/specdens; - dryvol(i) = dryvol(i) + vol(i); - crefin_real(i) = crefin_real(i) + vol(i)*specrefindex_real(isw); - crefin_im(i) = crefin_im(i) + vol(i)*specrefindex_im(isw); - } - } - - for(auto i = 1; i <= ncol; ++i) { - watervol(i) = qaerwat(i,k)/rhoh2o; - wetvol(i) = watervol(i) + dryvol(i); - if (watervol(i) < 0.) { - if (std::abs(watervol(i)) > 1.e-1*wetvol(i)) { - amrex::Print() << "watervol,wetvol=" << watervol(i) << "; " << wetvol(i) << std::endl; - } - watervol(i) = 0.; - wetvol(i) = dryvol(i); - } - // volume mixing - crefin_real(i) = crefin_real(i)+watervol(i)*crefwswr(isw); - crefin_real(i) = crefin_real(i)/std::max(wetvol(i),1.e-60); - crefin_im(i) = crefin_im(i)+watervol(i)*crefwswi(isw); - crefin_im(i) = crefin_im(i)/std::max(wetvol(i),1.e-60); - refr(i) = crefin_real(i); - refi(i) = std::abs(crefin_im(i)); - } - - // interpolate coefficients linear in refractive index - // first call calcs itab,jtab,ttab,utab - real3d extpswr("extpswr", ncoef, prefr, prefi); - real3d abspswr("abspswr", ncoef, prefr, prefi); - real3d asmpswr("asmpswr", ncoef, prefr, prefi); - real1d refitabswr("refitabswr", prefi); - real1d refrtabswr("refrtabswr", prefr); - - parallel_for(SimpleBounds<3>(ncoef,prefr,prefi), YAKL_LAMBDA (int icoef, int irefr, int irefi) - { - extpswr(icoef,irefr,irefi) = extpsw(icoef,irefr,irefi,isw); - abspswr(icoef,irefr,irefi) = abspsw(icoef,irefr,irefi,isw); - asmpswr(icoef,irefr,irefi) = asmpsw(icoef,irefr,irefi,isw); - refitabswr(irefi) = refitabsw(irefi,isw); - refrtabswr(irefr) = refrtabsw(irefr,isw); - }); - - yakl::memset(itab, 0); - binterp(extpswr, ncol, ncoef, prefr, prefi, refr, refi, - refrtabswr, refitabswr, itab, jtab, ttab, utab, cext); - binterp(abspswr, ncol, ncoef, prefr, prefi, refr, refi, - refrtabswr, refitabswr, itab, jtab, ttab, utab, cabs); - binterp(asmpswr, ncol, ncoef, prefr, prefi, refr, refi, - refrtabswr, refitabswr, itab, jtab, ttab, utab, casm); - - // parameterized optical properties - for(auto i=1; i <= ncol; ++i) { - if (logradsurf(i,k) <= xrmax) { - pext(i) = 0.5*cext(i,1); - for(auto nc = 2; nc <= ncoef; ++nc) { - pext(i) = pext(i) + cheb(nc,i,k)*cext(i,nc); - } - pext(i) = std::exp(pext(i)); - } else { - pext(i) = 1.5/(radsurf(i,k)*rhoh2o); // geometric optics - } - - // convert from m2/kg water to m2/kg aerosol - specpext(i) = pext(i); - pext(i) = pext(i)*wetvol(i)*rhoh2o; - pabs(i) = 0.5*cabs(i,1); - pasm(i) = 0.5*casm(i,1); - for(auto nc = 2; nc <= ncoef; ++nc) { - pabs(i) = pabs(i) + cheb(nc,i,k)*cabs(i,nc); - pasm(i) = pasm(i) + cheb(nc,i,k)*casm(i,nc); - } - pabs(i) = pabs(i)*wetvol(i)*rhoh2o; - pabs(i) = std::max(0.,pabs(i)); - pabs(i) = std::min(pext(i),pabs(i)); - palb(i) = 1.-pabs(i)/std::max(pext(i),1.e-40); - dopaer(i) = pext(i)*mass(i,k); - } - - for (auto i = 1; i <= ncol; ++i) { - if ((dopaer(i) <= -1.e-10) || (dopaer(i) >= 30.)) { - if (dopaer(i) <= -1.e-10) - amrex::Print() << "ERROR: Negative aerosol optical depth in this layer.\n"; - else { - // reset to the bound value - dopaer(i) = 25.0; - amrex::Print() << "WARNING: Aerosol optical depth is unreasonably high in this layer.\n"; - } - - for(auto l = 1; l < nspec; ++l) { - real specdens = 1.0e-20; - mam_consti.rad_cnst_get_mam_mmr_by_idx(list_idx, m-1, l, "a", specmmr); - mam_consti.get_mam_props_sw(list_idx, m-1, l, specdens, specrefindex_real, specrefindex_im); - volf = specmmr(i,k)/specdens; - } - if (dopaer(i) < -1.e-10) { - amrex::Print() << "*** halting with error!\n"; - exit(0); - } - } - } - - for(auto i=1; i <= ncol; ++i) { - tauxar(i,k,isw) = tauxar(i,k,isw) + dopaer(i); - wa(i,k,isw) = wa(i,k,isw) + dopaer(i)*palb(i); - ga(i,k,isw) = ga(i,k,isw) + dopaer(i)*palb(i)*pasm(i); - fa(i,k,isw) = fa(i,k,isw) + dopaer(i)*palb(i)*pasm(i)*pasm(i); - } - } //end do ! nlev - } //end do ! swbands - } // nmodes - } // modal_aero_sw - - // - // calculates aerosol lw radiative properties - // - void modal_aero_lw (int list_idx, real dt, - const real2d& pdeldry, const real2d& pmid, const real2d& temperature, const real2d& qt, - const real3d& tauxar, const real2d& clear_rh) - { - real sigma_logr_aer; // geometric standard deviation of number distribution - real alnsg_amode; - real1d xrad("xrad", ncol); - real3d cheby("cheby", ncoef, ncol, nlev); // chebychef polynomials - real2d mass("mass",ncol,nlev); // layer mass - - //real2d specmmr(:,:) ! species mass mixing ratio - real specdens; // species density (kg/m3) - real1d specrefrindex("specrefrindex", ncol); // species refractive index - real1d specrefiindex("specrefiindex", ncol); - - real1d vol("vol",ncol); // volume concentration of aerosol specie (m3/kg) - real1d dryvol("dryvol",ncol); // volume concentration of aerosol mode (m3/kg) - real1d wetvol("wetvol",ncol); // volume concentration of wet mode (m3/kg) - real1d watervol("watervol",ncol); // volume concentration of water in each mode (m3/kg) - real1d refr("refr",ncol); // real part of refractive index - real1d refi("refi",ncol); // imaginary part of refractive index - real1d crefinr("crefinr", ncol); - real1d crefini("crefini", ncol); - - real2d refrtablw("refrtablw",prefr,nlwbands); // table of real refractive indices for aerosols - real2d refitablw("refitablw",prefi,nlwbands); // table of imag refractive indices for aerosols - real4d absplw("absplw",ncoef,prefr,prefi,nlwbands); // specific absorption - - int1d itab("itab",ncol), jtab("jtab",ncol); - real1d ttab("ttab",ncol), utab("utab",ncol); - real2d cabs("cabs",ncol,ncoef); - real1d pabs("pabs",ncol); // parameterized specific absorption (m2/kg) - real1d dopaer("dopaer",ncol); // aerosol optical depth in layer - - real2d specmmr; - real2d dgnumwet; // wet number mode diameter (m) - real2d qaerwat; // aerosol water (g/g) - - constexpr int nerrmax_dopaer=1000; - int nerr_dopaer = 0; - real volf; // volume fraction of insoluble aerosol - amrex::ignore_unused(volf); - - // initialize output variables - yakl::memset(tauxar, 0.); - - // dry mass in each cell - parallel_for (SimpleBounds<2> (ncol, nlev), YAKL_LAMBDA (int icol, int ilev) - { - mass(icol,ilev) = pdeldry(icol,ilev)*rga; - }); - - // Calculate aerosol size distribution parameters and aerosol water uptake - if (clim_modal_aero) { // For prescribed aerosol codes - //radiation diagnostics are not supported for prescribed aerosols cases - if(list_idx != 0) - amrex::Print() << "Radiation diagnostic calls are not supported for prescribed aerosols\n"; - // diagnostic aerosol size calculations - modal_aero_calcsize_diag(list_idx, dgnumdry_m); - } - - // clear_rh provides alternate estimate non-cloudy relative humidity - ModalAeroWateruptake::modal_aero_wateruptake_dr(list_idx, ncol, nlev, nmodes, top_lev, - mam_consti, qt, temperature, pmid, dgnumdry_m, dgnumwet_m, - qaerwat_m, wetdens_m, clear_rh); - - for(auto m = 0; m < nmodes; ++m) { - parallel_for (SimpleBounds<2> (ncol, nlev), YAKL_LAMBDA (int icol, int ilev) - { - dgnumwet(icol, ilev) = dgnumwet_m(icol,ilev,m); - qaerwat(icol, ilev) = qaerwat_m(icol,ilev,m); - }); - - // get mode properties - mam_consti.get_mode_props(list_idx, m, sigma_logr_aer, - refrtablw, refitablw, absplw); - // get mode info - mam_consti.get_mode_nspec(list_idx, m, nspec); - - // calc size parameter for all columns - // this is the same calculation that's done in modal_size_parameters, but there - // some intermediate results are saved and the chebyshev polynomials are stored - // in a array with different index order. Could be unified. - top_lev = 1; - for(auto k = top_lev; k <= nlev; ++k) { - for(auto i = 1; i <= ncol; ++i) { - alnsg_amode = std::log(sigma_logr_aer); - // convert from number diameter to surface area - xrad(i) = std::log(0.5*dgnumwet(i,k)) + 2.0*alnsg_amode*alnsg_amode; - // normalize size parameter - xrad(i) = std::max(xrad(i), xrmin); - xrad(i) = std::min(xrad(i), xrmax); - xrad(i) = (2*xrad(i)-xrmax-xrmin)/(xrmax-xrmin); - // chebyshev polynomials - cheby(1,i,k) = 1.0; - cheby(2,i,k) = xrad(i); - for(auto nc = 3; nc <= ncoef; ++nc) { - cheby(nc,i,k) = 2.0*xrad(i)*cheby(nc-1,i,k)-cheby(nc-2,i,k); - } - } - } - - for(auto ilw = 1; ilw <= nlwbands; ++ilw) { - for(auto k = top_lev; k <= nlev; ++k) { - // form bulk refractive index. Use volume mixing for infrared - yakl::memset(crefinr, 0.0); - yakl::memset(crefini, 0.0); - yakl::memset(dryvol, 0.0); - // aerosol species loop - for(auto l = 0; l < nspec; ++l) { - mam_consti.rad_cnst_get_mam_mmr_by_idx(list_idx, m, l, "a", specmmr); - mam_consti.get_mam_props_lw(list_idx, m, l, specdens, specrefrindex, specrefiindex); - for(auto i = 1; i <= ncol; ++i) { - vol(i) = specmmr(i,k)/specdens; - dryvol(i) = dryvol(i) + vol(i); - crefinr(i) = crefinr(i) + vol(i)*specrefrindex(ilw); - crefini(i) = crefini(i) + vol(i)*specrefiindex(ilw); - } - } - for(auto i = 1; i <= ncol; ++i) { - watervol(i) = qaerwat(i,k)/rhoh2o; - wetvol(i) = watervol(i) + dryvol(i); - if (watervol(i) < 0.0) { - if (abs(watervol(i)) > 1.e-1*wetvol(i)) { - amrex::Print() << "watervol is too large\n"; - } - watervol(i) = 0.; - wetvol(i) = dryvol(i); - } - crefinr(i) = crefinr(i) + watervol(i)*crefwlwr(ilw); - crefini(i) = crefini(i) + watervol(i)*crefwlwi(ilw); - if (wetvol(i) > 1.e-40) { - crefinr(i) = crefinr(i)/wetvol(i); - crefini(i) = crefini(i)/wetvol(i); - } - refr(i) = crefinr(i); - refi(i) = crefini(i); - } - - // interpolate coefficients linear in refractive index - // first call calcs itab,jtab,ttab,utab - yakl::memset(itab, 0); - - real3d absplwr("absplwr", ncoef, prefr, prefi); - real1d refitablwr("refitablwr", prefi); - real1d refrtablwr("refrtablwr", prefr); - - parallel_for(SimpleBounds<3>(ncoef,prefr,prefi) , YAKL_LAMBDA (int icoef, int irefr, int irefi) - { - absplwr(icoef,irefr,irefi) = absplw(icoef,irefr,irefi,ilw); - refitablwr(irefi) = refitablw(irefi,ilw); - refrtablwr(irefr) = refrtablw(irefr,ilw); - }); - - binterp(absplwr, ncol, ncoef, prefr, prefi, refr, refi, - refrtablwr, refitablwr, itab, jtab, ttab, utab, cabs); - - // parameterized optical properties - for(auto i = 1; i <= ncol; ++i) { - pabs(i) = 0.5*cabs(i,1); - for(auto nc = 2; nc <= ncoef; ++nc) { - pabs(i) = pabs(i) + cheby(nc,i,k)*cabs(i,nc); - } - pabs(i) = pabs(i)*wetvol(i)*rhoh2o; - pabs(i) = std::max(0.,pabs(i)); - dopaer(i) = pabs(i)*mass(i,k); - } - - for(auto i = 1; i <= ncol; ++i) { - if ((dopaer(i) <= -1.e-10) || (dopaer(i) >= 20.)) { - if (dopaer(i) <= -1.e-10) - amrex::Print() << "ERROR: Negative aerosol optical depth in this layer.\n"; - else - amrex::Print() << "WARNING: Aerosol optical depth is unreasonably high in this layer.\n"; - - for(auto l = 0; l < nspec; ++l) { - mam_consti.rad_cnst_get_mam_mmr_by_idx(list_idx, m, l, "a", specmmr); - mam_consti.get_mam_props_lw(list_idx, m, l, specdens, specrefrindex, specrefiindex); - volf = specmmr(i,k)/specdens; - } - - nerr_dopaer = nerr_dopaer + 1; - if (nerr_dopaer >= nerrmax_dopaer || dopaer(i) < -1.e-10) { - amrex::Print() << "*** halting after nerr_dopaer = " << nerr_dopaer; - exit(EXIT_FAILURE); - } - } - } - - for(auto i = 1; i <= ncol; ++i) { - tauxar(i,k,ilw) = tauxar(i,k,ilw) + dopaer(i); - } - } // k = top_lev, nlev - } // nlwbands - } // m = 1, nmodes - } // modal_aero_lw -}; -#endif // ERF_MAM4_AERO_H_ diff --git a/Source/Radiation/ERF_Mam4Constituents.H b/Source/Radiation/ERF_Mam4Constituents.H deleted file mode 100644 index e90896871..000000000 --- a/Source/Radiation/ERF_Mam4Constituents.H +++ /dev/null @@ -1,1210 +0,0 @@ -// -// Provide constituent distributions and properties to the radiation and -// cloud microphysics routines. -// -// The logic to control which constituents are used in the climate calculations -// and which are used in diagnostic radiation calculations is contained in this module. -// -#include -#include "YAKL_netcdf.h" -#include "RRTMGP_const.h" -#include "ERF_PhysProp.H" -#include "ERF_RadConstants.H" -#include "ERF_Config.H" - -#ifndef ERF_MAM4_CONSTITUENTS_H_ -#define ERF_MAM4_CONSTITUENTS_H_ - -class MamConstituents { - public: - const int N_DIAG = 10; - std::string iceopticsfile, liqopticsfile; - std::string icecldoptics, liqcldoptics; - const bool oldcldoptics = false; - - // max number of externally mixed entities in the climate/diag lists - const int n_rad_cnst = 30; //N_RAD_CNST (hard coded here) - - // type to provide access to the components of a mode - struct mode_component_t { - int nspec; - // For "source" variables below, value is: - std::string source_num_a; // source of interstitial number conc field - std::string camname_num_a; // name registered in pbuf or constituents for number mixing ratio of interstitial species - std::string source_num_c; // source of cloud borne number conc field - std::string camname_num_c; // name registered in pbuf or constituents for number mixing ratio of cloud borne species - std::vector source_mmr_a; // source of interstitial specie mmr fields - std::vector camname_mmr_a; // name registered in pbuf or constituents for mmr of interstitial components - std::vector source_mmr_c; // source of cloud borne specie mmr fields - std::vector camname_mmr_c; // name registered in pbuf or constituents for mmr of cloud borne components - std::vector type; // specie type (as used in MAM code) - std::vector props; // file containing specie properties - int idx_num_a; // index in pbuf or constituents for number mixing ratio of interstitial species - int idx_num_c; // index in pbuf for number mixing ratio of interstitial species - std::vector idx_mmr_a; // index in pbuf or constituents for mmr of interstitial species - std::vector idx_mmr_c; // index in pbuf for mmr of interstitial species - std::vector idx_props; // ID used to access physical properties of mode species from phys_prop module - }; - - // type to provide access to all modes - struct modes_t { - int nmodes; - std::vector names; // names used to identify a mode in the climate/diag lists - std::vector types; // type of mode (as used in MAM code) - std::vector comps; // components which define the mode - }; - - //Storage for gas components in the climate/diagnostic lists - struct gas_t { - std::string source; // A for state (advected), N for pbuf (non-advected), Z for zero - std::string camname; // name of constituent in physics state or buffer - std::string mass_name; // name for mass per layer field in history output - int idx; // index from constituents or from pbuf - }; - - struct gaslist_t { - int ngas; - std::string list_id; - std::vector gas; // dimension(ngas) where ngas = nradgas is from radconstants - }; - - //Storage for bulk aerosol components in the climate/diagnostic lists - struct aerosol_t { - std::string source; // A for state (advected), N for pbuf (non-advected), Z for zero - std::string camname; // name of constituent in physics state or buffer - std::string physprop_file; // physprop filename - std::string mass_name; // name for mass per layer field in history output - int idx; // index of constituent in physics state or buffer - int physprop_id; // ID used to access physical properties from phys_prop module - }; - - struct aerlist_t { - int numaerosols; // number of aerosols - std::string list_id; - std::vector aer; // dimension(numaerosols) - }; - - // storage for modal aerosol components in the climate/diagnostic lists - struct modelist_t { - int nmodes; // number of modes - std::string list_id; - std::vector idx; // index of the mode in the mode definition object - std::vector physprop_files; // physprop filename - std::vector idx_props; // index of the mode properties in the physprop object - }; - - // gasses used in climate/diagnostic calculations - std::vector gaslist; - - // list of aerosols used in climate/diagnostic calcs - std::vector aerosollist; - - // list of aerosol modes used in climate/diagnostic calcs - std::vector ma_list; - - // mode definitions - modes_t modes; - - // physics prop - PhysProp prop; - - const int num_mode_types = 8; - const int num_spec_types = 8; - const std::vector mode_type_names {"accum", "aitken", "primary_carbon", "fine_seasalt", - "fine_dust", "coarse", "coarse_seasalt", "coarse_dust"}; - - const std::vector spec_type_names {"sulfate", "ammonium", "nitrate", "p-organic", - "s-organic", "black-c", "seasalt", "dust"}; - public: - // constructor - MamConstituents () - { - // - // NOTE: - // we hard-coded the use nmodes = 3, and nspeces = {6, 3, 3} - // - // allocate components that depend on nmodes - auto erf_rad_data_dir = getRadiationDataDir() + "/"; - - // initialize yakl - if (!yakl::isInitialized()) yakl::init(); - - int nmodes = 3; - std::vector num_spec{6, 3, 3}; - - modes.nmodes = 3; - modes.names.resize(nmodes); - modes.types.resize(nmodes); - modes.comps.resize(nmodes); - - modes.comps[0].nspec = num_spec[0]; - modes.names[0] = "mam3_mode_0"; - modes.types[0] = "accum"; - - modes.comps[0].source_num_a = "N"; - modes.comps[0].camname_num_a = "num_a1"; - modes.comps[0].source_num_c = "N"; - modes.comps[0].camname_num_c = "num_c1"; - - modes.comps[0].type.resize(num_spec[0]); - modes.comps[0].type[0] = "sulfate"; - modes.comps[0].type[1] = "p-organic"; - modes.comps[0].type[2] = "s-organic"; - modes.comps[0].type[3] = "black-c"; - modes.comps[0].type[4] = "dust"; - modes.comps[0].type[5] = "seasalt"; - - modes.comps[0].props.resize(num_spec[0]); - modes.comps[0].idx_props.resize(num_spec[0]); - modes.comps[0].props[0] = erf_rad_data_dir + "sulfate_rrtmg_c080918.nc"; - modes.comps[0].props[1] = erf_rad_data_dir + "ocpho_rrtmg_c101112.nc"; - modes.comps[0].props[2] = erf_rad_data_dir + "ocphi_rrtmg_c100508.nc"; - modes.comps[0].props[3] = erf_rad_data_dir + "bcpho_rrtmg_c100508.nc"; - modes.comps[0].props[4] = erf_rad_data_dir + "dust_aeronet_rrtmg_c141106.nc"; - modes.comps[0].props[5] = erf_rad_data_dir + "ssam_rrtmg_c100508.nc"; - - modes.comps[1].nspec = num_spec[1]; - modes.names[1] = "mam3_mode_1"; - modes.types[1] = "aitken"; - - modes.comps[1].source_num_a = "N"; - modes.comps[1].camname_num_a = "num_a2"; - modes.comps[1].source_num_c = "N"; - modes.comps[1].camname_num_c = "num_c2"; - - modes.comps[1].type.resize(num_spec[1]); - modes.comps[1].type[0] = "sulfate"; - modes.comps[1].type[1] = "s-organic"; - modes.comps[1].type[2] = "seasalt"; - - modes.comps[1].props.resize(num_spec[1]); - modes.comps[1].idx_props.resize(num_spec[1]); - modes.comps[1].props[0] = erf_rad_data_dir + "sulfate_rrtmg_c080918.nc"; - modes.comps[1].props[1] = erf_rad_data_dir + "ocphi_rrtmg_c100508.nc"; - modes.comps[1].props[2] = erf_rad_data_dir + "ssam_rrtmg_c100508.nc"; - - modes.comps[2].nspec = num_spec[2]; - modes.names[2] = "mam3_mode_2"; - modes.types[2] = "coarse"; - - modes.comps[2].source_num_a = "N"; - modes.comps[2].camname_num_a = "num_a3"; - modes.comps[2].source_num_c = "N"; - modes.comps[2].camname_num_c = "num_c3"; - - modes.comps[2].type.resize(num_spec[2]); - modes.comps[2].type[0] = "dust"; - modes.comps[2].type[1] = "seasalt"; - modes.comps[2].type[2] = "sulfate"; - - modes.comps[2].props.resize(num_spec[2]); - modes.comps[2].idx_props.resize(num_spec[2]); - modes.comps[2].props[0] = erf_rad_data_dir + "dust_aeronet_rrtmg_c141106.nc"; - modes.comps[2].props[1] = erf_rad_data_dir + "ssam_rrtmg_c100508.nc"; - modes.comps[2].props[2] = erf_rad_data_dir + "sulfate_rrtmg_c080918.nc"; - - gaslist.resize(N_DIAG); - aerosollist.resize(N_DIAG); - ma_list.resize(N_DIAG); - - // Set the list_id fields which distinguish the climate and diagnostic lists - for(auto i = 0; i < N_DIAG; ++i) { - aerosollist[i].list_id = ""; - gaslist[i].list_id = ""; - ma_list[i].list_id = ""; - } - - gaslist[0].ngas = 8; - gaslist[0].gas.resize(gaslist[0].ngas); - gaslist[0].gas[0].source = "A"; - gaslist[0].gas[1].source = "N"; - gaslist[0].gas[2].source = "N"; - gaslist[0].gas[3].source = "N"; - gaslist[0].gas[4].source = "N"; - gaslist[0].gas[5].source = "N"; - gaslist[0].gas[6].source = "N"; - gaslist[0].gas[7].source = "N"; - - gaslist[0].gas[0].camname = "Q"; - gaslist[0].gas[1].camname = "O3"; - gaslist[0].gas[2].camname = "O2"; - gaslist[0].gas[3].camname = "CO2"; - gaslist[0].gas[4].camname = "N2O"; - gaslist[0].gas[5].camname = "CH4"; - gaslist[0].gas[6].camname = "CFC11"; - gaslist[0].gas[7].camname = "CFC12"; - - ma_list[0].nmodes = 3; - ma_list[0].idx.resize(ma_list[0].nmodes); - ma_list[0].idx[0] = 0; - ma_list[0].idx[1] = 1; - ma_list[0].idx[2] = 2; - - ma_list[0].physprop_files.resize(ma_list[0].nmodes); - ma_list[0].idx_props.resize(ma_list[0].nmodes); - - ma_list[0].physprop_files.resize(ma_list[0].nmodes); - ma_list[0].physprop_files[0] = erf_rad_data_dir + "mam3_mode1_rrtmg_c110318.nc"; - ma_list[0].physprop_files[1] = erf_rad_data_dir + "mam3_mode2_rrtmg_c110318.nc"; - ma_list[0].physprop_files[2] = erf_rad_data_dir + "mam3_mode3_rrtmg_c110318.nc"; - - ma_list[0].idx_props.resize(ma_list[0].nmodes); - ma_list[0].idx_props[0] = 78384393; - ma_list[0].idx_props[1] = 8192; - ma_list[0].idx_props[2] = 78384392; - - // Start with the bulk aerosol species in the climate/diagnostic lists. - // The physprop_accum_unique_files routine has the side effect of returning the number - // of bulk aerosols in each list (they're identified by type='A'). - for(auto i = 0; i < ma_list[0].nmodes; ++i) - prop.physprop_accum_unique_files(ma_list[0].physprop_files[i], "A"); - - // Add physprop files for the species from the mode definitions. - for(auto i = 0; i < modes.nmodes; ++i) { - for(auto j = 0; j < modes.comps[i].nspec; ++j) - prop.physprop_accum_unique_files(modes.comps[i].props[j], "M"); - } - - // initialize the gas and aerosol lists - rad_cnst_init(); - } - - // The initialization of the gas and aerosol lists is finished by - // 1) read the physprop files - // 2) find the index of each constituent in the constituent or physics buffer arrays - // 3) find the index of the aerosol constituents used to access its properties from the - // physprop module. - inline - void rad_cnst_init () - { - //int num_aerosols; - //constexpr bool stricttest = true; - - // memory to point to if zero value requested - //allocate(zero_cols(pcols,pver)) - //zero_cols = 0.; - - // Allocate storage for the physical properties of each aerosol; read properties from - // the data files. - prop.physprop_init(); - - // Finish initializing the mode definitions. - init_mode_comps(); - - for(auto i = 0; i < ma_list.size(); ++i) { - for(auto imode = 0; imode < ma_list[i].nmodes; ++imode) { - // get the physprop_id from the phys_prop module - ma_list[i].idx_props[imode] = prop.physprop_get_id(ma_list[i].physprop_files[imode]); - } - } - } - - inline - void init_mode_comps () - { - for(auto m = 0; m < modes.nmodes; ++m) { - for(auto ispec = 0; ispec < modes.comps[m].nspec; ++ispec) { - modes.comps[m].idx_props[ispec] = prop.physprop_get_id(modes.comps[m].props[ispec]); - if (modes.comps[m].idx_props[ispec] == -1) - amrex::Print() << "ERROR idx not found for " << modes.comps[m].props[ispec] << std::endl; - } - } - } - - // Return pointer to mass mixing ratio for the gas from the specified - // climate or diagnostic list. - inline - void rad_cnst_get_gas (int list_idx, const std::string& gasname, real2d& mmr) const - { - gaslist_t list; - if (list_idx >= 0 && list_idx <= N_DIAG) { - list = gaslist[list_idx]; - } else { - amrex::Print() << " list_idx= " << list_idx << "\n"; - } - - // Get index of gas in internal arrays. rad_gas_index will abort if the - // specified gasname is not recognized by the radiative transfer code. - auto igas = RadConstants::rad_gas_index(gasname); - - // Get data source - auto source = list.gas[igas].source; - auto idx = list.gas[igas].idx; - - if (idx == 7) { - yakl::memset(mmr, 0.23); - } else { - yakl::memset(mmr, 1.0e-6); - } - - // select case( source ) - // case ('A') - // mmr => state%q(:,:,idx) - // case ('N') - // pbuf_get_field(pbuf, idx, mmr) - // case ('Z') - // mmr => zero_cols - // end select - } - - void get_nmodes (int list_idx, int& nmodes) const - { - auto m_list = ma_list[list_idx]; - nmodes = m_list.nmodes; - } - - void get_ngas (int list_idx, int& ngas) const - { - auto g_list = gaslist[list_idx]; - ngas = g_list.ngas; - } - - void get_naero (int list_idx, int& naero) const - { - auto a_list = aerosollist[list_idx]; - naero = a_list.numaerosols; - } - - void get_gas_names (int list_idx, std::vector& gasnames, bool& use_data_o3) const - { - auto g_list = gaslist[list_idx]; - - gasnames.reserve(g_list.ngas); - - for(auto i = 0; i < g_list.ngas; ++i) gasnames[i] = g_list.gas[i].camname; - - // Does the climate calculation use data ozone? - // get index of O3 in gas list - auto igas = RadConstants::rad_gas_index("O3"); - // Get data source - auto source = g_list.gas[igas].source; - use_data_o3 = false; - if (source == "N") use_data_o3 = true; - } - - void get_aero_names (int list_idx, std::vector& aernames) const - { - auto a_list = aerosollist[list_idx]; - - aernames.reserve(a_list.numaerosols); - - for(auto i = 0; i < a_list.numaerosols; ++i) aernames[i] = a_list.aer[i].camname; - } - - void get_mode_nspec (int list_idx, int m_idx, int& nspec) const - { - auto m_list = ma_list[list_idx]; - auto mm = m_list.idx[m_idx]; - - // number of species in the mode - nspec = modes.comps[mm].nspec; - } - - // Return info about modal aerosol list - void rad_cnst_get_info_by_mode (int list_idx, int m_idx, - std::string& mode_type, std::string& num_name, - std::string& num_name_cw, int& nspec) const - { - auto m_list = ma_list[list_idx]; - - // check for valid mode index - auto nmodes = m_list.nmodes; - if (m_idx < 0 || m_idx > nmodes) - amrex::Print() << "ERROR - invalid mode index: " << m_idx << "\n"; - - // get index into the mode definition object - auto mm = m_list.idx[m_idx]; - - // mode type - if (mode_type.empty()) mode_type = modes.types[mm]; - - // number of species in the mode - if (nspec == 0) nspec = modes.comps[mm].nspec; - - // name of interstitial number mixing ratio - if (num_name.empty()) num_name = modes.comps[mm].camname_num_a; - - // name of cloud borne number mixing ratio - if (num_name_cw.empty()) num_name_cw = modes.comps[mm].camname_num_c; - } - - // Return info about modal aerosol lists - void rad_cnst_get_info_by_mode_spec (int list_idx, int m_idx, int s_idx, - std::string& spec_type, std::string& spec_name, - std::string& spec_name_cw) const - { - auto m_list = ma_list[list_idx]; - - // check for valid mode index - auto nmodes = m_list.nmodes; - if (m_idx < 0 || m_idx > nmodes) - amrex::Print() << "ERROR - invalid mode index: " << m_idx << "\n"; - - // get index into the mode definition object - auto mm = m_list.idx[m_idx]; - - // check for valid specie index - auto nspec = modes.comps[mm].nspec; - if (s_idx < 0 || s_idx > nspec) - amrex::Print() << "ERROR - invalid specie index: " << s_idx << "\n"; - - // specie type - if (spec_type.empty()) spec_type = modes.comps[mm].type[s_idx]; - - // interstitial specie name - if (spec_name.empty()) spec_name = modes.comps[mm].camname_mmr_a[s_idx]; - - // cloud borne specie name - if (spec_name_cw.empty()) spec_name_cw = modes.comps[mm].camname_mmr_c[s_idx]; - } - - // Return info about modes in the specified climate/diagnostics list - void rad_cnst_get_info_by_spectype (int list_idx, const std::string& spectype, - int& mode_idx, int& spec_idx) const - { - auto m_list = ma_list[list_idx]; - - // number of modes in specified list - auto nmodes = m_list.nmodes; - - // loop through modes in specified climate/diagnostic list - auto found_spectype = false; - for(auto i = 0; i < nmodes; ++i) { - - // get index of the mode in the definition object - auto m_idx = m_list.idx[i]; - - // number of species in the mode - auto nspec = modes.comps[m_idx].nspec; - - // loop through species looking for spectype - for(auto ispec = 0; ispec < nspec; ++ispec) { - if (modes.comps[m_idx].type[ispec] == spectype) { - if (mode_idx) mode_idx = i; - if (spec_idx) spec_idx = ispec; - found_spectype = true; - exit(0); - } - } - if (found_spectype) exit(0); - } - - if (!found_spectype) { - mode_idx = -1; - spec_idx = -1; - } - } - - int rad_cnst_get_mode_idx (int list_idx, const std::string& mode_type) const - { - // if mode type not found return -1 - int mode_idx = -1; - - // specified mode list - auto m_list = ma_list[list_idx]; - - // number of modes in specified list - auto nmodes = m_list.nmodes; - - // loop through modes in specified climate/diagnostic list - for(auto i = 0; i < nmodes; ++i) { - // get index of the mode in the definition object - auto m_idx = m_list.idx[i]; - - // look in mode definition object (modes) for the mode types - if (modes.types[m_idx] == mode_type) { - mode_idx = i; - exit(0); - } - } - return mode_idx; - } - - int rad_cnst_get_spec_idx (int list_idx, int mode_idx, - const std::string& spec_type) const - { - // if specie type not found return -1 - int spec_idx = -1; - - // modes in specified list - auto m_list = ma_list[list_idx]; - - // get index of the specified mode in the definition object - auto m_idx = m_list.idx[mode_idx]; - - // object containing the components of the mode - auto mode_comps = modes.comps[m_idx]; - - // number of species in specified mode - auto nspec = mode_comps.nspec; - - // loop through species in specified mode - for(auto i=0; i= 0 && list_idx <= N_DIAG) { - aerlist = aerosollist[list_idx]; - } else { - amrex::Print() << "list_idx = " << list_idx << "\n"; - } - - auto naer = aerlist.numaerosols; - for(auto i = 0; i < naer; ++i) { - source = aerlist.aer[i].source; - idx = aerlist.aer[i].idx; - name = aerlist.aer[i].mass_name; - // construct name for column burden field by replacing the 'm_' prefix by 'cb_' - cbname = "cb_"; // name(3:len_trim(name)) - - // select case( source ) - // case ('A') - // mmr => state%q(:,:,idx) - // case ('N') - // call pbuf_get_field(pbuf, idx, mmr) - // end select - - // mass(:ncol,:) = mmr(:ncol,:) * state%pdeldry(:ncol,:) * rga - - // cb(:ncol) = sum(mass(:ncol,:),2); - - } - - // Associate pointer with requested gas list - auto g_list = gaslist[list_idx]; - - auto ngas = g_list.ngas; - for(auto i = 0; i < ngas; ++i) { - source = g_list.gas[i].source; - idx = g_list.gas[i].idx; - name = g_list.gas[i].mass_name; - cbname = "cb_"; // name(3:len_trim(name)) - - // select case( source ) - // case ('A') - // mmr => state%q(:,:,idx) - // case ('N') - // call pbuf_get_field(pbuf, idx, mmr) - // end select - - // mass(:ncol,:) = mmr(:ncol,:) * state%pdeldry(:ncol,:) * rga - - // cb(:ncol) = sum(mass(:ncol,:),2) - } - } - - // Return pointer to mass mixing ratio for the aerosol from the specified - // climate or diagnostic list. - void rad_cnst_get_aer_mmr_by_idx (int list_idx, int aer_idx, real2d& mmr) const - { - aerlist_t aerlist; - - if (list_idx >= 0 && list_idx <= N_DIAG) { - aerlist = aerosollist[list_idx]; - } else { - amrex::Print() << " list_idx = " << list_idx << std::endl; - exit(0); - } - - // Check for valid input aerosol index - if (aer_idx < 0 || aer_idx > aerlist.numaerosols) { - amrex::Print() << " aer_idx= " << aer_idx << " numaerosols= " << aerlist.numaerosols << std::endl; - exit(0); - } - - // Get data source - auto source = aerlist.aer[aer_idx].source; - //auto idx = aerlist.aer[aer_idx].idx; - } - - // Return pointer to mass mixing ratio for the modal aerosol specie from the specified - // climate or diagnostic list. - void rad_cnst_get_mam_mmr_by_idx (int list_idx, int mode_idx, int spec_idx, - const std::string& phase, real2d& mmr) const - { - //int idx; - std::string source; - modelist_t mlist; - - if (list_idx >= 0 && list_idx <= N_DIAG) { - mlist = ma_list[list_idx]; - } else { - amrex::Print() << "list_idx =" << list_idx << std::endl; - exit(0); - } - - // Check for valid mode index - if (mode_idx < 0 || mode_idx > mlist.nmodes) { - amrex::Print() << " mode_idx= " << mode_idx << " nmodes= " << mlist.nmodes << std::endl; - exit(0); - } - - // Get the index for the corresponding mode in the mode definition object - auto m_idx = mlist.idx[mode_idx]; - - // Check for valid specie index - if (spec_idx < 0 || spec_idx > modes.comps[m_idx].nspec) { - amrex::Print() << " spec_idx= " << spec_idx << " nspec= " << modes.comps[m_idx].nspec << std::endl; - exit(0); - } - - yakl::memset(mmr, 1.80e-17); - - // Get data source - if (phase == "a") { - // source = modes.comps[m_idx].source_mmr_a[spec_idx]; - // idx = modes.comps[m_idx].idx_mmr_a[spec_idx]; - } else if (phase == "c") { - // source = modes.comps[m_idx].source_mmr_c[spec_idx]; - // idx = modes.comps[m_idx].idx_mmr_c[spec_idx]; - } else { - amrex::Print() << "phase= " << phase << std::endl; - exit(0); - } - } - - void rad_cnst_get_mam_mmr_idx (int mode_idx, int spec_idx, int& idx) const - { - modelist_t mlist; - - // assume climate list (i.e., species are in the constituent array) - mlist = ma_list[0]; - - // Check for valid mode index - if (mode_idx < 0 || mode_idx > mlist.nmodes) { - amrex::Print() << "mode_idx= " << mode_idx << " nmodes= " << mlist.nmodes << std::endl; - exit(0); - } - - // Get the index for the corresponding mode in the mode definition object - auto m_idx = mlist.idx[mode_idx]; - - // Check for valid specie index - if (spec_idx < 0 || spec_idx > modes.comps[m_idx].nspec) { - amrex::Print() << "spec_idx= " << spec_idx << " nspec= " << modes.comps[m_idx].nspec << std::endl; - exit(0); - } - - // Assume data source is interstitial since that's what's in the constituent array - // idx = modes.comps[m_idx].idx_mmr_a[spec_idx]; - } - - // Return pointer to number mixing ratio for the aerosol mode from the specified - // climate or diagnostic list. - void rad_cnst_get_mode_num (int list_idx, int mode_idx, const std::string& phase, real2d& num) const - { - modelist_t mlist; - std::string source; - //int idx; - - if (list_idx >= 0 && list_idx <= N_DIAG) { - mlist = ma_list[list_idx]; - } else { - amrex::Print() << " list_idx =" << list_idx << std::endl; - exit(0); - } - - // Check for valid mode index - if (mode_idx < 0 || mode_idx > mlist.nmodes) { - amrex::Print() << " mode_idx= " << mode_idx << " nmodes= " << mlist.nmodes << std::endl; - exit(0); - } - - // Get the index for the corresponding mode in the mode definition object - auto m_idx = mlist.idx[mode_idx]; - - // Get data source - if (phase == "a") { - source = modes.comps[m_idx].source_num_a; - // idx = modes.comps[m_idx].idx_num_a; - } else if (phase == "c") { - source = modes.comps[m_idx].source_num_c; - // idx = modes.comps[m_idx].idx_num_c; - } else { - amrex::Print() << " phase= " << phase << std::endl; - exit(1); - } - } - - // Return constituent index of mode number mixing ratio for the aerosol mode in - // the climate list. - - // This is a special routine to allow direct access to information in the - // constituent array inside physics parameterizations that have been passed, - // and are operating over the entire constituent array. The interstitial phase - // is assumed since that's what is contained in the constituent array. - void rad_cnst_get_mode_num_idx (int mode_idx, int& cnst_idx) const - { - modelist_t mlist; - // assume climate list - mlist = ma_list[0]; - - // Check for valid mode index - if (mode_idx < 0 || mode_idx > mlist.nmodes) { - amrex::Print() << " mode_idx= " << mode_idx << " nmodes= " << mlist.nmodes << std::endl; - exit(0); - } - - // Get the index for the corresponding mode in the mode definition object - auto m_idx = mlist.idx[mode_idx]; - - // Check that source is 'A' which means the index is for the constituent array - auto source = modes.comps[m_idx].source_num_a; - if (source != "a") { - amrex::Print() << " source= " << source << std::endl; - exit(0); - } - - // Return index in constituent array - // cnst_idx = modes.comps[m_idx].idx_num_a; - } - - // Return the index of aerosol aer_name in the list specified by list_idx. - int rad_cnst_get_aer_idx (int list_idx, std::string& aer_name) const - { - aerlist_t aerlist; - - if (list_idx >= 0 && list_idx <= N_DIAG) { - aerlist = aerosollist[list_idx]; - } - else { - amrex::Print() << " list_idx =" << list_idx << std::endl; - exit(0); - } - - // Get index in aerosol list for requested name - auto aer_idx = -1; - for(auto i = 0; i < aerlist.numaerosols; ++i) { - if (aer_name == aerlist.aer[i].camname) { - aer_idx = i; - exit(0); - } - } - - if (aer_idx == -1) exit(0); - - return aer_idx; - } - - // Return requested properties for the mode from the specified - // climate or diagnostic list. - void get_mode_props (int list_idx, int mode_idx, real& sigmag, - real& rhcrystal, real& rhdeliques) const - { - modelist_t mlist; - if (list_idx >= 0 && list_idx <= N_DIAG) - mlist = ma_list[list_idx]; - - // Get the physprop index for the requested mode - auto id = mlist.idx_props[mode_idx]; - - prop.get_sigmag(id, sigmag); - prop.get_rhcrystal(id, rhcrystal); - prop.get_rhdeliques(id, rhdeliques); - } - - void get_mode_props (int list_idx, int mode_idx, real& sigmag, - real2d& refrtablw, real2d& refitablw, real4d& absplw) const - { - modelist_t mlist; - if (list_idx >= 0 && list_idx <= N_DIAG) - mlist = ma_list[list_idx]; - - // Get the physprop index for the requested mode - auto id = mlist.idx_props[mode_idx]; - - prop.get_sigmag(id, sigmag); - prop.get_refrtablw(id, refrtablw); - prop.get_refitablw(id, refitablw); - prop.get_absplw(id, absplw); - } - - void get_mode_props (int list_idx, int mode_idx, real& sigmag, real2d& refrtabsw, - real2d& refitabsw, real4d& extpsw, real4d& abspsw, real4d& asmpsw) const - { - modelist_t mlist; - if (list_idx >= 0 && list_idx <= N_DIAG) - mlist = ma_list[list_idx]; - - // Get the physprop index for the requested mode - auto id = mlist.idx_props[mode_idx]; - - prop.get_sigmag(id, sigmag); - prop.get_refrtabsw(id, refrtabsw); - prop.get_refitabsw(id, refitabsw); - prop.get_extpsw(id, extpsw); - prop.get_abspsw(id, abspsw); - prop.get_asmpsw(id, asmpsw); - } - - void get_mode_props (int list_idx, int mode_idx, int& ncoef, int& prefr, int& prefi) const - { - modelist_t mlist; - if (list_idx >= 0 && list_idx <= N_DIAG) - mlist = ma_list[list_idx]; - - // Get the physprop index for the requested mode - auto id = mlist.idx_props[mode_idx]; - - prop.get_ncoef(id, ncoef); - prop.get_prefr(id, prefr); - prop.get_prefi(id, prefi); - } - - inline - void get_mode_props (int list_idx, int mode_idx, real& dgnum, real& dgnumhi, - real& dgnumlo, real& sigmag) const - { - modelist_t mlist; - if (list_idx >= 0 && list_idx <= N_DIAG) - mlist = ma_list[list_idx]; - - // Get the physprop index for the requested mode - auto id = mlist.idx_props[mode_idx]; - - prop.get_dgnum(id, dgnum); - prop.get_dgnumhi(id, dgnumhi); - prop.get_dgnumlo(id, dgnumlo); - prop.get_sigmag(id, sigmag); - } - - inline - void get_mam_density_aer (int list_idx, int mode_idx, int spec_idx, - real& density_aer) const - { - modelist_t mlist; - if (list_idx >= 0 && list_idx <= N_DIAG) - mlist = ma_list[list_idx]; - - // Get the index for the corresponding mode in the mode definition object - auto m_idx = mlist.idx[mode_idx]; - auto id = modes.comps[m_idx].idx_props[spec_idx]; - prop.get_density_aer(id, density_aer); - } - - inline - void get_mam_hygro_aer (int list_idx, int mode_idx, int spec_idx, - real& hygro_aer) const - { - modelist_t mlist; - if (list_idx >= 0 && list_idx <= N_DIAG) - mlist = ma_list[list_idx]; - - // Get the index for the corresponding mode in the mode definition object - auto m_idx = mlist.idx[mode_idx]; - auto id = modes.comps[m_idx].idx_props[spec_idx]; - prop.get_hygro_aer(id, hygro_aer); - } - - // Return requested properties for the aerosol from the specified - // climate or diagnostic list. - inline - void get_mam_props (int list_idx, int mode_idx, int spec_idx, real& density_aer, - std::string& spectype, real& hygro_aer, - real1d& refindex_real_aer_sw, real1d& refindex_im_aer_sw) const - { - modelist_t mlist; - if (list_idx >= 0 && list_idx <= N_DIAG) - mlist = ma_list[list_idx]; - - // Get the index for the corresponding mode in the mode definition object - auto m_idx = mlist.idx[mode_idx]; - auto id = modes.comps[m_idx].idx_props[spec_idx]; - - prop.get_density_aer(id, density_aer); - prop.get_hygro_aer(id, hygro_aer); - prop.get_ref_real_aer_sw(id, refindex_real_aer_sw); - prop.get_ref_im_aer_sw(id, refindex_im_aer_sw); - spectype = modes.comps[m_idx].type[spec_idx]; - } - - inline - void get_mam_props_sw (int list_idx, int mode_idx, int spec_idx, - real& density_aer, real1d& refindex_real_aer_sw, - real1d& refindex_im_aer_sw) const - { - modelist_t mlist; - if (list_idx >= 0 && list_idx <= N_DIAG) - mlist = ma_list[list_idx]; - - // Get the index for the corresponding mode in the mode definition object - auto m_idx = mlist.idx[mode_idx]; - auto id = modes.comps[m_idx].idx_props[spec_idx]; - - prop.get_density_aer(id, density_aer); - prop.get_ref_real_aer_sw(id, refindex_real_aer_sw); - prop.get_ref_im_aer_sw(id, refindex_im_aer_sw); - } - - inline - void get_mam_props (int list_idx, int mode_idx, int spec_idx, real& density_aer) const - { - modelist_t mlist; - if (list_idx >= 0 && list_idx <= N_DIAG) - mlist = ma_list[list_idx]; - - auto m_idx = mlist.idx[mode_idx]; - auto id = modes.comps[m_idx].idx_props[spec_idx]; - - prop.get_density_aer(id, density_aer); - } - - inline - void get_mam_props_lw (int list_idx, int mode_idx, int spec_idx, - real& density_aer, - real1d& refindex_real_aer_lw, - real1d& refindex_im_aer_lw) const - { - modelist_t mlist; - if (list_idx >= 0 && list_idx <= N_DIAG) - mlist = ma_list[list_idx]; - - // Get the index for the corresponding mode in the mode definition object - auto m_idx = mlist.idx[mode_idx]; - auto id = modes.comps[m_idx].idx_props[spec_idx]; - - prop.get_density_aer(id, density_aer); - prop.get_ref_real_aer_lw(id, refindex_real_aer_lw); - prop.get_ref_im_aer_lw(id, refindex_im_aer_lw); - } - - inline - void get_aer_opticstype (int list_idx, int aer_idx, std::string& opticstype) const - { - auto aerlist = aerosollist[list_idx]; - auto id = aerlist.aer[aer_idx].physprop_id; - - prop.get_opticstype(id, opticstype); - } - - inline - void get_aer_sw_hygro_ext (int list_idx, int aer_idx, real2d& sw_hygro_ext) const - { - auto aerlist = aerosollist[list_idx]; - auto id = aerlist.aer[aer_idx].physprop_id; - - prop.get_sw_hygro_ext(id, sw_hygro_ext); - } - - inline - void get_aer_sw_hygro_ssa (int list_idx, int aer_idx, real2d& sw_hygro_ssa) const - { - auto aerlist = aerosollist[list_idx]; - auto id = aerlist.aer[aer_idx].physprop_id; - - prop.get_sw_hygro_ssa(id, sw_hygro_ssa); - } - - inline - void get_aer_sw_hygro_asm (int list_idx, int aer_idx, real2d& sw_hygro_asm) const - { - auto aerlist = aerosollist[list_idx]; - auto id = aerlist.aer[aer_idx].physprop_id; - - prop.get_sw_hygro_asm(id, sw_hygro_asm); - } - - inline - void get_aer_lw_hygro_abs (int list_idx, int aer_idx, real2d& lw_hygro_abs) const - { - auto aerlist = aerosollist[list_idx]; - auto id = aerlist.aer[aer_idx].physprop_id; - - prop.get_lw_hygro_abs(id, lw_hygro_abs); - } - - inline - void get_aer_sw_nonhygro_ext (int list_idx, int aer_idx, real1d& sw_nonhygro_ext) const - { - auto aerlist = aerosollist[list_idx]; - auto id = aerlist.aer[aer_idx].physprop_id; - - prop.get_sw_nonhygro_ext(id, sw_nonhygro_ext); - } - - inline - void get_aer_sw_nonhygro_ssa (int list_idx, int aer_idx, real1d& sw_nonhygro_ssa) const - { - auto aerlist = aerosollist[list_idx]; - auto id = aerlist.aer[aer_idx].physprop_id; - - prop.get_sw_nonhygro_ssa(id, sw_nonhygro_ssa); - } - - inline - void get_aer_sw_nonhygro_asm (int list_idx, int aer_idx, real1d& sw_nonhygro_asm) const - { - auto aerlist = aerosollist[list_idx]; - auto id = aerlist.aer[aer_idx].physprop_id; - - prop.get_sw_nonhygro_asm(id, sw_nonhygro_asm); - } - - inline - void get_aer_sw_nonhygro_scat (int list_idx, int aer_idx, real1d& sw_nonhygro_scat) const - { - auto aerlist = aerosollist[list_idx]; - auto id = aerlist.aer[aer_idx].physprop_id; - - prop.get_sw_nonhygro_scat(id, sw_nonhygro_scat); - } - - inline - void get_aer_sw_nonhygro_ascat (int list_idx, int aer_idx, real1d& sw_nonhygro_ascat) const - { - auto aerlist = aerosollist[list_idx]; - auto id = aerlist.aer[aer_idx].physprop_id; - - prop.get_sw_nonhygro_ascat(id, sw_nonhygro_ascat); - } - - inline - void get_aer_lw_abs (int list_idx, int aer_idx, real1d& lw_abs) const - { - auto aerlist = aerosollist[list_idx]; - auto id = aerlist.aer[aer_idx].physprop_id; - - prop.get_lw_abs(id, lw_abs); - } - - inline - void get_aer_refindex_aer_sw (int list_idx, int aer_idx, - real1d& refindex_real_aer_sw, - real1d& refindex_im_aer_sw) const - { - auto aerlist = aerosollist[list_idx]; - auto id = aerlist.aer[aer_idx].physprop_id; - - prop.get_ref_real_aer_sw(id, refindex_real_aer_sw); - prop.get_ref_im_aer_sw(id, refindex_im_aer_sw); - } - - inline - void get_aer_refindex_aer_lw (int list_idx, int aer_idx, - real1d& refindex_real_aer_lw, - real1d& refindex_im_aer_lw) const - { - auto aerlist = aerosollist[list_idx]; - auto id = aerlist.aer[aer_idx].physprop_id; - - prop.get_ref_real_aer_lw(id, refindex_real_aer_lw); - prop.get_ref_im_aer_lw(id, refindex_im_aer_lw); - } - - inline - void get_aer_r_sw_ext (int list_idx, int aer_idx, real2d& r_sw_ext) const - { - auto aerlist = aerosollist[list_idx]; - auto id = aerlist.aer[aer_idx].physprop_id; - - prop.get_r_sw_ext(id, r_sw_ext); - } - - inline - void get_aer_r_sw_scat (int list_idx, int aer_idx, real2d& r_sw_scat) const - { - auto aerlist = aerosollist[list_idx]; - auto id = aerlist.aer[aer_idx].physprop_id; - - prop.get_r_sw_scat(id, r_sw_scat); - } - - inline - void get_aer_r_sw_ascat (int list_idx, int aer_idx, real2d& r_sw_ascat) const - { - auto aerlist = aerosollist[list_idx]; - auto id = aerlist.aer[aer_idx].physprop_id; - - prop.get_r_sw_ascat(id, r_sw_ascat); - } - - inline - void get_aer_r_lw_abs (int list_idx, int aer_idx, real2d& r_lw_abs) const - { - auto aerlist = aerosollist[list_idx]; - auto id = aerlist.aer[aer_idx].physprop_id; - - prop.get_r_lw_abs(id, r_lw_abs); - } - - inline - void get_aer_mu (int list_idx, int aer_idx, real1d& mu) const - { - auto aerlist = aerosollist[list_idx]; - auto id = aerlist.aer[aer_idx].physprop_id; - - prop.get_mu(id, mu); - } - - inline - void get_aername (int list_idx, int aer_idx, std::string& aername) const - { - auto aerlist = aerosollist[list_idx]; - auto id = aerlist.aer[aer_idx].physprop_id; - - prop.get_aername(id, aername); - } - - inline - void get_density_aer (int list_idx, int aer_idx, real& density_aer) const - { - auto aerlist = aerosollist[list_idx]; - auto id = aerlist.aer[aer_idx].physprop_id; - - prop.get_density_aer(id, density_aer); - } - - inline - void get_hygro_aer (int list_idx, int aer_idx, real& hygro_aer) const - { - auto aerlist = aerosollist[list_idx]; - auto id = aerlist.aer[aer_idx].physprop_id; - - prop.get_hygro_aer(id, hygro_aer); - } - - inline - void get_dryrad_aer (int list_idx, int aer_idx, real& dryrad_aer) const - { - auto aerlist = aerosollist[list_idx]; - auto id = aerlist.aer[aer_idx].physprop_id; - - prop.get_dryrad_aer(id, dryrad_aer); - } - - inline - void get_dispersion_aer (int list_idx, int aer_idx, real& dispersion_aer) const - { - auto aerlist = aerosollist[list_idx]; - auto id = aerlist.aer[aer_idx].physprop_id; - - prop.get_dispersion_aer(id, dispersion_aer); - } - - inline - void get_num_to_mass_aer (int list_idx, int aer_idx, real& num_to_mass_aer) const - { - auto aerlist = aerosollist[list_idx]; - auto id = aerlist.aer[aer_idx].physprop_id; - - prop.get_num_to_mass_aer(id, num_to_mass_aer); - } -}; -#endif diff --git a/Source/Radiation/ERF_ModalAeroWaterUptake.H b/Source/Radiation/ERF_ModalAeroWaterUptake.H deleted file mode 100644 index 9a0101080..000000000 --- a/Source/Radiation/ERF_ModalAeroWaterUptake.H +++ /dev/null @@ -1,455 +0,0 @@ - -// RCE 07.04.13: Adapted from MIRAGE2 code / E3SM -#ifndef ERF_MODAL_AERO_WATERUPTAKE_H_ -#define ERF_MODAL_AERO_WATERUPTAKE_H_ - -#include -#include -#include -#include -#include "ERF_Constants.H" -#include "ERF_AeroRadProps.H" -#include "ERF_CloudRadProps.H" -#include "ERF_Mam4Constituents.H" - -using yakl::fortran::parallel_for; -using yakl::fortran::SimpleBounds; - -class ModalAeroWateruptake { - public: - constexpr static real third = 1./3.; - constexpr static real pi43 = PI*4.0/3.0; - constexpr static real huge_real = std::numeric_limits::max(); - constexpr static int imax = 200; - public: - static - void modal_aero_wateruptake_dr (int list_idx, int ncol, int nlev, int nmodes, int top_lev, - MamConstituents& consti, const real2d& h2ommr, const real2d& t, const real2d& pmid, - const real3d& dgnumdry_m, const real3d& dgnumwet_m, const real3d& qaerwat_m, - const real3d& wetdens_m, const real2d& clear_rh_in) - { - real2d raer("raer",ncol,nlev); // aerosol species MRs (kg/kg and #/kg) - real2d cldn("cldn",ncol,nlev); // layer cloud fraction (0-1) - real3d dgncur_a("dgncur",ncol,nlev,nmodes); - real3d dgncur_awet("dgncur_awet",ncol,nlev,nmodes); - real3d wetdens("wetdens",ncol,nlev,nmodes); - real3d qaerwat("qaerwat",ncol,nlev,nmodes); - - real2d dryvolmr("dryvolmr",ncol,nlev); //volume MR for aerosol mode (m3/kg) - real specdens; - real spechygro, spechygro_1; - real duma, dumb; - real sigmag; - real alnsg; - real v2ncur_a; - real drydens; // dry particle density (kg/m^3) - real2d rh("rh",ncol,nlev); // relative humidity (0-1) - - real1d es("es",ncol); // saturation vapor pressure - real1d qs("qs",ncol); // saturation specific humidity - real2d aerosol_water("aerosol_water",ncol,nlev); //sum of aerosol water (wat_a1 + wat_a2 + wat_a3 + wat_a4) - bool compute_wetdens; - - std::string trnum; // used to hold mode number (as characters) - - real3d naer("naer",ncol,nlev,nmodes); // aerosol number MR (bounded!) (#/kg-air) - real3d dryvol("dryvol",ncol,nlev,nmodes); // single-particle-mean dry volume (m3) - real3d drymass("drymass",ncol,nlev,nmodes); // single-particle-mean dry mass (kg) - real3d dryrad("dryrad",ncol,nlev,nmodes); // dry volume mean radius of aerosol (m) - real3d wetrad("wetrad",ncol,nlev,nmodes); // wet radius of aerosol (m) - real3d wetvol("wetvol",ncol,nlev,nmodes); // single-particle-mean wet volume (m3) - real3d wtrvol("wtrvol",ncol,nlev,nmodes); // single-particle-mean water volume in wet aerosol (m3) - real1d rhcrystal("rhcrystal",nmodes); - real1d rhdeliques("rhdeliques",nmodes); - real1d specdens_1("specdens_1",nmodes); - real3d maer("maer",ncol,nlev,nmodes); // aerosol wet mass MR (including water) (kg/kg-air) - real3d hygro("hygro",ncol,nlev,nmodes); // volume-weighted mean hygroscopicity (--) - - yakl::memset(naer, huge_real); - yakl::memset(dryvol, huge_real); - yakl::memset(drymass, huge_real); - yakl::memset(dryrad, huge_real); - yakl::memset(wetrad, huge_real); - yakl::memset(wetvol, huge_real); - yakl::memset(wtrvol, huge_real); - - yakl::memset(rhcrystal, huge_real); - yakl::memset(rhdeliques, huge_real); - yakl::memset(specdens_1, huge_real); - - yakl::memset(maer, 0.0); - yakl::memset(hygro, 0.); - - //by default set compute_wetdens to be true - compute_wetdens = true; - - dgncur_a = dgnumdry_m; - dgncur_awet = dgnumwet_m; - qaerwat = qaerwat_m; - wetdens = wetdens_m; - - // retrieve aerosol properties - for (auto m = 1; m <= nmodes; ++m) { - yakl::memset(dryvolmr, 0.); - - // get mode properties - consti.get_mode_props(list_idx, m-1, sigmag, rhcrystal(m), rhdeliques(m)); - - // get mode species - int nspec; - consti.get_mode_nspec(list_idx, m-1, nspec); - for(auto l = 0; l < nspec; ++l) { - // get species interstitial mixing ratio ('a') - consti.rad_cnst_get_mam_mmr_by_idx(list_idx, m-1, l, "a", raer); - consti.get_mam_density_aer(list_idx, m-1, l, specdens); - consti.get_mam_hygro_aer(list_idx, m-1, l, spechygro); - - if (l == 1) { - // save off these values to be used as defaults - specdens_1(m) = specdens; - spechygro_1 = spechygro; - } - - top_lev = 1; - for(auto k = top_lev; k <= nlev; ++k) { - for(auto i = 1; i <= ncol; ++i) { - duma = raer(i,k); - maer(i,k,m) = maer(i,k,m) + duma; - dumb = duma/specdens; - dryvolmr(i,k) = dryvolmr(i,k) + dumb; - hygro(i,k,m) = hygro(i,k,m) + dumb*spechygro; - } // i = 1, ncol - } // k = top_lev, pver - } // l = 1, nspec - - alnsg = log(sigmag); - top_lev = 1; - for(auto k = top_lev; k <= nlev; ++k) { - for(auto i = 1; i <= ncol; ++i) { - - if (dryvolmr(i,k) > 1.0e-30) { - hygro(i,k,m) = hygro(i,k,m)/dryvolmr(i,k); - } else { - hygro(i,k,m) = spechygro_1; - } - - // dry aerosol properties - v2ncur_a = 1. / ( (PI/6.)*(std::pow(dgncur_a(i,k,m),3.))*std::exp(4.5*std::pow(alnsg, 2.)) ); - // naer = aerosol number (#/kg) - naer(i,k,m) = dryvolmr(i,k)*v2ncur_a; - - // compute mean (1 particle) dry volume and mass for each mode - // old coding is replaced because the new (1/v2ncur_a) is equal to - // the mean particle volume - // also moletomass forces maer >= 1.0e-30, so (maer/dryvolmr) - // should never cause problems (but check for maer < 1.0e-31 anyway) - if (maer(i,k,m) > 1.0e-31) { - drydens = maer(i,k,m)/dryvolmr(i,k); - } else { - drydens = 1.0; - } - - dryvol(i,k,m) = 1.0/v2ncur_a; - drymass(i,k,m) = drydens*dryvol(i,k,m); - dryrad(i,k,m) = std::pow((dryvol(i,k,m)/pi43), third); - } // i = 1, ncol - } // k = top_lev, pver - } // modes - - // specify clear air relative humidity - bool has_clear_rh = false; - if (has_clear_rh) { - // use input relative humidity - // check that values are reasonable and apply upper limit - for(auto k = top_lev; k <= nlev; --k) { - for(auto i = 1; i <= ncol; ++i) { - rh(i,k) = clear_rh_in(i,k); - if ( rh(i,k) < 0 ) { - amrex::Print() << "modal_aero_wateruptake_dr: clear_rh_in is negative - rh:" << rh(i,k); - exit(EXIT_FAILURE); - } - // limit RH to 98% to be consistent with behavior when clear_rh_in is not provided - rh(i,k) = std::min(rh(i,k), 0.98); - } // i - } - } else { - // estimate clear air relative humidity using cloud fraction - for(auto k = top_lev; k <= nlev; ++k) { - for(auto i = 1; i <= ncol; ++i) { - WaterVaporSat::qsat(t(i,k), pmid(i,k), es(i), qs(i)); - if (qs(i) > h2ommr(i,k)) { - rh(i,k) = h2ommr(i,k)/qs(i); - } else { - rh(i,k) = 0.98; - } - - rh(i,k) = std::max(rh(i,k), 0.0); - rh(i,k) = std::min(rh(i,k), 0.98); - if (cldn(i,k) < 0.998) { - rh(i,k) = (rh(i,k) - cldn(i,k)) / (1.0 - cldn(i,k)); - } - rh(i,k) = std::max(rh(i,k), 0.0); - } // i = 1, ncol - } // k = top_lev, pver - } - - // compute aerosol wet radius and aerosol water - modal_aero_wateruptake_sub(ncol, nlev, nmodes, top_lev, - rhcrystal, rhdeliques, dryrad, - hygro, rh, dryvol, - wetrad, wetvol, wtrvol); - - for(auto m = 1; m <= nmodes; ++m) { - for(auto k = top_lev; k <= nlev; ++k) { - for(auto i = 1; i <= ncol; ++i) { - - dgncur_awet(i,k,m) = dgncur_a(i,k,m) * (wetrad(i,k,m)/dryrad(i,k,m)); - qaerwat(i,k,m) = rhoh2o*naer(i,k,m)*wtrvol(i,k,m); - - // compute aerosol wet density (kg/m3) - if(compute_wetdens) { - if (wetvol(i,k,m) > 1.0e-30) { - wetdens(i,k,m) = (drymass(i,k,m) + rhoh2o*wtrvol(i,k,m))/wetvol(i,k,m); - } - else { - wetdens(i,k,m) = specdens_1(m); - } - } - } //i = 1, ncol - } // k = top_lev, pver - } // m = 1, nmodes - } - - // Purpose: Compute aerosol wet radius - static - void modal_aero_wateruptake_sub (int ncol, int nlev, int nmodes, int top_lev, - const real1d& rhcrystal, const real1d& rhdeliques, const real3d& dryrad, - const real3d& hygro, const real2d& rh, const real3d& dryvol, - const real3d& wetrad, const real3d& wetvol, const real3d& wtrvol) - { - // loop over all aerosol modes - for(auto m = 1; m <= nmodes; ++m) { - real hystfac = 1.0 / std::max(1.0e-5, (rhdeliques(m) - rhcrystal(m))); - for(auto k = top_lev; k <= nlev; ++k) { - for(auto i = 1; i <= ncol; ++i) { - - // compute wet radius for each mode - modal_aero_kohler(dryrad(i,k,m), hygro(i,k,m), rh(i,k), wetrad(i,k,m)); - - wetrad(i,k,m) = std::max(wetrad(i,k,m), dryrad(i,k,m)); - wetvol(i,k,m) = pi43*std::pow(wetrad(i,k,m), 3.); - wetvol(i,k,m) = std::max(wetvol(i,k,m), dryvol(i,k,m)); - wtrvol(i,k,m) = wetvol(i,k,m) - dryvol(i,k,m); - wtrvol(i,k,m) = std::max(wtrvol(i,k,m), 0.0); - - // apply simple treatment of deliquesence/crystallization hysteresis - // for rhcrystal < rh < rhdeliques, aerosol water is a fraction of - // the "upper curve" value, and the fraction is a linear function of rh - if (rh(i,k) < rhcrystal(m)) { - wetrad(i,k,m) = dryrad(i,k,m); - wetvol(i,k,m) = dryvol(i,k,m); - wtrvol(i,k,m) = 0.0; - } - else if (rh(i,k) < rhdeliques(m)) { - wtrvol(i,k,m) = wtrvol(i,k,m)*hystfac*(rh(i,k) - rhcrystal(m)); - wtrvol(i,k,m) = std::max(wtrvol(i,k,m), 0.0); - wetvol(i,k,m) = dryvol(i,k,m) + wtrvol(i,k,m); - wetrad(i,k,m) = std::pow(wetvol(i,k,m)/pi43, 1./3.); - } - } // columns - } // levels - } // modes - } - - // calculates equilibrium radius r of haze droplets as function of - // dry particle mass and relative humidity s using kohler solution - // given in pruppacher and klett (eqn 6-35) - - // for multiple aerosol types, assumes an internal mixture of aerosols - YAKL_INLINE - static void modal_aero_kohler (const real& rdry_in, - const real& hygro, const real& s, - real& rwet_out) - { - const real eps = 1.e-4; - const real mw = 18.; - const real rhow = 1.; - const real surften = 76.; - const real tair = 273.; - const real third = 1./3.; - const real ugascon = 8.3e7; - - //effect of organics on surface tension is neglected - auto a=2.e4*mw*surften/(ugascon*tair*rhow); - - auto rdry = rdry_in*1.0e6; // convert (m) to (microns) - auto vol = std::pow(rdry, 3.); // vol is r**3, not volume - auto b = vol*hygro; - - //quartic - auto ss = std::max(std::min(s,1.-eps), 1.e-10); // relative humidity - auto slog = std::log(ss); // log relative humidity - auto p43 = -a/slog; - auto p42 = 0.; - auto p41 = b/slog-vol; - auto p40 = a*vol/slog; - - // cubic for rh=1 - auto p32 = 0.; - auto p31 = -b/a; - auto p30 = -vol; - - real r, r3, r4; - if(vol <= 1.e-12) { - r=rdry; - return; - } - - auto p = std::abs(p31)/(rdry*rdry); - if(p < eps) { - //approximate solution for small particles - r=rdry*(1.+p*third/(1.-slog*rdry/a)); - } - else { - amrex::GpuComplex cx4[4]; - makoh_quartic(cx4,p43,p42,p41,p40); - //find smallest real(r8) solution - r = 1000.*rdry; - int nsol = -1; - for(int n=0; n<4; ++n) { - auto xr=cx4[n].real(); - auto xi=cx4[n].imag(); - if(abs(xi) > abs(xr)*eps) continue; - if(xr > r) continue; - if(xr < rdry*(1.-eps)) continue; - if(xr != xr) continue; - r=xr; - nsol=n; - } - if(nsol == -1) { - amrex::Print() << "kohlerc: no real solution found(quartic), nsol= " << nsol << "\n"; - r=rdry; - } - } - - if(s > 1.-eps) { - // save quartic solution at s=1-eps - r4=r; - // cubic for rh=1 - p=abs(p31)/(rdry*rdry); - if(p < eps) { - r=rdry*(1.+p*third); - } - else { - amrex::GpuComplex cx3[3]; - makoh_cubic(cx3,p32,p31,p30); - //find smallest real(r8) solution - r=1000.*rdry; - int nsol = -1; - for(auto n=0; n<3; ++n) { - auto xr = cx3[n].real(); - auto xi = cx3[n].imag(); - if(abs(xi) > abs(xr)*eps) continue; - if(xr > r) continue; - if(xr < rdry*(1.-eps)) continue; - if(xr != xr) continue; - r=xr; - nsol=n; - } - if(nsol == -1) { - amrex::Print() << "kohlerc: no real solution found (cubic), nsol= " << nsol << "\n"; - r=rdry; - } - } - r3=r; - //now interpolate between quartic, cubic solutions - r=(r4*(1.-s)+r3*(s-1.+eps))/eps; - } - // bound and convert from microns to m - r = std::min(r,30.); // upper bound based on 1 day lifetime - rwet_out = r*1.e-6; - } - - // solves x**3 + p2 x**2 + p1 x + p0 = 0 - // where p0, p1, p2 are real - static - void makoh_cubic (amrex::GpuComplex cx[], - const real& p2, - const real& p1, - const real& p0) - { - const real eps = 1.0e-20; - const real third=1./3.; - - auto ci = amrex::GpuComplex(0., 1.); - auto sqrt3 = amrex::GpuComplex(std::sqrt(3.), 0.); - auto cw = 0.5*(-1.+ci*sqrt3); - auto cwsq = 0.5*(-1.-ci*sqrt3); - - if(p1 == 0.) { - // completely insoluble particle - cx[0] = amrex::GpuComplex(std::pow(-p0, third), 0.); - cx[1] = cx[0]; - cx[2] = cx[1]; - } - else { - auto q = amrex::GpuComplex(p1/3., 0.); - auto r = amrex::GpuComplex(p0/2., 0.); - auto crad = r*r+q*q*q; - crad = amrex::sqrt(crad); - - auto cy = r-crad; - if (amrex::abs(cy) > eps) cy = amrex::pow(cy, third); - auto cq = q; - auto cz = -cq/cy; - - cx[0] = -cy-cz; - cx[1] = -cw*cy-cwsq*cz; - cx[2] = -cwsq*cy-cw*cz; - } - } - - // solves x**4 + p3 x**3 + p2 x**2 + p1 x + p0 = 0 - // where p0, p1, p2, p3 are real - static - void makoh_quartic (amrex::GpuComplex cx[], - const real& p3, - const real& p2, - const real& p1, - const real& p0) - { - const real third=1./3.; - auto q = -p2*p2/36.+(p3*p1-4*p0)/12.; - auto r = -std::pow((p2/6), 3.0) + p2*(p3*p1-4.*p0)/48. - + (4.*p0*p2-p0*p3*p3-p1*p1)/16.; - - auto crad = amrex::sqrt(amrex::GpuComplex(r*r+q*q*q, 0.)); - auto cb = r-crad; - - if (cb.real() == 0. && cb.imag() == 0.) { - // insoluble particle - cx[0] = amrex::pow(amrex::GpuComplex(-p1, 0.0), third); - cx[1] = cx[0]; - cx[2] = cx[0]; - cx[3] = cx[0]; - } - else { - cb = amrex::pow(cb, third); - auto cy = -cb+q/cb+p2/6.; - auto cb0 = amrex::sqrt(cy*cy-p0); - auto cb1 = (p3*cy-p1)/(2.*cb0); - - cb = p3/2.+cb1; - crad = cb*cb-4.*(cy+cb0); - crad = amrex::sqrt(crad); - cx[1] = (-cb+crad)/2.; - cx[2] = (-cb-crad)/2.; - cb = p3/2.-cb1; - crad = cb*cb-4.*(cy-cb0); - crad = amrex::sqrt(crad); - cx[3] = (-cb+crad)/2.; - cx[4] = (-cb-crad)/2.; - } - } -}; -#endif diff --git a/Source/Radiation/ERF_Optics.H b/Source/Radiation/ERF_Optics.H deleted file mode 100644 index 42efe8944..000000000 --- a/Source/Radiation/ERF_Optics.H +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Optics class stores shortwave and longwave cloud optical properties *by band*. - * Dimensions are nswbands,ncol,nlev. Generally, this should be able to handle cases - * were ncol might be something like nday, and nlev could be arbitrary so long as - * corresponding fields were defined for all indices of nlev. - */ -#ifndef ERF_OPTICS_H -#define ERF_OPTICS_H - -#include -#include -#include - -#include -#include -#include -#include - -#include "ERF_Constants.H" -#include "ERF_AeroRadProps.H" -#include "ERF_CloudRadProps.H" - -// Radiation code interface class -class Optics { - public: - // constructor - Optics () = default; - explicit Optics(int ngases, char* gas_names[]); - // deconstructor - ~Optics () = default; - - // get short wave cloud optics property - void get_cloud_optics_sw (int ncol, int nlev, int nbnd, - bool do_snow, const real2d& cld, const real2d& cldfsnow, const real2d& iclwp, - const real2d& iciwp, const real2d& icswp, const real2d& lambdac, const real2d& mu, - const real2d& dei, const real2d& des, const real2d& rel, const real2d& rei, - const real3d& tau_out, const real3d& ssa_out, const real3d& asm_out, - const real3d& liq_tau_out, const real3d& ice_tau_out, const real3d& snw_tau_out); - - // get long wave cloud optics property - void get_cloud_optics_lw (int ncol, int nlev, int nbnd, bool do_snow, const real2d& cld, const real2d& cldfsnow, const real2d& iclwp, - const real2d& iciwp, const real2d& icswp, const real2d& lambdac, const real2d& mu, const real2d& dei, const real2d& des, - const real2d& rei, const real3d& tau_out, const real3d& liq_tau_out, const real3d& ice_tau_out, const real3d& snw_tau_out); - - // sample short wave cloud optics property - void sample_cloud_optics_sw (int ncol, int nlev, int ngpt, const int1d& gpt2bnd, - const real2d& pmid, const real2d& cld, const real2d& cldfsnow, - const real3d& tau_bnd, const real3d& ssa_bnd, const real3d& asm_bnd, - const real3d& tau_gpt, const real3d& ssa_gpt, const real3d& asm_gpt); - - // sample long wave cloud optics property - void sample_cloud_optics_lw (int ncol, int nlev, int ngpt, const int1d& gpt2bnd, - const real2d& pmid, const real2d& cld, const real2d& cldfsnow, - const real3d& tau_bnd, const real3d& tau_gpt); - - // set the short wave aerosol optics property - void set_aerosol_optics_sw (int icall, int ncol, int nlev, int nswbands, real dt, const int1d& night_indices, - bool is_cmip6_volc, const real3d& tau_out, const real3d& ssa_out, const real3d& asm_out, - const real2d& clear_rh); - - // set the long wave aerosol optics property - void set_aerosol_optics_lw (int icall, real dt, bool is_cmip6_volc, const real2d& zi, - const real3d& tau, const real2d& clear_rh); - - // mcica subcol mask - void mcica_subcol_mask (int ngpt, int ncol, int nlev, const real2d& cldfrac, const bool3d& iscloudy); - - // combine properties - void combine_properties (int nbands, int ncols, int nlevs, - const real2d& fraction1, const real3d& property1, - const real2d& fraction2, const real3d& property2, - const real3d& combined_property); - - // initialize and load gas property data for rrtmgp radiation - void initialize (int ngas, int nmodes, int num_aeros, - int nswbands, int nlwbands, - int ncol, int nlev, int nrh, int top_lev, - const std::vector& aero_names, - const real2d& zi, const real2d& pmid, const real2d& pdel, - const real2d& temp, const real2d& qi, - const real2d& geom_radius); - - // finalize/clean up - void finalize (); - - private: - // number of gas for radiation model - int ngas; - char** gas_names; - - std::string icecldoptics; - std::string liqcldoptics; - - CloudRadProps cloud_optics; - AerRadProps aero_optics; -}; - -#endif // ERF_OPTICS_H diff --git a/Source/Radiation/ERF_Optics.cpp b/Source/Radiation/ERF_Optics.cpp deleted file mode 100644 index dda426133..000000000 --- a/Source/Radiation/ERF_Optics.cpp +++ /dev/null @@ -1,467 +0,0 @@ -// -// get cloud and aerosol optics for short wave and long wave -// -#include "ERF_Optics.H" -#include "ERF_Slingo.H" -#include "ERF_EbertCurry.H" -#include "ERF_RadConstants.H" -#include "AMReX_Random.H" -using yakl::fortran::parallel_for; -using yakl::fortran::SimpleBounds; - -void Optics::initialize (int ngas, int nmodes, int num_aeros, - int nswbands, int nlwbands, - int ncol, int nlev, int nrh, int top_lev, - const std::vector& aero_names, - const real2d& zi, const real2d& pmid, const real2d& pdel, - const real2d& temp, const real2d& qi, - const real2d& geom_radius) -{ - cloud_optics.initialize(); - aero_optics.initialize(ngas, nmodes, num_aeros, - nswbands, nlwbands, ncol, nlev, nrh, top_lev, - aero_names, zi, pmid, pdel, temp, qi, geom_radius); -} - -void Optics::finalize () -{ - // nothing needs to be done here -} - - -void Optics::get_cloud_optics_sw (int ncol, int nlev, int nbnd, - bool do_snow, const real2d& cld, const real2d& cldfsnow, const real2d& iclwp, - const real2d& iciwp, const real2d& icswp, const real2d& lambdac, const real2d& mu, - const real2d& dei, const real2d& des, const real2d& rel, const real2d& rei, - const real3d& tau_out, const real3d& ssa_out, const real3d& asm_out, - const real3d& liq_tau_out, const real3d& ice_tau_out, const real3d& snw_tau_out) -{ - //Temporary variables to hold cloud optical properties before combining into - // output arrays. Same shape as output arrays, so get shapes from output. - real3d liq_tau("liq_tau",nbnd, ncol, nlev); - real3d liq_tau_ssa("liq_tau_ssa",nbnd, ncol, nlev); - real3d liq_tau_ssa_g("liq_tau_ssa_g", nbnd, ncol, nlev); - real3d liq_tau_ssa_f("liq_tau_ssa_f", nbnd, ncol, nlev); - real3d ice_tau("ice_tau", nbnd, ncol, nlev); - real3d ice_tau_ssa("ice_tau_ssa", nbnd, ncol, nlev); - real3d ice_tau_ssa_g("ice_tau_ssa_g", nbnd, ncol, nlev); - real3d ice_tau_ssa_f("ice_tau_ssa_f", nbnd, ncol, nlev); - real3d cld_tau("cld_tau", nbnd, ncol, nlev); - real3d cld_tau_ssa("cld_tau_ssa", nbnd, ncol, nlev); - real3d cld_tau_ssa_g("cld_tau_ssa_g", nbnd, ncol, nlev); - real3d cld_tau_ssa_f("cld_tau_ssa_f", nbnd, ncol, nlev); - real3d snow_tau("snow_tau", nbnd, ncol, nlev ); - real3d snow_tau_ssa("snow_tau_ssa", nbnd, ncol, nlev); - real3d snow_tau_ssa_g("snow_tau_ssa_g", nbnd, ncol, nlev); - real3d snow_tau_ssa_f("snow_tau_ssa_f", nbnd, ncol, nlev); - real3d combined_tau("combined_tau", nbnd, ncol, nlev); - real3d combined_tau_ssa("combined_tau_ssa", nbnd, ncol, nlev); - real3d combined_tau_ssa_g("combined_tau_ssa_g", nbnd, ncol, nlev); - real3d combined_tau_ssa_f("combined_tau_ssa_f", nbnd, ncol, nlev); - - // Initialize outputs - yakl::memset(tau_out, 0.); - yakl::memset(ssa_out, 0.); - yakl::memset(asm_out, 0.); - yakl::memset(liq_tau_out, 0.); - yakl::memset(ice_tau_out, 0.); - yakl::memset(snw_tau_out, 0.); - - // Initialize local variables - yakl::memset(ice_tau, 0.); - yakl::memset(ice_tau_ssa, 0.); - yakl::memset(ice_tau_ssa_g, 0.); - yakl::memset(ice_tau_ssa_f, 0.); - yakl::memset(liq_tau, 0.); - yakl::memset(liq_tau_ssa, 0.); - yakl::memset(liq_tau_ssa_g, 0.); - yakl::memset(liq_tau_ssa_f, 0.); - yakl::memset(snow_tau, 0.); - yakl::memset(snow_tau_ssa, 0.); - yakl::memset(snow_tau_ssa_g, 0.); - yakl::memset(snow_tau_ssa_f, 0.); - yakl::memset(combined_tau, 0.); - yakl::memset(combined_tau_ssa, 0.); - yakl::memset(combined_tau_ssa_g, 0.); - yakl::memset(combined_tau_ssa_f, 0.); - - // Get ice cloud optics - if (icecldoptics == "mitchell") { - cloud_optics.mitchell_ice_optics_sw(ncol, nlev, iciwp, dei, - ice_tau, ice_tau_ssa, - ice_tau_ssa_g, ice_tau_ssa_f); - } - else if (icecldoptics == "ebertcurry") { - EbertCurry::ec_ice_optics_sw(ncol, nlev, nbnd, cld, iciwp, - rei, ice_tau, ice_tau_ssa, - ice_tau_ssa_g, ice_tau_ssa_f); - } - - // Get liquid cloud optics - if (liqcldoptics == "gammadist") { - cloud_optics.gammadist_liq_optics_sw(ncol, nlev, iclwp, lambdac, mu, - liq_tau, liq_tau_ssa, - liq_tau_ssa_g, liq_tau_ssa_f); - } - else if (liqcldoptics == "slingo") { - Slingo::slingo_liq_optics_sw(ncol, nlev, nbnd, cld, iclwp, rel, - liq_tau, liq_tau_ssa, - liq_tau_ssa_g, liq_tau_ssa_f); - } - - // Get snow cloud optics - if (do_snow) { - cloud_optics.mitchell_ice_optics_sw(ncol, nlev, icswp, des, - snow_tau, snow_tau_ssa, - snow_tau_ssa_g, snow_tau_ssa_f); - } - else { - // We are not doing snow optics, so set these to zero so we can still use - // the arrays without additional logic - yakl::memset(snow_tau, 0.); - yakl::memset(snow_tau_ssa, 0.); - yakl::memset(snow_tau_ssa_g, 0.); - yakl::memset(snow_tau_ssa_f, 0.); - } - - // Combine all cloud optics from CAM routines - parallel_for(SimpleBounds<3>(nbnd, ncol, nlev), YAKL_LAMBDA (int ibnd, int icol, int ilev) - { - cld_tau(ibnd, icol, ilev) = ice_tau(ibnd, icol, ilev) + liq_tau(ibnd, icol, ilev); - cld_tau_ssa(ibnd, icol, ilev) = ice_tau_ssa(ibnd, icol, ilev) + liq_tau_ssa(ibnd, icol, ilev); - cld_tau_ssa_g(ibnd, icol, ilev) = ice_tau_ssa_g(ibnd, icol, ilev) + liq_tau_ssa_g(ibnd, icol, ilev); - }); - - if (do_snow) { - combine_properties( nbnd, ncol, nlev, - cld, cld_tau, cldfsnow, snow_tau, combined_tau); - - combine_properties( nbnd, ncol, nlev, - cld, cld_tau_ssa, cldfsnow, snow_tau_ssa, combined_tau_ssa); - - combine_properties( nbnd, ncol, nlev, - cld, cld_tau_ssa_g, cldfsnow, snow_tau_ssa_g, combined_tau_ssa_g); - } - else { - parallel_for(SimpleBounds<3>(nbnd, ncol, nlev), YAKL_LAMBDA (int ibnd, int icol, int ilev) - { - combined_tau(ibnd, icol, ilev) = cld_tau(ibnd, icol, ilev); - combined_tau_ssa(ibnd, icol, ilev) = cld_tau_ssa(ibnd, icol, ilev); - combined_tau_ssa_g(ibnd, icol, ilev) = cld_tau_ssa_g(ibnd, icol, ilev); - }); - } - - // Copy to output arrays, converting to optical depth, single scattering - // albedo, and asymmetry parameter from the products that the CAM routines - // return. Make sure we do not try to divide by zero... - parallel_for(SimpleBounds<3>(nbnd, ncol, nlev), YAKL_LAMBDA (int iband, int icol, int ilev) - { - tau_out(icol,ilev,iband) = combined_tau(iband,icol,ilev); - if (combined_tau(iband,icol,ilev) > 0) { - ssa_out(icol,ilev,iband) - = combined_tau_ssa(iband,icol,ilev) / combined_tau(iband,icol,ilev); - } else { - ssa_out(icol,ilev,iband) = 1.; - } - - if (combined_tau_ssa(iband,icol,ilev) > 0) { - asm_out(icol,ilev,iband) - = combined_tau_ssa_g(iband,icol,ilev) / combined_tau_ssa(iband,icol,ilev); - } else { - asm_out(icol,ilev,iband) = 0.; - } - - // Re-order diagnostics outputs - liq_tau_out(icol,ilev,iband) = liq_tau(iband,icol,ilev); - ice_tau_out(icol,ilev,iband) = ice_tau(iband,icol,ilev); - snw_tau_out(icol,ilev,iband) = snow_tau(iband,icol,ilev); - }); -} - -//---------------------------------------------------------------------------- -void Optics::get_cloud_optics_lw (int ncol, int nlev, int nbnd, bool do_snow, const real2d& cld, const real2d& cldfsnow, const real2d& iclwp, - const real2d& iciwp, const real2d& icswp, const real2d& lambdac, const real2d& mu, const real2d& dei, const real2d& des, - const real2d& rei, const real3d& tau_out, const real3d& liq_tau_out, const real3d& ice_tau_out, const real3d& snw_tau_out) -{ - // Temporary variables to hold absorption optical depth - real3d ice_tau("ice_tau", nbnd, ncol, nlev); - real3d liq_tau("liq_tau", nbnd, ncol, nlev); - real3d snow_tau("snow_tau", nbnd, ncol, nlev); - real3d cld_tau("cld_tau", nbnd, ncol, nlev); - real3d combined_tau("combined_tau", nbnd, ncol, nlev); - - // Initialize outputs - yakl::memset(tau_out, 0.); - yakl::memset(liq_tau_out, 0.); - yakl::memset(ice_tau_out, 0.); - yakl::memset(snw_tau_out, 0.); - - // Initialize local variables - yakl::memset(ice_tau, 0.); - yakl::memset(liq_tau, 0.); - yakl::memset(snow_tau, 0.); - yakl::memset(cld_tau, 0.); - yakl::memset(combined_tau, 0.); - - // Get ice optics - if (icecldoptics == "mitchell") { - cloud_optics.mitchell_ice_optics_lw(ncol, nlev, iciwp, dei, ice_tau); - } - else if (icecldoptics == "ebertcurry") { - EbertCurry::ec_ice_optics_lw(ncol, nlev, nbnd, cld, iclwp, iciwp, rei, ice_tau); - } - - // Get liquid optics - if (liqcldoptics == "gammadist") { - cloud_optics.gammadist_liq_optics_lw(ncol, nlev, iclwp, lambdac, mu, liq_tau); - } - else if (liqcldoptics == "slingo") { - Slingo::slingo_liq_optics_lw(ncol, nlev, nbnd, cld, iclwp, iciwp, liq_tau); - } - - // Combined cloud optics - parallel_for(SimpleBounds<3>(nbnd, ncol, nlev), YAKL_LAMBDA (int ibnd, int icol, int ilev) - { - cld_tau(ibnd, icol, ilev) = liq_tau(ibnd, icol, ilev) + ice_tau(ibnd, icol, ilev); - }); - - // Get snow optics? - if (do_snow) { - cloud_optics.mitchell_ice_optics_lw(ncol, nlev, icswp, des, snow_tau); - combine_properties(nbnd, ncol, nlev, - cld, cld_tau, cldfsnow, snow_tau, combined_tau); - } - else { - parallel_for(SimpleBounds<3>(nbnd, ncol, nlev), YAKL_LAMBDA (int ibnd, int icol, int ilev) - { - combined_tau(ibnd,icol,ilev) = cld_tau(ibnd,icol,ilev); - }); - } - - // Set output optics - parallel_for(SimpleBounds<3>(nbnd, ncol, nlev), YAKL_LAMBDA (int ibnd, int icol, int ilev) - { - tau_out(icol,ilev,ibnd) = combined_tau(ibnd,icol,ilev); - liq_tau_out(icol,ilev,ibnd) = liq_tau(ibnd,icol,ilev); - ice_tau_out(icol,ilev,ibnd) = ice_tau(ibnd,icol,ilev); - snw_tau_out(icol,ilev,ibnd) = snow_tau(ibnd,icol,ilev); - }); -} - -//---------------------------------------------------------------------------- -// Provide a procedure to combine cloud optical properties by weighting -// contributions by fraction present. I.e., for combining cloud and snow -// optical properties, we weight the cloud and snow properties by the fraction -// of cloud and snow present. -void Optics::combine_properties (int nbands, int ncols, int nlevs, - const real2d& fraction1, const real3d& property1, - const real2d& fraction2, const real3d& property2, - const real3d& combined_property) -{ - // Combined fraction (max of property 1 and 2) - real2d combined_fraction("combined_fraction", ncols,nlevs); - - // Combined fraction - parallel_for(SimpleBounds<2>(ncols, nlevs), YAKL_LAMBDA (int icol, int ilev) - { - combined_fraction(icol, ilev) = std::max(fraction1(icol, ilev), fraction2(icol, ilev)); - }); - - // Combine optical properties by weighting by amount of cloud and snow - yakl::memset(combined_property, 0.); - parallel_for(SimpleBounds<3>(nlevs, ncols, nbands), YAKL_LAMBDA (int ilev, int icol, int iband) - { - if (combined_fraction(icol,ilev) > 0) { - combined_property(iband,icol,ilev) = ( - fraction1(icol,ilev) * property1(iband,icol,ilev) - + fraction2(icol,ilev) * property2(iband,icol,ilev) - ) / combined_fraction(icol,ilev); - } - }); -} - -//---------------------------------------------------------------------------- -// Do MCICA sampling of optics here. This will map bands to gpoints, -// while doing stochastic sampling of cloud state -void Optics::sample_cloud_optics_sw (int ncol, int nlev, int ngpt, const int1d& gpt2bnd, - const real2d& pmid, const real2d& cld, const real2d& cldfsnow, - const real3d& tau_bnd, const real3d& ssa_bnd, const real3d& asm_bnd, - const real3d& tau_gpt, const real3d& ssa_gpt, const real3d& asm_gpt) -{ - //real(r8), dimension(ncol,nlev) :: combined_cld - real2d combined_cld("combined_cld", ncol, nlev); - - //logical, dimension(ngpt,ncol,nlev) :: iscloudy - bool3d iscloudy("iscloudy", ngpt, ncol, nlev); - - // Combined snow and cloud fraction - parallel_for(SimpleBounds<2>(ncol, nlev), YAKL_LAMBDA (int icol, int ilev) - { - combined_cld(icol,ilev) = std::max(cld(icol,ilev), cldfsnow(icol,ilev)); - }); - - // Get stochastic subcolumn cloud mask - mcica_subcol_mask(ngpt, ncol, nlev, combined_cld, iscloudy); - - // Generate subcolumns for homogeneous clouds - parallel_for(SimpleBounds<3>(ngpt, nlev, ncol), YAKL_LAMBDA (int igpt, int ilev, int icol) - { - if (iscloudy(igpt,icol,ilev) && combined_cld(icol,ilev) > 0.) { - tau_gpt(icol,ilev,igpt) = tau_bnd(icol,ilev,gpt2bnd(igpt)); - ssa_gpt(icol,ilev,igpt) = ssa_bnd(icol,ilev,gpt2bnd(igpt)); - asm_gpt(icol,ilev,igpt) = asm_bnd(icol,ilev,gpt2bnd(igpt)); - } else { - tau_gpt(icol,ilev,igpt) = 0.; - ssa_gpt(icol,ilev,igpt) = 1.; - asm_gpt(icol,ilev,igpt) = 0.; - } - }); - } - -//---------------------------------------------------------------------------- -// Do MCICA sampling of optics here. This will map bands to gpoints, -// while doing stochastic sampling of cloud state -void Optics::sample_cloud_optics_lw (int ncol, int nlev, int ngpt, const int1d& gpt2bnd, - const real2d& pmid, const real2d& cld, const real2d& cldfsnow, - const real3d& tau_bnd, const real3d& tau_gpt) -{ - //real(r8), dimension(ncol,nlev) :: combined_cld - real2d combined_cld("combined_cld", ncol, nlev); - - //logical, dimension(ngpt,ncol,nlev) :: iscloudy - bool3d iscloudy("iscloudy", ngpt, ncol, nlev); - - // Combine cloud and snow fractions for MCICA sampling - parallel_for(SimpleBounds<2>(ncol, nlev), YAKL_LAMBDA (int icol, int ilev) - { - combined_cld(icol,ilev) = std::max(cld(icol,ilev), cldfsnow(icol,ilev)); - }); - - // Get the stochastic subcolumn cloudy mask - mcica_subcol_mask(ngpt, ncol, nlev, combined_cld, iscloudy); - - // Map optics to g-points, selecting a single subcolumn for each - // g-point. This implementation generates homogeneous clouds, but it would be - // straightforward to extend this to handle horizontally heterogeneous clouds - // as well. - parallel_for(SimpleBounds<3>(ngpt, nlev, ncol), YAKL_LAMBDA (int igpt, int ilev, int icol) - { - if (iscloudy(igpt,icol,ilev) && combined_cld(icol,ilev) > 0.) { - tau_gpt(icol,ilev,igpt) = tau_bnd(icol,ilev,gpt2bnd(igpt)); - } else { - tau_gpt(icol,ilev,igpt) = 0.; - } - }); - } - -//---------------------------------------------------------------------------- -void Optics::set_aerosol_optics_sw (int icall, int ncol, int nlev, int nswbands, real dt, - const int1d& night_indices, bool is_cmip6_volc, const real3d& tau_out, - const real3d& ssa_out, const real3d& asm_out, const real2d& clear_rh) -{ - // NOTE: aer_rad_props expects 0:pver indexing on these! It appears this is to - // account for the extra layer added above model top, but it is not entirely - // clear. This is not done for the longwave, and it is not really documented - // anywhere that I can find. Regardless, optical properties for the zero index - // are set to zero in aer_rad_props_sw as far as I can tell. - // - // NOTE: dimension ordering is different than for cloud optics! - real3d tau("tau", ncol, nlev+1, nswbands); - real3d tau_w("tau_w", ncol, nlev+1, nswbands); - real3d tau_w_g("tau_w_g", ncol, nlev+1, nswbands); - real3d tau_w_f("tau_w_f", ncol, nlev+1, nswbands); - - // Get aerosol absorption optical depth from CAM routine - yakl::memset(tau, 0.); - yakl::memset(tau_w, 0.); - yakl::memset(tau_w_g, 0.); - yakl::memset(tau_w_f, 0.); - - int1d ic("icount",1); - intHost1d ic_host("ic_host",1); - parallel_for(SimpleBounds<1>(ncol), YAKL_LAMBDA (int i) - { - if (night_indices(i) > 0) ++ic(1); - }); - ic.deep_copy_to(ic_host); - - aero_optics.aer_rad_props_sw(icall, dt, - ic_host(1), night_indices, is_cmip6_volc, - tau, tau_w, tau_w_g, tau_w_f, clear_rh); - - // Extract quantities from products - parallel_for(SimpleBounds<3>(ncol, nlev, nswbands), YAKL_LAMBDA (int icol, int ilev, int iband) - { - // Copy cloud optical depth over directly - tau_out(icol,ilev,iband) = tau(icol,ilev,iband); - // Extract single scattering albedo from the product-defined fields - if (tau(icol,ilev,iband) > 0) { - ssa_out(icol,ilev,iband) = tau_w(icol,ilev,iband) / tau(icol,ilev,iband); - } else { - ssa_out(icol,ilev,iband) = 1.; - } - - // Extract asymmetry parameter from the product-defined fields - if (tau_w(icol,ilev,iband) > 0) { - asm_out(icol,ilev,iband) = tau_w_g(icol,ilev,iband) / tau_w(icol,ilev,iband); - } else { - asm_out(icol,ilev,iband) = 0.; - } - }); - } - -//---------------------------------------------------------------------------- -void Optics::set_aerosol_optics_lw (int icall, real dt, bool is_cmip6_volc, const real2d& zi, - const real3d& tau, const real2d& clear_rh) -{ - // Get aerosol absorption optical depth from CAM routine - yakl::memset(tau, 0.); - aero_optics.aer_rad_props_lw(is_cmip6_volc, icall, dt, zi, tau, clear_rh); -} - - -// Arrays use CAM vertical index convention: index increases from top to bottom. -// This index ordering is assumed in the maximum-random overlap algorithm which starts -// at the top of a column and marches down, with each layer depending on the state -// of the layer above it. -// -void Optics::mcica_subcol_mask (int ngpt, int ncol, int nlev, - const real2d& cldfrac, const bool3d& iscloudy) -{ - // Local vars - const real cldmin = 1.0e-80; // min cloud fraction - real2d cldf("cldf",ncol,nlev); // cloud fraction clipped to cldmin - real3d cdf("cdf", ngpt, ncol, nlev); // random numbers - - // clip cloud fraction - parallel_for(SimpleBounds<2>(ncol, nlev), YAKL_LAMBDA (int icol, int ilev) - { - cldf(icol,ilev) = cldfrac(icol,ilev); - if (cldf(icol,ilev) < cldmin) cldf(icol,ilev) = 0.; - }); - - amrex::RandomEngine engine; - // Generate random numbers in each subcolumn at every level - parallel_for(SimpleBounds<3>(ngpt, ncol, nlev), YAKL_LAMBDA (int isubcol, int icol, int ilev) - { - cdf(isubcol,icol,ilev) = amrex::Random(engine); - }); - - // Maximum-Random overlap - // i) pick a random number for top layer. - // ii) walk down the column: - // - if the layer above is cloudy, use the same random number as in the layer above - // - if the layer above is clear, use a new random number - parallel_for(SimpleBounds<3>(nlev, ncol, ngpt), YAKL_LAMBDA (int k, int i, int isubcol) - { - if (k > 1) { - if (cdf(isubcol,i,k-1) > 1. - cldf(i,k-1) ) { - cdf(isubcol,i,k) = cdf(isubcol,i,k-1); - } else { - cdf(isubcol,i,k) = cdf(isubcol,i,k) * (1. - cldf(i,k-1)); - } - iscloudy(isubcol,i,k) = cdf(isubcol,i,k) >= 1.-cldf(i,k) ? true : false; - } - }); -} - diff --git a/Source/Radiation/ERF_OrbCosZenith.H b/Source/Radiation/ERF_OrbCosZenith.H new file mode 100644 index 000000000..d1da7db53 --- /dev/null +++ b/Source/Radiation/ERF_OrbCosZenith.H @@ -0,0 +1,43 @@ +#ifndef ERF_ORB_COS_ZENITH_H +#define ERF_ORB_COS_ZENITH_H + +#include +#include + +amrex::Real +orbital_cos_zenith (amrex::Real& jday, + amrex::Real& lat, + amrex::Real& lon, + amrex::Real& declin, + amrex::Real dt_avg = -1., + amrex::Real uniform_angle = -1., + amrex::Real constant_zenith_angle_deg = -1.); + + +amrex::Real +orbital_avg_cos_zenith (amrex::Real& jday, + amrex::Real& lat, + amrex::Real& lon, + amrex::Real& declin, + amrex::Real& dt_avg); + + +void +orbital_params (int& iyear_AD, + amrex::Real& eccen, + amrex::Real& obliq, + amrex::Real& mvelp, + amrex::Real& obliqr, + amrex::Real& lambm0, + amrex::Real& mvelpp); + + +void +orbital_decl (amrex::Real& calday, + amrex::Real& eccen, + amrex::Real& mvelpp, + amrex::Real& lambm0, + amrex::Real& obliqr, + amrex::Real& delta, + amrex::Real& eccf); +#endif diff --git a/Source/Radiation/ERF_OrbCosZenith.cpp b/Source/Radiation/ERF_OrbCosZenith.cpp new file mode 100644 index 000000000..216ff72e7 --- /dev/null +++ b/Source/Radiation/ERF_OrbCosZenith.cpp @@ -0,0 +1,573 @@ +#include + +using namespace amrex; + +Real +orbital_cos_zenith (Real& jday, + Real& lat, + Real& lon, + Real& declin, + Real dt_avg = -1., + Real uniform_angle = -1., + Real constant_zenith_angle_deg = -1.) +{ + // Constant zenith angle is true + if ( constant_zenith_angle_deg >= 0. ) { + return std::cos( constant_zenith_angle_deg * PI/180. ); + } + + // Uniform angle is true + if ( uniform_angle >= 0.) { + return std::cos(uniform_angle); + } + + // Perform the calculation of Orbital_Cos_Zenith + bool use_dt_avg = false; + if (dt_avg > 0.) { + use_dt_avg = true; + } + // If dt for the average cosz is specified, then call the shr_orb_avg_cosz + if (use_dt_avg) { + return Orbital_Avg_Cos_Zenith(jday, lat, lon, declin, dt_avg); + } else { + return std::sin(lat)*std::sin(declin) - std::cos(lat)*std::cos(declin) * + std::cos((jday-floor(jday))*Real(2.0)*PI + lon); + } +} + + +Real +orbital_avg_cos_zenith (Real& jday, + Real& lat, + Real& lon, + Real& declin, + Real& dt_avg) +{ + // adjust latitude so that its tangent will be defined + if (lat == PIoTwo) { + del = lat - Real(1.0e-05); + } else if (lat == -piover2) { + del = lat + Real(1.0e-05); + } else { + del = lat; + } + + // adjust declination so that its tangent will be defined + if (declin == PIoTwo) { + phi = declin - Real(1.0e-05); + } else if (declin == -piover2) { + phi = declin + Real(1.0e-05); + } else { + phi = declin; + } + + // define the cosine of the half-day length + // adjust for cases of all daylight or all night + Real h; + Real cos_h = - std::tan(del) * std::tan(phi); + if (cos_h <= -1.0) { + h = pi; + } else if (cos_h >= 1.0) { + h = 0.0; + } else { + h = std::acos(cos_h); + } + + // Define Local Time t and t + dt + // adjust t to be between -pi and pi + Real t1 = (jday - int(jday)) * 2.0*PI + lon - PI; + if (t1 >= PI) { + t1 = t1 - 2.0*PI; + } else if (t1 < -PI) { + t1 = t1 + 2.0*PI; + } + + Real dt = dt_avg / Real(86400.0) * 2.0*PI; + Real t2 = t1 + dt; + + // Compute Cosine Solar Zenith angle + // define terms needed in the cosine zenith angle equation + Real aa = std::sin(lat) * std::sin(declin); + Real bb = std::cos(lat) * std::cos(declin); + + // define the hour angle + // force it to be between -h and h + // consider the situation when the night period is too short + Real tt1,tt2,tt3,tt4; + if ( (t2 >= PI) && (t1 <= PI) && (pi - h <= dt)) { + tt2 = h; + tt1 = std::min(std::max(t1, -h), h); + tt4 = std::min(std::max(t2, 2.0*PI - h), 2.0*PI + h); + tt3 = 2.0*PI - h; + } else if (t2 >= -pi .and. t1 <= -pi .and. pi - h <= dt) { + tt2 = - 2.0*PI + h; + tt1 = std::min(std::max(t1, -2.0*PI - h), -2.0*PI + h); + tt4 = std::min(std::max(t2, -h), h); + tt3 = -h; + } else { + if (t2 > pi) { + tt2 = std::min(std::max(t2 - 2.0*PI, -h), h); + } else if (t2 < - PI) { + tt2 = std::min(std::max(t2 + 2.0*PI, -h), h); + } else { + tt2 = std::min(std::max(t2 , -h), h); + } + + if (t1 > pi) { + tt1 = std::min(std::max(t1 - 2.0*PI, -h), h); + } else if (t1 < - pi) { + tt1 = std::min(std::max(t1 + 2.0*PI, -h), h); + } else { + tt1 = std::min(std::max(t1 , -h), h); + } + tt4 = 0.0; + tt3 = 0.0; + } + + // perform a time integration to obtain cosz if desired + // output is valid over the period from t to t + dt + if ( (tt2 > tt1) || (tt4 > tt3) ) { + return (aa * (tt2 - tt1) + bb * (sin(tt2) - sin(tt1))) / dt + + (aa * (tt4 - tt3) + bb * (sin(tt4) - sin(tt3))) / dt; + } else { + return 0.0; + } +} + + +void +orbital_params (int& iyear_AD, + Real& eccen, + Real& obliq, + Real& mvelp, + Real& obliqr, + Real& lambm0, + Real& mvelpp) +{ + /* + !------------------------------------------------------------------------------- + ! + ! Calculate earths orbital parameters using Dave Threshers formula which + ! came from Berger, Andre. 1978 "A Simple Algorithm to Compute Long-Term + ! Variations of Daily Insolation". Contribution 18, Institute of Astronomy + ! and Geophysics, Universite Catholique de Louvain, Louvain-la-Neuve, Belgium + ! + !------------------------------------------------------------------------------- + */ + + int poblen = 47; // # of elements in series wrt obliquity + int pecclen = 19; // # of elements in series wrt eccentricity + int pmvelen = 78; // # of elements in series wrt vernal equinox + static constexpr Real psecdeg = Real(1.0)/Real(3600.0); // arc sec to deg conversion + static constexpr Real degrad = PI/Real(180.); // degree to radian conversion factor + + // Cosine series data for computation of obliquity: amplitude (arc seconds), + // rate (arc seconds/year), phase (degrees). + // amplitudes for obliquity cos series + Vector obamp = {-2462.2214466, -857.3232075, -629.3231835, + -414.2804924, -311.7632587, 308.9408604, + -162.5533601, -116.1077911, 101.1189923, + -67.6856209, 24.9079067, 22.5811241, + -21.1648355, -15.6549876, 15.3936813, + 14.6660938, -11.7273029, 10.2742696, + 6.4914588, 5.8539148, -5.4872205, + -5.4290191, 5.1609570, 5.0786314, + -4.0735782, 3.7227167, 3.3971932, + -2.8347004, -2.6550721, -2.5717867, + -2.4712188, 2.4625410, 2.2464112, + -2.0755511, -1.9713669, -1.8813061, + -1.8468785, 1.8186742, 1.7601888, + -1.5428851, 1.4738838, -1.4593669, + 1.4192259, -1.1818980, 1.1756474, + -1.1316126, 1.0896928}; + // rates for obliquity cosine series + Vector obrate = {31.609974, 32.620504, 24.172203, + 31.983787, 44.828336, 30.973257, + 43.668246, 32.246691, 30.599444, + 42.681324, 43.836462, 47.439436, + 63.219948, 64.230478, 1.010530, + 7.437771, 55.782177, 0.373813, + 13.218362, 62.583231, 63.593761, + 76.438310, 45.815258, 8.448301, + 56.792707, 49.747842, 12.058272, + 75.278220, 65.241008, 64.604291, + 1.647247, 7.811584, 12.207832, + 63.856665, 56.155990, 77.448840, + 6.801054, 62.209418, 20.656133, + 48.344406, 55.145460, 69.000539, + 11.071350, 74.291298, 11.047742, + 0.636717, 12.844549}; + + // phases for obliquity cosine series + Vector obphas = {251.9025, 280.8325, 128.3057, + 292.7252, 15.3747, 263.7951, + 308.4258, 240.0099, 222.9725, + 268.7809, 316.7998, 319.6024, + 143.8050, 172.7351, 28.9300, + 123.5968, 20.2082, 40.8226, + 123.4722, 155.6977, 184.6277, + 267.2772, 55.0196, 152.5268, + 49.1382, 204.6609, 56.5233, + 200.3284, 201.6651, 213.5577, + 17.0374, 164.4194, 94.5422, + 131.9124, 61.0309, 296.2073, + 135.4894, 114.8750, 247.0691, + 256.6114, 32.1008, 143.6804, + 16.8784, 160.6835, 27.5932, + 348.1074, 82.6496}; + + // Cosine/sine series data for computation of eccentricity and fixed vernal + // equinox longitude of perihelion (fvelp): amplitude, + // rate (arc seconds/year), phase (degrees). + + // ampl for eccen/fvelp cos/sin series + Vector ecamp = {0.01860798, 0.01627522, -0.01300660, + 0.00988829, -0.00336700, 0.00333077, + -0.00235400, 0.00140015, 0.00100700, + 0.00085700, 0.00064990, 0.00059900, + 0.00037800, -0.00033700, 0.00027600, + 0.00018200, -0.00017400, -0.00012400, + 0.00001250}; + + // rates for eccen/fvelp cos/sin series + Vector ecrate = { 4.2072050, 7.3460910, 17.8572630, + 17.2205460, 16.8467330, 5.1990790, + 18.2310760, 26.2167580, 6.3591690, + 16.2100160, 3.0651810, 16.5838290, + 18.4939800, 6.1909530, 18.8677930, + 17.4255670, 6.1860010, 18.4174410, + 0.6678630}; + + // phases for eccen/fvelp cos/sin series + Vector ecphas = { 28.620089, 193.788772, 308.307024, + 320.199637, 279.376984, 87.195000, + 349.129677, 128.443387, 154.143880, + 291.269597, 114.860583, 332.092251, + 296.414411, 145.769910, 337.237063, + 152.092288, 126.839891, 210.667199, + 72.108838}; + + // Sine series data for computation of moving vernal equinox longitude of + // perihelion: amplitude (arc seconds), rate (arc sec/year), phase (degrees). + + // amplitudes for mvelp sine series + Vector mvamp = { 7391.0225890, 2555.1526947, 2022.7629188, + -1973.6517951, 1240.2321818, 953.8679112, + -931.7537108, 872.3795383, 606.3544732, + -496.0274038, 456.9608039, 346.9462320, + -305.8412902, 249.6173246, -199.1027200, + 191.0560889, -175.2936572, 165.9068833, + 161.1285917, 139.7878093, -133.5228399, + 117.0673811, 104.6907281, 95.3227476, + 86.7824524, 86.0857729, 70.5893698, + -69.9719343, -62.5817473, 61.5450059, + -57.9364011, 57.1899832, -57.0236109, + -54.2119253, 53.2834147, 52.1223575, + -49.0059908, -48.3118757, -45.4191685, + -42.2357920, -34.7971099, 34.4623613, + -33.8356643, 33.6689362, -31.2521586, + -30.8798701, 28.4640769, -27.1960802, + 27.0860736, -26.3437456, 24.7253740, + 24.6732126, 24.4272733, 24.0127327, + 21.7150294, -21.5375347, 18.1148363, + -16.9603104, -16.1765215, 15.5567653, + 15.4846529, 15.2150632, 14.5047426, + -14.3873316, 13.1351419, 12.8776311, + 11.9867234, 11.9385578, 11.7030822, + 11.6018181, -11.2617293, -10.4664199, + 10.4333970, -10.2377466, 10.1934446, + -10.1280191, 10.0289441, -10.0034259}; + + // rates for mvelp sine series + Vector mvrate = {31.609974, 32.620504, 24.172203, + 0.636717, 31.983787, 3.138886, + 30.973257, 44.828336, 0.991874, + 0.373813, 43.668246, 32.246691, + 30.599444, 2.147012, 10.511172, + 42.681324, 13.650058, 0.986922, + 9.874455, 13.013341, 0.262904, + 0.004952, 1.142024, 63.219948, + 0.205021, 2.151964, 64.230478, + 43.836462, 47.439436, 1.384343, + 7.437771, 18.829299, 9.500642, + 0.431696, 1.160090, 55.782177, + 12.639528, 1.155138, 0.168216, + 1.647247, 10.884985, 5.610937, + 12.658184, 1.010530, 1.983748, + 14.023871, 0.560178, 1.273434, + 12.021467, 62.583231, 63.593761, + 76.438310, 4.280910, 13.218362, + 17.818769, 8.359495, 56.792707, + 8.448301, 1.978796, 8.863925, + 0.186365, 8.996212, 6.771027, + 45.815258, 12.002811, 75.278220, + 65.241008, 18.870667, 22.009553, + 64.604291, 11.498094, 0.578834, + 9.237738, 49.747842, 2.147012, + 1.196895, 2.133898, 0.173168}; + + // phases for mvelp sine series + Vector mvrate mvphas = {251.9025, 280.8325, 128.3057, + 348.1074, 292.7252, 165.1686, + 263.7951, 15.3747, 58.5749, + 40.8226, 308.4258, 240.0099, + 222.9725, 106.5937, 114.5182, + 268.7809, 279.6869, 39.6448, + 126.4108, 291.5795, 307.2848, + 18.9300, 273.7596, 143.8050, + 191.8927, 125.5237, 172.7351, + 316.7998, 319.6024, 69.7526, + 123.5968, 217.6432, 85.5882, + 156.2147, 66.9489, 20.2082, + 250.7568, 48.0188, 8.3739, + 17.0374, 155.3409, 94.1709, + 221.1120, 28.9300, 117.1498, + 320.5095, 262.3602, 336.2148, + 233.0046, 155.6977, 184.6277, + 267.2772, 78.9281, 123.4722, + 188.7132, 180.1364, 49.1382, + 152.5268, 98.2198, 97.4808, + 221.5376, 168.2438, 161.1199, + 55.0196, 262.6495, 200.3284, + 201.6651, 294.6547, 99.8233, + 213.5577, 154.1631, 232.7153, + 138.3034, 204.6609, 106.5938, + 250.4676, 332.3345, 27.3039}; + + //---------------------------Local variables---------------------------------- + int i; // Index for series summations + Real obsum; // Obliquity series summation + Real cossum; // Cos series summation for eccentricity/fvelp + Real sinsum; // Sin series summation for eccentricity/fvelp + Real fvelp; // Fixed vernal equinox long of perihelion + Real mvsum; // mvelp series summation + Real beta; // Intermediate argument for lambm0 + Real years; // Years to time of interest ( pos <=> future) + Real eccen2; // eccentricity squared + Real eccen3; // eccentricity cubed + Real yb4_1950AD; // number of years before 1950 AD + + // Check for flag to use input orbit parameters + if ( iyear_AD == ORB_UNDEF_INT ) { + + // The AMIP II settings (for a 1995 orbit) are adopted + obliq = 23.4441; + eccen = 0.016715; + mvelp = 102.7; + eccen2 = eccen*eccen; + eccen3 = eccen2*eccen; + + } else { // Otherwise calculate based on years before present + + /* + The following calculates the earths obliquity, orbital eccentricity + (and various powers of it) and vernal equinox mean longitude of + perihelion for years in the past (future = negative of years past), + using constants (see parameter section) given in the program of: + + Berger, Andre. 1978 A Simple Algorithm to Compute Long-Term Variations + of Daily Insolation. Contribution 18, Institute of Astronomy and + Geophysics, Universite Catholique de Louvain, Louvain-la-Neuve, Belgium. + + and formulas given in the paper (where less precise constants are also + given): + + Berger, Andre. 1978. Long-Term Variations of Daily Insolation and + Quaternary Climatic Changes. J. of the Atmo. Sci. 35:2362-2367 + + The algorithm is valid only to 1,000,000 years past or hence. + For a solution valid to 5-10 million years past see the above author. + Algorithm below is better for years closer to present than is the + 5-10 million year solution. + + Years to time of interest must be negative of years before present + (1950) in formulas that follow. + */ + + yb4_1950AD = Real(1950.0) - Real(iyear_AD); + years = - yb4_1950AD; + + /* + In the summations below, cosine or sine arguments, which end up in + degrees, must be converted to radians via multiplication by degrad. + + Summation of cosine series for obliquity (epsilon in Berger 1978) in + degrees. Convert the amplitudes and rates, which are in arc secs, into + degrees via multiplication by psecdeg (arc seconds to degrees conversion + factor). For obliq, first term is Berger 1978 epsilon star; second + term is series summation in degrees. + */ + + obsum = 0.0; + for (int i(0); i 0.0) { + fvelp = .5*PI; + } + } else if (cossum <= 0.0) { + fvelp = std::atan(sinsum/cossum) + PI; + } else { + if (sinsum < 0.0) { + fvelp = std::atan(sinsum/cossum) + 2.0*PI; + } else { + fvelp = std::atan(sinsum/cossum); + } + } + + /* + Summation of sin series for computation of moving vernal equinox long + of perihelion (mvelp; omega bar in Berger 1978) in degrees. For mvelp, + first term is fvelp in degrees; second term is Berger 1978 psi bar + times years and in degrees; third term is Berger 1978 zeta; fourth + term is series summation in degrees. Convert the amplitudes and rates, + which are in arc seconds, into degrees via multiplication by psecdeg. + Series summation plus second and third terms constitute Berger 1978 + psi, which is the general precession. + */ + mvsum = 0.0; + for (int i(0); i= 360.0); + + } // end of test on whether to calculate or use input orbital params + + // Orbit needs the obliquity in radians + + obliqr = obliq*degrad; + + /* + 180 degrees must be added to mvelp since observations are made from the + earth and the sun is considered (wrongly for the algorithm) to go around + the earth. For a more graphic explanation see Appendix B in: + + A. Berger, M. Loutre and C. Tricot. 1993. Insolation and Earth Orbital + Periods. J. of Geophysical Research 98:10,341-10,362. + + Additionally, orbit will need this value in radians. So mvelp becomes + mvelpp (mvelp plus pi) + */ + + mvelpp = (mvelp + 180.0)*degrad; + + // Set up an argument used several times in lambm0 calculation ahead. + + beta = std::sqrt(1.0 - eccen2); + + /* + The mean longitude at the vernal equinox (lambda m nought in Berger + 1978; in radians) is calculated from the following formula given in + Berger 1978. At the vernal equinox the true longitude (lambda in Berger + 1978) is 0. + */ + + lambm0 = 2.0*( (.5*eccen + .125*eccen3)*(1.0 + beta)*std::sin(mvelpp) + - .250*eccen2*(.5 + beta)*std::sin(2.0*mvelpp) + + .125*eccen3*(1./3. + beta)*std::sin(3.0*mvelpp) ); +} + + +void +orbital_decl (Real& calday, + Real& eccen, + Real& mvelpp, + Real& lambm0, + Real& obliqr, + Real& delta, + Real& eccf) +{ + + Real lambm; // Lambda m, mean long of perihelion (rad) + Real lmm; // Intermediate argument involving lambm + Real lamb; // Lambda, the earths long of perihelion + Real invrho; // Inverse normalized sun/earth distance + Real sinl; // Sine of lmm + static constexpr Real dayspy = Real(365.0); // Day per year + static constexpr Real ve = Real( 80.5); // Calday of vernal equinox + + /* + Compute eccentricity factor and solar declination using + day value where a round day (such as 213.0) refers to 0z at + Greenwich longitude. + + Use formulas from Berger, Andre 1978: Long-Term Variations of Daily + Insolation and Quaternary Climatic Changes. J. of the Atmo. Sci. + 35:2362-2367. + + To get the earths true longitude (position in orbit; lambda in Berger + 1978) which is necessary to find the eccentricity factor and declination, + must first calculate the mean longitude (lambda m in Berger 1978) at + the present day. This is done by adding to lambm0 (the mean longitude + at the vernal equinox, set as March 21 at noon, when lambda=0; in radians) + an increment (delta lambda m in Berger 1978) that is the number of + days past or before (a negative increment) the vernal equinox divided by + the days in a model year times the 2*pi radians in a complete orbit. + */ + + lambm = lambm0 + (calday - ve)*2.0*PI/dayspy; + lmm = lambm - mvelpp; + + // The earths true longitude, in radians, is then found from + // the formula in Berger 1978: + + sinl = std::sin(lmm); + lamb = lambm + eccen*( 2.0*sinl + eccen*( 1.25*sin(2.0*lmm) + + eccen*((13.0/12.0)*std::sin(3.0*lmm) - 0.25*sinl))); + + /* + Using the obliquity, eccentricity, moving vernal equinox longitude of + perihelion (plus), and earths true longitude, the declination (delta) + and the normalized earth/sun distance (rho in Berger 1978; actually inverse + rho will be used), and thus the eccentricity factor (eccf), can be + calculated from formulas given in Berger 1978. + */ + + invrho = (1.0 + eccen*std::cos(lamb - mvelpp)) / (1.0 - eccen*eccen); + + // Set solar declination and eccentricity factor + + delta = std::asin(std::sin(obliqr)*std::sin(lamb)); + eccf = invrho*invrho; +} diff --git a/Source/Radiation/ERF_Parameterizations.H b/Source/Radiation/ERF_Parameterizations.H deleted file mode 100644 index 61427efe8..000000000 --- a/Source/Radiation/ERF_Parameterizations.H +++ /dev/null @@ -1,43 +0,0 @@ -// -// parameterization provides the tracer particle properties -// that are not provided by the microphysics model -// -#ifndef ERF_PARAMETERIZATIONS_H_ -#define ERF_PARAMETERIZATIONS_H_ - -#include "rrtmgp_const.h" -#include "ERF_RadConstants.H" -#include "ERF_Constants.H" - - AMREX_FORCE_INLINE - real liquid_effective_radius (real temperature, real landfrac, real icefrac = 0., real snowh = 0.) - { - const real rliqland = 8.0; // liquid drop size if over land - const real rliqocean = 14.0; // liquid drop size if over ocean - const real rliqice = 14.0; // liquid drop size if over sea ice - - real reffective = 0.; - // jrm Reworked effective radius algorithm - // Start with temperature-dependent value appropriate for continental air - reffective = rliqland+(rliqocean-rliqland)*std::min(1.0, std::max(0.0, (tmelt-temperature)*0.05)); - - // Modify for snow depth over land - reffective = reffective + (rliqocean-reffective)*std::min(1.0, std::max(0.0, snowh*10.)); - - // Ramp between polluted value over land to clean value over ocean. - reffective = reffective + (rliqocean-reffective) * std::min(1.0, std::max(0.0, 1.0 - landfrac)); - - // Ramp between the resultant value and a sea ice value in the presence of ice. - reffective = reffective + (rliqice-reffective) * std::min(1.0, std::max(0.0, icefrac)); - return reffective; - } - - AMREX_FORCE_INLINE - real ice_effective_radius(real temperature) - { - int index = int(temperature - (RadConstants::icesize_table_min_temp - 1.)); - index = std::min(std::max(index, 1), 94); - real fraction = temperature-int(temperature); - return RadConstants::retab[index]*(1.-fraction)+RadConstants::retab[index+1]*fraction; - } -#endif diff --git a/Source/Radiation/ERF_PhysProp.H b/Source/Radiation/ERF_PhysProp.H deleted file mode 100644 index 8c11c1733..000000000 --- a/Source/Radiation/ERF_PhysProp.H +++ /dev/null @@ -1,864 +0,0 @@ - -// Properties of aerosols that are used by radiation and other parameterizations. - -#ifndef ERF_PHYS_PROP_H_ -#define ERF_PHYS_PROP_H_ - -#include "YAKL_netcdf.h" -#include "rrtmgp_const.h" -#include "ERF_Rad_constants.H" -#include -#include -#include - -using yakl::fortran::parallel_for; -using yakl::fortran::SimpleBounds; - -class PhysProp { - public: - // Data from one input dataset is stored in a structure of type(physprop_type). - struct physprop_t { - std::string sourcefile; // Absolute pathname of data file. - std::string opticsmethod; // one of {hygro, nonhygro} - - // for hygroscopic species of externally mixed aerosols - real2d sw_hygro_ext; - real2d sw_hygro_ssa; - real2d sw_hygro_asm; - real2d lw_hygro_abs; - - // for nonhygroscopic species of externally mixed aerosols - real1d sw_nonhygro_ext; - real1d sw_nonhygro_ssa; - real1d sw_nonhygro_asm; - real1d sw_nonhygro_scat; - real1d sw_nonhygro_ascat; - real1d lw_abs; - - // complex refractive index - real1d refindex_real_aer_sw; - real1d refindex_im_aer_sw; - real1d refindex_real_aer_lw; - real1d refindex_im_aer_lw; - - // for radius-dependent mass-specific quantities - real2d r_sw_ext; - real2d r_sw_scat; - real2d r_sw_ascat; - real2d r_lw_abs; - real1d mu; - - // for modal optics - real4d extpsw; // specific extinction - real4d abspsw; // specific absorption - real4d asmpsw; // asymmetry factor - real4d absplw; // specific absorption - real2d refrtabsw; // table of real refractive indices for aerosols visible - real2d refitabsw; // table of imag refractive indices for aerosols visible - real2d refrtablw; // table of real refractive indices for aerosols infrared - real2d refitablw; // table of imag refractive indices for aerosols infrared - - // microphysics parameters. - std::string aername; // for output of number concentration - real density_aer; // density of aerosol (kg/m3) - real hygro_aer; // hygroscopicity of aerosol - real dryrad_aer; // number mode radius (m) of aerosol size distribution - real dispersion_aer; // geometric standard deviation of aerosol size distribution - real num_to_mass_aer; // ratio of number concentration to mass concentration (#/kg) - // *** Is this actually (kg/#) ??? - //mode parameters - int ncoef; // number of Chebyshev coefficients - int prefr; // dimension in table of real refractive indices - int prefi; // dimension in table of imag refractive indices - real sigmag; // geometric standard deviation of the number distribution for aerosol mode - real dgnum; // geometric dry mean diameter of the number distribution for aerosol mode - real dgnumlo; // lower limit of dgnum - real dgnumhi; // upper limit of dgnum - real rhcrystal; // crystallization relative humidity for mode - real rhdeliques; // deliquescence relative humidity for mode - }; - - // This module stores data in an array of physprop_type structures. The way this data - // is accessed outside the module is via a physprop ID, which is an index into the array. - std::vector physprop; - - //Temporary storage location for filenames in namelist, and construction of dynamic index - // to properties. The unique filenames specified in the namelist are the identifiers of - // the properties. Searching the uniquefilenames array provides the index into the physprop - // array. - std::vector uniquefilenames; - - public: - void physprop_accum_unique_files (const std::string& filename, - const std::string& type) - { - // Count number of aerosols in input radname array. Aerosols are identified - // as strings with a ".nc" suffix. - // Construct a cumulative list of unique filenames containing physical property data. - - // check if filename is either a bulk aerosol or a mode - if (type == "A" || type == "M") { - // check if this filename has been used by another aerosol. If not - // then add it to the list of unique names. - if (physprop_get_id(filename) < 0) - uniquefilenames.push_back(filename); - } - } - - // Read properties from the aerosol data files. - // ***N.B.*** The calls to physprop_accum_unique_files must be made before calling - // this init routine. physprop_accum_unique_files is responsible for building - // the list of files to be read here. - void physprop_init () - { - int numphysprops = uniquefilenames.size(); - physprop.resize(numphysprops); - - for(auto fileindex = 0; fileindex < numphysprops; ++fileindex) { - physprop[fileindex].sourcefile = uniquefilenames[fileindex]; - aerosol_optics_init(physprop[fileindex]); - } - } - - // Look for filename in the global list of unique filenames (module data uniquefilenames). - // If found, return it's index in the list. Otherwise return -1. - int physprop_get_id (std::string filename) const - { - auto physprop_id = -1; - auto numphysprops = uniquefilenames.size(); - for(auto iphysprop = 0; iphysprop < numphysprops; ++iphysprop) { - if(uniquefilenames[iphysprop] == filename) { - physprop_id = iphysprop; - break; - } - } - return physprop_id; - } - - void get_sourcefile (int& id, std::string& sourcefile) const - { - if (id < 0 || id > physprop.size()) - printf("get_sourcefile: illegal ID value %d\n", id); - sourcefile = physprop[id].sourcefile; - } - - void get_opticstype (int& id, std::string& opticstype) const - { - if (id < 0 || id > physprop.size()) - printf("get_opticstype: illegal ID value %d\n", id); - opticstype = physprop[id].opticsmethod; - } - - void get_sw_hygro_ext (int& id, real2d& sw_hygro_ext) const - { - if (id < 0 || id > physprop.size()) - printf("get_sw_hygro_ext: illegal ID value %d\n", id); - sw_hygro_ext = physprop[id].sw_hygro_ext; - } - - void get_sw_hygro_ssa (int& id, real2d& sw_hygro_ssa) const - { - if (id < 0 || id > physprop.size()) - printf("get_sw_hygro_ssa: illegal ID value %d\n", id); - sw_hygro_ssa = physprop[id].sw_hygro_ssa; - } - - void get_sw_hygro_asm (int& id, real2d& sw_hygro_asm) const - { - if (id < 0 || id > physprop.size()) - printf("get_sw_hygro_asm: illegal ID value %d\n", id); - sw_hygro_asm = physprop[id].sw_hygro_asm; - } - - void get_lw_hygro_abs (int& id, real2d& lw_hygro_abs) const - { - if (id < 0 || id > physprop.size()) - printf("get_lw_hygro_abs: illegal ID value %d\n", id); - lw_hygro_abs = physprop[id].lw_hygro_abs; - } - - void get_sw_nonhygro_ext (int& id, real1d& sw_nonhygro_ext) const - { - if (id < 0 || id > physprop.size()) - printf("get_sw_nonhygro_ext: illegal ID value %d\n", id); - sw_nonhygro_ext = physprop[id].sw_nonhygro_ext; - } - - void get_sw_nonhygro_ssa (int& id, real1d& sw_nonhygro_ssa) const - { - if (id < 0 || id > physprop.size()) - printf("get_sw_nonhygro_ssa: illegal ID value %d\n", id); - sw_nonhygro_ssa = physprop[id].sw_nonhygro_ssa; - } - - void get_sw_nonhygro_asm (int& id, real1d& sw_nonhygro_asm) const - { - if (id < 0 || id > physprop.size()) - printf("get_sw_nonhygro_asm: illegal ID value %d\n", id); - sw_nonhygro_asm = physprop[id].sw_nonhygro_asm; - } - - void get_sw_nonhygro_scat (int& id, real1d& sw_nonhygro_scat) const - { - if (id < 0 || id > physprop.size()) - printf("get_sw_nonhygro_scat: illegal ID value %d\n", id); - sw_nonhygro_scat = physprop[id].sw_nonhygro_scat; - } - - void get_sw_nonhygro_ascat (int& id, real1d& sw_nonhygro_ascat) const - { - if (id < 0 || id > physprop.size()) - printf("get_sw_nonhygro_ascat: illegal ID value %d\n", id); - sw_nonhygro_ascat = physprop[id].sw_nonhygro_ascat; - } - - void get_lw_abs (int& id, real1d& lw_abs) const - { - if (id < 0 || id > physprop.size()) - printf("get_lw_abs: illegal ID value %d\n", id); - lw_abs = physprop[id].lw_abs; - } - - void get_ref_real_aer_sw (int& id, real1d& ref_real_aer_sw) const - { - if (id < 0 || id > physprop.size()) - printf("get_ref_real_aer_sw: illegal ID value %d\n", id); - ref_real_aer_sw = physprop[id].refindex_real_aer_sw; - } - - void get_ref_real_aer_lw (int& id, real1d& ref_real_aer_lw) const - { - if (id < 0 || id > physprop.size()) - printf("get_ref_real_aer_lw: illegal ID value %d\n", id); - ref_real_aer_lw = physprop[id].refindex_real_aer_lw; - } - - void get_ref_im_aer_sw (int& id, real1d& ref_im_aer_sw) const - { - if (id < 0 || id > physprop.size()) - printf("get_ref_im_aer_sw: illegal ID value %d\n", id); - ref_im_aer_sw = physprop[id].refindex_im_aer_sw; - } - - void get_ref_im_aer_lw (int& id, real1d& ref_im_aer_lw) const - { - if (id < 0 || id > physprop.size()) - printf("get_ref_im_aer_lw: illegal ID value %d\n", id); - ref_im_aer_lw = physprop[id].refindex_im_aer_lw; - } - - void get_r_sw_ext (int& id, real2d& r_sw_ext) const - { - if (id < 0 || id > physprop.size()) - printf("get_r_sw_ext: illegal ID value %d\n", id); - r_sw_ext = physprop[id].r_sw_ext; - } - - void get_r_sw_scat (int& id, real2d& r_sw_scat) const - { - if (id < 0 || id > physprop.size()) - printf("get_r_sw_scat: illegal ID value %d\n", id); - r_sw_scat = physprop[id].r_sw_scat; - } - - void get_r_sw_ascat (int& id, real2d& r_sw_ascat) const - { - if (id < 0 || id > physprop.size()) - printf("get_r_sw_ascat: illegal ID value %d\n", id); - r_sw_ascat = physprop[id].r_sw_ascat; - } - - void get_r_lw_abs (int& id, real2d& r_lw_abs) const - { - if (id < 0 || id > physprop.size()) - printf("get_r_lw_abs: illegal ID value %d\n", id); - r_lw_abs = physprop[id].r_lw_abs; - } - - void get_mu (int& id, real1d& mu) const - { - if (id < 0 || id > physprop.size()) - printf("get_mu: illegal ID value %d\n", id); - mu = physprop[id].mu; - } - - void get_extpsw (int& id, real4d& extpsw) const - { - if (id < 0 || id > physprop.size()) - printf("get_expsw: illegal ID value %d\n", id); - extpsw = physprop[id].extpsw; - } - - void get_abspsw (int& id, real4d& abspsw) const - { - if (id < 0 || id > physprop.size()) - printf("get_abspsw: illegal ID value %d\n", id); - abspsw = physprop[id].abspsw; - } - - void get_asmpsw (int& id, real4d& asmpsw) const - { - if (id < 0 || id > physprop.size()) - printf("get_asmpsw: illegal ID value %d\n", id); - asmpsw = physprop[id].asmpsw; - } - - void get_absplw (int& id, real4d& absplw) const - { - if (id < 0 || id > physprop.size()) - printf("get_absplw: illegal ID value %d\n", id); - absplw = physprop[id].absplw; - } - - void get_refrtabsw (int& id, real2d& refrtabsw) const - { - if (id < 0 || id > physprop.size()) - printf("get_refrtabsw: illegal ID value %d\n", id); - refrtabsw = physprop[id].refrtabsw; - } - - void get_refitabsw (int& id, real2d& refitabsw) const - { - if (id < 0 || id > physprop.size()) - printf("get_refitabsw: illegal ID value %d\n", id); - refitabsw = physprop[id].refitabsw; - } - - void get_refrtablw (int& id, real2d& refrtablw) const - { - if (id < 0 || id > physprop.size()) - printf("get_refrtablw: illegal ID value %d\n", id); - refrtablw = physprop[id].refrtablw; - } - - void get_refitablw (int& id, real2d& refitablw) const - { - if (id < 0 || id > physprop.size()) - printf("ger_refitablw: illegal ID value %d\n", id); - refitablw = physprop[id].refitablw; - } - - void get_aername(int& id, std::string& aername) const - { - if (id < 0 || id > physprop.size()) - printf("get_aername: illegal ID value %d\n", id); - aername = physprop[id].aername; - } - - void get_density_aer(int& id, real& density_aer) const - { - if (id < 0 || id > physprop.size()) - printf("get_density_aer: illegal ID value %d\n", id); - density_aer = physprop[id].density_aer; - } - - void get_hygro_aer (int& id, real& hygro_aer) const - { - if (id < 0 || id > physprop.size()) - printf("get_hygro_aer: illegal ID value %d\n", id); - hygro_aer = physprop[id].hygro_aer; - } - - void get_dryrad_aer (int& id, real& dryrad_aer) const - { - if (id < 0 || id > physprop.size()) - printf("get_dryrad_aer: illegal ID value %d\n", id); - dryrad_aer = physprop[id].dryrad_aer; - } - - void get_dispersion_aer (int& id, real& dispersion_aer) const - { - if (id < 0 || id > physprop.size()) - printf("get_dispersion_aer: illegal ID value %d\n", id); - dispersion_aer = physprop[id].dispersion_aer; - } - - void get_num_to_mass_aer (int& id, real& num_to_mass_aer) const - { - if (id < 0 || id > physprop.size()) - printf("get_num_to_mass_aer: illegal ID value %d\n", id); - num_to_mass_aer = physprop[id].num_to_mass_aer; - } - - void get_ncoef (int& id, int& ncoef) const - { - if (id < 0 || id > physprop.size()) - printf("get_ncoef: illegal ID value %d\n", id); - ncoef = physprop[id].ncoef; - } - - void get_prefr (int& id, int& prefr) const - { - if (id < 0 || id > physprop.size()) - printf("get_prefr: illegal ID value %d\n", id); - prefr = physprop[id].prefr; - } - - void get_prefi (int& id, int& prefi) const - { - if (id < 0 || id > physprop.size()) - printf("get_prefi: illegal ID value %d\n", id); - prefi = physprop[id].prefi; - } - - void get_sigmag (int& id, real& sigmag) const - { - if (id < 0 || id > physprop.size()) - printf("get_sigmag: illegal ID value %d\n", id); - sigmag = physprop[id].sigmag; - } - - void get_dgnum (int& id, real& dgnum) const - { - if (id < 0 || id > physprop.size()) - printf("get_dgnum: illegal ID value %d\n", id); - dgnum = physprop[id].dgnum; - } - - void get_dgnumlo (int& id, real& dgnumlo) const - { - if (id < 0 || id > physprop.size()) - printf("get_dgnumlo: illegal ID value %d\n", id); - dgnumlo = physprop[id].dgnumlo; - } - - void get_dgnumhi (int& id, real& dgnumhi) const - { - if (id < 0 || id > physprop.size()) - printf("get_dgnumhi: illegal ID value %d\n", id); - dgnumhi = physprop[id].dgnumhi; - } - - void get_rhcrystal (int& id, real& rhcrystal) const - { - if (id < 0 || id > physprop.size()) - printf("get_rhcrystal: illegal ID value %d\n", id); - rhcrystal = physprop[id].rhcrystal; - } - - void get_rhdeliques (int& id, real& rhdeliques) const - { - if (id < 0 || id > physprop.size()) - printf("get_rhdeliques: illegal ID value %d\n", id); - rhdeliques = physprop[id].rhdeliques; - } - - // Determine the opticstype, then call the - // appropriate routine to read the data. - void aerosol_optics_init (physprop_t& phys_prop) - { - using charHost1d = FArray; - yakl::SimpleNetCDF prop; - prop.open(phys_prop.sourcefile, yakl::NETCDF_MODE_READ); - charHost1d temp; - prop.read(temp, "opticsmethod"); - for (auto ichar = 1 ; ichar <= temp.extent(0); ichar++) - if (!isspace(temp(ichar))) phys_prop.opticsmethod += temp(ichar); - - if(strcmp(phys_prop.opticsmethod.c_str(),"zero") == 0) { - zero_optics_init(phys_prop, prop); - } else if (strcmp(phys_prop.opticsmethod.c_str(),"hygro") == 0) { - hygro_optics_init(phys_prop, prop); - } else if (strcmp(phys_prop.opticsmethod.c_str(),"hygroscopic") == 0) { - hygroscopic_optics_init(phys_prop, prop); - } else if (strcmp(phys_prop.opticsmethod.c_str(),"nonhygro") == 0) { - nonhygro_optics_init(phys_prop, prop); - } else if (strcmp(phys_prop.opticsmethod.c_str(),"insoluble") == 0) { - insoluble_optics_init(phys_prop, prop); - } else if (strcmp(phys_prop.opticsmethod.c_str(),"volcanic_radius") == 0) { - volcanic_radius_optics_init(phys_prop, prop); - } else if (strcmp(phys_prop.opticsmethod.c_str(),"volcanic") == 0) { - volcanic_optics_init(phys_prop, prop); - } else if (strcmp(phys_prop.opticsmethod.c_str(),"modal") == 0) { - modal_optics_init(phys_prop, prop); - } else { - amrex::Print() << "no options available\n"; - } - } - - void hygro_optics_init (physprop_t& phys_prop, yakl::SimpleNetCDF& prop) - { - real1d frh; - real2d fsw_ext; - real2d fsw_ssa; - real2d fsw_asm; - real2d flw_abs; - - auto nrh = RadConstants::nrh; - //auto nbnd = prop.getDimSize( "lw_band" ); - auto nswbands = prop.getDimSize( "sw_band" ); - - prop.read( fsw_ext, "ext_sw"); - prop.read( fsw_ssa, "ssa_sw"); - prop.read( fsw_asm, "asm_sw"); - prop.read( flw_abs, "abs_lw"); - prop.read( frh, "rh"); - - real1d fswe("fswe",nrh), - fsws("fsws",nrh), - fswa("fswa",nrh); - - // interpolate onto cam's rh mesh - for(auto kbnd = 0; kbnd < nswbands; ++kbnd) { - for(auto krh = 0; krh < nrh; ++krh) { - fswe(krh) = fsw_ext(krh,kbnd)/fsw_ext(1,kbnd); - fsws(krh) = fsw_ssa(krh,kbnd)/fsw_ssa(1,kbnd); - fswa(krh) = fsw_asm(krh,kbnd)/fsw_asm(1,kbnd); - } - - // interpolation - for(auto krh = 0; krh < nrh; ++krh) { - auto rh = 1.0/nrh*(krh-1); - phys_prop.sw_hygro_ext(krh,kbnd) = exp_interpol(frh,fswe,rh)*fsw_ext(1,kbnd); - phys_prop.sw_hygro_ssa(krh,kbnd) = lin_interpol(frh,fsws,rh)*fsw_ssa(1,kbnd); - phys_prop.sw_hygro_asm(krh,kbnd) = lin_interpol(frh,fswa,rh)*fsw_asm(1,kbnd); - } - } - - // read refractive index data if available - refindex_aer_init(phys_prop, prop); - - // read bulk aero props - bulk_props_init(phys_prop, prop); - } - - // Read optics data of type 'nonhygro' - void zero_optics_init (physprop_t& phys_prop, yakl::SimpleNetCDF& prop) - { - auto nlwbands = prop.getDimSize( "lw_band" ); - auto nswbands = prop.getDimSize( "sw_band" ); - - // perhaps this doesn't even need allocated. - phys_prop.sw_nonhygro_ext = real1d("sw_nonhygro_ext", nswbands); - phys_prop.sw_nonhygro_ssa = real1d("sw_nonhygro_ssa", nswbands); - phys_prop.sw_nonhygro_asm = real1d("sw_nonhygro_asm", nswbands); - phys_prop.lw_abs = real1d("lwabs", nlwbands); - - yakl::memset(phys_prop.sw_nonhygro_ext, 0.); - yakl::memset(phys_prop.sw_nonhygro_ssa, 0.); - yakl::memset(phys_prop.sw_nonhygro_asm, 0.); - yakl::memset(phys_prop.lw_abs, 0.); - } - - void insoluble_optics_init (physprop_t& phys_prop, yakl::SimpleNetCDF& prop) - { - auto nbnd = prop.getDimSize( "lw_band" ); - auto nswbands = prop.getDimSize( "sw_band" ); - realHost2d ext_sw, ssa_sw, asm_sw, abs_lw; - - phys_prop.sw_nonhygro_ext = real1d("sw_nonhygro_ext", nswbands); - phys_prop.sw_nonhygro_ssa = real1d("sw_nonhygro_ssa", nswbands); - phys_prop.sw_nonhygro_asm = real1d("sw_nonhygro_asm", nswbands); - phys_prop.lw_abs = real1d("lw_abs", nbnd); - - prop.read( ext_sw, "ext_sw"); - prop.read( ssa_sw, "ssa_sw"); - prop.read( asm_sw, "asm_sw"); - prop.read( abs_lw, "abs_lw"); - - parallel_for (SimpleBounds<1>(nswbands), YAKL_LAMBDA (int i) - { - phys_prop.sw_nonhygro_ext(i) = ext_sw(i,1); - phys_prop.sw_nonhygro_ssa(i) = ssa_sw(i,1); - phys_prop.sw_nonhygro_asm(i) = asm_sw(i,1); - }); - - parallel_for (SimpleBounds<1>(nbnd), YAKL_LAMBDA (int i) - { - phys_prop.lw_abs(i) = abs_lw(i,1); - }); - - // read refractive index data if available - refindex_aer_init(phys_prop, prop); - - // read bulk aero props - bulk_props_init(phys_prop, prop); - } - - // Read optics data of type 'volcanic_radius' - void volcanic_radius_optics_init (physprop_t& phys_prop, yakl::SimpleNetCDF& prop) - { - //auto n_mu_samples = prop.getDimSize( "mu_samples" ); - //auto nbnd = prop.getDimSize( "lw_band" ); - //auto nswbands = prop.getDimSize( "sw_band" ); - - prop.read( phys_prop.r_sw_ext, "bext_sw"); - prop.read( phys_prop.r_sw_scat, "bsca_sw"); - prop.read( phys_prop.r_sw_ascat, "basc_sw"); - prop.read( phys_prop.r_lw_abs, "babs_lw"); - prop.read( phys_prop.mu, "mu_samples"); - - // read bulk aero props - bulk_props_init(phys_prop, prop); - } - - // Read optics data of type 'volcanic' - void volcanic_optics_init (physprop_t& phys_prop, yakl::SimpleNetCDF& prop) - { - //auto nbnd = prop.getDimSize( "lw_band" ); - //auto nswbands = prop.getDimSize( "sw_band" ); - - prop.read( phys_prop.sw_nonhygro_ext, "bext_sw"); - prop.read( phys_prop.sw_nonhygro_scat, "bsca_sw"); - prop.read( phys_prop.sw_nonhygro_ascat, "basc_sw"); - prop.read( phys_prop.lw_abs, "babs_lw"); - - // read bulk aero props - bulk_props_init(phys_prop, prop); - } - - // Read optics data of type 'hygroscopic' and interpolate it to CAM's rh mesh. - void hygroscopic_optics_init (physprop_t& phys_prop, yakl::SimpleNetCDF& prop) - { - // temp data from hygroscopic file before interpolation onto cam-rh-mesh - //int nfilerh; // number of rh values in file - real1d frh; - real2d fsw_ext, fsw_ssa, fsw_asm, flw_abs; - - auto nrh = RadConstants::nrh; - auto nlwbands = prop.getDimSize( "lw_band" ); - auto nswbands = prop.getDimSize( "sw_band" ); - - phys_prop.sw_hygro_ext = real2d("sw_hygro_ext",nrh,nswbands); - phys_prop.sw_hygro_ssa = real2d("sw_hygro_ssa",nrh,nswbands); - phys_prop.sw_hygro_asm = real2d("sw_hygro_asm",nrh,nswbands); - phys_prop.lw_hygro_abs = real2d("lw_hygro_abs",nrh,nlwbands); - - prop.read( fsw_ext, "ext_sw"); - prop.read( fsw_ssa, "ssa_sw"); - prop.read( fsw_asm, "asm_sw"); - prop.read( flw_abs, "abs_lw"); - prop.read( frh, "rh"); - - real1d fswe("",nrh), fsws("",nrh), - fswa("",nrh), flwa("",nrh); - - // interpolate onto cam's rh mesh - parallel_for (SimpleBounds<2> (nswbands, nrh), YAKL_LAMBDA (int kbnd, int krh) - { - fswe(krh) = fsw_ext(krh,kbnd) / fsw_ext(1,kbnd); - fsws(krh) = fsw_ssa(krh,kbnd) / fsw_ssa(1,kbnd); - fswa(krh) = fsw_asm(krh,kbnd) / fsw_asm(1,kbnd); - }); - - parallel_for (SimpleBounds<2> (nswbands, nrh), YAKL_LAMBDA (int kbnd, int krh) - { - auto rh = 1.0 / nrh * (krh - 1); - phys_prop.sw_hygro_ext(krh,kbnd) = exp_interpol( frh, fswe, rh ) * fsw_ext(1, kbnd); - phys_prop.sw_hygro_ssa(krh,kbnd) = lin_interpol( frh, fswe, rh ) * fsw_ssa(1, kbnd); - phys_prop.sw_hygro_asm(krh,kbnd) = lin_interpol( frh, fswa, rh ) * fsw_asm(1, kbnd); - }); - - // interpolate long wave - parallel_for (SimpleBounds<2> (nlwbands, nrh), YAKL_LAMBDA (int kbnd, int krh) - { - flwa(krh) = flw_abs(krh,kbnd) / flw_abs(1,kbnd); - }); - - parallel_for (SimpleBounds<2> (nlwbands, nrh), YAKL_LAMBDA (int kbnd, int krh) - { - auto rh = 1.0 / nrh * (krh - 1); - phys_prop.lw_hygro_abs(krh,kbnd) = exp_interpol( frh, flwa, rh ) * flw_abs(1, kbnd); - }); - - // read refractive index data if available - refindex_aer_init(phys_prop, prop); - - // bulk aero props - bulk_props_init(phys_prop, prop); - } - - // Read optics data of type 'nonhygro' - void nonhygro_optics_init (physprop_t& phys_prop, yakl::SimpleNetCDF& prop) - { - //auto nlwbands = prop.getDimSize( "lw_band" ); - //auto nswbands = prop.getDimSize( "sw_band" ); - - prop.read( phys_prop.sw_nonhygro_ext, "ext_sw"); - prop.read( phys_prop.sw_nonhygro_ssa, "ssa_sw"); - prop.read( phys_prop.sw_nonhygro_asm, "asm_sw"); - prop.read( phys_prop.lw_abs, "abs_lw"); - - // read refractive index data if available - refindex_aer_init(phys_prop, prop); - - // read bulk aero props - bulk_props_init(phys_prop, prop); - } - - // Read refractive indices of aerosol - void refindex_aer_init (physprop_t& phys_prop, yakl::SimpleNetCDF& prop) - { - prop.read(phys_prop.refindex_real_aer_sw, "refindex_real_aer_sw"); - prop.read(phys_prop.refindex_im_aer_sw, "refindex_im_aer_sw"); - - prop.read(phys_prop.refindex_real_aer_lw, "refindex_real_aer_lw"); - prop.read(phys_prop.refindex_im_aer_lw, "refindex_im_aer_lw"); - } - - // Read optics data for modal aerosols - void modal_optics_init (physprop_t& phys_prop, yakl::SimpleNetCDF& prop) - { - // NOTE: Definitions for real arrays come from rrtmgp_const.h - // and they default to styleFortran ordering. - realHost5d extpsw, abspsw, asmpsw, absplw; - auto nlwbnd = prop.getDimSize( "lw_band" ); - auto nswbnd = prop.getDimSize( "sw_band" ); - auto ncoef = prop.getDimSize( "coef_number" ); - auto prefr = prop.getDimSize( "refindex_real" ); - auto prefi = prop.getDimSize( "refindex_im" ); - phys_prop.ncoef = ncoef; - phys_prop.prefr = prefr; - phys_prop.prefi = prefi; - - prop.read(extpsw, "extpsw" ); - prop.read(abspsw, "abspsw" ); - prop.read(asmpsw, "asmpsw" ); - prop.read(absplw, "absplw" ); - - // styleFortran ordering to be consistent with realHost5d definition - phys_prop.extpsw = real4d("extpsw", ncoef, prefr, prefi, nswbnd); - phys_prop.abspsw = real4d("abspsw", ncoef, prefr, prefi, nswbnd); - phys_prop.asmpsw = real4d("asmpsw", ncoef, prefr, prefi, nswbnd); - phys_prop.absplw = real4d("absplw", ncoef, prefr, prefi, nswbnd); - - parallel_for (SimpleBounds<4>(nswbnd, prefr, prefi, ncoef), - YAKL_LAMBDA (int i, int j, int k, int l) - { - phys_prop.extpsw(i,j,k,l) = extpsw(i,j,k,1,l); - phys_prop.abspsw(i,j,k,l) = abspsw(i,j,k,1,l); - phys_prop.asmpsw(i,j,k,l) = asmpsw(i,j,k,1,l); - }); - - parallel_for (SimpleBounds<4>(nlwbnd, prefr, prefi, ncoef), - YAKL_LAMBDA (int i, int j, int k, int l) - { - phys_prop.absplw(i,j,k,l) = absplw(i,j,k,1,l); - }); - - prop.read(phys_prop.refrtabsw, "refindex_real_sw" ); - prop.read(phys_prop.refitabsw, "refindex_im_sw" ); - prop.read(phys_prop.refrtablw, "refindex_real_lw" ); - prop.read(phys_prop.refitablw, "refindex_im_lw" ); - - prop.read(phys_prop.sigmag, "sigmag" ); - prop.read(phys_prop.dgnum, "dgnum" ); - - prop.read(phys_prop.dgnumlo, "dgnumlo" ); - prop.read(phys_prop.dgnumhi, "dgnumhi" ); - - prop.read(phys_prop.rhcrystal, "rhcrystal" ); - prop.read(phys_prop.rhdeliques, "rhdeliques" ); - } - - void bulk_props_init (physprop_t& phys_prop, yakl::SimpleNetCDF& prop) - { - using charHost1d = FArray; - charHost1d temp; - prop.read(temp, "name"); - phys_prop.aername = ""; - for (auto ichar = 1 ; ichar <= temp.extent(0); ichar++) - if (!isspace(temp(ichar))) phys_prop.aername += temp(ichar); - - // Read props for bulk aerosols - prop.read( phys_prop.density_aer, "density" ); - prop.read( phys_prop.dispersion_aer, "sigma_logr" ); - prop.read( phys_prop.dryrad_aer, "dryrad" ); - prop.read( phys_prop.hygro_aer, "hygroscopicity" ); - prop.read( phys_prop.num_to_mass_aer, "num_to_mass_ratio" ); - } - - // Purpose: - //interpolates f(x) to point y - //assuming f(x) = f(x0) exp a(x - x0) - //where a = ( ln f(x1) - ln f(x0) ) / (x1 - x0) - //x0 <= x <= x1 - //assumes x is monotonically increasing - real exp_interpol (const real1d& x, const real1d& f, const real& y) - { - auto n = x.extent(0); - int k; - - // find k such that x(k) < y =< x(k+1) - // set k = 1 if y <= x(1) and k = n-1 if y > x(n) - if (y <= x(1)) { - k = 1; - } - else if (y >= x(n)) { - k = n - 1; - } - else { - k = 1; - while (y > x(k+1) && k < n) { - k = k + 1; - } - } - - // interpolate - auto a = (log(f(k+1)/f(k)))/(x(k+1)-x(k)); - return f(k)*exp(a*(y-x(k))); - } - - //Purpose: - // interpolates f(x) to point y - // assuming f(x) = f(x0) + a * (x - x0) - // where a = ( f(x1) - f(x0) ) / (x1 - x0) - // x0 <= x <= x1 - // assumes x is monotonically increasing - // - real lin_interpol (const real1d& x, const real1d& f, const real& y) - { - auto n = x.extent(0); - int k; - // find k such that x(k) < y =< x(k+1) - // set k = 1 if y <= x(1) and k = n-1 if y > x(n) - if (y <= x(1)) { - k = 1; - } - else if (y >= x(n)) { - k = n - 1; - } - else { - k = 1; - while (y > x(k+1) && k < n) { - k = k + 1; - } - } - // interpolate - auto a = (f(k+1)-f(k))/(x(k+1)-x(k)); - return f(k)+a*(y-x(k)); - } - - // Purpose: - // write out aerosol optical properties - // for a set of test rh values - // to test hygroscopic growth interpolation - void aer_optics_log_rh (std::string name, const real1d& ext, const real1d& ssa, const real1d& asmin) - { - const int nrh_test = 36; - //int krh; - real1d rh_test("rh_test", nrh_test); - //auto nrh = ext.extent(0); - - parallel_for (SimpleBounds<1> (nrh_test), YAKL_LAMBDA (int krh_test) - { - rh_test(krh_test) = sqrt(sqrt(sqrt(sqrt(((krh_test - 1.0) / (nrh_test - 1)))))); - }); - - // loop through test rh values - parallel_for (SimpleBounds<1> (nrh_test), YAKL_LAMBDA (int krh_test) - { - /* - // find corresponding rh index - auto rh = rh_test(krh_test); - auto krh = std::min(floor( (rh) * nrh ) + 1, static_cast(nrh - 1)); - auto wrh = (rh) *nrh - krh; - auto exti = ext(krh + 1) * (wrh + 1) - ext(krh) * wrh; - auto ssai = ssa(krh + 1) * (wrh + 1) - ssa(krh) * wrh; - auto asmi = asmin(krh + 1) * (wrh + 1) - asmin(krh) * wrh; - */ - }); - } -}; - -#endif diff --git a/Source/Radiation/ERF_RRTMGP.H b/Source/Radiation/ERF_RRTMGP.H deleted file mode 100644 index 45bc233a5..000000000 --- a/Source/Radiation/ERF_RRTMGP.H +++ /dev/null @@ -1,125 +0,0 @@ -/* - * RTE-RRTMGP radiation model interface to ERF - * The orgiinal code is developed by RobertPincus, and the code is open source available at: - * https://github.com/earth-system-radiation/rte-rrtmgp - * For details of the radiation algorithm, please reference to the following paper, - * https://agupubs.onlinelibrary.wiley.com/doi/10.1029/2019MS001621 - * - * NOTE: we use the C++ version of RTE-RRTMGP, which is reimplemented the original Fortran - * code using C++ YAKL for CUDA, HiP and SYCL application by E3SM ECP team, the C++ version - * of the rte-rrtmgp code is located at: - * https://github.com/E3SM-Project/rte-rrtmgp - * - * The RTE-RRTMGP uses BSD-3-Clause Open Source License, if you want to make changes, - * and modifications to the code, please refer to BSD-3-Clause Open Source License. - */ -#ifndef ERF_RRTMGP_H -#define ERF_RRTMGP_H - -#include -#include -#include - -#include - -// rrtmgp includes -#include -#include -#include -#include -#include -#include -#include -#include - -using yakl::fortran::parallel_for; -using yakl::fortran::SimpleBounds; - -// Radiation code interface class -class Rrtmgp { - public: - // constructor - Rrtmgp () = default; - - // deconstructor - ~Rrtmgp () = default; - - // initialize and load gas property data for rrtmgp radiation - void initialize (int num_gas, const std::vector& active_gas_names, - const char* rrtmgp_coefficients_file_sw, - const char* rrtmgp_coefficients_file_lw); - - // finalize/clean up - void finalize (); - - // run rrtmgp short wave model - void run_shortwave_rrtmgp (int ngas, int ncol, int nlay, - const real3d& gas_vmr, const real2d& pmid, const real2d& tmid, const real2d& pint, - const real1d& coszrs , const real2d& albedo_dir, const real2d& albedo_dif, - const real3d& cld_tau_gpt, const real3d& cld_ssa_gpt, const real3d& cld_asm_gpt, - const real3d& aer_tau_bnd, const real3d& aer_ssa_bnd, const real3d& aer_asm_bnd, - const real2d& allsky_flux_up , const real2d& allsky_flux_dn , const real2d& allsky_flux_net , const real2d& allsky_flux_dn_dir, - const real3d& allsky_bnd_flux_up, const real3d& allsky_bnd_flux_dn, const real3d& allsky_bnd_flux_net, const real3d& allsky_bnd_flux_dn_dir, - const real2d& clrsky_flux_up , const real2d& clrsky_flux_dn , const real2d& clrsky_flux_net , const real2d& clrsky_flux_dn_dir, - const real3d& clrsky_bnd_flux_up, const real3d& clrsky_bnd_flux_dn, const real3d& clrsky_bnd_flux_net, const real3d& clrsky_bnd_flux_dn_dir, - double tsi_scaling); - - // run rrtmgp long wave model - void run_longwave_rrtmgp (int ngas, int ncol, int nlay, const real3d& gas_vmr, - const real2d& pmid, const real2d& tmid, const real2d& pint , const real2d& tint, - const real2d& emis_sfc, - const real3d& cld_tau_gpt , const real3d& aer_tau_bnd , - const real2d& allsky_flux_up , const real2d& allsky_flux_dn , const real2d& allsky_flux_net , - const real3d& allsky_bnd_flux_up, const real3d& allsky_bnd_flux_dn, const real3d& allsky_bnd_flux_net, - const real2d& clrsky_flux_up , const real2d& clrsky_flux_dn , const real2d& clrsky_flux_net , - const real3d& clrsky_bnd_flux_up, const real3d& clrsky_bnd_flux_dn, const real3d& clrsky_bnd_flux_net); - - int get_nband_sw () { - return k_dist_sw.get_nband(); - } - - int get_nband_lw () { - return k_dist_lw.get_nband(); - } - - int get_ngpt_sw () { - return k_dist_sw.get_ngpt(); - } - - int get_ngpt_lw () { - return k_dist_lw.get_ngpt(); - } - - double get_min_temperature () { - return std::min(k_dist_sw.temp_ref_min, k_dist_lw.temp_ref_min); - } - - double get_max_temperature () { - return std::max(k_dist_sw.temp_ref_max, k_dist_lw.temp_ref_max); - } - - void get_gpoint_bands_sw (int1d& gpoint_bands) { - gpoint_bands = k_dist_sw.get_gpoint_bands(); - yakl::fence(); - } - - void get_gpoint_bands_lw (int1d& gpoint_bands) { - gpoint_bands = k_dist_lw.get_gpoint_bands(); - yakl::fence(); - } - - private: - // number of gas for radiation model - int ngas; - string1d active_gases; - - // coefficient files - char const *coefficients_file_sw; // short wave gas optics coefficient files - char const *coefficients_file_lw; // long wave gas optics coefficient files - - // Objects for gas optics data - GasOpticsRRTMGP k_dist_sw; - GasOpticsRRTMGP k_dist_lw; -}; - -#endif // ERF_RRTMGP_H diff --git a/Source/Radiation/ERF_RRTMGP_Interface.H b/Source/Radiation/ERF_RRTMGP_Interface.H new file mode 100644 index 000000000..f2e991a67 --- /dev/null +++ b/Source/Radiation/ERF_RRTMGP_Interface.H @@ -0,0 +1,145 @@ +#ifndef ERF_RRTMGP_INTERFACE_H +#define ERF_RRTMGP_INTERFACE_H + +#include "mo_gas_optics_rrtmgp.h" +#include "mo_cloud_optics.h" +#include "mo_fluxes_byband.h" +#include "mo_load_coefficients.h" +#include "rrtmgp_const.h" +#include "mo_gas_concentrations.h" +#include "mo_gas_optics_rrtmgp.h" +#include "mo_cloud_optics.h" +#include "mo_rte_sw.h" +#include "mo_rte_lw.h" +#include "mo_load_cloud_coefficients.h" + +#include "ERF_RRTMGP_Utils.H" + +#include "ERF_Constants.H" + +void init_kls (); +void finalize_kls (); + +namespace rrtmgp { + +extern GasOpticsRRTMGP k_dist_sw; +extern GasOpticsRRTMGP k_dist_lw; + +extern CloudOptics cloud_optics_sw; +extern CloudOptics cloud_optics_lw; + +extern bool initialized; + +void +rrtmgp_initialize (GasConcs &gas_concs, + const std::string& coefficients_file_sw, const std::string& coefficients_file_lw, + const std::string& cloud_optics_file_sw, const std::string& cloud_optics_file_lw); + + +void +rrtmgp_finalize (); + + +void +compute_band_by_band_surface_albedos (const int ncol, const int nswbands, + real1d &sfc_alb_dir_vis, real1d &sfc_alb_dir_nir, + real1d &sfc_alb_dif_vis, real1d &sfc_alb_dif_nir, + real2d &sfc_alb_dir, real2d &sfc_alb_dif); + + +void +compute_broadband_surface_fluxes (const int ncol, const int ktop, const int nswbands, + real3d &sw_bnd_flux_dir , real3d &sw_bnd_flux_dif , + real1d &sfc_flux_dir_vis, real1d &sfc_flux_dir_nir, + real1d &sfc_flux_dif_vis, real1d &sfc_flux_dif_nir); + + +void +rrtmgp_main (const int ncol, const int nlay, + real2d &p_lay, real2d &t_lay, real2d &p_lev, real2d &t_lev, + GasConcs &gas_concs, + real2d &sfc_alb_dir, real2d &sfc_alb_dif, real1d &mu0, + real2d &lwp, real2d &iwp, real2d &rel, real2d &rei, real2d &cldfrac, + real3d &aer_tau_sw, real3d &aer_ssa_sw, real3d &aer_asm_sw, real3d &aer_tau_lw, + real3d &cld_tau_sw_bnd, real3d &cld_tau_lw_bnd, + real3d &cld_tau_sw_gpt, real3d &cld_tau_lw_gpt, + real2d &sw_flux_up, real2d &sw_flux_dn, real2d &sw_flux_dn_dir, + real2d &lw_flux_up, real2d &lw_flux_dn, + real2d &sw_clnclrsky_flux_up, real2d &sw_clnclrsky_flux_dn, real2d &sw_clnclrsky_flux_dn_dir, + real2d &sw_clrsky_flux_up, real2d &sw_clrsky_flux_dn, real2d &sw_clrsky_flux_dn_dir, + real2d &sw_clnsky_flux_up, real2d &sw_clnsky_flux_dn, real2d &sw_clnsky_flux_dn_dir, + real2d &lw_clnclrsky_flux_up, real2d &lw_clnclrsky_flux_dn, + real2d &lw_clrsky_flux_up, real2d &lw_clrsky_flux_dn, + real2d &lw_clnsky_flux_up, real2d &lw_clnsky_flux_dn, + real3d &sw_bnd_flux_up, real3d &sw_bnd_flux_dn, real3d &sw_bnd_flux_dn_dir, + real3d &lw_bnd_flux_up, real3d &lw_bnd_flux_dn, + const Real tsi_scaling, + const bool extra_clnclrsky_diag = false, const bool extra_clnsky_diag = false); + + +void +rrtmgp_sw (const int ncol, const int nlay, + GasOpticsRRTMGP &k_dist, + real2d &p_lay, real2d &t_lay, real2d &p_lev, real2d &t_lev, + GasConcs &gas_concs, + real2d &sfc_alb_dir, real2d &sfc_alb_dif, real1d &mu0, + OpticalProps2str &aerosol, OpticalProps2str &clouds, + FluxesByband &fluxes, + FluxesBroadband &clnclrsky_fluxes, + FluxesBroadband &clrsky_fluxes, + FluxesBroadband &clnsky_fluxes, + const Real tsi_scaling, + const bool extra_clnclrsky_diag, + const bool extra_clnsky_diag); + + +void +rrtmgp_lw (const int ncol, const int nlay, + GasOpticsRRTMGP &k_dist, + real2d &p_lay, real2d &t_lay, real2d &p_lev, real2d &t_lev, + GasConcs &gas_concs, + OpticalProps1scl &aerosol, OpticalProps1scl &clouds, + FluxesByband &fluxes, + FluxesBroadband &clnclrsky_fluxes, + FluxesBroadband &clrsky_fluxes, + FluxesBroadband &clnsky_fluxes, + const bool extra_clnclrsky_diag, + const bool extra_clnsky_diag); + + +int3d +get_subcolumn_mask (const int ncol, + const int nlay, + const int ngpt, + real2d &cldf, + const int overlap_option, + int1d &seeds); + + +void +compute_cloud_area (int ncol, + int nlay, + int ngpt, + Real pmin, + Real pmax, + const real2d& pmid, + const real3d& cld_tau_gpt, + real1d& cld_area); + + +void +compute_aerocom_cloudtop (int ncol, int nlay, const real2d &tmid, const real2d &pmid, + const real2d &p_del, const real2d &z_del, const real2d &qc, + const real2d &qi, const real2d &rel, const real2d &rei, + const real2d &cldfrac_tot, const real2d &nc, + real1d &T_mid_at_cldtop, real1d &p_mid_at_cldtop, + real1d &cldfrac_ice_at_cldtop, real1d &cldfrac_liq_at_cldtop, + real1d &cldfrac_tot_at_cldtop, real1d &cdnc_at_cldtop, + real1d &eff_radius_qc_at_cldtop, real1d &eff_radius_qi_at_cldtop); + +int get_wavelength_index (OpticalProps &kdist, double wavelength); +int get_wavelength_index_sw (double wavelength); +int get_wavelength_index_lw (double wavelength); + +} // namespace rrtmgp +#endif diff --git a/Source/Radiation/ERF_RRTMGP_Interface.cpp b/Source/Radiation/ERF_RRTMGP_Interface.cpp new file mode 100644 index 000000000..becb0f97c --- /dev/null +++ b/Source/Radiation/ERF_RRTMGP_Interface.cpp @@ -0,0 +1,1171 @@ +#include "ERF_RRTMGP_Interface.H" + +void init_kls () +{ + // Initialize yakl + if(!yakl::isInitialized()) { yakl::init(); } +} + +void finalize_kls() +{ + // Finalize YAKL + yakl::finalize(); +} + +namespace rrtmgp { + +using yakl::fortran::parallel_for; +using yakl::fortran::SimpleBounds; +using yakl::intrinsics::merge; + +/* + * Objects containing k-distribution information need to be initialized + * once and then persist throughout the life of the program, so we + * declare them here within the rrtmgp namespace. + */ +GasOpticsRRTMGP k_dist_sw; +GasOpticsRRTMGP k_dist_lw; + +/* + * Objects containing cloud optical property look-up table information. + * We want to initialize these once and use throughout the life of the + * program, so declare here and read data in during rrtmgp_initialize(). + */ +CloudOptics cloud_optics_sw; +CloudOptics cloud_optics_lw; + +bool initialized = false; +bool initialized_k = false; + + +OpticalProps2str +get_cloud_optics_sw (const int ncol, + const int nlay, + CloudOptics &cloud_optics, + GasOpticsRRTMGP &kdist, + real2d &lwp, + real2d &iwp, + real2d &rel, + real2d &rei) +{ + // Initialize optics + OpticalProps2str clouds; + clouds.init(kdist.get_band_lims_wavenumber()); + clouds.alloc_2str(ncol, nlay); + + // Needed for consistency with all-sky example problem? + cloud_optics.set_ice_roughness(2); + + // Limit effective radii to be within bounds of lookup table + auto rel_limited = real2d("rel_limited", ncol, nlay); + auto rei_limited = real2d("rei_limited", ncol, nlay); + limit_to_bounds(rel, cloud_optics.radliq_lwr, cloud_optics.radliq_upr, rel_limited); + limit_to_bounds(rei, cloud_optics.radice_lwr, cloud_optics.radice_upr, rei_limited); + + // Calculate cloud optics + cloud_optics.cloud_optics(ncol, nlay, lwp, iwp, rel_limited, rei_limited, clouds); + + // Return optics + return clouds; +} + + +OpticalProps1scl +get_cloud_optics_lw (const int ncol, + const int nlay, + CloudOptics &cloud_optics, + GasOpticsRRTMGP &kdist, + real2d &lwp, + real2d &iwp, + real2d &rel, + real2d &rei) +{ + // Initialize optics + OpticalProps1scl clouds; + clouds.init(kdist.get_band_lims_wavenumber()); + clouds.alloc_1scl(ncol, nlay); // this is dumb, why do we need to init and alloc separately?! + + // Needed for consistency with all-sky example problem? + cloud_optics.set_ice_roughness(2); + + // Limit effective radii to be within bounds of lookup table + auto rel_limited = real2d("rel_limited", ncol, nlay); + auto rei_limited = real2d("rei_limited", ncol, nlay); + limit_to_bounds(rel, cloud_optics.radliq_lwr, cloud_optics.radliq_upr, rel_limited); + limit_to_bounds(rei, cloud_optics.radice_lwr, cloud_optics.radice_upr, rei_limited); + + // Calculate cloud optics + cloud_optics.cloud_optics(ncol, nlay, lwp, iwp, rel_limited, rei_limited, clouds); + + // Return optics + return clouds; +} + + +OpticalProps2str +get_subsampled_clouds (const int ncol, + const int nlay, + const int nbnd, + const int ngpt, + OpticalProps2str &cloud_optics, + GasOpticsRRTMGP &kdist, + real2d &cld, + real2d &p_lay) +{ + // Initialized subsampled optics + OpticalProps2str subsampled_optics; + subsampled_optics.init(kdist.get_band_lims_wavenumber(), kdist.get_band_lims_gpoint(), "subsampled_optics"); + subsampled_optics.alloc_2str(ncol, nlay); + + // Check that we do not have clouds with no optical properties; this would get corrected + // when we assign optical props, but we want to use a "radiative cloud fraction" + // for the subcolumn sampling too because otherwise we can get vertically-contiguous cloud + // mask profiles with no actual cloud properties in between, which would just further overestimate + // the vertical correlation of cloudy layers. I.e., cloudy layers might look maximally overlapped + // even when separated by layers with no cloud properties, when in fact those layers should be + // randomly overlapped. + auto cldfrac_rad = real2d("cldfrac_rad", ncol, nlay); + memset(cldfrac_rad, 0.0); // Start with all zeros + parallel_for(SimpleBounds<3>(nbnd,nlay,ncol), YAKL_LAMBDA (int ibnd, int ilay, int icol) + { + if (cloud_optics.tau(icol,ilay,ibnd) > 0) { + cldfrac_rad(icol,ilay) = cld(icol,ilay); + } + }); + + // Get subcolumn cloud mask; note that get_subcolumn_mask exposes overlap assumption as an option, + // but the only currently supported options are 0 (trivial all-or-nothing cloud) or 1 (max-rand), + // so overlap has not been exposed as an option beyond this subcolumn. In the future, we should + // support generalized overlap as well, with parameters derived from DPSCREAM simulations with very + // high resolution. + int overlap = 1; + + // Get unique seeds for each column that are reproducible across different MPI rank layouts; + // use decimal part of pressure for this, consistent with the implementation in EAM + auto seeds = int1d("seeds", ncol); + parallel_for(SimpleBounds<1>(ncol), YAKL_LAMBDA(int icol) + { + seeds(icol) = 1.0e9 * (p_lay(icol,nlay) - int(p_lay(icol,nlay))); + }); + auto cldmask = get_subcolumn_mask(ncol, nlay, ngpt, cldfrac_rad, overlap, seeds); + + // Assign optical properties to subcolumns (note this implements MCICA) + auto gpoint_bands = kdist.get_gpoint_bands(); + parallel_for(SimpleBounds<3>(ngpt,nlay,ncol), YAKL_LAMBDA(int igpt, int ilay, int icol) + { + auto ibnd = gpoint_bands(igpt); + if (cldmask(icol,ilay,igpt) == 1) { + subsampled_optics.tau(icol,ilay,igpt) = cloud_optics.tau(icol,ilay,ibnd); + subsampled_optics.ssa(icol,ilay,igpt) = cloud_optics.ssa(icol,ilay,ibnd); + subsampled_optics.g (icol,ilay,igpt) = cloud_optics.g (icol,ilay,ibnd); + } else { + subsampled_optics.tau(icol,ilay,igpt) = 0; + subsampled_optics.ssa(icol,ilay,igpt) = 0; + subsampled_optics.g (icol,ilay,igpt) = 0; + } + }); + return subsampled_optics; +} + + +OpticalProps1scl +get_subsampled_clouds (const int ncol, + const int nlay, + const int nbnd, + const int ngpt, + OpticalProps1scl &cloud_optics, + GasOpticsRRTMGP &kdist, + real2d &cld, + real2d &p_lay) +{ + // Initialized subsampled optics + OpticalProps1scl subsampled_optics; + subsampled_optics.init(kdist.get_band_lims_wavenumber(), kdist.get_band_lims_gpoint(), "subsampled_optics"); + subsampled_optics.alloc_1scl(ncol, nlay); + + // Check that we do not have clouds with no optical properties; this would get corrected + // when we assign optical props, but we want to use a "radiative cloud fraction" + // for the subcolumn sampling too because otherwise we can get vertically-contiguous cloud + // mask profiles with no actual cloud properties in between, which would just further overestimate + // the vertical correlation of cloudy layers. I.e., cloudy layers might look maximally overlapped + // even when separated by layers with no cloud properties, when in fact those layers should be + // randomly overlapped. + auto cldfrac_rad = real2d("cldfrac_rad", ncol, nlay); + memset(cldfrac_rad, 0.0); // Start with all zeros + parallel_for(SimpleBounds<3>(nbnd,nlay,ncol), YAKL_LAMBDA (int ibnd, int ilay, int icol) + { + if (cloud_optics.tau(icol,ilay,ibnd) > 0) { + cldfrac_rad(icol,ilay) = cld(icol,ilay); + } + }); + + // Get subcolumn cloud mask + int overlap = 1; + // Get unique seeds for each column that are reproducible across different MPI rank layouts; + // use decimal part of pressure for this, consistent with the implementation in EAM; use different + // seed values for longwave and shortwave + auto seeds = int1d("seeds", ncol); + parallel_for(SimpleBounds<1>(ncol), YAKL_LAMBDA(int icol) + { + seeds(icol) = 1e9 * (p_lay(icol,nlay-1) - int(p_lay(icol,nlay-1))); + }); + auto cldmask = get_subcolumn_mask(ncol, nlay, ngpt, cldfrac_rad, overlap, seeds); + // Assign optical properties to subcolumns (note this implements MCICA) + auto gpoint_bands = kdist.get_gpoint_bands(); + parallel_for(SimpleBounds<3>(ngpt,nlay,ncol), YAKL_LAMBDA(int igpt, int ilay, int icol) + { + auto ibnd = gpoint_bands(igpt); + if (cldmask(icol,ilay,igpt) == 1) { + subsampled_optics.tau(icol,ilay,igpt) = cloud_optics.tau(icol,ilay,ibnd); + } else { + subsampled_optics.tau(icol,ilay,igpt) = 0; + } + }); + return subsampled_optics; +} + + +/* + * The following routines provide a simple interface to RRTMGP. These + * can be used as-is, but are intended to be wrapped by the SCREAM AD + * interface to radiation. + */ +void +rrtmgp_initialize (GasConcs& gas_concs, + const std::string& coefficients_file_sw, + const std::string& coefficients_file_lw, + const std::string& cloud_optics_file_sw, + const std::string& cloud_optics_file_lw) +{ + // Initialize YAKL + if (!yakl::isInitialized()) { yakl::init(); } + + // Load and initialize absorption coefficient data + load_and_init(k_dist_sw, coefficients_file_sw, gas_concs); + load_and_init(k_dist_lw, coefficients_file_lw, gas_concs); + + // Load and initialize cloud optical property look-up table information + load_cld_lutcoeff(cloud_optics_sw, cloud_optics_file_sw); + load_cld_lutcoeff(cloud_optics_lw, cloud_optics_file_lw); + + // We are now initialized! + initialized = true; +} + + +void +rrtmgp_finalize () +{ + initialized = false; + k_dist_sw.finalize(); + k_dist_lw.finalize(); + cloud_optics_sw.finalize(); //~CloudOptics(); + cloud_optics_lw.finalize(); //~CloudOptics(); +} + + +void +compute_band_by_band_surface_albedos (const int ncol, + const int nswbands, + real1d& sfc_alb_dir_vis, + real1d& sfc_alb_dir_nir, + real1d& sfc_alb_dif_vis, + real1d& sfc_alb_dif_nir, + real2d& sfc_alb_dir, + real2d& sfc_alb_dif) +{ + AMREX_ASSERT_WITH_MESSAGE(initialized, "ERROR: rrtmgp_initialize must be called before GasOpticsRRTMGP object can be used."); + + auto wavenumber_limits = k_dist_sw.get_band_lims_wavenumber(); + + AMREX_ASSERT_WITH_MESSAGE(yakl::intrinsics::size(wavenumber_limits, 1) == 2, + "Error! 1st dimension for wavenumber_limits should be 2."); + AMREX_ASSERT_WITH_MESSAGE(yakl::intrinsics::size(wavenumber_limits, 2) == nswbands, + "Error! 2nd dimension for wavenumber_limits should be " + std::to_string(nswbands) + " (nswbands)."); + + // Loop over bands, and determine for each band whether it is broadly in the + // visible or infrared part of the spectrum (visible or "not visible") + parallel_for(SimpleBounds<2>(nswbands, ncol), YAKL_LAMBDA(const int ibnd, const int icol) + { + // Threshold between visible and infrared is 0.7 micron, or 14286 cm^-1. + const real visible_wavenumber_threshold = 14286; + + // Wavenumber is in the visible if it is above the visible wavenumber + // threshold, and in the infrared if it is below the threshold + const bool is_visible_wave1 = (wavenumber_limits(1, ibnd) > visible_wavenumber_threshold ? true : false); + const bool is_visible_wave2 = (wavenumber_limits(2, ibnd) > visible_wavenumber_threshold ? true : false); + + if (is_visible_wave1 && is_visible_wave2) { + + // Entire band is in the visible + sfc_alb_dir(icol,ibnd) = sfc_alb_dir_vis(icol); + sfc_alb_dif(icol,ibnd) = sfc_alb_dif_vis(icol); + + } else if (!is_visible_wave1 && !is_visible_wave2) { + + // Entire band is in the longwave (near-infrared) + sfc_alb_dir(icol,ibnd) = sfc_alb_dir_nir(icol); + sfc_alb_dif(icol,ibnd) = sfc_alb_dif_nir(icol); + + } else { + + // Band straddles the visible to near-infrared transition, so we take + // the albedo to be the average of the visible and near-infrared + // broadband albedos + sfc_alb_dir(icol,ibnd) = 0.5*(sfc_alb_dir_vis(icol) + sfc_alb_dir_nir(icol)); + sfc_alb_dif(icol,ibnd) = 0.5*(sfc_alb_dif_vis(icol) + sfc_alb_dif_nir(icol)); + + } + }); +} + + +void +compute_broadband_surface_fluxes (const int ncol, + const int ktop, + const int nswbands, + real3d& sw_bnd_flux_dir , + real3d& sw_bnd_flux_dif , + real1d& sfc_flux_dir_vis, + real1d& sfc_flux_dir_nir, + real1d& sfc_flux_dif_vis, + real1d& sfc_flux_dif_nir) +{ + // Band 10 straddles the near-IR and visible, so divide contributions from band 10 between both broadband sums + // TODO: Hard-coding these band indices is really bad practice. If the bands ever were to change (like when + // the RRTMG bands were re-ordered for RRTMGP), we would be using the wrong bands for the IR and UV/VIS. This + // should be refactored to grab the correct bands by specifying appropriate wavenumber rather than index. + //sfc_flux_dir_nir(i) = sum(sw_bnd_flux_dir(i+1,kbot,1:9)) + 0.5 * sw_bnd_flux_dir(i+1,kbot,10); + //sfc_flux_dir_vis(i) = sum(sw_bnd_flux_dir(i+1,kbot,11:14)) + 0.5 * sw_bnd_flux_dir(i+1,kbot,10); + //sfc_flux_dif_nir(i) = sum(sw_bnd_flux_dif(i+1,kbot,1:9)) + 0.5 * sw_bnd_flux_dif(i+1,kbot,10); + //sfc_flux_dif_vis(i) = sum(sw_bnd_flux_dif(i+1,kbot,11:14)) + 0.5 * sw_bnd_flux_dif(i+1,kbot,10); + + // Initialize sums over bands + memset(sfc_flux_dir_nir, 0); + memset(sfc_flux_dir_vis, 0); + memset(sfc_flux_dif_nir, 0); + memset(sfc_flux_dif_vis, 0); + + // Threshold between visible and infrared is 0.7 micron, or 14286 cm^-1. + const real visible_wavenumber_threshold = 14286; + auto wavenumber_limits = k_dist_sw.get_band_lims_wavenumber(); + parallel_for(SimpleBounds<1>(ncol), YAKL_LAMBDA(const int icol) + { + for (int ibnd = 1; ibnd <= nswbands; ++ibnd) { + // Wavenumber is in the visible if it is above the visible wavenumber + // threshold, and in the infrared if it is below the threshold + const bool is_visible_wave1 = (wavenumber_limits(1, ibnd) > visible_wavenumber_threshold ? true : false); + const bool is_visible_wave2 = (wavenumber_limits(2, ibnd) > visible_wavenumber_threshold ? true : false); + + if (is_visible_wave1 && is_visible_wave2) { + + // Entire band is in the visible + sfc_flux_dir_vis(icol) += sw_bnd_flux_dir(icol,ktop,ibnd); + sfc_flux_dif_vis(icol) += sw_bnd_flux_dif(icol,ktop,ibnd); + + } else if (!is_visible_wave1 && !is_visible_wave2) { + + // Entire band is in the longwave (near-infrared) + sfc_flux_dir_nir(icol) += sw_bnd_flux_dir(icol,ktop,ibnd); + sfc_flux_dif_nir(icol) += sw_bnd_flux_dif(icol,ktop,ibnd); + + } else { + + // Band straddles the visible to near-infrared transition, so put half + // the flux in visible and half in near-infrared fluxes + sfc_flux_dir_vis(icol) += 0.5 * sw_bnd_flux_dir(icol,ktop,ibnd); + sfc_flux_dif_vis(icol) += 0.5 * sw_bnd_flux_dif(icol,ktop,ibnd); + sfc_flux_dir_nir(icol) += 0.5 * sw_bnd_flux_dir(icol,ktop,ibnd); + sfc_flux_dif_nir(icol) += 0.5 * sw_bnd_flux_dif(icol,ktop,ibnd); + } + } + }); +} + + +void +rrtmgp_main (const int ncol, const int nlay, + real2d &p_lay, real2d &t_lay, real2d &p_lev, real2d &t_lev, + GasConcs &gas_concs, + real2d &sfc_alb_dir, real2d &sfc_alb_dif, real1d &mu0, + real2d &lwp, real2d &iwp, real2d &rel, real2d &rei, real2d &cldfrac, + real3d &aer_tau_sw, real3d &aer_ssa_sw, real3d &aer_asm_sw, real3d &aer_tau_lw, + real3d &cld_tau_sw_bnd, real3d &cld_tau_lw_bnd, + real3d &cld_tau_sw_gpt, + real3d &cld_tau_lw_gpt, + real2d &sw_flux_up, real2d &sw_flux_dn, real2d &sw_flux_dn_dir, + real2d &lw_flux_up, real2d &lw_flux_dn, + real2d &sw_clnclrsky_flux_up, real2d &sw_clnclrsky_flux_dn, real2d &sw_clnclrsky_flux_dn_dir, + real2d &sw_clrsky_flux_up, real2d &sw_clrsky_flux_dn, real2d &sw_clrsky_flux_dn_dir, + real2d &sw_clnsky_flux_up, real2d &sw_clnsky_flux_dn, real2d &sw_clnsky_flux_dn_dir, + real2d &lw_clnclrsky_flux_up, real2d &lw_clnclrsky_flux_dn, + real2d &lw_clrsky_flux_up, real2d &lw_clrsky_flux_dn, + real2d &lw_clnsky_flux_up, real2d &lw_clnsky_flux_dn, + real3d &sw_bnd_flux_up, real3d &sw_bnd_flux_dn, real3d &sw_bnd_flux_dn_dir, + real3d &lw_bnd_flux_up, real3d &lw_bnd_flux_dn, + const Real tsi_scaling, + const bool extra_clnclrsky_diag, const bool extra_clnsky_diag) +{ + // Setup pointers to RRTMGP SW fluxes + FluxesByband fluxes_sw; + fluxes_sw.flux_up = sw_flux_up; + fluxes_sw.flux_dn = sw_flux_dn; + fluxes_sw.flux_dn_dir = sw_flux_dn_dir; + fluxes_sw.bnd_flux_up = sw_bnd_flux_up; + fluxes_sw.bnd_flux_dn = sw_bnd_flux_dn; + fluxes_sw.bnd_flux_dn_dir = sw_bnd_flux_dn_dir; + // Clean-clear-sky + FluxesBroadband clnclrsky_fluxes_sw; + clnclrsky_fluxes_sw.flux_up = sw_clnclrsky_flux_up; + clnclrsky_fluxes_sw.flux_dn = sw_clnclrsky_flux_dn; + clnclrsky_fluxes_sw.flux_dn_dir = sw_clnclrsky_flux_dn_dir; + // Clear-sky + FluxesBroadband clrsky_fluxes_sw; + clrsky_fluxes_sw.flux_up = sw_clrsky_flux_up; + clrsky_fluxes_sw.flux_dn = sw_clrsky_flux_dn; + clrsky_fluxes_sw.flux_dn_dir = sw_clrsky_flux_dn_dir; + // Clean-sky + FluxesBroadband clnsky_fluxes_sw; + clnsky_fluxes_sw.flux_up = sw_clnsky_flux_up; + clnsky_fluxes_sw.flux_dn = sw_clnsky_flux_dn; + clnsky_fluxes_sw.flux_dn_dir = sw_clnsky_flux_dn_dir; + + // Setup pointers to RRTMGP LW fluxes + FluxesByband fluxes_lw; + fluxes_lw.flux_up = lw_flux_up; + fluxes_lw.flux_dn = lw_flux_dn; + fluxes_lw.bnd_flux_up = lw_bnd_flux_up; + fluxes_lw.bnd_flux_dn = lw_bnd_flux_dn; + // Clean-clear-sky + FluxesBroadband clnclrsky_fluxes_lw; + clnclrsky_fluxes_lw.flux_up = lw_clnclrsky_flux_up; + clnclrsky_fluxes_lw.flux_dn = lw_clnclrsky_flux_dn; + // Clear-sky + FluxesBroadband clrsky_fluxes_lw; + clrsky_fluxes_lw.flux_up = lw_clrsky_flux_up; + clrsky_fluxes_lw.flux_dn = lw_clrsky_flux_dn; + // Clean-sky + FluxesBroadband clnsky_fluxes_lw; + clnsky_fluxes_lw.flux_up = lw_clnsky_flux_up; + clnsky_fluxes_lw.flux_dn = lw_clnsky_flux_dn; + + auto nswbands = k_dist_sw.get_nband(); + auto nlwbands = k_dist_lw.get_nband(); + + // Setup aerosol optical properties + OpticalProps2str aerosol_sw; + OpticalProps1scl aerosol_lw; + aerosol_sw.init(k_dist_sw.get_band_lims_wavenumber()); + aerosol_sw.alloc_2str(ncol, nlay); + parallel_for(SimpleBounds<3>(nswbands,nlay,ncol) , YAKL_LAMBDA (int ibnd, int ilay, int icol) + { + aerosol_sw.tau(icol,ilay,ibnd) = aer_tau_sw(icol,ilay,ibnd); + aerosol_sw.ssa(icol,ilay,ibnd) = aer_ssa_sw(icol,ilay,ibnd); + aerosol_sw.g (icol,ilay,ibnd) = aer_asm_sw(icol,ilay,ibnd); + }); + aerosol_lw.init(k_dist_lw.get_band_lims_wavenumber()); + aerosol_lw.alloc_1scl(ncol, nlay); + parallel_for(SimpleBounds<3>(nlwbands,nlay,ncol) , YAKL_LAMBDA (int ibnd, int ilay, int icol) + { + aerosol_lw.tau(icol,ilay,ibnd) = aer_tau_lw(icol,ilay,ibnd); + }); + + // Convert cloud physical properties to optical properties for input to RRTMGP + OpticalProps2str clouds_sw = get_cloud_optics_sw(ncol, nlay, cloud_optics_sw, k_dist_sw, lwp, iwp, rel, rei); + OpticalProps1scl clouds_lw = get_cloud_optics_lw(ncol, nlay, cloud_optics_lw, k_dist_lw, lwp, iwp, rel, rei); + clouds_sw.tau.deep_copy_to(cld_tau_sw_bnd); + clouds_lw.tau.deep_copy_to(cld_tau_lw_bnd); + + // Do subcolumn sampling to map bands -> gpoints based on cloud fraction and overlap assumption; + // This implements the Monte Carlo Independing Column Approximation by mapping only a single + // subcolumn (cloud state) to each gpoint. + auto nswgpts = k_dist_sw.get_ngpt(); + auto clouds_sw_gpt = get_subsampled_clouds(ncol, nlay, nswbands, nswgpts, clouds_sw, k_dist_sw, cldfrac, p_lay); + // Longwave + auto nlwgpts = k_dist_lw.get_ngpt(); + auto clouds_lw_gpt = get_subsampled_clouds(ncol, nlay, nlwbands, nlwgpts, clouds_lw, k_dist_lw, cldfrac, p_lay); + + // Copy cloud properties to outputs (is this needed, or can we just use pointers?) + // Alternatively, just compute and output a subcolumn cloud mask + parallel_for(SimpleBounds<3>(nswgpts, nlay, ncol), YAKL_LAMBDA (int igpt, int ilay, int icol) + { + cld_tau_sw_gpt(icol,ilay,igpt) = clouds_sw_gpt.tau(icol,ilay,igpt); + }); + parallel_for(SimpleBounds<3>(nlwgpts, nlay, ncol), YAKL_LAMBDA (int igpt, int ilay, int icol) + { + cld_tau_lw_gpt(icol,ilay,igpt) = clouds_lw_gpt.tau(icol,ilay,igpt); + }); + + // Do shortwave + rrtmgp_sw(ncol, nlay, + k_dist_sw, p_lay, t_lay, p_lev, t_lev, gas_concs, + sfc_alb_dir, sfc_alb_dif, mu0, aerosol_sw, clouds_sw_gpt, + fluxes_sw, clnclrsky_fluxes_sw, clrsky_fluxes_sw, clnsky_fluxes_sw, + tsi_scaling, extra_clnclrsky_diag, extra_clnsky_diag); + + // Do longwave + rrtmgp_lw(ncol, nlay, + k_dist_lw, p_lay, t_lay, p_lev, t_lev, gas_concs, + aerosol_lw, clouds_lw_gpt, + fluxes_lw, clnclrsky_fluxes_lw, clrsky_fluxes_lw, clnsky_fluxes_lw, + extra_clnclrsky_diag, extra_clnsky_diag); + +} + + +int3d +get_subcolumn_mask (const int ncol, + const int nlay, + const int ngpt, + real2d& cldf, + const int overlap_option, + int1d& seeds) +{ + // Routine will return subcolumn mask with values of 0 indicating no cloud, 1 indicating cloud + auto subcolumn_mask = int3d("subcolumn_mask", ncol, nlay, ngpt); + + // Subcolumn generators are a means for producing a variable x(i,j,k), where + // + // c(i,j,k) = 1 for x(i,j,k) > 1 - cldf(i,j) + // c(i,j,k) = 0 for x(i,j,k) <= 1 - cldf(i,j) + // + // I am going to call this "cldx" to be just slightly less ambiguous + auto cldx = real3d("cldx", ncol, nlay, ngpt); + + // Apply overlap assumption to set cldx + if (overlap_option == 0) { // Dummy mask, always cloudy + memset(cldx, 1); + } else { // Default case, maximum-random overlap + // Maximum-random overlap: + // Uses essentially the algorithm described in eq (14) in Raisanen et al. 2004, + // https://rmets.onlinelibrary.wiley.com/doi/epdf/10.1256/qj.03.99. Also the same + // algorithm used in RRTMG implementation of maximum-random overlap (see + // https://github.com/AER-RC/RRTMG_SW/blob/master/src/mcica_subcol_gen_sw.f90) + // + // First, fill cldx with random numbers. Need to use a unique seed for each column! + parallel_for(SimpleBounds<1>(ncol), YAKL_LAMBDA(int icol) + { + yakl::Random rand(seeds(icol)); + for (int igpt = 1; igpt <= ngpt; igpt++) { + for (int ilay = 1; ilay <= nlay; ilay++) { + cldx(icol,ilay,igpt) = rand.genFP(); + } + } + }); + // Step down columns and apply algorithm from eq (14) + parallel_for(SimpleBounds<2>(ngpt,ncol), YAKL_LAMBDA(int igpt, int icol) + { + for (int ilay = 2; ilay <= nlay; ilay++) { + // Check cldx in level above and see if it satisfies conditions to create a cloudy subcolumn + if (cldx(icol,ilay-1,igpt) > 1.0 - cldf(icol,ilay-1)) { + // Cloudy subcolumn above, use same random number here so that clouds in these two adjacent + // layers are maximimally overlapped + cldx(icol,ilay,igpt) = cldx(icol,ilay-1,igpt); + } else { + // Cloud-less above, use new random number so that clouds are distributed + // randomly in this layer. Need to scale new random number to range + // [0, 1.0 - cldf(ilay-1)] because we have artifically changed the distribution + // of random numbers in this layer with the above branch of the conditional, + // which would otherwise inflate cloud fraction in this layer. + cldx(icol,ilay,igpt) = cldx(icol,ilay ,igpt) * (1.0 - cldf(icol,ilay-1)); + } + } + }); + } + + // Use cldx array to create subcolumn mask + parallel_for(SimpleBounds<3>(ngpt,nlay,ncol), YAKL_LAMBDA(int igpt, int ilay, int icol) + { + if (cldx(icol,ilay,igpt) > 1.0 - cldf(icol,ilay)) { + subcolumn_mask(icol,ilay,igpt) = 1; + } else { + subcolumn_mask(icol,ilay,igpt) = 0; + } + }); + return subcolumn_mask; +} + + +void +rrtmgp_sw (const int ncol, + const int nlay, + GasOpticsRRTMGP& k_dist, + real2d& p_lay, + real2d& t_lay, + real2d& p_lev, + real2d& t_lev, + GasConcs& gas_concs, + real2d& sfc_alb_dir, + real2d& sfc_alb_dif, + real1d& mu0, + OpticalProps2str& aerosol, + OpticalProps2str& clouds, + FluxesByband& fluxes, + FluxesBroadband& clnclrsky_fluxes, + FluxesBroadband& clrsky_fluxes, + FluxesBroadband& clnsky_fluxes, + const Real tsi_scaling, + const bool extra_clnclrsky_diag, + const bool extra_clnsky_diag) +{ + // Get problem sizes + int nbnd = k_dist.get_nband(); + int ngpt = k_dist.get_ngpt(); + int ngas = gas_concs.get_num_gases(); + + // Associate local pointers for fluxes + auto& flux_up = fluxes.flux_up; + auto& flux_dn = fluxes.flux_dn; + auto& flux_dn_dir = fluxes.flux_dn_dir; + auto& bnd_flux_up = fluxes.bnd_flux_up; + auto& bnd_flux_dn = fluxes.bnd_flux_dn; + auto& bnd_flux_dn_dir = fluxes.bnd_flux_dn_dir; + auto& clnclrsky_flux_up = clnclrsky_fluxes.flux_up; + auto& clnclrsky_flux_dn = clnclrsky_fluxes.flux_dn; + auto& clnclrsky_flux_dn_dir = clnclrsky_fluxes.flux_dn_dir; + auto& clrsky_flux_up = clrsky_fluxes.flux_up; + auto& clrsky_flux_dn = clrsky_fluxes.flux_dn; + auto& clrsky_flux_dn_dir = clrsky_fluxes.flux_dn_dir; + auto& clnsky_flux_up = clnsky_fluxes.flux_up; + auto& clnsky_flux_dn = clnsky_fluxes.flux_dn; + auto& clnsky_flux_dn_dir = clnsky_fluxes.flux_dn_dir; + + // Reset fluxes to zero + parallel_for(SimpleBounds<2>(nlay+1,ncol), YAKL_LAMBDA(int ilev, int icol) + { + flux_up (icol,ilev) = 0; + flux_dn (icol,ilev) = 0; + flux_dn_dir(icol,ilev) = 0; + clnclrsky_flux_up (icol,ilev) = 0; + clnclrsky_flux_dn (icol,ilev) = 0; + clnclrsky_flux_dn_dir(icol,ilev) = 0; + clrsky_flux_up (icol,ilev) = 0; + clrsky_flux_dn (icol,ilev) = 0; + clrsky_flux_dn_dir(icol,ilev) = 0; + clnsky_flux_up (icol,ilev) = 0; + clnsky_flux_dn (icol,ilev) = 0; + clnsky_flux_dn_dir(icol,ilev) = 0; + }); + parallel_for(SimpleBounds<3>(nbnd,nlay+1,ncol), YAKL_LAMBDA(int ibnd, int ilev, int icol) + { + bnd_flux_up (icol,ilev,ibnd) = 0; + bnd_flux_dn (icol,ilev,ibnd) = 0; + bnd_flux_dn_dir(icol,ilev,ibnd) = 0; + }); + + // Get daytime indices + auto dayIndices = int1d("dayIndices", ncol); + memset(dayIndices, -1); + // Loop below has to be done on host, so create host copies + // TODO: there is probably a way to do this on the device + auto dayIndices_h = dayIndices.createHostCopy(); + auto mu0_h = mu0.createHostCopy(); + int nday = 0; + for (int icol = 1; icol <= ncol; icol++) { + if (mu0_h(icol) > 0) { + nday++; + dayIndices_h(nday) = icol; + } + } + + // Copy data back to the device + dayIndices_h.deep_copy_to(dayIndices); + if (nday == 0) { + // No daytime columns in this chunk, skip the rest of this routine + return; + } + + // Subset mu0 + auto mu0_day = real1d("mu0_day", nday); + parallel_for(SimpleBounds<1>(nday), YAKL_LAMBDA(int iday) + { + mu0_day(iday) = mu0(dayIndices(iday)); + }); + + // subset state variables + auto p_lay_day = real2d("p_lay_day", nday, nlay); + auto t_lay_day = real2d("t_lay_day", nday, nlay); + parallel_for(SimpleBounds<2>(nlay,nday), YAKL_LAMBDA(int ilay, int iday) + { + p_lay_day(iday,ilay) = p_lay(dayIndices(iday),ilay); + t_lay_day(iday,ilay) = t_lay(dayIndices(iday),ilay); + }); + auto p_lev_day = real2d("p_lev_day", nday, nlay+1); + auto t_lev_day = real2d("t_lev_day", nday, nlay+1); + parallel_for(SimpleBounds<2>(nlay+1,nday), YAKL_LAMBDA(int ilev, int iday) + { + p_lev_day(iday,ilev) = p_lev(dayIndices(iday),ilev); + t_lev_day(iday,ilev) = t_lev(dayIndices(iday),ilev); + }); + + // Subset gases + auto gas_names = gas_concs.get_gas_names(); + GasConcs gas_concs_day; + gas_concs_day.init(gas_names, nday, nlay); + for (int igas = 0; igas < ngas; igas++) { + auto vmr_day = real2d("vmr_day", nday, nlay); + auto vmr = real2d("vmr" , ncol, nlay); + gas_concs.get_vmr(gas_names[igas], vmr); + parallel_for(SimpleBounds<2>(nlay,nday), YAKL_LAMBDA(int ilay, int iday) + { + vmr_day(iday,ilay) = vmr(dayIndices(iday),ilay); + }); + gas_concs_day.set_vmr(gas_names[igas], vmr_day); + } + + // Subset aerosol optics + OpticalProps2str aerosol_day; + aerosol_day.init(k_dist.get_band_lims_wavenumber()); + aerosol_day.alloc_2str(nday, nlay); + parallel_for(SimpleBounds<3>(nbnd,nlay,nday), YAKL_LAMBDA(int ibnd, int ilay, int iday) + { + aerosol_day.tau(iday,ilay,ibnd) = aerosol.tau(dayIndices(iday),ilay,ibnd); + aerosol_day.ssa(iday,ilay,ibnd) = aerosol.ssa(dayIndices(iday),ilay,ibnd); + aerosol_day.g (iday,ilay,ibnd) = aerosol.g (dayIndices(iday),ilay,ibnd); + }); + + // Subset cloud optics + // TODO: nbnd -> ngpt once we pass sub-sampled cloud state + OpticalProps2str clouds_day; + clouds_day.init(k_dist.get_band_lims_wavenumber(), k_dist.get_band_lims_gpoint()); + clouds_day.alloc_2str(nday, nlay); + parallel_for(SimpleBounds<3>(ngpt,nlay,nday), YAKL_LAMBDA(int igpt, int ilay, int iday) + { + clouds_day.tau(iday,ilay,igpt) = clouds.tau(dayIndices(iday),ilay,igpt); + clouds_day.ssa(iday,ilay,igpt) = clouds.ssa(dayIndices(iday),ilay,igpt); + clouds_day.g (iday,ilay,igpt) = clouds.g (dayIndices(iday),ilay,igpt); + }); + + // RRTMGP assumes surface albedos have a screwy dimension ordering + // for some strange reason, so we need to transpose these; also do + // daytime subsetting in the same kernel + real2d sfc_alb_dir_T("sfc_alb_dir", nbnd, nday); + real2d sfc_alb_dif_T("sfc_alb_dif", nbnd, nday); + parallel_for(SimpleBounds<2>(nbnd,nday), YAKL_LAMBDA(int ibnd, int icol) + { + sfc_alb_dir_T(ibnd,icol) = sfc_alb_dir(dayIndices(icol),ibnd); + sfc_alb_dif_T(ibnd,icol) = sfc_alb_dif(dayIndices(icol),ibnd); + }); + + // Temporaries we need for daytime-only fluxes + auto flux_up_day = real2d("flux_up_day", nday, nlay+1); + auto flux_dn_day = real2d("flux_dn_day", nday, nlay+1); + auto flux_dn_dir_day = real2d("flux_dn_dir_day", nday, nlay+1); + auto bnd_flux_up_day = real3d("bnd_flux_up_day", nday, nlay+1, nbnd); + auto bnd_flux_dn_day = real3d("bnd_flux_dn_day", nday, nlay+1, nbnd); + auto bnd_flux_dn_dir_day = real3d("bnd_flux_dn_dir_day", nday, nlay+1, nbnd); + FluxesByband fluxes_day; + fluxes_day.flux_up = flux_up_day; + fluxes_day.flux_dn = flux_dn_day; + fluxes_day.flux_dn_dir = flux_dn_dir_day; + fluxes_day.bnd_flux_up = bnd_flux_up_day; + fluxes_day.bnd_flux_dn = bnd_flux_dn_day; + fluxes_day.bnd_flux_dn_dir = bnd_flux_dn_dir_day; + + // Allocate space for optical properties + OpticalProps2str optics; + optics.alloc_2str(nday, nlay, k_dist); + + OpticalProps2str optics_no_aerosols; + if (extra_clnsky_diag) { + // Allocate space for optical properties (no aerosols) + optics_no_aerosols.alloc_2str(nday, nlay, k_dist); + } + + // Limit temperatures for gas optics look-up tables + auto t_lay_limited = real2d("t_lay_limited", nday, nlay); + limit_to_bounds(t_lay_day, k_dist_sw.get_temp_min(), k_dist_sw.get_temp_max(), t_lay_limited); + + // Do gas optics + real2d toa_flux("toa_flux", nday, ngpt); + auto p_lay_host = p_lay.createHostCopy(); + bool top_at_1 = p_lay_host(1, 1) < p_lay_host(1, nlay); + + k_dist.gas_optics(nday, nlay, top_at_1, p_lay_day, p_lev_day, + t_lay_limited, gas_concs_day, optics, toa_flux); + if (extra_clnsky_diag) { + k_dist.gas_optics(nday, nlay, top_at_1, p_lay_day, p_lev_day, + t_lay_limited, gas_concs_day, optics_no_aerosols, toa_flux); + } + + // Apply tsi_scaling + parallel_for(SimpleBounds<2>(ngpt,nday), YAKL_LAMBDA(int igpt, int iday) + { + toa_flux(iday,igpt) = tsi_scaling * toa_flux(iday,igpt); + }); + + if (extra_clnclrsky_diag) { + // Compute clear-clean-sky (just gas) fluxes on daytime columns + rte_sw(optics, top_at_1, mu0_day, toa_flux, sfc_alb_dir_T, sfc_alb_dif_T, fluxes_day); + // Expand daytime fluxes to all columns + parallel_for(SimpleBounds<2>(nlay+1,nday), YAKL_LAMBDA(int ilev, int iday) + { + int icol = dayIndices(iday); + clnclrsky_flux_up (icol,ilev) = flux_up_day (iday,ilev); + clnclrsky_flux_dn (icol,ilev) = flux_dn_day (iday,ilev); + clnclrsky_flux_dn_dir(icol,ilev) = flux_dn_dir_day(iday,ilev); + }); + } + + // Combine gas and aerosol optics + aerosol_day.delta_scale(); + aerosol_day.increment(optics); + + // Compute clearsky (gas + aerosol) fluxes on daytime columns + rte_sw(optics, top_at_1, mu0_day, toa_flux, sfc_alb_dir_T, sfc_alb_dif_T, fluxes_day); + + // Expand daytime fluxes to all columns + parallel_for(SimpleBounds<2>(nlay+1,nday), YAKL_LAMBDA(int ilev, int iday) + { + int icol = dayIndices(iday); + clrsky_flux_up (icol,ilev) = flux_up_day (iday,ilev); + clrsky_flux_dn (icol,ilev) = flux_dn_day (iday,ilev); + clrsky_flux_dn_dir(icol,ilev) = flux_dn_dir_day(iday,ilev); + }); + + // Now merge in cloud optics and do allsky calculations + + // Combine gas and cloud optics + clouds_day.delta_scale(); + clouds_day.increment(optics); + + // Compute fluxes on daytime columns + rte_sw(optics, top_at_1, mu0_day, toa_flux, sfc_alb_dir_T, sfc_alb_dif_T, fluxes_day); + + // Expand daytime fluxes to all columns + parallel_for(SimpleBounds<2>(nlay+1,nday), YAKL_LAMBDA(int ilev, int iday) + { + int icol = dayIndices(iday); + flux_up (icol,ilev) = flux_up_day (iday,ilev); + flux_dn (icol,ilev) = flux_dn_day (iday,ilev); + flux_dn_dir(icol,ilev) = flux_dn_dir_day(iday,ilev); + }); + parallel_for(SimpleBounds<3>(nbnd,nlay+1,nday), YAKL_LAMBDA(int ibnd, int ilev, int iday) + { + int icol = dayIndices(iday); + bnd_flux_up (icol,ilev,ibnd) = bnd_flux_up_day (iday,ilev,ibnd); + bnd_flux_dn (icol,ilev,ibnd) = bnd_flux_dn_day (iday,ilev,ibnd); + bnd_flux_dn_dir(icol,ilev,ibnd) = bnd_flux_dn_dir_day(iday,ilev,ibnd); + }); + + if (extra_clnsky_diag) { + // First increment clouds in optics_no_aerosols + clouds_day.increment(optics_no_aerosols); + // Compute cleansky (gas + clouds) fluxes on daytime columns + rte_sw(optics_no_aerosols, top_at_1, mu0_day, toa_flux, sfc_alb_dir_T, sfc_alb_dif_T, fluxes_day); + // Expand daytime fluxes to all columns + parallel_for(SimpleBounds<2>(nlay+1,nday), YAKL_LAMBDA(int ilev, int iday) + { + int icol = dayIndices(iday); + clnsky_flux_up (icol,ilev) = flux_up_day (iday,ilev); + clnsky_flux_dn (icol,ilev) = flux_dn_day (iday,ilev); + clnsky_flux_dn_dir(icol,ilev) = flux_dn_dir_day(iday,ilev); + }); + } +} + + +void +rrtmgp_lw (const int ncol, + const int nlay, + GasOpticsRRTMGP& k_dist, + real2d& p_lay, + real2d& t_lay, + real2d& p_lev, + real2d& t_lev, + GasConcs& gas_concs, + OpticalProps1scl& aerosol, + OpticalProps1scl& clouds, + FluxesByband& fluxes, + FluxesBroadband& clnclrsky_fluxes, + FluxesBroadband& clrsky_fluxes, + FluxesBroadband& clnsky_fluxes, + const bool extra_clnclrsky_diag, + const bool extra_clnsky_diag) +{ + // Problem size + int nbnd = k_dist.get_nband(); + + // Associate local pointers for fluxes + auto& flux_up = fluxes.flux_up; + auto& flux_dn = fluxes.flux_dn; + auto& bnd_flux_up = fluxes.bnd_flux_up; + auto& bnd_flux_dn = fluxes.bnd_flux_dn; + auto& clnclrsky_flux_up = clnclrsky_fluxes.flux_up; + auto& clnclrsky_flux_dn = clnclrsky_fluxes.flux_dn; + auto& clrsky_flux_up = clrsky_fluxes.flux_up; + auto& clrsky_flux_dn = clrsky_fluxes.flux_dn; + auto& clnsky_flux_up = clnsky_fluxes.flux_up; + auto& clnsky_flux_dn = clnsky_fluxes.flux_dn; + + // Reset fluxes to zero + parallel_for(SimpleBounds<2>(nlay + 1, ncol), YAKL_LAMBDA(int ilev, int icol) + { + flux_up(icol, ilev) = 0; + flux_dn(icol, ilev) = 0; + clnclrsky_flux_up(icol, ilev) = 0; + clnclrsky_flux_dn(icol, ilev) = 0; + clrsky_flux_up(icol, ilev) = 0; + clrsky_flux_dn(icol, ilev) = 0; + clnsky_flux_up(icol, ilev) = 0; + clnsky_flux_dn(icol, ilev) = 0; + }); + parallel_for(SimpleBounds<3>(nbnd, nlay + 1, ncol), YAKL_LAMBDA(int ibnd, int ilev, int icol) + { + bnd_flux_up(icol, ilev, ibnd) = 0; + bnd_flux_dn(icol, ilev, ibnd) = 0; + }); + + // Allocate space for optical properties + OpticalProps1scl optics; + optics.alloc_1scl(ncol, nlay, k_dist); + OpticalProps1scl optics_no_aerosols; + if (extra_clnsky_diag) { + // Allocate space for optical properties (no aerosols) + optics_no_aerosols.alloc_1scl(ncol, nlay, k_dist); + } + + // Boundary conditions + SourceFuncLW lw_sources; + lw_sources.alloc(ncol, nlay, k_dist); + real1d t_sfc ("t_sfc" ,ncol); + real2d emis_sfc("emis_sfc",nbnd,ncol); + + // Surface temperature + auto p_lay_host = p_lay.createHostCopy(); + bool top_at_1 = p_lay_host(1, 1) < p_lay_host(1, nlay); + parallel_for(SimpleBounds<1>(ncol), YAKL_LAMBDA(int icol) + { + t_sfc(icol) = t_lev(icol, merge(nlay+1, 1, top_at_1)); + }); + memset(emis_sfc , amrex::Real(0.98)); + + // Get Gaussian quadrature weights + // TODO: move this crap out of userland! + // Weights and angle secants for first order (k=1) Gaussian quadrature. + // Values from Table 2, Clough et al, 1992, doi:10.1029/92JD01419 + // after Abramowitz & Stegun 1972, page 921 + int constexpr max_gauss_pts = 4; + realHost2d gauss_Ds_host ("gauss_Ds" ,max_gauss_pts,max_gauss_pts); + gauss_Ds_host(1,1) = 1.66_wp ; gauss_Ds_host(2,1) = 0._wp; gauss_Ds_host(3,1) = 0._wp; gauss_Ds_host(4,1) = 0._wp; + gauss_Ds_host(1,2) = 1.18350343_wp; gauss_Ds_host(2,2) = 2.81649655_wp; gauss_Ds_host(3,2) = 0._wp; gauss_Ds_host(4,2) = 0._wp; + gauss_Ds_host(1,3) = 1.09719858_wp; gauss_Ds_host(2,3) = 1.69338507_wp; gauss_Ds_host(3,3) = 4.70941630_wp; gauss_Ds_host(4,3) = 0._wp; + gauss_Ds_host(1,4) = 1.06056257_wp; gauss_Ds_host(2,4) = 1.38282560_wp; gauss_Ds_host(3,4) = 2.40148179_wp; gauss_Ds_host(4,4) = 7.15513024_wp; + + realHost2d gauss_wts_host("gauss_wts",max_gauss_pts,max_gauss_pts); + gauss_wts_host(1,1) = 0.5_wp ; gauss_wts_host(2,1) = 0._wp ; gauss_wts_host(3,1) = 0._wp ; gauss_wts_host(4,1) = 0._wp ; + gauss_wts_host(1,2) = 0.3180413817_wp; gauss_wts_host(2,2) = 0.1819586183_wp; gauss_wts_host(3,2) = 0._wp ; gauss_wts_host(4,2) = 0._wp ; + gauss_wts_host(1,3) = 0.2009319137_wp; gauss_wts_host(2,3) = 0.2292411064_wp; gauss_wts_host(3,3) = 0.0698269799_wp; gauss_wts_host(4,3) = 0._wp ; + gauss_wts_host(1,4) = 0.1355069134_wp; gauss_wts_host(2,4) = 0.2034645680_wp; gauss_wts_host(3,4) = 0.1298475476_wp; gauss_wts_host(4,4) = 0.0311809710_wp; + + real2d gauss_Ds ("gauss_Ds" ,max_gauss_pts,max_gauss_pts); + real2d gauss_wts("gauss_wts",max_gauss_pts,max_gauss_pts); + gauss_Ds_host .deep_copy_to(gauss_Ds ); + gauss_wts_host.deep_copy_to(gauss_wts); + + // Limit temperatures for gas optics look-up tables + auto t_lay_limited = real2d("t_lay_limited", ncol, nlay); + auto t_lev_limited = real2d("t_lev_limited", ncol, nlay+1); + limit_to_bounds(t_lay, k_dist_lw.get_temp_min(), k_dist_lw.get_temp_max(), t_lay_limited); + limit_to_bounds(t_lev, k_dist_lw.get_temp_min(), k_dist_lw.get_temp_max(), t_lev_limited); + + // Do gas optics + k_dist.gas_optics(ncol, nlay, top_at_1, p_lay, p_lev, t_lay_limited, t_sfc, gas_concs, optics, lw_sources, real2d(), t_lev_limited); + if (extra_clnsky_diag) { + k_dist.gas_optics(ncol, nlay, top_at_1, p_lay, p_lev, t_lay_limited, t_sfc, gas_concs, optics_no_aerosols, lw_sources, real2d(), t_lev_limited); + } + + if (extra_clnclrsky_diag) { + // Compute clean-clear-sky fluxes before we add in aerosols and clouds + rte_lw(max_gauss_pts, gauss_Ds, gauss_wts, optics, top_at_1, lw_sources, emis_sfc, clnclrsky_fluxes); + } + + // Combine gas and aerosol optics + aerosol.increment(optics); + + // Compute clear-sky fluxes before we add in clouds + rte_lw(max_gauss_pts, gauss_Ds, gauss_wts, optics, top_at_1, lw_sources, emis_sfc, clrsky_fluxes); + + // Combine gas and cloud optics + clouds.increment(optics); + + // Compute allsky fluxes + rte_lw(max_gauss_pts, gauss_Ds, gauss_wts, optics, top_at_1, lw_sources, emis_sfc, fluxes); + + if (extra_clnsky_diag) { + // First increment clouds in optics_no_aerosols + clouds.increment(optics_no_aerosols); + // Compute clean-sky fluxes + rte_lw(max_gauss_pts, gauss_Ds, gauss_wts, optics_no_aerosols, top_at_1, lw_sources, emis_sfc, clnsky_fluxes); + } +} + + +void +compute_cloud_area (int ncol, + int nlay, + int ngpt, + const Real pmin, + const Real pmax, + const real2d& pmid, + const real3d& cld_tau_gpt, + real1d& cld_area) +{ + // Subcolumn binary cld mask; if any layers with pressure between pmin and pmax are cloudy + // then 2d subcol mask is 1, otherwise it is 0 + auto subcol_mask = real2d("subcol_mask", ncol, ngpt); + memset(subcol_mask, 0); + parallel_for(SimpleBounds<3>(ngpt, nlay, ncol), YAKL_LAMBDA(int igpt, int ilay, int icol) + { + // NOTE: using plev would need to assume level ordering (top to bottom or bottom to top), but + // using play/pmid does not + if (cld_tau_gpt(icol,ilay,igpt) > 0 && pmid(icol,ilay) >= pmin && pmid(icol,ilay) < pmax) { + subcol_mask(icol,igpt) = 1; + } + }); + // Compute average over subcols to get cloud area + auto ngpt_inv = 1.0 / ngpt; + memset(cld_area, 0); + parallel_for(SimpleBounds<1>(ncol), YAKL_LAMBDA(int icol) + { + // This loop needs to be serial because of the atomic reduction + for (int igpt = 1; igpt <= ngpt; ++igpt) { + cld_area(icol) += subcol_mask(icol,igpt) * ngpt_inv; + } + }); +} + + +int +get_wavelength_index_sw (double wavelength) { return get_wavelength_index(k_dist_sw, wavelength); } + + +int +get_wavelength_index_lw (double wavelength) { return get_wavelength_index(k_dist_lw, wavelength); } + + +int +get_wavelength_index (OpticalProps& kdist, + double wavelength) +{ + // Get wavelength bounds for all wavelength bands + auto wavelength_bounds = kdist.get_band_lims_wavelength(); + + // Find the band index for the specified wavelength + // Note that bands are stored in wavenumber space, units of cm-1, so if we are passed wavelength + // in units of meters, we need a conversion factor of 10^2 + int nbnds = kdist.get_nband(); + yakl::ScalarLiveOut band_index(-1); + parallel_for(SimpleBounds<1>(nbnds), YAKL_LAMBDA(int ibnd) + { + if (wavelength_bounds(1,ibnd) < wavelength_bounds(2,ibnd)) { + if (wavelength_bounds(1,ibnd) <= wavelength * 1e2 && wavelength * 1e2 <= wavelength_bounds(2,ibnd)) { + band_index = ibnd; + } + } else { + if (wavelength_bounds(1,ibnd) >= wavelength * 1e2 && wavelength * 1e2 >= wavelength_bounds(2,ibnd)) { + band_index = ibnd; + } + } + }); + return band_index.hostRead(); +} + + +void +compute_aerocom_cloudtop (int ncol, int nlay, const real2d &tmid, const real2d &pmid, + const real2d &p_del, const real2d &z_del, const real2d &qc, + const real2d &qi, const real2d &rel, const real2d &rei, + const real2d &cldfrac_tot, const real2d &nc, + real1d &T_mid_at_cldtop, real1d &p_mid_at_cldtop, + real1d &cldfrac_ice_at_cldtop, real1d &cldfrac_liq_at_cldtop, + real1d &cldfrac_tot_at_cldtop, real1d &cdnc_at_cldtop, + real1d &eff_radius_qc_at_cldtop, real1d &eff_radius_qi_at_cldtop) +{ + /* The goal of this routine is to calculate properties at cloud top + * based on the AeroCom recommendation. See reference for routine + * get_subcolumn_mask above, where equation 14 is used for the + * maximum-random overlap assumption for subcolumn generation. We use + * equation 13, the column counterpart. + */ + // Set outputs to zero + memset(T_mid_at_cldtop, 0.0); + memset(p_mid_at_cldtop, 0.0); + memset(cldfrac_ice_at_cldtop, 0.0); + memset(cldfrac_liq_at_cldtop, 0.0); + memset(cldfrac_tot_at_cldtop, 0.0); + memset(cdnc_at_cldtop, 0.0); + memset(eff_radius_qc_at_cldtop, 0.0); + memset(eff_radius_qi_at_cldtop, 0.0); + + // Initialize the 1D "clear fraction" as 1 (totally clear) + auto aerocom_clr = real1d("aerocom_clr", ncol); + memset(aerocom_clr, 1.0); + + // TODO: move tunable constant to namelist + constexpr real q_threshold = 0.0; // BAD_CONSTANT! + + // TODO: move tunable constant to namelist + constexpr real cldfrac_tot_threshold = 0.001; // BAD_CONSTANT! + + // Loop over all columns in parallel + parallel_for(SimpleBounds<1>(ncol), YAKL_LAMBDA(int icol) + { + // Loop over all layers in serial (due to accumulative + // product), starting at 2 (second highest) layer because the + // highest is assumed to hav no clouds + for(int ilay = 2; ilay <= nlay; ++ilay) { + // Only do the calculation if certain conditions are met + if((qc(icol, ilay) + qi(icol, ilay)) > q_threshold && + (cldfrac_tot(icol, ilay) > cldfrac_tot_threshold)) { + /* PART I: Probabilistically determining cloud top */ + // Populate aerocom_tmp as the clear-sky fraction + // probability of this level, where aerocom_clr is that of + // the previous level + auto aerocom_tmp = aerocom_clr(icol) * + (1.0 - std::max(cldfrac_tot(icol, ilay - 1), + cldfrac_tot(icol, ilay))) / + (1.0 - std::min(cldfrac_tot(icol, ilay - 1), + 1.0 - cldfrac_tot_threshold)); + // Temporary variable for probability "weights" + auto aerocom_wts = aerocom_clr(icol) - aerocom_tmp; + // Temporary variable for liquid "phase" + auto aerocom_phi = qc(icol, ilay) / (qc(icol, ilay) + qi(icol, ilay)); + /* PART II: The inferred properties */ + /* In general, converting a 3D property X to a 2D cloud-top + * counterpart x follows: x(i) += X(i,k) * weights * Phase + * but X and Phase are not always needed */ + // T_mid_at_cldtop + T_mid_at_cldtop(icol) += tmid(icol, ilay) * aerocom_wts; + // p_mid_at_cldtop + p_mid_at_cldtop(icol) += pmid(icol, ilay) * aerocom_wts; + // cldfrac_ice_at_cldtop + cldfrac_ice_at_cldtop(icol) += (1.0 - aerocom_phi) * aerocom_wts; + // cldfrac_liq_at_cldtop + cldfrac_liq_at_cldtop(icol) += aerocom_phi * aerocom_wts; + // cdnc_at_cldtop + /* We need to convert nc from 1/mass to 1/volume first, and + * from grid-mean to in-cloud, but after that, the + * calculation follows the general logic */ + auto cdnc = nc(icol, ilay) * p_del(icol, ilay) / + z_del(icol, ilay) / CONST_GRAV / + cldfrac_tot(icol, ilay); + cdnc_at_cldtop(icol) += cdnc * aerocom_phi * aerocom_wts; + // eff_radius_qc_at_cldtop + eff_radius_qc_at_cldtop(icol) += rel(icol, ilay) * aerocom_phi * aerocom_wts; + // eff_radius_qi_at_cldtop + eff_radius_qi_at_cldtop(icol) += rei(icol, ilay) * (1.0 - aerocom_phi) * aerocom_wts; + // Reset aerocom_clr to aerocom_tmp to accumulate + aerocom_clr(icol) = aerocom_tmp; + } + } + // After the serial loop over levels, the cloudy fraction is + // defined as (1 - aerocom_clr). This is true because + // aerocom_clr is the result of accumulative probabilities + // (their products) + cldfrac_tot_at_cldtop(icol) = 1.0 - aerocom_clr(icol); + }); +} + +} // namespace rrtmgp + diff --git a/Source/Radiation/ERF_RRTMGP_Utils.H b/Source/Radiation/ERF_RRTMGP_Utils.H new file mode 100644 index 000000000..dd453798d --- /dev/null +++ b/Source/Radiation/ERF_RRTMGP_Utils.H @@ -0,0 +1,144 @@ +#ifndef ERF_RRTMGP_UTILS_H +#define ERF_RRTMGP_UTILS_H + +#include "rrtmgp_const.h" +#include "rrtmgp_conversion.h" + +#include "YAKL.h" +#include "YAKL_Bounds_fortran.h" + +#include "ERF_Constants.H" + +namespace rrtmgp { + +// Things we need from YAKL +using yakl::intrinsics::maxval; +using yakl::intrinsics::minval; +using yakl::intrinsics::count; +using yakl::intrinsics::sum; + + +template +void +mixing_ratio_to_cloud_mass (yakl::Array const &mixing_ratio, + yakl::Array const &cloud_fraction, + yakl::Array const &dp, + yakl::Array &cloud_mass) +{ + int ncol = mixing_ratio.dimension[0]; + int nlay = mixing_ratio.dimension[1]; + yakl::fortran::parallel_for(yakl::fortran::SimpleBounds<2>(nlay, ncol), YAKL_LAMBDA(int ilay, int icol) + { + // Compute in-cloud mixing ratio (mixing ratio of the cloudy part of the layer) + // NOTE: these thresholds (from E3SM) seem arbitrary, but included here for consistency + // This limits in-cloud mixing ratio to 0.005 kg/kg. According to note in cloud_diagnostics + // in EAM, this is consistent with limits in MG2. Is this true for P3? + if (cloud_fraction(icol,ilay) > 0) { + // Compute layer-integrated cloud mass (per unit area) + auto incloud_mixing_ratio = std::min(mixing_ratio(icol,ilay) / std::max(0.0001, cloud_fraction(icol,ilay)), 0.005); + cloud_mass(icol,ilay) = incloud_mixing_ratio * dp(icol,ilay) / CONST_GRAV; + } else { + cloud_mass(icol,ilay) = 0; + } + }); +} + + +template +void +limit_to_bounds (S const &arr_in, + T const lower, + T const upper, + S &arr_out) +{ + yakl::c::parallel_for(arr_in.totElems(), YAKL_LAMBDA(int i) + { + arr_out.data()[i] = std::min(std::max(arr_in.data()[i], lower), upper); + }); +} + + +// Provide a routine to compute heating due to radiative fluxes. This is +// computed as net flux into a layer, converted to a heating rate. It is +// the responsibility of the user to ensure fields are passed with the +// proper units. I.e., pressure at level interfaces should be in Pa, +// fluxes in W m-2, Cpair in J kg-1 K-1, gravit in m s-2. This will give +// heating in units of K s-1. +// TODO: we should probably update this to use the pseudo-density pdel instead +// of approximating pdel by differencing the level interface pressures. +// We are leaving this for the time being for consistency with SCREAMv0, +// from which this code was directly ported. +template +void +compute_heating_rate (yakl::Array const &flux_up, + yakl::Array const &flux_dn, + yakl::Array const &rho , + yakl::Array const &dz , + yakl::Array &heating_rate) +{ + auto ncol = flux_up.dimension[0]; + auto nlay = flux_up.dimension[1]-1; + yakl::fortran::parallel_for(yakl::fortran::SimpleBounds<2>(nlay,ncol), YAKL_LAMBDA(int ilay, int icol) + { + heating_rate(icol,ilay) = ( flux_up(icol,ilay+1) - flux_up(icol,ilay) + - flux_dn(icol,ilay+1) + flux_dn(icol,ilay) ) + * / (Cp_d * rho(icol,ilay) * dz(icol,ilay); + }); +} + + +inline +bool +radiation_do (const int irad, + const int nstep) +{ + // If irad == 0, then never do radiation; + // Otherwise, we always call radiation at the first step, + // and afterwards we do radiation if the timestep is divisible + // by irad + if (irad == 0) { + return false; + } else { + return ( (nstep == 0) || (nstep % irad == 0) ); + } +} + + +// Verify that array only contains values within valid range, and if not +// report min and max of array +template +bool +check_range (T x, + Real xmin, + Real xmax, + std::string msg, + std::ostream& out=std::cout) +{ + bool pass = true; + auto _xmin = minval(x); + auto _xmax = maxval(x); + if (_xmin < xmin or _xmax > xmax) { + // How many outside range? + auto bad_mask = x.createDeviceCopy(); + memset(bad_mask, 0); + yakl::c::parallel_for(yakl::c::SimpleBounds<1>(x.totElems()), YAKL_LAMBDA (int i) + { + if (x.data()[i] < xmin or x.data()[i] > xmax) { + bad_mask.data()[i] = 1; + } + }); + auto num_bad = sum(bad_mask); + if (num_bad > 0) { + pass = false; + out << msg << ": " + << num_bad << " values outside range " + << "[" << xmin << "," << xmax << "]" + << "; minval = " << _xmin + << "; maxval = " << _xmax << "\n"; + } + } + return pass; +} + +} // namespace rrtmgp +#endif diff --git a/Source/Radiation/ERF_RadConstants.H b/Source/Radiation/ERF_RadConstants.H deleted file mode 100644 index 06b117998..000000000 --- a/Source/Radiation/ERF_RadConstants.H +++ /dev/null @@ -1,264 +0,0 @@ -// -// This module contains constexprants that are specific to the radiative transfer -// code used in the RRTMG model. -// -#ifndef ERF_RAD_CONSTANTS_H -#define ERF_RAD_CONSTANTS_H - -#include "rrtmgp_const.h" -using yakl::fortran::parallel_for; -using yakl::fortran::SimpleBounds; - -class RadConstants { - public: - // number of shorwave spectral intervals - static constexpr int nswbands = 14; - static constexpr int nbndsw = 14; - - // Wavenumbers of band boundaries - // - // Note: Currently rad_solar_var extends the lowest band down to - // 100 cm^-1 if it is too high to cover the far-IR. Any changes meant - // to affect IR solar variability should take note of this. - - static constexpr real wavenum_low[] = // in cm^-1 - {2600., 3250., 4000., 4650., 5150., 6150., 7700., - 8050., 12850., 16000., 22650., 29000., 38000., 820.}; - - static constexpr real wavenum_high[] = // in cm^-1 - {3250., 4000., 4650., 5150., 6150., 7700., 8050., - 12850.,16000., 22650., 29000., 38000., 50000., 2600.}; - - // Solar irradiance at 1 A.U. in W/m^2 assumed by radiation code - // Rescaled so that sum is precisely 1368.22 and fractional amounts sum to 1.0 - static constexpr real solar_ref_band_irradiance[] = - {12.11, 20.3600000000001, 23.73, - 22.43, 55.63, 102.93, 24.29, - 345.74, 218.19, 347.20, - 129.49, 50.15, 3.08, 12.89}; - - // parameterizations related to radiation (when microphysics model doesn't provide) - static constexpr real icesize_table_min_temp = 180.; - static constexpr real retab[] = { 5.92779, 6.26422, 6.61973, 6.99539, 7.39234, - 7.81177, 8.25496, 8.72323, 9.21800, 9.74075, 10.2930, - 10.8765, 11.4929, 12.1440, 12.8317, 13.5581, 14.2319, - 15.0351, 15.8799, 16.7674, 17.6986, 18.6744, 19.6955, - 20.7623, 21.8757, 23.0364, 24.2452, 25.5034, 26.8125, - 27.7895, 28.6450, 29.4167, 30.1088, 30.7306, 31.2943, - 31.8151, 32.3077, 32.7870, 33.2657, 33.7540, 34.2601, - 34.7892, 35.3442, 35.9255, 36.5316, 37.1602, 37.8078, - 38.4720, 39.1508, 39.8442, 40.5552, 41.2912, 42.0635, - 42.8876, 43.7863, 44.7853, 45.9170, 47.2165, 48.7221, - 50.4710, 52.4980, 54.8315, 57.4898, 60.4785, 63.7898, - 65.5604, 71.2885, 75.4113, 79.7368, 84.2351, 88.8833, - 93.6658, 98.5739, 103.603, 108.752, 114.025, 119.424, - 124.954, 130.630, 136.457, 142.446, 148.608, 154.956, - 161.503, 168.262, 175.248, 182.473, 189.952, 197.699, - 205.728, 214.055, 222.694, 231.661, 240.971, 250.639}; - - // These are indices to the band for diagnostic output - static constexpr int idx_sw_diag = 10; // index to sw visible band - static constexpr int idx_nir_diag = 8; // index to sw near infrared (778-1240 nm) band - static constexpr int idx_uv_diag = 11; // index to sw uv (345-441 nm) band - - static constexpr int rrtmg_sw_cloudsim_band = 9; // rrtmg band for .67 micron - - // Number of evenly spaced intervals in rh - // The globality of this mesh may not be necessary - // Perhaps it could be specific to the aerosol - // But it is difficult to see how refined it must be - // for lookup. This value was found to be sufficient - // for Sulfate and probably necessary to resolve the - // high variation near rh = 1. Alternative methods - // were found to be too slow. - // Optimal approach would be for cam to specify size of aerosol - // based on each aerosol's characteristics. Radiation - // should know nothing about hygroscopic growth! - static constexpr int nrh = 1000; - - // LONGWAVE DATA - - // These are indices to the band for diagnostic output - static constexpr int idx_lw_diag = 7; // index to (H20 window) LW band - static constexpr int rrtmg_lw_cloudsim_band = 6; // rrtmg band for 10.5 micron - - // number of lw bands - static constexpr int nlwbands = 16; - static constexpr int nbndlw = 16; - - static constexpr real wavenumber1_longwave[] = // Longwave spectral band limits (cm-1) - {10., 350., 500., 630., 700., 820., 980., 1080., - 1180., 1390., 1480., 1800., 2080., 2250., 2390., 2600.}; - - static constexpr real wavenumber2_longwave[] = // Longwave spectral band limits (cm-1) - {350., 500., 630., 700., 820., 980., 1080., 1180., - 1390., 1480., 1800., 2080., 2250., 2390., 2600., 3250.}; - - //GASES TREATED BY RADIATION (line spectrae) - - // gasses required by radiation - static constexpr int nradgas = 8; - static constexpr const char* gaslist[] = - {"H2O","O3", "O2", "CO2", "N2O", "CH4", "CFC11", "CFC12"}; - - // use enum unit - enum Units { - inv_cm, - meter, - nanometer, - micrometer, - centimeter - }; - - // provide Solar Irradiance for each band in RRTMG - inline - static void get_solar_band_fraction_irrad (real1d& fractional_irradiance) - { - real tsi = 0; // total solar irradiance - for(auto i = 0; i < nswbands; ++i) - tsi = tsi + solar_ref_band_irradiance[i]; - - for(auto i = 0; i < nswbands; ++i) - fractional_irradiance(i) = solar_ref_band_irradiance[i] / tsi; - } - - // provide Total Solar Irradiance assumed by RRTMG - inline - static void get_ref_total_solar_irrad (real& tsi) - { - tsi = 0; - for(auto i = 0; i < nswbands; ++i) - tsi = tsi + solar_ref_band_irradiance[i]; - } - - // solar irradiance in each band (W/m^2) - inline - static void get_ref_solar_band_irrad (real1d& band_irrad ) - { - for(auto i = 0; i < nswbands; ++i) - band_irrad(i) = solar_ref_band_irradiance[i]; - } - - // number of solar (shortwave) bands in the rrtmg code - inline - static void get_number_sw_bands (int& number_of_bands) - { - number_of_bands = nswbands; - } - - // provide spectral boundaries of each longwave band - inline - static void get_lw_spectral_boundaries (real1d& low_boundaries, - real1d& high_boundaries, - Units units) - { - switch (units) { - case inv_cm: - for(auto i = 0; i < nlwbands; ++i) { - low_boundaries(i) = wavenumber1_longwave[i]; - high_boundaries(i) = wavenumber2_longwave[i]; - } - case meter: - for(auto i = 0; i < nlwbands; ++i) { - low_boundaries(i) = 1.e-2/wavenumber2_longwave[i]; - high_boundaries(i) = 1.e-2/wavenumber1_longwave[i]; - } - case nanometer: - for(auto i = 0; i < nlwbands; ++i) { - low_boundaries(i) = 1.e7/wavenumber2_longwave[i]; - high_boundaries(i) = 1.e7/wavenumber1_longwave[i]; - } - case micrometer: - for(auto i = 0; i < nlwbands; ++i) { - low_boundaries(i) = 1.e4/wavenumber2_longwave[i]; - high_boundaries(i) = 1.e4/wavenumber1_longwave[i]; - } - case centimeter: - for(auto i = 0; i < nlwbands; ++i) { - low_boundaries(i) = 1./wavenumber2_longwave[i]; - high_boundaries(i) = 1./wavenumber1_longwave[i]; - } - default: - amrex::Print() << "get_lw_spectral_boundaries: spectral units not acceptable\n"; - } - } - - inline - static void get_lw_spectral_midpoints (real1d& band_midpoints, Units units) - { - real1d lower_boundaries("lower_boundaries", nlwbands); - real1d upper_boundaries("upper_boundaries", nlwbands); - //int iband; - - // Get band boundaries - get_lw_spectral_boundaries(lower_boundaries, upper_boundaries, units); - - // Get band midpoints - for(auto iband = 0; iband < nlwbands; ++iband) { - band_midpoints(iband) = 0.5*(lower_boundaries(iband) + upper_boundaries(iband)); - } - - } - - // provide spectral boundaries of each shortwave band - inline - static void get_sw_spectral_boundaries (real1d& low_boundaries, real1d& high_boundaries, Units units) - { - switch (units) { - case inv_cm: - for(auto i = 0; i < nswbands; ++i) { - low_boundaries(i) = wavenum_low[i]; - high_boundaries(i) = wavenum_high[i]; - } - case meter: - for(auto i = 0; i < nswbands; ++i) { - low_boundaries(i) = 1.e-2/wavenum_high[i]; - high_boundaries(i) = 1.e-2/wavenum_low[i]; - } - case nanometer: - for(auto i = 0; i < nswbands; ++i) { - low_boundaries(i) = 1.e7/wavenum_high[i]; - high_boundaries(i) = 1.e7/wavenum_low[i]; - } - case micrometer: - for(auto i = 0; i < nswbands; ++i) { - low_boundaries(i) = 1.e4/wavenum_high[i]; - high_boundaries(i) = 1.e4/wavenum_low[i]; - } - case centimeter: - for(auto i = 0; i < nswbands; ++i) { - low_boundaries(i) = 1./wavenum_high[i]; - high_boundaries(i) = 1./wavenum_low[i]; - } - default: - amrex::Print() << "rad_constants.F90: spectral units not acceptable\n"; - } - } - - inline - static void get_sw_spectral_midpoints (real1d& band_midpoints, Units units) - { - real1d lower_boundaries("lower_boundaries", nswbands); - real1d upper_boundaries("upper_boundaries", nswbands); - - // Get band boundaries - get_sw_spectral_boundaries(lower_boundaries, upper_boundaries, units); - - //Get band midpoints - for(auto iband = 0; iband < nswbands; ++iband) { - band_midpoints(iband) = 0.5 * (lower_boundaries(iband) + upper_boundaries(iband)); - } - } - - // return the index in the gaslist array of the specified gasname - inline - static int rad_gas_index (std::string gasname) - { - for(auto igas = 0; igas < nradgas; ++igas) { - if (gaslist[igas] == gasname) return igas; - } - amrex::Abort("rad_gas_index: can not find gas with name " + gasname); - return -1; - } -}; -#endif diff --git a/Source/Radiation/ERF_Radiation.H b/Source/Radiation/ERF_Radiation.H index bcb3a0551..7db56fb60 100644 --- a/Source/Radiation/ERF_Radiation.H +++ b/Source/Radiation/ERF_Radiation.H @@ -1,3 +1,6 @@ +#ifndef ERF_RADIATION_H +#define ERF_RADIATION_H + /* * RTE-RRTMGP radiation model interface to ERF * The original code is developed by RobertPincus, and the code is open source available at: @@ -11,104 +14,128 @@ * The RTE-RRTMGP uses BSD-3-Clause Open Source License, if you want to make changes, * and modifications to the code, please refer to BSD-3-Clause Open Source License. */ -#ifndef ERF_RADIATION_H -#define ERF_RADIATION_H - +#include #include #include #include +#include + +#include + +#include #include #include #include #include -#include "ERF_Config.H" -#include "ERF_Constants.H" -#include "ERF_RadConstants.H" -#include "ERF_RRTMGP.H" -#include "ERF_Optics.H" -#include "ERF_AeroRadProps.H" -#include "ERF_Parameterizations.H" -#include "ERF_Albedo.H" +#include +#include +#include +#include +#include +#include +#include -// Radiation code interface class class Radiation { - public: - Radiation () { - // First, make sure yakl has been initialized - if (!yakl::isInitialized()) yakl::init(); - } +public: + // Constructor + Radiation (SolverChoice& sc); + + // Destructor ~Radiation () = default; - // init - void initialize (const amrex::MultiFab& cons_in, - amrex::MultiFab* lsm_fluxes, - amrex::MultiFab* lsm_zenith, - amrex::MultiFab* qheating_rates, - amrex::MultiFab* lat, - amrex::MultiFab* lon, - amrex::Vector qmoist, - const amrex::BoxArray& grids, - const amrex::Geometry& geom, - const amrex::Real& dt_advance, - const bool& do_sw_rad, - const bool& do_lw_rad, - const bool& do_aero_rad, - const bool& do_snow_opt, - const bool& is_cmip6_volcano); - - // run radiation model - void run (); - - // call back - void on_complete (); - - void radiation_driver_lw (int ncol, int nlev, - const real3d& gas_vmr, - const real2d& pmid, const real2d& pint, const real2d& tmid, const real2d& tint, - const real3d& cld_tau_gpt, const real3d& aer_tau_bnd, - FluxesByband& fluxes_clrsky, FluxesByband& fluxes_allsky, - const real2d& qrl, const real2d& qrlc); - - void radiation_driver_sw (int ncol, - const real3d& gas_vmr, const real2d& pmid, const real2d& pint, const real2d& tmid, - const real2d& albedo_dir, const real2d& albedo_dif, const real1d& coszrs, - const real3d& cld_tau_gpt, const real3d& cld_ssa_gpt, const real3d& cld_asm_gpt, - const real3d& aer_tau_bnd, const real3d& aer_ssa_bnd, const real3d& aer_asm_bnd, - FluxesByband& fluxes_clrsky, FluxesByband& fluxes_allsky, - const real2d& qrs, const real2d& qrsc); - - void set_daynight_indices (const real1d& coszrs, - const int1d& day_indices, - const int1d& night_indices); - - void get_gas_vmr (const std::vector& gas_names, - const real3d& gas_vmr); - - void calculate_heating_rate (const real2d& flux_up, - const real2d& flux_dn, - const real2d& pint, - const real2d& heating_rate); + // Set the grid info for columnar data in YAKL + void + set_grids (int& level, + int& step, + const amrex::Real& time, + const amrex::Real& dt, + const amrex::BoxArray& ba, + const amrex::Geometry& geom, + const amrex::MultiFab* cons_in, + amrex::MultiFab* lsm_fluxes, + amrex::MultiFab* lsm_zenith, + amrex::MultiFab* qheating_rates, + amrex::MultiFab* z_phys, + amrex::MultiFab* lat, + amrex::MultiFab* lon); + + // Initialize the temporary variables + void + alloc_buffers (); + + // Clear the temporary variables + void + dealloc_buffers (); + + // Fill YAKL Arrays from AMReX MultiFabs + void + mf_to_yakl_buffers(); + + // Fill AMReX MultiFabs from YAKL Arrays void - export_surface_fluxes(FluxesByband& fluxes, - std::string band); + yakl_buffers_to_mf (); - void yakl_to_mf(const real2d &data, amrex::MultiFab &mf); - void expand_yakl1d_to_mf(const real1d &data, amrex::MultiFab &mf); + // Initialize the implementation + void + initialize_impl (); + + // Run the implementation + void + run_impl (); + + // Finalize the implementation + void + finalize_impl (); + + // Wrapper for implementation steps + void + rad_run_impl () + { + if (m_update_radiation) { + this->initialize_impl(); + this->run_impl(); + this->finalize_impl(); + } + } + +private: - void writePlotfile(const std::string& plot_prefix, const amrex::Real time, const int level_step); + // Process interface vars from ERF/AMReX + //=================================================================================== - private: - // geometry + // Grid level + int m_lev; + + // Step number + int m_step; + + // Timestep at given level + amrex::Real m_dt; + + // Geometry at given level amrex::Geometry m_geom; - // valid boxes on which to evolve the solution - amrex::BoxArray m_box; + // Boxarray at given level + amrex::BoxArray m_ba; + + // Are we updating radiation? + bool m_update_rad = false; + + // Do we have moisture and cold comps? + bool m_moist = false; + bool m_ice = false; + + // Pointer to the CC conserved vars + amrex::MultiFab* m_cons_in = nullptr; // Pointer to the radiation source terms - amrex::MultiFab* qrad_src = nullptr; + amrex::MultiFab* m_qrad_src = nullptr; + + // Pointer to the terrain heights + amrex::MultiFab* m_z_phys = nullptr; // Pointer to latitude and longitude amrex::MultiFab* m_lat = nullptr; @@ -118,163 +145,170 @@ class Radiation { amrex::MultiFab* m_lsm_fluxes = nullptr; amrex::MultiFab* m_lsm_zenith = nullptr; - std::string moisture_type = "None"; - bool has_qmoist; - - amrex::Vector rank_offsets; - - // Specified uniform angle for radiation - amrex::Real uniform_angle = 78.463; - - // Orbital properties (constant for now) - static constexpr amrex::Real eccen = 0.0; // Earth's eccentricity factor (unitless) (typically 0 to 0.1) - static constexpr amrex::Real obliqr = 23.0 * PI / 180.0; // Earth's obliquity in radians - static constexpr amrex::Real mvelpp = 103.0 * PI / 180.0 + PI; // Earth's moving vernal equinox longitude of perihelion plus pi (radians) - static constexpr amrex::Real lambm0 = -3.2503635878519378e-2; // Mean longitude of perihelion at the vernal equinox (radians) - - // number of vertical levels - int nlev, zlo, zhi; - - // number of columns in horizontal plane - int ncol; - - int nlwgpts, nswgpts; - int nlwbands, nswbands; - - // radiation options - bool do_short_wave_rad; - bool do_long_wave_rad; - bool do_snow_optics; - - // Flag to indicate whether to do aerosol optical calculations. This - // zeroes out the aerosol optical properties if False - bool do_aerosol_rad = true; - - // rrtmgp - Rrtmgp radiation; - - // optics radiation properties - Optics optics; - - // aerosol optics properties - AerRadProps aer_rad; - - // input/output of radiation model - // - // Net flux calculated in this routine; used to check energy conservation in - // the physics package driver? - real1d net_flux; - - // This should be module data or something specific to aerosol where it is used? - bool is_cmip6_volc; // true if cmip6 style volcanic file is read otherwise false - - real dt; // time step(s) - needed for aerosol optics call - - // Surface and top fluxes - real1d fsns; // Surface solar absorbed flux - real1d fsnt; // Net column abs solar flux at model top - real1d flns; // Srf longwave cooling (up-down) flux - real1d flnt; // Net outgoing lw flux at model top - real1d fsds; // Surface solar down flux - - // radiation data - const std::vector active_gases = { - "H2O", "CO2", "O3", "N2O", - "CO" , "CH4", "O2", "N2" }; - - bool spectralflux = false; // calculate fluxes (up and down) per band. - - // Flag to indicate whether or not to use the radiation timestep for solar zenith - // angle calculations. If true, use the radiation timestep for all solar zenith - // angle (cosz) calculations. - // TODO: How does this differ if value is .false.? - bool use_rad_dt_cosz = false; - - // Value for prescribing an invariant solar constant (i.e. total solar - // irradiance at TOA). Used for idealized experiments such as RCE. - // Disabled when value is less than 0. - real fixed_total_solar_irradiance = -1.; - - // The RRTMGP warnings are printed when the state variables need to be limited, - // such as when the temperature drops too low. This is not normally an issue, - // but in aquaplanet and RCE configurations these situations occur much more - // frequently, so this flag was added to be able to disable those messages. - bool rrtmgp_enable_temperature_warnings = true; - - // Output diagnostic brightness temperatures at the top of the - // atmosphere for 7 TOVS/HIRS channels (2,4,6,8,10,11,12) and 4 TOVS/MSU - // channels (1,2,3,4). - // TODO: where are these options set? - bool dohirs = false; - int ihirsfq = 1; // frequency (timesteps) of brightness temperature calcs - - // time step to use for the shr_orb_cosz calculation, if use_rad_dt_cosz set to true - // TODO: where is this set, and what is shr_orb_cosz? Alternative solar zenith - // angle calculation? What is the other behavior? - real dt_avg = 0.0; - - // k-distribution coefficients files to read from. These are set via namelist - // variables. - std::string rrtmgp_data_path; - std::string rrtmgp_coefficients_file_sw; - std::string rrtmgp_coefficients_file_lw; - std::string rrtmgp_coefficients_file_name_sw = "rrtmgp_coefficients_sw_20181204.nc"; - std::string rrtmgp_coefficients_file_name_lw = "rrtmgp_coefficients_lw_20181204.nc"; - - // Band midpoints; these need to be module variables because of how cam_history works; - // add_hist_coord sets up pointers to these, so they need to persist. - real1d sw_band_midpoints; - real1d lw_band_midpoints; - - // rad constituents mixing ratios - int ngas, naer; - std::vector gasnames; - std::vector aernames; - - int1d rrtmg_to_rrtmgp; - - // Pointers to heating rates on physics buffer - real2d qrs; // shortwave radiative heating rate - real2d qrl; // longwave radiative heating rate - - // Pointers to fields on the physics buffer - real2d zi; - real2d clear_rh; - - // Clear-sky heating rates are not on the physics buffer, and we have no - // reason to put them there, so declare these are regular arrays here - real2d qrsc; - real2d qrlc; - - // moisture inputs from ERF - real2d qt, qi, qc, qn; // [ncol, nlev] - real2d tmid, pmid, pdel; // [ncol, nlev] - real2d pint, tint; // [ncol, nlev+1] - real2d albedo_dir, albedo_dif; // [nswbands, ncol] - - // timestep variables -- defined during each call to run() - real1d coszrs; // [ncol] - real2d cld, cldfsnow, iclwp, iciwp, icswp, dei, des, lambdac, mu, rei, rel; // [ncol, nlev] - // cloud, snow, and aerosol optical properties - real3d cld_tau_gpt_sw, cld_ssa_gpt_sw, cld_asm_gpt_sw; // [ncol, nlev, nswgpts] - real3d cld_tau_bnd_sw, cld_ssa_bnd_sw, cld_asm_bnd_sw; // [ncol, nlev, nswbands] - real3d aer_tau_bnd_sw, aer_ssa_bnd_sw, aer_asm_bnd_sw; // [ncol, nlev, nswbands] - real3d cld_tau_bnd_lw, aer_tau_bnd_lw; // [ncol, nlev, nlwbands] - real3d cld_tau_gpt_lw; // [ncol, nlev, nlwgpts] - // diagnostic only: - real3d liq_tau_bnd_sw, ice_tau_bnd_sw, snw_tau_bnd_sw; // [ncol, nlev, nswbands] - real3d liq_tau_bnd_lw, ice_tau_bnd_lw, snw_tau_bnd_lw; // [ncol, nlev, nlwbands] - // gas volume mixing ratios - real3d gas_vmr; // [ngas, ncol, nlev] - int1d gpoint_bands_sw, gpoint_bands_lw; // [nswgpts], [nlwgpts] - // shortwave radiation vars: - FluxesByband sw_fluxes_allsky, sw_fluxes_clrsky; - real1d cld_tau_bnd_sw_1d, cld_ssa_bnd_sw_1d, cld_asm_bnd_sw_1d; // [nswbands] - real1d cld_tau_bnd_sw_o_1d, cld_ssa_bnd_sw_o_1d, cld_asm_bnd_sw_o_1d; // [nswbands] - // aerosol optics: - real1d aer_tau_bnd_sw_1d, aer_ssa_bnd_sw_1d, aer_asm_bnd_sw_1d; // [nswbands] - real1d aer_tau_bnd_sw_o_1d, aer_ssa_bnd_sw_o_1d, aer_asm_bnd_sw_o_1d; // [nswbands] - // longwave radiation vars: - FluxesByband lw_fluxes_allsky, lw_fluxes_clrsky; -}; -#endif // ERF_RADIATION_H + // Path, data file, and coefficient file for K-distribution + std::string rrtmgp_file_path = "."; + std::string rrtmgp_coeffs_sw = "rrtmgp-data-sw-g112-210809.nc"; + std::string rrtmgp_coeffs_lw = "rrtmgp-data-lw-g128-210809.nc"; + std::string rrtmgp_cloud_optics_sw = "rrtmgp-cloud-optics-coeffs-sw.nc"; + std::string rrtmgp_cloud_optics_lw = "rrtmgp-cloud-optics-coeffs-lw.nc"; + std::string rrtmgp_coeffs_file_sw; + std::string rrtmgp_coeffs_file_lw; + std::string rrtmgp_cloud_optics_file_sw; + std::string rrtmgp_cloud_optics_file_lw; + + // TODO: Are these parsed from the inputs file in EAMXX? + // Active gases + int m_ngas = 8; + const std::vector m_gas_names = {"H2O", "CO2", "O3", "N2O", + "CO" , "CH4", "O2", "N2" }; + const std::vector m_mol_weight_gas = {18.01528, 44.00950, 47.9982, 44.0128, + 28.01010, 16.04246, 31.9980, 28.0134}; // g/mol + real1d m_gas_mol_weights; + string1d gas_names_yakl_offset; + GasConcs m_gas_concs; + + // Process interface vars modeled after EAMXX + //=================================================================================== + + // Keep track of number of columns and levels + int m_ncol; + int m_nlay; + + // Offsets for MultiFab <-> YAKL transfer + amrex::Vector m_col_offsets; + + // Whether we use aerosol forcing in radiation + bool m_do_aerosol_rad = true; + + // Whether we do extra aerosol forcing calls + bool m_extra_clnsky_diag = false; + bool m_extra_clnclrsky_diag = false; + + // The orbital year, used for zenith angle calculations: + // If > 0, use constant orbital year for duration of simulation + // If < 0, use year from timestamp for orbital parameters + int m_orbital_year = -9999; + int m_orbital_mon = -9999; + int m_orbital_day = -9999; + + // Orbital parameters, used for zenith angle calculations. + // If >= 0, bypass computation based on orbital year and use fixed parameters + // If < 0, compute based on orbital year, specified above + bool m_fixed_orbital_year = false; + amrex::Real m_orbital_eccen = -9999.; // Eccentricity + amrex::Real m_orbital_obliq = -9999.; // Obliquity + amrex::Real m_orbital_mvelp = -9999.; // Vernal Equinox Mean Longitude of Perihelion + + // Value for prescribing an invariant solar constant (i.e. total solar irradiance + // at TOA). Used for idealized experiments such as RCE. This is only used when a + // positive value is supplied. + amrex::Real m_fixed_total_solar_irradiance = -9999.; + + // Fixed solar zenith angle to use for shortwave calculations + // This is only used if a positive value is supplied + amrex::Real m_fixed_solar_zenith_angle = -9999.; + + // Need to hard-code some dimension sizes for now. + // TODO: find a better way of configuring this + int m_nswbands = 14; + int m_nlwbands = 16; + int m_nswgpts = 112; + int m_nlwgpts = 128; + + // Prescribed greenhouse gas surface concentrations in moles / moles air + amrex::Real m_o3vmr = 1.8868676125307193E-7; + amrex::Real m_co2vmr = 388.717e-6; + amrex::Real m_n2ovmr = 323.141e-9; + amrex::Real m_ch4vmr = 1807.851e-9; + amrex::Real m_f11vmr = 768.7644e-12; + amrex::Real m_f12vmr = 531.2820e-12; + amrex::Real m_n2vmr = 0.7906; + amrex::Real m_covmr = 1.0e-7; + + // Rad frequency in number of steps + int m_rad_freq_in_steps = 1; + + // Whether or not to do subcolumn sampling of cloud state for MCICA + bool m_do_subcol_sampling = true; + + // 1d size (ncol) + real1d cosine_zenith; + real1d mu0; + real1d sfc_alb_dir_vis; + real1d sfc_alb_dir_nir; + real1d sfc_alb_dif_vis; + real1d sfc_alb_dif_nir; + real1d sfc_flux_dir_vis; + real1d sfc_flux_dir_nir; + real1d sfc_flux_dif_vis; + real1d sfc_flux_dif_nir; + + // 2d size (ncol, nlay) + real2d d_dz; + real2d r_lay; + real2d p_lay; + real2d t_lay; + real2d z_del; + real2d p_del; + real2d qc; + real2d nc; + real2d qi; + real2d cldfrac_tot; + real2d eff_radius_qc; + real2d eff_radius_qi; + real2d tmp2d; + real2d lwp; + real2d iwp; + real2d sw_heating; + real2d lw_heating; + + // 2d size (ncol, nlay+1) + real2d d_tint; + real2d p_lev; + real2d t_lev; + real2d sw_flux_up; + real2d sw_flux_dn; + real2d sw_flux_dn_dir; + real2d lw_flux_up; + real2d lw_flux_dn; + real2d sw_clnclrsky_flux_up; + real2d sw_clnclrsky_flux_dn; + real2d sw_clnclrsky_flux_dn_dir; + real2d sw_clrsky_flux_up; + real2d sw_clrsky_flux_dn; + real2d sw_clrsky_flux_dn_dir; + real2d sw_clnsky_flux_up; + real2d sw_clnsky_flux_dn; + real2d sw_clnsky_flux_dn_dir; + real2d lw_clnclrsky_flux_up; + real2d lw_clnclrsky_flux_dn; + real2d lw_clrsky_flux_up; + real2d lw_clrsky_flux_dn; + real2d lw_clnsky_flux_up; + real2d lw_clnsky_flux_dn; + + // 3d size (ncol, nlay+1, nswbands) + real3d sw_bnd_flux_up; + real3d sw_bnd_flux_dn; + real3d sw_bnd_flux_dir; + real3d sw_bnd_flux_dif; + + // 2d size (ncol, nswbands) + real2d sfc_alb_dir; + real2d sfc_alb_dif; + + // 3d size (ncol, nlay, n[sw,lw]bands) + real3d aero_tau_sw; + real3d aero_ssa_sw; + real3d aero_g_sw; + real3d aero_tau_lw; + + // 3d size (ncol, nlay, n[sw,lw]bnds) + real3d cld_tau_sw_bnd; + real3d cld_tau_lw_bnd; + + // 3d size (ncol, nlay, n[sw,lw]gpts) + real3d cld_tau_sw_gpt; + real3d cld_tau_lw_gpt; +} + diff --git a/Source/Radiation/ERF_Radiation.cpp b/Source/Radiation/ERF_Radiation.cpp index 062bb92be..94ffb8876 100644 --- a/Source/Radiation/ERF_Radiation.cpp +++ b/Source/Radiation/ERF_Radiation.cpp @@ -11,1332 +11,694 @@ * The RTE-RRTMGP uses BSD-3-Clause Open Source License, if you want to make changes, * and modifications to the code, please refer to BSD-3-Clause Open Source License. */ -#include -#include -#include - -#include "ERF_Radiation.H" -#include "ERF_m2005_effradius.H" -#include -#include -#include -#include -#include -#include -#include "ERF_Constants.H" -#include "ERF_IndexDefines.H" -#include "ERF_DataStruct.H" -#include "ERF_EOS.H" -#include "ERF_TileNoZ.H" -#include "ERF_Orbit.H" + +#include using namespace amrex; using yakl::intrinsics::size; using yakl::fortran::parallel_for; using yakl::fortran::SimpleBounds; -namespace internal { - void initial_fluxes (int nz, int nlay, int nbands, FluxesByband& fluxes) - { - fluxes.flux_up = real2d("flux_up" , nz, nlay+1); - fluxes.flux_dn = real2d("flux_dn" , nz, nlay+1); - fluxes.flux_net = real2d("flux_net" , nz, nlay+1); - fluxes.flux_dn_dir = real2d("flux_dn_dir", nz, nlay+1); - - fluxes.bnd_flux_up = real3d("flux_up" , nz, nlay+1, nbands); - fluxes.bnd_flux_dn = real3d("flux_dn" , nz, nlay+1, nbands); - fluxes.bnd_flux_net = real3d("flux_net" , nz, nlay+1, nbands); - fluxes.bnd_flux_dn_dir = real3d("flux_dn_dir", nz, nlay+1, nbands); - } - - void expand_day_fluxes (const FluxesByband& daytime_fluxes, - FluxesByband& expanded_fluxes, - const int1d& day_indices) - { - auto ncol = size(daytime_fluxes.bnd_flux_up, 1); - auto nlev = size(daytime_fluxes.bnd_flux_up, 2); - auto nbnds = size(daytime_fluxes.bnd_flux_up, 3); - - int1d nday_1d("nday_1d", 1),nday_host("nday_host",1); - yakl::memset(nday_1d, 0); - parallel_for(SimpleBounds<1>(ncol), YAKL_LAMBDA (int icol) - { - if (day_indices(icol) > 0) nday_1d(1)++; - //printf("daynight indices(check): %d, %d, %d\n",icol,day_indices(icol),nday_1d(1)); - }); - - nday_1d.deep_copy_to(nday_host); - auto nday = nday_host(1); - AMREX_ASSERT_WITH_MESSAGE((nday>0) && (nday<=ncol), "RADIATION: Invalid number of days!"); - parallel_for(SimpleBounds<3>(nday, nlev, nbnds), YAKL_LAMBDA (int iday, int ilev, int ibnd) - { - // Map daytime index to proper column index - auto icol = day_indices(iday); - //auto icol = iday; - // Expand broadband fluxes - expanded_fluxes.flux_up(icol,ilev) = daytime_fluxes.flux_up(iday,ilev); - expanded_fluxes.flux_dn(icol,ilev) = daytime_fluxes.flux_dn(iday,ilev); - expanded_fluxes.flux_net(icol,ilev) = daytime_fluxes.flux_net(iday,ilev); - expanded_fluxes.flux_dn_dir(icol,ilev) = daytime_fluxes.flux_dn_dir(iday,ilev); - - // Expand band-by-band fluxes - expanded_fluxes.bnd_flux_up(icol,ilev,ibnd) = daytime_fluxes.bnd_flux_up(iday,ilev,ibnd); - expanded_fluxes.bnd_flux_dn(icol,ilev,ibnd) = daytime_fluxes.bnd_flux_dn(iday,ilev,ibnd); - expanded_fluxes.bnd_flux_net(icol,ilev,ibnd) = daytime_fluxes.bnd_flux_net(iday,ilev,ibnd); - expanded_fluxes.bnd_flux_dn_dir(icol,ilev,ibnd) = daytime_fluxes.bnd_flux_dn_dir(iday,ilev,ibnd); - }); - } - - // Utility function to reorder an array given a new indexing - void reordered (const real1d& array_in, const int1d& new_indexing, const real1d& array_out) - { - // Reorder array based on input index mapping, which maps old indices to new - parallel_for(SimpleBounds<1>(size(array_in, 1)), YAKL_LAMBDA (int i) - { - array_out(i) = array_in(new_indexing(i)); - }); - } -} -// init -void Radiation::initialize (const MultiFab& cons_in, - MultiFab* lsm_fluxes, - MultiFab* lsm_zenith, - MultiFab* qheating_rates, - MultiFab* lat, - MultiFab* lon, - Vector qmoist, - const BoxArray& grids, - const Geometry& geom, - const Real& dt_advance, - const bool& do_sw_rad, - const bool& do_lw_rad, - const bool& do_aero_rad, - const bool& do_snow_opt, - const bool& is_cmip6_volcano) +Radiation::Radiation (SolverChoice& sc) { - m_geom = geom; - m_box = grids; + // Initialize YAKL + if (!yakl::isInitialized()) { yakl::init(); } - qrad_src = qheating_rates; + // Check if we have a valid moisture model + if (sc.moisture_type != MoistureType::None) { m_moist = true; } - auto dz = m_geom.CellSize(2); - auto lowz = m_geom.ProbLo(2); - - dt = dt_advance; - - do_short_wave_rad = do_sw_rad; - do_long_wave_rad = do_lw_rad; - do_aerosol_rad = do_aero_rad; - do_snow_optics = do_snow_opt; - is_cmip6_volc = is_cmip6_volcano; - - m_lat = lat; - m_lon = lon; - - m_lsm_fluxes = lsm_fluxes; - m_lsm_zenith = lsm_zenith; - - rrtmgp_data_path = getRadiationDataDir() + "/"; - rrtmgp_coefficients_file_sw = rrtmgp_data_path + rrtmgp_coefficients_file_name_sw; - rrtmgp_coefficients_file_lw = rrtmgp_data_path + rrtmgp_coefficients_file_name_lw; + // Check if we have a moisture model with ice + if (sc.moisture_type == MoistureType::SAM) { m_ice = true; } + // Construct parser object for following reads ParmParse pp("erf"); - pp.query("fixed_total_solar_irradiance", fixed_total_solar_irradiance); - pp.query("radiation_uniform_angle" , uniform_angle); - pp.query("moisture_model", moisture_type); // TODO: get from SolverChoice? - has_qmoist = (moisture_type != "None"); - - nlev = geom.Domain().length(2); - ncol = 0; - rank_offsets.resize(cons_in.local_size()); - for (MFIter mfi(cons_in, TileNoZ()); mfi.isValid(); ++mfi) { - const auto& box3d = mfi.tilebox(); - int nx = box3d.length(0); - int ny = box3d.length(1); - rank_offsets[mfi.LocalIndex()] = ncol; - ncol += nx * ny; - } - - ngas = active_gases.size(); - - // initialize cloud, aerosol, and radiation - radiation.initialize(ngas, active_gases, - rrtmgp_coefficients_file_sw.c_str(), - rrtmgp_coefficients_file_lw.c_str()); - - // initialize the radiation data - nswbands = radiation.get_nband_sw(); - nswgpts = radiation.get_ngpt_sw(); - nlwbands = radiation.get_nband_lw(); - nlwgpts = radiation.get_ngpt_lw(); - - rrtmg_to_rrtmgp = int1d("rrtmg_to_rrtmgp",14); - parallel_for(14, YAKL_LAMBDA (int i) - { - if (i == 1) { - rrtmg_to_rrtmgp(i) = 13; - } else { - rrtmg_to_rrtmgp(i) = i - 1; - } - }); - - tmid = real2d("tmid", ncol, nlev); - pmid = real2d("pmid", ncol, nlev); - pdel = real2d("pdel", ncol, nlev); - - pint = real2d("pint", ncol, nlev+1); - tint = real2d("tint", ncol, nlev+1); - - qt = real2d("qt", ncol, nlev); - qc = real2d("qc", ncol, nlev); - qi = real2d("qi", ncol, nlev); - qn = real2d("qn", ncol, nlev); - zi = real2d("zi", ncol, nlev); - - // Get the temperature, density, theta, qt and qp from input - for (MFIter mfi(cons_in, TileNoZ()); mfi.isValid(); ++mfi) { - const auto& box3d = mfi.tilebox(); - auto nx = box3d.length(0); - - auto states_array = cons_in.array(mfi); - auto qt_array = (has_qmoist) ? qmoist[0]->array(mfi) : Array4 {}; - auto qv_array = (has_qmoist) ? qmoist[1]->array(mfi) : Array4 {}; - auto qc_array = (has_qmoist) ? qmoist[2]->array(mfi) : Array4 {}; - auto qi_array = (has_qmoist && qmoist.size()>=8) ? qmoist[3]->array(mfi) : Array4 {}; - const int offset = rank_offsets[mfi.LocalIndex()]; - - // Get pressure, theta, temperature, density, and qt, qp - ParallelFor(box3d, [=] AMREX_GPU_DEVICE (int i, int j, int k) - { - auto icol = (j-box3d.smallEnd(1))*nx + (i-box3d.smallEnd(0)) + 1 + offset; - auto ilev = k+1; - Real qv = (qv_array) ? qv_array(i,j,k): 0.0; - qt(icol,ilev) = (qt_array) ? qt_array(i,j,k): 0.0; - qc(icol,ilev) = (qc_array) ? qc_array(i,j,k): 0.0; - qi(icol,ilev) = (qi_array) ? qi_array(i,j,k): 0.0; - qn(icol,ilev) = qc(icol,ilev) + qi(icol,ilev); - tmid(icol,ilev) = getTgivenRandRTh(states_array(i,j,k,Rho_comp),states_array(i,j,k,RhoTheta_comp),qv); - // NOTE: RRTMGP code expects pressure in pa - pmid(icol,ilev) = getPgivenRTh(states_array(i,j,k,RhoTheta_comp),qv); - }); - } - - parallel_for(SimpleBounds<2>(ncol, nlev+1), YAKL_LAMBDA (int icol, int ilev) - { - if (ilev == 1) { - pint(icol, 1) = -0.5*pmid(icol, 2) + 1.5*pmid(icol, 1); - tint(icol, 1) = -0.5*tmid(icol, 2) + 1.5*tmid(icol, 1); - } else if (ilev <= nlev) { - pint(icol, ilev) = 0.5*(pmid(icol, ilev-1) + pmid(icol, ilev)); - tint(icol, ilev) = 0.5*(tmid(icol, ilev-1) + tmid(icol, ilev)); - } else { - pint(icol, nlev+1) = -0.5*pmid(icol, nlev-1) + 1.5*pmid(icol, nlev); - tint(icol, nlev+1) = -0.5*tmid(icol, nlev-1) + 1.5*tmid(icol, nlev); - } - }); - - parallel_for(SimpleBounds<2>(ncol, nlev), YAKL_LAMBDA (int icol, int ilev) - { - zi(icol, ilev) = lowz + (ilev+0.5)*dz; - pdel(icol,ilev) = pint(icol,ilev+1) - pint(icol,ilev); - }); - - albedo_dir = real2d("albedo_dir", nswbands, ncol); - albedo_dif = real2d("albedo_dif", nswbands, ncol); - - qrs = real2d("qrs", ncol, nlev); // shortwave radiative heating rate - qrl = real2d("qrl", ncol, nlev); // longwave radiative heating rate - - // Clear-sky heating rates are not on the physics buffer, and we have no - // reason to put them there, so declare these are regular arrays here - qrsc = real2d("qrsc", ncol, nlev); - qrlc = real2d("qrlc", ncol, nlev); - - int nmodes = 3; - int nrh = 1; - int top_lev = 1; - naer = 4; - std::vector aero_names {"H2O", "N2", "O2", "O3"}; - auto geom_radius = real2d("geom_radius", ncol, nlev); - yakl::memset(geom_radius, 0.1); - - optics.initialize(ngas, nmodes, naer, nswbands, nlwbands, - ncol, nlev, nrh, top_lev, aero_names, zi, - pmid, pdel, tmid, qt, geom_radius); - - amrex::Print() << "LW coefficients file: " << rrtmgp_coefficients_file_lw - << "\nSW coefficients file: " << rrtmgp_coefficients_file_sw - << "\nFrequency (timesteps) of Shortwave Radiation calc: " << dt - << "\nFrequency (timesteps) of Longwave Radiation calc: " << dt - << "\nDo aerosol radiative calculations: " << do_aerosol_rad << std::endl; + // Radiation timestep, as a number of atm steps + pp.query("rad_freq_in_steps", m_rad_freq_in_steps); + + // Do MCICA subcolumn sampling + pp.query("do_subcol_sampling", m_do_subcol_sampling); + + // Determine orbital year. If orbital_year is negative, use current year + // from timestamp for orbital year; if positive, use provided orbital year + // for duration of simulation. + m_fixed_orbital_year = pp.query("orbital_year",m_orbital_year); + + // Get orbital parameters from yaml file + pp.query("orbital_eccentricity", m_orbital_eccen); + pp.query("orbital_obliquity" , m_orbital_obliq); + pp.query("orbital_mvelp" , m_orbital_mvelp); + + // Value for prescribing an invariant solar constant (i.e. total solar irradiance at + // TOA). Used for idealized experiments such as RCE. Disabled when value is less than 0. + pp.query("fixed_total_solar_irradiance", m_fixed_total_solar_irradiance); + + // Determine whether or not we are using a fixed solar zenith angle (positive value) + pp.query("Fixed Solar Zenith Angle", m_fixed_solar_zenith_angle); + + // Get prescribed surface values of greenhouse gases + pp.query("o3vmr" , m_o3vmr ); + pp.query("co2vmr", m_co2vmr); + pp.query("n2ovmr", m_n2ovmr); + pp.query("ch4vmr", m_ch4vmr); + pp.query("f11vmr", m_f11vmr); + pp.query("f12vmr", m_f12vmr); + pp.query("n2vmr" , m_n2vmr ); + pp.query("covmr" , m_covmr ); + + // Required aerosol optical properties from SPA + pp.query("do_aerosol_rad", m_do_aerosol_rad); + + // Whether we do extra clean/clear sky calculations + pp.query("extra_clnclrsky_diag", m_extra_clnclrsky_diag); + pp.query("extra_clnsky_diag" , m_extra_clnsky_diag); + + // Parse path and file names + pp.query("rrtmgp_file_path" , rrtmgp_file_path); + pp.query("rrtmgp_coeffs_sw" , rrtmgp_coeffs_sw ); + pp.query("rrtmgp_coeffs_lw" , rrtmgp_coeffs_lw ); + pp.query("rrtmgp_cloud_optics_sw", rrtmgp_cloud_optics_sw); + pp.query("rrtmgp_cloud_optics_lw", rrtmgp_cloud_optics_lw); + + // Append file names to path + rrtmgp_coeffs_file_sw = rrtmgp_file_path + "/" + rrtmgp_coeffs_sw; + rrtmgp_coeffs_file_lw = rrtmgp_file_path + "/" + rrtmgp_coeffs_lw; + rrtmgp_cloud_optics_file_sw = rrtmgp_file_path + "/" + rrtmgp_cloud_optics_sw; + rrtmgp_cloud_optics_file_lw = rrtmgp_file_path + "/" + rrtmgp_cloud_optics_lw; } +void +Radiation::set_grids (int& level, + int& step, + const amrex::Real& time, + const amrex::Real& dt, + const amrex::BoxArray& ba, + const amrex::Geometry& geom, + const amrex::MultiFab* cons_in, + amrex::MultiFab* lsm_fluxes, + amrex::MultiFab* lsm_zenith, + amrex::MultiFab* qheating_rates, + amrex::MultiFab* z_phys, + amrex::MultiFab* lat, + amrex::MultiFab* lon) -// run radiation model -void Radiation::run () { - // Cosine solar zenith angle for all columns in chunk - coszrs = real1d("coszrs", ncol); - - // Pointers to fields on the physics buffer - cld = real2d("cld", ncol, nlev); - cldfsnow = real2d("cldfsnow", ncol, nlev); - iclwp = real2d("iclwp", ncol, nlev); - iciwp = real2d("iciwp", ncol, nlev); - icswp = real2d("icswp", ncol, nlev); - dei = real2d("dei", ncol, nlev); - des = real2d("des", ncol, nlev); - lambdac = real2d("lambdac", ncol, nlev); - mu = real2d("mu", ncol, nlev); - rei = real2d("rei", ncol, nlev); - rel = real2d("rel", ncol, nlev); - - // Cloud, snow, and aerosol optical properties - cld_tau_gpt_sw = real3d("cld_tau_gpt_sw", ncol, nlev, nswgpts); - cld_ssa_gpt_sw = real3d("cld_ssa_gpt_sw", ncol, nlev, nswgpts); - cld_asm_gpt_sw = real3d("cld_asm_gpt_sw", ncol, nlev, nswgpts); - - cld_tau_bnd_sw = real3d("cld_tau_bnd_sw", ncol, nlev, nswbands); - cld_ssa_bnd_sw = real3d("cld_ssa_bnd_sw", ncol, nlev, nswbands); - cld_asm_bnd_sw = real3d("cld_asm_bnd_sw", ncol, nlev, nswbands); - - aer_tau_bnd_sw = real3d("aer_tau_bnd_sw", ncol, nlev, nswbands); - aer_ssa_bnd_sw = real3d("aer_ssa_bnd_sw", ncol, nlev, nswbands); - aer_asm_bnd_sw = real3d("aer_asm_bnd_sw", ncol, nlev, nswbands); - - cld_tau_bnd_lw = real3d("cld_tau_bnd_lw", ncol, nlev, nlwbands); - aer_tau_bnd_lw = real3d("aer_tau_bnd_lw", ncol, nlev, nlwbands); - - cld_tau_gpt_lw = real3d("cld_tau_gpt_lw", ncol, nlev, nlwgpts); - - // NOTE: these are diagnostic only - liq_tau_bnd_sw = real3d("liq_tau_bnd_sw", ncol, nlev, nswbands); - ice_tau_bnd_sw = real3d("ice_tau_bnd_sw", ncol, nlev, nswbands); - snw_tau_bnd_sw = real3d("snw_tau_bnd_sw", ncol, nlev, nswbands); - liq_tau_bnd_lw = real3d("liq_tau_bnd_lw", ncol, nlev, nlwbands); - ice_tau_bnd_lw = real3d("ice_tau_bnd_lw", ncol, nlev, nlwbands); - snw_tau_bnd_lw = real3d("snw_tau_bnd_lw", ncol, nlev, nlwbands); - - // Gas volume mixing ratios - gas_vmr = real3d("gas_vmr", ngas, ncol, nlev); - - // Needed for shortwave aerosol; - //int nday, nnight; // Number of daylight columns - int1d day_indices("day_indices", ncol), night_indices("night_indices", ncol); // Indices of daylight coumns - - // Flag to carry (QRS,QRL)*dp across time steps. - // TODO: what does this mean? - bool conserve_energy = true; - - // For loops over diagnostic calls - //bool active_calls(0:N_DIAG) - - // Zero-array for cloud properties if not diagnosed by microphysics - real2d zeros("zeros", ncol, nlev); - - gpoint_bands_sw = int1d("gpoint_bands_sw", nswgpts); - gpoint_bands_lw = int1d("gpoint_bands_lw", nlwgpts); - - // Do shortwave stuff... - if (do_short_wave_rad) { - // Radiative fluxes - internal::initial_fluxes(ncol, nlev+1, nswbands, sw_fluxes_allsky); - internal::initial_fluxes(ncol, nlev+1, nswbands, sw_fluxes_clrsky); - - // TODO: Integrate calendar day computation - int calday = 1; - // Get cosine solar zenith angle for current time step. - if (m_lat) { - zenith(calday, m_lat, m_lon, rank_offsets, coszrs, ncol, - eccen, mvelpp, lambm0, obliqr); - } else { - zenith(calday, m_lat, m_lon, rank_offsets, coszrs, ncol, - eccen, mvelpp, lambm0, obliqr, uniform_angle); - } - - // Get albedo. This uses CAM routines internally and just provides a - // wrapper to improve readability of the code here. - set_albedo(coszrs, albedo_dir, albedo_dif); - - // Do shortwave cloud optics calculations - yakl::memset(cld_tau_gpt_sw, 0.); - yakl::memset(cld_ssa_gpt_sw, 0.); - yakl::memset(cld_asm_gpt_sw, 0.); - - // set cloud fraction to be 1, and snow fraction 0 - yakl::memset(cldfsnow, 0.0); - yakl::memset(cld, 1.0); - - parallel_for (SimpleBounds<2>(ncol, nlev), YAKL_LAMBDA (int i, int k) - { - iciwp(i,k) = std::min(qi(i,k)/std::max(1.0e-4,cld(i,k)),0.005)*pmid(i,k)/CONST_GRAV; - iclwp(i,k) = std::min(qt(i,k)/std::max(1.0e-4,cld(i,k)),0.005)*pmid(i,k)/CONST_GRAV; - icswp(i,k) = qn(i,k)/std::max(1.0e-4,cldfsnow(i,k))*pmid(i,k)/CONST_GRAV; - }); - - m2005_effradius(qc, qc, qi, qi, qt, qt, cld, pmid, tmid, - rel, rei, dei, lambdac, mu, des); - - // calculate the cloud radiation - optics.get_cloud_optics_sw(ncol, nlev, nswbands, do_snow_optics, cld, - cldfsnow, iclwp, iciwp, icswp, - lambdac, mu, dei, des, rel, rei, - cld_tau_bnd_sw, cld_ssa_bnd_sw, cld_asm_bnd_sw, - liq_tau_bnd_sw, ice_tau_bnd_sw, snw_tau_bnd_sw); - - // Now reorder bands to be consistent with RRTMGP - // We need to fix band ordering because the old input files assume RRTMG - // band ordering, but this has changed in RRTMGP. - // TODO: fix the input files themselves! - cld_tau_bnd_sw_1d = real1d("cld_tau_bnd_sw_1d", nswbands); - cld_ssa_bnd_sw_1d = real1d("cld_ssa_bnd_sw_1d", nswbands); - cld_asm_bnd_sw_1d = real1d("cld_asm_bnd_sw_1d", nswbands); - cld_tau_bnd_sw_o_1d = real1d("cld_tau_bnd_sw_1d", nswbands); - cld_ssa_bnd_sw_o_1d = real1d("cld_ssa_bnd_sw_1d", nswbands); - cld_asm_bnd_sw_o_1d = real1d("cld_asm_bnd_sw_1d", nswbands); - - parallel_for(SimpleBounds<2>(ncol, nlev), YAKL_LAMBDA (int icol, int ilay) - { - for (auto ibnd = 1; ibnd <= nswbands; ++ibnd) { - cld_tau_bnd_sw_1d(ibnd) = cld_tau_bnd_sw(icol,ilay,ibnd); - cld_ssa_bnd_sw_1d(ibnd) = cld_ssa_bnd_sw(icol,ilay,ibnd); - cld_asm_bnd_sw_1d(ibnd) = cld_asm_bnd_sw(icol,ilay,ibnd); - } - internal::reordered(cld_tau_bnd_sw_1d, rrtmg_to_rrtmgp, cld_tau_bnd_sw_o_1d); - internal::reordered(cld_ssa_bnd_sw_1d, rrtmg_to_rrtmgp, cld_ssa_bnd_sw_o_1d); - internal::reordered(cld_asm_bnd_sw_1d, rrtmg_to_rrtmgp, cld_asm_bnd_sw_o_1d); - for (auto ibnd = 1; ibnd <= nswbands; ++ibnd) { - cld_tau_bnd_sw(icol,ilay,ibnd) = cld_tau_bnd_sw_o_1d(ibnd); - cld_ssa_bnd_sw(icol,ilay,ibnd) = cld_ssa_bnd_sw_o_1d(ibnd); - cld_asm_bnd_sw(icol,ilay,ibnd) = cld_asm_bnd_sw_o_1d(ibnd); - } - }); - - // And now do the MCICA sampling to get cloud optical properties by - // gpoint/cloud state - radiation.get_gpoint_bands_sw(gpoint_bands_sw); - - optics.sample_cloud_optics_sw(ncol, nlev, nswgpts, gpoint_bands_sw, - pmid, cld, cldfsnow, - cld_tau_bnd_sw, cld_ssa_bnd_sw, cld_asm_bnd_sw, - cld_tau_gpt_sw, cld_ssa_gpt_sw, cld_asm_gpt_sw); - - // Aerosol needs night indices - // TODO: remove this dependency, it's just used to mask aerosol outputs - set_daynight_indices(coszrs, day_indices, night_indices); - int1d nday("nday",1); - int1d nnight("nnight",1); - yakl::memset(nday, 0); - yakl::memset(nnight, 0); - for (auto icol=1; icol<=ncol; ++icol) { - if (day_indices(icol) > 0) nday(1)++; - if (night_indices(icol) > 0) nnight(1)++; - } - - AMREX_ALWAYS_ASSERT(nday(1) + nnight(1) == ncol); - - // get aerosol optics - do_aerosol_rad = false; // TODO: this causes issues if enabled - { - // Get gas concentrations - get_gas_vmr(active_gases, gas_vmr); - - // Get aerosol optics - if (do_aerosol_rad) { - yakl::memset(aer_tau_bnd_sw, 0.); - yakl::memset(aer_ssa_bnd_sw, 0.); - yakl::memset(aer_asm_bnd_sw, 0.); - - clear_rh = real2d("clear_rh",ncol, nswbands); - yakl::memset(clear_rh, 0.01); - - optics.set_aerosol_optics_sw(0, ncol, nlev, nswbands, dt, night_indices, - is_cmip6_volc, aer_tau_bnd_sw, aer_ssa_bnd_sw, aer_asm_bnd_sw, clear_rh); - - // Now reorder bands to be consistent with RRTMGP - // TODO: fix the input files themselves! - aer_tau_bnd_sw_1d = real1d("cld_tau_bnd_sw_1d", nswbands); - aer_ssa_bnd_sw_1d = real1d("cld_ssa_bnd_sw_1d", nswbands); - aer_asm_bnd_sw_1d = real1d("cld_asm_bnd_sw_1d", nswbands); - aer_tau_bnd_sw_o_1d = real1d("cld_tau_bnd_sw_1d", nswbands); - aer_ssa_bnd_sw_o_1d = real1d("cld_ssa_bnd_sw_1d", nswbands); - aer_asm_bnd_sw_o_1d = real1d("cld_asm_bnd_sw_1d", nswbands); - - parallel_for(SimpleBounds<2>(ncol, nlev), YAKL_LAMBDA (int icol, int ilay) - { - for (auto ibnd = 1; ibnd <= nswbands; ++ibnd) { - aer_tau_bnd_sw_1d(ibnd) = aer_tau_bnd_sw(icol,ilay,ibnd); - aer_ssa_bnd_sw_1d(ibnd) = aer_ssa_bnd_sw(icol,ilay,ibnd); - aer_asm_bnd_sw_1d(ibnd) = aer_asm_bnd_sw(icol,ilay,ibnd); - } - internal::reordered(aer_tau_bnd_sw_1d, rrtmg_to_rrtmgp, aer_tau_bnd_sw_o_1d); - internal::reordered(aer_ssa_bnd_sw_1d, rrtmg_to_rrtmgp, aer_ssa_bnd_sw_o_1d); - internal::reordered(aer_asm_bnd_sw_1d, rrtmg_to_rrtmgp, aer_asm_bnd_sw_o_1d); - for (auto ibnd = 1; ibnd <= nswbands; ++ibnd) { - aer_tau_bnd_sw(icol,ilay,ibnd) = aer_tau_bnd_sw_o_1d(ibnd); - aer_ssa_bnd_sw(icol,ilay,ibnd) = aer_ssa_bnd_sw_o_1d(ibnd); - aer_asm_bnd_sw(icol,ilay,ibnd) = aer_asm_bnd_sw_o_1d(ibnd); - } - }); - } else { - yakl::memset(aer_tau_bnd_sw, 0.); - yakl::memset(aer_ssa_bnd_sw, 0.); - yakl::memset(aer_asm_bnd_sw, 0.); - } - - yakl::memset(cld_tau_gpt_sw, 0.); - yakl::memset(cld_ssa_gpt_sw, 0.); - yakl::memset(cld_asm_gpt_sw, 0.); - - // Call the shortwave radiation driver - radiation_driver_sw(ncol, gas_vmr, - pmid, pint, tmid, albedo_dir, albedo_dif, coszrs, - cld_tau_gpt_sw, cld_ssa_gpt_sw, cld_asm_gpt_sw, - aer_tau_bnd_sw, aer_ssa_bnd_sw, aer_asm_bnd_sw, - sw_fluxes_allsky, sw_fluxes_clrsky, qrs, qrsc); - } - - // Set surface fluxes that are used by the land model - export_surface_fluxes(sw_fluxes_allsky, "shortwave"); - + // Reset data members for AMR + m_lev = level; + m_step = step; + m_dt = dt; + m_geom = geom; + m_cons_in = cons_in; + m_lsm_fluxes = lsm_fluxes; + m_qheating_rates = qheating_rates; + m_z_phys = z_phys; + m_lat = lat; + m_lon = lon; + + // Update the day and month + time_t timestamp = time_t(int(time)); + struct tm *timeinfo = localtime(×tamp); + if (m_fixed_orbital_year) { + m_orbital_mon = timeinfo->tm_mon + 1; + m_orbital_day = timeinfo->tm_mday; } else { - // Conserve energy - if (conserve_energy) { - parallel_for(SimpleBounds<2>(ncol, nlev), YAKL_LAMBDA (int icol, int ilev) - { - qrs(icol,ilev) = qrs(icol,ilev)/pdel(icol,ilev); - }); - } - } // dosw - - // Do longwave stuff... - if (do_long_wave_rad) { - // Allocate longwave outputs; why is this not part of the fluxes_t object? - internal::initial_fluxes(ncol, nlev, nlwbands, lw_fluxes_allsky); - internal::initial_fluxes(ncol, nlev, nlwbands, lw_fluxes_clrsky); - - // NOTE: fluxes defined at interfaces, so initialize to have vertical dimension nlev_rad+1 - yakl::memset(cld_tau_gpt_lw, 0.); - - optics.get_cloud_optics_lw(ncol, nlev, nlwbands, do_snow_optics, cld, cldfsnow, iclwp, iciwp, icswp, - lambdac, mu, dei, des, rei, - cld_tau_bnd_lw, liq_tau_bnd_lw, ice_tau_bnd_lw, snw_tau_bnd_lw); - - radiation.get_gpoint_bands_lw(gpoint_bands_lw); - - optics.sample_cloud_optics_lw(ncol, nlev, nlwgpts, gpoint_bands_lw, - pmid, cld, cldfsnow, - cld_tau_bnd_lw, cld_tau_gpt_lw); - - // Get gas concentrations - get_gas_vmr(active_gases, gas_vmr); - - // Get aerosol optics - yakl::memset(aer_tau_bnd_lw, 0.); - if (do_aerosol_rad) { - aer_rad.aer_rad_props_lw(is_cmip6_volc, 0, dt, zi, aer_tau_bnd_lw, clear_rh); - } - - // Call the longwave radiation driver to calculate fluxes and heating rates - radiation_driver_lw(ncol, nlev, gas_vmr, pmid, pint, tmid, tint, cld_tau_gpt_lw, aer_tau_bnd_lw, - lw_fluxes_allsky, lw_fluxes_clrsky, qrl, qrlc); - - // Set surface fluxes that are used by the land model - export_surface_fluxes(lw_fluxes_allsky, "longwave"); - + m_orbital_year = timeinfo->tm_year + 1900; + m_orbital_mon = timeinfo->tm_mon + 1; + m_orbital_day = timeinfo->tm_mday; } - else { - // Conserve energy (what does this mean exactly?) - if (conserve_energy) { - parallel_for(SimpleBounds<2>(ncol, nlev), YAKL_LAMBDA (int icol, int ilev) - { - qrl(icol,ilev) = qrl(icol,ilev)/pdel(icol,ilev); - }); + + // Only allocate and proceed if we are going to update radiation + m_update_rad = false; + if (m_rad_freq_in_steps > 0) { m_update_rad = ( (m_step == 0) || (m_step % m_rad_freq_in_steps == 0) ); } + + if (update_rad) { + // Reset vector of offsets for columnar data + m_nlay = geom.Domain().length(2); + + m_ncol = 0; + m_col_offsets.clear(); + m_col_offsets.resize(int(ba.size())); + for (MFIter mfi(cons_in, TileNoZ()); mfi.isValid(); ++mfi) { + const auto& vbx = mfi.validbox(); + int nx = vbx.length(0); + int ny = vbx.length(1); + m_col_offsets[mfi.index()] = ncol; + m_ncol += nx * ny; } - } // dolw - - // Populate source term for theta dycore variable - for (MFIter mfi(*(qrad_src)); mfi.isValid(); ++mfi) { - auto qrad_src_array = qrad_src->array(mfi); - const auto& box3d = mfi.tilebox(); - auto nx = box3d.length(0); - int const offset = rank_offsets[mfi.LocalIndex()]; - amrex::ParallelFor(box3d, [=] AMREX_GPU_DEVICE (int i, int j, int k) - { - // Map (col,lev) to (i,j,k) - auto icol = (j-box3d.smallEnd(1))*nx + (i-box3d.smallEnd(0)) + 1 + offset; - auto ilev = k+1; - // TODO: We do not include the cloud source term qrsc/qrlc. - // Do these simply sum for a net source or do we pick one? + // Allocate the buffer arrays + alloc_buffers(); - // SW and LW sources - qrad_src_array(i,j,k,0) = qrs(icol,ilev); - qrad_src_array(i,j,k,1) = qrl(icol,ilev); - }); + // Fill the YAKL Arrays from AMReX MFs + mf_to_yakl_buffers(); } } -void Radiation::radiation_driver_sw (int ncol, const real3d& gas_vmr, - const real2d& pmid, const real2d& pint, const real2d& tmid, - const real2d& albedo_dir, const real2d& albedo_dif, const real1d& coszrs, - const real3d& cld_tau_gpt, const real3d& cld_ssa_gpt, const real3d& cld_asm_gpt, - const real3d& aer_tau_bnd, const real3d& aer_ssa_bnd, const real3d& aer_asm_bnd, - FluxesByband& fluxes_clrsky, FluxesByband& fluxes_allsky, const real2d& qrs, - const real2d& qrsc) +void +Radiation::alloc_buffers () { - // Incoming solar radiation, scaled for solar zenith angle - // and earth-sun distance - real2d solar_irradiance_by_gpt("solar_irradiance_by_gpt",ncol,nswgpts); - - // Gathered indices of day and night columns - // chunk_column_index = day_indices(daylight_column_index) - int1d day_indices("day_indices",ncol), night_indices("night_indices", ncol); // Indices of daylight coumns - - real1d coszrs_day("coszrs_day", ncol); - real2d albedo_dir_day("albedo_dir_day", nswbands, ncol), albedo_dif_day("albedo_dif_day", nswbands, ncol); - real2d pmid_day("pmid_day", ncol, nlev); - real2d tmid_day("tmid_day", ncol, nlev); - real2d pint_day("pint_day", ncol, nlev+1); - - real3d gas_vmr_day("gas_vmr_day", ngas, ncol, nlev); - - real3d cld_tau_gpt_day("cld_tau_gpt_day", ncol, nlev, nswgpts); - real3d cld_ssa_gpt_day("cld_ssa_gpt_day", ncol, nlev, nswgpts); - real3d cld_asm_gpt_day("cld_asm_gpt_day", ncol, nlev, nswgpts); - real3d aer_tau_bnd_day("aer_tau_bnd_day", ncol, nlev, nswbands); - real3d aer_ssa_bnd_day("aer_ssa_bnd_day", ncol, nlev, nswbands); - real3d aer_asm_bnd_day("aer_asm_bnd_day", ncol, nlev, nswbands); - - real3d cld_tau_gpt_rad("cld_tau_gpt_rad", ncol, nlev+1, nswgpts); - real3d cld_ssa_gpt_rad("cld_ssa_gpt_rad", ncol, nlev+1, nswgpts); - real3d cld_asm_gpt_rad("cld_asm_gpt_rad", ncol, nlev+1, nswgpts); - real3d aer_tau_bnd_rad("aer_tau_bnd_rad", ncol, nlev+1, nswgpts); - real3d aer_ssa_bnd_rad("aer_ssa_bnd_rad", ncol, nlev+1, nswgpts); - real3d aer_asm_bnd_rad("aer_asm_bnd_rad", ncol, nlev+1, nswgpts); - - // Scaling factor for total sky irradiance; used to account for orbital - // eccentricity, and could be used to scale total sky irradiance for different - // climates as well (i.e., paleoclimate simulations) - real tsi_scaling; - real solar_declination; - - if (fixed_total_solar_irradiance<0) { - // TODO: Integrate calendar day computation - int calday = 1; - // Get orbital eccentricity factor to scale total sky irradiance - shr_orb_decl(calday, eccen, mvelpp, lambm0, obliqr, solar_declination, tsi_scaling); - } else { - // For fixed TSI we divide by the default solar constant of 1360.9 - // At some point we will want to replace this with a method that - // retrieves the solar constant - tsi_scaling = fixed_total_solar_irradiance / 1360.9; - } - - // Gather night/day column indices for subsetting SW inputs; we only want to - // do the shortwave radiative transfer during the daytime to save - // computational cost (and because RRTMGP will fail for cosine solar zenith - // angles less than or equal to zero) - set_daynight_indices(coszrs, day_indices, night_indices); - int1d nday("nday",1); - int1d nnight("nnight", 1); - yakl::memset(nday, 0); - yakl::memset(nnight, 0); - parallel_for(SimpleBounds<1>(ncol), YAKL_LAMBDA (int icol) + // 1d size (m_ngas) + m_gas_mol_weights = real1d("m_gas_mol_weights", m_ngas); + realHost1d m_gas_mol_weights_h("m_gas_mol_weights_h", m_ngas); + parallel_for(m_ngas, YAKL_LAMBDA (int igas) { - if (day_indices(icol) > 0) nday(1)++; - if (night_indices(icol) > 0) nnight(1)++; + m_gas_mol_weights_h(igas) = m_mol_weight_gas[igas]; + gas_names_yakl_offset(igas) = m_gas_names[igas]; }); + m_gas_mol_weights.deep_copy_to(m_gas_mol_weights_h); + + + // 1d size (ncol) + cosine_zenith = real1d("cosine_zenith" , m_ncol); + mu0 = real1d("mu0" , m_ncol); + sfc_alb_dir_vis = real1d("sfc_alb_dir_vis" , m_ncol); + sfc_alb_dir_nir = real1d("sfc_alb_dir_nir" , m_ncol); + sfc_alb_dif_vis = real1d("sfc_alb_dif_vis" , m_ncol); + sfc_alb_dif_nir = real1d("sfc_alb_dif_nir" , m_ncol); + sfc_flux_dir_vis = real1d("sfc_flux_dir_vis", m_ncol); + sfc_flux_dir_nir = real1d("sfc_flux_dir_nir", m_ncol); + sfc_flux_dif_vis = real1d("sfc_flux_dif_vis", m_ncol); + sfc_flux_dif_nir = real1d("sfc_flux_dif_nir", m_ncol); + + // 2d size (ncol, nlay) + d_dz = real2d("d_dz" , m_ncol, m_nlay); + r_lay = real2d("r_lay" , m_ncol, m_nlay); + p_lay = real2d("p_lay" , m_ncol, m_nlay); + t_lay = real2d("t_lay" , m_ncol, m_nlay); + z_del = real2d("z_del" , m_ncol, m_nlay); + p_del = real2d("p_del" , m_ncol, m_nlay); + qc = real2d("qc" , m_ncol, m_nlay); + nc = real2d("nc" , m_ncol, m_nlay); + qi = real2d("qi" , m_ncol, m_nlay); + cldfrac_tot = real2d("cldfrac_tot" , m_ncol, m_nlay); + eff_radius_qc = real2d("eff_radius_qc", m_ncol, m_nlay); + eff_radius_qi = real2d("eff_radius_qi", m_ncol, m_nlay); + tmp2d = real2d("tmp2d" , m_ncol, m_nlay); + lwp = real2d("lwp" , m_ncol, m_nlay); + iwp = real2d("iwp" , m_ncol, m_nlay); + sw_heating = real2d("sw_heating" , m_ncol, m_nlay); + lw_heating = real2d("lw_heating" , m_ncol, m_nlay); + + // 2d size (ncol, nlay+1) + d_tint = real2d("d_tint" , m_ncol, m_nlay+1); + p_lev = real2d("p_lev" , m_ncol, m_nlay+1); + t_lev = real2d("t_lev" , m_ncol, m_nlay+1); + sw_flux_up = real2d("sw_flux_up" , m_ncol, m_nlay+1); + sw_flux_dn = real2d("sw_flux_dn" , m_ncol, m_nlay+1); + sw_flux_dn_dir = real2d("sw_flux_dn_dir" , m_ncol, m_nlay+1); + lw_flux_up = real2d("sw_flux_up" , m_ncol, m_nlay+1); + lw_flux_dn = real2d("sw_flux_dn" , m_ncol, m_nlay+1); + sw_clnclrsky_flux_up = real2d("sw_clnclrsky_flux_up" , m_ncol, m_nlay+1); + sw_clnclrsky_flux_dn = real2d("sw_clnclrsky_flux_dn" , m_ncol, m_nlay+1); + sw_clnclrsky_flux_dn_dir = real2d("sw_clnclrsky_flux_dn_dir", m_ncol, m_nlay+1); + sw_clrsky_flux_up = real2d("sw_clrsky_flux_up" , m_ncol, m_nlay+1); + sw_clrsky_flux_dn = real2d("sw_clrsky_flux_dn" , m_ncol, m_nlay+1); + sw_clrsky_flux_dn_dir = real2d("sw_clrsky_flux_dn_dir" , m_ncol, m_nlay+1); + sw_clnsky_flux_up = real2d("sw_clnsky_flux_up" , m_ncol, m_nlay+1); + sw_clnsky_flux_dn = real2d("sw_clnsky_flux_dn" , m_ncol, m_nlay+1); + sw_clnsky_flux_dn_dir = real2d("sw_clnsky_flux_dn_dir" , m_ncol, m_nlay+1); + lw_clnclrsky_flux_up = real2d("lw_clnclrsky_flux_up" , m_ncol, m_nlay+1); + lw_clnclrsky_flux_dn = real2d("lw_clnclrsky_flux_dn" , m_ncol, m_nlay+1); + lw_clrsky_flux_up = real2d("lw_clrsky_flux_up" , m_ncol, m_nlay+1); + lw_clrsky_flux_dn = real2d("lw_clrsky_flux_dn" , m_ncol, m_nlay+1); + lw_clnsky_flux_up = real2d("lw_clnsky_flux_up" , m_ncol, m_nlay+1); + lw_clnsky_flux_dn = real2d("lw_clnsky_flux_dn" , m_ncol, m_nlay+1); + + // 3d size (ncol, nlay+1, nswbands) + sw_bnd_flux_up = real3d("sw_bnd_flux_up" , m_ncol, m_nlay+1, m_nswbands); + sw_bnd_flux_dn = real3d("sw_bnd_flux_dn" , m_ncol, m_nlay+1, m_nswbands); + sw_bnd_flux_dir = real3d("sw_bnd_flux_dir", m_ncol, m_nlay+1, m_nswbands); + sw_bnd_flux_dif = real3d("sw_bnd_flux_dif", m_ncol, m_nlay+1, m_nswbands); + + // 2d size (ncol, nswbands) + sfc_alb_dir = real2d("sfc_alb_dir", m_ncol, m_nswbands); + sfc_alb_dif = real2d("sfc_alb_dif", m_ncol, m_nswbands); + + // 3d size (ncol, nlay, n[sw,lw]bands) + aero_tau_sw = real3d("aero_tau_sw", m_ncol, m_nlay, m_nswbands); + aero_ssa_sw = real3d("aero_ssa_sw", m_ncol, m_nlay, m_nswbands); + aero_g_sw = real3d("aero_g_sw" , m_ncol, m_nlay, m_nswbands); + aero_tau_lw = real3d("aero_tau_lw", m_ncol, m_nlay, m_nlwbands); + + // 3d size (ncol, nlay, n[sw,lw]bnds) + cld_tau_sw_bnd = real3d("cld_tau_sw_bnd", m_ncol, m_nlay, m_nswbands); + cld_tau_lw_bnd = real3d("cld_tau_lw_bnd", m_ncol, m_nlay, m_nlwbands); + + // 3d size (ncol, nlay, n[sw,lw]gpts) + cld_tau_sw_gpt = real3d("cld_tau_sw_gpt", m_ncol, m_nlay, m_nswgpts); + cld_tau_lw_gpt = real3d("cld_tau_lw_gpt", m_ncol, m_nlay, m_nlwgpts); +} - AMREX_ASSERT(nday(1) + nnight(1) == ncol); +void +Radiation::dealloc_buffers () +{ + // 1d size (m_ngas) + m_gas_mol_weights.deallocate(); + + // 1d size (ncol) + cosine_zenith.deallocate(); + mu0.deallocate(); + sfc_alb_dir_vis.deallocate(); + sfc_alb_dir_nir.deallocate(); + sfc_alb_dif_vis.deallocate(); + sfc_alb_dif_nir.deallocate(); + sfc_flux_dir_vis.deallocate(); + sfc_flux_dir_nir.deallocate(); + sfc_flux_dif_vis.deallocate(); + sfc_flux_dif_nir.deallocate(); + + // 2d size (ncol, nlay) + d_dz.deallocate(); + r_lay.deallocate(); + p_lay.deallocate(); + t_lay.deallocate(); + z_del.deallocate(); + p_del.deallocate(); + qc.deallocate(); + nc.deallocate(); + qi.deallocate(); + cldfrac_tot.deallocate(); + eff_radius_qc.deallocate(); + eff_radius_qi.deallocate(); + tmp2d.deallocate(); + lwp.deallocate(); + iwp.deallocate(); + + sw_heating.deallocate(); + lw_heating.deallocate(); + + // 2d size (ncol, nlay+1) + d_tint.deallocate(); + p_lev.deallocate(); + t_lev.deallocate(); + + sw_flux_up.deallocate(); + sw_flux_dn.deallocate(); + sw_flux_dn_dir.deallocate(); + lw_flux_up.deallocate(); + lw_flux_dn.deallocate(); + sw_clnclrsky_flux_up.deallocate(); + sw_clnclrsky_flux_dn.deallocate(); + sw_clnclrsky_flux_dn_dir.deallocate(); + sw_clrsky_flux_up.deallocate(); + sw_clrsky_flux_dn.deallocate(); + sw_clrsky_flux_dn_dir.deallocate(); + sw_clnsky_flux_up.deallocate(); + sw_clnsky_flux_dn.deallocate(); + sw_clnsky_flux_dn_dir.deallocate(); + lw_clnclrsky_flux_up.deallocate(); + lw_clnclrsky_flux_dn.deallocate(); + lw_clrsky_flux_up.deallocate(); + lw_clrsky_flux_dn.deallocate(); + lw_clnsky_flux_up.deallocate(); + lw_clnsky_flux_dn.deallocate(); + + // 3d size (ncol, nlay+1, nswbands) + sw_bnd_flux_up.deallocate(); + sw_bnd_flux_dn.deallocate(); + sw_bnd_flux_dir.deallocate(); + sw_bnd_flux_dif.deallocate(); + + // 2d size (ncol, nswbands) + sfc_alb_dir.deallocate(); + sfc_alb_dif.deallocate(); + + // 3d size (ncol, nlay, n[sw,lw]bands) + aero_tau_sw.deallocate(); + aero_ssa_sw.deallocate(); + aero_g_sw.deallocate(); + aero_tau_lw.deallocate(); + + // 3d size (ncol, nlay, n[sw,lw]bnds) + cld_tau_sw_bnd.deallocate(); + cld_tau_lw_bnd.deallocate(); + + // 3d size (ncol, nlay, n[sw,lw]gpts) + cld_tau_sw_gpt.deallocate(); + cld_tau_lw_gpt.deallocate(); +} - intHost1d num_day("num_day",1); - intHost1d num_night("num_night",1); - nday.deep_copy_to(num_day); - nnight.deep_copy_to(num_night); - // If no daytime columns in this chunk, then we return zeros - if (num_day(1) == 0) { - // reset_fluxes(fluxes_allsky) - // reset_fluxes(fluxes_clrsky) - yakl::memset(qrs, 0.); - yakl::memset(qrsc, 0.); - return; +void +Radiation::mf_to_yakl_buffers () +{ + bool moist = m_moist; + bool ice = m_ice; + int ncol = m_ncol; + int nlay = m_nlay; + Real dz = geom.CellSize(2); + for (MFIter mfi(*m_cons_in); mfi.isValid(); ++mfi) { + const auto& vbx = mfi.validbox(); + const int nx = vbx.length(0); + const int imin = vbx.smallEnd(0); + const int jmin = vbx.smallEnd(1); + const int offset = m_col_offsets[mfi.Index()]; + const Array4< Real>& cons_arr = m_cons_in->const_array(mfi); + const Array4& z_arr = (m_z_phys) ? m_z_phys->const_array(mfi) : + Array4{}; + ParallelFor(vbx, [=] AMREX_GPU_DEVICE (int i, int j, int k) + { + // map [i,j,k] 0-based to [icol, ilay] 1-based + const int icol = (j-jmin)*nx + (i-imin) + 1 + offset; + const int ilay = k+1; + + // EOS input (at CC) + Real r = cons_arr(i,j,k,Rho_comp); + Real rt = cons_arr(i,j,k,RhoTheta_comp); + Real qv = (moist) ? cons_arr(i,j,k,RhoQ1_comp)/r : 0.0; + Real qc = (moist) ? cons_arr(i,j,k,RhoQ2_comp)/r : 0.0; + Real qi = (ice) ? cons_arr(i,j,k,RhoQ3_comp)/r : 0.0; + + // EOS avg to z-face + Real r_lo = cons_arr(i,j,k-1,Rho_comp); + Real rt_lo = cons_arr(i,j,k-1,RhoTheta_comp); + Real qv_lo = (moist) ? cons_arr(i,j,k-1,RhoQ1_comp)/r_lo : 0.0; + Real qc_lo = (moist) ? cons_arr(i,j,k-1,RhoQ2_comp)/r_lo : 0.0; + Real qi_lo = (ice) ? cons_arr(i,j,k-1,RhoQ3_comp)/r_lo : 0.0; + Real r_avg = 0.5 * (r + r_lo); + Real rt_avg = 0.5 * (rt + rt_lo); + Real qv_avg = 0.5 * (qv + qv_lo); + Real qc_avg = 0.5 * (qc + qc_lo); + Real qi_avg = 0.5 * (qi + qi_lo); + + // Buffers at CC + r_lay(icol,ilay) = r; + p_lay(icol,ilay) = getPgivenRTh(rt, qv); + t_lay(icol,ilay) = getTgivenRandRTh(r, rt, qv); + z_del(icol,ilay) = (z_arr) ? 0.25 * ( (z_arr(i ,j ,k+1) - z_arr(i ,j ,k)) + + (z_arr(i+1,j ,k+1) - z_arr(i+1,j ,k)) + + (z_arr(i ,j+1,k+1) - z_arr(i ,j+1,k)) + + (z_arr(i+1,j ,k+1) - z_arr(i+1,j ,k)) ) : dz; + qc(icol,ilay) = qc; + qi(icol,ilay) = qi; + cld_frac_tot(icol,ilay) = ((qc+qi)>1.0e-5) ? 1. : 0.; + + // HACK HACK HACK + lwp(icol,ilay) = 0.0; + iwp(icol,ilay) = 0.0; + + // Buffers on z-faces (nlay+1) + p_lev(icol,ilay) = getPgivenRTh(rt_avg, qv_avg); + t_lev(icol,ilay) = getTgivenRandRTh(r_avg, rt_avg, qv_avg); + if (ilay==nlay) { + Real r_hi = cons_arr(i,j,k+1,Rho_comp); + Real rt_hi = cons_arr(i,j,k+1,RhoTheta_comp); + Real qv_hi = (moist) ? cons_arr(i,j,k+1,RhoQ1_comp)/r_hi : 0.0; + r_avg = 0.5 * (r + r_hi); + rt_avg = 0.5 * (rt + rt_hi); + qv_avg = 0.5 * (qv + qv_hi); + p_lev(icol,ilay+1) = getPgivenRTh(rt_avg, qv_avg); + t_lev(icol,ilay+1) = getTgivenRandRTh(r_avg, rt_avg, qv_avg); + } + }); } - // Compress to daytime-only arrays - parallel_for(SimpleBounds<2>(num_day(1), nlev), YAKL_LAMBDA (int iday, int ilev) - { - // 2D arrays - auto icol = day_indices(iday); - tmid_day(iday,ilev) = tmid(icol,ilev); - pmid_day(iday,ilev) = pmid(icol,ilev); - pint_day(iday,ilev) = pint(icol,ilev); - }); - parallel_for(SimpleBounds<1>(num_day(1)), YAKL_LAMBDA (int iday) - { - // copy extra level for pmid - auto icol = day_indices(iday); - pint_day(iday,nlev+1) = pint(icol,nlev+1); - - coszrs_day(iday) = coszrs(icol); - AMREX_ASSERT(coszrs_day(iday) > 0.0); - }); - parallel_for(SimpleBounds<3>(num_day(1), nlev, nswgpts), YAKL_LAMBDA (int iday, int ilev, int igpt) - { - auto icol = day_indices(iday); - cld_tau_gpt_day(iday,ilev,igpt) = cld_tau_gpt(icol,ilev,igpt); - cld_ssa_gpt_day(iday,ilev,igpt) = cld_ssa_gpt(icol,ilev,igpt); - cld_asm_gpt_day(iday,ilev,igpt) = cld_asm_gpt(icol,ilev,igpt); - }); - parallel_for(SimpleBounds<2>(num_day(1), nswbands), YAKL_LAMBDA (int iday, int ibnd) - { - // albedo dims: [nswbands, ncol] - auto icol = day_indices(iday); - albedo_dir_day(ibnd,iday) = albedo_dir(ibnd,icol); - albedo_dif_day(ibnd,iday) = albedo_dif(ibnd,icol); - }); - - parallel_for(SimpleBounds<3>(num_day(1), nlev, nswbands), YAKL_LAMBDA (int iday, int ilev, int ibnd) - { - auto icol = day_indices(iday); - aer_tau_bnd_day(iday,ilev,ibnd) = aer_tau_bnd(icol,ilev,ibnd); - aer_ssa_bnd_day(iday,ilev,ibnd) = aer_ssa_bnd(icol,ilev,ibnd); - aer_asm_bnd_day(iday,ilev,ibnd) = aer_asm_bnd(icol,ilev,ibnd); - }); - - // Allocate shortwave fluxes (allsky and clearsky) - // NOTE: fluxes defined at interfaces, so initialize to have vertical - // dimension nlev_rad+1, while we initialized the RRTMGP input variables to - // have vertical dimension nlev_rad (defined at midpoints). - FluxesByband fluxes_clrsky_day, fluxes_allsky_day; - internal::initial_fluxes(num_day(1), nlev+1, nswbands, fluxes_allsky_day); - internal::initial_fluxes(num_day(1), nlev+1, nswbands, fluxes_clrsky_day); - - // Add an empty level above model top - // TODO: combine with day compression above - yakl::memset(cld_tau_gpt_rad, 0.); - yakl::memset(cld_ssa_gpt_rad, 0.); - yakl::memset(cld_asm_gpt_rad, 0.); - - yakl::memset(aer_tau_bnd_rad, 0.); - yakl::memset(aer_ssa_bnd_rad, 0.); - yakl::memset(aer_asm_bnd_rad, 0.); - - parallel_for(SimpleBounds<3>(num_day(1), nlev, nswgpts), YAKL_LAMBDA (int iday, int ilev, int igpt) - { - cld_tau_gpt_rad(iday,ilev,igpt) = cld_tau_gpt_day(iday,ilev,igpt); - cld_ssa_gpt_rad(iday,ilev,igpt) = cld_ssa_gpt_day(iday,ilev,igpt); - cld_asm_gpt_rad(iday,ilev,igpt) = cld_asm_gpt_day(iday,ilev,igpt); - }); - - parallel_for(SimpleBounds<3>(num_day(1), nlev, ngas), YAKL_LAMBDA (int iday, int ilev, int igas) + // Separate YAKL kernel for derived quantities + parallel_for(SimpleBounds<2>(ncol, nlay), YAKL_LAMBDA (int icol, int ilay) { - auto icol = day_indices(iday); - gas_vmr_day(igas,iday,ilev) = gas_vmr(igas,icol,ilev); + p_del(icol,ilay) = p_lev(icol,ilay+1) - p_lev(icol,ilay); + nc(icol,ilay) = 0.0; + rel(icol,ilay) = 0.0; + rei(icol,ilay) = 0.0; }); - parallel_for(SimpleBounds<3>(num_day(1), nlev, nswbands), YAKL_LAMBDA (int iday, int ilev, int ibnd) - { - aer_tau_bnd_rad(iday,ilev,ibnd) = aer_tau_bnd_day(iday,ilev,ibnd); - aer_ssa_bnd_rad(iday,ilev,ibnd) = aer_ssa_bnd_day(iday,ilev,ibnd); - aer_asm_bnd_rad(iday,ilev,ibnd) = aer_asm_bnd_day(iday,ilev,ibnd); - }); - - // Do shortwave radiative transfer calculations - radiation.run_shortwave_rrtmgp(ngas, num_day(1), nlev, gas_vmr_day, pmid_day, - tmid_day, pint_day, coszrs_day, albedo_dir_day, albedo_dif_day, - cld_tau_gpt_rad, cld_ssa_gpt_rad, cld_asm_gpt_rad, aer_tau_bnd_rad, aer_ssa_bnd_rad, aer_asm_bnd_rad, - fluxes_allsky_day.flux_up , fluxes_allsky_day.flux_dn , fluxes_allsky_day.flux_net , fluxes_allsky_day.flux_dn_dir , - fluxes_allsky_day.bnd_flux_up, fluxes_allsky_day.bnd_flux_dn, fluxes_allsky_day.bnd_flux_net, fluxes_allsky_day.bnd_flux_dn_dir, - fluxes_clrsky_day.flux_up , fluxes_clrsky_day.flux_dn , fluxes_clrsky_day.flux_net , fluxes_clrsky_day.flux_dn_dir , - fluxes_clrsky_day.bnd_flux_up, fluxes_clrsky_day.bnd_flux_dn, fluxes_clrsky_day.bnd_flux_net, fluxes_clrsky_day.bnd_flux_dn_dir, - tsi_scaling); - - // Expand fluxes from daytime-only arrays to full chunk arrays - internal::expand_day_fluxes(fluxes_allsky_day, fluxes_allsky, day_indices); - internal::expand_day_fluxes(fluxes_clrsky_day, fluxes_clrsky, day_indices); - - // Calculate heating rates - calculate_heating_rate(fluxes_allsky.flux_up, - fluxes_allsky.flux_dn, - pint, qrs); - - calculate_heating_rate(fluxes_clrsky.flux_up, - fluxes_allsky.flux_dn, - pint, qrsc); + // HACK HACK HACK + // No LSM, so follow EAMXX dummy atmos and set constants + yakl::memset(mu0, 0.86); + yakl::memset(sfc_alb_dir_vis, 0.06); + yakl::memset(sfc_alb_dir_nir, 0.06); + yakl::memset(sfc_alb_dif_vis, 0.06); + yakl::memset(sfc_alb_dif_nir, 0.06); + + // HACK HACK HACK + yakl::memset(aero_tau_sw, 0.0); + yakl::memset(aero_ssa_sw, 0.0); + yakl::memset(aero_g_sw , 0.0); + yakl::memset(aero_tau_lw, 0.0); } -void Radiation::radiation_driver_lw (int ncol, int nlev, - const real3d& gas_vmr, - const real2d& pmid, const real2d& pint, const real2d& tmid, const real2d& tint, - const real3d& cld_tau_gpt, const real3d& aer_tau_bnd, FluxesByband& fluxes_clrsky, - FluxesByband& fluxes_allsky, const real2d& qrl, const real2d& qrlc) -{ - real3d cld_tau_gpt_rad("cld_tau_gpt_rad", ncol, nlev+1, nlwgpts); - real3d aer_tau_bnd_rad("aer_tau_bnd_rad", ncol, nlev+1, nlwgpts); - - // Surface emissivity needed for longwave - real2d surface_emissivity("surface_emissivity", nlwbands, ncol); - // Temporary heating rates on radiation vertical grid - real3d gas_vmr_rad("gas_vmr_rad", ngas, ncol, nlev); +void +Radiation::yakl_buffers_to_mf () +{ + for (MFIter mfi(*m_cons_in); mfi.isValid(); ++mfi) { + const auto& vbx = mfi.validbox(); + const auto& sbx = makeSlab(vbx,2,vbx.smallEnd(2)); + const int nx = vbx.length(0); + const int imin = vbx.smallEnd(0); + const int jmin = vbx.smallEnd(1); + const int offset = m_col_offsets[mfi.Index()]; + const Array4& q_arr = m_qheating_rates->const_array(mfi); + const Array4& lsm_arr = (m_lsm_fluxes) m_lsm_fluxes->array(mfi) : + Array4{}; + ParallelFor(vbx, [=] AMREX_GPU_DEVICE (int i, int j, int k) + { + // map [i,j,k] 0-based to [icol, ilay] 1-based + const int icol = (j-jmin)*nx + (i-imin) + 1 + offset; + const int ilay = k+1; - // Set surface emissivity to 1 here. There is a note in the RRTMG - // implementation that this is treated in the land model, but the old - // RRTMG implementation also sets this to 1. This probably does not make - // a lot of difference either way, but if a more intelligent value - // exists or is assumed in the model we should use it here as well. - // TODO: set this more intelligently? - yakl::memset(surface_emissivity, 1.0); + // Heating rate for SW and LW (this is for Temperature) + q_arr(i,j,k,0) = sw_heating(icol,ilay); + q_arr(i,j,k,1) = lw_heating(icol,ilay); + }); + ParallelFor(sbx, [=] AMREX_GPU_DEVICE (int i, int j, int k) + { + // map [i,j,k] 0-based to [icol, ilay] 1-based + const int icol = (j-jmin)*nx + (i-imin) + 1 + offset; - // Add an empty level above model top - yakl::memset(cld_tau_gpt_rad, 0.); - yakl::memset(aer_tau_bnd_rad, 0.); + // SW fluxes for LSM + lsm_arr(i,j,k,0) = sfc_flux_dir_vis(icol); + lsm_arr(i,j,k,1) = sfc_flux_dir_nir(icol); + lsm_arr(i,j,k,2) = sfc_flux_dif_vis(icol); + lsm_arr(i,j,k,3) = sfc_flux_dif_nir(icol); - parallel_for(SimpleBounds<3>(ncol, nlev, nlwgpts), YAKL_LAMBDA (int icol, int ilev, int igpt) - { - cld_tau_gpt_rad(icol,ilev,igpt) = cld_tau_gpt(icol,ilev,igpt); - aer_tau_bnd_rad(icol,ilev,igpt) = aer_tau_bnd(icol,ilev,igpt); - }); + // New SW flux for LSM + lsm_arr(i,j,k,4) = sfc_flux_dir_vis(icol) + sfc_flux_dir_nir(icol) + + sfc_flux_dif_vis(icol) + sfc_flux_dif_nir(icol); - parallel_for(SimpleBounds<3>(ncol, nlev, ngas), YAKL_LAMBDA (int icol, int ilev, int igas) - { - gas_vmr_rad(igas,icol,ilev) = gas_vmr(igas,icol,ilev); - }); - - // Do longwave radiative transfer calculations - radiation.run_longwave_rrtmgp(ngas, ncol, nlev, - gas_vmr_rad, pmid, tmid, pint, tint, - surface_emissivity, cld_tau_gpt_rad, aer_tau_bnd_rad, - fluxes_allsky.flux_up , fluxes_allsky.flux_dn , fluxes_allsky.flux_net , - fluxes_allsky.bnd_flux_up, fluxes_allsky.bnd_flux_dn, fluxes_allsky.bnd_flux_net, - fluxes_clrsky.flux_up , fluxes_clrsky.flux_dn , fluxes_clrsky.flux_net , - fluxes_clrsky.bnd_flux_up, fluxes_clrsky.bnd_flux_dn, fluxes_clrsky.bnd_flux_net); - - // Calculate heating rates - calculate_heating_rate(fluxes_allsky.flux_up, - fluxes_allsky.flux_dn, - pint, qrl); - - calculate_heating_rate(fluxes_allsky.flux_up, - fluxes_allsky.flux_dn, - pint, qrlc); -} - -// Initialize array of daytime indices to be all zero. If any zeros exist when -// we are done, something went wrong. -void Radiation::set_daynight_indices (const real1d& coszrs, const int1d& day_indices, const int1d& night_indices) -{ - // Loop over columns and identify daytime columns as those where the cosine - // solar zenith angle exceeds zero. Note that we wrap the setting of - // day_indices in an if-then to make sure we are not accessing day_indices out - // of bounds, and stopping with an informative error message if we do for some reason. - int1d iday("iday", 1); - int1d inight("inight",1); - yakl::memset(iday, 0); - yakl::memset(inight, 0); - yakl::memset(day_indices, 0); - yakl::memset(night_indices, 0); - parallel_for(SimpleBounds<1>(ncol), YAKL_LAMBDA (int icol) - { - if (coszrs(icol) > 0.) { - iday(1) += 1; - day_indices(iday(1)) = icol; - } else { - inight(1) += 1; - night_indices(inight(1)) = icol; - } - }); + // LW fluxe for LSM (at bottom surface) + lsm_arr(i,j,k,5) = lw_flux_dn(icol,1); + }); + } } -void Radiation::get_gas_vmr (const std::vector& gas_names, const real3d& gas_vmr) +void +Radiation::initialize_impl () { - // Mass mixing ratio - real2d mmr("mmr", ncol, nlev); - - // Gases and molecular weights. Note that we do NOT have CFCs yet (I think - // this is coming soon in RRTMGP). RRTMGP also allows for absorption due to - // CO and N2, which RRTMG did not have. - const std::vector gas_species = {"H2O", "CO2", "O3", "N2O", - "CO" , "CH4", "O2", "N2"}; - const std::vector mol_weight_gas = {18.01528, 44.00950, 47.9982, 44.0128, - 28.01010, 16.04246, 31.9980, 28.0134}; // g/mol - // Molar weight of air - //const real mol_weight_air = 28.97; // g/mol - // Defaults for gases that are not available (TODO: is this still accurate?) - const real co_vol_mix_ratio = 1.0e-7; - const real n2_vol_mix_ratio = 0.7906; - - // initialize - yakl::memset(gas_vmr, 0.); - - // For each gas species needed for RRTMGP, read the mass mixing ratio from the - // CAM rad_constituents interface, convert to volume mixing ratios, and - // subset for daytime-only indices if needed. - for (auto igas = 0; igas < gas_names.size(); ++igas) { - - std::cout << "gas_name: " << gas_names[igas] << "; igas: " << igas << std::endl; - - if (gas_names[igas] == "CO"){ - // CO not available, use default - parallel_for(SimpleBounds<2>(ncol, nlev), YAKL_LAMBDA (int icol, int ilev) - { - gas_vmr(igas+1,icol,ilev) = co_vol_mix_ratio; - }); - } else if (gas_names[igas] == "N2") { - // N2 not available, use default - parallel_for(SimpleBounds<2>(ncol, nlev), YAKL_LAMBDA (int icol, int ilev) - { - gas_vmr(igas+1,icol,ilev) = n2_vol_mix_ratio; - }); - } else if (gas_names[igas] == "H2O") { - // Water vapor is represented as specific humidity in CAM, so we - // need to handle water a little differently - // rad_cnst_get_gas(icall, gas_species[igas], mmr); - - // Convert to volume mixing ratio by multiplying by the ratio of - // molecular weight of dry air to molecular weight of gas. Note that - // first specific humidity (held in the mass_mix_ratio array read - // from rad_constituents) is converted to an actual mass mixing ratio. - parallel_for(SimpleBounds<2>(ncol, nlev), YAKL_LAMBDA (int icol, int ilev) - { - gas_vmr(igas+1,icol,ilev) = qt(icol,ilev); //mmr(icol,ilev) / ( - // 1. - mmr(icol,ilev))*mol_weight_air / mol_weight_gas[igas]; - }); - } else if (gas_names[igas] == "CO2") { - parallel_for(SimpleBounds<2>(ncol, nlev), YAKL_LAMBDA (int icol, int ilev) - { - gas_vmr(igas+1,icol,ilev) = 3.8868676125307193E-4; - }); - } else if (gas_names[igas] == "O3") { - parallel_for(SimpleBounds<2>(ncol, nlev), YAKL_LAMBDA (int icol, int ilev) - { - gas_vmr(igas+1,icol,ilev) = 1.8868676125307193E-7; - }); - } else if (gas_names[igas] == "N2O") { - parallel_for(SimpleBounds<2>(ncol, nlev), YAKL_LAMBDA (int icol, int ilev) - { - gas_vmr(igas+1,icol,ilev) = 3.8868676125307193E-7; - }); - } else if (gas_names[igas] == "CH4") { - parallel_for(SimpleBounds<2>(ncol, nlev), YAKL_LAMBDA (int icol, int ilev) - { - gas_vmr(igas+1,icol,ilev) = 1.8868676125307193E-6; - }); - } else if (gas_names[igas] == "O2") { - parallel_for(SimpleBounds<2>(ncol, nlev), YAKL_LAMBDA (int icol, int ilev) - { - gas_vmr(igas+1,icol,ilev) = 0.2095; - }); - } else { - // Get mass mixing ratio from the rad_constituents interface - // rad_cnst_get_gas(icall, gas_species[igas], mmr); - - // Convert to volume mixing ratio by multiplying by the ratio of - // molecular weight of dry air to molecular weight of gas - parallel_for(SimpleBounds<2>(ncol, nlev), YAKL_LAMBDA (int icol, int ilev) - { - gas_vmr(igas+1,icol,ilev) = 1.0e-6; //mmr(icol,ilev) - // * mol_weight_air / mol_weight_gas[igas]; - }); - } - } // igas + // Call API to initialize + m_gas_concs.init(gas_names_yakl_offset, m_ncol, m_nlay); + rrtmgp::rrtmgp_initialize(m_gas_concs, + rrtmgp_coeffs_file_sw , rrtmgp_coeffs_file_lw , + rrtmgp_cloud_optics_file_sw, rrtmgp_cloud_optics_file_lw); } -// Loop over levels and calculate heating rates; note that the fluxes *should* -// be defined at interfaces, so the loop ktop,kbot and grabbing the current -// and next value of k should be safe. ktop should be the top interface, and -// kbot + 1 should be the bottom interface. -// NOTE: to get heating rate in K/day, normally we would use: -// H = dF / dp * g * (sec/day) * (1e-5) / (cpair) -// Here we just use -// H = dF / dp * g -// Why? Something to do with convenience with applying the fluxes to the -// heating tendency? -void Radiation::calculate_heating_rate (const real2d& flux_up, - const real2d& flux_dn, - const real2d& pint, - const real2d& heating_rate) -{ - // NOTE: The pressure is in [pa] for RRTMGP to use. - // The fluxes are in [W/m^2] and gravity is [m/s^2]. - // The heating rate is {dF/dP * g / Cp} with units [K/s] - real1d heatfac("heatfac",1); - yakl::memset(heatfac, 1.0/Cp_d); - parallel_for(SimpleBounds<2>(ncol, nlev), YAKL_LAMBDA (int icol, int ilev) - { - heating_rate(icol,ilev) = heatfac(1) * ( (flux_up(icol,ilev+1) - flux_dn(icol,ilev+1)) - - (flux_up(icol,ilev ) - flux_dn(icol,ilev )) ) - * CONST_GRAV/(pint(icol,ilev+1)-pint(icol,ilev)); - /* - if (icol==1) { - amrex::Print() << "HR: " << ilev << ' ' - << heating_rate(icol,ilev) << ' ' - << (flux_up(icol,ilev+1) - flux_dn(icol,ilev+1)) << ' ' - << (flux_up(icol,ilev ) - flux_dn(icol,ilev )) << ' ' - << flux_up(icol,ilev+1) << ' ' - << flux_dn(icol,ilev+1) << ' ' - << (flux_up(icol,ilev+1) - flux_dn(icol,ilev+1)) - - (flux_up(icol,ilev ) - flux_dn(icol,ilev )) << ' ' - << (pint(icol,ilev+1)-pint(icol,ilev)) << ' ' - << CONST_GRAV << "\n"; - } - */ - }); -} void -Radiation::export_surface_fluxes(FluxesByband& fluxes, - std::string band) +Radiation::run_impl () { - // No work to be done if we don't have valid pointers - if (!m_lsm_fluxes) return; - - if (band == "shortwave") { - real3d flux_dn_diffuse("flux_dn_diffuse", ncol, nlev+1, nswbands); - - // Calculate diffuse flux from total and direct - // This only occurs at the bottom level (k index) - parallel_for (SimpleBounds<3>(ncol, 1, nswbands), YAKL_LAMBDA (int icol, int ilev, int ibnd) - { - flux_dn_diffuse(icol,ilev,ibnd) = fluxes.bnd_flux_dn(icol,ilev,ibnd) - - fluxes.bnd_flux_dn_dir(icol,ilev,ibnd); - }); - - // Populate the LSM data structure (this is a 2D MF) - for (MFIter mfi(*(m_lsm_fluxes)); mfi.isValid(); ++mfi) { - auto lsm_array = m_lsm_fluxes->array(mfi); - const auto& box3d = mfi.tilebox(); - auto nx = box3d.length(0); - const int offset = rank_offsets[mfi.LocalIndex()]; - amrex::ParallelFor(box3d, [=] AMREX_GPU_DEVICE (int i, int j, int k) - { - // Map (col,lev) to (i,j,k) - auto icol = (j-box3d.smallEnd(1))*nx + (i-box3d.smallEnd(0)) + 1 + offset; - auto ilev = k+1; - - // Direct fluxes - Real sum1(0.0), sum2(0.0); - for (int ibnd(1); ibnd<=9; ++ibnd) { - sum1 += fluxes.bnd_flux_dn_dir(icol,ilev,ibnd); - } - for (int ibnd(11); ibnd<=14; ++ibnd) { - sum2 += fluxes.bnd_flux_dn_dir(icol,ilev,ibnd); - } - sum1 += 0.5 * fluxes.bnd_flux_dn_dir(icol,ilev,10); - sum2 += 0.5 * fluxes.bnd_flux_dn_dir(icol,ilev,10); - lsm_array(i,j,k,0) = sum1; - lsm_array(i,j,k,1) = sum2; - - // Diffuse fluxes - sum1=0.0; sum2=0.0; - for (int ibnd(1); ibnd<=9; ++ibnd) { - sum1 += flux_dn_diffuse(icol,ilev,ibnd); - } - for (int ibnd(11); ibnd<=14; ++ibnd) { - sum2 += flux_dn_diffuse(icol,ilev,ibnd); - } - sum1 += 0.5 * flux_dn_diffuse(icol,ilev,10); - sum2 += 0.5 * flux_dn_diffuse(icol,ilev,10); - lsm_array(i,j,k,2) = sum1; - lsm_array(i,j,k,3) = sum2; - - // Net fluxes - lsm_array(i,j,k,4) = fluxes.flux_net(icol,ilev); - }); - } - } else if (band == "longwave") { - // Populate the LSM data structure (this is a 2D MF) - for (MFIter mfi(*(m_lsm_fluxes)); mfi.isValid(); ++mfi) { - auto lsm_array = m_lsm_fluxes->array(mfi); - const auto& box3d = mfi.tilebox(); - auto nx = box3d.length(0); - const int offset = rank_offsets[mfi.LocalIndex()]; - amrex::ParallelFor(box3d, [=] AMREX_GPU_DEVICE (int i, int j, int k) - { - // Map (col,lev) to (i,j,k) - auto icol = (j-box3d.smallEnd(1))*nx + (i-box3d.smallEnd(0)) + 1 + offset; - auto ilev = k+1; - - // Net fluxes - lsm_array(i,j,k,5) = fluxes.flux_dn(icol,ilev); - }); - } - } else { - amrex::Abort("Unknown radiation band type!"); + // Local copies + const auto ncol = m_ncol; + const auto nlay = m_nlay; + const auto nlwbands = m_nlwbands; + const auto nswbands = m_nswbands; + const auto nlwgpts = m_nlwgpts; + const auto do_aerosol_rad = m_do_aerosol_rad; + + // Compute orbital parameters; these are used both for computing + // the solar zenith angle and also for computing total solar + // irradiance scaling (tsi_scaling). + Real obliqr, lambm0, mvelpp; + auto orbital_year = m_orbital_year; + auto eccen = m_orbital_eccen; + auto obliq = m_orbital_obliq; + auto mvelp = m_orbital_mvelp; + if (eccen >= 0 && obliq >= 0 && mvelp >= 0) { + // fixed orbital parameters forced with orbital_year == ORB_UNDEF_INT + orbital_year = ORB_UNDEF_INT; } -} - -// call back -void Radiation::on_complete () { } - -void Radiation::yakl_to_mf(const real2d &data, amrex::MultiFab &mf) -{ - // creates a MF from a YAKL real2d mapping from [col, lev] to [x,y,z] - // by reshaping the yakl array to the geometry of the output qsrc multifab - mf = amrex::MultiFab(m_box, qrad_src->DistributionMap(), 1, 0); - if (!data.initialized()) - { - // yakl array hasn't been created yet, so create an empty MF - mf.setVal(0.0); - return; + orbital_params(&orbital_year, &eccen, &obliq, + &mvelp, &obliqr, &lambm0, &mvelpp); + + // Use the orbital parameters to calculate the solar declination and eccentricity factor + Real delta, eccf; + // TODO: Generalize this. + auto calday = (m_orbital_mon-1.0)*365.0/12.0 + m_orbital_day; // Want day + fraction; calday 1 == Jan 1 0Z + orbital_decl(calday, eccen, mvelpp, + lambm0, obliqr, &delta, &eccf); + + // Overwrite eccf if using a fixed solar constant. + auto fixed_total_solar_irradiance = m_fixed_total_solar_irradiance; + if (fixed_total_solar_irradiance >= 0){ + eccf = fixed_total_solar_irradiance/1360.9; } - for (MFIter mfi(mf); mfi.isValid(); ++mfi) { - auto mf_arr = mf.array(mfi); - const auto& box3d = mfi.tilebox(); - const int nx = box3d.length(0); - const int offset = rank_offsets[mfi.LocalIndex()]; - amrex::ParallelFor(box3d, [=] AMREX_GPU_DEVICE (int i, int j, int k) - { - // map [i,j,k] 0-based to [icol, ilev] 1-based - const int icol = (j-box3d.smallEnd(1))*nx + (i-box3d.smallEnd(0)) + 1 + offset; - const int ilev = k+1; - AMREX_ASSERT(icol <= static_cast(data.get_dimensions()(1))); - AMREX_ASSERT(ilev <= static_cast(data.get_dimensions()(2))); - mf_arr(i, j, k) = data(icol, ilev); - }); + // Precompute volume mixing ratio (VMR) for all gases + // + // H2O is obtained from qv. + // All other comps are set to constants for now + for (int igas(0); igas < m_ngas; ++igas) { + auto name = m_gas_names[igas]; + auto gas_mol_weight = m_mol_weight_gas[igas]; + if (name == "H2O") { + parallel_for(SimpleBounds<2>(ncol, nlay), YAKL_LAMBDA (int icol, int ilay) + { + //h2o_vmr(icol,ilay) = qv(icol,ilay) / (1.0 - qv(icol,ilay)) * mwdair/gas_mol_weight; + tmp2d(icol,ilay) = qv(icol,ilay) * mwdair/ gas_mol_weight; + }); + } else if (name == "CO2") { + yakl::memset(tmp2d, m_co2vmr); + } else if (name == "O3") { + yakl::memset(tmp2d, m_o3vmr ); + } else if (name == "N2O") { + yakl::memset(tmp2d, m_n2ovmr); + } else if (name == "CO") { + yakl::memset(tmp2d, m_covmr ); + } else if (name == "CH4") { + yakl::memset(tmp2d, m_ch4vmr); + } else if (name == "O2") { + yakl::memset(tmp2d, m_o2vmr ); + } else if (name == "N2") { + yakl::memset(tmp2d, m_n2vmr ); + } else { + Abort("Radiation: Unknown gas component."); + } + + // Populate GasConcs object + m_gas_concs.set_vmr(name, tmp2d); } -} -void Radiation::expand_yakl1d_to_mf(const real1d &data, amrex::MultiFab &mf) -{ - // copies the 1D yakl data to a 3D MF - AMREX_ASSERT(data.get_dimensions()(1) == ncol); - mf = amrex::MultiFab(m_box, qrad_src->DistributionMap(), 1, 0); - if (!data.initialized()) - { - // yakl array hasn't been created yet, so create an empty MF - mf.setVal(0.0); - return; - } - for (MFIter mfi(mf); mfi.isValid(); ++mfi) { - auto mf_arr = mf.array(mfi); - const auto& box3d = mfi.tilebox(); - const int nx = box3d.length(0); - const int offset = rank_offsets[mfi.LocalIndex()]; - amrex::ParallelFor(box3d, [=] AMREX_GPU_DEVICE (int i, int j, int k) + // TODO: No LSM so leaving comment for code + // Calculate T_int from longwave flux up from the surface, assuming + // blackbody emission with emissivity of 1. + + /* + // NOTE: mu0 is HACKED to a constant + //==================================== + + // Determine the cosine zenith angle. + // This must be done on HOST and copied to device. + realHost1d h_mu0("h_mu0", ncol); + if (m_fixed_solar_zenith_angle > 0) { + yakl::memset(h_mu0, m_fixed_solar_zenith_angle); + } else { + parallel_for(ncol, YAKL_LAMBDA (int icol) { - // map [i,j,k] 0-based to [icol, ilev] 1-based - const int icol = (j-box3d.smallEnd(1))*nx + (i-box3d.smallEnd(0)) + 1 + offset; - AMREX_ASSERT(icol <= static_cast(data.get_dimensions()(1))); - mf_arr(i, j, k) = data(icol); + // Convert lat/lon to radians + double lat = h_lat(icol)*PC::Pi/180.0; + double lon = h_lon(icol)*PC::Pi/180.0; + h_mu0(icol) = orbital_cos_zenith(calday, lat, lon, delta, m_rad_freq_in_steps * dt); }); } -} - -void Radiation::writePlotfile(const std::string& plot_prefix, const amrex::Real time, const int level_step) -{ - // Note: Radiation::initialize() is not called until the first timestep, so skip over the initial file write at t=0 - if (!qrad_src) - { - return; - } + mu0.deep_copy_to(h_mu0); + */ - std::string plotfilename = amrex::Concatenate(plot_prefix + "_rad", level_step, 5); - - // list of real2d (3D) variables to plot - amrex::Vector plotvars_2d; - plotvars_2d.push_back(&cld); - plotvars_2d.push_back(&cldfsnow); - plotvars_2d.push_back(&iclwp); - plotvars_2d.push_back(&iciwp); - plotvars_2d.push_back(&icswp); - plotvars_2d.push_back(&dei); - plotvars_2d.push_back(&des); - plotvars_2d.push_back(&lambdac); - plotvars_2d.push_back(&mu); - plotvars_2d.push_back(&rei); - plotvars_2d.push_back(&rel); - - // SW allsky - plotvars_2d.push_back(&sw_fluxes_allsky.flux_up); - plotvars_2d.push_back(&sw_fluxes_allsky.flux_dn); - plotvars_2d.push_back(&sw_fluxes_allsky.flux_net); - plotvars_2d.push_back(&sw_fluxes_allsky.flux_dn_dir); - // SW clearsky - plotvars_2d.push_back(&sw_fluxes_clrsky.flux_up); - plotvars_2d.push_back(&sw_fluxes_clrsky.flux_dn); - plotvars_2d.push_back(&sw_fluxes_clrsky.flux_net); - plotvars_2d.push_back(&sw_fluxes_clrsky.flux_dn_dir); - - // LW allsky - plotvars_2d.push_back(&lw_fluxes_allsky.flux_up); - plotvars_2d.push_back(&lw_fluxes_allsky.flux_dn); - plotvars_2d.push_back(&lw_fluxes_allsky.flux_net); - // LW clearsky - plotvars_2d.push_back(&lw_fluxes_clrsky.flux_up); - plotvars_2d.push_back(&lw_fluxes_clrsky.flux_dn); - plotvars_2d.push_back(&lw_fluxes_clrsky.flux_net); - - plotvars_2d.push_back(&qrs); - plotvars_2d.push_back(&qrl); - plotvars_2d.push_back(&zi); - plotvars_2d.push_back(&clear_rh); - plotvars_2d.push_back(&qrsc); - plotvars_2d.push_back(&qrlc); - plotvars_2d.push_back(&qt); - plotvars_2d.push_back(&qi); - plotvars_2d.push_back(&qc); - plotvars_2d.push_back(&qn); - plotvars_2d.push_back(&tmid); - plotvars_2d.push_back(&pmid); - plotvars_2d.push_back(&pdel); - - //plotvars_2d.push_back(&pint); // NOTE these have nlev + 1 - //plotvars_2d.push_back(&tint); - - //plotvars_2d.push_back(&albedo_dir); // [nswbands, ncol] - //plotvars_2d.push_back(&albedo_dif); - - // names of plotted variables - amrex::Vector varnames_2d; - varnames_2d.push_back("cld"); - varnames_2d.push_back("cldfsnow"); - varnames_2d.push_back("iclwp"); - varnames_2d.push_back("iciwp"); - varnames_2d.push_back("icswp"); - varnames_2d.push_back("dei"); - varnames_2d.push_back("des"); - varnames_2d.push_back("lambdac"); - varnames_2d.push_back("mu"); - varnames_2d.push_back("rei"); - varnames_2d.push_back("rel"); - - // SW allsky - varnames_2d.push_back("sw_fluxes_allsky.flux_up"); - varnames_2d.push_back("sw_fluxes_allsky.flux_dn"); - varnames_2d.push_back("sw_fluxes_allsky.flux_net"); - varnames_2d.push_back("sw_fluxes_allsky.flux_dn_dir"); - // SW clearsky - varnames_2d.push_back("sw_fluxes_clrsky.flux_up"); - varnames_2d.push_back("sw_fluxes_clrsky.flux_dn"); - varnames_2d.push_back("sw_fluxes_clrsky.flux_net"); - varnames_2d.push_back("sw_fluxes_clrsky.flux_dn_dir"); - - // LW allsky - varnames_2d.push_back("lw_fluxes_allsky.flux_up"); - varnames_2d.push_back("lw_fluxes_allsky.flux_dn"); - varnames_2d.push_back("lw_fluxes_allsky.flux_net"); - // LW clearsky - varnames_2d.push_back("lw_fluxes_clrsky.flux_up"); - varnames_2d.push_back("lw_fluxes_clrsky.flux_dn"); - varnames_2d.push_back("lw_fluxes_clrsky.flux_net"); - - varnames_2d.push_back("qrs"); - varnames_2d.push_back("qrl"); - varnames_2d.push_back("zi"); - varnames_2d.push_back("clear_rh"); - varnames_2d.push_back("qrsc"); - varnames_2d.push_back("qrlc"); - varnames_2d.push_back("qt"); - varnames_2d.push_back("qi"); - varnames_2d.push_back("qc"); - varnames_2d.push_back("qn"); - varnames_2d.push_back("tmid"); - varnames_2d.push_back("pmid"); - varnames_2d.push_back("pdel"); - - //varnames_2d.push_back("pint"); // NOTE these have nlev + 1 - //varnames_2d.push_back("tint"); - - //varnames_2d.push_back("albedo_dir"); - //varnames_2d.push_back("albedo_dif"); - - // list 3D variables defined over bands (SW and LW) - // these are 4D fields split into 3D so they can use the same AMReX plotfile - amrex::Vector plotvars_3d; - amrex::Vector varnames_3d; - plotvars_3d.push_back(&cld_tau_bnd_sw); - plotvars_3d.push_back(&cld_ssa_bnd_sw); - plotvars_3d.push_back(&cld_asm_bnd_sw); - plotvars_3d.push_back(&aer_tau_bnd_sw); - plotvars_3d.push_back(&aer_ssa_bnd_sw); - plotvars_3d.push_back(&aer_asm_bnd_sw); - plotvars_3d.push_back(&cld_tau_bnd_lw); - plotvars_3d.push_back(&aer_tau_bnd_lw); - // diagnostic variables: - plotvars_3d.push_back(&liq_tau_bnd_sw); - plotvars_3d.push_back(&ice_tau_bnd_sw); - plotvars_3d.push_back(&snw_tau_bnd_sw); - plotvars_3d.push_back(&liq_tau_bnd_lw); - plotvars_3d.push_back(&ice_tau_bnd_lw); - plotvars_3d.push_back(&snw_tau_bnd_lw); - - varnames_3d.push_back("cld_tau_bnd_sw"); - varnames_3d.push_back("cld_ssa_bnd_sw"); - varnames_3d.push_back("cld_asm_bnd_sw"); - varnames_3d.push_back("aer_tau_bnd_sw"); - varnames_3d.push_back("aer_ssa_bnd_sw"); - varnames_3d.push_back("aer_asm_bnd_sw"); - varnames_3d.push_back("cld_tau_bnd_lw"); - varnames_3d.push_back("aer_tau_bnd_lw"); - // diagnostic variables: - varnames_3d.push_back("liq_tau_bnd_sw"); - varnames_3d.push_back("ice_tau_bnd_sw"); - varnames_3d.push_back("snw_tau_bnd_sw"); - varnames_3d.push_back("liq_tau_bnd_lw"); - varnames_3d.push_back("ice_tau_bnd_lw"); - varnames_3d.push_back("snw_tau_bnd_lw"); - - AMREX_ASSERT(varnames_2d.size() == plotvars_2d.size()); - AMREX_ASSERT(varnames_3d.size() == plotvars_3d.size()); - - int output_size = plotvars_2d.size() + 1; // 2D vars + coszrs - // add in total output size of all 3D vars - for (int i = 0; i < plotvars_3d.size(); i++) - { - output_size += plotvars_3d[i]->get_dimensions()(3); - } + // Compute layer cloud mass (per unit area) + rrtmgp::mixing_ratio_to_cloud_mass(qc, cldfrac_tot, p_del, lwp); + rrtmgp::mixing_ratio_to_cloud_mass(qi, cldfrac_tot, p_del, iwp); - // convert each YAKL array to a MF and combine into a single MF for plotting - MultiFab fab(m_box, qrad_src->DistributionMap(), output_size, 0); - // copy 2D vars first - for (int i = 0; i < plotvars_2d.size(); i++) + // Convert to g/m2 (needed by RRTMGP) + parallel_for(SimpleBounds<2>(ncol, nlay), YAKL_LAMBDA (int icol, int ilay) { - amrex::MultiFab mf; - yakl_to_mf(*plotvars_2d[i], mf); - MultiFab::Copy(fab, mf, 0, i, 1, 0); - } - - int dst = plotvars_2d.size(); // output component index - - // expand coszrs from 2D to 3D so it can be saved with the same file - varnames_2d.push_back("coszrs"); - amrex::MultiFab coszrs_mf; - expand_yakl1d_to_mf(coszrs, coszrs_mf); - MultiFab::Copy(fab, coszrs_mf, 0, dst, 1, 0); - dst++; + lwp(icol,ilay) *= 1.e3; + iwp(icol,ilay) *= 1.e3; + }); - // copy 3D vars - for (int i = 0; i < plotvars_3d.size(); i++) + // Compute band-by-band surface_albedos. This is needed since + // the AD passes broadband albedos, but rrtmgp require band-by-band. + rrtmgp::compute_band_by_band_surface_albedos(ncol, nswbands, + sfc_alb_dir_vis, sfc_alb_dir_nir, + sfc_alb_dif_vis, sfc_alb_dif_nir, + sfc_alb_dir, sfc_alb_dif); + + // Run RRTMGP driver + rrtmgp::rrtmgp_main(ncol, m_nlay, + p_lay, t_lay, p_lev, t_lev, + m_gas_concs, + sfc_alb_dir, sfc_alb_dif, mu0, + lwp, iwp, rel, rei, cldfrac_tot, + aero_tau_sw, aero_ssa_sw, aero_g_sw, aero_tau_lw, + cld_tau_sw_bnd, cld_tau_lw_bnd, + cld_tau_sw_gpt, cld_tau_lw_gpt, + sw_flux_up, sw_flux_dn, sw_flux_dn_dir, lw_flux_up, lw_flux_dn, + sw_clnclrsky_flux_up, sw_clnclrsky_flux_dn, sw_clnclrsky_flux_dn_dir, + sw_clrsky_flux_up, sw_clrsky_flux_dn, sw_clrsky_flux_dn_dir, + sw_clnsky_flux_up, sw_clnsky_flux_dn, sw_clnsky_flux_dn_dir, + lw_clnclrsky_flux_up, lw_clnclrsky_flux_dn, + lw_clrsky_flux_up, lw_clrsky_flux_dn, + lw_clnsky_flux_up, lw_clnsky_flux_dn, + sw_bnd_flux_up, sw_bnd_flux_dn, sw_bnd_flux_dir, lw_bnd_flux_up, lw_bnd_flux_dn, + eccf, m_atm_logger, + m_extra_clnclrsky_diag, m_extra_clnsky_diag); + + + // Update heating tendency + rrtmgp::compute_heating_rate(sw_flux_up, sw_flux_dn, r_lay, d_dz, sw_heating); + rrtmgp::compute_heating_rate(lw_flux_up, lw_flux_dn, r_lay, d_dz, lw_heating); + + + // Compute surface fluxes + const int kbot = nlay + 1; // Should this be 1 for our layout? + parallel_for(SimpleBounds<3>(ncol, nlay+1, nswbands), YAKL_LAMBDA (int icol, int ilay, int ibnd) { - // split real3ds into real2d for each band for output - // TODO: find a better way to do this - const int var_nbands = plotvars_3d[i]->get_dimensions()(3); // either nswbands or nlwbands - for (int bnd = 1; bnd <= var_nbands; bnd++) - { - // add variable name to 2d list - varnames_2d.push_back(varnames_3d[i] + "_" + std::to_string(bnd)); + sw_bnd_flux_dif(icol,ilay,ibnd) = sw_bnd_flux_dn(icol,ilay,ibnd) - sw_bnd_flux_dir(icol,ilay,ibnd); + }); + rrtmgp::compute_broadband_surface_fluxes(ncol, kbot, nswbands, + sw_bnd_flux_dir , sw_bnd_flux_dif , + sfc_flux_dir_vis, sfc_flux_dir_nir, + sfc_flux_dif_vis, sfc_flux_dif_nir); + + + // TODO: Verify these are not needed, we don't have such output variables + //======================================================================= + + // Compute diagnostic total cloud area (vertically-projected cloud cover) + real1d cldlow ("cldlow", ncol); + real1d cldmed ("cldmed", ncol); + real1d cldhgh ("cldhgh", ncol); + real1d cldtot ("cldtot", ncol); + // NOTE: limits for low, mid, and high clouds are mostly taken from EAM F90 source, with the + // exception that I removed the restriction on low clouds to be above (numerically lower pressures) + // 1200 hPa, and on high clouds to be below (numerically high pressures) 50 hPa. This probably + // does not matter in practice, as clouds probably should not be produced above 50 hPa and we + // should not be encountering surface pressure above 1200 hPa, but in the event that things go off + // the rails we might want to look at these still. + rrtmgp::compute_cloud_area(ncol, nlay, nlwgpts, 700e2, std::numeric_limits::max(), p_lay, cld_tau_lw_gpt, cldlow); + rrtmgp::compute_cloud_area(ncol, nlay, nlwgpts, 400e2, 700e2, p_lay, cld_tau_lw_gpt, cldmed); + rrtmgp::compute_cloud_area(ncol, nlay, nlwgpts, 0, 400e2, p_lay, cld_tau_lw_gpt, cldhgh); + rrtmgp::compute_cloud_area(ncol, nlay, nlwgpts, 0, std::numeric_limits::max(), p_lay, cld_tau_lw_gpt, cldtot); + + + // Compute cloud-top diagnostics following AeroCOM recommendation + auto idx_067 = rrtmgp::get_wavelength_index_sw(0.67e-6); // Get visible 0.67 micron band for COSP + auto idx_105 = rrtmgp::get_wavelength_index_lw(10.5e-6); // Get IR 10.5 micron band for COSP + // Compute cloud-top diagnostics following AeroCom recommendation + real1d cdnc_at_cldtop ("cdnc_at_cldtop" , ncol); + real1d T_mid_at_cldtop ("T_mid_at_cldtop", ncol); + real1d p_mid_at_cldtop ("p_mid_at_cldtop", ncol); + real1d cldfrac_ice_at_cldtop ("cldfrac_ice_at_cldtop", ncol); + real1d cldfrac_liq_at_cldtop ("cldfrac_liq_at_cldtop", ncol); + real1d cldfrac_tot_at_cldtop ("cldfrac_tot_at_cldtop", ncol); + real1d eff_radius_qc_at_cldtop ("eff_radius_qc_at_cldtop", ncol); + real1d eff_radius_qi_at_cldtop ("eff_radius_qi_at_cldtop", ncol); + rrtmgp::compute_aerocom_cloudtop(ncol, nlay, t_lay, p_lay, p_del, z_del, qc, qi, rel, rei, cldfrac_tot, + nc, T_mid_at_cldtop, p_mid_at_cldtop, cldfrac_ice_at_cldtop, + cldfrac_liq_at_cldtop, cldfrac_tot_at_cldtop, cdnc_at_cldtop, + eff_radius_qc_at_cldtop, eff_radius_qi_at_cldtop); +} - amrex::MultiFab mf; - real2d band_var = plotvars_3d[i]->slice<2>(yakl::COLON, yakl::COLON, bnd); - yakl_to_mf(band_var, mf); - MultiFab::Copy(fab, mf, 0, dst, 1, 0); - dst++; - } - } - AMREX_ASSERT(dst == fab.nComp()); - - // this should now match full output size with all 3D variables expanded - AMREX_ASSERT(varnames_2d.size() == output_size); +void +Radiation::finalize_impl () +{ + // Finish rrtmgp + m_gas_concs.reset(); + rrtmgp::rrtmgp_finalize(); + // Fill the AMReX MFs from YAKL Arrays + yakl_buffers_to_mf(); - amrex::WriteSingleLevelPlotfile(plotfilename, fab, varnames_2d, m_geom, time, level_step); + // Deallocate the buffer arrays + dealloc_buffers(); } - diff --git a/Source/Radiation/ERF_RunLongWaveRRTMGP.cpp b/Source/Radiation/ERF_RunLongWaveRRTMGP.cpp deleted file mode 100644 index 50c8d6a56..000000000 --- a/Source/Radiation/ERF_RunLongWaveRRTMGP.cpp +++ /dev/null @@ -1,146 +0,0 @@ -#include "mo_gas_concentrations.h" -#include "mo_gas_optics_rrtmgp.h" -#include "mo_load_coefficients.h" -#include "mo_rte_sw.h" -#include "mo_rte_lw.h" -#include "mo_optical_props.h" -#include "mo_fluxes_byband.h" -#include "ERF_RRTMGP.H" - -void Rrtmgp::run_longwave_rrtmgp (int ngas, int ncol, int nlay, - const real3d& gas_vmr, - const real2d& pmid, const real2d& tmid, const real2d& pint, const real2d& tint, - const real2d& emis_sfc , - const real3d& cld_tau_gpt , const real3d& aer_tau_bnd , - const real2d& allsky_flux_up , const real2d& allsky_flux_dn , const real2d& allsky_flux_net , - const real3d& allsky_bnd_flux_up, const real3d& allsky_bnd_flux_dn, const real3d& allsky_bnd_flux_net, - const real2d& clrsky_flux_up , const real2d& clrsky_flux_dn , const real2d& clrsky_flux_net , - const real3d& clrsky_bnd_flux_up, const real3d& clrsky_bnd_flux_dn, const real3d& clrsky_bnd_flux_net) -{ - // Wrap pointers in YAKL arrays - int nlwbands = k_dist_lw.get_nband(); - int nlwgpts = k_dist_lw.get_ngpt(); - - // Populate gas concentrations - GasConcs gas_concs; - gas_concs.init(active_gases, ncol, nlay); - real2d tmp2d; - tmp2d = real2d("tmp", ncol, nlay); - for (int igas = 1; igas <= ngas; igas++) { - parallel_for(SimpleBounds<2>(nlay,ncol), YAKL_LAMBDA(int ilay, int icol) - { - tmp2d(icol,ilay) = gas_vmr(igas,icol,ilay); - }); - gas_concs.set_vmr(active_gases(igas), tmp2d); - } - - // Boundary conditions - SourceFuncLW lw_sources; - lw_sources.alloc(ncol, nlay, k_dist_lw); - - // Weights and angle secants for first order (k=1) Gaussian quadrature. - // Values from Table 2, Clough et al, 1992, doi:10.1029/92JD01419 - // after Abramowitz & Stegun 1972, page 921 - int constexpr max_gauss_pts = 4; - realHost2d gauss_Ds_host ("gauss_Ds" ,max_gauss_pts,max_gauss_pts); - gauss_Ds_host(1,1) = 1.66_wp ; gauss_Ds_host(2,1) = 0._wp; gauss_Ds_host(3,1) = 0._wp; gauss_Ds_host(4,1) = 0._wp; - gauss_Ds_host(1,2) = 1.18350343_wp; gauss_Ds_host(2,2) = 2.81649655_wp; gauss_Ds_host(3,2) = 0._wp; gauss_Ds_host(4,2) = 0._wp; - gauss_Ds_host(1,3) = 1.09719858_wp; gauss_Ds_host(2,3) = 1.69338507_wp; gauss_Ds_host(3,3) = 4.70941630_wp; gauss_Ds_host(4,3) = 0._wp; - gauss_Ds_host(1,4) = 1.06056257_wp; gauss_Ds_host(2,4) = 1.38282560_wp; gauss_Ds_host(3,4) = 2.40148179_wp; gauss_Ds_host(4,4) = 7.15513024_wp; - - realHost2d gauss_wts_host("gauss_wts",max_gauss_pts,max_gauss_pts); - gauss_wts_host(1,1) = 0.5_wp ; gauss_wts_host(2,1) = 0._wp ; gauss_wts_host(3,1) = 0._wp ; gauss_wts_host(4,1) = 0._wp ; - gauss_wts_host(1,2) = 0.3180413817_wp; gauss_wts_host(2,2) = 0.1819586183_wp; gauss_wts_host(3,2) = 0._wp ; gauss_wts_host(4,2) = 0._wp ; - gauss_wts_host(1,3) = 0.2009319137_wp; gauss_wts_host(2,3) = 0.2292411064_wp; gauss_wts_host(3,3) = 0.0698269799_wp; gauss_wts_host(4,3) = 0._wp ; - gauss_wts_host(1,4) = 0.1355069134_wp; gauss_wts_host(2,4) = 0.2034645680_wp; gauss_wts_host(3,4) = 0.1298475476_wp; gauss_wts_host(4,4) = 0.0311809710_wp; - - real2d gauss_Ds ("gauss_Ds" ,max_gauss_pts,max_gauss_pts); - real2d gauss_wts("gauss_wts",max_gauss_pts,max_gauss_pts); - gauss_Ds_host .deep_copy_to(gauss_Ds ); - gauss_wts_host.deep_copy_to(gauss_wts); - - // Populate optical property objects - OpticalProps1scl combined_optics; - combined_optics.alloc_1scl(ncol, nlay, k_dist_lw); - bool1d top_at_1_g("top_at_1_g",1); - boolHost1d top_at_1_h("top_at_1_h",1); - bool top_at_1; - real1d t_sfc("t_sfc", ncol); - parallel_for(SimpleBounds<1>(ncol), YAKL_LAMBDA (int icol) - { - t_sfc(icol) = tint(icol,nlay+1); - top_at_1_g(1) = pmid(1, 1) < pmid (1, nlay); - }); - top_at_1_g.deep_copy_to(top_at_1_h); - top_at_1 = top_at_1_h(1); - k_dist_lw.gas_optics(ncol, nlay, top_at_1, pmid, pint, tmid, t_sfc, gas_concs, combined_optics, lw_sources, real2d(), tint); - - // Add in aerosol; we can define this by bands or gpoints. If we define by - // bands, then internally when increment() is called it will map these to - // gpoints. Not sure if there is a beneift one way or another. - OpticalProps1scl aerosol_optics; - auto &aerosol_optics_tau = aerosol_optics.tau; - if (false) { - aerosol_optics.alloc_1scl(ncol, nlay, k_dist_lw); - auto gpt_bnd = aerosol_optics.get_gpoint_bands(); - parallel_for(SimpleBounds<3>(nlwgpts,nlay,ncol) , YAKL_LAMBDA (int igpt, int ilay, int icol) - { - aerosol_optics_tau(icol,ilay,igpt) = aer_tau_bnd(icol,ilay,gpt_bnd(igpt)); - }); - } else { - aerosol_optics.alloc_1scl(ncol, nlay, k_dist_lw.get_band_lims_wavenumber()); - parallel_for(SimpleBounds<3>(nlwbands,nlay,ncol), YAKL_LAMBDA (int ibnd, int ilay, int icol) - { - aerosol_optics_tau(icol,ilay,ibnd) = aer_tau_bnd(icol,ilay,ibnd); - }); - } - aerosol_optics.increment(combined_optics); - - // Do the clearsky calculation before adding in clouds - FluxesByband fluxes_clrsky; - fluxes_clrsky.flux_up = real2d("clrsky_flux_up" , ncol, nlay+1); // clrsky_flux_up; - fluxes_clrsky.flux_dn = real2d("clrsky_flux_dn" , ncol, nlay+1); //clrsky_flux_dn; - fluxes_clrsky.flux_net = real2d("clrsky_flux_net", ncol, nlay+1); //clrsky_flux_net; - fluxes_clrsky.bnd_flux_up = real3d("clrsky_bnd_flux_up" , ncol, nlay+1, nlwbands); //clrsky_bnd_flux_up; - fluxes_clrsky.bnd_flux_dn = real3d("clrsky_bnd_flux_dn" , ncol, nlay+1, nlwbands); //clrsky_bnd_flux_dn; - fluxes_clrsky.bnd_flux_net = real3d("clrsky_bnd_flux_net", ncol, nlay+1, nlwbands); //clrsky_bnd_flux_net; - - rte_lw(max_gauss_pts, gauss_Ds, gauss_wts, combined_optics, top_at_1, lw_sources, emis_sfc, fluxes_clrsky); - - // Copy fluxes back out of FluxesByband object - fluxes_clrsky.flux_up.deep_copy_to(clrsky_flux_up); - fluxes_clrsky.flux_dn.deep_copy_to(clrsky_flux_dn); - fluxes_clrsky.flux_net.deep_copy_to(clrsky_flux_net); - fluxes_clrsky.bnd_flux_up.deep_copy_to(clrsky_bnd_flux_up); - fluxes_clrsky.bnd_flux_dn.deep_copy_to(clrsky_bnd_flux_dn); - fluxes_clrsky.bnd_flux_net.deep_copy_to(clrsky_bnd_flux_net); - - // Add in clouds - OpticalProps1scl cloud_optics; - cloud_optics.alloc_1scl(ncol, nlay, k_dist_lw); - auto &cloud_optics_tau = cloud_optics.tau; - parallel_for(SimpleBounds<3>(nlwgpts,nlay,ncol) , YAKL_LAMBDA (int igpt, int ilay, int icol) { - cloud_optics_tau(icol,ilay,igpt) = cld_tau_gpt(icol,ilay,igpt); - }); - cloud_optics.increment(combined_optics); - - // Call LW flux driver - FluxesByband fluxes_allsky; - fluxes_allsky.flux_up = real2d("allsky_flux_up" , ncol, nlay+1); //allsky_flux_up; - fluxes_allsky.flux_dn = real2d("allsky_flux_dn" , ncol, nlay+1); //allsky_flux_dn; - fluxes_allsky.flux_net = real2d("allsky_flux_net", ncol, nlay+1); //allsky_flux_net; - fluxes_allsky.bnd_flux_up = real3d("allsky_bnd_flux_up" , ncol, nlay+1, nlwbands); //allsky_bnd_flux_up; - fluxes_allsky.bnd_flux_dn = real3d("allsky_bnd_flux_dn" , ncol, nlay+1, nlwbands); //allsky_bnd_flux_dn; - fluxes_allsky.bnd_flux_net = real3d("allsky_bnd_flux_net", ncol, nlay+1, nlwbands); //allsky_bnd_flux_net; - - rte_lw(max_gauss_pts, gauss_Ds, gauss_wts, combined_optics, top_at_1, lw_sources, emis_sfc, fluxes_allsky); - - // Copy fluxes back out of FluxesByband object - fluxes_allsky.flux_up.deep_copy_to(allsky_flux_up); - fluxes_allsky.flux_dn.deep_copy_to(allsky_flux_dn); - fluxes_allsky.flux_net.deep_copy_to(allsky_flux_net); - fluxes_allsky.bnd_flux_up.deep_copy_to(allsky_bnd_flux_up); - fluxes_allsky.bnd_flux_dn.deep_copy_to(allsky_bnd_flux_dn); - fluxes_allsky.bnd_flux_net.deep_copy_to(allsky_bnd_flux_net); - yakl::fence(); -} diff --git a/Source/Radiation/ERF_RunShortWaveRRTMGP.cpp b/Source/Radiation/ERF_RunShortWaveRRTMGP.cpp deleted file mode 100644 index 300f72616..000000000 --- a/Source/Radiation/ERF_RunShortWaveRRTMGP.cpp +++ /dev/null @@ -1,150 +0,0 @@ -#include "ERF_Rrtmgp.H" -#include - -void Rrtmgp::run_shortwave_rrtmgp (int ngas, int ncol, int nlay, - const real3d& gas_vmr, const real2d& pmid , const real2d& tmid , const real2d& pint, - const real1d& coszrs , const real2d& albedo_dir, const real2d& albedo_dif, - const real3d& cld_tau_gpt, const real3d& cld_ssa_gpt, const real3d& cld_asm_gpt, - const real3d& aer_tau_bnd, const real3d& aer_ssa_bnd, const real3d& aer_asm_bnd, - const real2d& allsky_flux_up , const real2d& allsky_flux_dn , const real2d& allsky_flux_net , const real2d& allsky_flux_dn_dir, - const real3d& allsky_bnd_flux_up, const real3d& allsky_bnd_flux_dn, const real3d& allsky_bnd_flux_net, const real3d& allsky_bnd_flux_dn_dir, - const real2d& clrsky_flux_up , const real2d& clrsky_flux_dn , const real2d& clrsky_flux_net , const real2d& clrsky_flux_dn_dir, - const real3d& clrsky_bnd_flux_up, const real3d& clrsky_bnd_flux_dn, const real3d& clrsky_bnd_flux_net, const real3d& clrsky_bnd_flux_dn_dir, - double tsi_scaling) -{ - // Wrap pointers in YAKL arrays - int nswbands = k_dist_sw.get_nband(); - int nswgpts = k_dist_sw.get_ngpt(); - - // Populate gas concentrations object - GasConcs gas_concs; - gas_concs.init(active_gases, ncol, nlay); - real2d tmp2d; - tmp2d = real2d("tmp", ncol, nlay); - for (int igas = 1; igas <= ngas; igas++) { - parallel_for(SimpleBounds<2>(nlay,ncol), YAKL_LAMBDA(int ilay, int icol) { - tmp2d(icol,ilay) = gas_vmr(igas,icol,ilay); - }); - gas_concs.set_vmr(active_gases(igas), tmp2d); - } - - // Do gas optics - // TODO: should we avoid allocating here? - OpticalProps2str combined_optics; - combined_optics.alloc_2str(ncol, nlay, k_dist_sw); - bool1d top_at_1_g("top_at_1_g",1); - boolHost1d top_at_1_h("top_at_1_h",1); - bool top_at_1; - parallel_for(SimpleBounds<1>(1), YAKL_LAMBDA (int ilay) { // HACK: Single loop kernel is not efficient - top_at_1_g(1) = pmid(1, 1) < pmid (1, nlay); - }); - top_at_1_g.deep_copy_to(top_at_1_h); - top_at_1 = top_at_1_h(1); - real2d toa_flux("toa_flux", ncol, nswgpts); - -#ifdef AMREX_DEBUG - // print extra info from RRTMGP - k_dist_sw.print_norms(); - - // check pressure is within bounds - real pint_min = yakl::intrinsics::minval(pint); - real pint_max = yakl::intrinsics::maxval(pint); - AMREX_ASSERT(pint_max < k_dist_sw.get_press_max()); - AMREX_ASSERT(pint_min > k_dist_sw.get_press_min()); -#endif - - k_dist_sw.gas_optics(ncol, nlay, top_at_1, pmid, pint, tmid, gas_concs, combined_optics, toa_flux); - - // Apply TOA flux scaling - parallel_for(SimpleBounds<2>(nswgpts,ncol), YAKL_LAMBDA (int igpt, int icol) { - toa_flux(icol, igpt) = tsi_scaling * toa_flux(icol,igpt); - }); - - // Add in aerosol, allocate on gpts and map aer_*_bnd from bands - OpticalProps2str aerosol_optics; - aerosol_optics.alloc_2str(ncol, nlay, k_dist_sw); - auto gpt_bnd = aerosol_optics.get_gpoint_bands(); - parallel_for(SimpleBounds<3>(nswgpts,nlay,ncol) , YAKL_LAMBDA (int igpt, int ilay, int icol) - { - aerosol_optics.tau(icol,ilay,igpt) = aer_tau_bnd(icol,ilay,gpt_bnd(igpt)); - aerosol_optics.ssa(icol,ilay,igpt) = aer_ssa_bnd(icol,ilay,gpt_bnd(igpt)); - aerosol_optics.g (icol,ilay,igpt) = aer_asm_bnd(icol,ilay,gpt_bnd(igpt)); - }); - - aerosol_optics.delta_scale(); - - // NOTE: aero_optics is allocated on nswgpts and combined_optics is on nswgpts - // The `increment` call below can handle matching or differing (nswgpts/nswbnds) - // sizes; see calls in `mo_optical_props_kernels.cpp`. Since we have matching - // gpt sizes, we will call `increment_2stream_by_2stream` - aerosol_optics.increment(combined_optics); - - // Do the clearsky calculation before adding in clouds - FluxesByband fluxes_clrsky; - fluxes_clrsky.flux_up = real2d("clrsky_flux_up" , ncol, nlay+1); // clrsky_flux_up; - fluxes_clrsky.flux_dn = real2d("clrsky_flux_nd" , ncol, nlay+1); //clrsky_flux_dn; - fluxes_clrsky.flux_net = real2d("clrsky_flux_net", ncol, nlay+1); //clrsky_flux_net; - fluxes_clrsky.flux_dn_dir = real2d("clrsky_flux_dn_dir", ncol, nlay+1); //clrsky_flux_dn_dir; - fluxes_clrsky.bnd_flux_up = real3d("clrsky_bnd_flux_up" , ncol, nlay+1, nswbands); //clrsky_bnd_flux_up; - fluxes_clrsky.bnd_flux_dn = real3d("clrsky_bnd_flux_dn" , ncol, nlay+1, nswbands); //clrsky_bnd_flux_dn; - fluxes_clrsky.bnd_flux_net = real3d("clrsky_bnd_flux_net", ncol, nlay+1, nswbands); //clrsky_bnd_flux_net; - fluxes_clrsky.bnd_flux_dn_dir = real3d("clrsky_bnd_flux_dn_dir", ncol, nlay+1, nswbands); //clrsky_bnd_flux_dn_dir; - - rte_sw(combined_optics, top_at_1, coszrs, toa_flux, albedo_dir, albedo_dif, fluxes_clrsky); - - // Copy fluxes back out of FluxesByband object - fluxes_clrsky.flux_up.deep_copy_to (clrsky_flux_up); - fluxes_clrsky.flux_dn.deep_copy_to (clrsky_flux_dn); - fluxes_clrsky.flux_net.deep_copy_to(clrsky_flux_net); - fluxes_clrsky.flux_dn_dir.deep_copy_to(clrsky_flux_dn_dir); - fluxes_clrsky.bnd_flux_up.deep_copy_to (clrsky_bnd_flux_up); - fluxes_clrsky.bnd_flux_dn.deep_copy_to (clrsky_bnd_flux_dn); - fluxes_clrsky.bnd_flux_net.deep_copy_to(clrsky_bnd_flux_net); - fluxes_clrsky.bnd_flux_dn_dir.deep_copy_to(clrsky_bnd_flux_dn_dir); - - // Add in clouds, which are already on gpts - OpticalProps2str cloud_optics; - cloud_optics.alloc_2str(ncol, nlay, k_dist_sw); - auto &cloud_optics_tau = cloud_optics.tau; - auto &cloud_optics_ssa = cloud_optics.ssa; - auto &cloud_optics_g = cloud_optics.g ; - parallel_for(SimpleBounds<3>(nswgpts,nlay,ncol) , YAKL_LAMBDA (int igpt, int ilay, int icol) { - cloud_optics_tau(icol,ilay,igpt) = cld_tau_gpt(icol,ilay,igpt); - cloud_optics_ssa(icol,ilay,igpt) = cld_ssa_gpt(icol,ilay,igpt); - cloud_optics_g (icol,ilay,igpt) = cld_asm_gpt(icol,ilay,igpt); - }); - - cloud_optics.delta_scale(); - - // TODO: Sort through cloud optics, this increment does not - // modify the optical properties (tau/ssa/g) so we get - // the same fluxes and heating rate. - - // NOTE: See above and here we again have matching gpt sizes - cloud_optics.increment(combined_optics); - - // Call SW flux driver - FluxesByband fluxes_allsky; - fluxes_allsky.flux_up = real2d("allsky_flux_up" , ncol, nlay+1); //allsky_flux_up; - fluxes_allsky.flux_dn = real2d("allsky_flux_dn" , ncol, nlay+1); //allsky_flux_dn; - fluxes_allsky.flux_net = real2d("allsky_flux_net", ncol, nlay+1); //allsky_flux_net; - fluxes_allsky.flux_dn_dir = real2d("allsky_flux_dn_dir", ncol, nlay+1); //allsky_flux_dn_dir; - fluxes_allsky.bnd_flux_up = real3d("allsky_bnd_flux_up" , ncol, nlay+1, nswbands); //allsky_bnd_flux_up; - fluxes_allsky.bnd_flux_dn = real3d("allsky_bnd_flux_dn" , ncol, nlay+1, nswbands); //allsky_bnd_flux_dn; - fluxes_allsky.bnd_flux_net = real3d("allsky_bnd_flux_net", ncol, nlay+1, nswbands); //allsky_bnd_flux_net; - fluxes_allsky.bnd_flux_dn_dir = real3d("allsky_bnd_flux_dn_dir", ncol, nlay+1, nswbands); //allsky_bnd_flux_dn_dir; - - rte_sw(combined_optics, top_at_1, coszrs, toa_flux, albedo_dir, albedo_dif, fluxes_allsky); - - // Copy fluxes back out of FluxesByband object - fluxes_allsky.flux_up.deep_copy_to(allsky_flux_up); - fluxes_allsky.flux_dn.deep_copy_to(allsky_flux_dn); - fluxes_allsky.flux_net.deep_copy_to(allsky_flux_net); - fluxes_allsky.flux_dn_dir.deep_copy_to(allsky_flux_dn_dir); - fluxes_allsky.bnd_flux_up.deep_copy_to(allsky_bnd_flux_up); - fluxes_allsky.bnd_flux_dn.deep_copy_to(allsky_bnd_flux_dn); - fluxes_allsky.bnd_flux_net.deep_copy_to(allsky_bnd_flux_net); - fluxes_allsky.bnd_flux_dn_dir.deep_copy_to(allsky_bnd_flux_dn_dir); - yakl::fence(); -} - diff --git a/Source/Radiation/ERF_Slingo.H b/Source/Radiation/ERF_Slingo.H deleted file mode 100644 index 17b6a93d9..000000000 --- a/Source/Radiation/ERF_Slingo.H +++ /dev/null @@ -1,157 +0,0 @@ -//------------------------------------------------------------------------------------------------ -// Implements Slingo Optics for MG/RRTMG for liquid clouds and -// a copy of the old cloud routine for reference -//------------------------------------------------------------------------------------------------ -#ifndef ERF_SLINGO_H_ -#define ERF_SLINGO_H_ -using yakl::fortran::parallel_for; -using yakl::fortran::SimpleBounds; - -class Slingo { - public: - static void slingo_liq_optics_sw (int ncol, int nlev, int nswbands, - const real2d& cldn, const real2d& cliqwp, const real2d& rel, - const real3d& liq_tau, const real3d& liq_tau_w, - const real3d& liq_tau_w_g, const real3d& liq_tau_w_f) - { - real1d wavmin("wavmin", nswbands); - real1d wavmax("wavmax", nswbands); - - // Minimum cloud amount (as a fraction of the grid-box area) to - // distinguish from clear sky - const real cldmin = 1.0e-80; - - // Decimal precision of cloud amount (0 -> preserve full resolution; - // 10^-n -> preserve n digits of cloud amount) - const real cldeps = 0.0; - - // A. Slingo's data for cloud particle radiative properties (from 'A GCM - // Parameterization for the Shortwave Properties of Water Clouds' JAS - // vol. 46 may 1989 pp 1419-1427) - real1d abarl("abarl", 4); // A coefficient for extinction optical depth - real1d bbarl("bbarl", 4); // B coefficient for extinction optical depth - real1d cbarl("cbarl", 4); // C coefficient for extinction optical depth - real1d dbarl("dbarl", 4); // D coefficient for extinction optical depth - real1d ebarl("ebarl", 4); // E coefficient for extinction optical depth - real1d fbarl("fbarl", 4); // F coefficient for extinction optical depth - parallel_for(SimpleBounds<1>(1), YAKL_LAMBDA (int i) - { - abarl(1) = 2.817e-02; - abarl(2) = 2.682e-02; - abarl(3) = 2.264e-02; - abarl(4) = 1.281e-02; - bbarl(1) = 1.305; - bbarl(2) = 1.346; - bbarl(3) = 1.454; - bbarl(4) = 1.641; - cbarl(1) = -5.62e-08; - cbarl(2) = -6.94e-06; - cbarl(3) = 4.64e-04; - abarl(4) = 0.201; - dbarl(1) = 1.63e-07; - dbarl(2) = 2.35e-05; - dbarl(3) = 1.24e-03; - dbarl(4) = 7.56e-03; - ebarl(1) = 0.829; - ebarl(2) = 0.794; - ebarl(3) = 0.754; - ebarl(4) = 0.826; - fbarl(1) = 2.482e-03; - fbarl(2) = 4.226e-03; - fbarl(3) = 6.560e-03; - fbarl(4) = 4.353e-03; - }); - - // Caution... A. Slingo recommends no less than 4.0 micro-meters nor - // greater than 20 micro-meters. Here we set effective radius limits - // for liquid to the range 4.2 < rel < 16 micron (Slingo 89) - const real rel_min = 4.2; - const real rel_max = 16.; - - int indxsl; - - RadConstants::get_sw_spectral_boundaries(wavmin,wavmax,RadConstants::micrometer); - - for (auto ns=0; ns 2.38) { - indxsl = 4; - } - - // Set cloud extinction optical depth, single scatter albedo, - // asymmetry parameter, and forward scattered fraction: - parallel_for(SimpleBounds<2>(nlev, ncol), YAKL_LAMBDA (int k, int i) - { - auto abarli = abarl(indxsl); - auto bbarli = bbarl(indxsl); - auto cbarli = cbarl(indxsl); - auto dbarli = dbarl(indxsl); - auto ebarli = ebarl(indxsl); - auto fbarli = fbarl(indxsl); - real tmp1l, tmp2l, tmp3l, g; - - // note that optical properties for liquid valid only - // in range of 4.2 > rel > 16 micron (Slingo 89) - if (cldn(i,k) >= cldmin && cldn(i,k) >= cldeps) { - tmp1l = abarli + bbarli/std::min(std::max(rel_min,rel(i,k)),rel_max); - liq_tau(ns,i,k) = 1000.*cliqwp(i,k)*tmp1l; - } else { - liq_tau(ns,i,k) = 0.0; - } - - tmp2l = 1. - cbarli - dbarli*std::min(std::max(rel_min,rel(i,k)),rel_max); - tmp3l = fbarli*std::min(std::max(rel_min,rel(i,k)),rel_max); - // Do not let single scatter albedo be 1. Delta-eddington solution - // for non-conservative case has different analytic form from solution - // for conservative case, and raddedmx is written for non-conservative case. - liq_tau_w(ns,i,k) = liq_tau(ns,i,k) * std::min(tmp2l,.999999); - g = ebarli + tmp3l; - liq_tau_w_g(ns,i,k) = liq_tau_w(ns,i,k) * g; - liq_tau_w_f(ns,i,k) = liq_tau_w(ns,i,k) * g * g; - }); - } // nswbands - } - - static void slingo_liq_optics_lw (int ncol, int nlev, int nlwbands, const real2d& cldn, - const real2d& iclwpth, const real2d& iciwpth, const real3d& abs_od) - { - real2d ficemr("ficemr", ncol, nlev); - real2d cwp("cwp", ncol, nlev); - real2d cldtau("cldtau", ncol, nlev); - - parallel_for(SimpleBounds<2>(nlev, ncol), YAKL_LAMBDA (int k, int i) - { - cwp (i,k) = 1000.0 * iclwpth(i,k) + 1000.0 * iciwpth(i, k); - ficemr(i,k) = 1000.0 * iciwpth(i,k)/(std::max(1.e-18, cwp(i,k))); - }); - - parallel_for(SimpleBounds<2>(nlev, ncol), YAKL_LAMBDA (int k, int i) - { - // Note from Andrew Conley: - // Optics for RK no longer supported, This is constructed to get - // close to bit for bit. Otherwise we could simply use liquid water path - //note that optical properties for ice valid only - //in range of 13 > rei > 130 micron (Ebert and Curry 92) - real kabsl = 0.090361; - auto kabs = kabsl*(1.-ficemr(i,k)); - cldtau(i,k) = kabs*cwp(i,k); - }); - - parallel_for(SimpleBounds<3>(nlwbands, ncol, nlev), YAKL_LAMBDA (int lwband, int icol, int ilev) - { - abs_od(lwband,icol,ilev) = cldtau(icol,ilev); - }); - } -}; -#endif diff --git a/Source/Radiation/Make.package b/Source/Radiation/Make.package index 9efacfb5f..db393f39f 100644 --- a/Source/Radiation/Make.package +++ b/Source/Radiation/Make.package @@ -1,13 +1,35 @@ -CEXE_headers += ERF_RRTMGP.H -CEXE_headers += ERF_RadConstants.H -CEXE_headers += ERF_Slingo.H -CEXE_headers += ERF_EbertCurry.H -CEXE_headers += ERF_LinearInterpolate.H -CEXE_headers += ERF_PhysProp.H - -CEXE_sources += ERF_Finalize_rrtmgp.cpp -CEXE_sources += ERF_InitRRTMGP.cpp -CEXE_sources += ERF_RunLongWaveRRTMGP.cpp -CEXE_sources += ERF_RunShortWaveRRTMGP.cpp +CEXE_headers += ERF_RRTMGP_Interface.H +CEXE_headers += ERF_RRTMGP_Utils.H +CEXE_headers += ERF_Radiation.H +CEXE_headers += ERF_OrbCosZenith.H +CEXE_sources += ERF_RRTMGP_Interface.cpp +CEXE_sources += ERF_RRTMGP_Utils.cpp +CEXE_sources += ERF_Radiation.cpp +CEXE_sources += ERF_OrbCosZenith.cpp + +# RRTMGP SUBMODULE FILES +#CEXE_sources += mo_rrtmgp_util_reorder.cpp +#CEXE_sources += mo_gas_optics_kernels.cpp +#CEXE_sources += expand_and_transpose.cpp +#CEXE_sources += mo_fluxes_broadband_kernels.cpp +#CEXE_sources += mo_optical_props_kernels.cpp +#CEXE_sources += mo_rte_solver_kernels.cpp +#CEXE_sources += mo_load_coefficients.cpp +#CEXE_sources += mo_garand_atmos_io.cpp +#CEXE_sources += mo_load_cloud_coefficients.cpp +#CEXE_sources += mo_fluxes_byband_kernels.cpp + +#CEXE_headers += mo_gas_concentrations.h +#CEXE_headers += mo_gas_optics_rrtmgp.h +#CEXE_headers += mo_cloud_optics.h +#CEXE_headers += mo_fluxes_byband.h +#CEXE_headers += mo_load_coefficients.h +#CEXE_headers += rrtmgp_const.h +#CEXE_headers += mo_cloud_optics.h +#CEXE_headers += mo_rte_sw.h +#CEXE_headers += mo_rte_lw.h +#CEXE_headers += mo_load_cloud_coefficients.h +#CEXE_headers += rrtmgp_const.h +#CEXE_headers += rrtmgp_conversion.h \ No newline at end of file diff --git a/Source/TimeIntegration/ERF_AdvanceRadiation.cpp b/Source/TimeIntegration/ERF_AdvanceRadiation.cpp index 0c5d912c3..2e027a0d1 100644 --- a/Source/TimeIntegration/ERF_AdvanceRadiation.cpp +++ b/Source/TimeIntegration/ERF_AdvanceRadiation.cpp @@ -7,28 +7,14 @@ void ERF::advance_radiation (int lev, MultiFab& cons, const Real& dt_advance) { - bool do_sw_rad {true}; - bool do_lw_rad {true}; - bool do_aero_rad {true}; - bool do_snow_opt {true}; - bool is_cmip6_volcano {false}; - - rad.initialize(cons, - sw_lw_fluxes[lev].get(), - solar_zenith[lev].get(), - qheating_rates[lev].get(), - lat_m[lev].get(), - lon_m[lev].get(), - qmoist[lev], - grids[lev], - Geom(lev), - dt_advance, - do_sw_rad, - do_lw_rad, - do_aero_rad, - do_snow_opt, - is_cmip6_volcano); - rad.run(); - rad.on_complete(); + // TODO: Address issue with lev>0 not spanning all z? + if (lev == 0) { + rad[lev]->set_grids(lev, istep[lev], t_new[lev], dt_advance, + cons.boxArray(), geom[lev], &(cons), + ls_lw_fluxes[lev], solar_zenith[lev], + qheating_rates[lev], z_phys_nd[lev], + lat_m[lev], lon_m[lev]); + rad[lev]->rad_run_impl(); + } } #endif From 71e8710964b7f1ce4c5971734b6f6bffdcc6d8cd Mon Sep 17 00:00:00 2001 From: AMLattanzi Date: Fri, 10 Jan 2025 13:49:36 -0800 Subject: [PATCH 02/13] Fix BuildERFExe.cmake and include a verbose debug file to temporary checks. --- Build/cmake_with_radiation_DEBUG.sh | 20 ++++++++++++++++++++ CMake/BuildERFExe.cmake | 24 ++++++++++-------------- 2 files changed, 30 insertions(+), 14 deletions(-) create mode 100755 Build/cmake_with_radiation_DEBUG.sh diff --git a/Build/cmake_with_radiation_DEBUG.sh b/Build/cmake_with_radiation_DEBUG.sh new file mode 100755 index 000000000..451c93c08 --- /dev/null +++ b/Build/cmake_with_radiation_DEBUG.sh @@ -0,0 +1,20 @@ +#!/bin/bash + +# Example CMake config script for an OSX laptop with OpenMPI + +cmake -DCMAKE_INSTALL_PREFIX:PATH=./install \ + -DCMAKE_CXX_COMPILER:STRING=mpicxx \ + -DCMAKE_C_COMPILER:STRING=mpicc \ + -DCMAKE_Fortran_COMPILER:STRING=mpifort \ + -DMPIEXEC_PREFLAGS:STRING=--oversubscribe \ + -DCMAKE_BUILD_TYPE:STRING=Debug \ + -DERF_DIM:STRING=3 \ + -DERF_ENABLE_MPI:BOOL=ON \ + -DERF_ENABLE_TESTS:BOOL=ON \ + -DERF_ENABLE_RRTMGP:BOOL=ON \ + -DERF_ENABLE_NETCDF:BOOL=ON \ + -DERF_ENABLE_HDF5:BOOL=ON \ + -DERF_ENABLE_FCOMPARE:BOOL=ON \ + -DERF_ENABLE_DOCUMENTATION:BOOL=OFF \ + -DCMAKE_EXPORT_COMPILE_COMMANDS:BOOL=ON \ + .. && make VERBOSE=1 diff --git a/CMake/BuildERFExe.cmake b/CMake/BuildERFExe.cmake index 995296402..73e79e84c 100644 --- a/CMake/BuildERFExe.cmake +++ b/CMake/BuildERFExe.cmake @@ -65,9 +65,18 @@ function(build_erf_lib erf_lib_name) endif() if(ERF_ENABLE_RRTMGP) + target_include_directories(${erf_lib_name} PUBLIC + $ + $ + $ + $ + $ + $ + $ + $ + ) target_sources(${erf_lib_name} PRIVATE ${SRC_DIR}/Radiation/ERF_RRTMGP_Interface.cpp - ${SRC_DIR}/Radiation/ERF_RRTMGP_Utils.cpp ${SRC_DIR}/Radiation/ERF_Radiation.cpp ${SRC_DIR}/Radiation/ERF_OrbCosZenith.cpp ${CMAKE_SOURCE_DIR}/Submodules/RRTMGP/cpp/rrtmgp/mo_rrtmgp_util_reorder.cpp @@ -81,19 +90,7 @@ function(build_erf_lib erf_lib_name) ${CMAKE_SOURCE_DIR}/Submodules/RRTMGP/cpp/examples/all-sky/mo_load_cloud_coefficients.cpp ${CMAKE_SOURCE_DIR}/Submodules/RRTMGP/cpp/extensions/fluxes_byband/mo_fluxes_byband_kernels.cpp ) - - # The interface code needs to know about the RRTMGP includes target_compile_definitions(${erf_lib_name} PUBLIC ERF_USE_RRTMGP) - - target_include_directories(${erf_lib_name} SYSTEM PUBLIC - ${CMAKE_SOURCE_DIR}/Submodules/RRTMGP/cpp/rrtmgp - ${CMAKE_SOURCE_DIR}/Submodules/RRTMGP/cpp/rrtmgp/kernels - ${CMAKE_SOURCE_DIR}/Submodules/RRTMGP/cpp/rte - ${CMAKE_SOURCE_DIR}/Submodules/RRTMGP/cpp/rte/kernels - ${CMAKE_SOURCE_DIR}/Submodules/RRTMGP/cpp/examples - ${CMAKE_SOURCE_DIR}/Submodules/RRTMGP/cpp/examples/all-sky - ${CMAKE_SOURCE_DIR}/Submodules/RRTMGP/cpp/extensions/cloud_optics - ) endif() target_sources(${erf_lib_name} @@ -273,7 +270,6 @@ endif() if(ERF_ENABLE_RRTMGP) target_link_libraries(${erf_lib_name} PUBLIC yakl) - target_link_libraries(${erf_lib_name} PUBLIC rrtmgp) endif() #Link to amrex library From 209e3a2d59124fcb6b211f6daae8c392770df2eb Mon Sep 17 00:00:00 2001 From: AMLattanzi Date: Fri, 10 Jan 2025 14:28:31 -0800 Subject: [PATCH 03/13] Bump the submodule and add an include. --- .gitmodules | 3 ++- CMake/BuildERFExe.cmake | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.gitmodules b/.gitmodules index 16611622c..3a65f4087 100644 --- a/.gitmodules +++ b/.gitmodules @@ -9,11 +9,12 @@ [submodule "Submodules/RRTMGP"] path = Submodules/RRTMGP url = https://github.com/E3SM-Project/rte-rrtmgp + branch = jgfouca/more_perf shallow = true [submodule "Submodules/NOAH-MP"] path = Submodules/NOAH-MP url = https://github.com/AIEADA/noahmp.git - shallow=true + shallow = true [submodule "Submodules/WW3"] path = Submodules/WW3 url = https://github.com/erf-model/WW3 diff --git a/CMake/BuildERFExe.cmake b/CMake/BuildERFExe.cmake index 73e79e84c..fe2b710f9 100644 --- a/CMake/BuildERFExe.cmake +++ b/CMake/BuildERFExe.cmake @@ -67,6 +67,7 @@ function(build_erf_lib erf_lib_name) if(ERF_ENABLE_RRTMGP) target_include_directories(${erf_lib_name} PUBLIC $ + $ $ $ $ @@ -74,6 +75,7 @@ function(build_erf_lib erf_lib_name) $ $ $ + $ ) target_sources(${erf_lib_name} PRIVATE ${SRC_DIR}/Radiation/ERF_RRTMGP_Interface.cpp From de573bc5c5d46963199a506af1a5d7a40437676b Mon Sep 17 00:00:00 2001 From: AMLattanzi Date: Fri, 10 Jan 2025 14:44:02 -0800 Subject: [PATCH 04/13] Set RRTMGP submodule to E3SM hash. --- Submodules/RRTMGP | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Submodules/RRTMGP b/Submodules/RRTMGP index ddf82a25a..b24ca1f61 160000 --- a/Submodules/RRTMGP +++ b/Submodules/RRTMGP @@ -1 +1 @@ -Subproject commit ddf82a25a3a59a8f0fe904b69181cb7bd99881fb +Subproject commit b24ca1f616e45659b334dbd7297017cb7927367e From eb83d9c4d0c14816716d02d46d4b1c278525b171 Mon Sep 17 00:00:00 2001 From: AMLattanzi Date: Fri, 10 Jan 2025 17:06:32 -0800 Subject: [PATCH 05/13] Working through issus in Rad source files. --- CMake/BuildERFExe.cmake | 1 + Source/Radiation/ERF_RRTMGP_Interface.H | 37 ++++++++++---------- Source/Radiation/ERF_RRTMGP_Interface.cpp | 14 +++++--- Source/Radiation/ERF_RRTMGP_Utils.H | 41 ++--------------------- Source/Radiation/ERF_Radiation.H | 17 +++++----- Source/Radiation/ERF_Radiation.cpp | 10 +++--- 6 files changed, 44 insertions(+), 76 deletions(-) diff --git a/CMake/BuildERFExe.cmake b/CMake/BuildERFExe.cmake index fe2b710f9..086f173f1 100644 --- a/CMake/BuildERFExe.cmake +++ b/CMake/BuildERFExe.cmake @@ -93,6 +93,7 @@ function(build_erf_lib erf_lib_name) ${CMAKE_SOURCE_DIR}/Submodules/RRTMGP/cpp/extensions/fluxes_byband/mo_fluxes_byband_kernels.cpp ) target_compile_definitions(${erf_lib_name} PUBLIC ERF_USE_RRTMGP) + target_compile_definitions(${erf_lib_name} PUBLIC RRTMGP_ENABLE_YAKL) endif() target_sources(${erf_lib_name} diff --git a/Source/Radiation/ERF_RRTMGP_Interface.H b/Source/Radiation/ERF_RRTMGP_Interface.H index f2e991a67..c7e66daa6 100644 --- a/Source/Radiation/ERF_RRTMGP_Interface.H +++ b/Source/Radiation/ERF_RRTMGP_Interface.H @@ -1,21 +1,20 @@ #ifndef ERF_RRTMGP_INTERFACE_H #define ERF_RRTMGP_INTERFACE_H -#include "mo_gas_optics_rrtmgp.h" -#include "mo_cloud_optics.h" -#include "mo_fluxes_byband.h" -#include "mo_load_coefficients.h" -#include "rrtmgp_const.h" -#include "mo_gas_concentrations.h" -#include "mo_gas_optics_rrtmgp.h" -#include "mo_cloud_optics.h" -#include "mo_rte_sw.h" -#include "mo_rte_lw.h" -#include "mo_load_cloud_coefficients.h" - -#include "ERF_RRTMGP_Utils.H" - -#include "ERF_Constants.H" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include void init_kls (); void finalize_kls (); @@ -73,7 +72,7 @@ rrtmgp_main (const int ncol, const int nlay, real2d &lw_clnsky_flux_up, real2d &lw_clnsky_flux_dn, real3d &sw_bnd_flux_up, real3d &sw_bnd_flux_dn, real3d &sw_bnd_flux_dn_dir, real3d &lw_bnd_flux_up, real3d &lw_bnd_flux_dn, - const Real tsi_scaling, + const real tsi_scaling, const bool extra_clnclrsky_diag = false, const bool extra_clnsky_diag = false); @@ -88,7 +87,7 @@ rrtmgp_sw (const int ncol, const int nlay, FluxesBroadband &clnclrsky_fluxes, FluxesBroadband &clrsky_fluxes, FluxesBroadband &clnsky_fluxes, - const Real tsi_scaling, + const real tsi_scaling, const bool extra_clnclrsky_diag, const bool extra_clnsky_diag); @@ -120,8 +119,8 @@ void compute_cloud_area (int ncol, int nlay, int ngpt, - Real pmin, - Real pmax, + real pmin, + real pmax, const real2d& pmid, const real3d& cld_tau_gpt, real1d& cld_area); diff --git a/Source/Radiation/ERF_RRTMGP_Interface.cpp b/Source/Radiation/ERF_RRTMGP_Interface.cpp index becb0f97c..2859a812e 100644 --- a/Source/Radiation/ERF_RRTMGP_Interface.cpp +++ b/Source/Radiation/ERF_RRTMGP_Interface.cpp @@ -274,14 +274,18 @@ compute_band_by_band_surface_albedos (const int ncol, real2d& sfc_alb_dir, real2d& sfc_alb_dif) { + /* AMREX_ASSERT_WITH_MESSAGE(initialized, "ERROR: rrtmgp_initialize must be called before GasOpticsRRTMGP object can be used."); + */ auto wavenumber_limits = k_dist_sw.get_band_lims_wavenumber(); + /* AMREX_ASSERT_WITH_MESSAGE(yakl::intrinsics::size(wavenumber_limits, 1) == 2, "Error! 1st dimension for wavenumber_limits should be 2."); AMREX_ASSERT_WITH_MESSAGE(yakl::intrinsics::size(wavenumber_limits, 2) == nswbands, "Error! 2nd dimension for wavenumber_limits should be " + std::to_string(nswbands) + " (nswbands)."); + */ // Loop over bands, and determine for each band whether it is broadly in the // visible or infrared part of the spectrum (visible or "not visible") @@ -403,7 +407,7 @@ rrtmgp_main (const int ncol, const int nlay, real2d &lw_clnsky_flux_up, real2d &lw_clnsky_flux_dn, real3d &sw_bnd_flux_up, real3d &sw_bnd_flux_dn, real3d &sw_bnd_flux_dn_dir, real3d &lw_bnd_flux_up, real3d &lw_bnd_flux_dn, - const Real tsi_scaling, + const real tsi_scaling, const bool extra_clnclrsky_diag, const bool extra_clnsky_diag) { // Setup pointers to RRTMGP SW fluxes @@ -548,7 +552,7 @@ get_subcolumn_mask (const int ncol, yakl::Random rand(seeds(icol)); for (int igpt = 1; igpt <= ngpt; igpt++) { for (int ilay = 1; ilay <= nlay; ilay++) { - cldx(icol,ilay,igpt) = rand.genFP(); + cldx(icol,ilay,igpt) = rand.genFP(); } } }); @@ -604,7 +608,7 @@ rrtmgp_sw (const int ncol, FluxesBroadband& clnclrsky_fluxes, FluxesBroadband& clrsky_fluxes, FluxesBroadband& clnsky_fluxes, - const Real tsi_scaling, + const real tsi_scaling, const bool extra_clnclrsky_diag, const bool extra_clnsky_diag) { @@ -1005,8 +1009,8 @@ void compute_cloud_area (int ncol, int nlay, int ngpt, - const Real pmin, - const Real pmax, + const real pmin, + const real pmax, const real2d& pmid, const real3d& cld_tau_gpt, real1d& cld_area) diff --git a/Source/Radiation/ERF_RRTMGP_Utils.H b/Source/Radiation/ERF_RRTMGP_Utils.H index dd453798d..2c9830e3f 100644 --- a/Source/Radiation/ERF_RRTMGP_Utils.H +++ b/Source/Radiation/ERF_RRTMGP_Utils.H @@ -2,7 +2,7 @@ #define ERF_RRTMGP_UTILS_H #include "rrtmgp_const.h" -#include "rrtmgp_conversion.h" +//#include "rrtmgp_conversion.h" #include "YAKL.h" #include "YAKL_Bounds_fortran.h" @@ -82,7 +82,7 @@ compute_heating_rate (yakl::Array const &flux_up, { heating_rate(icol,ilay) = ( flux_up(icol,ilay+1) - flux_up(icol,ilay) - flux_dn(icol,ilay+1) + flux_dn(icol,ilay) ) - * / (Cp_d * rho(icol,ilay) * dz(icol,ilay); + / (Cp_d * rho(icol,ilay) * dz(icol,ilay)); }); } @@ -103,42 +103,5 @@ radiation_do (const int irad, } } - -// Verify that array only contains values within valid range, and if not -// report min and max of array -template -bool -check_range (T x, - Real xmin, - Real xmax, - std::string msg, - std::ostream& out=std::cout) -{ - bool pass = true; - auto _xmin = minval(x); - auto _xmax = maxval(x); - if (_xmin < xmin or _xmax > xmax) { - // How many outside range? - auto bad_mask = x.createDeviceCopy(); - memset(bad_mask, 0); - yakl::c::parallel_for(yakl::c::SimpleBounds<1>(x.totElems()), YAKL_LAMBDA (int i) - { - if (x.data()[i] < xmin or x.data()[i] > xmax) { - bad_mask.data()[i] = 1; - } - }); - auto num_bad = sum(bad_mask); - if (num_bad > 0) { - pass = false; - out << msg << ": " - << num_bad << " values outside range " - << "[" << xmin << "," << xmax << "]" - << "; minval = " << _xmin - << "; maxval = " << _xmax << "\n"; - } - } - return pass; -} - } // namespace rrtmgp #endif diff --git a/Source/Radiation/ERF_Radiation.H b/Source/Radiation/ERF_Radiation.H index 7db56fb60..ea5c006bd 100644 --- a/Source/Radiation/ERF_Radiation.H +++ b/Source/Radiation/ERF_Radiation.H @@ -14,6 +14,7 @@ * The RTE-RRTMGP uses BSD-3-Clause Open Source License, if you want to make changes, * and modifications to the code, please refer to BSD-3-Clause Open Source License. */ + #include #include #include @@ -50,11 +51,11 @@ public: void set_grids (int& level, int& step, - const amrex::Real& time, - const amrex::Real& dt, - const amrex::BoxArray& ba, - const amrex::Geometry& geom, - const amrex::MultiFab* cons_in, + amrex::Real& time, + amrex::Real& dt, + amrex::BoxArray& ba, + amrex::Geometry& geom, + amrex::MultiFab* cons_in, amrex::MultiFab* lsm_fluxes, amrex::MultiFab* lsm_zenith, amrex::MultiFab* qheating_rates, @@ -94,7 +95,7 @@ public: void rad_run_impl () { - if (m_update_radiation) { + if (m_update_rad) { this->initialize_impl(); this->run_impl(); this->finalize_impl(); @@ -310,5 +311,5 @@ private: // 3d size (ncol, nlay, n[sw,lw]gpts) real3d cld_tau_sw_gpt; real3d cld_tau_lw_gpt; -} - +}; +#endif diff --git a/Source/Radiation/ERF_Radiation.cpp b/Source/Radiation/ERF_Radiation.cpp index 94ffb8876..f4a72a776 100644 --- a/Source/Radiation/ERF_Radiation.cpp +++ b/Source/Radiation/ERF_Radiation.cpp @@ -91,11 +91,11 @@ Radiation::Radiation (SolverChoice& sc) void Radiation::set_grids (int& level, int& step, - const amrex::Real& time, - const amrex::Real& dt, - const amrex::BoxArray& ba, - const amrex::Geometry& geom, - const amrex::MultiFab* cons_in, + amrex::Real& time, + amrex::Real& dt, + amrex::BoxArray& ba, + amrex::Geometry& geom, + amrex::MultiFab* cons_in, amrex::MultiFab* lsm_fluxes, amrex::MultiFab* lsm_zenith, amrex::MultiFab* qheating_rates, From 88e4aa18a3ea3f0c008a5a055b0cab1e26f89b02 Mon Sep 17 00:00:00 2001 From: AMLattanzi Date: Mon, 13 Jan 2025 12:18:20 -0800 Subject: [PATCH 06/13] Fix ERF code, still header issue. --- Source/ERF_Constants.H | 2 + Source/IO/ERF_Plotfile.cpp | 4 +- Source/Radiation/ERF_OrbCosZenith.H | 60 +-- Source/Radiation/ERF_OrbCosZenith.cpp | 451 +++++++++--------- Source/Radiation/ERF_Radiation.H | 46 +- Source/Radiation/ERF_Radiation.cpp | 107 +++-- .../TimeIntegration/ERF_AdvanceRadiation.cpp | 6 +- Source/TimeIntegration/ERF_TI_slow_rhs_fun.H | 2 +- 8 files changed, 349 insertions(+), 329 deletions(-) diff --git a/Source/ERF_Constants.H b/Source/ERF_Constants.H index 275f22522..618282d73 100644 --- a/Source/ERF_Constants.H +++ b/Source/ERF_Constants.H @@ -92,4 +92,6 @@ constexpr amrex::Real ttrice = 20.00; // transition range from es over H2O t constexpr amrex::Real epsilo = Rd_on_Rv; constexpr amrex::Real omeps = 1. - epsilo; constexpr amrex::Real rhoh2o = 1.000e3; // density of liquid water + +static constexpr int ORB_UNDEF_INT = 2000000000; // undefined int #endif diff --git a/Source/IO/ERF_Plotfile.cpp b/Source/IO/ERF_Plotfile.cpp index 51c96ccf4..8c80b6ec1 100644 --- a/Source/IO/ERF_Plotfile.cpp +++ b/Source/IO/ERF_Plotfile.cpp @@ -1437,11 +1437,13 @@ ERF::WritePlotFile (int which, PlotFileType plotfile_type, Vector p } #ifdef ERF_USE_RRTMGP + /* // write additional RRTMGP data // TODO: currently single level only if (which==1 && plot_rad) { - rad.writePlotfile(plot_file_1, t_new[0], istep[0]); + rad[0]->writePlotfile(plot_file_1, t_new[0], istep[0]); } + */ #endif // Single level diff --git a/Source/Radiation/ERF_OrbCosZenith.H b/Source/Radiation/ERF_OrbCosZenith.H index d1da7db53..395eeeb6f 100644 --- a/Source/Radiation/ERF_OrbCosZenith.H +++ b/Source/Radiation/ERF_OrbCosZenith.H @@ -1,43 +1,47 @@ #ifndef ERF_ORB_COS_ZENITH_H #define ERF_ORB_COS_ZENITH_H -#include +#include +#include + #include -amrex::Real -orbital_cos_zenith (amrex::Real& jday, - amrex::Real& lat, - amrex::Real& lon, - amrex::Real& declin, - amrex::Real dt_avg = -1., - amrex::Real uniform_angle = -1., - amrex::Real constant_zenith_angle_deg = -1.); +typedef double real; + +real +orbital_cos_zenith (real& jday, + real& lat, + real& lon, + real& declin, + real dt_avg = -1., + real uniform_angle = -1., + real constant_zenith_angle_deg = -1.); -amrex::Real -orbital_avg_cos_zenith (amrex::Real& jday, - amrex::Real& lat, - amrex::Real& lon, - amrex::Real& declin, - amrex::Real& dt_avg); +real +orbital_avg_cos_zenith (real& jday, + real& lat, + real& lon, + real& declin, + real& dt_avg); void orbital_params (int& iyear_AD, - amrex::Real& eccen, - amrex::Real& obliq, - amrex::Real& mvelp, - amrex::Real& obliqr, - amrex::Real& lambm0, - amrex::Real& mvelpp); + real& eccen, + real& obliq, + real& mvelp, + real& obliqr, + real& lambm0, + real& mvelpp); void -orbital_decl (amrex::Real& calday, - amrex::Real& eccen, - amrex::Real& mvelpp, - amrex::Real& lambm0, - amrex::Real& obliqr, - amrex::Real& delta, - amrex::Real& eccf); +orbital_decl (real& calday, + real& eccen, + real& mvelpp, + real& lambm0, + real& obliqr, + real& delta, + real& eccf); #endif diff --git a/Source/Radiation/ERF_OrbCosZenith.cpp b/Source/Radiation/ERF_OrbCosZenith.cpp index 216ff72e7..30805fdbf 100644 --- a/Source/Radiation/ERF_OrbCosZenith.cpp +++ b/Source/Radiation/ERF_OrbCosZenith.cpp @@ -1,15 +1,13 @@ #include -using namespace amrex; - -Real -orbital_cos_zenith (Real& jday, - Real& lat, - Real& lon, - Real& declin, - Real dt_avg = -1., - Real uniform_angle = -1., - Real constant_zenith_angle_deg = -1.) +real +orbital_cos_zenith (real& jday, + real& lat, + real& lon, + real& declin, + real dt_avg, + real uniform_angle, + real constant_zenith_angle_deg) { // Constant zenith angle is true if ( constant_zenith_angle_deg >= 0. ) { @@ -28,45 +26,47 @@ orbital_cos_zenith (Real& jday, } // If dt for the average cosz is specified, then call the shr_orb_avg_cosz if (use_dt_avg) { - return Orbital_Avg_Cos_Zenith(jday, lat, lon, declin, dt_avg); + return orbital_avg_cos_zenith(jday, lat, lon, declin, dt_avg); } else { return std::sin(lat)*std::sin(declin) - std::cos(lat)*std::cos(declin) * - std::cos((jday-floor(jday))*Real(2.0)*PI + lon); + std::cos((jday-floor(jday))*real(2.0)*PI + lon); } } -Real -orbital_avg_cos_zenith (Real& jday, - Real& lat, - Real& lon, - Real& declin, - Real& dt_avg) +real +orbital_avg_cos_zenith (real& jday, + real& lat, + real& lon, + real& declin, + real& dt_avg) { // adjust latitude so that its tangent will be defined + real del; if (lat == PIoTwo) { - del = lat - Real(1.0e-05); - } else if (lat == -piover2) { - del = lat + Real(1.0e-05); + del = lat - real(1.0e-05); + } else if (lat == -PIoTwo) { + del = lat + real(1.0e-05); } else { del = lat; } // adjust declination so that its tangent will be defined + real phi; if (declin == PIoTwo) { - phi = declin - Real(1.0e-05); - } else if (declin == -piover2) { - phi = declin + Real(1.0e-05); + phi = declin - real(1.0e-05); + } else if (declin == -PIoTwo) { + phi = declin + real(1.0e-05); } else { phi = declin; } // define the cosine of the half-day length // adjust for cases of all daylight or all night - Real h; - Real cos_h = - std::tan(del) * std::tan(phi); + real h; + real cos_h = - std::tan(del) * std::tan(phi); if (cos_h <= -1.0) { - h = pi; + h = PI; } else if (cos_h >= 1.0) { h = 0.0; } else { @@ -75,37 +75,37 @@ orbital_avg_cos_zenith (Real& jday, // Define Local Time t and t + dt // adjust t to be between -pi and pi - Real t1 = (jday - int(jday)) * 2.0*PI + lon - PI; + real t1 = (jday - int(jday)) * 2.0*PI + lon - PI; if (t1 >= PI) { t1 = t1 - 2.0*PI; } else if (t1 < -PI) { t1 = t1 + 2.0*PI; } - Real dt = dt_avg / Real(86400.0) * 2.0*PI; - Real t2 = t1 + dt; + real dt = dt_avg / real(86400.0) * 2.0*PI; + real t2 = t1 + dt; // Compute Cosine Solar Zenith angle // define terms needed in the cosine zenith angle equation - Real aa = std::sin(lat) * std::sin(declin); - Real bb = std::cos(lat) * std::cos(declin); + real aa = std::sin(lat) * std::sin(declin); + real bb = std::cos(lat) * std::cos(declin); // define the hour angle // force it to be between -h and h // consider the situation when the night period is too short - Real tt1,tt2,tt3,tt4; - if ( (t2 >= PI) && (t1 <= PI) && (pi - h <= dt)) { + real tt1,tt2,tt3,tt4; + if ( (t2 >= PI) && (t1 <= PI) && (PI - h <= dt) ) { tt2 = h; tt1 = std::min(std::max(t1, -h), h); tt4 = std::min(std::max(t2, 2.0*PI - h), 2.0*PI + h); tt3 = 2.0*PI - h; - } else if (t2 >= -pi .and. t1 <= -pi .and. pi - h <= dt) { + } else if ( (t2 >= -PI) && (t1 <= -PI) && (PI - h <= dt) ) { tt2 = - 2.0*PI + h; tt1 = std::min(std::max(t1, -2.0*PI - h), -2.0*PI + h); tt4 = std::min(std::max(t2, -h), h); tt3 = -h; } else { - if (t2 > pi) { + if (t2 > PI) { tt2 = std::min(std::max(t2 - 2.0*PI, -h), h); } else if (t2 < - PI) { tt2 = std::min(std::max(t2 + 2.0*PI, -h), h); @@ -113,9 +113,9 @@ orbital_avg_cos_zenith (Real& jday, tt2 = std::min(std::max(t2 , -h), h); } - if (t1 > pi) { + if (t1 > PI) { tt1 = std::min(std::max(t1 - 2.0*PI, -h), h); - } else if (t1 < - pi) { + } else if (t1 < -PI) { tt1 = std::min(std::max(t1 + 2.0*PI, -h), h); } else { tt1 = std::min(std::max(t1 , -h), h); @@ -137,12 +137,12 @@ orbital_avg_cos_zenith (Real& jday, void orbital_params (int& iyear_AD, - Real& eccen, - Real& obliq, - Real& mvelp, - Real& obliqr, - Real& lambm0, - Real& mvelpp) + real& eccen, + real& obliq, + real& mvelp, + real& obliqr, + real& lambm0, + real& mvelpp) { /* !------------------------------------------------------------------------------- @@ -158,194 +158,193 @@ orbital_params (int& iyear_AD, int poblen = 47; // # of elements in series wrt obliquity int pecclen = 19; // # of elements in series wrt eccentricity int pmvelen = 78; // # of elements in series wrt vernal equinox - static constexpr Real psecdeg = Real(1.0)/Real(3600.0); // arc sec to deg conversion - static constexpr Real degrad = PI/Real(180.); // degree to radian conversion factor + static constexpr real psecdeg = real(1.0)/real(3600.0); // arc sec to deg conversion + static constexpr real degrad = PI/real(180.); // degree to radian conversion factor // Cosine series data for computation of obliquity: amplitude (arc seconds), // rate (arc seconds/year), phase (degrees). // amplitudes for obliquity cos series - Vector obamp = {-2462.2214466, -857.3232075, -629.3231835, - -414.2804924, -311.7632587, 308.9408604, - -162.5533601, -116.1077911, 101.1189923, - -67.6856209, 24.9079067, 22.5811241, - -21.1648355, -15.6549876, 15.3936813, - 14.6660938, -11.7273029, 10.2742696, - 6.4914588, 5.8539148, -5.4872205, - -5.4290191, 5.1609570, 5.0786314, - -4.0735782, 3.7227167, 3.3971932, - -2.8347004, -2.6550721, -2.5717867, - -2.4712188, 2.4625410, 2.2464112, - -2.0755511, -1.9713669, -1.8813061, - -1.8468785, 1.8186742, 1.7601888, - -1.5428851, 1.4738838, -1.4593669, - 1.4192259, -1.1818980, 1.1756474, - -1.1316126, 1.0896928}; + std::vector obamp = {-2462.2214466, -857.3232075, -629.3231835, + -414.2804924, -311.7632587, 308.9408604, + -162.5533601, -116.1077911, 101.1189923, + -67.6856209, 24.9079067, 22.5811241, + -21.1648355, -15.6549876, 15.3936813, + 14.6660938, -11.7273029, 10.2742696, + 6.4914588, 5.8539148, -5.4872205, + -5.4290191, 5.1609570, 5.0786314, + -4.0735782, 3.7227167, 3.3971932, + -2.8347004, -2.6550721, -2.5717867, + -2.4712188, 2.4625410, 2.2464112, + -2.0755511, -1.9713669, -1.8813061, + -1.8468785, 1.8186742, 1.7601888, + -1.5428851, 1.4738838, -1.4593669, + 1.4192259, -1.1818980, 1.1756474, + -1.1316126, 1.0896928}; // rates for obliquity cosine series - Vector obrate = {31.609974, 32.620504, 24.172203, - 31.983787, 44.828336, 30.973257, - 43.668246, 32.246691, 30.599444, - 42.681324, 43.836462, 47.439436, - 63.219948, 64.230478, 1.010530, - 7.437771, 55.782177, 0.373813, - 13.218362, 62.583231, 63.593761, - 76.438310, 45.815258, 8.448301, - 56.792707, 49.747842, 12.058272, - 75.278220, 65.241008, 64.604291, - 1.647247, 7.811584, 12.207832, - 63.856665, 56.155990, 77.448840, - 6.801054, 62.209418, 20.656133, - 48.344406, 55.145460, 69.000539, - 11.071350, 74.291298, 11.047742, - 0.636717, 12.844549}; + std::vector obrate = {31.609974, 32.620504, 24.172203, + 31.983787, 44.828336, 30.973257, + 43.668246, 32.246691, 30.599444, + 42.681324, 43.836462, 47.439436, + 63.219948, 64.230478, 1.010530, + 7.437771, 55.782177, 0.373813, + 13.218362, 62.583231, 63.593761, + 76.438310, 45.815258, 8.448301, + 56.792707, 49.747842, 12.058272, + 75.278220, 65.241008, 64.604291, + 1.647247, 7.811584, 12.207832, + 63.856665, 56.155990, 77.448840, + 6.801054, 62.209418, 20.656133, + 48.344406, 55.145460, 69.000539, + 11.071350, 74.291298, 11.047742, + 0.636717, 12.844549}; // phases for obliquity cosine series - Vector obphas = {251.9025, 280.8325, 128.3057, - 292.7252, 15.3747, 263.7951, - 308.4258, 240.0099, 222.9725, - 268.7809, 316.7998, 319.6024, - 143.8050, 172.7351, 28.9300, - 123.5968, 20.2082, 40.8226, - 123.4722, 155.6977, 184.6277, - 267.2772, 55.0196, 152.5268, - 49.1382, 204.6609, 56.5233, - 200.3284, 201.6651, 213.5577, - 17.0374, 164.4194, 94.5422, - 131.9124, 61.0309, 296.2073, - 135.4894, 114.8750, 247.0691, - 256.6114, 32.1008, 143.6804, - 16.8784, 160.6835, 27.5932, - 348.1074, 82.6496}; + std::vector obphas = {251.9025, 280.8325, 128.3057, + 292.7252, 15.3747, 263.7951, + 308.4258, 240.0099, 222.9725, + 268.7809, 316.7998, 319.6024, + 143.8050, 172.7351, 28.9300, + 123.5968, 20.2082, 40.8226, + 123.4722, 155.6977, 184.6277, + 267.2772, 55.0196, 152.5268, + 49.1382, 204.6609, 56.5233, + 200.3284, 201.6651, 213.5577, + 17.0374, 164.4194, 94.5422, + 131.9124, 61.0309, 296.2073, + 135.4894, 114.8750, 247.0691, + 256.6114, 32.1008, 143.6804, + 16.8784, 160.6835, 27.5932, + 348.1074, 82.6496}; // Cosine/sine series data for computation of eccentricity and fixed vernal // equinox longitude of perihelion (fvelp): amplitude, // rate (arc seconds/year), phase (degrees). // ampl for eccen/fvelp cos/sin series - Vector ecamp = {0.01860798, 0.01627522, -0.01300660, - 0.00988829, -0.00336700, 0.00333077, - -0.00235400, 0.00140015, 0.00100700, - 0.00085700, 0.00064990, 0.00059900, - 0.00037800, -0.00033700, 0.00027600, - 0.00018200, -0.00017400, -0.00012400, - 0.00001250}; + std::vector ecamp = { 0.01860798, 0.01627522, -0.01300660, + 0.00988829, -0.00336700, 0.00333077, + -0.00235400, 0.00140015, 0.00100700, + 0.00085700, 0.00064990, 0.00059900, + 0.00037800, -0.00033700, 0.00027600, + 0.00018200, -0.00017400, -0.00012400, + 0.00001250}; // rates for eccen/fvelp cos/sin series - Vector ecrate = { 4.2072050, 7.3460910, 17.8572630, - 17.2205460, 16.8467330, 5.1990790, - 18.2310760, 26.2167580, 6.3591690, - 16.2100160, 3.0651810, 16.5838290, - 18.4939800, 6.1909530, 18.8677930, - 17.4255670, 6.1860010, 18.4174410, - 0.6678630}; + std::vector ecrate = { 4.2072050, 7.3460910, 17.8572630, + 17.2205460, 16.8467330, 5.1990790, + 18.2310760, 26.2167580, 6.3591690, + 16.2100160, 3.0651810, 16.5838290, + 18.4939800, 6.1909530, 18.8677930, + 17.4255670, 6.1860010, 18.4174410, + 0.6678630}; // phases for eccen/fvelp cos/sin series - Vector ecphas = { 28.620089, 193.788772, 308.307024, - 320.199637, 279.376984, 87.195000, - 349.129677, 128.443387, 154.143880, - 291.269597, 114.860583, 332.092251, - 296.414411, 145.769910, 337.237063, - 152.092288, 126.839891, 210.667199, - 72.108838}; + std::vector ecphas = { 28.620089, 193.788772, 308.307024, + 320.199637, 279.376984, 87.195000, + 349.129677, 128.443387, 154.143880, + 291.269597, 114.860583, 332.092251, + 296.414411, 145.769910, 337.237063, + 152.092288, 126.839891, 210.667199, + 72.108838}; // Sine series data for computation of moving vernal equinox longitude of // perihelion: amplitude (arc seconds), rate (arc sec/year), phase (degrees). // amplitudes for mvelp sine series - Vector mvamp = { 7391.0225890, 2555.1526947, 2022.7629188, - -1973.6517951, 1240.2321818, 953.8679112, - -931.7537108, 872.3795383, 606.3544732, - -496.0274038, 456.9608039, 346.9462320, - -305.8412902, 249.6173246, -199.1027200, - 191.0560889, -175.2936572, 165.9068833, - 161.1285917, 139.7878093, -133.5228399, - 117.0673811, 104.6907281, 95.3227476, - 86.7824524, 86.0857729, 70.5893698, - -69.9719343, -62.5817473, 61.5450059, - -57.9364011, 57.1899832, -57.0236109, - -54.2119253, 53.2834147, 52.1223575, - -49.0059908, -48.3118757, -45.4191685, - -42.2357920, -34.7971099, 34.4623613, - -33.8356643, 33.6689362, -31.2521586, - -30.8798701, 28.4640769, -27.1960802, - 27.0860736, -26.3437456, 24.7253740, - 24.6732126, 24.4272733, 24.0127327, - 21.7150294, -21.5375347, 18.1148363, - -16.9603104, -16.1765215, 15.5567653, - 15.4846529, 15.2150632, 14.5047426, - -14.3873316, 13.1351419, 12.8776311, - 11.9867234, 11.9385578, 11.7030822, - 11.6018181, -11.2617293, -10.4664199, - 10.4333970, -10.2377466, 10.1934446, - -10.1280191, 10.0289441, -10.0034259}; + std::vector mvamp = { 7391.0225890, 2555.1526947, 2022.7629188, + -1973.6517951, 1240.2321818, 953.8679112, + -931.7537108, 872.3795383, 606.3544732, + -496.0274038, 456.9608039, 346.9462320, + -305.8412902, 249.6173246, -199.1027200, + 191.0560889, -175.2936572, 165.9068833, + 161.1285917, 139.7878093, -133.5228399, + 117.0673811, 104.6907281, 95.3227476, + 86.7824524, 86.0857729, 70.5893698, + -69.9719343, -62.5817473, 61.5450059, + -57.9364011, 57.1899832, -57.0236109, + -54.2119253, 53.2834147, 52.1223575, + -49.0059908, -48.3118757, -45.4191685, + -42.2357920, -34.7971099, 34.4623613, + -33.8356643, 33.6689362, -31.2521586, + -30.8798701, 28.4640769, -27.1960802, + 27.0860736, -26.3437456, 24.7253740, + 24.6732126, 24.4272733, 24.0127327, + 21.7150294, -21.5375347, 18.1148363, + -16.9603104, -16.1765215, 15.5567653, + 15.4846529, 15.2150632, 14.5047426, + -14.3873316, 13.1351419, 12.8776311, + 11.9867234, 11.9385578, 11.7030822, + 11.6018181, -11.2617293, -10.4664199, + 10.4333970, -10.2377466, 10.1934446, + -10.1280191, 10.0289441, -10.0034259}; // rates for mvelp sine series - Vector mvrate = {31.609974, 32.620504, 24.172203, - 0.636717, 31.983787, 3.138886, - 30.973257, 44.828336, 0.991874, - 0.373813, 43.668246, 32.246691, - 30.599444, 2.147012, 10.511172, - 42.681324, 13.650058, 0.986922, - 9.874455, 13.013341, 0.262904, - 0.004952, 1.142024, 63.219948, - 0.205021, 2.151964, 64.230478, - 43.836462, 47.439436, 1.384343, - 7.437771, 18.829299, 9.500642, - 0.431696, 1.160090, 55.782177, - 12.639528, 1.155138, 0.168216, - 1.647247, 10.884985, 5.610937, - 12.658184, 1.010530, 1.983748, - 14.023871, 0.560178, 1.273434, - 12.021467, 62.583231, 63.593761, - 76.438310, 4.280910, 13.218362, - 17.818769, 8.359495, 56.792707, - 8.448301, 1.978796, 8.863925, - 0.186365, 8.996212, 6.771027, - 45.815258, 12.002811, 75.278220, - 65.241008, 18.870667, 22.009553, - 64.604291, 11.498094, 0.578834, - 9.237738, 49.747842, 2.147012, - 1.196895, 2.133898, 0.173168}; + std::vector mvrate = {31.609974, 32.620504, 24.172203, + 0.636717, 31.983787, 3.138886, + 30.973257, 44.828336, 0.991874, + 0.373813, 43.668246, 32.246691, + 30.599444, 2.147012, 10.511172, + 42.681324, 13.650058, 0.986922, + 9.874455, 13.013341, 0.262904, + 0.004952, 1.142024, 63.219948, + 0.205021, 2.151964, 64.230478, + 43.836462, 47.439436, 1.384343, + 7.437771, 18.829299, 9.500642, + 0.431696, 1.160090, 55.782177, + 12.639528, 1.155138, 0.168216, + 1.647247, 10.884985, 5.610937, + 12.658184, 1.010530, 1.983748, + 14.023871, 0.560178, 1.273434, + 12.021467, 62.583231, 63.593761, + 76.438310, 4.280910, 13.218362, + 17.818769, 8.359495, 56.792707, + 8.448301, 1.978796, 8.863925, + 0.186365, 8.996212, 6.771027, + 45.815258, 12.002811, 75.278220, + 65.241008, 18.870667, 22.009553, + 64.604291, 11.498094, 0.578834, + 9.237738, 49.747842, 2.147012, + 1.196895, 2.133898, 0.173168}; // phases for mvelp sine series - Vector mvrate mvphas = {251.9025, 280.8325, 128.3057, - 348.1074, 292.7252, 165.1686, - 263.7951, 15.3747, 58.5749, - 40.8226, 308.4258, 240.0099, - 222.9725, 106.5937, 114.5182, - 268.7809, 279.6869, 39.6448, - 126.4108, 291.5795, 307.2848, - 18.9300, 273.7596, 143.8050, - 191.8927, 125.5237, 172.7351, - 316.7998, 319.6024, 69.7526, - 123.5968, 217.6432, 85.5882, - 156.2147, 66.9489, 20.2082, - 250.7568, 48.0188, 8.3739, - 17.0374, 155.3409, 94.1709, - 221.1120, 28.9300, 117.1498, - 320.5095, 262.3602, 336.2148, - 233.0046, 155.6977, 184.6277, - 267.2772, 78.9281, 123.4722, - 188.7132, 180.1364, 49.1382, - 152.5268, 98.2198, 97.4808, - 221.5376, 168.2438, 161.1199, - 55.0196, 262.6495, 200.3284, - 201.6651, 294.6547, 99.8233, - 213.5577, 154.1631, 232.7153, - 138.3034, 204.6609, 106.5938, - 250.4676, 332.3345, 27.3039}; + std::vector mvphas = {251.9025, 280.8325, 128.3057, + 348.1074, 292.7252, 165.1686, + 263.7951, 15.3747, 58.5749, + 40.8226, 308.4258, 240.0099, + 222.9725, 106.5937, 114.5182, + 268.7809, 279.6869, 39.6448, + 126.4108, 291.5795, 307.2848, + 18.9300, 273.7596, 143.8050, + 191.8927, 125.5237, 172.7351, + 316.7998, 319.6024, 69.7526, + 123.5968, 217.6432, 85.5882, + 156.2147, 66.9489, 20.2082, + 250.7568, 48.0188, 8.3739, + 17.0374, 155.3409, 94.1709, + 221.1120, 28.9300, 117.1498, + 320.5095, 262.3602, 336.2148, + 233.0046, 155.6977, 184.6277, + 267.2772, 78.9281, 123.4722, + 188.7132, 180.1364, 49.1382, + 152.5268, 98.2198, 97.4808, + 221.5376, 168.2438, 161.1199, + 55.0196, 262.6495, 200.3284, + 201.6651, 294.6547, 99.8233, + 213.5577, 154.1631, 232.7153, + 138.3034, 204.6609, 106.5938, + 250.4676, 332.3345, 27.3039}; //---------------------------Local variables---------------------------------- - int i; // Index for series summations - Real obsum; // Obliquity series summation - Real cossum; // Cos series summation for eccentricity/fvelp - Real sinsum; // Sin series summation for eccentricity/fvelp - Real fvelp; // Fixed vernal equinox long of perihelion - Real mvsum; // mvelp series summation - Real beta; // Intermediate argument for lambm0 - Real years; // Years to time of interest ( pos <=> future) - Real eccen2; // eccentricity squared - Real eccen3; // eccentricity cubed - Real yb4_1950AD; // number of years before 1950 AD + real obsum; // Obliquity series summation + real cossum; // Cos series summation for eccentricity/fvelp + real sinsum; // Sin series summation for eccentricity/fvelp + real fvelp; // Fixed vernal equinox long of perihelion + real mvsum; // mvelp series summation + real beta; // Intermediate argument for lambm0 + real years; // Years to time of interest ( pos <=> future) + real eccen2; // eccentricity squared + real eccen3; // eccentricity cubed + real yb4_1950AD; // number of years before 1950 AD // Check for flag to use input orbit parameters if ( iyear_AD == ORB_UNDEF_INT ) { @@ -384,7 +383,7 @@ orbital_params (int& iyear_AD, (1950) in formulas that follow. */ - yb4_1950AD = Real(1950.0) - Real(iyear_AD); + yb4_1950AD = real(1950.0) - real(iyear_AD); years = - yb4_1950AD; /* @@ -400,9 +399,9 @@ orbital_params (int& iyear_AD, obsum = 0.0; for (int i(0); i 0.0) { - fvelp = .5*PI; + fvelp = 0.5*PI; } } else if (cossum <= 0.0) { fvelp = std::atan(sinsum/cossum) + PI; @@ -459,9 +458,9 @@ orbital_params (int& iyear_AD, */ mvsum = 0.0; for (int i(0); i m_gas_names = {"H2O", "CO2", "O3", "N2O", "CO" , "CH4", "O2", "N2" }; const std::vector m_mol_weight_gas = {18.01528, 44.00950, 47.9982, 44.0128, 28.01010, 16.04246, 31.9980, 28.0134}; // g/mol - real1d m_gas_mol_weights; - string1d gas_names_yakl_offset; - GasConcs m_gas_concs; + + // Prescribed greenhouse gas surface concentrations in moles / moles air + amrex::Real m_co2vmr = 388.717e-6; + amrex::Real m_o3vmr = 1.8868676125307193E-7; + amrex::Real m_n2ovmr = 323.141e-9; + amrex::Real m_covmr = 1.0e-7; + amrex::Real m_ch4vmr = 1807.851e-9; + amrex::Real m_o2vmr = 0.209448; + amrex::Real m_n2vmr = 0.7906; + //amrex::Real m_f11vmr = 768.7644e-12; + //amrex::Real m_f12vmr = 531.2820e-12; + + real1d m_gas_mol_weights; + string1dv gas_names_yakl_offset; + GasConcs m_gas_concs; // Process interface vars modeled after EAMXX //=================================================================================== @@ -216,15 +227,7 @@ private: int m_nswgpts = 112; int m_nlwgpts = 128; - // Prescribed greenhouse gas surface concentrations in moles / moles air - amrex::Real m_o3vmr = 1.8868676125307193E-7; - amrex::Real m_co2vmr = 388.717e-6; - amrex::Real m_n2ovmr = 323.141e-9; - amrex::Real m_ch4vmr = 1807.851e-9; - amrex::Real m_f11vmr = 768.7644e-12; - amrex::Real m_f12vmr = 531.2820e-12; - amrex::Real m_n2vmr = 0.7906; - amrex::Real m_covmr = 1.0e-7; + // Rad frequency in number of steps int m_rad_freq_in_steps = 1; @@ -251,9 +254,10 @@ private: real2d t_lay; real2d z_del; real2d p_del; - real2d qc; - real2d nc; - real2d qi; + real2d qv_lay; + real2d qc_lay; + real2d nc_lay; + real2d qi_lay; real2d cldfrac_tot; real2d eff_radius_qc; real2d eff_radius_qi; @@ -294,6 +298,10 @@ private: real3d sw_bnd_flux_dir; real3d sw_bnd_flux_dif; + // 3d size (ncol, nlay+1, nlwbands) + real3d lw_bnd_flux_up; + real3d lw_bnd_flux_dn; + // 2d size (ncol, nswbands) real2d sfc_alb_dir; real2d sfc_alb_dif; diff --git a/Source/Radiation/ERF_Radiation.cpp b/Source/Radiation/ERF_Radiation.cpp index f4a72a776..23fa5fbea 100644 --- a/Source/Radiation/ERF_Radiation.cpp +++ b/Source/Radiation/ERF_Radiation.cpp @@ -58,14 +58,13 @@ Radiation::Radiation (SolverChoice& sc) pp.query("Fixed Solar Zenith Angle", m_fixed_solar_zenith_angle); // Get prescribed surface values of greenhouse gases - pp.query("o3vmr" , m_o3vmr ); pp.query("co2vmr", m_co2vmr); + pp.query("o3vmr" , m_o3vmr ); pp.query("n2ovmr", m_n2ovmr); + pp.query("covmr" , m_covmr ); pp.query("ch4vmr", m_ch4vmr); - pp.query("f11vmr", m_f11vmr); - pp.query("f12vmr", m_f12vmr); + pp.query("o2vmr" , m_o2vmr ); pp.query("n2vmr" , m_n2vmr ); - pp.query("covmr" , m_covmr ); // Required aerosol optical properties from SPA pp.query("do_aerosol_rad", m_do_aerosol_rad); @@ -92,8 +91,8 @@ void Radiation::set_grids (int& level, int& step, amrex::Real& time, - amrex::Real& dt, - amrex::BoxArray& ba, + const amrex::Real& dt, + const amrex::BoxArray& ba, amrex::Geometry& geom, amrex::MultiFab* cons_in, amrex::MultiFab* lsm_fluxes, @@ -104,7 +103,7 @@ Radiation::set_grids (int& level, amrex::MultiFab* lon) { - // Reset data members for AMR + // Set data members that may change m_lev = level; m_step = step; m_dt = dt; @@ -132,18 +131,18 @@ Radiation::set_grids (int& level, m_update_rad = false; if (m_rad_freq_in_steps > 0) { m_update_rad = ( (m_step == 0) || (m_step % m_rad_freq_in_steps == 0) ); } - if (update_rad) { + if (m_update_rad) { // Reset vector of offsets for columnar data m_nlay = geom.Domain().length(2); m_ncol = 0; m_col_offsets.clear(); m_col_offsets.resize(int(ba.size())); - for (MFIter mfi(cons_in, TileNoZ()); mfi.isValid(); ++mfi) { + for (MFIter mfi(*m_cons_in); mfi.isValid(); ++mfi) { const auto& vbx = mfi.validbox(); int nx = vbx.length(0); int ny = vbx.length(1); - m_col_offsets[mfi.index()] = ncol; + m_col_offsets[mfi.index()] = m_ncol; m_ncol += nx * ny; } @@ -164,7 +163,7 @@ Radiation::alloc_buffers () parallel_for(m_ngas, YAKL_LAMBDA (int igas) { m_gas_mol_weights_h(igas) = m_mol_weight_gas[igas]; - gas_names_yakl_offset(igas) = m_gas_names[igas]; + gas_names_yakl_offset[igas] = m_gas_names[igas]; }); m_gas_mol_weights.deep_copy_to(m_gas_mol_weights_h); @@ -188,9 +187,10 @@ Radiation::alloc_buffers () t_lay = real2d("t_lay" , m_ncol, m_nlay); z_del = real2d("z_del" , m_ncol, m_nlay); p_del = real2d("p_del" , m_ncol, m_nlay); - qc = real2d("qc" , m_ncol, m_nlay); - nc = real2d("nc" , m_ncol, m_nlay); - qi = real2d("qi" , m_ncol, m_nlay); + qv_lay = real2d("qv" , m_ncol, m_nlay); + qc_lay = real2d("qc" , m_ncol, m_nlay); + nc_lay = real2d("nc" , m_ncol, m_nlay); + qi_lay = real2d("qi" , m_ncol, m_nlay); cldfrac_tot = real2d("cldfrac_tot" , m_ncol, m_nlay); eff_radius_qc = real2d("eff_radius_qc", m_ncol, m_nlay); eff_radius_qi = real2d("eff_radius_qi", m_ncol, m_nlay); @@ -231,6 +231,10 @@ Radiation::alloc_buffers () sw_bnd_flux_dir = real3d("sw_bnd_flux_dir", m_ncol, m_nlay+1, m_nswbands); sw_bnd_flux_dif = real3d("sw_bnd_flux_dif", m_ncol, m_nlay+1, m_nswbands); + // 3d size (ncol, nlay+1, nlwbands) + lw_bnd_flux_up = real3d("lw_bnd_flux_up" , m_ncol, m_nlay+1, m_nlwbands); + lw_bnd_flux_dn = real3d("lw_bnd_flux_up" , m_ncol, m_nlay+1, m_nlwbands); + // 2d size (ncol, nswbands) sfc_alb_dir = real2d("sfc_alb_dir", m_ncol, m_nswbands); sfc_alb_dif = real2d("sfc_alb_dif", m_ncol, m_nswbands); @@ -275,9 +279,10 @@ Radiation::dealloc_buffers () t_lay.deallocate(); z_del.deallocate(); p_del.deallocate(); - qc.deallocate(); - nc.deallocate(); - qi.deallocate(); + qv_lay.deallocate(); + qc_lay.deallocate(); + nc_lay.deallocate(); + qi_lay.deallocate(); cldfrac_tot.deallocate(); eff_radius_qc.deallocate(); eff_radius_qi.deallocate(); @@ -320,6 +325,10 @@ Radiation::dealloc_buffers () sw_bnd_flux_dir.deallocate(); sw_bnd_flux_dif.deallocate(); + // 3d size (ncol, nlay+1, nlwbands) + lw_bnd_flux_up.deallocate(); + lw_bnd_flux_dn.deallocate(); + // 2d size (ncol, nswbands) sfc_alb_dir.deallocate(); sfc_alb_dif.deallocate(); @@ -347,14 +356,14 @@ Radiation::mf_to_yakl_buffers () bool ice = m_ice; int ncol = m_ncol; int nlay = m_nlay; - Real dz = geom.CellSize(2); + Real dz = m_geom.CellSize(2); for (MFIter mfi(*m_cons_in); mfi.isValid(); ++mfi) { const auto& vbx = mfi.validbox(); const int nx = vbx.length(0); const int imin = vbx.smallEnd(0); const int jmin = vbx.smallEnd(1); - const int offset = m_col_offsets[mfi.Index()]; - const Array4< Real>& cons_arr = m_cons_in->const_array(mfi); + const int offset = m_col_offsets[mfi.index()]; + const Array4& cons_arr = m_cons_in->const_array(mfi); const Array4& z_arr = (m_z_phys) ? m_z_phys->const_array(mfi) : Array4{}; ParallelFor(vbx, [=] AMREX_GPU_DEVICE (int i, int j, int k) @@ -374,13 +383,9 @@ Radiation::mf_to_yakl_buffers () Real r_lo = cons_arr(i,j,k-1,Rho_comp); Real rt_lo = cons_arr(i,j,k-1,RhoTheta_comp); Real qv_lo = (moist) ? cons_arr(i,j,k-1,RhoQ1_comp)/r_lo : 0.0; - Real qc_lo = (moist) ? cons_arr(i,j,k-1,RhoQ2_comp)/r_lo : 0.0; - Real qi_lo = (ice) ? cons_arr(i,j,k-1,RhoQ3_comp)/r_lo : 0.0; Real r_avg = 0.5 * (r + r_lo); Real rt_avg = 0.5 * (rt + rt_lo); Real qv_avg = 0.5 * (qv + qv_lo); - Real qc_avg = 0.5 * (qc + qc_lo); - Real qi_avg = 0.5 * (qi + qi_lo); // Buffers at CC r_lay(icol,ilay) = r; @@ -390,9 +395,10 @@ Radiation::mf_to_yakl_buffers () + (z_arr(i+1,j ,k+1) - z_arr(i+1,j ,k)) + (z_arr(i ,j+1,k+1) - z_arr(i ,j+1,k)) + (z_arr(i+1,j ,k+1) - z_arr(i+1,j ,k)) ) : dz; - qc(icol,ilay) = qc; - qi(icol,ilay) = qi; - cld_frac_tot(icol,ilay) = ((qc+qi)>1.0e-5) ? 1. : 0.; + qv_lay(icol,ilay) = qv; + qc_lay(icol,ilay) = qc; + qi_lay(icol,ilay) = qi; + cldfrac_tot(icol,ilay) = ((qc+qi)>1.0e-5) ? 1. : 0.; // HACK HACK HACK lwp(icol,ilay) = 0.0; @@ -417,10 +423,10 @@ Radiation::mf_to_yakl_buffers () // Separate YAKL kernel for derived quantities parallel_for(SimpleBounds<2>(ncol, nlay), YAKL_LAMBDA (int icol, int ilay) { - p_del(icol,ilay) = p_lev(icol,ilay+1) - p_lev(icol,ilay); - nc(icol,ilay) = 0.0; - rel(icol,ilay) = 0.0; - rei(icol,ilay) = 0.0; + p_del(icol,ilay) = p_lev(icol,ilay+1) - p_lev(icol,ilay); + nc_lay(icol,ilay) = 0.0; + eff_radius_qc(icol,ilay) = 0.0; + eff_radius_qi(icol,ilay) = 0.0; }); // HACK HACK HACK @@ -448,10 +454,10 @@ Radiation::yakl_buffers_to_mf () const int nx = vbx.length(0); const int imin = vbx.smallEnd(0); const int jmin = vbx.smallEnd(1); - const int offset = m_col_offsets[mfi.Index()]; - const Array4& q_arr = m_qheating_rates->const_array(mfi); - const Array4& lsm_arr = (m_lsm_fluxes) m_lsm_fluxes->array(mfi) : - Array4{}; + const int offset = m_col_offsets[mfi.index()]; + const Array4& q_arr = m_qheating_rates->array(mfi); + const Array4& lsm_arr = (m_lsm_fluxes) ? m_lsm_fluxes->array(mfi) : + Array4{}; ParallelFor(vbx, [=] AMREX_GPU_DEVICE (int i, int j, int k) { // map [i,j,k] 0-based to [icol, ilay] 1-based @@ -508,7 +514,7 @@ Radiation::run_impl () // Compute orbital parameters; these are used both for computing // the solar zenith angle and also for computing total solar // irradiance scaling (tsi_scaling). - Real obliqr, lambm0, mvelpp; + real obliqr, lambm0, mvelpp; auto orbital_year = m_orbital_year; auto eccen = m_orbital_eccen; auto obliq = m_orbital_obliq; @@ -517,15 +523,15 @@ Radiation::run_impl () // fixed orbital parameters forced with orbital_year == ORB_UNDEF_INT orbital_year = ORB_UNDEF_INT; } - orbital_params(&orbital_year, &eccen, &obliq, - &mvelp, &obliqr, &lambm0, &mvelpp); + orbital_params(orbital_year, eccen, obliq, + mvelp, obliqr, lambm0, mvelpp); // Use the orbital parameters to calculate the solar declination and eccentricity factor - Real delta, eccf; + real delta, eccf; // TODO: Generalize this. auto calday = (m_orbital_mon-1.0)*365.0/12.0 + m_orbital_day; // Want day + fraction; calday 1 == Jan 1 0Z orbital_decl(calday, eccen, mvelpp, - lambm0, obliqr, &delta, &eccf); + lambm0, obliqr, delta, eccf); // Overwrite eccf if using a fixed solar constant. auto fixed_total_solar_irradiance = m_fixed_total_solar_irradiance; @@ -543,8 +549,8 @@ Radiation::run_impl () if (name == "H2O") { parallel_for(SimpleBounds<2>(ncol, nlay), YAKL_LAMBDA (int icol, int ilay) { - //h2o_vmr(icol,ilay) = qv(icol,ilay) / (1.0 - qv(icol,ilay)) * mwdair/gas_mol_weight; - tmp2d(icol,ilay) = qv(icol,ilay) * mwdair/ gas_mol_weight; + //h2o_vmr(icol,ilay) = qv_lay(icol,ilay) / (1.0 - qv_lay(icol,ilay)) * mwdair/gas_mol_weight; + tmp2d(icol,ilay) = qv_lay(icol,ilay) * mwdair/ gas_mol_weight; }); } else if (name == "CO2") { yakl::memset(tmp2d, m_co2vmr); @@ -595,8 +601,8 @@ Radiation::run_impl () */ // Compute layer cloud mass (per unit area) - rrtmgp::mixing_ratio_to_cloud_mass(qc, cldfrac_tot, p_del, lwp); - rrtmgp::mixing_ratio_to_cloud_mass(qi, cldfrac_tot, p_del, iwp); + rrtmgp::mixing_ratio_to_cloud_mass(qc_lay, cldfrac_tot, p_del, lwp); + rrtmgp::mixing_ratio_to_cloud_mass(qi_lay, cldfrac_tot, p_del, iwp); // Convert to g/m2 (needed by RRTMGP) parallel_for(SimpleBounds<2>(ncol, nlay), YAKL_LAMBDA (int icol, int ilay) @@ -617,7 +623,7 @@ Radiation::run_impl () p_lay, t_lay, p_lev, t_lev, m_gas_concs, sfc_alb_dir, sfc_alb_dif, mu0, - lwp, iwp, rel, rei, cldfrac_tot, + lwp, iwp, eff_radius_qc, eff_radius_qi, cldfrac_tot, aero_tau_sw, aero_ssa_sw, aero_g_sw, aero_tau_lw, cld_tau_sw_bnd, cld_tau_lw_bnd, cld_tau_sw_gpt, cld_tau_lw_gpt, @@ -629,8 +635,7 @@ Radiation::run_impl () lw_clrsky_flux_up, lw_clrsky_flux_dn, lw_clnsky_flux_up, lw_clnsky_flux_dn, sw_bnd_flux_up, sw_bnd_flux_dn, sw_bnd_flux_dir, lw_bnd_flux_up, lw_bnd_flux_dn, - eccf, m_atm_logger, - m_extra_clnclrsky_diag, m_extra_clnsky_diag); + eccf, m_extra_clnclrsky_diag, m_extra_clnsky_diag); // Update heating tendency @@ -682,10 +687,10 @@ Radiation::run_impl () real1d cldfrac_tot_at_cldtop ("cldfrac_tot_at_cldtop", ncol); real1d eff_radius_qc_at_cldtop ("eff_radius_qc_at_cldtop", ncol); real1d eff_radius_qi_at_cldtop ("eff_radius_qi_at_cldtop", ncol); - rrtmgp::compute_aerocom_cloudtop(ncol, nlay, t_lay, p_lay, p_del, z_del, qc, qi, rel, rei, cldfrac_tot, - nc, T_mid_at_cldtop, p_mid_at_cldtop, cldfrac_ice_at_cldtop, - cldfrac_liq_at_cldtop, cldfrac_tot_at_cldtop, cdnc_at_cldtop, - eff_radius_qc_at_cldtop, eff_radius_qi_at_cldtop); + rrtmgp::compute_aerocom_cloudtop(ncol, nlay, t_lay, p_lay, p_del, z_del, qc_lay, qi_lay, eff_radius_qc, + eff_radius_qi, cldfrac_tot, nc_lay, T_mid_at_cldtop, p_mid_at_cldtop, + cldfrac_ice_at_cldtop, cldfrac_liq_at_cldtop, cldfrac_tot_at_cldtop, + cdnc_at_cldtop, eff_radius_qc_at_cldtop, eff_radius_qi_at_cldtop); } diff --git a/Source/TimeIntegration/ERF_AdvanceRadiation.cpp b/Source/TimeIntegration/ERF_AdvanceRadiation.cpp index 2e027a0d1..b42820f48 100644 --- a/Source/TimeIntegration/ERF_AdvanceRadiation.cpp +++ b/Source/TimeIntegration/ERF_AdvanceRadiation.cpp @@ -11,9 +11,9 @@ void ERF::advance_radiation (int lev, if (lev == 0) { rad[lev]->set_grids(lev, istep[lev], t_new[lev], dt_advance, cons.boxArray(), geom[lev], &(cons), - ls_lw_fluxes[lev], solar_zenith[lev], - qheating_rates[lev], z_phys_nd[lev], - lat_m[lev], lon_m[lev]); + sw_lw_fluxes[lev].get() , solar_zenith[lev].get(), + qheating_rates[lev].get(), z_phys_nd[lev].get() , + lat_m[lev].get(), lon_m[lev].get()); rad[lev]->rad_run_impl(); } } diff --git a/Source/TimeIntegration/ERF_TI_slow_rhs_fun.H b/Source/TimeIntegration/ERF_TI_slow_rhs_fun.H index 8e21174ca..fa6b5a370 100644 --- a/Source/TimeIntegration/ERF_TI_slow_rhs_fun.H +++ b/Source/TimeIntegration/ERF_TI_slow_rhs_fun.H @@ -438,7 +438,7 @@ make_sources(level, nrk, slow_dt, old_stage_time, S_data, S_prim, cc_src, z_phys_cc[level], #if defined(ERF_USE_RRTMGP) - qheating_rates[level], + qheating_rates[level].get(), #endif fine_geom, solverChoice, mapfac_u[level], mapfac_v[level], mapfac_m[level], From 56e88e69f246b6f88266de5f4a4a6724298beb2e Mon Sep 17 00:00:00 2001 From: AMLattanzi Date: Mon, 13 Jan 2025 14:57:11 -0800 Subject: [PATCH 07/13] Compiled in debug. --- Build/cmake_with_radiation_DEBUG.sh | 20 -------------------- CMake/BuildERFExe.cmake | 9 +-------- CMakeLists.txt | 10 +++++++--- 3 files changed, 8 insertions(+), 31 deletions(-) delete mode 100755 Build/cmake_with_radiation_DEBUG.sh diff --git a/Build/cmake_with_radiation_DEBUG.sh b/Build/cmake_with_radiation_DEBUG.sh deleted file mode 100755 index 451c93c08..000000000 --- a/Build/cmake_with_radiation_DEBUG.sh +++ /dev/null @@ -1,20 +0,0 @@ -#!/bin/bash - -# Example CMake config script for an OSX laptop with OpenMPI - -cmake -DCMAKE_INSTALL_PREFIX:PATH=./install \ - -DCMAKE_CXX_COMPILER:STRING=mpicxx \ - -DCMAKE_C_COMPILER:STRING=mpicc \ - -DCMAKE_Fortran_COMPILER:STRING=mpifort \ - -DMPIEXEC_PREFLAGS:STRING=--oversubscribe \ - -DCMAKE_BUILD_TYPE:STRING=Debug \ - -DERF_DIM:STRING=3 \ - -DERF_ENABLE_MPI:BOOL=ON \ - -DERF_ENABLE_TESTS:BOOL=ON \ - -DERF_ENABLE_RRTMGP:BOOL=ON \ - -DERF_ENABLE_NETCDF:BOOL=ON \ - -DERF_ENABLE_HDF5:BOOL=ON \ - -DERF_ENABLE_FCOMPARE:BOOL=ON \ - -DERF_ENABLE_DOCUMENTATION:BOOL=OFF \ - -DCMAKE_EXPORT_COMPILE_COMMANDS:BOOL=ON \ - .. && make VERBOSE=1 diff --git a/CMake/BuildERFExe.cmake b/CMake/BuildERFExe.cmake index 086f173f1..c4e2faa83 100644 --- a/CMake/BuildERFExe.cmake +++ b/CMake/BuildERFExe.cmake @@ -94,6 +94,7 @@ function(build_erf_lib erf_lib_name) ) target_compile_definitions(${erf_lib_name} PUBLIC ERF_USE_RRTMGP) target_compile_definitions(${erf_lib_name} PUBLIC RRTMGP_ENABLE_YAKL) + target_link_libraries(${erf_lib_name} PUBLIC yakl) endif() target_sources(${erf_lib_name} @@ -232,10 +233,6 @@ endif() endif() endif() - if(ERF_ENABLE_RRTMGP) - target_include_directories(${erf_lib_name} PUBLIC $) - endif() - if(ERF_ENABLE_MPI) target_link_libraries(${erf_lib_name} PUBLIC $<$:MPI::MPI_CXX>) endif() @@ -271,10 +268,6 @@ endif() target_include_directories(${erf_lib_name} PUBLIC $) target_include_directories(${erf_lib_name} PUBLIC $) - if(ERF_ENABLE_RRTMGP) - target_link_libraries(${erf_lib_name} PUBLIC yakl) - endif() - #Link to amrex library target_link_libraries_system(${erf_lib_name} PUBLIC AMReX::amrex) if(ERF_ENABLE_CUDA) diff --git a/CMakeLists.txt b/CMakeLists.txt index 67fb0bfe4..3593a717b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -176,17 +176,21 @@ if(ERF_ENABLE_RRTMGP) endif() # Build YAKL as a static library - # YAKL_HOME is YAKL's source directlry set(YAKL_HOME ${CMAKE_SOURCE_DIR}/Submodules/YAKL) - # YAKL_BIN is where we're placing the YAKL library set(YAKL_BIN ${CMAKE_BINARY_DIR}/yakl) - # Build the YAKL static library add_subdirectory(${YAKL_HOME} ${YAKL_BIN}) # Build the static rrtmgp library set(RRTMGP_BIN ${CMAKE_BINARY_DIR}/rrtmgp) add_subdirectory(${CMAKE_SOURCE_DIR}/Submodules/RRTMGP/cpp ${RRTMGP_BIN}) + # Set up libraries and definitions + set(RRTMGP_ENABLE_YAKL TRUE) + include(${YAKL_HOME}/yakl_utils.cmake) + yakl_process_target(rrtmgp) + target_compile_definitions(rrtmgp PUBLIC RRTMGP_ENABLE_YAKL) + target_include_directories(rrtmgp PUBLIC ${YAKL_HOME}) + message(STATUS "YAKL Flags: ${YAKL_COMPILER_FLAGS}") endif() ########################### ERF ##################################### From 3c1bcd40a3affc6f1ec3fed5291537b7fe2b13d2 Mon Sep 17 00:00:00 2001 From: AMLattanzi Date: Mon, 13 Jan 2025 15:23:38 -0800 Subject: [PATCH 08/13] Fix codespell. --- Source/ERF.cpp | 2 +- Source/Radiation/ERF_RRTMGP_Interface.cpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Source/ERF.cpp b/Source/ERF.cpp index 173c191f8..e59c88a97 100644 --- a/Source/ERF.cpp +++ b/Source/ERF.cpp @@ -143,7 +143,7 @@ ERF::ERF_shared () // NOTE: size canopy model before readparams (if file exists, we construct) m_forest_drag.resize(nlevs_max); for (int lev = 0; lev < max_level; ++lev) { m_forest_drag[lev] = nullptr;} - + ReadParameters(); initializeMicrophysics(nlevs_max); diff --git a/Source/Radiation/ERF_RRTMGP_Interface.cpp b/Source/Radiation/ERF_RRTMGP_Interface.cpp index 2859a812e..3838477ce 100644 --- a/Source/Radiation/ERF_RRTMGP_Interface.cpp +++ b/Source/Radiation/ERF_RRTMGP_Interface.cpp @@ -568,7 +568,7 @@ get_subcolumn_mask (const int ncol, } else { // Cloud-less above, use new random number so that clouds are distributed // randomly in this layer. Need to scale new random number to range - // [0, 1.0 - cldf(ilay-1)] because we have artifically changed the distribution + // [0, 1.0 - cldf(ilay-1)] because we have artificially changed the distribution // of random numbers in this layer with the above branch of the conditional, // which would otherwise inflate cloud fraction in this layer. cldx(icol,ilay,igpt) = cldx(icol,ilay ,igpt) * (1.0 - cldf(icol,ilay-1)); @@ -1117,7 +1117,7 @@ compute_aerocom_cloudtop (int ncol, int nlay, const real2d &tmid, const real2d & { // Loop over all layers in serial (due to accumulative // product), starting at 2 (second highest) layer because the - // highest is assumed to hav no clouds + // highest is assumed to have no clouds for(int ilay = 2; ilay <= nlay; ++ilay) { // Only do the calculation if certain conditions are met if((qc(icol, ilay) + qi(icol, ilay)) > q_threshold && From a542f237f1857903f31ba5eaaf628d04f4e40d49 Mon Sep 17 00:00:00 2001 From: AMLattanzi Date: Tue, 14 Jan 2025 15:09:32 -0800 Subject: [PATCH 09/13] Ran multiple steps with reasonable heating sources. --- Docs/sphinx_doc/Inputs.rst | 48 +++++++++ Exec/DevTests/Radiation/input_sounding_moist | 4 + Exec/DevTests/Radiation/inputs_radiation | 60 +++++++---- Source/ERF.cpp | 4 +- Source/Radiation/ERF_RRTMGP_Utils.H | 9 ++ Source/Radiation/ERF_Radiation.H | 12 ++- Source/Radiation/ERF_Radiation.cpp | 104 +++++++++++++------ 7 files changed, 181 insertions(+), 60 deletions(-) create mode 100644 Exec/DevTests/Radiation/input_sounding_moist diff --git a/Docs/sphinx_doc/Inputs.rst b/Docs/sphinx_doc/Inputs.rst index a5f31ad27..13a49c257 100644 --- a/Docs/sphinx_doc/Inputs.rst +++ b/Docs/sphinx_doc/Inputs.rst @@ -1507,6 +1507,54 @@ List of Parameters | | in treatment of moisture | | | +-----------------------------+--------------------------+--------------------+------------+ + +Radiation +========= + +ERF allows for radiative heating computations with the RRTMGP library. Source code must be compiled with CMAKE and the following flags enabled: ``-DERF_ENABLE_RRTMGP:BOOL=ON``, ``-DERF_ENABLE_NETCDF:BOOL=ON``, and ``-DERF_ENABLE_HDF5:BOOL=ON``; see **ERF/Build/cmake_with_radiation.sh**. + +List of Parameters +------------------ + ++--------------------------------+--------------------------+--------------------+-----------------------------------+ +| Parameter | Definition | Acceptable | Default | +| | | Values | | ++================================+==========================+====================+===================================+ +| **erf.rad_freq_in_steps** | Number of steps between | int | 1 | ++--------------------------------+--------------------------+--------------------+-----------------------------------+ +| **erf.nswbands** | Number sw bands | int | 14 | ++--------------------------------+--------------------------+--------------------+-----------------------------------+ +| **erf.nlwbands** | Number lw bands | int | 16 | ++--------------------------------+--------------------------+--------------------+-----------------------------------+ +| **erf.nswgpts** | Number sw gauss pts | int | 112 | ++--------------------------------+--------------------------+--------------------+-----------------------------------+ +| **erf.nlwgpts** | Number lw gaiss pts | int | 128 | ++--------------------------------+--------------------------+--------------------+-----------------------------------+ +| **erf.co2vmr** | CO2 volume mixing ratio | Real | 388.717e-6 | ++--------------------------------+--------------------------+--------------------+-----------------------------------+ +| **erf.o3 vmr** | O3 volume mixing ratio | Real | 1.887e-7 | ++--------------------------------+--------------------------+--------------------+-----------------------------------+ +| **erf.n2ovmr** | N2O volume mixing ratio | Real | 323.141e-9 | ++--------------------------------+--------------------------+--------------------+-----------------------------------+ +| **erf.covmr** | CO volume mixing ratio | Real | 1.000e-7 | ++--------------------------------+--------------------------+--------------------+-----------------------------------+ +| **erf.ch4vmr** | CH4 volume mixing ratio | Real | 1.807e-6 | ++--------------------------------+--------------------------+--------------------+-----------------------------------+ +| **erf.o2vmr** | O2 volume mixing ratio | Real | 0.209 | ++--------------------------------+--------------------------+--------------------+-----------------------------------+ +| **erf.n2vmr** | N2 volume mixing ratio | Real | 0.791 | ++--------------------------------+--------------------------+--------------------+-----------------------------------+ +| **erf.rrtmgp_file_path** | path to NC files | String | "./" | ++--------------------------------+--------------------------+--------------------+-----------------------------------+ +| **erf.rrtmgp_coeffs_sw** | path to NC files | String | rrtmgp-data-sw-g224-2018-12-04.nc | ++--------------------------------+--------------------------+--------------------+-----------------------------------+ +| **erf.rrtmgp_coeffs_lw** | path to NC files | String | rrtmgp-data-lw-g224-2018-12-04.nc | ++--------------------------------+--------------------------+--------------------+-----------------------------------+ +| **erf.rrtmgp_cloud_optics_sw** | path to NC files | String | rrtmgp-cloud-optics-coeffs-sw.nc | ++--------------------------------+--------------------------+--------------------+-----------------------------------+ +| **erf.rrtmgp_cloud_optics_lw** | path to NC files | String | rrtmgp-cloud-optics-coeffs-lw.nc | ++--------------------------------+--------------------------+--------------------+-----------------------------------+ + Runtime Error Checking ====================== diff --git a/Exec/DevTests/Radiation/input_sounding_moist b/Exec/DevTests/Radiation/input_sounding_moist new file mode 100644 index 000000000..f5de180ba --- /dev/null +++ b/Exec/DevTests/Radiation/input_sounding_moist @@ -0,0 +1,4 @@ + 1000.0 290.0 5.0 + 0.0 290.0 5.0 0.0 0.0 +10000.0 290.0 5.0 0.0 0.0 +20000.0 290.0 5.0 0.0 0.0 diff --git a/Exec/DevTests/Radiation/inputs_radiation b/Exec/DevTests/Radiation/inputs_radiation index 3683db5d0..cfd82bd16 100644 --- a/Exec/DevTests/Radiation/inputs_radiation +++ b/Exec/DevTests/Radiation/inputs_radiation @@ -1,18 +1,17 @@ # ------------------ INPUTS TO MAIN PROGRAM ------------------- -max_step = 1000 -stop_time = 90000.0 +max_step = 100 +stop_time = 100.0 amrex.fpe_trap_invalid = 1 -fabarray.mfiter_tile_size = 2048 1024 2048 +fabarray.mfiter_tile_size = 1024 1024 1024 # PROBLEM SIZE & GEOMETRY geometry.prob_lo = -25600. 0. 0. geometry.prob_hi = 25600. 400. 12800. amr.n_cell = 128 4 32 -# periodic in x to match WRF setup -# - as an alternative, could use symmetry at x=0 and outflow at x=25600 +# Periodic in x and y geometry.is_periodic = 1 1 0 zlo.type = "SlipWall" zhi.type = "SlipWall" @@ -32,30 +31,47 @@ amr.max_level = 0 # maximum level number allowed # CHECKPOINT FILES amr.check_file = chk # root name of checkpoint file amr.check_int = 10000 # number of timesteps between checkpoints -#amr.restart = chk01000 # PLOTFILES -erf.plot_file_1 = plt # root name of plotfile -erf.plot_int_1 = 1 # number of timesteps between plotfiles -erf.plot_vars_1 = density rhotheta rhoQ1 rhoQ2 x_velocity y_velocity z_velocity pressure theta temp qt qp qv qc qi qsrc_sw qsrc_lw - -erf.plot_rad = true +erf.plot_file_1 = plt # root name of plotfile +erf.plot_int_1 = 1 # number of timesteps between plotfiles +erf.plot_vars_1 = density rhotheta rhoQ1 rhoQ2 x_velocity y_velocity z_velocity pressure theta temp qv qc # SOLVER CHOICE -erf.use_gravity = true -erf.use_coriolis = false - -erf.moisture_model = "SAM" -erf.les_type = "Deardorff" - -#erf.molec_diff_type = "ConstantAlpha" -erf.molec_diff_type = "None" -#erf.dynamic_viscosity = 75.0 # [kg/(m-s)] -#erf.alpha_T = 75.0 # [m^2/s] +erf.use_gravity = true +erf.use_coriolis = false +erf.moisture_model = "SatAdj" +erf.molec_diff_type = "Constant" +erf.dynamic_viscosity = 1.0 # [kg/(m-s)] +erf.alpha_T = 1.0 # [m^2/s] +erf.alpha_C = 1.0 # [m^2/s] +erf.init_type = "input_sounding" +erf.input_sounding_file = "input_sounding_moist" +erf.init_sounding_ideal = true + +# RADIATION INPUTS +erf.rad_freq_in_steps = 1 +erf.do_subcol_sampling = true +erf.orbital_year = 2018 +erf.co2vmr = 388.717e-6 +erf.o3vmr = 1.887e-7 +erf.n2ovmr = 323.141e-9 +erf.covmr = 1.000e-7 +erf.ch4vmr = 1.807e-6 +erf.o2vmr = 0.209 +erf.n2vmr = 0.791 +erf.nswbands = 14 +erf.nlwbands = 16 +erf.nswgpts = 224 +erf.nlwgpts = 256 +erf.rrtmgp_file_path = /home/alattanz/git/data-files +erf.rrtmgp_coeffs_sw = rrtmgp-data-sw-g224-2018-12-04.nc +erf.rrtmgp_coeffs_lw = rrtmgp-data-lw-g256-2018-12-04.nc +erf.rrtmgp_cloud_optics_sw = rrtmgp-cloud-optics-coeffs-sw.nc +erf.rrtmgp_cloud_optics_lw = rrtmgp-cloud-optics-coeffs-lw.nc # PROBLEM PARAMETERS (optional) prob.T_0 = 300.0 prob.U_0 = 0 prob.KE_0 = 0.1 - prob.T_pert = 3 diff --git a/Source/ERF.cpp b/Source/ERF.cpp index e59c88a97..03507efe6 100644 --- a/Source/ERF.cpp +++ b/Source/ERF.cpp @@ -142,7 +142,7 @@ ERF::ERF_shared () // NOTE: size canopy model before readparams (if file exists, we construct) m_forest_drag.resize(nlevs_max); - for (int lev = 0; lev < max_level; ++lev) { m_forest_drag[lev] = nullptr;} + for (int lev = 0; lev <= max_level; ++lev) { m_forest_drag[lev] = nullptr;} ReadParameters(); initializeMicrophysics(nlevs_max); @@ -153,7 +153,7 @@ ERF::ERF_shared () #ifdef ERF_USE_RRTMGP rad.resize(nlevs_max); - for (int lev = 0; lev < max_level; ++lev) { rad[lev] = std::make_unique(solverChoice); } + for (int lev = 0; lev <= max_level; ++lev) { rad[lev] = std::make_unique(lev,solverChoice); } #endif const std::string& pv1 = "plot_vars_1"; setPlotVariables(pv1,plot_var_names_1); diff --git a/Source/Radiation/ERF_RRTMGP_Utils.H b/Source/Radiation/ERF_RRTMGP_Utils.H index 2c9830e3f..8e80ce797 100644 --- a/Source/Radiation/ERF_RRTMGP_Utils.H +++ b/Source/Radiation/ERF_RRTMGP_Utils.H @@ -83,6 +83,15 @@ compute_heating_rate (yakl::Array const &flux_up, heating_rate(icol,ilay) = ( flux_up(icol,ilay+1) - flux_up(icol,ilay) - flux_dn(icol,ilay+1) + flux_dn(icol,ilay) ) / (Cp_d * rho(icol,ilay) * dz(icol,ilay)); + + /* + if (icol == 1) { + printf("Heating Rate %i %e %e %e %e %e %e\n",ilay, + flux_up(icol,ilay+1), flux_up(icol,ilay), + flux_dn(icol,ilay+1), flux_dn(icol,ilay), + rho(icol,ilay), dz(icol,ilay)); + } + */ }); } diff --git a/Source/Radiation/ERF_Radiation.H b/Source/Radiation/ERF_Radiation.H index 358f0c323..6e576a641 100644 --- a/Source/Radiation/ERF_Radiation.H +++ b/Source/Radiation/ERF_Radiation.H @@ -42,7 +42,8 @@ class Radiation { public: // Constructor - Radiation (SolverChoice& sc); + Radiation (const int& lev, + SolverChoice& sc); // Destructor ~Radiation () = default; @@ -96,9 +97,11 @@ public: rad_run_impl () { if (m_update_rad) { + amrex::Print() << "Advancing radiation at level: " << m_lev << " ..."; this->initialize_impl(); this->run_impl(); this->finalize_impl(); + amrex::Print() << "DONE\n"; } } @@ -148,8 +151,8 @@ private: // Path, data file, and coefficient file for K-distribution std::string rrtmgp_file_path = "."; - std::string rrtmgp_coeffs_sw = "rrtmgp-data-sw-g112-210809.nc"; - std::string rrtmgp_coeffs_lw = "rrtmgp-data-lw-g128-210809.nc"; + std::string rrtmgp_coeffs_sw = "rrtmgp-data-sw-g224-2018-12-04.nc"; + std::string rrtmgp_coeffs_lw = "rrtmgp-data-lw-g224-2018-12-04.nc"; std::string rrtmgp_cloud_optics_sw = "rrtmgp-cloud-optics-coeffs-sw.nc"; std::string rrtmgp_cloud_optics_lw = "rrtmgp-cloud-optics-coeffs-lw.nc"; std::string rrtmgp_coeffs_file_sw; @@ -202,6 +205,7 @@ private: int m_orbital_year = -9999; int m_orbital_mon = -9999; int m_orbital_day = -9999; + int m_orbital_sec = -9999; // Orbital parameters, used for zenith angle calculations. // If >= 0, bypass computation based on orbital year and use fixed parameters @@ -227,8 +231,6 @@ private: int m_nswgpts = 112; int m_nlwgpts = 128; - - // Rad frequency in number of steps int m_rad_freq_in_steps = 1; diff --git a/Source/Radiation/ERF_Radiation.cpp b/Source/Radiation/ERF_Radiation.cpp index 23fa5fbea..6ec4187a4 100644 --- a/Source/Radiation/ERF_Radiation.cpp +++ b/Source/Radiation/ERF_Radiation.cpp @@ -20,7 +20,8 @@ using yakl::fortran::parallel_for; using yakl::fortran::SimpleBounds; -Radiation::Radiation (SolverChoice& sc) +Radiation::Radiation (const int& lev, + SolverChoice& sc) { // Initialize YAKL if (!yakl::isInitialized()) { yakl::init(); } @@ -45,7 +46,7 @@ Radiation::Radiation (SolverChoice& sc) // for duration of simulation. m_fixed_orbital_year = pp.query("orbital_year",m_orbital_year); - // Get orbital parameters from yaml file + // Get orbital parameters from inputs file pp.query("orbital_eccentricity", m_orbital_eccen); pp.query("orbital_obliquity" , m_orbital_obliq); pp.query("orbital_mvelp" , m_orbital_mvelp); @@ -73,6 +74,12 @@ Radiation::Radiation (SolverChoice& sc) pp.query("extra_clnclrsky_diag", m_extra_clnclrsky_diag); pp.query("extra_clnsky_diag" , m_extra_clnsky_diag); + // Parse the band and gauss pt sizes + pp.query("nswbands", m_nswbands); + pp.query("nlwbands", m_nlwbands); + pp.query("nswgpts" , m_nswgpts ); + pp.query("nlwgpts" , m_nlwgpts ); + // Parse path and file names pp.query("rrtmgp_file_path" , rrtmgp_file_path); pp.query("rrtmgp_coeffs_sw" , rrtmgp_coeffs_sw ); @@ -85,6 +92,17 @@ Radiation::Radiation (SolverChoice& sc) rrtmgp_coeffs_file_lw = rrtmgp_file_path + "/" + rrtmgp_coeffs_lw; rrtmgp_cloud_optics_file_sw = rrtmgp_file_path + "/" + rrtmgp_cloud_optics_sw; rrtmgp_cloud_optics_file_lw = rrtmgp_file_path + "/" + rrtmgp_cloud_optics_lw; + + // Output for user + if (lev == 0) { + Print() << "Radiation interface constructed:\n"; + Print() << "========================================================\n"; + Print() << "Coeff SW file: " << rrtmgp_coeffs_file_sw << "\n"; + Print() << "Coeff LW file: " << rrtmgp_coeffs_file_lw << "\n"; + Print() << "Cloud SW file: " << rrtmgp_cloud_optics_file_sw << "\n"; + Print() << "Cloud LW file: " << rrtmgp_cloud_optics_file_lw << "\n"; + Print() << "========================================================\n"; + } } void @@ -121,10 +139,12 @@ Radiation::set_grids (int& level, if (m_fixed_orbital_year) { m_orbital_mon = timeinfo->tm_mon + 1; m_orbital_day = timeinfo->tm_mday; + m_orbital_sec = timeinfo->tm_hour*3600 + timeinfo->tm_min*60 + timeinfo->tm_sec; } else { m_orbital_year = timeinfo->tm_year + 1900; m_orbital_mon = timeinfo->tm_mon + 1; m_orbital_day = timeinfo->tm_mday; + m_orbital_sec = timeinfo->tm_hour*3600 + timeinfo->tm_min*60 + timeinfo->tm_sec; } // Only allocate and proceed if we are going to update radiation @@ -160,14 +180,14 @@ Radiation::alloc_buffers () // 1d size (m_ngas) m_gas_mol_weights = real1d("m_gas_mol_weights", m_ngas); realHost1d m_gas_mol_weights_h("m_gas_mol_weights_h", m_ngas); + parallel_for(m_ngas, YAKL_LAMBDA (int igas) { - m_gas_mol_weights_h(igas) = m_mol_weight_gas[igas]; - gas_names_yakl_offset[igas] = m_gas_names[igas]; + m_gas_mol_weights_h(igas) = m_mol_weight_gas[igas-1]; + gas_names_yakl_offset.push_back(m_gas_names[igas-1]); }); m_gas_mol_weights.deep_copy_to(m_gas_mol_weights_h); - // 1d size (ncol) cosine_zenith = real1d("cosine_zenith" , m_ncol); mu0 = real1d("mu0" , m_ncol); @@ -395,12 +415,14 @@ Radiation::mf_to_yakl_buffers () + (z_arr(i+1,j ,k+1) - z_arr(i+1,j ,k)) + (z_arr(i ,j+1,k+1) - z_arr(i ,j+1,k)) + (z_arr(i+1,j ,k+1) - z_arr(i+1,j ,k)) ) : dz; + // TODO: candidate for deletion + d_dz(icol,ilay) = z_del(icol,ilay); qv_lay(icol,ilay) = qv; qc_lay(icol,ilay) = qc; qi_lay(icol,ilay) = qi; cldfrac_tot(icol,ilay) = ((qc+qi)>1.0e-5) ? 1. : 0.; - // HACK HACK HACK + // TODO: Fill properly lwp(icol,ilay) = 0.0; iwp(icol,ilay) = 0.0; @@ -424,12 +446,13 @@ Radiation::mf_to_yakl_buffers () parallel_for(SimpleBounds<2>(ncol, nlay), YAKL_LAMBDA (int icol, int ilay) { p_del(icol,ilay) = p_lev(icol,ilay+1) - p_lev(icol,ilay); + // TODO: Fill properly nc_lay(icol,ilay) = 0.0; eff_radius_qc(icol,ilay) = 0.0; eff_radius_qi(icol,ilay) = 0.0; }); - // HACK HACK HACK + // TODO: Fill properly // No LSM, so follow EAMXX dummy atmos and set constants yakl::memset(mu0, 0.86); yakl::memset(sfc_alb_dir_vis, 0.06); @@ -437,7 +460,7 @@ Radiation::mf_to_yakl_buffers () yakl::memset(sfc_alb_dif_vis, 0.06); yakl::memset(sfc_alb_dif_nir, 0.06); - // HACK HACK HACK + // TODO: Fill properly yakl::memset(aero_tau_sw, 0.0); yakl::memset(aero_ssa_sw, 0.0); yakl::memset(aero_g_sw , 0.0); @@ -455,37 +478,55 @@ Radiation::yakl_buffers_to_mf () const int imin = vbx.smallEnd(0); const int jmin = vbx.smallEnd(1); const int offset = m_col_offsets[mfi.index()]; - const Array4& q_arr = m_qheating_rates->array(mfi); - const Array4& lsm_arr = (m_lsm_fluxes) ? m_lsm_fluxes->array(mfi) : - Array4{}; + const Array4& q_arr = m_qheating_rates->array(mfi); ParallelFor(vbx, [=] AMREX_GPU_DEVICE (int i, int j, int k) { // map [i,j,k] 0-based to [icol, ilay] 1-based const int icol = (j-jmin)*nx + (i-imin) + 1 + offset; const int ilay = k+1; - // Heating rate for SW and LW (this is for Temperature) + // Temperature heating rate for SW and LW q_arr(i,j,k,0) = sw_heating(icol,ilay); q_arr(i,j,k,1) = lw_heating(icol,ilay); - }); - ParallelFor(sbx, [=] AMREX_GPU_DEVICE (int i, int j, int k) - { - // map [i,j,k] 0-based to [icol, ilay] 1-based - const int icol = (j-jmin)*nx + (i-imin) + 1 + offset; - - // SW fluxes for LSM - lsm_arr(i,j,k,0) = sfc_flux_dir_vis(icol); - lsm_arr(i,j,k,1) = sfc_flux_dir_nir(icol); - lsm_arr(i,j,k,2) = sfc_flux_dif_vis(icol); - lsm_arr(i,j,k,3) = sfc_flux_dif_nir(icol); - // New SW flux for LSM - lsm_arr(i,j,k,4) = sfc_flux_dir_vis(icol) + sfc_flux_dir_nir(icol) - + sfc_flux_dif_vis(icol) + sfc_flux_dif_nir(icol); - - // LW fluxe for LSM (at bottom surface) - lsm_arr(i,j,k,5) = lw_flux_dn(icol,1); + // Convert the rates for theta_d + Real exner = getExnergivenP(Real(p_lay(icol,ilay)), R_d/Cp_d); + q_arr(i,j,k,0) *= exner; + q_arr(i,j,k,1) *= exner; + + /* + if (i==0 && j==0) { + Print() << "Qsrcs: " << IntVect(i,j,k) << ' ' + << IntVect(icol,0,ilay) << ' ' + << q_arr(i,j,k,0) << ' ' + << q_arr(i,j,k,1) << ' ' + << sw_heating(icol,ilay) << ' ' + << lw_heating(icol,ilay) << ' ' + << exner << "\n"; + } + */ }); + if (m_lsm_fluxes) { + const Array4& lsm_arr = m_lsm_fluxes->array(mfi); + ParallelFor(sbx, [=] AMREX_GPU_DEVICE (int i, int j, int k) + { + // map [i,j,k] 0-based to [icol, ilay] 1-based + const int icol = (j-jmin)*nx + (i-imin) + 1 + offset; + + // SW fluxes for LSM + lsm_arr(i,j,k,0) = sfc_flux_dir_vis(icol); + lsm_arr(i,j,k,1) = sfc_flux_dir_nir(icol); + lsm_arr(i,j,k,2) = sfc_flux_dif_vis(icol); + lsm_arr(i,j,k,3) = sfc_flux_dif_nir(icol); + + // Net SW flux for LSM + lsm_arr(i,j,k,4) = sfc_flux_dir_vis(icol) + sfc_flux_dir_nir(icol) + + sfc_flux_dif_vis(icol) + sfc_flux_dif_nir(icol); + + // LW flux for LSM (at bottom surface) + lsm_arr(i,j,k,5) = lw_flux_dn(icol,1); + }); + } } } @@ -528,8 +569,9 @@ Radiation::run_impl () // Use the orbital parameters to calculate the solar declination and eccentricity factor real delta, eccf; - // TODO: Generalize this. - auto calday = (m_orbital_mon-1.0)*365.0/12.0 + m_orbital_day; // Want day + fraction; calday 1 == Jan 1 0Z + // TODO: Generalize the days per month. + // Want day + fraction; calday 1 == Jan 1 0Z + real calday = (m_orbital_mon-1.0)*365.0/12.0 + (m_orbital_day-1.0) + m_orbital_sec/86400.0; orbital_decl(calday, eccen, mvelpp, lambm0, obliqr, delta, eccf); From 4aaea12ac4046babc4bf23565bcd0ee49add8db2 Mon Sep 17 00:00:00 2001 From: Aaron Lattanzi Date: Wed, 15 Jan 2025 10:19:22 -0800 Subject: [PATCH 10/13] Clear gas names before next population. --- Source/Radiation/ERF_Radiation.cpp | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/Source/Radiation/ERF_Radiation.cpp b/Source/Radiation/ERF_Radiation.cpp index 6ec4187a4..412adfb9b 100644 --- a/Source/Radiation/ERF_Radiation.cpp +++ b/Source/Radiation/ERF_Radiation.cpp @@ -180,7 +180,7 @@ Radiation::alloc_buffers () // 1d size (m_ngas) m_gas_mol_weights = real1d("m_gas_mol_weights", m_ngas); realHost1d m_gas_mol_weights_h("m_gas_mol_weights_h", m_ngas); - + gas_names_yakl_offset.clear(); parallel_for(m_ngas, YAKL_LAMBDA (int igas) { m_gas_mol_weights_h(igas) = m_mol_weight_gas[igas-1]; @@ -494,17 +494,17 @@ Radiation::yakl_buffers_to_mf () q_arr(i,j,k,0) *= exner; q_arr(i,j,k,1) *= exner; - /* + if (i==0 && j==0) { - Print() << "Qsrcs: " << IntVect(i,j,k) << ' ' - << IntVect(icol,0,ilay) << ' ' - << q_arr(i,j,k,0) << ' ' - << q_arr(i,j,k,1) << ' ' - << sw_heating(icol,ilay) << ' ' - << lw_heating(icol,ilay) << ' ' - << exner << "\n"; + AllPrint() << "Qsrcs: " << IntVect(i,j,k) << ' ' + << IntVect(icol,0,ilay) << ' ' + << q_arr(i,j,k,0) << ' ' + << q_arr(i,j,k,1) << ' ' + << sw_heating(icol,ilay) << ' ' + << lw_heating(icol,ilay) << ' ' + << exner << "\n"; } - */ + }); if (m_lsm_fluxes) { const Array4& lsm_arr = m_lsm_fluxes->array(mfi); From e531a885f409854cb139e9d6abc5ef30525bf40a Mon Sep 17 00:00:00 2001 From: AMLattanzi Date: Tue, 21 Jan 2025 11:17:04 -0800 Subject: [PATCH 11/13] Add to codespell ignore. --- .codespell-ignore-words | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.codespell-ignore-words b/.codespell-ignore-words index fa19262b4..aa889352f 100644 --- a/.codespell-ignore-words +++ b/.codespell-ignore-words @@ -6,3 +6,6 @@ pres rime TE wth +abl +ABL +indx From 18a49c9c0bd7fb01cc9e442d39ad60d3dc8721c0 Mon Sep 17 00:00:00 2001 From: AMLattanzi Date: Tue, 21 Jan 2025 16:30:08 -0800 Subject: [PATCH 12/13] deep copy fixes and o3 vert prof. --- Exec/DevTests/Radiation/inputs_radiation | 2 +- Source/Radiation/ERF_Radiation.H | 10 +- Source/Radiation/ERF_Radiation.cpp | 141 +++++++++++------------ 3 files changed, 75 insertions(+), 78 deletions(-) diff --git a/Exec/DevTests/Radiation/inputs_radiation b/Exec/DevTests/Radiation/inputs_radiation index cfd82bd16..a86a11adb 100644 --- a/Exec/DevTests/Radiation/inputs_radiation +++ b/Exec/DevTests/Radiation/inputs_radiation @@ -54,7 +54,7 @@ erf.rad_freq_in_steps = 1 erf.do_subcol_sampling = true erf.orbital_year = 2018 erf.co2vmr = 388.717e-6 -erf.o3vmr = 1.887e-7 +erf.o3vmr = 1.887e-7 1.887e-7 1.887e-7 1.887e-7 1.887e-7 1.887e-7 1.887e-7 1.887e-7 1.887e-7 1.887e-7 1.887e-7 1.887e-7 1.887e-7 1.887e-7 1.887e-7 1.887e-7 1.887e-7 1.887e-7 1.887e-7 1.887e-7 1.887e-7 1.887e-7 1.887e-7 1.887e-7 1.887e-7 1.887e-7 1.887e-7 1.887e-7 1.887e-7 1.887e-7 1.887e-7 1.887e-7 erf.n2ovmr = 323.141e-9 erf.covmr = 1.000e-7 erf.ch4vmr = 1.807e-6 diff --git a/Source/Radiation/ERF_Radiation.H b/Source/Radiation/ERF_Radiation.H index 6e576a641..37ed4b49e 100644 --- a/Source/Radiation/ERF_Radiation.H +++ b/Source/Radiation/ERF_Radiation.H @@ -169,7 +169,7 @@ private: // Prescribed greenhouse gas surface concentrations in moles / moles air amrex::Real m_co2vmr = 388.717e-6; - amrex::Real m_o3vmr = 1.8868676125307193E-7; + amrex::Vector m_o3vmr; amrex::Real m_n2ovmr = 323.141e-9; amrex::Real m_covmr = 1.0e-7; amrex::Real m_ch4vmr = 1807.851e-9; @@ -178,6 +178,7 @@ private: //amrex::Real m_f11vmr = 768.7644e-12; //amrex::Real m_f12vmr = 531.2820e-12; + int m_o3_size; real1d m_gas_mol_weights; string1dv gas_names_yakl_offset; GasConcs m_gas_concs; @@ -237,6 +238,9 @@ private: // Whether or not to do subcolumn sampling of cloud state for MCICA bool m_do_subcol_sampling = true; + // 1d size (1 or nlay) + real1d o3_lay; + // 1d size (ncol) real1d cosine_zenith; real1d mu0; @@ -248,9 +252,10 @@ private: real1d sfc_flux_dir_nir; real1d sfc_flux_dif_vis; real1d sfc_flux_dif_nir; + real1d lat; + real1d lon; // 2d size (ncol, nlay) - real2d d_dz; real2d r_lay; real2d p_lay; real2d t_lay; @@ -258,7 +263,6 @@ private: real2d p_del; real2d qv_lay; real2d qc_lay; - real2d nc_lay; real2d qi_lay; real2d cldfrac_tot; real2d eff_radius_qc; diff --git a/Source/Radiation/ERF_Radiation.cpp b/Source/Radiation/ERF_Radiation.cpp index 412adfb9b..97e70c4ca 100644 --- a/Source/Radiation/ERF_Radiation.cpp +++ b/Source/Radiation/ERF_Radiation.cpp @@ -60,7 +60,7 @@ Radiation::Radiation (const int& lev, // Get prescribed surface values of greenhouse gases pp.query("co2vmr", m_co2vmr); - pp.query("o3vmr" , m_o3vmr ); + pp.queryarr("o3vmr" , m_o3vmr ); pp.query("n2ovmr", m_n2ovmr); pp.query("covmr" , m_covmr ); pp.query("ch4vmr", m_ch4vmr); @@ -186,7 +186,18 @@ Radiation::alloc_buffers () m_gas_mol_weights_h(igas) = m_mol_weight_gas[igas-1]; gas_names_yakl_offset.push_back(m_gas_names[igas-1]); }); - m_gas_mol_weights.deep_copy_to(m_gas_mol_weights_h); + m_gas_mol_weights_h.deep_copy_to(m_gas_mol_weights); + + // 1d size (1 or nlay) + m_o3_size = m_o3vmr.size(); + AMREX_ASSERT_WITH_MESSAGE(((m_o3_size==1) || (m_o3_size==m_nlay)), "O3 VMR array must be length 1 or nlay"); + o3_lay = real1d("o3_lay", m_o3_size); + realHost1d o3_lay_h("o3_lay_h", m_o3_size); + parallel_for(m_o3_size, YAKL_LAMBDA (int io3) + { + o3_lay_h(io3) = m_o3vmr[io3-1]; + }); + o3_lay_h.deep_copy_to(o3_lay); // 1d size (ncol) cosine_zenith = real1d("cosine_zenith" , m_ncol); @@ -199,9 +210,10 @@ Radiation::alloc_buffers () sfc_flux_dir_nir = real1d("sfc_flux_dir_nir", m_ncol); sfc_flux_dif_vis = real1d("sfc_flux_dif_vis", m_ncol); sfc_flux_dif_nir = real1d("sfc_flux_dif_nir", m_ncol); + lat = real1d("lat" , m_ncol); + lon = real1d("lon" , m_ncol);; // 2d size (ncol, nlay) - d_dz = real2d("d_dz" , m_ncol, m_nlay); r_lay = real2d("r_lay" , m_ncol, m_nlay); p_lay = real2d("p_lay" , m_ncol, m_nlay); t_lay = real2d("t_lay" , m_ncol, m_nlay); @@ -209,7 +221,6 @@ Radiation::alloc_buffers () p_del = real2d("p_del" , m_ncol, m_nlay); qv_lay = real2d("qv" , m_ncol, m_nlay); qc_lay = real2d("qc" , m_ncol, m_nlay); - nc_lay = real2d("nc" , m_ncol, m_nlay); qi_lay = real2d("qi" , m_ncol, m_nlay); cldfrac_tot = real2d("cldfrac_tot" , m_ncol, m_nlay); eff_radius_qc = real2d("eff_radius_qc", m_ncol, m_nlay); @@ -280,6 +291,9 @@ Radiation::dealloc_buffers () // 1d size (m_ngas) m_gas_mol_weights.deallocate(); + // 1d size (1 or nlay) + o3_lay.deallocate(); + // 1d size (ncol) cosine_zenith.deallocate(); mu0.deallocate(); @@ -291,9 +305,10 @@ Radiation::dealloc_buffers () sfc_flux_dir_nir.deallocate(); sfc_flux_dif_vis.deallocate(); sfc_flux_dif_nir.deallocate(); + lat.deallocate(); + lon.deallocate(); // 2d size (ncol, nlay) - d_dz.deallocate(); r_lay.deallocate(); p_lay.deallocate(); t_lay.deallocate(); @@ -301,7 +316,6 @@ Radiation::dealloc_buffers () p_del.deallocate(); qv_lay.deallocate(); qc_lay.deallocate(); - nc_lay.deallocate(); qi_lay.deallocate(); cldfrac_tot.deallocate(); eff_radius_qc.deallocate(); @@ -386,6 +400,10 @@ Radiation::mf_to_yakl_buffers () const Array4& cons_arr = m_cons_in->const_array(mfi); const Array4& z_arr = (m_z_phys) ? m_z_phys->const_array(mfi) : Array4{}; + const Array4& lat_arr = (m_lat) ? m_lat->const_array(mfi) : + Array4{}; + const Array4& lon_arr = (m_lon) ? m_lon->const_array(mfi) : + Array4{}; ParallelFor(vbx, [=] AMREX_GPU_DEVICE (int i, int j, int k) { // map [i,j,k] 0-based to [icol, ilay] 1-based @@ -415,14 +433,12 @@ Radiation::mf_to_yakl_buffers () + (z_arr(i+1,j ,k+1) - z_arr(i+1,j ,k)) + (z_arr(i ,j+1,k+1) - z_arr(i ,j+1,k)) + (z_arr(i+1,j ,k+1) - z_arr(i+1,j ,k)) ) : dz; - // TODO: candidate for deletion - d_dz(icol,ilay) = z_del(icol,ilay); qv_lay(icol,ilay) = qv; qc_lay(icol,ilay) = qc; qi_lay(icol,ilay) = qi; cldfrac_tot(icol,ilay) = ((qc+qi)>1.0e-5) ? 1. : 0.; - // TODO: Fill properly + // NOTE: These are populated in 'mixing_ratio_to_cloud_mass' lwp(icol,ilay) = 0.0; iwp(icol,ilay) = 0.0; @@ -439,6 +455,13 @@ Radiation::mf_to_yakl_buffers () p_lev(icol,ilay+1) = getPgivenRTh(rt_avg, qv_avg); t_lev(icol,ilay+1) = getTgivenRandRTh(r_avg, rt_avg, qv_avg); } + + // 1D data structures + if (k==0) { + lat(icol) = (m_lat) ? lat_arr(i,j,0) : 39.809860; + lon(icol) = (m_lon) ? lon_arr(i,j,0) : -98.555183; + } + }); } @@ -446,8 +469,7 @@ Radiation::mf_to_yakl_buffers () parallel_for(SimpleBounds<2>(ncol, nlay), YAKL_LAMBDA (int icol, int ilay) { p_del(icol,ilay) = p_lev(icol,ilay+1) - p_lev(icol,ilay); - // TODO: Fill properly - nc_lay(icol,ilay) = 0.0; + // TODO: How to compute these? eff_radius_qc(icol,ilay) = 0.0; eff_radius_qi(icol,ilay) = 0.0; }); @@ -494,7 +516,7 @@ Radiation::yakl_buffers_to_mf () q_arr(i,j,k,0) *= exner; q_arr(i,j,k,1) *= exner; - + /* if (i==0 && j==0) { AllPrint() << "Qsrcs: " << IntVect(i,j,k) << ' ' << IntVect(icol,0,ilay) << ' ' @@ -504,6 +526,7 @@ Radiation::yakl_buffers_to_mf () << lw_heating(icol,ilay) << ' ' << exner << "\n"; } + */ }); if (m_lsm_fluxes) { @@ -547,19 +570,16 @@ Radiation::run_impl () // Local copies const auto ncol = m_ncol; const auto nlay = m_nlay; - const auto nlwbands = m_nlwbands; const auto nswbands = m_nswbands; - const auto nlwgpts = m_nlwgpts; - const auto do_aerosol_rad = m_do_aerosol_rad; // Compute orbital parameters; these are used both for computing // the solar zenith angle and also for computing total solar // irradiance scaling (tsi_scaling). real obliqr, lambm0, mvelpp; - auto orbital_year = m_orbital_year; - auto eccen = m_orbital_eccen; - auto obliq = m_orbital_obliq; - auto mvelp = m_orbital_mvelp; + int orbital_year = m_orbital_year; + real eccen = m_orbital_eccen; + real obliq = m_orbital_obliq; + real mvelp = m_orbital_mvelp; if (eccen >= 0 && obliq >= 0 && mvelp >= 0) { // fixed orbital parameters forced with orbital_year == ORB_UNDEF_INT orbital_year = ORB_UNDEF_INT; @@ -568,12 +588,13 @@ Radiation::run_impl () mvelp, obliqr, lambm0, mvelpp); // Use the orbital parameters to calculate the solar declination and eccentricity factor - real delta, eccf; + real delta = 0.; + real eccf = 0.; // TODO: Generalize the days per month. // Want day + fraction; calday 1 == Jan 1 0Z - real calday = (m_orbital_mon-1.0)*365.0/12.0 + (m_orbital_day-1.0) + m_orbital_sec/86400.0; - orbital_decl(calday, eccen, mvelpp, - lambm0, obliqr, delta, eccf); + static constexpr real dpm = (365.0/12.0); + real calday = (m_orbital_mon-1.0)*dpm + std::min(m_orbital_day-1.0,dpm-1.0) + m_orbital_sec/86400.0; + orbital_decl(calday, eccen, mvelpp, lambm0, obliqr, delta, eccf); // Overwrite eccf if using a fixed solar constant. auto fixed_total_solar_irradiance = m_fixed_total_solar_irradiance; @@ -591,13 +612,19 @@ Radiation::run_impl () if (name == "H2O") { parallel_for(SimpleBounds<2>(ncol, nlay), YAKL_LAMBDA (int icol, int ilay) { - //h2o_vmr(icol,ilay) = qv_lay(icol,ilay) / (1.0 - qv_lay(icol,ilay)) * mwdair/gas_mol_weight; tmp2d(icol,ilay) = qv_lay(icol,ilay) * mwdair/ gas_mol_weight; }); } else if (name == "CO2") { yakl::memset(tmp2d, m_co2vmr); } else if (name == "O3") { - yakl::memset(tmp2d, m_o3vmr ); + if (m_o3_size==1) { + yakl::memset(tmp2d, m_o3vmr[0] ); + } else { + parallel_for(SimpleBounds<2>(ncol, nlay), YAKL_LAMBDA (int icol, int ilay) + { + tmp2d(icol,ilay) = o3_lay(ilay); + }); + } } else if (name == "N2O") { yakl::memset(tmp2d, m_n2ovmr); } else if (name == "CO") { @@ -621,9 +648,6 @@ Radiation::run_impl () // Calculate T_int from longwave flux up from the surface, assuming // blackbody emission with emissivity of 1. - /* - // NOTE: mu0 is HACKED to a constant - //==================================== // Determine the cosine zenith angle. // This must be done on HOST and copied to device. @@ -631,18 +655,25 @@ Radiation::run_impl () if (m_fixed_solar_zenith_angle > 0) { yakl::memset(h_mu0, m_fixed_solar_zenith_angle); } else { + realHost1d h_lat("h_lat", ncol); + realHost1d h_lon("h_lon", ncol); + lat.deep_copy_to(h_lat); + lon.deep_copy_to(h_lon); parallel_for(ncol, YAKL_LAMBDA (int icol) { // Convert lat/lon to radians - double lat = h_lat(icol)*PC::Pi/180.0; - double lon = h_lon(icol)*PC::Pi/180.0; - h_mu0(icol) = orbital_cos_zenith(calday, lat, lon, delta, m_rad_freq_in_steps * dt); + real dt = real(m_dt); + real lat_col = h_lat(icol)*PI/180.0; + real lon_col = h_lon(icol)*PI/180.0; + real lcalday = calday; + real ldelta = delta; + h_mu0(icol) = orbital_cos_zenith(lcalday, lat_col, lon_col, ldelta, m_rad_freq_in_steps * dt); }); } - mu0.deep_copy_to(h_mu0); - */ + h_mu0.deep_copy_to(mu0); - // Compute layer cloud mass (per unit area) + + // Compute layer cloud mass (per unit area), populates lwp/iwp rrtmgp::mixing_ratio_to_cloud_mass(qc_lay, cldfrac_tot, p_del, lwp); rrtmgp::mixing_ratio_to_cloud_mass(qi_lay, cldfrac_tot, p_del, iwp); @@ -681,8 +712,8 @@ Radiation::run_impl () // Update heating tendency - rrtmgp::compute_heating_rate(sw_flux_up, sw_flux_dn, r_lay, d_dz, sw_heating); - rrtmgp::compute_heating_rate(lw_flux_up, lw_flux_dn, r_lay, d_dz, lw_heating); + rrtmgp::compute_heating_rate(sw_flux_up, sw_flux_dn, r_lay, z_del, sw_heating); + rrtmgp::compute_heating_rate(lw_flux_up, lw_flux_dn, r_lay, z_del, lw_heating); // Compute surface fluxes @@ -695,44 +726,6 @@ Radiation::run_impl () sw_bnd_flux_dir , sw_bnd_flux_dif , sfc_flux_dir_vis, sfc_flux_dir_nir, sfc_flux_dif_vis, sfc_flux_dif_nir); - - - // TODO: Verify these are not needed, we don't have such output variables - //======================================================================= - - // Compute diagnostic total cloud area (vertically-projected cloud cover) - real1d cldlow ("cldlow", ncol); - real1d cldmed ("cldmed", ncol); - real1d cldhgh ("cldhgh", ncol); - real1d cldtot ("cldtot", ncol); - // NOTE: limits for low, mid, and high clouds are mostly taken from EAM F90 source, with the - // exception that I removed the restriction on low clouds to be above (numerically lower pressures) - // 1200 hPa, and on high clouds to be below (numerically high pressures) 50 hPa. This probably - // does not matter in practice, as clouds probably should not be produced above 50 hPa and we - // should not be encountering surface pressure above 1200 hPa, but in the event that things go off - // the rails we might want to look at these still. - rrtmgp::compute_cloud_area(ncol, nlay, nlwgpts, 700e2, std::numeric_limits::max(), p_lay, cld_tau_lw_gpt, cldlow); - rrtmgp::compute_cloud_area(ncol, nlay, nlwgpts, 400e2, 700e2, p_lay, cld_tau_lw_gpt, cldmed); - rrtmgp::compute_cloud_area(ncol, nlay, nlwgpts, 0, 400e2, p_lay, cld_tau_lw_gpt, cldhgh); - rrtmgp::compute_cloud_area(ncol, nlay, nlwgpts, 0, std::numeric_limits::max(), p_lay, cld_tau_lw_gpt, cldtot); - - - // Compute cloud-top diagnostics following AeroCOM recommendation - auto idx_067 = rrtmgp::get_wavelength_index_sw(0.67e-6); // Get visible 0.67 micron band for COSP - auto idx_105 = rrtmgp::get_wavelength_index_lw(10.5e-6); // Get IR 10.5 micron band for COSP - // Compute cloud-top diagnostics following AeroCom recommendation - real1d cdnc_at_cldtop ("cdnc_at_cldtop" , ncol); - real1d T_mid_at_cldtop ("T_mid_at_cldtop", ncol); - real1d p_mid_at_cldtop ("p_mid_at_cldtop", ncol); - real1d cldfrac_ice_at_cldtop ("cldfrac_ice_at_cldtop", ncol); - real1d cldfrac_liq_at_cldtop ("cldfrac_liq_at_cldtop", ncol); - real1d cldfrac_tot_at_cldtop ("cldfrac_tot_at_cldtop", ncol); - real1d eff_radius_qc_at_cldtop ("eff_radius_qc_at_cldtop", ncol); - real1d eff_radius_qi_at_cldtop ("eff_radius_qi_at_cldtop", ncol); - rrtmgp::compute_aerocom_cloudtop(ncol, nlay, t_lay, p_lay, p_del, z_del, qc_lay, qi_lay, eff_radius_qc, - eff_radius_qi, cldfrac_tot, nc_lay, T_mid_at_cldtop, p_mid_at_cldtop, - cldfrac_ice_at_cldtop, cldfrac_liq_at_cldtop, cldfrac_tot_at_cldtop, - cdnc_at_cldtop, eff_radius_qc_at_cldtop, eff_radius_qi_at_cldtop); } From 4cb44d23d718f47424afb15f7690091e433af51b Mon Sep 17 00:00:00 2001 From: AMLattanzi Date: Wed, 22 Jan 2025 15:13:38 -0800 Subject: [PATCH 13/13] This passed the unit test. --- Exec/DevTests/Radiation/ERF_Prob.H | 26 --- Exec/DevTests/Radiation/ERF_Prob.cpp | 204 ------------------- Exec/DevTests/Radiation/input_sounding_moist | 10 +- Exec/DevTests/Radiation/inputs_radiation | 39 ++-- Source/Radiation/ERF_Radiation.H | 11 + Source/Radiation/ERF_Radiation.cpp | 110 +++++++++- 6 files changed, 139 insertions(+), 261 deletions(-) diff --git a/Exec/DevTests/Radiation/ERF_Prob.H b/Exec/DevTests/Radiation/ERF_Prob.H index fe58ef527..b12218b8d 100644 --- a/Exec/DevTests/Radiation/ERF_Prob.H +++ b/Exec/DevTests/Radiation/ERF_Prob.H @@ -27,32 +27,6 @@ public: #include "Prob/ERF_InitDensityHSEDry.H" - void init_custom_pert ( - const amrex::Box& bx, - const amrex::Box& xbx, - const amrex::Box& ybx, - const amrex::Box& zbx, - amrex::Array4 const& state, - amrex::Array4 const& state_pert, - amrex::Array4 const& x_vel_pert, - amrex::Array4 const& y_vel_pert, - amrex::Array4 const& z_vel_pert, - amrex::Array4 const& r_hse, - amrex::Array4 const& p_hse, - amrex::Array4 const& z_nd, - amrex::Array4 const& z_cc, - amrex::GeometryData const& geomdata, - amrex::Array4 const& mf_m, - amrex::Array4 const& mf_u, - amrex::Array4 const& mf_v, - const SolverChoice& sc) override; - - void erf_init_rayleigh ( - amrex::Vector >& rayleigh_ptrs, - amrex::Geometry const& geom, - std::unique_ptr& z_phys_nd, - amrex::Real zdamp) override; - protected: std::string name() override { return "Radiation"; } diff --git a/Exec/DevTests/Radiation/ERF_Prob.cpp b/Exec/DevTests/Radiation/ERF_Prob.cpp index e377c8b41..34d1788e4 100644 --- a/Exec/DevTests/Radiation/ERF_Prob.cpp +++ b/Exec/DevTests/Radiation/ERF_Prob.cpp @@ -25,207 +25,3 @@ Problem::Problem() init_base_parms(parms.rho_0, parms.T_0); } - -AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE -Real -init_supercell_temperature(Real z, Real z_0, Real z_trop, Real z_top, - Real T_0, Real T_trop, Real T_top) -{ - if (z <= z_trop) { - Real lapse = - (T_trop - T_0) / (z_trop - z_0); - return T_0 - lapse * (z - z_0); - } else { - Real lapse = - (T_top - T_trop) / (z_top - z_trop); - return T_trop - lapse * (z - z_trop); - } -} - -AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE -Real -init_supercell_pressure(Real z, Real z_0, - Real z_trop, Real z_top, - Real T_0, Real T_trop, Real T_top) -{ - if (z <= z_trop) { - Real lapse = - (T_trop - T_0) / (z_trop - z_0); - Real T = init_supercell_temperature(z, z_0, z_trop, z_top, T_0, T_trop, T_top); - return p_0 * std::pow( T / T_0 , CONST_GRAV/(R_d*lapse) ); - } else { - // Get pressure at the tropopause - Real lapse = - (T_trop - T_0) / (z_trop - z_0); - Real p_trop = p_0 * std::pow( T_trop / T_0 , CONST_GRAV/(R_d*lapse) ); - // Get pressure at requested height - lapse = - (T_top - T_trop) / (z_top - z_trop); - if (lapse != 0) { - Real T = init_supercell_temperature(z, z_0, z_trop, z_top, T_0, T_trop, T_top); - return p_trop * std::pow( T / T_trop , CONST_GRAV/(R_d*lapse) ); - } else { - return p_trop * std::exp(-CONST_GRAV*(z-z_trop)/(R_d*T_trop)); - } - } -} - -AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE -Real -init_supercell_sat_mix(Real press, Real T ) { - return 380./(press) * std::exp(17.27*(T-273.)/(T-36.)); -} - -AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE -Real -init_supercell_relhum(Real z, Real z_trop) -{ - if (z <= z_trop) { - return 1. - 0.75 * pow(z / z_trop , 1.25); - } else { - return 0.25; - } -} - -void -Problem::init_custom_pert( - const Box& bx, - const Box& xbx, - const Box& ybx, - const Box& zbx, - amrex::Array4 const& /*state*/, - amrex::Array4 const& state_pert, - amrex::Array4 const& x_vel_pert, - amrex::Array4 const& y_vel_pert, - amrex::Array4 const& z_vel_pert, - Array4 const& r_hse, - Array4 const& p_hse, - Array4 const& /*z_nd*/, - Array4 const& /*z_cc*/, - GeometryData const& geomdata, - Array4 const& /*mf_m*/, - Array4 const& /*mf_u*/, - Array4 const& /*mf_v*/, - const SolverChoice& sc) -{ - const int khi = geomdata.Domain().bigEnd()[2]; - - const bool use_moisture = (sc.moisture_type != MoistureType::None); - - // This is what we do at k = 0 -- note we assume p = p_0 and T = T_0 at z=0 - const Real& dz = geomdata.CellSize()[2]; - const Real& prob_lo_z = geomdata.ProbLo()[2]; - const Real& prob_hi_z = geomdata.ProbHi()[2]; - - const Real rdOcp = sc.rdOcp; - - // const Real thetatr = 343.0; - // const Real theta0 = 300.0; - const Real ztr = 12000.0; - const Real Ttr = 213.0; - const Real Ttop = 213.0; - const Real deltaz = 1000.*0.0; - const Real zs = 5000.; - const Real us = 30.; - const Real uc = 15.; - - ParallelForRNG(bx, [=, parms_d=parms] AMREX_GPU_DEVICE(int i, int j, int k, const amrex::RandomEngine& engine) noexcept - { - // Geometry (note we must include these here to get the data on device) - const auto prob_lo = geomdata.ProbLo(); - const auto dx = geomdata.CellSize(); - const Real z = prob_lo[2] + (k + 0.5) * dx[2]; - - Real relhum = init_supercell_relhum(z, ztr); - Real temp = init_supercell_temperature(z, prob_lo_z, ztr, prob_hi_z, parms_d.T_0, Ttr, Ttop); - Real press = init_supercell_pressure(z, prob_lo_z, ztr, prob_hi_z, parms_d.T_0, Ttr, Ttop); - Real qvs = init_supercell_sat_mix(press, temp); - -#if 0 - Real thetaeq; - Real faceq; - if (z <= ztr) { - thetaeq = theta0 + (thetatr - theta0)*std::pow(z/ztr, 5./4.); - } else { - thetaeq = thetatr*std::exp(CONST_GRAV*(z-ztr)/c_p*Ttr); - } - - faceq = 1.; - for (int km = 0; km < k; ++km) { - Real zloc = prob_lo[2]+(km+0.5)*dx[2]; - Real theq; - if (zloc <= ztr) { - theq = theta0 + (thetatr - theta0)*std::pow(zloc/ztr, 5./4.); - } else { - theq = thetatr*std::exp(CONST_GRAV*(zloc-ztr)/c_p*Ttr); - } - faceq -= CONST_GRAV/(c_p*theq)*dz; - } - - temp = faceq*thetaeq; -#endif - - if (relhum*qvs > 0.014) relhum = 0.014/qvs; - Real qvapor = std::min(0.014, qvs*relhum); - Real rho = press/(R_d+qvapor*R_v)/temp; - - // perturb theta - Real rand_double = amrex::Random(engine) - 1.0; // Random number in [-1,1] - Real scaling = (khi-static_cast(k))/khi; // Less effect at higher levels - Real deltaT = parms_d.T_pert*scaling*rand_double; - - Real theta = getThgivenRandT(rho, temp+deltaT, rdOcp); - - // This version perturbs rho but not p - state_pert(i, j, k, RhoTheta_comp) = rho*theta - getRhoThetagivenP(p_hse(i,j,k)); - state_pert(i, j, k, Rho_comp) = rho - r_hse(i,j,k); - - // Set scalar = 0 everywhere - state_pert(i, j, k, RhoScalar_comp) = 0.0; - - // mean states - if (use_moisture) { - state_pert(i, j, k, RhoQ1_comp) = rho*qvapor; - state_pert(i, j, k, RhoQ2_comp) = 0.0; - } - }); - - // Set the x-velocity - ParallelFor(xbx, [=] AMREX_GPU_DEVICE(int i, int j, int k) noexcept - { - const Real z = prob_lo_z + (k+0.5) * dz; - if (z < zs-deltaz) { - x_vel_pert(i, j, k) = us*(z/zs) - uc; - } else if (std::abs(z-zs) < deltaz) { - x_vel_pert(i, j, k) = (-0.8+3.*(z/zs)-1.25*(z/zs)*(z/zs))*us-uc; - } else { - x_vel_pert(i, j, k) = us-uc; - } - }); - - // Set the y-velocity - ParallelFor(ybx, [=] AMREX_GPU_DEVICE(int i, int j, int k) noexcept { - y_vel_pert(i, j, k) = 0.0; - }); - - // Set the z-velocity - ParallelFor(zbx, [=] AMREX_GPU_DEVICE(int i, int j, int k) noexcept { - z_vel_pert(i, j, k) = 0.0; - }); - - amrex::Gpu::streamSynchronize(); -} - -void -Problem::erf_init_rayleigh( - amrex::Vector >& rayleigh_ptrs, - amrex::Geometry const& geom, - std::unique_ptr& /*z_phys_nd*/, - amrex::Real /*zdamp*/) -{ - const int khi = geom.Domain().bigEnd()[2]; - - // We just use these values to test the Rayleigh damping - for (int k = 0; k <= khi; k++) - { - rayleigh_ptrs[Rayleigh::ubar][k] = 2.0; - rayleigh_ptrs[Rayleigh::vbar][k] = 1.0; - rayleigh_ptrs[Rayleigh::wbar][k] = 0.0; - rayleigh_ptrs[Rayleigh::thetabar][k] = parms.T_0; - } -} diff --git a/Exec/DevTests/Radiation/input_sounding_moist b/Exec/DevTests/Radiation/input_sounding_moist index f5de180ba..861ddf25d 100644 --- a/Exec/DevTests/Radiation/input_sounding_moist +++ b/Exec/DevTests/Radiation/input_sounding_moist @@ -1,4 +1,6 @@ - 1000.0 290.0 5.0 - 0.0 290.0 5.0 0.0 0.0 -10000.0 290.0 5.0 0.0 0.0 -20000.0 290.0 5.0 0.0 0.0 + 1000.0 300.0 15.5 + 0.0 300.0 15.5 0.0 0.0 +10000.0 400.0 3.7 0.0 0.0 +25000.0 600.0 7.5e-3 0.0 0.0 +35000.0 700.0 4.0e-3 0.0 0.0 +50000.0 1500.0 3.5e-3 0.0 0.0 diff --git a/Exec/DevTests/Radiation/inputs_radiation b/Exec/DevTests/Radiation/inputs_radiation index a86a11adb..a6aaff4aa 100644 --- a/Exec/DevTests/Radiation/inputs_radiation +++ b/Exec/DevTests/Radiation/inputs_radiation @@ -1,5 +1,5 @@ # ------------------ INPUTS TO MAIN PROGRAM ------------------- -max_step = 100 +max_step = 100 stop_time = 100.0 amrex.fpe_trap_invalid = 1 @@ -7,9 +7,9 @@ amrex.fpe_trap_invalid = 1 fabarray.mfiter_tile_size = 1024 1024 1024 # PROBLEM SIZE & GEOMETRY -geometry.prob_lo = -25600. 0. 0. -geometry.prob_hi = 25600. 400. 12800. -amr.n_cell = 128 4 32 +geometry.prob_lo = 0. 0. 0. +geometry.prob_hi = 15000. 400. 50000. +amr.n_cell = 42 4 42 # Periodic in x and y geometry.is_periodic = 1 1 0 @@ -42,24 +42,25 @@ erf.use_gravity = true erf.use_coriolis = false erf.moisture_model = "SatAdj" erf.molec_diff_type = "Constant" -erf.dynamic_viscosity = 1.0 # [kg/(m-s)] -erf.alpha_T = 1.0 # [m^2/s] -erf.alpha_C = 1.0 # [m^2/s] +erf.dynamic_viscosity = 0.0 # [kg/(m-s)] +erf.alpha_T = 0.0 # [m^2/s] +erf.alpha_C = 0.0 # [m^2/s] erf.init_type = "input_sounding" erf.input_sounding_file = "input_sounding_moist" erf.init_sounding_ideal = true # RADIATION INPUTS erf.rad_freq_in_steps = 1 -erf.do_subcol_sampling = true +erf.do_subcol_sampling = true +erf.rad_write_fluxes = false erf.orbital_year = 2018 -erf.co2vmr = 388.717e-6 -erf.o3vmr = 1.887e-7 1.887e-7 1.887e-7 1.887e-7 1.887e-7 1.887e-7 1.887e-7 1.887e-7 1.887e-7 1.887e-7 1.887e-7 1.887e-7 1.887e-7 1.887e-7 1.887e-7 1.887e-7 1.887e-7 1.887e-7 1.887e-7 1.887e-7 1.887e-7 1.887e-7 1.887e-7 1.887e-7 1.887e-7 1.887e-7 1.887e-7 1.887e-7 1.887e-7 1.887e-7 1.887e-7 1.887e-7 -erf.n2ovmr = 323.141e-9 -erf.covmr = 1.000e-7 -erf.ch4vmr = 1.807e-6 -erf.o2vmr = 0.209 -erf.n2vmr = 0.791 +erf.co2vmr = 0.00036 +erf.o3vmr = 3.0e-8 3.0e-8 3.0e-8 3.0e-8 3.0e-8 3.0e-8 3.0e-8 3.0e-8 3.0e-8 3.0e-8 3.0e-8 3.0e-8 3.0e-8 3.0e-8 4.0e-8 4.25e-8 4.5e-8 4.9e-8 5.4e-8 6.0e-8 6.9e-8 8.0e-8 9.25e-8 1.07e-7 1.25e-7 1.63e-7 2.7e-7 5.4e-7 1.0e-6 1.7e-6 2.6e-6 4.0e-6 5.75e-6 7.5e-6 9.0e-6 9.6e-6 9.34e-6 7.83e-6 5.4e-6 3.3e-6 1.9e-6 9.1e-7 +erf.n2ovmr = 3.2e-7 +erf.covmr = 1.5e-7 +erf.ch4vmr = 1.7e-6 +erf.o2vmr = 0.209 +erf.n2vmr = 0.7906 erf.nswbands = 14 erf.nlwbands = 16 erf.nswgpts = 224 @@ -68,10 +69,4 @@ erf.rrtmgp_file_path = /home/alattanz/git/data-files erf.rrtmgp_coeffs_sw = rrtmgp-data-sw-g224-2018-12-04.nc erf.rrtmgp_coeffs_lw = rrtmgp-data-lw-g256-2018-12-04.nc erf.rrtmgp_cloud_optics_sw = rrtmgp-cloud-optics-coeffs-sw.nc -erf.rrtmgp_cloud_optics_lw = rrtmgp-cloud-optics-coeffs-lw.nc - -# PROBLEM PARAMETERS (optional) -prob.T_0 = 300.0 -prob.U_0 = 0 -prob.KE_0 = 0.1 -prob.T_pert = 3 +erf.rrtmgp_cloud_optics_lw = rrtmgp-cloud-optics-coeffs-lw.nc diff --git a/Source/Radiation/ERF_Radiation.H b/Source/Radiation/ERF_Radiation.H index 37ed4b49e..b4aaf9f3f 100644 --- a/Source/Radiation/ERF_Radiation.H +++ b/Source/Radiation/ERF_Radiation.H @@ -29,6 +29,7 @@ #include #include #include +#include #include #include @@ -80,6 +81,10 @@ public: void yakl_buffers_to_mf (); + // Write the rrtmgp fluxes + void + write_rrtmgp_fluxes (); + // Initialize the implementation void initialize_impl (); @@ -116,6 +121,9 @@ private: // Step number int m_step; + // Current time + amrex::Real m_time; + // Timestep at given level amrex::Real m_dt; @@ -128,6 +136,9 @@ private: // Are we updating radiation? bool m_update_rad = false; + // Are we writing out the fluxes? + bool m_rad_write_fluxes = false; + // Do we have moisture and cold comps? bool m_moist = false; bool m_ice = false; diff --git a/Source/Radiation/ERF_Radiation.cpp b/Source/Radiation/ERF_Radiation.cpp index 97e70c4ca..bbdbf514d 100644 --- a/Source/Radiation/ERF_Radiation.cpp +++ b/Source/Radiation/ERF_Radiation.cpp @@ -38,6 +38,9 @@ Radiation::Radiation (const int& lev, // Radiation timestep, as a number of atm steps pp.query("rad_freq_in_steps", m_rad_freq_in_steps); + // Flag to write fluxes to plt file + pp.query("rad_write_fluxes", m_rad_write_fluxes); + // Do MCICA subcolumn sampling pp.query("do_subcol_sampling", m_do_subcol_sampling); @@ -124,6 +127,7 @@ Radiation::set_grids (int& level, // Set data members that may change m_lev = level; m_step = step; + m_time = time; m_dt = dt; m_geom = geom; m_cons_in = cons_in; @@ -553,6 +557,41 @@ Radiation::yakl_buffers_to_mf () } } +void +Radiation::write_rrtmgp_fluxes () +{ + int n_fluxes = 5; + MultiFab mf_flux(m_cons_in->boxArray(), m_cons_in->DistributionMap(), n_fluxes, 0); + + for (MFIter mfi(mf_flux); mfi.isValid(); ++mfi) { + const auto& vbx = mfi.validbox(); + const int nx = vbx.length(0); + const int imin = vbx.smallEnd(0); + const int jmin = vbx.smallEnd(1); + const int offset = m_col_offsets[mfi.index()]; + const Array4& dst_arr = mf_flux.array(mfi); + ParallelFor(vbx, [=] AMREX_GPU_DEVICE (int i, int j, int k) + { + // map [i,j,k] 0-based to [icol, ilay] 1-based + const int icol = (j-jmin)*nx + (i-imin) + 1 + offset; + const int ilay = k+1; + + // SW and LW fluxes + dst_arr(i,j,k,0) = sw_flux_up(icol,ilay); + dst_arr(i,j,k,1) = sw_flux_dn(icol,ilay); + dst_arr(i,j,k,2) = sw_flux_dn_dir(icol,ilay); + dst_arr(i,j,k,3) = lw_flux_up(icol,ilay); + dst_arr(i,j,k,4) = lw_flux_dn(icol,ilay); + }); + } + + + std::string plotfilename = amrex::Concatenate("plt_rad", m_step, 5); + Vector flux_names = {"sw_flux_up", "sw_flux_dn", "sw_flux_dir", + "lw_flux_up", "lw_flux_dn"}; + WriteSingleLevelPlotfile(plotfilename, mf_flux, flux_names, m_geom, m_time, m_step); +} + void Radiation::initialize_impl () { @@ -588,8 +627,7 @@ Radiation::run_impl () mvelp, obliqr, lambm0, mvelpp); // Use the orbital parameters to calculate the solar declination and eccentricity factor - real delta = 0.; - real eccf = 0.; + real delta, eccf; // TODO: Generalize the days per month. // Want day + fraction; calday 1 == Jan 1 0Z static constexpr real dpm = (365.0/12.0); @@ -672,7 +710,6 @@ Radiation::run_impl () } h_mu0.deep_copy_to(mu0); - // Compute layer cloud mass (per unit area), populates lwp/iwp rrtmgp::mixing_ratio_to_cloud_mass(qc_lay, cldfrac_tot, p_del, lwp); rrtmgp::mixing_ratio_to_cloud_mass(qi_lay, cldfrac_tot, p_del, iwp); @@ -689,7 +726,7 @@ Radiation::run_impl () rrtmgp::compute_band_by_band_surface_albedos(ncol, nswbands, sfc_alb_dir_vis, sfc_alb_dir_nir, sfc_alb_dif_vis, sfc_alb_dif_nir, - sfc_alb_dir, sfc_alb_dif); + sfc_alb_dir , sfc_alb_dif); // Run RRTMGP driver rrtmgp::rrtmgp_main(ncol, m_nlay, @@ -710,12 +747,72 @@ Radiation::run_impl () sw_bnd_flux_up, sw_bnd_flux_dn, sw_bnd_flux_dir, lw_bnd_flux_up, lw_bnd_flux_dn, eccf, m_extra_clnclrsky_diag, m_extra_clnsky_diag); +#if 0 + // UNIT TEST + //================================================================================ + yakl::memset(mu0, 0.86); + yakl::memset(sfc_alb_dir_vis, 0.06); + yakl::memset(sfc_alb_dir_nir, 0.06); + yakl::memset(sfc_alb_dif_vis, 0.06); + yakl::memset(sfc_alb_dif_nir, 0.06); + + // Generate some fake liquid and ice water data. We pick values to be midway between + // the min and max of the valid lookup table values for effective radii + real rel_val = 0.5 * (rrtmgp::cloud_optics_sw.get_min_radius_liq() + + rrtmgp::cloud_optics_sw.get_max_radius_liq()); + real rei_val = 0.5 * (rrtmgp::cloud_optics_sw.get_min_radius_ice() + + rrtmgp::cloud_optics_sw.get_max_radius_ice()); + + // Restrict clouds to troposphere (> 100 hPa = 100*100 Pa) and not very close to the ground (< 900 hPa), and + // put them in 2/3 of the columns since that's roughly the total cloudiness of earth. + // Set sane values for liquid and ice water path. + // NOTE: these "sane" values are in g/m2! + parallel_for( SimpleBounds<2>(nlay,ncol) , YAKL_LAMBDA (int ilay, int icol) + { + cldfrac_tot(icol,ilay) = (p_lay(icol,ilay) > 100._wp * 100._wp) && + (p_lay(icol,ilay) < 900._wp * 100._wp) && + (icol%3 != 0); + // Ice and liquid will overlap in a few layers + lwp(icol,ilay) = (cldfrac_tot(icol,ilay) && t_lay(icol,ilay) > 263._wp) ? 10._wp : 0._wp; + iwp(icol,ilay) = (cldfrac_tot(icol,ilay) && t_lay(icol,ilay) < 273._wp) ? 10._wp : 0._wp; + eff_radius_qc(icol,ilay) = (lwp(icol,ilay) > 0._wp) ? rel_val : 0._wp; + eff_radius_qi(icol,ilay) = (iwp(icol,ilay) > 0._wp) ? rei_val : 0._wp; + }); + + rrtmgp::compute_band_by_band_surface_albedos(ncol, nswbands, + sfc_alb_dir_vis, sfc_alb_dir_nir, + sfc_alb_dif_vis, sfc_alb_dif_nir, + sfc_alb_dir , sfc_alb_dif); + + yakl::memset(aero_tau_sw, 0.0); + yakl::memset(aero_ssa_sw, 0.0); + yakl::memset(aero_g_sw , 0.0); + yakl::memset(aero_tau_lw, 0.0); + + rrtmgp::rrtmgp_main(ncol, m_nlay, + p_lay, t_lay, p_lev, t_lev, + m_gas_concs, + sfc_alb_dir, sfc_alb_dif, mu0, + lwp, iwp, eff_radius_qc, eff_radius_qi, cldfrac_tot, + aero_tau_sw, aero_ssa_sw, aero_g_sw, aero_tau_lw, + cld_tau_sw_bnd, cld_tau_lw_bnd, + cld_tau_sw_gpt, cld_tau_lw_gpt, + sw_flux_up, sw_flux_dn, sw_flux_dn_dir, lw_flux_up, lw_flux_dn, + sw_clnclrsky_flux_up, sw_clnclrsky_flux_dn, sw_clnclrsky_flux_dn_dir, + sw_clrsky_flux_up, sw_clrsky_flux_dn, sw_clrsky_flux_dn_dir, + sw_clnsky_flux_up, sw_clnsky_flux_dn, sw_clnsky_flux_dn_dir, + lw_clnclrsky_flux_up, lw_clnclrsky_flux_dn, + lw_clrsky_flux_up, lw_clrsky_flux_dn, + lw_clnsky_flux_up, lw_clnsky_flux_dn, + sw_bnd_flux_up, sw_bnd_flux_dn, sw_bnd_flux_dir, lw_bnd_flux_up, lw_bnd_flux_dn, + 1.0); + //================================================================================ +#endif // Update heating tendency rrtmgp::compute_heating_rate(sw_flux_up, sw_flux_dn, r_lay, z_del, sw_heating); rrtmgp::compute_heating_rate(lw_flux_up, lw_flux_dn, r_lay, z_del, lw_heating); - // Compute surface fluxes const int kbot = nlay + 1; // Should this be 1 for our layout? parallel_for(SimpleBounds<3>(ncol, nlay+1, nswbands), YAKL_LAMBDA (int icol, int ilay, int ibnd) @@ -739,6 +836,9 @@ Radiation::finalize_impl () // Fill the AMReX MFs from YAKL Arrays yakl_buffers_to_mf(); + // Write fluxes if requested + if (m_rad_write_fluxes) { write_rrtmgp_fluxes(); } + // Deallocate the buffer arrays dealloc_buffers(); }