From 87f6fad5ce8a5762cb86e791c5e2ce1a7930440a Mon Sep 17 00:00:00 2001 From: Matt Dawson Date: Tue, 31 Dec 2024 11:52:57 -0800 Subject: [PATCH] =?UTF-8?q?Add=20calculation=20of=20solar=20zenith=20angle?= =?UTF-8?q?=20and=20Earth=E2=80=93Sun=20distance=20(#337)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Tag name (required for release branches): Originator(s): Matt Dawson Description (include the issue title, and the keyword ['closes', 'fixes', 'resolves'] followed by the issue number): Adds the calculation of solar zenith angle and Earth–Sun distance and makes them available as CCPP standard named variables. @nusbaume - I wasn't sure if I put the call to recalculate the orbital properties is correct. If it isn't let me know, and I can move it. closes #328 Additionally: - adds some stubbed-out dependencies needed for MUSICA, until the actual values are available from other physics schemes or the host model - adds some testing infrastructure needed for unit tests of this code. Once #326 is merged in, I can update this PR to use the new unit testing infrastructure In draft until https://github.com/ESCOMP/atmospheric_physics/pull/171 is merged in Describe any changes made to build system: none Describe any changes made to the namelist: none List any changes to the defaults for the input datasets (e.g. boundary datasets): none List all files eliminated and why: none List all files added and what they do: List all existing files that have been modified, and describe the changes: (Helpful git command: `git diff --name-status development...`) If there are new failures (compared to the `test/existing-test-failures.txt` file), have them OK'd by the gatekeeper, note them here, and add them to the file. If there are baseline differences, include the test and the reason for the diff. What is the nature of the change? Roundoff? derecho/intel/aux_sima: derecho/gnu/aux_sima: If this changes climate describe any run(s) done to evaluate the new climate in enough detail that it(they) could be reproduced: CAM-SIMA date used for the baseline comparison tests if different than latest: --------- Co-authored-by: Courtney Peverley Co-authored-by: Kuan-Chih Wang --- .gitignore | 1 + src/control/cam_comp.F90 | 65 +++--- src/data/registry.xml | 2 + src/physics/ncar_ccpp | 2 +- .../utils/musica_ccpp_dependencies.F90 | 197 ++++++++++++++++++ .../utils/musica_ccpp_dependencies.meta | 48 +++++ src/physics/utils/orbital_data.F90 | 90 ++++++++ src/physics/utils/orbital_data.meta | 21 ++ test/CMakeLists.txt | 25 +++ test/cmake/SetDefaults.cmake | 4 + test/cmake/TestUtils.cmake | 23 ++ test/docker/Dockerfile | 40 ++++ test/unit/CMakeLists.txt | 1 + test/unit/physics/CMakeLists.txt | 1 + test/unit/physics/utils/CMakeLists.txt | 17 ++ test/unit/physics/utils/test_orbital_data.F90 | 57 +++++ .../physics/utils/test_orbital_data_stubs.F90 | 65 ++++++ test/valgrind.supp | 81 +++++++ 18 files changed, 715 insertions(+), 25 deletions(-) create mode 100644 src/physics/utils/musica_ccpp_dependencies.F90 create mode 100644 src/physics/utils/musica_ccpp_dependencies.meta create mode 100644 src/physics/utils/orbital_data.F90 create mode 100644 src/physics/utils/orbital_data.meta create mode 100644 test/CMakeLists.txt create mode 100644 test/cmake/SetDefaults.cmake create mode 100644 test/cmake/TestUtils.cmake create mode 100644 test/docker/Dockerfile create mode 100644 test/unit/CMakeLists.txt create mode 100644 test/unit/physics/CMakeLists.txt create mode 100644 test/unit/physics/utils/CMakeLists.txt create mode 100644 test/unit/physics/utils/test_orbital_data.F90 create mode 100644 test/unit/physics/utils/test_orbital_data_stubs.F90 create mode 100644 test/valgrind.supp diff --git a/.gitignore b/.gitignore index 04af4cbc..7d6f4be2 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,4 @@ test_driver_*.sh *~ .#* \#*# +**/.vscode/ diff --git a/src/control/cam_comp.F90 b/src/control/cam_comp.F90 index 8e12e405..0a8b1b63 100644 --- a/src/control/cam_comp.F90 +++ b/src/control/cam_comp.F90 @@ -84,25 +84,27 @@ subroutine cam_init(caseid, ctitle, model_doi_url, & ! !----------------------------------------------------------------------- - use cam_initfiles, only: cam_initfiles_open - use dyn_grid, only: model_grid_init - use phys_comp, only: phys_init - use phys_comp, only: phys_register - use dyn_comp, only: dyn_init -! use cam_restart, only: cam_read_restart - use camsrfexch, only: hub2atm_alloc, atm2hub_alloc - use cam_history, only: history_init_files -! use history_scam, only: scm_intht - use cam_pio_utils, only: init_pio_subsystem - use cam_instance, only: inst_suffix -! use history_defaults, only: initialize_iop_history - use stepon, only: stepon_init - use air_composition, only: air_composition_init - use cam_ccpp_cap, only: cam_ccpp_initialize_constituents - use physics_grid, only: columns_on_task - use vert_coord, only: pver - use phys_vars_init_check, only: mark_as_initialized - use tropopause_climo_read, only: tropopause_climo_read_file + use cam_initfiles, only: cam_initfiles_open + use dyn_grid, only: model_grid_init + use phys_comp, only: phys_init + use phys_comp, only: phys_register + use dyn_comp, only: dyn_init +! use cam_restart, only: cam_read_restart + use camsrfexch, only: hub2atm_alloc, atm2hub_alloc + use cam_history, only: history_init_files +! use history_scam, only: scm_intht + use cam_pio_utils, only: init_pio_subsystem + use cam_instance, only: inst_suffix +! use history_defaults, only: initialize_iop_history + use stepon, only: stepon_init + use air_composition, only: air_composition_init + use cam_ccpp_cap, only: cam_ccpp_initialize_constituents + use physics_grid, only: columns_on_task + use vert_coord, only: pver + use phys_vars_init_check, only: mark_as_initialized + use tropopause_climo_read, only: tropopause_climo_read_file + use musica_ccpp_dependencies, only: musica_ccpp_dependencies_init + use orbital_data, only: orbital_data_init ! Arguments character(len=cl), intent(in) :: caseid ! case ID @@ -259,6 +261,16 @@ subroutine cam_init(caseid, ctitle, model_doi_url, & ! end if call history_init_files(model_doi_url, caseid, ctitle) + ! Temporary: Prescribe realistic but inaccurate physical quantities + ! necessary for MUSICA that are currently unavailable in CAM-SIMA. + ! + ! Remove this when MUSICA input data are available from CAM-SIMA or + ! other physics schemes. + call musica_ccpp_dependencies_init(columns_on_task, pver, iulog) + + ! Initialize orbital data + call orbital_data_init(columns_on_task) + end subroutine cam_init ! @@ -271,8 +283,16 @@ subroutine cam_timestep_init() ! !----------------------------------------------------------------------- - use phys_comp, only: phys_timestep_init - use stepon, only: stepon_timestep_init + use phys_comp, only: phys_timestep_init + use physics_grid, only: lat_rad, lon_rad + use orbital_data, only: orbital_data_advance + use stepon, only: stepon_timestep_init + + ! Update current fractional calendar day. Needs to be updated at every timestep. + calday = get_curr_calday() + + ! Update the orbital data + call orbital_data_advance(calday, lat_rad, lon_rad) ! Update timestep flags in physics state is_first_timestep = is_first_step() @@ -294,9 +314,6 @@ subroutine cam_timestep_init() ! call phys_timestep_init() - ! Update current fractional calendar day. Needs to be updated at every timestep. - calday = get_curr_calday() - end subroutine cam_timestep_init ! !----------------------------------------------------------------------- diff --git a/src/data/registry.xml b/src/data/registry.xml index 91658e20..f1e02fbc 100644 --- a/src/data/registry.xml +++ b/src/data/registry.xml @@ -9,6 +9,8 @@ $SRCROOT/src/control/camsrfexch.meta $SRCROOT/src/control/runtime_obj.meta $SRCROOT/src/data/physconst.meta + $SRCROOT/src/physics/utils/orbital_data.meta + $SRCROOT/src/physics/utils/musica_ccpp_dependencies.meta $SRCROOT/src/physics/utils/physics_grid.meta $SRCROOT/src/physics/utils/cam_constituents.meta $SRCROOT/src/physics/utils/tropopause_climo_read.meta diff --git a/src/physics/ncar_ccpp b/src/physics/ncar_ccpp index 491e5624..45c37530 160000 --- a/src/physics/ncar_ccpp +++ b/src/physics/ncar_ccpp @@ -1 +1 @@ -Subproject commit 491e56247815ef23bfd8dba65d1e3c3b78ba164a +Subproject commit 45c37530ead4f5b93621e3d979e972ea91217923 diff --git a/src/physics/utils/musica_ccpp_dependencies.F90 b/src/physics/utils/musica_ccpp_dependencies.F90 new file mode 100644 index 00000000..3bd53167 --- /dev/null +++ b/src/physics/utils/musica_ccpp_dependencies.F90 @@ -0,0 +1,197 @@ +! Copyright (C) 2024 National Science Foundation-National Center for Atmospheric Research +! SPDX-License-Identifier: Apache-2.0 +module musica_ccpp_dependencies +!-------------------------------------------------------------------------- +! +! This module temporarily provides data that MUSICA chemistry consumes but +! does not produce. The values are realistic but are not based on the +! actual model state. These should be removed as the producers of this data +! are added to CAM-SIMA or as CCPP-compliant physics schemes. +! +! IMPORTANT: This module must be completely removed before doing any actual +! science with MUSICA chemistry in CAM-SIMA. +! +!-------------------------------------------------------------------------- + + use ccpp_kinds, only: kind_phys + + implicit none + private + + public :: musica_ccpp_dependencies_init + + !> \section arg_table_musica_ccpp_dependencies Argument Table + !! \htmlinclude arg_table_musica_ccpp_dependencies.html + !! + integer, public, protected :: photolysis_wavelength_grid_section_dimension = 102 + integer, public, protected :: photolysis_wavelength_grid_interface_dimension = 103 + real(kind_phys), allocatable, public, protected :: photolysis_wavelength_grid_interfaces(:) + real(kind_phys), allocatable, public, protected :: extraterrestrial_radiation_flux(:) + real(kind_phys), allocatable, public, protected :: surface_albedo(:) + real(kind_phys), allocatable, public, protected :: blackbody_temperature_at_surface(:) + real(kind_phys), allocatable, public, protected :: cloud_area_fraction(:,:) + + ! local parameters + character(len=*), parameter :: module_name = '(musica_ccpp_dependencies)' + +!============================================================================== +contains +!============================================================================== + + subroutine musica_ccpp_dependencies_init(horizontal_dimension, & + vertical_layer_dimension, log_file_unit) + + use cam_abortutils, only: check_allocate + + !----------------------------------------------------------------------- + ! + ! Initialize the MUSICA scheme dependencies. + ! + !----------------------------------------------------------------------- + + integer, intent(in) :: horizontal_dimension + integer, intent(in) :: vertical_layer_dimension + integer, intent(in) :: log_file_unit + + integer :: error_code + character(len=*), parameter :: subroutine_name = & + trim(module_name)//':(musica_ccpp_dependencies_init)' + + write(log_file_unit,*) 'WARNING: Using placeholder data for MUSICA chemistry.' + + allocate(photolysis_wavelength_grid_interfaces(photolysis_wavelength_grid_interface_dimension), & + stat=error_code) + call check_allocate(error_code, subroutine_name, & + 'photolysis_wavelength_grid_interfaces(photolysis_wavelength_grid_interface_dimension)', & + file=__FILE__, line=__LINE__) + allocate(extraterrestrial_radiation_flux(photolysis_wavelength_grid_section_dimension), & + stat=error_code) + call check_allocate(error_code, subroutine_name, & + 'extraterrestrial_radiation_flux(photolysis_wavelength_grid_section_dimension)', & + file=__FILE__, line=__LINE__) + allocate(surface_albedo(horizontal_dimension), stat=error_code) + call check_allocate(error_code, subroutine_name, & + 'surface_albedo(horizontal_dimension)', & + file=__FILE__, line=__LINE__) + allocate(blackbody_temperature_at_surface(horizontal_dimension), stat=error_code) + call check_allocate(error_code, subroutine_name, & + 'blackbody_temperature_at_surface(horizontal_dimension)', & + file=__FILE__, line=__LINE__) + allocate(cloud_area_fraction(horizontal_dimension, vertical_layer_dimension), stat=error_code) + call check_allocate(error_code, subroutine_name, & + 'cloud_area_fraction(horizontal_dimension, vertical_layer_dimension)', & + file=__FILE__, line=__LINE__) + + surface_albedo(:) = 0.1_kind_phys + blackbody_temperature_at_surface(:) = 292.3_kind_phys + cloud_area_fraction(:,:) = 0.7_kind_phys + extraterrestrial_radiation_flux(:) = 1.0e14_kind_phys + photolysis_wavelength_grid_interfaces = (/ & + 120.0e-9_kind_phys, & + 121.4e-9_kind_phys, & + 121.9e-9_kind_phys, & + 123.5e-9_kind_phys, & + 124.3e-9_kind_phys, & + 125.5e-9_kind_phys, & + 126.3e-9_kind_phys, & + 127.1e-9_kind_phys, & + 130.1e-9_kind_phys, & + 131.1e-9_kind_phys, & + 135.0e-9_kind_phys, & + 140.0e-9_kind_phys, & + 145.0e-9_kind_phys, & + 150.0e-9_kind_phys, & + 155.0e-9_kind_phys, & + 160.0e-9_kind_phys, & + 165.0e-9_kind_phys, & + 168.0e-9_kind_phys, & + 171.0e-9_kind_phys, & + 173.0e-9_kind_phys, & + 174.4e-9_kind_phys, & + 175.4e-9_kind_phys, & + 177.0e-9_kind_phys, & + 178.6e-9_kind_phys, & + 180.2e-9_kind_phys, & + 181.8e-9_kind_phys, & + 183.5e-9_kind_phys, & + 185.2e-9_kind_phys, & + 186.9e-9_kind_phys, & + 188.7e-9_kind_phys, & + 190.5e-9_kind_phys, & + 192.3e-9_kind_phys, & + 194.2e-9_kind_phys, & + 196.1e-9_kind_phys, & + 198.0e-9_kind_phys, & + 200.0e-9_kind_phys, & + 202.0e-9_kind_phys, & + 204.1e-9_kind_phys, & + 206.2e-9_kind_phys, & + 208.0e-9_kind_phys, & + 211.0e-9_kind_phys, & + 214.0e-9_kind_phys, & + 217.0e-9_kind_phys, & + 220.0e-9_kind_phys, & + 223.0e-9_kind_phys, & + 226.0e-9_kind_phys, & + 229.0e-9_kind_phys, & + 232.0e-9_kind_phys, & + 235.0e-9_kind_phys, & + 238.0e-9_kind_phys, & + 241.0e-9_kind_phys, & + 244.0e-9_kind_phys, & + 247.0e-9_kind_phys, & + 250.0e-9_kind_phys, & + 253.0e-9_kind_phys, & + 256.0e-9_kind_phys, & + 259.0e-9_kind_phys, & + 263.0e-9_kind_phys, & + 267.0e-9_kind_phys, & + 271.0e-9_kind_phys, & + 275.0e-9_kind_phys, & + 279.0e-9_kind_phys, & + 283.0e-9_kind_phys, & + 287.0e-9_kind_phys, & + 291.0e-9_kind_phys, & + 295.0e-9_kind_phys, & + 298.5e-9_kind_phys, & + 302.5e-9_kind_phys, & + 305.5e-9_kind_phys, & + 308.5e-9_kind_phys, & + 311.5e-9_kind_phys, & + 314.5e-9_kind_phys, & + 317.5e-9_kind_phys, & + 322.5e-9_kind_phys, & + 327.5e-9_kind_phys, & + 332.5e-9_kind_phys, & + 337.5e-9_kind_phys, & + 342.5e-9_kind_phys, & + 347.5e-9_kind_phys, & + 350.0e-9_kind_phys, & + 355.0e-9_kind_phys, & + 360.0e-9_kind_phys, & + 365.0e-9_kind_phys, & + 370.0e-9_kind_phys, & + 375.0e-9_kind_phys, & + 380.0e-9_kind_phys, & + 385.0e-9_kind_phys, & + 390.0e-9_kind_phys, & + 395.0e-9_kind_phys, & + 400.0e-9_kind_phys, & + 405.0e-9_kind_phys, & + 410.0e-9_kind_phys, & + 415.0e-9_kind_phys, & + 420.0e-9_kind_phys, & + 430.0e-9_kind_phys, & + 440.0e-9_kind_phys, & + 450.0e-9_kind_phys, & + 500.0e-9_kind_phys, & + 550.0e-9_kind_phys, & + 600.0e-9_kind_phys, & + 650.0e-9_kind_phys, & + 700.0e-9_kind_phys, & + 750.0e-9_kind_phys & + /) + + end subroutine musica_ccpp_dependencies_init + +end module musica_ccpp_dependencies \ No newline at end of file diff --git a/src/physics/utils/musica_ccpp_dependencies.meta b/src/physics/utils/musica_ccpp_dependencies.meta new file mode 100644 index 00000000..0d14908e --- /dev/null +++ b/src/physics/utils/musica_ccpp_dependencies.meta @@ -0,0 +1,48 @@ +[ccpp-table-properties] + name = musica_ccpp_dependencies + type = module +[ccpp-arg-table] + name = musica_ccpp_dependencies + type = module +[ photolysis_wavelength_grid_section_dimension ] + standard_name = photolysis_wavelength_grid_section_dimension + units = count + type = integer + dimensions = () + protected = True +[ photolysis_wavelength_grid_interface_dimension ] + standard_name = photolysis_wavelength_grid_interface_dimension + units = count + type = integer + dimensions = () + protected = True +[ photolysis_wavelength_grid_interfaces ] + standard_name = photolysis_wavelength_grid_interfaces + units = m + type = real | kind = kind_phys + dimensions = (photolysis_wavelength_grid_interface_dimension) + protected = True +[ extraterrestrial_radiation_flux ] + standard_name = extraterrestrial_radiation_flux + units = photons cm-2 s-1 nm-1 + type = real | kind = kind_phys + dimensions = (photolysis_wavelength_grid_section_dimension) + protected = True +[ surface_albedo ] + standard_name = surface_albedo_due_to_UV_and_VIS_direct + units = none + type = real | kind = kind_phys + dimensions = (horizontal_dimension) + protected = True +[ blackbody_temperature_at_surface ] + standard_name = blackbody_temperature_at_surface + type = real | kind = kind_phys + units = K + dimensions = (horizontal_dimension) + protected = True +[ cloud_area_fraction ] + standard_name = cloud_area_fraction + type = real | kind = kind_phys + units = fraction + dimensions = (horizontal_dimension,vertical_layer_dimension) + protected = True diff --git a/src/physics/utils/orbital_data.F90 b/src/physics/utils/orbital_data.F90 new file mode 100644 index 00000000..df9f852d --- /dev/null +++ b/src/physics/utils/orbital_data.F90 @@ -0,0 +1,90 @@ +! Copyright (C) 2024 National Science Foundation-National Center for Atmospheric Research +! SPDX-License-Identifier: Apache-2.0 +module orbital_data +!-------------------------------------------------------------------------- +! +! Provides access to conditions calculated based on the Earth's orbit. +! +!-------------------------------------------------------------------------- + + use ccpp_kinds, only: kind_phys + use shr_orb_mod, only: FILL_R8 => SHR_ORB_UNDEF_REAL + + implicit none + private + + public :: orbital_data_init, orbital_data_advance + + !> \section arg_table_orbital_data Argument Table + !! \htmlinclude arg_table_orbital_data.html + !! + real(kind_phys), protected, public :: solar_declination = FILL_R8 ! Solar declination angle [radians] + real(kind_phys), protected, public :: earth_sun_distance = FILL_R8 ! Earth-sun distance [AU] + real(kind_phys), allocatable, protected, public :: solar_zenith_angle(:) ! Solar zenith angle (column) [radians] + + ! Local parameters + character(len=*), parameter :: module_name = '(orbital_data)' + +!======================================================================= +contains +!======================================================================= + + subroutine orbital_data_init(number_of_columns) + + use cam_abortutils, only: check_allocate + + !----------------------------------------------------------------------- + ! + ! Initialize the orbital data module. + ! + !----------------------------------------------------------------------- + + integer, intent(in) :: number_of_columns + + integer :: error_code + character(len=*), parameter :: subroutine_name = & + trim(module_name)//':(orbital_data_init)' + + allocate(solar_zenith_angle(number_of_columns), source=FILL_R8, & + stat=error_code) + call check_allocate(error_code, subroutine_name, & + 'solar_zenith_angle(number_of_columns)', & + file=__FILE__, line=__LINE__) + + end subroutine orbital_data_init + + !======================================================================= + + subroutine orbital_data_advance(calendar_day, latitudes, longitudes) + + !----------------------------------------------------------------------- + ! + ! Advance the orbital data to the current simulation time. + ! + !----------------------------------------------------------------------- + + use shr_orb_mod, only: shr_orb_decl, shr_orb_cosz + use cam_control_mod, only: eccen, mvelpp, lambm0, obliqr + + real(kind_phys), intent(in) :: calendar_day ! Fractional Julian calendar day (1.xx to 365.xx) + real(kind_phys), intent(in) :: latitudes(:) ! Centered latitude (column) [radians] + real(kind_phys), intent(in) :: longitudes(:) ! Centered longitude (column) [radians] + + integer :: i + + ! Compute the solar declination angle [radians] and Earth-sun distance [AU] + call shr_orb_decl(calendar_day, eccen, mvelpp, lambm0, obliqr, & + solar_declination, earth_sun_distance) + + ! Compute the solar zenith angle [radians] + do i = 1, size(latitudes) + solar_zenith_angle(i) = acos(shr_orb_cosz(calendar_day, latitudes(i), & + longitudes(i), solar_declination)) + end do + + end subroutine orbital_data_advance + + !======================================================================= + +end module orbital_data + diff --git a/src/physics/utils/orbital_data.meta b/src/physics/utils/orbital_data.meta new file mode 100644 index 00000000..611f7535 --- /dev/null +++ b/src/physics/utils/orbital_data.meta @@ -0,0 +1,21 @@ +[ccpp-table-properties] + name = orbital_data + type = module +[ccpp-arg-table] + name = orbital_data + type = module +[solar_declination] + standard_name = solar_declination + units = rad + type = real | kind = kind_phys + dimensions = () +[earth_sun_distance] + standard_name = earth_sun_distance + units = AU + type = real | kind = kind_phys + dimensions = () +[solar_zenith_angle] + standard_name = solar_zenith_angle + units = rad + type = real | kind = kind_phys + dimensions = (horizontal_dimension) \ No newline at end of file diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt new file mode 100644 index 00000000..21b46f77 --- /dev/null +++ b/test/CMakeLists.txt @@ -0,0 +1,25 @@ +cmake_minimum_required(VERSION 3.21) + +project( + cam-sima-tests + VERSION 0.0.0 + LANGUAGES Fortran +) + +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH};${CMAKE_CURRENT_LIST_DIR}/cmake) +set(CMAKE_USER_MAKE_RULES_OVERRIDE ${CMAKE_MODULE_PATH}/SetDefaults.cmake) +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) + +option(CCPP_ENABLE_MEMCHECK "Enable memory checks in tests" OFF) + +set(INSTALL_GTEST OFF CACHE BOOL "" FORCE) +set(BUILD_GMOCK OFF CACHE BOOL "" FORCE) + +set(CAM_SIMA_SRC_PATH ${CMAKE_SOURCE_DIR}/../src) +set(CAM_SIMA_TEST_PATH ${CMAKE_SOURCE_DIR}) + +include(TestUtils) +include(CTest) +enable_testing() + +add_subdirectory(unit) \ No newline at end of file diff --git a/test/cmake/SetDefaults.cmake b/test/cmake/SetDefaults.cmake new file mode 100644 index 00000000..39e5d1d5 --- /dev/null +++ b/test/cmake/SetDefaults.cmake @@ -0,0 +1,4 @@ +# Overwrite the init values choosen by CMake +if (CMAKE_Fortran_COMPILER_ID MATCHES "GNU") + set(CMAKE_Fortran_FLAGS_DEBUG_INIT "-g") +endif() diff --git a/test/cmake/TestUtils.cmake b/test/cmake/TestUtils.cmake new file mode 100644 index 00000000..df31b9cd --- /dev/null +++ b/test/cmake/TestUtils.cmake @@ -0,0 +1,23 @@ +################################################################################ +# Utility functions for creating tests + +if(CCPP_ENABLE_MEMCHECK) + find_program(MEMORYCHECK_COMMAND "valgrind") + + # Set the Valgrind suppressions file for tests + set(MEMCHECK_SUPPRESS "--suppressions=${CMAKE_SOURCE_DIR}/valgrind.supp") +endif() + +################################################################################ +# Runs a test with memory checking if enabled + +function(add_memory_check_test test_name test_binary test_args working_dir) + if(CCPP_ENABLE_MEMCHECK) + add_test(NAME memcheck_${test_name} + COMMAND mpirun -v -np 1 ${MEMORYCHECK_COMMAND} --leak-check=full --error-exitcode=1 --trace-children=yes --gen-suppressions=all ${MEMCHECK_SUPPRESS} + ${test_binary} ${test_args} + WORKING_DIRECTORY ${working_dir}) + endif() +endfunction(add_memory_check_test) + +################################################################################ \ No newline at end of file diff --git a/test/docker/Dockerfile b/test/docker/Dockerfile new file mode 100644 index 00000000..474be7e5 --- /dev/null +++ b/test/docker/Dockerfile @@ -0,0 +1,40 @@ +# This Dockerfile is designed for running CAM-SIMA fortran unit tests and integration tests. +FROM ubuntu:22.04 + +ARG BUILD_TYPE=Debug + +RUN apt update \ + && apt install -y sudo \ + && useradd -m test_user \ + && echo "test_user ALL=(root) NOPASSWD: ALL" >> /etc/sudoers.d/test_user \ + && chmod 0440 /etc/sudoers.d/test_user + +USER test_user +WORKDIR /home/test_user + +RUN sudo apt update \ + && sudo apt -y install \ + cmake \ + cmake-curses-gui \ + gfortran \ + git \ + libopenmpi-dev \ + make \ + openmpi-bin \ + valgrind \ + vim \ + && sudo apt clean + +ENV FC=mpif90 +ENV FFLAGS="-I/usr/include/" + +COPY . cam_sima +RUN sudo chown -R test_user:test_user cam_sima + +RUN cd cam_sima/test \ + && cmake -S . -B build \ + -D CMAKE_BUILD_TYPE=${BUILD_TYPE} \ + -D CCPP_ENABLE_MEMCHECK=ON \ + && cmake --build ./build + +WORKDIR /home/test_user/cam_sima/test/build \ No newline at end of file diff --git a/test/unit/CMakeLists.txt b/test/unit/CMakeLists.txt new file mode 100644 index 00000000..e220ee79 --- /dev/null +++ b/test/unit/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectory(physics) \ No newline at end of file diff --git a/test/unit/physics/CMakeLists.txt b/test/unit/physics/CMakeLists.txt new file mode 100644 index 00000000..47202fc6 --- /dev/null +++ b/test/unit/physics/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectory(utils) \ No newline at end of file diff --git a/test/unit/physics/utils/CMakeLists.txt b/test/unit/physics/utils/CMakeLists.txt new file mode 100644 index 00000000..abf72100 --- /dev/null +++ b/test/unit/physics/utils/CMakeLists.txt @@ -0,0 +1,17 @@ +# Test orbital_data module +add_executable(test_physics_utils_orbital_data test_orbital_data.F90 test_orbital_data_stubs.F90) + +target_sources(test_physics_utils_orbital_data + PUBLIC + ${CAM_SIMA_SRC_PATH}/physics/utils/orbital_data.F90 +) + +target_compile_options(test_physics_utils_orbital_data PRIVATE -ffree-line-length-none) + +add_test( + NAME test_physics_utils_orbital_data + COMMAND $ + WORKING_DIRECTORY ${CMAKE_RUNTIME_OUTPUT_DIRECTORY} +) + +add_memory_check_test(test_physics_utils_orbital_data $ "" ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}) \ No newline at end of file diff --git a/test/unit/physics/utils/test_orbital_data.F90 b/test/unit/physics/utils/test_orbital_data.F90 new file mode 100644 index 00000000..d04c5573 --- /dev/null +++ b/test/unit/physics/utils/test_orbital_data.F90 @@ -0,0 +1,57 @@ +! Copyright (C) 2024 National Science Foundation-National Center for Atmospheric Research +! SPDX-License-Identifier: Apache-2.0 +!-------------------------------------------------------------------------- +! +! Tests of the orbital_data module. +! +!-------------------------------------------------------------------------- + +! Assert macros +#define ASSERT(x) if (.not.(x)) then; write(*,*) "Assertion failed[", __FILE__, ":", __LINE__, "]: x"; stop 1; endif +#define ASSERT_NEAR( a, b, abs_error ) if( (abs(a - b) >= abs_error) .and. (abs(a - b) /= 0.0) ) then; write(*,*) "Assertion failed[", __FILE__, ":", __LINE__, "]: a, b"; stop 1; endif + +program test_orbital_data + + implicit none + + call test_orbital_data_functions() + +contains + + subroutine test_orbital_data_functions + + use orbital_data + use shr_kind_mod, only: R8 => SHR_KIND_R8 + implicit none + + integer, parameter :: NUMBER_OF_COLUMNS = 3 + real(kind=R8) :: calendar_day + real(kind=R8) :: latitudes(NUMBER_OF_COLUMNS) + real(kind=R8) :: longitudes(NUMBER_OF_COLUMNS) + integer :: i + + calendar_day = 10.5_R8 + latitudes = [1.0_R8, 2.0_R8, 3.0_R8] + longitudes = [4.0_R8, 5.0_R8, 6.0_R8] + + call orbital_data_init(NUMBER_OF_COLUMNS) + + ASSERT(allocated(solar_zenith_angle)) + ASSERT(size(solar_zenith_angle) == NUMBER_OF_COLUMNS) + ASSERT(all(solar_zenith_angle == -1.0_R8)) + ASSERT(solar_declination == -1.0_R8) + ASSERT(earth_sun_distance == -1.0_R8) + + call orbital_data_advance(calendar_day, latitudes, longitudes) + + ASSERT(allocated(solar_zenith_angle)) + ASSERT(size(solar_zenith_angle) == NUMBER_OF_COLUMNS) + ASSERT_NEAR(solar_zenith_angle(1), acos(latitudes(1) + longitudes(1) + calendar_day * 21.0_R8), 1.0E-6_R8) + ASSERT_NEAR(solar_zenith_angle(2), acos(latitudes(2) + longitudes(2) + calendar_day * 21.0_R8), 1.0E-6_R8) + ASSERT_NEAR(solar_zenith_angle(3), acos(latitudes(3) + longitudes(3) + calendar_day * 21.0_R8), 1.0E-6_R8) + ASSERT(solar_declination == calendar_day * 2.0_R8) + ASSERT(earth_sun_distance == calendar_day * 3.0_R8) + + end subroutine test_orbital_data_functions + +end program test_orbital_data \ No newline at end of file diff --git a/test/unit/physics/utils/test_orbital_data_stubs.F90 b/test/unit/physics/utils/test_orbital_data_stubs.F90 new file mode 100644 index 00000000..7822a140 --- /dev/null +++ b/test/unit/physics/utils/test_orbital_data_stubs.F90 @@ -0,0 +1,65 @@ +! Copyright (C) 2024 National Science Foundation-National Center for Atmospheric Research +! SPDX-License-Identifier: Apache-2.0 +!-------------------------------------------------------------------------- +! +! Stub modules for the orbital_data module tests. +! +!-------------------------------------------------------------------------- + +module shr_kind_mod + implicit none + integer, parameter :: SHR_KIND_R8 = selected_real_kind(15) +end module shr_kind_mod +module ccpp_kinds + implicit none + integer, parameter :: kind_phys = selected_real_kind(15) +end module ccpp_kinds +module cam_abortutils + implicit none + public :: check_allocate +contains + subroutine check_allocate(error_code, subroutine_name, variable_name, file, line) + integer, intent(in) :: error_code + character(len=*), intent(in) :: subroutine_name + character(len=*), intent(in) :: variable_name + character(len=*), intent(in) :: file + integer, intent(in) :: line + if (error_code /= 0) then + write(*,*) "Allocation of '", variable_name, "' failed with code ", error_code, " in ", subroutine_name, " at ", file, ":", line + stop 3 + end if + end subroutine check_allocate +end module cam_abortutils +module shr_orb_mod + use shr_kind_mod, only: R8 => SHR_KIND_R8 + implicit none + real(kind=R8), parameter :: SHR_ORB_UNDEF_REAL = -1.0_R8 + public :: shr_orb_decl, shr_orb_cosz +contains + subroutine shr_orb_decl(calendar_day, eccen, mvelpp, lambm0, obliqr, solar_declination, earth_sun_distance) + real(kind=R8), intent(in) :: calendar_day + real(kind=R8), intent(in) :: eccen + real(kind=R8), intent(in) :: mvelpp + real(kind=R8), intent(in) :: lambm0 + real(kind=R8), intent(in) :: obliqr + real(kind=R8), intent(out) :: solar_declination + real(kind=R8), intent(out) :: earth_sun_distance + solar_declination = calendar_day * 2.0 + earth_sun_distance = calendar_day * 3.0 + end subroutine shr_orb_decl + real(R8) pure function shr_orb_cosz(calendar_day, latitude, longitude, solar_declination) + real(kind=R8), intent(in) :: calendar_day + real(kind=R8), intent(in) :: latitude + real(kind=R8), intent(in) :: longitude + real(kind=R8), intent(in) :: solar_declination + shr_orb_cosz = latitude + longitude + solar_declination + end function shr_orb_cosz +end module shr_orb_mod +module cam_control_mod + use shr_kind_mod, only: R8 => SHR_KIND_R8 + implicit none + real(kind=R8) :: eccen = 2.0_R8 + real(kind=R8) :: mvelpp = 3.0_R8 + real(kind=R8) :: lambm0 = 4.0_R8 + real(kind=R8) :: obliqr = 5.0_R8 +end module cam_control_mod \ No newline at end of file diff --git a/test/valgrind.supp b/test/valgrind.supp new file mode 100644 index 00000000..ee1ba85e --- /dev/null +++ b/test/valgrind.supp @@ -0,0 +1,81 @@ +############################################################## +# +# MUSICA TUV-x suppressions +# +# TODO(jiwon) We are experiencing memory leak issues in certain +# functions of TUV-x. It appears that these leaks occur only +# occasionally during initialization. We believe it’s acceptable +# to add a Valgrind suppression for now, and we will investigate +# further if it becomes a significant concern. +# +############################################################## +{ + Suppress_MUSICA_TUV-x_Leak1 + Memcheck:Leak + fun:malloc + fun:__musica_config_MOD_get_string + fun:__tuvx_radiator_aerosol_MOD_constructor + fun:__tuvx_radiator_factory_MOD_radiator_builder + fun:__tuvx_radiator_warehouse_MOD_constructor + fun:__tuvx_radiative_transfer_MOD_constructor + fun:__tuvx_core_MOD_constructor + fun:InternalCreateTuvx + ... +} +{ + Suppress_MUSICA_TUV-x_Leak2 + Memcheck:Leak + fun:malloc + fun:__musica_config_MOD_get_string + fun:__tuvx_radiator_MOD_base_constructor + fun:__tuvx_radiator_MOD_constructor + fun:__tuvx_radiator_factory_MOD_radiator_builder + fun:__tuvx_radiator_warehouse_MOD_constructor + fun:__tuvx_radiative_transfer_MOD_constructor + fun:__tuvx_core_MOD_constructor + fun:InternalCreateTuvx + ... +} +{ + Suppress_MUSICA_TUV-x_CreateRadiator + Memcheck:Leak + match-leak-kinds: definite + fun:malloc + fun:__musica_string_MOD_string_assign_char + fun:__tuvx_radiator_from_host_MOD_constructor_char + fun:__tuvx_radiator_from_host_MOD_constructor_string + fun:InternalCreateRadiator + ... +} +{ + Suppress_MUSICA_TUV-x_AddRadiator + Memcheck:Leak + match-leak-kinds: definite + fun:malloc + fun:__tuvx_radiator_from_host_MOD___copy_tuvx_radiator_from_host_Radiator_from_host_t + fun:__tuvx_radiator_warehouse_MOD_add_radiator + fun:InternalAddRadiator + ... +} +{ + Suppress_MUSICA_TUV-x_GetRadiator + Memcheck:Leak + match-leak-kinds: definite + fun:malloc + fun:__tuvx_radiator_from_host_MOD___copy_tuvx_radiator_from_host_Radiator_from_host_t + fun:InternalGetRadiator + ... +} +{ + Suppress_MUSICA_TUV-x_CreateTuvx-RadiatorFromHost + Memcheck:Leak + match-leak-kinds: definite + fun:malloc + fun:__tuvx_radiator_from_host_MOD___copy_tuvx_radiator_from_host_Radiator_from_host_t + fun:__tuvx_radiator_warehouse_MOD_add_radiator + fun:__tuvx_radiator_warehouse_MOD_add_radiators + fun:__tuvx_radiative_transfer_MOD_constructor + fun:__tuvx_core_MOD_constructor + fun:InternalCreateTuvx + ... +} \ No newline at end of file