diff --git a/src/core_atmosphere/Makefile b/src/core_atmosphere/Makefile index 966027bc77..9c3a58686e 100644 --- a/src/core_atmosphere/Makefile +++ b/src/core_atmosphere/Makefile @@ -2,12 +2,13 @@ # # To build a dycore-only MPAS-Atmosphere model, comment-out or delete -# the definition of PHYSICS, below +# the definition of PHYSICS and CHEMISTRY, below # -# If MPAS_CAM_DYCORE is found in CPPFLAGS, PHYSICS will become undefined automatically +# If MPAS_CAM_DYCORE is found in CPPFLAGS, PHYSICS and CHEMISTRY will become undefined automatically # ifeq ($(findstring MPAS_CAM_DYCORE,$(CPPFLAGS)),) PHYSICS = -DDO_PHYSICS + CHEMISTRY = -DDO_CHEMISTRY endif ifdef PHYSICS @@ -15,16 +16,21 @@ ifdef PHYSICS PHYS_OBJS = libphys/*.o endif +ifdef CHEMISTRY + CHEMCORE = chemcore + CHEM_OBJS = libchem/*.o +endif + OBJS = mpas_atm_core.o \ mpas_atm_core_interface.o \ mpas_atm_dimensions.o \ mpas_atm_threading.o \ mpas_atm_halos.o -all: $(PHYSCORE) dycore diagcore atmcore utilities +all: $(PHYSCORE) $(CHEMCORE) dycore diagcore atmcore utilities core_reg: - $(CPP) $(CPPFLAGS) $(CPPINCLUDES) $(PHYSICS) Registry.xml > Registry_processed.xml + $(CPP) $(CPPFLAGS) $(CPPINCLUDES) $(PHYSICS) $(CHEMISTRY) Registry.xml > Registry_processed.xml core_input_gen: if [ ! -e default_inputs ]; then mkdir default_inputs; fi @@ -47,7 +53,11 @@ physcore: mpas_atm_dimensions.o ( cd ../..; ln -sf ./src/core_atmosphere/physics/physics_wrf/files/*DATA* .) ( cd ../..; ln -sf ./src/core_atmosphere/physics/physics_noahmp/parameters/*TBL .) -dycore: mpas_atm_dimensions.o $(PHYSCORE) +chemcore: + ( cd chemistry; $(MAKE) all CHEMISTRY="$(CHEMISTRY)" ) + ( mkdir libchem; cd libchem; ar -x ../chemistry/libchem.a ) + +dycore: mpas_atm_dimensions.o $(PHYSCORE) $(CHEMCORE) ( cd dynamics; $(MAKE) all PHYSICS="$(PHYSICS)" ) diagcore: $(PHYSCORE) dycore @@ -57,7 +67,7 @@ utilities: $(PHYSCORE) ( cd utils; $(MAKE) all PHYSICS="$(PHYSICS)" ) atmcore: $(PHYSCORE) dycore diagcore $(OBJS) - ar -ru libdycore.a $(OBJS) dynamics/*.o $(PHYS_OBJS) diagnostics/*.o + ar -ru libdycore.a $(OBJS) dynamics/*.o $(PHYS_OBJS) $(CHEM_OBJS) diagnostics/*.o mpas_atm_core_interface.o: mpas_atm_core.o @@ -67,6 +77,7 @@ mpas_atm_dimensions.o: clean: ( cd physics; $(MAKE) clean ) + ( cd chemistry; $(MAKE) clean ) ( cd dynamics; $(MAKE) clean ) ( cd diagnostics; $(MAKE) clean ) ( cd utils; $(MAKE) clean ) @@ -83,8 +94,8 @@ clean: .F.o: $(RM) $@ $*.mod ifeq "$(GEN_F90)" "true" - $(CPP) $(CPPFLAGS) $(PHYSICS) $(CPPINCLUDES) -I./inc $< > $*.f90 - $(FC) $(FFLAGS) -c $*.f90 $(FCINCLUDES) -I../framework -I../operators -I./physics -I./dynamics -I./diagnostics -I./physics/physics_wrf -I./physics/physics_mmm -I./physics/physics_noaa/UGWP -I../external/esmf_time_f90 + $(CPP) $(CPPFLAGS) $(PHYSICS) $(CHEMISTRY) $(CPPINCLUDES) -I./inc $< > $*.f90 + $(FC) $(FFLAGS) -c $*.f90 $(FCINCLUDES) -I../framework -I../operators -I./physics -I./dynamics -I./diagnostics -I./physics/physics_wrf -I./physics/physics_mmm -I./physics/physics_noaa/UGWP -I../external/esmf_time_f90 -I./chemistry else - $(FC) $(CPPFLAGS) $(PHYSICS) $(FFLAGS) -c $*.F $(CPPINCLUDES) $(FCINCLUDES) -I./inc -I../framework -I../operators -I./physics -I./dynamics -I./diagnostics -I./physics/physics_wrf -I./physics/physics_mmm -I./physics/physics_noaa/UGWP -I../external/esmf_time_f90 + $(FC) $(CPPFLAGS) $(PHYSICS) $(CHEMISTRY) $(FFLAGS) -c $*.F $(CPPINCLUDES) $(FCINCLUDES) -I./inc -I../framework -I../operators -I./physics -I./dynamics -I./diagnostics -I./physics/physics_wrf -I./physics/physics_mmm -I./physics/physics_noaa/UGWP -I../external/esmf_time_f90 -I./chemistry endif diff --git a/src/core_atmosphere/chemistry/Makefile b/src/core_atmosphere/chemistry/Makefile new file mode 100644 index 0000000000..5bd4480b7d --- /dev/null +++ b/src/core_atmosphere/chemistry/Makefile @@ -0,0 +1,47 @@ +.SUFFIXES: .F .o + +all: +ifeq ($(MUSICA),true) + echo "****** building MUSICA ******" + $(MAKE) core_mpas_musica core_chemistry +else + echo "****** not building MUSICA ******" + $(MAKE) core_chemistry +endif + +dummy: + echo "****** compiling chemistry ******" + +OBJS = \ + mpas_atm_chemistry.o + +ifeq ($(MUSICA),true) +core_mpas_musica: + echo "****** building CORE MPAS MUSICA ******" + (cd musica; $(MAKE) all) +endif + +core_chemistry: $(if $(filter true,$(MUSICA)),core_mpas_musica) + ($(MAKE) chem_interface) + ar -ru libchem.a $(OBJS) +ifeq ($(MUSICA),true) + ($(MAKE) -C ./musica mpas_musica_lib) +endif + +chem_interface: $(OBJS) + +clean: + $(RM) *.o *.mod *.f90 libchem.a + ( cd musica; $(MAKE) clean ) + @# Certain systems with intel compilers generate *.i files + @# This removes them during the clean process + $(RM) *.i + +.F.o: + $(RM) $@ $*.mod +ifeq "$(GEN_F90)" "true" + $(CPP) $(CPPFLAGS) $(CPPINCLUDES) $< > $*.f90 + $(FC) $(FFLAGS) -c $*.f90 $(FCINCLUDES) -I./musica -I.. -I../../framework -I../../../external/esmf_time_f90 +else + $(FC) $(CPPFLAGS) $(FFLAGS) -c $*.F $(CPPINCLUDES) $(FCINCLUDES) -I./musica -I.. -I../../framework -I../../../external/esmf_time_f90 +endif diff --git a/src/core_atmosphere/chemistry/mpas_atm_chemistry.F b/src/core_atmosphere/chemistry/mpas_atm_chemistry.F new file mode 100644 index 0000000000..39715cf37a --- /dev/null +++ b/src/core_atmosphere/chemistry/mpas_atm_chemistry.F @@ -0,0 +1,126 @@ +! Copyright (c) 2025 The University Corporation for Atmospheric Research (UCAR). +! +! Unless noted otherwise source code is licensed under the BSD license. +! Additional copyright and license information can be found in the LICENSE file +! distributed with this code, or at https://mpas-dev.github.io/license.html . +! +!----------------------------------------------------------------------- +! mpas_atm_chemistry +! +!> \brief Manages interactions with chemistry packages +!> \author CheMPAS-A Developers +!> \date 20 August 2025 +!> \details +!> This module manages the interactions with chemistry packages, +!> including initialization, time-stepping, and finalization. +!> It provides a framework for integrating various chemistry models +!> into the CheMPAS-A system. +! +!------------------------------------------------------------------------- +module mpas_atm_chemistry + + implicit none + + private + + public :: chemistry_init, chemistry_step, chemistry_finalize + + contains + + !------------------------------------------------------------------------ + ! routine chemistry_init + ! + !> \brief Initializes the chemistry packages + !> \author CheMPAS-A Developers + !> \date 20 August 2025 + !> \details + !> This routine initializes the chemistry packages, setting up + !> necessary parameters and data structures for the simulation. + !------------------------------------------------------------------------ + subroutine chemistry_init(configs, dimensions) + +#ifdef MPAS_USE_MUSICA + use mpas_musica, only: musica_init +#endif + use mpas_log, only : mpas_log_write + use mpas_derived_types, only: mpas_pool_type + use mpas_kind_types, only: StrKIND + use mpas_pool_routines, only: mpas_pool_get_config, mpas_pool_get_dimension + + type (mpas_pool_type), intent(in) :: configs + type (mpas_pool_type), intent(in) :: dimensions + +#ifdef MPAS_USE_MUSICA + integer :: error_code + character(len=:), allocatable :: error_message + integer :: nVertLevels + integer, pointer :: nVertLevels_ptr + ! MUSICA will get the MICM JSON config from a namelist + ! hardcode filepath for now + character(len=StrKIND) :: filepath = 'chapman.json' +#endif + + call mpas_log_write('Initializing chemistry packages...') + +#ifdef MPAS_USE_MUSICA + call mpas_pool_get_dimension(dimensions, 'nVertLevels', nVertLevels_ptr) + nVertLevels = nVertLevels_ptr + + call musica_init(filepath, nVertLevels, error_code, error_message) + + ! TODO check error_code and generate MPAS error log message +#endif + + end subroutine chemistry_init + + + !------------------------------------------------------------------------ + ! routine chemistry_step + ! + !> \brief Steps the chemistry packages + !> \author CheMPAS-A Developers + !> \date 20 August 2025 + !> \details + !> This routine steps the chemistry packages, updating their state + !> based on the current simulation time and conditions. + !------------------------------------------------------------------------ + subroutine chemistry_step() + +#ifdef MPAS_USE_MUSICA + use mpas_musica, only: musica_step +#endif + use mpas_log, only : mpas_log_write + +#ifdef MPAS_USE_MUSICA + call mpas_log_write('Stepping chemistry packages...') + ! call musica_step() +#endif + + end subroutine chemistry_step + + + !------------------------------------------------------------------------ + ! routine chemistry_finalize + ! + !> \brief Finalizes the chemistry packages + !> \author CheMPAS-A Developers + !> \date 20 August 2025 + !> \details + !> This routine finalizes the chemistry packages, cleaning up + !> any resources and data structures used during the simulation. + !------------------------------------------------------------------------ + subroutine chemistry_finalize() + +#ifdef MPAS_USE_MUSICA + use mpas_musica, only: musica_finalize +#endif + use mpas_log, only : mpas_log_write + +#ifdef MPAS_USE_MUSICA + call mpas_log_write('Finalizing chemistry packages...') + call musica_finalize() +#endif + + end subroutine chemistry_finalize + +end module mpas_atm_chemistry diff --git a/src/core_atmosphere/chemistry/musica/Makefile b/src/core_atmosphere/chemistry/musica/Makefile new file mode 100644 index 0000000000..3a11055c77 --- /dev/null +++ b/src/core_atmosphere/chemistry/musica/Makefile @@ -0,0 +1,31 @@ +.SUFFIXES: .F .o + +.PHONY: mpas_musica mpas_musica_lib + +all: dummy mpas_musica + +dummy: + echo "****** compiling mpas_musica ******" + +OBJS = \ + mpas_musica.o + +mpas_musica: $(OBJS) + +mpas_musica_lib: + ar -ru ./../libchem.a $(OBJS) + +clean: + $(RM) *.f90 *.o *.mod + @# Certain systems with intel compilers generate *.i files + @# This removes them during the clean process + $(RM) *.i + +.F.o: +ifeq "$(GEN_F90)" "true" + $(CPP) $(CPPFLAGS) $(COREDEF) $(CPPINCLUDES) $< > $*.f90 + $(FC) $(FFLAGS) -c $*.f90 $(FCINCLUDES) -I.. -I../../../framework -I../../../external/esmf_time_f90 +else + $(FC) $(CPPFLAGS) $(COREDEF) $(FFLAGS) -c $*.F $(CPPINCLUDES) $(FCINCLUDES) -I.. -I../../../framework -I../../../external/esmf_time_f90 +endif + diff --git a/src/core_atmosphere/chemistry/musica/mpas_musica.F b/src/core_atmosphere/chemistry/musica/mpas_musica.F new file mode 100644 index 0000000000..649b2b3624 --- /dev/null +++ b/src/core_atmosphere/chemistry/musica/mpas_musica.F @@ -0,0 +1,161 @@ +! Copyright (c) 2025 The University Corporation for Atmospheric Research (UCAR). +! +! Unless noted otherwise source code is licensed under the BSD license. +! Additional copyright and license information can be found in the LICENSE file +! distributed with this code, or at https://mpas-dev.github.io/license.html . +! +!----------------------------------------------------------------------- +! mpas_musica +! +!> \brief Manages interactions with the MUSICA chemistry package +!> \author CheMPAS-A Developers +!> \date 20 August 2025 +!> \details +!> This module manages the interactions with the MUSICA chemistry package, +!> including initialization, time-stepping, and finalization for +!> MICM (ODE solver) and TUV-x (photolysis rate constant calculator). +!-------------------------------------------------------------------------- +module mpas_musica + + use musica_micm, only: micm_t, get_micm_version + ! get_micm_version is here to avoid an ICE when within musica_init + use musica_state, only: state_t + + implicit none + + private + + public :: musica_init, musica_step, musica_finalize + + type(micm_t), pointer :: micm => null ( ) ! Pointer to the MICM ODE solver instance + type(state_t), pointer :: state => null ( ) ! Pointer to the state of the MICM solver + + contains + + !------------------------------------------------------------------------ + ! subroutine musica_init + ! + !> \brief Initializes the MUSICA chemistry package + !> \author CheMPAS-A Developers + !> \date 20 August 2025 + !> \details + !> This subroutine initializes the MUSICA chemistry package, + !> setting up necessary parameters and data structures for the simulation. + !> For now, we will load fixed configurations for MICM and TUV-x. + !> Later, we will gradually replace the fixed configuration elements + !> with runtime updates using the MUSICA API + !------------------------------------------------------------------------ + subroutine musica_init(filename_of_micm_configuration, & + number_of_grid_cells, & + error_code, error_message) + + use musica_micm, only : RosenbrockStandardOrder + use musica_util, only : error_t, string_t + + use mpas_log, only : mpas_log_write + + character(len=*), intent(in) :: filename_of_micm_configuration + integer, intent(in) :: number_of_grid_cells + integer, intent(out) :: error_code + character(len=:), allocatable, intent(out) :: error_message + + type(error_t) :: error + type(string_t) :: micm_version + + ! TEMPORARY: Hard-coded options for the MICM solver + integer :: solver_type = RosenbrockStandardOrder + + micm_version = get_micm_version() + + call mpas_log_write('Initializing MUSICA chemistry package...') + call mpas_log_write('MICM version: ' // micm_version%value_) + call mpas_log_write('MICM number of grid cells: $i', intArgs=[number_of_grid_cells]) + + micm => micm_t(trim(filename_of_micm_configuration), solver_type, error) + if (has_error_occurred(error, error_message, error_code)) return + + state => micm%get_state(number_of_grid_cells, error) + if (has_error_occurred(error, error_message, error_code)) return + + end subroutine musica_init + + !------------------------------------------------------------------------ + ! subroutine musica_step + ! + !> \brief Steps the MUSICA chemistry package + !> \author CheMPAS-A Developers + !> \date 20 August 2025 + !> \details + !> This subroutine steps the MUSICA chemistry package, updating its state + !> based on the current simulation time and conditions. + !> It first calls the TUV-x package to compute photolysis rates, + !> then calls the MICM package to solve the ODEs for chemical species + !> concentrations. + !------------------------------------------------------------------------ + subroutine musica_step() + + use mpas_log, only : mpas_log_write + + call mpas_log_write('Stepping MUSICA chemistry package...') + + ! Here we would typically call the TUV-x and MICM packages to perform + ! the necessary computations, but for now we will just log the step. + + end subroutine musica_step + + !------------------------------------------------------------------------ + ! subroutine musica_finalize + ! + !> \brief Finalizes the MUSICA chemistry package + !> \author CheMPAS-A Developers + !> \date 20 August 2025 + !> \details + !> This subroutine finalizes the MUSICA chemistry package, + !> cleaning up any resources and data structures used during the simulation. + !------------------------------------------------------------------------- + subroutine musica_finalize() + + use mpas_log, only : mpas_log_write + + call mpas_log_write('Finalizing MUSICA chemistry package...') + + ! Here we would typically clean up resources, but for now we do nothing. + + end subroutine musica_finalize + + !------------------------------------------------------------------------- + ! function has_error_occurred + ! + !> \author CheMPAS-A Developers + !> \date 20 August 2025 + ! \details + !> Evaluate a MUSICA error for failure and convert to error data + !> @param[in] error The error code to evaluate and convert. + !> @param[out] error_message The error message. + !> @param[out] error_code The error code. + !> @return True for an error, false for success. + !------------------------------------------------------------------------- + logical function has_error_occurred(error, error_message, error_code) + use musica_util, only: error_t + + type(error_t), intent(in) :: error + character(len=:), allocatable, intent(out) :: error_message + integer, intent(out) :: error_code + + character(len=30) :: error_code_str + + if ( error%is_success( ) ) then + error_code = 0 + error_message = '' + has_error_occurred = .false. + return + end if + error_code = error%code( ) + write(error_code_str, '(I30)') error%code( ) + error_message = '[MUSICA Error]: ' // error%category( ) // '[' // & + trim( adjustl( error_code_str ) ) // ']: ' // error%message( ) + has_error_occurred = .true. + + end function has_error_occurred + +end module mpas_musica diff --git a/src/core_atmosphere/mpas_atm_core.F b/src/core_atmosphere/mpas_atm_core.F index aaebf865c7..d3e8c8a846 100644 --- a/src/core_atmosphere/mpas_atm_core.F +++ b/src/core_atmosphere/mpas_atm_core.F @@ -35,6 +35,9 @@ function atm_core_init(domain, startTimeStamp) result(ierr) use mpas_attlist, only : mpas_modify_att use mpas_string_utils, only : mpas_string_replace use mpas_atm_halos, only: atm_build_halo_groups, exchange_halo_group +#ifdef DO_CHEMISTRY + use mpas_atm_chemistry, only: chemistry_init +#endif implicit none @@ -282,6 +285,13 @@ function atm_core_init(domain, startTimeStamp) result(ierr) ! call mpas_atm_dynamics_init(domain) + ! + ! Initialize the chemistry package + ! +#ifdef DO_CHEMISTRY + call chemistry_init(domain % blocklist % configs, domain % blocklist % dimensions) +#endif + end function atm_core_init @@ -972,9 +982,12 @@ subroutine atm_do_timestep(domain, dt, itimestep) use mpas_atmphys_driver use mpas_atmphys_manager use mpas_atmphys_update +#endif +#ifdef DO_CHEMISTRY + use mpas_atm_chemistry, only: chemistry_step #endif use mpas_atm_halos, only: exchange_halo_group - + implicit none type (domain_type), intent(inout) :: domain @@ -1006,6 +1019,10 @@ subroutine atm_do_timestep(domain, dt, itimestep) endif #endif +#ifdef DO_CHEMISTRY + call chemistry_step() +#endif + call atm_timestep(domain, dt, currTime, itimestep, exchange_halo_group) end subroutine atm_do_timestep @@ -1024,6 +1041,10 @@ function atm_core_finalize(domain) result(ierr) use mpas_atmphys_finalize #endif +#ifdef DO_CHEMISTRY + use mpas_atm_chemistry, only: chemistry_finalize +#endif + implicit none type (domain_type), intent(inout) :: domain @@ -1040,6 +1061,13 @@ function atm_core_finalize(domain) result(ierr) ! call mpas_atm_dynamics_finalize(domain) + ! + ! Finalize chemistry + ! +#ifdef DO_CHEMISTRY + call chemistry_finalize() +#endif + call mpas_atm_diag_cleanup() call mpas_destroy_clock(clock, ierr)