diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 00000000..ff393d44 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,246 @@ +#*********************************************************************** +#* GNU Lesser General Public License +#* +#* This file is part of the GFDL Flexible Modeling System (FMS) Coupler. +#* +#* FMS Coupler is free software: you can redistribute it and/or modify +#* it under the terms of the GNU Lesser General Public License as +#* published by the Free Software Foundation, either version 3 of the +#* License, or (at your option) any later version. +#* +#* FMS Coupler is distributed in the hope that it will be useful, but +#* WITHOUT ANY WARRANTY; without even the implied warranty of +#* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +#* General Public License for more details. +#* +#* You should have received a copy of the GNU Lesser General Public +#* License along with FMS Coupler. +#* If not, see . +#*********************************************************************** + +cmake_minimum_required(VERSION 3.15) +project(fmscoupler LANGUAGES Fortran) + +include(FetchContent) + +# define some file locations +set(EXTERNAL_SOURCE_DIR ${CMAKE_SOURCE_DIR}/external) +file(MAKE_DIRECTORY ${EXTERNAL_SOURCE_DIR}) + +# Options for local dependencies +set(fms_path "${CMAKE_SOURCE_DIR}/../FMS" CACHE STRING "Path to local fms repository") +set(atmos_path "${CMAKE_SOURCE_DIR}/../atmos_null" CACHE STRING "Path to local atmospere component repository") +set(ocean_path "${CMAKE_SOURCE_DIR}/../ocean_null" CACHE STRING "Path to local ocean component repository") +set(land_path "${CMAKE_SOURCE_DIR}/../land_null" CACHE STRING "Path to local land component repository") +set(ice_path "${CMAKE_SOURCE_DIR}/../ice_null" CACHE STRING "Path to local ice component repository") +set(ice_param_path "${CMAKE_SOURCE_DIR}/../ice_param" CACHE STRING "Path to local ice_param component repository") + +# Options for cloning depedencies +option(FETCH_FMS "Clone FMS repository for building" OFF) +option(FETCH_COMPONENTS "Clone model component repositories for building" OFF) +set(fms_tag "main" CACHE STRING "Branch/Tag name for FMS. Only used if -DFETCH_FMS=on") +set(atmos_tag "main" CACHE STRING "Branch/Tag name for atmosphere component. Only used if -DFETCH_COMPONENTS=on") +set(ocean_tag "main" CACHE STRING "Branch/Tag name for ocean component. Only used if -DFETCH_COMPONENTS=on") +set(land_tag "main" CACHE STRING "Branch/Tag name for land component. Only used if -DFETCH_COMPONENTS=on") +set(ice_tag "main" CACHE STRING "Branch/Tag name for ice component. Only used if -DFETCH_COMPONENTS=on") +set(ice_param_tag "main" CACHE STRING "Branch/Tag name for ice_param component. Only used if -DFETCH_COMPONENTS=on") +set(fms_repo "https://github.com/noaa-gfdl/fms.git" + CACHE STRING "Repository URL for FMS. Only used if -DFETCH_FMS=on") +set(atmos_repo "https://github.com/noaa-gfdl/atmos_null.git" + CACHE STRING "Repository URL for atmosphere component. Only used if -DFETCH_COMPONENTS=on") +set(ocean_repo "https://github.com/noaa-gfdl/ocean_null.git" + CACHE STRING "Repository URL for ocean component. Only used if -DFETCH_COMPONENTS=on") +set(land_repo "https://github.com/noaa-gfdl/land_null.git" + CACHE STRING "Repository URL for land component. Only used if -DFETCH_COMPONENTS=on") +set(ice_repo "https://github.com/noaa-gfdl/ice_null.git" + CACHE STRING "Repository URL for ice component. Only used if -DFETCH_COMPONENTS=on") +set(ice_param_repo "https://github.com/noaa-gfdl/ice_param.git" + CACHE STRING "Repository URL for ice_param component. Only used if -DFETCH_COMPONENTS=on") + +# TODO some more error checking here to ensure fetching/local options make sense +if(NOT FETCH_FMS) + message(NOTICE "-DFETCH_FMS=off - expecting local FMS repository at ${fms_path}.") +endif() + +if(NOT FETCH_COMPONENTS) + message(NOTICE + "-DFETCH_COMPONENTS=off - expecting components locally available in ${CMAKE_SOURCE_DIR}/.. if path not specified" + ) +endif() + +# build FMS, cloning if -DFETCH_FMS=on, otherwise expects a local path provided +if(FETCH_FMS) + FetchContent_Declare( + FMS + GIT_REPOSITORY ${fms_repo} + GIT_TAG ${fms_tag} + SOURCE_DIR ${EXTERNAL_SOURCE_DIR}/FMS + ) + FetchContent_MakeAvailable(FMS) + message(STATUS "FMS cloned to ${FMS_SOURCE_DIR}") +else() + add_subdirectory(${fms_path} ${CMAKE_CURRENT_BINARY_DIR}/fms_build) +endif() + +# set variables for component repos +# ice_param isn't a direct dependency of the coupler, but is almost always needed by +# whatever ice_model you end up using so is included here +set(COMPONENTS + ocean + atmos + land + ice + ice_param +) +# clone or symlink each component repo +foreach(component_name IN LISTS COMPONENTS) + if(FETCH_COMPONENTS) + set(component_url ${${component_name}_repo}) + message(STATUS "Cloning ${component_url} to ${EXTERNAL_SOURCE_DIR}/${component_name}") + FetchContent_Declare( + ${component_name} + GIT_REPOSITORY ${component_url} + SOURCE_DIR ${EXTERNAL_SOURCE_DIR}/${component_name} + GIT_TAG main + ) + FetchContent_MakeAvailable(${component_name}) + else() + set(component_path ${${component_name}_path} ) + message(STATUS "Creating symlink in ${EXTERNAL_SOURCE_DIR} for ${component_path}") + file(CREATE_LINK + "${component_path}" + "${EXTERNAL_SOURCE_DIR}/${component_name}" + SYMBOLIC + ) + endif() +endforeach() + +# Build libraries for each external component +foreach(component_name IN LISTS COMPONENTS) + file(GLOB_RECURSE component_srcs ${EXTERNAL_SOURCE_DIR}/${component_name}/*.F90) + # TODO if/when cmake is added to component libraries, this should use add_subdirectory instead + add_library(${component_name} STATIC ${component_srcs}) + target_include_directories(${component_name} PUBLIC ${EXTERNAL_SOURCE_DIR}/FMS) + target_link_libraries(${component_name} PUBLIC FMS::fms) + target_compile_definitions(${component_name} PUBLIC INTERNAL_FILE_NML) + set_target_properties(${component_name} PROPERTIES COMPILE_FLAGS "-fdefault-real-8") + set_target_properties(${component_name} PROPERTIES + Fortran_MODULE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/${component_name}_mods" + ) +endforeach() + +# ice_null has additional dependencies instead of just fms +target_link_libraries(ice PRIVATE FMS::fms ocean ice_param) +target_include_directories(ice PRIVATE + "${CMAKE_CURRENT_BINARY_DIR}/ocean_mods" + "${CMAKE_CURRENT_BINARY_DIR}/ice_param_mods" +) + +# set some paths +set(SHARED_MOD_SRCS ${CMAKE_SOURCE_DIR}/shared/surface_flux.F90) +set(SIMPLE_MOD_SRCS + ${CMAKE_SOURCE_DIR}/simple/flux_exchange.F90 + ${CMAKE_SOURCE_DIR}/simple/ice_model.F90 +) +set(FULL_MOD_SRCS + ${CMAKE_SOURCE_DIR}/full/atm_land_ice_flux_exchange.F90 + ${CMAKE_SOURCE_DIR}/full/atmos_ocean_dep_fluxes_calc.F90 + ${CMAKE_SOURCE_DIR}/full/atmos_ocean_fluxes_calc.F90 + ${CMAKE_SOURCE_DIR}/full/flux_exchange.F90 + ${CMAKE_SOURCE_DIR}/full/full_coupler_mod.F90 + ${CMAKE_SOURCE_DIR}/full/ice_ocean_flux_exchange.F90 + ${CMAKE_SOURCE_DIR}/full/land_ice_flux_exchange.F90 +) +set(SIMPLE_EXEC_SRC ${CMAKE_SOURCE_DIR}/simple/coupler_main.F90) +set(FULL_EXEC_SRC ${CMAKE_SOURCE_DIR}/full/coupler_main.F90) + +# library for simple coupler modules + shared +add_library(coupler_shared_simple STATIC ${SIMPLE_MOD_SRCS} ${SHARED_MOD_SRCS}) +set_target_properties(coupler_shared_simple PROPERTIES + Fortran_MODULE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/simple_coupler_mods" +) +set_target_properties(coupler_shared_simple PROPERTIES COMPILE_FLAGS "-fdefault-real-8") +target_compile_definitions(coupler_shared_simple PUBLIC use_AM3_physics _USE_LEGACY_LAND_) +target_include_directories(coupler_shared_simple PUBLIC + ${CMAKE_CURRENT_BINARY_DIR}/ocean_mods + ${CMAKE_CURRENT_BINARY_DIR}/atmos_mods + ${CMAKE_CURRENT_BINARY_DIR}/land_mods + ${CMAKE_CURRENT_BINARY_DIR}/ice_param_mods +) +target_link_libraries(coupler_shared_simple PRIVATE + FMS::fms + ocean + atmos + land + ice_param +) + +# library for full coupler modules + shared +add_library(coupler_shared_full STATIC ${FULL_MOD_SRCS} ${SHARED_MOD_SRCS}) +set_target_properties(coupler_shared_full PROPERTIES + Fortran_MODULE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/full_coupler_mods" +) +set_target_properties(coupler_shared_full PROPERTIES COMPILE_FLAGS "-fdefault-real-8") +target_compile_definitions(coupler_shared_full PUBLIC use_AM3_physics _USE_LEGACY_LAND_) +target_include_directories(coupler_shared_full PUBLIC + ${CMAKE_SOURCE_DIR}/full + ${CMAKE_CURRENT_BINARY_DIR}/ocean_mods + ${CMAKE_CURRENT_BINARY_DIR}/atmos_mods + ${CMAKE_CURRENT_BINARY_DIR}/land_mods + ${CMAKE_CURRENT_BINARY_DIR}/ice_mods + ${CMAKE_CURRENT_BINARY_DIR}/ice_param_mods +) +target_link_libraries(coupler_shared_full PRIVATE + FMS::fms + ocean + atmos + land + ice + ice_param +) + + +# simple coupler executable +add_executable(coupler_simple ${CMAKE_SOURCE_DIR}/simple/coupler_main.F90) +set_target_properties(coupler_simple PROPERTIES COMPILE_FLAGS "-fdefault-real-8") +target_include_directories(coupler_simple PUBLIC + ${CMAKE_CURRENT_BINARY_DIR}/simple_coupler_mods + ${CMAKE_CURRENT_BINARY_DIR}/ocean_mods + ${CMAKE_CURRENT_BINARY_DIR}/atmos_mods + ${CMAKE_CURRENT_BINARY_DIR}/land_mods +) +target_link_libraries(coupler_simple PRIVATE + coupler_shared_simple + FMS::fms + ocean + atmos + land +) +set_target_properties(coupler_simple PROPERTIES RUNTIME_OUTPUT_NAME "coupler_simple.x") + +# full coupler executable +add_executable(coupler_full ${CMAKE_SOURCE_DIR}/full/coupler_main.F90) +set_target_properties(coupler_full PROPERTIES COMPILE_FLAGS "-fdefault-real-8") +target_include_directories(coupler_full PUBLIC + ${CMAKE_SOURCE_DIR}/full + ${CMAKE_CURRENT_BINARY_DIR}/full_coupler_mods + ${CMAKE_CURRENT_BINARY_DIR}/ocean_mods + ${CMAKE_CURRENT_BINARY_DIR}/atmos_mods + ${CMAKE_CURRENT_BINARY_DIR}/land_mods + ${CMAKE_CURRENT_BINARY_DIR}/ice_mods +) +target_link_libraries(coupler_full PRIVATE + coupler_shared_full + FMS::fms + ocean + atmos + land + ice +) +set_target_properties(coupler_full PROPERTIES RUNTIME_OUTPUT_NAME "coupler_full.x") + +# simple test run, only intended for null_model builds +enable_testing() + +add_test(NAME null_model_run + COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/t/test_run.sh) diff --git a/README.md b/README.md index 96d8f02c..6c4a41de 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,49 @@ The coupling between the models is designed to conserve fluxes. For coupled mode specification file is used to initialize the model grids and perform exchanges between the models. The next sections describe how this file and associated grids are used in the coupler. +### Software Structure and Build System + +The FMScoupler repository contains 2 distinct "driver" programs, "full" and "simple", which serve as the executable +program to run GFDL developed climate models. It also contains a collection of helper modules that define routines used +in the drivers, which are split between the shared/ and the full/simple subdirectories. + +The "full" coupler has 5 direct dependencies: FMS[github.com/noaa-gfdl/fms], "ice_model_mod", "land_model_mod", +"atmos_model_mod", "ocean_model_mod". The "simple" coupler differs in that in does not require a ocean model, and +includes its own ice_model module, rather than using an external repository. "ice_param" is a dependency of the simple +ice_model, so is also included in the build. It is commonly used by GFDL ice models as well, so will be linked with +the ice component by default. + +The component modules can be provided by a number of different repositories that define the +physical calculations performed in each component. + +A cmake build is available to build the coupler and component libraries. It is currently only tested with the null model +and supports either locally cloned repositories or automagically cloning components during configuration. + +To build using local components, all source code must be cloned in a single directory: + +```{shell} +[Ryan.Mulhall@lscamd50-d example-build-dir]$ ls +atmos_null FMScoupler FMS ice_null ice_param land_null ocean_null +``` +Which then can be built/tested with: + +```{shell} +cmake FMScoupler/ +make -j +make test +``` + +To have cmake clone all dependencies, build the null model, and run a test with cmake: + +```{shell} +mkdir build +cd build +cmake -DFETCH_FMS=on -DFETCH_COMPONENTS=on .. +make -j +make test +``` + + ### Grid Specification Files At runtime, the coupled model sets up its grid using a given `grid_spec.nc` file that it reads from the INPUT subdirectory. This NetCDF file contains grid information for all of the component models diff --git a/t/test_run.sh b/t/test_run.sh new file mode 100755 index 00000000..7734f5f4 --- /dev/null +++ b/t/test_run.sh @@ -0,0 +1,50 @@ +#!/usr/bin/env sh +# +# Run script for a simple test run using the null_model +# This is the same run done in null_model_build.sh, just without any building steps +rundir=$PWD + +# Run the null models test +# Setup the run directory +rm -rf run +mkdir ${rundir}/run +cd ${rundir}/run +mkdir RESTART +# Get the data files required for the run +tarFile=coupler_null_test_data_full_simple.tar.gz +curl -O ftp://ftp.gfdl.noaa.gov/perm/GFDL_pubrelease/test_data/${tarFile} +tar zxf ${tarFile} +# add an io layout to the full nml +sed -i '22i io_layout = 1, 1' input-full.nml +# Get the full namelist +ln -s input-full.nml input.nml +# Run the null model with the full coupler +mpiexec -n 1 ${rundir}/coupler_full.x + +# Report on the status of the run with the full coupler +if [ $? -eq 0 ] +then + echo "::note title=Run Succeeded - full coupler:: Full coupler null model ran successfully." +else + echo "::error title=Run Failed - full coupler:: Full coupler null model run failed execution." + exit 1 +fi + +# Using the same run directory, setup for the simple coupler +# Clear out the RESTART directory +mv RESTART RESTART_full +mkdir RESTART +# Get the simple namelist +rm input.nml +ln -s input-simple.nml input.nml +# Run the null simple coupler test +mpiexec -n 1 ${rundir}/coupler_simple.x + +# Report on the status of the run with the simple coupler +if [ $? -eq 0 ] +then + echo "::note title=Run Succeeded - simple coupler:: simple coupler null model ran successfully" +else + echo "::error title=Run Failed - simple coupler:: simple coupler null model run failed execution." + exit 1 +fi