From 7520688a2915f25dc3434b1a6c382fae77eb6a16 Mon Sep 17 00:00:00 2001 From: Steven Smith Date: Thu, 22 Jan 2026 13:33:19 -0800 Subject: [PATCH 1/5] Pandas read_csv error and Peg version of Python black style checker (#691) Fixes Pandas read_csv delim_whitespace API change and peg the version of Python Black style checker. Fixes #692 Fixes #690 --- pftools/python/parflow/tools/io.py | 2 +- pftools/python/pyproject.toml | 4 ++-- pftools/python/requirements_dev.txt | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pftools/python/parflow/tools/io.py b/pftools/python/parflow/tools/io.py index 387925a72..882b71fef 100644 --- a/pftools/python/parflow/tools/io.py +++ b/pftools/python/parflow/tools/io.py @@ -1491,7 +1491,7 @@ def _read_vegm(file_name): in the vegm.dat file except for x/y """ # Assume first two lines are comments and use generic column names - df = pd.read_csv(file_name, delim_whitespace=True, skiprows=2, header=None) + df = pd.read_csv(file_name, sep="\s+", skiprows=2, header=None) df.columns = [f"c{i}" for i in range(df.shape[1])] # Number of columns and rows determined by last line of file diff --git a/pftools/python/pyproject.toml b/pftools/python/pyproject.toml index 5a807443f..b50bcffc2 100644 --- a/pftools/python/pyproject.toml +++ b/pftools/python/pyproject.toml @@ -34,11 +34,11 @@ all = [ "imageio>=2.9.0", "h5py", "twine", - "black" + "black==25.12.0" ] pfsol = ["imageio>=2.9.0"] pdi = ["h5py"] -dev = ["twine", "black"] +dev = ["twine", "black==25.12.0"] [project.urls] Homepage = "https://github.com/parflow/parflow/tree/master/pftools/python" diff --git a/pftools/python/requirements_dev.txt b/pftools/python/requirements_dev.txt index 46c7b2ef5..525404890 100644 --- a/pftools/python/requirements_dev.txt +++ b/pftools/python/requirements_dev.txt @@ -1,3 +1,3 @@ ###### Requirements for development of PFTools package ###### twine -black +black==25.12.0 From 81262541e044e580165e87d3d0588d3bec7b0c3f Mon Sep 17 00:00:00 2001 From: reedmaxwell Date: Thu, 22 Jan 2026 18:48:25 -0500 Subject: [PATCH 2/5] q_overland initialization fix (#689) q_overlnd_x and q_overlnd_y were not being initialized if surface_predictor == True but are used in the surface predictor calculation even if they are not output. This was causing PF to throw an error for this case. --------- Co-authored-by: Steven Smith --- pfsimulator/parflow_lib/solver_richards.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pfsimulator/parflow_lib/solver_richards.c b/pfsimulator/parflow_lib/solver_richards.c index 2b9d5c4d1..c14ea083a 100644 --- a/pfsimulator/parflow_lib/solver_richards.c +++ b/pfsimulator/parflow_lib/solver_richards.c @@ -993,7 +993,8 @@ SetupRichards(PFModule * this_module) if (public_xtra->write_silo_qx_overland || public_xtra->print_qx_overland || public_xtra->write_pdi_qx_overland - || public_xtra->write_netcdf_qx_overland) + || public_xtra->write_netcdf_qx_overland + || public_xtra->surface_predictor) { instance_xtra->q_overlnd_x = NewVectorType(grid2d, 1, 1, vector_cell_centered_2D); @@ -1007,7 +1008,8 @@ SetupRichards(PFModule * this_module) if (public_xtra->write_silo_qy_overland || public_xtra->print_qy_overland || public_xtra->write_pdi_qy_overland - || public_xtra->write_netcdf_qy_overland) + || public_xtra->write_netcdf_qy_overland + || public_xtra->surface_predictor) { instance_xtra->q_overlnd_y = NewVectorType(grid2d, 1, 1, vector_cell_centered_2D); From 52120cbd6bac5d2109d2a1c1073bde38906818c3 Mon Sep 17 00:00:00 2001 From: Muhammad Fahad Azeemi Date: Tue, 27 Jan 2026 22:12:58 +0100 Subject: [PATCH 3/5] CMake vars uppercase, restore RMM_ROOT, update GPU docs (#694) ### Summary This PR standardizes CMake variable naming and updates GPU documentation. It addresses a breaking configuration change where `RMM_ROOT`, originally defined in uppercase, was later changed to mixed-case (`rmm_ROOT`), which silently broke existing user build scripts when upgrading ParFlow. This PR restores the original uppercase convention and enforces consistent naming across all third-party dependencies to prevent similar regressions. ### Changes - **CMake variable consistency:** - All third-party CMake variables are now uppercase, including: - `RMM_ROOT` (restored from mixed-case `rmm_ROOT`) - `UMPIRE_ROOT` (updated from `umpire_ROOT`) - This aligns with existing variables such as `KOKKOS_ROOT`, `SILO_ROOT`, `HYPER_ROOT`, and `SUNDIALS_ROOT`. - **Workflow files** updated to reflect the uppercase RMM_ROOT and UMPIRE_ROOT variables. - **Backward compatibility fix:** - `RMM_ROOT` was originally uppercase; changing it to lowercase silently broke existing user scripts when upgrading to newer ParFlow versions. This PR restores the original convention to avoid unexpected build failures. - **Documentation updates (README-GPU.md):** - Removed obsolete references to RMM v0.10 and the previous upgrade work-in-progress note - Ensured consistent use of `RMM_ROOT` - Updated the RMM link to the official repository --- .github/workflows/linux.yml | 4 ++-- .github/workflows/self_hosted.yml | 4 ++-- CMakeLists.txt | 22 +++++++++++----------- README-GPU.md | 16 ++++++++-------- 4 files changed, 23 insertions(+), 23 deletions(-) diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index ca455507d..deeba49c2 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -397,7 +397,7 @@ jobs: make -j make install fi - echo "RMM_FLAGS=-Drmm_ROOT=$PARFLOW_DEP_DIR/rmm" >> $GITHUB_ENV + echo "RMM_FLAGS=-DRMM_ROOT=$PARFLOW_DEP_DIR/rmm" >> $GITHUB_ENV - name: Umpire Install env: @@ -414,7 +414,7 @@ jobs: cmake --build build --parallel 4 cmake --install build fi - echo "UMPIRE_FLAGS=-Dumpire_ROOT=$PARFLOW_DEP_DIR/Umpire" >> $GITHUB_ENV + echo "UMPIRE_FLAGS=-DUMPIRE_ROOT=$PARFLOW_DEP_DIR/Umpire" >> $GITHUB_ENV - name: Kokkos Install env: diff --git a/.github/workflows/self_hosted.yml b/.github/workflows/self_hosted.yml index 9f5494e1b..329026ba8 100644 --- a/.github/workflows/self_hosted.yml +++ b/.github/workflows/self_hosted.yml @@ -48,7 +48,7 @@ jobs: cmake .. -DCMAKE_INSTALL_PREFIX=$GITHUB_WORKSPACE/depend/rmm -DBUILD_TESTS=OFF make -j make install - echo "RMM_FLAGS=-Drmm_ROOT=$GITHUB_WORKSPACE/depend/rmm" >> $GITHUB_ENV + echo "RMM_FLAGS=-DRMM_ROOT=$GITHUB_WORKSPACE/depend/rmm" >> $GITHUB_ENV - name: Install Umpire if: matrix.config.memory_manager == 'umpire' @@ -63,7 +63,7 @@ jobs: cmake .. -DCMAKE_INSTALL_PREFIX=$GITHUB_WORKSPACE/depend/Umpire -DENABLE_CUDA=On make -j make install - echo "UMPIRE_FLAGS=-Dumpire_ROOT=$GITHUB_WORKSPACE/depend/Umpire" >> $GITHUB_ENV + echo "UMPIRE_FLAGS=-DUMPIRE_ROOT=$GITHUB_WORKSPACE/depend/Umpire" >> $GITHUB_ENV - name: Configure ParFlow run: | diff --git a/CMakeLists.txt b/CMakeLists.txt index c2179b0ae..4f3c5b59a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -125,27 +125,27 @@ else(PARFLOW_ACCELERATOR_BACKEND STREQUAL "none") message(FATAL_ERROR "ERROR: Unknown backend type! PARFLOW_ACCELERATOR_BACKEND=${PARFLOW_ACCELERATOR_BACKEND} does not exist!") endif(PARFLOW_ACCELERATOR_BACKEND STREQUAL "none") -# Include Umpire or RMM memory manager for pool allocation +# Include UMPIRE or RMM memory manager for pool allocation if(PARFLOW_HAVE_CUDA OR PARFLOW_HAVE_KOKKOS) - if (DEFINED umpire_ROOT AND DEFINED RMM_ROOT) - message(FATAL_ERROR "ERROR: Cannot have both Umpire and RMM enabled at the same time.") - endif(DEFINED umpire_ROOT AND DEFINED RMM_ROOT) + if (DEFINED UMPIRE_ROOT AND DEFINED RMM_ROOT) + message(FATAL_ERROR "ERROR: Cannot have both UMPIRE and RMM enabled at the same time.") + endif(DEFINED UMPIRE_ROOT AND DEFINED RMM_ROOT) # Determine memory manager and print single status message - if(DEFINED umpire_ROOT) - find_package(umpire REQUIRED) + if(DEFINED UMPIRE_ROOT) + find_package(umpire REQUIRED HINTS ${UMPIRE_ROOT}) set(PARFLOW_HAVE_UMPIRE "yes") - message(STATUS "MEMORY MANAGER: Umpire is sleceted") + message(STATUS "MEMORY MANAGER: UMPIRE is sleceted") - elseif(DEFINED rmm_ROOT) - find_package(rmm REQUIRED) + elseif(DEFINED RMM_ROOT) + find_package(rmm REQUIRED HINTS ${RMM_ROOT}) set(PARFLOW_HAVE_RMM "yes") message(STATUS "MEMORY MANAGER: RMM is sleceted") - else(DEFINED umpire_ROOT) + else(DEFINED UMPIRE_ROOT) message(STATUS "MEMORY MANAGER: NO memoery manager is sleceted") - endif(DEFINED umpire_ROOT) + endif(DEFINED UMPIRE_ROOT) endif(PARFLOW_HAVE_CUDA OR PARFLOW_HAVE_KOKKOS) #----------------------------------------------------------------------------- diff --git a/README-GPU.md b/README-GPU.md index aad504b53..a1f69eb45 100644 --- a/README-GPU.md +++ b/README-GPU.md @@ -9,16 +9,16 @@ Building with CUDA or Kokkos may improve the performance significantly for large ## CMake -Building with GPU acceleration requires a [CUDA](https://docs.nvidia.com/cuda/cuda-installation-guide-linux/index.html) installation, and in case Kokkos is used, [Kokkos](https://github.com/kokkos/kokkos) installation. However, the performance can be further improved by using pool allocation for Unified Memory. Supported memory managers for pool allocation are: [RMM v0.10](https://github.com/rapidsai/rmm/tree/branch-0.10) and [Umpire](https://umpire.readthedocs.io/en/develop/). Note that we are in the process of updating RMM to the newest API, but that should not affect users. Note that you should use only one memory manager, you can't pick both RMM and Umpire. Performance can be improved even more with direct communication between GPUs (requires using a CUDA-Aware MPI library). +Building with GPU acceleration requires a [CUDA](https://docs.nvidia.com/cuda/cuda-installation-guide-linux/index.html) installation, and in case Kokkos is used, [Kokkos](https://github.com/kokkos/kokkos) installation. However, the performance can be further improved by using pool allocation. Supported memory managers for pool allocation are: [RMM](https://github.com/rapidsai/rmm) and [Umpire](https://github.com/llnl/Umpire/tree/develop). Note that you should use only one memory manager, you can't pick both RMM and Umpire. Performance can be improved even more with direct communication between GPUs (requires using a CUDA-Aware MPI library). The GPU acceleration is activated by specifying either *PARFLOW_ACCELERATOR_BACKEND=cuda* option to the CMake, i.e., ```shell cmake ../parflow -DPARFLOW_AMPS_LAYER=mpi1 -DCMAKE_BUILD_TYPE=Release -DPARFLOW_ENABLE_TIMING=TRUE -DPARFLOW_HAVE_CLM=ON -DCMAKE_INSTALL_PREFIX=${PARFLOW_DIR} -DPARFLOW_ACCELERATOR_BACKEND=cuda ``` -or *PARFLOW_ACCELERATOR_BACKEND=kokkos* and *DKokkos_ROOT=/path/to/Kokkos* i.e., +or *PARFLOW_ACCELERATOR_BACKEND=kokkos* and *DKOKKOS_ROOT=/path/to/Kokkos* i.e., ```shell -cmake ../parflow -DPARFLOW_AMPS_LAYER=mpi1 -DCMAKE_BUILD_TYPE=Release -DPARFLOW_ENABLE_TIMING=TRUE -DPARFLOW_HAVE_CLM=ON -DCMAKE_INSTALL_PREFIX=${PARFLOW_DIR} -DPARFLOW_ACCELERATOR_BACKEND=kokkos -DKokkos_ROOT=/path/to/Kokkos +cmake ../parflow -DPARFLOW_AMPS_LAYER=mpi1 -DCMAKE_BUILD_TYPE=Release -DPARFLOW_ENABLE_TIMING=TRUE -DPARFLOW_HAVE_CLM=ON -DCMAKE_INSTALL_PREFIX=${PARFLOW_DIR} -DPARFLOW_ACCELERATOR_BACKEND=kokkos -DKOKKOS_ROOT=/path/to/Kokkos ``` where *DPARFLOW_AMPS_LAYER=mpi1* leverages GPU-based data packing and unpacking. By default, the packed data is copied to a host staging buffer which is then passed for MPI to avoid special requirements for the MPI library. Direct communication between GPUs (with [GPUDirect P2P/RDMA](https://developer.nvidia.com/gpudirect)) can be activated by specifying an environment variable *PARFLOW_USE_GPUDIRECT=1* during runtime in which case the memory copy between CPU and GPU is avoided and a GPU pointer is passed for MPI, but this requires a CUDA-Aware MPI library (support for Unified Memory is not required with the native CUDA backend because the pointers passed to the MPI library point to pinned GPU memory allocations, but is required with the Kokkos backend). @@ -28,17 +28,17 @@ cmake ../parflow -DPARFLOW_AMPS_LAYER=mpi1 -DCMAKE_BUILD_TYPE=Release -DPARFLOW_ ``` or ```shell -cmake ../parflow -DPARFLOW_AMPS_LAYER=mpi1 -DCMAKE_BUILD_TYPE=Release -DPARFLOW_ENABLE_TIMING=TRUE -DPARFLOW_HAVE_CLM=ON -DCMAKE_INSTALL_PREFIX=${PARFLOW_DIR} -DPARFLOW_ACCELERATOR_BACKEND=kokkos -DKokkos_ROOT=/path/to/Kokkos -DRMM_ROOT=/path/to/RMM +cmake ../parflow -DPARFLOW_AMPS_LAYER=mpi1 -DCMAKE_BUILD_TYPE=Release -DPARFLOW_ENABLE_TIMING=TRUE -DPARFLOW_HAVE_CLM=ON -DCMAKE_INSTALL_PREFIX=${PARFLOW_DIR} -DPARFLOW_ACCELERATOR_BACKEND=kokkos -DKOKKOS_ROOT=/path/to/Kokkos -DRMM_ROOT=/path/to/RMM ``` -Similarly, the Umpire library can be activated by specifying the Umpire root directory with -Dumpire_ROOT=/path/to/umpire/root +Similarly, the Umpire library can be activated by specifying the Umpire root directory with -DUMPIRE_ROOT=/path/to/umpire/root ```shell -cmake ../parflow -DPARFLOW_AMPS_LAYER=mpi1 -DCMAKE_BUILD_TYPE=Release -DPARFLOW_ENABLE_TIMING=TRUE -DPARFLOW_HAVE_CLM=ON -DCMAKE_INSTALL_PREFIX=${PARFLOW_DIR} -DPARFLOW_ACCELERATOR_BACKEND=cuda -Dumpire_ROOT=/path/to/umpire +cmake ../parflow -DPARFLOW_AMPS_LAYER=mpi1 -DCMAKE_BUILD_TYPE=Release -DPARFLOW_ENABLE_TIMING=TRUE -DPARFLOW_HAVE_CLM=ON -DCMAKE_INSTALL_PREFIX=${PARFLOW_DIR} -DPARFLOW_ACCELERATOR_BACKEND=cuda -DUMPIRE_ROOT=/path/to/umpire ``` or ```shell -cmake ../parflow -DPARFLOW_AMPS_LAYER=mpi1 -DCMAKE_BUILD_TYPE=Release -DPARFLOW_ENABLE_TIMING=TRUE -DPARFLOW_HAVE_CLM=ON -DCMAKE_INSTALL_PREFIX=${PARFLOW_DIR} -DPARFLOW_ACCELERATOR_BACKEND=kokkos -DKokkos_ROOT=/path/to/Kokkos -Dumpire_ROOT=/path/to/umpire +cmake ../parflow -DPARFLOW_AMPS_LAYER=mpi1 -DCMAKE_BUILD_TYPE=Release -DPARFLOW_ENABLE_TIMING=TRUE -DPARFLOW_HAVE_CLM=ON -DCMAKE_INSTALL_PREFIX=${PARFLOW_DIR} -DPARFLOW_ACCELERATOR_BACKEND=kokkos -DKOKKOS_ROOT=/path/to/Kokkos -DUMPIRE_ROOT=/path/to/umpire ``` Note that on some systems, nvcc cannot locate the MPI include files by default, if this is the case, defining the environment variable CUDAHOSTCXX=mpicxx might help. @@ -47,7 +47,7 @@ Finally, you must make sure you are building the code for the correct GPU archit ```shell -cmake ../parflow -DPARFLOW_AMPS_LAYER=mpi1 -DCMAKE_BUILD_TYPE=Release -DPARFLOW_ENABLE_TIMING=TRUE -DPARFLOW_HAVE_CLM=ON -DCMAKE_INSTALL_PREFIX=${PARFLOW_DIR} -DPARFLOW_ACCELERATOR_BACKEND=cuda -Dumpire_ROOT=/path/to/umpire -DCMAKE_CUDA_ARCHITECTURES=90 +cmake ../parflow -DPARFLOW_AMPS_LAYER=mpi1 -DCMAKE_BUILD_TYPE=Release -DPARFLOW_ENABLE_TIMING=TRUE -DPARFLOW_HAVE_CLM=ON -DCMAKE_INSTALL_PREFIX=${PARFLOW_DIR} -DPARFLOW_ACCELERATOR_BACKEND=cuda -DUMPIRE_ROOT=/path/to/umpire -DCMAKE_CUDA_ARCHITECTURES=90 ``` ## Running Parflow with GPU acceleration From a2213358dffc347d7635b95b60a92cd95842fed3 Mon Sep 17 00:00:00 2001 From: reedmaxwell Date: Tue, 27 Jan 2026 19:19:58 -0500 Subject: [PATCH 4/5] Feature: CLM snow parameterization options (#695) ## Summary Add configurable snow parameterization options for CLM. ## New Features - **Rain-snow partitioning:** CLM, WetbulbThreshold, WetbulbLinear methods - **Thin snow damping:** Prevents spurious early-season melt from warm ground - **Albedo schemes:** CLM, VIC, Tarboton temperature-dependent aging ## New Keys (12 total) - `Solver.CLM.SnowPartition`, `WetbulbThreshold` - `Solver.CLM.ThinSnowDamping`, `ThinSnowThreshold` - `Solver.CLM.AlbedoScheme`, `AlbedoVisNew`, `AlbedoNirNew`, `AlbedoMin` - `Solver.CLM.AlbedoDecayVis`, `AlbedoDecayNir`, `AlbedoAccumA`, `AlbedoThawA` ## Changes - YAML schema with types, defaults, and bounds - RST documentation with physics background - 3 bibliography references - 3 Python tests with reference outputs ## References - Wang et al. (2019) GRL, doi:10.1029/2019GL085722 - Tarboton & Luce (1996) WRR, doi:10.1029/96WR01049 - Andreadis et al. (2009) JHM ## Test Plan - [x] `clm_snow_defaults` - backward compatibility - [x] `clm_snow_partition` - WetbulbThreshold method - [x] `clm_snow_albedo` - Tarboton scheme All pass via `ctest -R clm_snow` --------- Co-authored-by: Reed Co-authored-by: Claude Co-authored-by: Steven Smith --- docs/user_manual/keys.rst | 176 +++++++ docs/user_manual/refs.bib | 33 ++ pf-keys/definitions/solver.yaml | 140 ++++++ pfsimulator/clm/clm.F90 | 37 +- pfsimulator/clm/clm_hydro_canopy.F90 | 90 +++- pfsimulator/clm/clm_meltfreeze.F90 | 28 +- pfsimulator/clm/clm_snowalb.F90 | 132 +++++- pfsimulator/clm/clmtype.F90 | 18 +- pfsimulator/parflow_lib/parflow_proto_f.h | 59 +-- pfsimulator/parflow_lib/solver_richards.c | 126 ++++- .../clm_snow_albedo.out.alpha.pfb | Bin 0 -> 2100 bytes ...clm_snow_albedo.out.clm_output.00001.C.pfb | Bin 0 -> 4700 bytes ...clm_snow_albedo.out.clm_output.00002.C.pfb | Bin 0 -> 4700 bytes ...clm_snow_albedo.out.clm_output.00003.C.pfb | Bin 0 -> 4700 bytes ...clm_snow_albedo.out.clm_output.00004.C.pfb | Bin 0 -> 4700 bytes ...clm_snow_albedo.out.clm_output.00005.C.pfb | Bin 0 -> 4700 bytes .../clm_snow_albedo.out.mask.pfb | Bin 0 -> 2100 bytes .../clm_snow_albedo/clm_snow_albedo.out.n.pfb | Bin 0 -> 2100 bytes .../clm_snow_albedo.out.perm_x.pfb | Bin 0 -> 2100 bytes .../clm_snow_albedo.out.perm_y.pfb | Bin 0 -> 2100 bytes .../clm_snow_albedo.out.perm_z.pfb | Bin 0 -> 2100 bytes .../clm_snow_albedo.out.porosity.pfb | Bin 0 -> 2100 bytes .../clm_snow_albedo.out.press.00000.pfb | Bin 0 -> 2100 bytes .../clm_snow_albedo.out.press.00001.pfb | Bin 0 -> 2100 bytes .../clm_snow_albedo.out.press.00002.pfb | Bin 0 -> 2100 bytes .../clm_snow_albedo.out.press.00003.pfb | Bin 0 -> 2100 bytes .../clm_snow_albedo.out.press.00004.pfb | Bin 0 -> 2100 bytes .../clm_snow_albedo.out.press.00005.pfb | Bin 0 -> 2100 bytes .../clm_snow_albedo.out.satur.00000.pfb | Bin 0 -> 2100 bytes .../clm_snow_albedo.out.satur.00001.pfb | Bin 0 -> 2100 bytes .../clm_snow_albedo.out.satur.00002.pfb | Bin 0 -> 2100 bytes .../clm_snow_albedo.out.satur.00003.pfb | Bin 0 -> 2100 bytes .../clm_snow_albedo.out.satur.00004.pfb | Bin 0 -> 2100 bytes .../clm_snow_albedo.out.satur.00005.pfb | Bin 0 -> 2100 bytes .../clm_snow_albedo.out.specific_storage.pfb | Bin 0 -> 2100 bytes .../clm_snow_albedo.out.sres.pfb | Bin 0 -> 2100 bytes .../clm_snow_albedo.out.ssat.pfb | Bin 0 -> 2100 bytes .../clm_snow_defaults.out.alpha.pfb | Bin 0 -> 2100 bytes ...m_snow_defaults.out.clm_output.00001.C.pfb | Bin 0 -> 4700 bytes ...m_snow_defaults.out.clm_output.00002.C.pfb | Bin 0 -> 4700 bytes ...m_snow_defaults.out.clm_output.00003.C.pfb | Bin 0 -> 4700 bytes ...m_snow_defaults.out.clm_output.00004.C.pfb | Bin 0 -> 4700 bytes ...m_snow_defaults.out.clm_output.00005.C.pfb | Bin 0 -> 4700 bytes .../clm_snow_defaults.out.mask.pfb | Bin 0 -> 2100 bytes .../clm_snow_defaults.out.n.pfb | Bin 0 -> 2100 bytes .../clm_snow_defaults.out.perm_x.pfb | Bin 0 -> 2100 bytes .../clm_snow_defaults.out.perm_y.pfb | Bin 0 -> 2100 bytes .../clm_snow_defaults.out.perm_z.pfb | Bin 0 -> 2100 bytes .../clm_snow_defaults.out.porosity.pfb | Bin 0 -> 2100 bytes .../clm_snow_defaults.out.press.00000.pfb | Bin 0 -> 2100 bytes .../clm_snow_defaults.out.press.00001.pfb | Bin 0 -> 2100 bytes .../clm_snow_defaults.out.press.00002.pfb | Bin 0 -> 2100 bytes .../clm_snow_defaults.out.press.00003.pfb | Bin 0 -> 2100 bytes .../clm_snow_defaults.out.press.00004.pfb | Bin 0 -> 2100 bytes .../clm_snow_defaults.out.press.00005.pfb | Bin 0 -> 2100 bytes .../clm_snow_defaults.out.satur.00000.pfb | Bin 0 -> 2100 bytes .../clm_snow_defaults.out.satur.00001.pfb | Bin 0 -> 2100 bytes .../clm_snow_defaults.out.satur.00002.pfb | Bin 0 -> 2100 bytes .../clm_snow_defaults.out.satur.00003.pfb | Bin 0 -> 2100 bytes .../clm_snow_defaults.out.satur.00004.pfb | Bin 0 -> 2100 bytes .../clm_snow_defaults.out.satur.00005.pfb | Bin 0 -> 2100 bytes ...clm_snow_defaults.out.specific_storage.pfb | Bin 0 -> 2100 bytes .../clm_snow_defaults.out.sres.pfb | Bin 0 -> 2100 bytes .../clm_snow_defaults.out.ssat.pfb | Bin 0 -> 2100 bytes .../clm_snow_partition.out.alpha.pfb | Bin 0 -> 2100 bytes ..._snow_partition.out.clm_output.00001.C.pfb | Bin 0 -> 4700 bytes ..._snow_partition.out.clm_output.00002.C.pfb | Bin 0 -> 4700 bytes ..._snow_partition.out.clm_output.00003.C.pfb | Bin 0 -> 4700 bytes ..._snow_partition.out.clm_output.00004.C.pfb | Bin 0 -> 4700 bytes ..._snow_partition.out.clm_output.00005.C.pfb | Bin 0 -> 4700 bytes .../clm_snow_partition.out.mask.pfb | Bin 0 -> 2100 bytes .../clm_snow_partition.out.n.pfb | Bin 0 -> 2100 bytes .../clm_snow_partition.out.perm_x.pfb | Bin 0 -> 2100 bytes .../clm_snow_partition.out.perm_y.pfb | Bin 0 -> 2100 bytes .../clm_snow_partition.out.perm_z.pfb | Bin 0 -> 2100 bytes .../clm_snow_partition.out.porosity.pfb | Bin 0 -> 2100 bytes .../clm_snow_partition.out.press.00000.pfb | Bin 0 -> 2100 bytes .../clm_snow_partition.out.press.00001.pfb | Bin 0 -> 2100 bytes .../clm_snow_partition.out.press.00002.pfb | Bin 0 -> 2100 bytes .../clm_snow_partition.out.press.00003.pfb | Bin 0 -> 2100 bytes .../clm_snow_partition.out.press.00004.pfb | Bin 0 -> 2100 bytes .../clm_snow_partition.out.press.00005.pfb | Bin 0 -> 2100 bytes .../clm_snow_partition.out.satur.00000.pfb | Bin 0 -> 2100 bytes .../clm_snow_partition.out.satur.00001.pfb | Bin 0 -> 2100 bytes .../clm_snow_partition.out.satur.00002.pfb | Bin 0 -> 2100 bytes .../clm_snow_partition.out.satur.00003.pfb | Bin 0 -> 2100 bytes .../clm_snow_partition.out.satur.00004.pfb | Bin 0 -> 2100 bytes .../clm_snow_partition.out.satur.00005.pfb | Bin 0 -> 2100 bytes ...lm_snow_partition.out.specific_storage.pfb | Bin 0 -> 2100 bytes .../clm_snow_partition.out.sres.pfb | Bin 0 -> 2100 bytes .../clm_snow_partition.out.ssat.pfb | Bin 0 -> 2100 bytes test/python/clm/CMakeLists.txt | 5 +- test/python/clm/clm_snow_albedo.py | 413 +++++++++++++++++ test/python/clm/clm_snow_defaults.py | 429 ++++++++++++++++++ test/python/clm/clm_snow_partition.py | 410 +++++++++++++++++ test/tcl/clm/snow_forcing.1hr.txt | 6 + 96 files changed, 2037 insertions(+), 65 deletions(-) create mode 100644 test/correct_output/clm_snow_albedo/clm_snow_albedo.out.alpha.pfb create mode 100644 test/correct_output/clm_snow_albedo/clm_snow_albedo.out.clm_output.00001.C.pfb create mode 100644 test/correct_output/clm_snow_albedo/clm_snow_albedo.out.clm_output.00002.C.pfb create mode 100644 test/correct_output/clm_snow_albedo/clm_snow_albedo.out.clm_output.00003.C.pfb create mode 100644 test/correct_output/clm_snow_albedo/clm_snow_albedo.out.clm_output.00004.C.pfb create mode 100644 test/correct_output/clm_snow_albedo/clm_snow_albedo.out.clm_output.00005.C.pfb create mode 100644 test/correct_output/clm_snow_albedo/clm_snow_albedo.out.mask.pfb create mode 100644 test/correct_output/clm_snow_albedo/clm_snow_albedo.out.n.pfb create mode 100644 test/correct_output/clm_snow_albedo/clm_snow_albedo.out.perm_x.pfb create mode 100644 test/correct_output/clm_snow_albedo/clm_snow_albedo.out.perm_y.pfb create mode 100644 test/correct_output/clm_snow_albedo/clm_snow_albedo.out.perm_z.pfb create mode 100644 test/correct_output/clm_snow_albedo/clm_snow_albedo.out.porosity.pfb create mode 100644 test/correct_output/clm_snow_albedo/clm_snow_albedo.out.press.00000.pfb create mode 100644 test/correct_output/clm_snow_albedo/clm_snow_albedo.out.press.00001.pfb create mode 100644 test/correct_output/clm_snow_albedo/clm_snow_albedo.out.press.00002.pfb create mode 100644 test/correct_output/clm_snow_albedo/clm_snow_albedo.out.press.00003.pfb create mode 100644 test/correct_output/clm_snow_albedo/clm_snow_albedo.out.press.00004.pfb create mode 100644 test/correct_output/clm_snow_albedo/clm_snow_albedo.out.press.00005.pfb create mode 100644 test/correct_output/clm_snow_albedo/clm_snow_albedo.out.satur.00000.pfb create mode 100644 test/correct_output/clm_snow_albedo/clm_snow_albedo.out.satur.00001.pfb create mode 100644 test/correct_output/clm_snow_albedo/clm_snow_albedo.out.satur.00002.pfb create mode 100644 test/correct_output/clm_snow_albedo/clm_snow_albedo.out.satur.00003.pfb create mode 100644 test/correct_output/clm_snow_albedo/clm_snow_albedo.out.satur.00004.pfb create mode 100644 test/correct_output/clm_snow_albedo/clm_snow_albedo.out.satur.00005.pfb create mode 100644 test/correct_output/clm_snow_albedo/clm_snow_albedo.out.specific_storage.pfb create mode 100644 test/correct_output/clm_snow_albedo/clm_snow_albedo.out.sres.pfb create mode 100644 test/correct_output/clm_snow_albedo/clm_snow_albedo.out.ssat.pfb create mode 100644 test/correct_output/clm_snow_defaults/clm_snow_defaults.out.alpha.pfb create mode 100644 test/correct_output/clm_snow_defaults/clm_snow_defaults.out.clm_output.00001.C.pfb create mode 100644 test/correct_output/clm_snow_defaults/clm_snow_defaults.out.clm_output.00002.C.pfb create mode 100644 test/correct_output/clm_snow_defaults/clm_snow_defaults.out.clm_output.00003.C.pfb create mode 100644 test/correct_output/clm_snow_defaults/clm_snow_defaults.out.clm_output.00004.C.pfb create mode 100644 test/correct_output/clm_snow_defaults/clm_snow_defaults.out.clm_output.00005.C.pfb create mode 100644 test/correct_output/clm_snow_defaults/clm_snow_defaults.out.mask.pfb create mode 100644 test/correct_output/clm_snow_defaults/clm_snow_defaults.out.n.pfb create mode 100644 test/correct_output/clm_snow_defaults/clm_snow_defaults.out.perm_x.pfb create mode 100644 test/correct_output/clm_snow_defaults/clm_snow_defaults.out.perm_y.pfb create mode 100644 test/correct_output/clm_snow_defaults/clm_snow_defaults.out.perm_z.pfb create mode 100644 test/correct_output/clm_snow_defaults/clm_snow_defaults.out.porosity.pfb create mode 100644 test/correct_output/clm_snow_defaults/clm_snow_defaults.out.press.00000.pfb create mode 100644 test/correct_output/clm_snow_defaults/clm_snow_defaults.out.press.00001.pfb create mode 100644 test/correct_output/clm_snow_defaults/clm_snow_defaults.out.press.00002.pfb create mode 100644 test/correct_output/clm_snow_defaults/clm_snow_defaults.out.press.00003.pfb create mode 100644 test/correct_output/clm_snow_defaults/clm_snow_defaults.out.press.00004.pfb create mode 100644 test/correct_output/clm_snow_defaults/clm_snow_defaults.out.press.00005.pfb create mode 100644 test/correct_output/clm_snow_defaults/clm_snow_defaults.out.satur.00000.pfb create mode 100644 test/correct_output/clm_snow_defaults/clm_snow_defaults.out.satur.00001.pfb create mode 100644 test/correct_output/clm_snow_defaults/clm_snow_defaults.out.satur.00002.pfb create mode 100644 test/correct_output/clm_snow_defaults/clm_snow_defaults.out.satur.00003.pfb create mode 100644 test/correct_output/clm_snow_defaults/clm_snow_defaults.out.satur.00004.pfb create mode 100644 test/correct_output/clm_snow_defaults/clm_snow_defaults.out.satur.00005.pfb create mode 100644 test/correct_output/clm_snow_defaults/clm_snow_defaults.out.specific_storage.pfb create mode 100644 test/correct_output/clm_snow_defaults/clm_snow_defaults.out.sres.pfb create mode 100644 test/correct_output/clm_snow_defaults/clm_snow_defaults.out.ssat.pfb create mode 100644 test/correct_output/clm_snow_partition/clm_snow_partition.out.alpha.pfb create mode 100644 test/correct_output/clm_snow_partition/clm_snow_partition.out.clm_output.00001.C.pfb create mode 100644 test/correct_output/clm_snow_partition/clm_snow_partition.out.clm_output.00002.C.pfb create mode 100644 test/correct_output/clm_snow_partition/clm_snow_partition.out.clm_output.00003.C.pfb create mode 100644 test/correct_output/clm_snow_partition/clm_snow_partition.out.clm_output.00004.C.pfb create mode 100644 test/correct_output/clm_snow_partition/clm_snow_partition.out.clm_output.00005.C.pfb create mode 100644 test/correct_output/clm_snow_partition/clm_snow_partition.out.mask.pfb create mode 100644 test/correct_output/clm_snow_partition/clm_snow_partition.out.n.pfb create mode 100644 test/correct_output/clm_snow_partition/clm_snow_partition.out.perm_x.pfb create mode 100644 test/correct_output/clm_snow_partition/clm_snow_partition.out.perm_y.pfb create mode 100644 test/correct_output/clm_snow_partition/clm_snow_partition.out.perm_z.pfb create mode 100644 test/correct_output/clm_snow_partition/clm_snow_partition.out.porosity.pfb create mode 100644 test/correct_output/clm_snow_partition/clm_snow_partition.out.press.00000.pfb create mode 100644 test/correct_output/clm_snow_partition/clm_snow_partition.out.press.00001.pfb create mode 100644 test/correct_output/clm_snow_partition/clm_snow_partition.out.press.00002.pfb create mode 100644 test/correct_output/clm_snow_partition/clm_snow_partition.out.press.00003.pfb create mode 100644 test/correct_output/clm_snow_partition/clm_snow_partition.out.press.00004.pfb create mode 100644 test/correct_output/clm_snow_partition/clm_snow_partition.out.press.00005.pfb create mode 100644 test/correct_output/clm_snow_partition/clm_snow_partition.out.satur.00000.pfb create mode 100644 test/correct_output/clm_snow_partition/clm_snow_partition.out.satur.00001.pfb create mode 100644 test/correct_output/clm_snow_partition/clm_snow_partition.out.satur.00002.pfb create mode 100644 test/correct_output/clm_snow_partition/clm_snow_partition.out.satur.00003.pfb create mode 100644 test/correct_output/clm_snow_partition/clm_snow_partition.out.satur.00004.pfb create mode 100644 test/correct_output/clm_snow_partition/clm_snow_partition.out.satur.00005.pfb create mode 100644 test/correct_output/clm_snow_partition/clm_snow_partition.out.specific_storage.pfb create mode 100644 test/correct_output/clm_snow_partition/clm_snow_partition.out.sres.pfb create mode 100644 test/correct_output/clm_snow_partition/clm_snow_partition.out.ssat.pfb create mode 100644 test/python/clm/clm_snow_albedo.py create mode 100644 test/python/clm/clm_snow_defaults.py create mode 100644 test/python/clm/clm_snow_partition.py create mode 100644 test/tcl/clm/snow_forcing.1hr.txt diff --git a/docs/user_manual/keys.rst b/docs/user_manual/keys.rst index 4d1f30557..5a2ec782c 100644 --- a/docs/user_manual/keys.rst +++ b/docs/user_manual/keys.rst @@ -6337,6 +6337,182 @@ to be active. .Solver.CLM.UseSlopeAspect = True ## Python syntax +.. _CLM Snow Parameterization: + +CLM Snow Parameterization +^^^^^^^^^^^^^^^^^^^^^^^^^ + +These keys control snow physics options for improved snow modeling. +All keys default to backward-compatible behavior when not set. +Note that ``CLM`` must be compiled and linked at runtime for these +options to be active. + +**Rain-Snow Partitioning** + +Standard ``CLM`` uses air temperature to partition precipitation into rain +and snow. However, falling hydrometeors cool via evaporation, making +wet-bulb temperature a better predictor, especially in dry mountain +climates where snow can persist at air temperatures above 0°C. +Reference: :cite:p:`Wang2019`. + +*string* **Solver.CLM.SnowPartition** CLM Selects the method for +partitioning precipitation into rain and snow. The valid types for +this key are **CLM**, **WetbulbThreshold**, **WetbulbLinear**. + +**CLM**: + Standard air temperature threshold with linear transition (default). + +**WetbulbThreshold**: + Sharp threshold at wet-bulb temperature. Better for dry mountain climates. + +**WetbulbLinear**: + Linear transition around wet-bulb temperature threshold. + +.. container:: list + + :: + + pfset Solver.CLM.SnowPartition "WetbulbThreshold" ## TCL syntax + .Solver.CLM.SnowPartition = "WetbulbThreshold" ## Python syntax + +*double* **Solver.CLM.WetbulbThreshold** 274.15 Threshold temperature in +Kelvin for wetbulb partitioning methods. Default 274.15 K (1°C). Only +used when ``Solver.CLM.SnowPartition`` is ``WetbulbThreshold`` or +``WetbulbLinear``. + +.. container:: list + + :: + + pfset Solver.CLM.WetbulbThreshold 274.15 ## TCL syntax + .Solver.CLM.WetbulbThreshold = 274.15 ## Python syntax + +**Thin Snow Damping** + +Thin early-season snowpacks can experience spurious melt due to warm +ground heat flux. This option reduces melt energy for shallow snowpacks +to prevent premature ablation. + +*double* **Solver.CLM.ThinSnowDamping** 1.0 Fraction of melt energy +retained for thin snowpacks. A value of 1.0 means no damping (default, +backward compatible). A value of 0.1 means only 10% of melt energy is +applied (90% reduction). + +.. container:: list + + :: + + pfset Solver.CLM.ThinSnowDamping 0.3 ## TCL syntax + .Solver.CLM.ThinSnowDamping = 0.3 ## Python syntax + +*double* **Solver.CLM.ThinSnowThreshold** 50.0 Snow water equivalent +threshold in mm below which thin snow damping applies. + +.. container:: list + + :: + + pfset Solver.CLM.ThinSnowThreshold 50.0 ## TCL syntax + .Solver.CLM.ThinSnowThreshold = 50.0 ## Python syntax + +**Albedo Schemes** + +Snow albedo controls the radiation balance and strongly influences melt +timing. Three schemes are available: + +*string* **Solver.CLM.AlbedoScheme** CLM Snow albedo calculation method. +The valid types for this key are **CLM**, **VIC**, **Tarboton**. + +**CLM**: + Age-based exponential decay using the snowage variable (default). + +**VIC**: + Separate decay rates for cold (accumulating) and warm (melting) + conditions based on ground temperature. Reference: :cite:p:`Andreadis2009`. + +**Tarboton**: + Arrhenius temperature-dependent aging where decay accelerates near + the melting point. Reference: :cite:p:`Tarboton1996`. + +.. container:: list + + :: + + pfset Solver.CLM.AlbedoScheme "Tarboton" ## TCL syntax + .Solver.CLM.AlbedoScheme = "Tarboton" ## Python syntax + +*double* **Solver.CLM.AlbedoVisNew** 0.95 Fresh snow visible-band albedo. +Physically ranges 0.85-0.98. + +.. container:: list + + :: + + pfset Solver.CLM.AlbedoVisNew 0.95 ## TCL syntax + .Solver.CLM.AlbedoVisNew = 0.95 ## Python syntax + +*double* **Solver.CLM.AlbedoNirNew** 0.65 Fresh snow near-infrared albedo. +Physically ranges 0.5-0.7. + +.. container:: list + + :: + + pfset Solver.CLM.AlbedoNirNew 0.65 ## TCL syntax + .Solver.CLM.AlbedoNirNew = 0.65 ## Python syntax + +*double* **Solver.CLM.AlbedoMin** 0.4 Minimum snow albedo floor for aged +or dirty snow. + +.. container:: list + + :: + + pfset Solver.CLM.AlbedoMin 0.4 ## TCL syntax + .Solver.CLM.AlbedoMin = 0.4 ## Python syntax + +*double* **Solver.CLM.AlbedoDecayVis** 0.5 Visible albedo decay coefficient +for ``CLM`` and ``Tarboton`` schemes. + +.. container:: list + + :: + + pfset Solver.CLM.AlbedoDecayVis 0.5 ## TCL syntax + .Solver.CLM.AlbedoDecayVis = 0.5 ## Python syntax + +*double* **Solver.CLM.AlbedoDecayNir** 0.2 NIR albedo decay coefficient +for ``CLM`` and ``Tarboton`` schemes. NIR typically decays faster than visible. + +.. container:: list + + :: + + pfset Solver.CLM.AlbedoDecayNir 0.2 ## TCL syntax + .Solver.CLM.AlbedoDecayNir = 0.2 ## Python syntax + +*double* **Solver.CLM.AlbedoAccumA** 0.94 VIC scheme cold-phase +(accumulation) decay base per hour. Should be greater than ``AlbedoThawA``. + +.. container:: list + + :: + + pfset Solver.CLM.AlbedoAccumA 0.94 ## TCL syntax + .Solver.CLM.AlbedoAccumA = 0.94 ## Python syntax + +*double* **Solver.CLM.AlbedoThawA** 0.82 VIC scheme melt-phase decay base +per hour. Should be less than ``AlbedoAccumA`` since melt conditions age +snow faster. + +.. container:: list + + :: + + pfset Solver.CLM.AlbedoThawA 0.82 ## TCL syntax + .Solver.CLM.AlbedoThawA = 0.82 ## Python syntax + + .. _ParFlow NetCDF4 Parallel I/O: ParFlow NetCDF4 Parallel I/O diff --git a/docs/user_manual/refs.bib b/docs/user_manual/refs.bib index aa9377929..623389017 100644 --- a/docs/user_manual/refs.bib +++ b/docs/user_manual/refs.bib @@ -1378,3 +1378,36 @@ @misc{endian title = {Endianness --- Wikipedia, The Free Encyclopedia}, url = {http://en.wikipedia.org/wiki/Endianness}, } + +@article{Wang2019, +author = {Wang, Y. and Broxton, P. and Fang, Y. and Behrangi, A. and Barlage, M. and Zeng, X. and Niu, G.}, +journal = {Geophysical Research Letters}, +number = {22}, +pages = {13104--13113}, +title = {A Wet-Bulb Temperature-Based Rain-Snow Partitioning Scheme Improves Snowpack Prediction Over the Drier Western United States}, +volume = {46}, +year = {2019}, +doi = {10.1029/2019GL085722}, +} + +@article{Andreadis2009, +author = {Andreadis, K. M. and Storck, P. and Lettenmaier, D. P.}, +journal = {Journal of Hydrometeorology}, +number = {6}, +pages = {1379--1395}, +title = {Modeling Snow Accumulation and Ablation Processes in Forested Environments}, +volume = {10}, +year = {2009}, +doi = {10.1175/2009JHM1083.1}, +} + +@article{Tarboton1996, +author = {Tarboton, D. G. and Luce, C. H.}, +journal = {Water Resources Research}, +number = {4}, +pages = {1195--1207}, +title = {Utah Energy Balance Snow Accumulation and Melt Model (UEB)}, +volume = {32}, +year = {1996}, +doi = {10.1029/96WR01049}, +} diff --git a/pf-keys/definitions/solver.yaml b/pf-keys/definitions/solver.yaml index 25a029370..2a1305dc6 100644 --- a/pf-keys/definitions/solver.yaml +++ b/pf-keys/definitions/solver.yaml @@ -494,6 +494,146 @@ Solver: BoolDomain: RequiresModule: CLM + # ----------------------------------------------------------------------------- + # CLM Snow Parameterization Keys + # ----------------------------------------------------------------------------- + + SnowPartition: + help: > + [Type: string] Selects the rain-snow partitioning method. CLM uses air + temperature threshold, while wetbulb methods account for evaporative + cooling of falling hydrometeors. Reference: Wang et al. (2019) GRL. + default: CLM + domains: + EnumDomain: + enum_list: + - CLM + - WetbulbThreshold + - WetbulbLinear + RequiresModule: CLM + + WetbulbThreshold: + help: > + [Type: double] Threshold temperature (K) for wetbulb rain-snow partitioning. + Default 274.15 K (1 degree C). Only used when SnowPartition is WetbulbThreshold + or WetbulbLinear. + default: 274.15 + domains: + DoubleValue: + min_value: 268.15 + max_value: 278.15 + RequiresModule: CLM + + ThinSnowDamping: + help: > + [Type: double] Fraction of melt energy retained for thin snowpacks. + 1.0 = no damping (default, backward compatible), 0.1 = 90% energy reduction. + Prevents spurious early-season melt from warm ground heat flux. + default: 1.0 + domains: + DoubleValue: + min_value: 0.0 + max_value: 1.0 + RequiresModule: CLM + + ThinSnowThreshold: + help: > + [Type: double] SWE threshold (mm) below which thin snow damping applies. + default: 50.0 + domains: + DoubleValue: + min_value: 1.0 + max_value: 500.0 + RequiresModule: CLM + + AlbedoScheme: + help: > + [Type: string] Snow albedo calculation method. CLM uses age-based decay, + VIC uses separate cold/melt decay rates based on ground temperature, + Tarboton uses Arrhenius temperature-dependent aging. + References: Andreadis et al. (2009), Tarboton and Luce (1996). + default: CLM + domains: + EnumDomain: + enum_list: + - CLM + - VIC + - Tarboton + RequiresModule: CLM + + AlbedoVisNew: + help: > + [Type: double] Fresh snow visible-band albedo. Physically ranges 0.85-0.98. + default: 0.95 + domains: + DoubleValue: + min_value: 0.80 + max_value: 0.99 + RequiresModule: CLM + + AlbedoNirNew: + help: > + [Type: double] Fresh snow near-infrared albedo. Physically ranges 0.5-0.7. + default: 0.65 + domains: + DoubleValue: + min_value: 0.40 + max_value: 0.85 + RequiresModule: CLM + + AlbedoMin: + help: > + [Type: double] Minimum snow albedo floor for aged or dirty snow. + default: 0.4 + domains: + DoubleValue: + min_value: 0.2 + max_value: 0.7 + RequiresModule: CLM + + AlbedoDecayVis: + help: > + [Type: double] Visible albedo decay coefficient for CLM and Tarboton schemes. + default: 0.5 + domains: + DoubleValue: + min_value: 0.1 + max_value: 2.0 + RequiresModule: CLM + + AlbedoDecayNir: + help: > + [Type: double] NIR albedo decay coefficient for CLM and Tarboton schemes. + NIR typically decays faster than visible. + default: 0.2 + domains: + DoubleValue: + min_value: 0.05 + max_value: 1.0 + RequiresModule: CLM + + AlbedoAccumA: + help: > + [Type: double] VIC scheme cold-phase (accumulation) decay base per hour. + Should be greater than AlbedoThawA. + default: 0.94 + domains: + DoubleValue: + min_value: 0.80 + max_value: 0.99 + RequiresModule: CLM + + AlbedoThawA: + help: > + [Type: double] VIC scheme melt-phase decay base per hour. + Should be less than AlbedoAccumA since melt conditions age snow faster. + default: 0.82 + domains: + DoubleValue: + min_value: 0.70 + max_value: 0.95 + RequiresModule: CLM + # ----------------------------------------------------------------------------- # CLM input variables to write drv_clmin.dat (Input) and drv_vegp.dat (VegParams) files # ----------------------------------------------------------------------------- diff --git a/pfsimulator/clm/clm.F90 b/pfsimulator/clm/clm.F90 index b2c2cb994..85a7a83fa 100644 --- a/pfsimulator/clm/clm.F90 +++ b/pfsimulator/clm/clm.F90 @@ -10,7 +10,10 @@ subroutine clm_lsm(pressure,saturation,evap_trans,top,bottom,porosity,pf_dz_mult write_CLM_binary,slope_accounting_CLM,beta_typepf,veg_water_stress_typepf,wilting_pointpf,field_capacitypf, & res_satpf,irr_typepf, irr_cyclepf, irr_ratepf, irr_startpf, irr_stoppf, irr_thresholdpf, & qirr_pf,qirr_inst_pf,irr_flag_pf,irr_thresholdtypepf,soi_z,clm_next,clm_write_logs, & -clm_last_rst,clm_daily_rst,rz_water_stress_typepf, pf_nlevsoi, pf_nlevlak) +clm_last_rst,clm_daily_rst,rz_water_stress_typepf, pf_nlevsoi, pf_nlevlak, & +snow_partition_typepf,tw_thresholdpf,thin_snow_dampingpf,thin_snow_thresholdpf, & +albedo_schemepf,albedo_vis_newpf,albedo_nir_newpf,albedo_minpf, & +albedo_decay_vispf,albedo_decay_nirpf,albedo_accum_apf,albedo_thaw_apf) !========================================================================= ! @@ -147,6 +150,22 @@ subroutine clm_lsm(pressure,saturation,evap_trans,top,bottom,porosity,pf_dz_mult real(r8) :: irr_thresholdpf ! irrigation threshold criteria for deficit cycle (units of soil moisture content) integer :: irr_thresholdtypepf ! irrigation threshold criteria type -- top layer, bottom layer, column avg + ! snow parameterization keys @RMM 2025 + integer :: snow_partition_typepf ! rain-snow partition: 0=CLM, 1=wetbulb threshold, 2=wetbulb linear + real(r8) :: tw_thresholdpf ! wetbulb temperature threshold for snow [K] + real(r8) :: thin_snow_dampingpf ! thin snow energy damping factor [0-1] + real(r8) :: thin_snow_thresholdpf ! SWE threshold for damping [kg/m2] + + ! snow albedo parameterization keys @RMM 2025 + integer :: albedo_schemepf ! albedo scheme: 0=CLM, 1=VIC, 2=Tarboton + real(r8) :: albedo_vis_newpf ! fresh snow VIS albedo [0-1] + real(r8) :: albedo_nir_newpf ! fresh snow NIR albedo [0-1] + real(r8) :: albedo_minpf ! minimum albedo floor [0-1] + real(r8) :: albedo_decay_vispf ! VIS decay coefficient [0-1] + real(r8) :: albedo_decay_nirpf ! NIR decay coefficient [0-1] + real(r8) :: albedo_accum_apf ! VIC cold-phase decay base + real(r8) :: albedo_thaw_apf ! VIC melt-phase decay base + ! local indices & counters integer :: i,j,k,k1,j1,l1 ! indices for local looping integer :: bj,bl ! indices for local looping !BH @@ -487,6 +506,22 @@ subroutine clm_lsm(pressure,saturation,evap_trans,top,bottom,porosity,pf_dz_mult clm(t)%field_capacity = field_capacitypf clm(t)%res_sat = res_satpf + ! for snow parameterization @RMM 2025 + clm(t)%snow_partition_type = snow_partition_typepf + clm(t)%tw_threshold = tw_thresholdpf + clm(t)%thin_snow_damping = thin_snow_dampingpf + clm(t)%thin_snow_threshold = thin_snow_thresholdpf + + ! for snow albedo parameterization @RMM 2025 + clm(t)%albedo_scheme = albedo_schemepf + clm(t)%albedo_vis_new = albedo_vis_newpf + clm(t)%albedo_nir_new = albedo_nir_newpf + clm(t)%albedo_min = albedo_minpf + clm(t)%albedo_decay_vis = albedo_decay_vispf + clm(t)%albedo_decay_nir = albedo_decay_nirpf + clm(t)%albedo_accum_a = albedo_accum_apf + clm(t)%albedo_thaw_a = albedo_thaw_apf + ! for irrigation clm(t)%irr_type = irr_typepf clm(t)%irr_cycle = irr_cyclepf diff --git a/pfsimulator/clm/clm_hydro_canopy.F90 b/pfsimulator/clm/clm_hydro_canopy.F90 index a065b1cd8..136cf2248 100644 --- a/pfsimulator/clm/clm_hydro_canopy.F90 +++ b/pfsimulator/clm/clm_hydro_canopy.F90 @@ -32,6 +32,11 @@ subroutine clm_hydro_canopy (clm) use clm_varcon, only : tfrz, istice, istwet, istsoil implicit none + ! Parameters for saturation vapor pressure calculation + real(r8), parameter :: es_a = 611.2d0 ! [Pa] reference saturation vapor pressure + real(r8), parameter :: es_b = 17.67d0 ! coefficient for Clausius-Clapeyron + real(r8), parameter :: es_c = 243.5d0 ! [C] coefficient for Clausius-Clapeyron + !=== Arguments =========================================================== type (clm1d), intent(inout) :: clm !CLM 1-D Module @@ -56,6 +61,15 @@ subroutine clm_hydro_canopy (clm) real(r8) :: & dewmxi ! inverse of maximum allowed dew [1/mm] + ! Variables for wetbulb rain-snow partitioning @RMM 2025 + real(r8) :: t_c ! air temperature in Celsius + real(r8) :: t_wb ! wetbulb temperature in Celsius + real(r8) :: t_wb_k ! wetbulb temperature in Kelvin + real(r8) :: rh_pct ! relative humidity in percent + real(r8) :: e_sat ! saturation vapor pressure [Pa] + real(r8) :: e_act ! actual vapor pressure [Pa] + real(r8) :: q_sat ! saturation specific humidity [kg/kg] + !=== End Variable List =================================================== qflx_candrip = 0. @@ -144,22 +158,80 @@ subroutine clm_hydro_canopy (clm) clm%qflx_prec_grnd = qflx_through + qflx_candrip endif - ! 1.3 The percentage of liquid water by mass, which is arbitrarily set to + ! 1.3 The percentage of liquid water by mass, which is arbitrarily set to ! vary linearly with air temp, from 0% at 273.16 to 40% max at 275.16. + ! @RMM 2025: Only use itypprc shortcut for CLM default method (type 0). + ! Wetbulb methods (type 1,2) need to run regardless of itypprc to catch + ! cases where air temp is warm but wetbulb is cold (humid conditions). - if (clm%itypprc <= 1) then + if (clm%itypprc <= 1 .and. clm%snow_partition_type == 0) then flfall = 1. ! fraction of liquid water within falling precip. clm%qflx_snow_grnd = 0. ! ice onto ground (mm/s) clm%qflx_rain_grnd = clm%qflx_prec_grnd ! liquid water onto ground (mm/s) dz_snowf = 0. ! rate of snowfall, snow depth/s (m/s) else - if (clm%forc_t <= tfrz) then - flfall = 0. - else if (clm%forc_t <= tfrz+2.) then - flfall = -54.632 + 0.2*clm%forc_t - else - flfall = 0.4 - endif + ! @RMM 2025: Select rain-snow partitioning method + ! snow_partition_type: 0=CLM linear (air temp), 1=wetbulb threshold, 2=wetbulb linear + select case (clm%snow_partition_type) + + case (1, 2) ! Wetbulb-based methods + ! Calculate wetbulb temperature using Stull (2011) psychrometric approximation + ! First convert air temp to Celsius + t_c = clm%forc_t - tfrz + + ! Calculate saturation vapor pressure using Clausius-Clapeyron + e_sat = es_a * exp(es_b * t_c / (t_c + es_c)) + + ! Calculate saturation specific humidity + q_sat = 0.622d0 * e_sat / (clm%forc_pbot - 0.378d0 * e_sat) + + ! Calculate relative humidity from specific humidity + ! Avoid division by zero and cap at 100% + if (q_sat > 0.0d0) then + rh_pct = min(100.0d0, max(0.0d0, 100.0d0 * clm%forc_q / q_sat)) + else + rh_pct = 100.0d0 + endif + + ! Stull (2011) wet-bulb temperature approximation (result in Celsius) + ! Valid for RH >= 5% and T between -20C and 50C + t_wb = t_c * atan(0.151977d0 * sqrt(rh_pct + 8.313659d0)) & + + atan(t_c + rh_pct) & + - atan(rh_pct - 1.676331d0) & + + 0.00391838d0 * (rh_pct**1.5d0) * atan(0.023101d0 * rh_pct) & + - 4.686035d0 + + ! Convert wetbulb to Kelvin for comparison + t_wb_k = t_wb + tfrz + + if (clm%snow_partition_type == 1) then + ! Wetbulb threshold method: all snow below threshold, all rain above + if (t_wb_k <= clm%tw_threshold) then + flfall = 0.0d0 ! all snow + else + flfall = 1.0d0 ! all rain + endif + else ! case 2: wetbulb linear + ! Linear transition over 2K range centered on threshold + if (t_wb_k <= clm%tw_threshold - 1.0d0) then + flfall = 0.0d0 + else if (t_wb_k >= clm%tw_threshold + 1.0d0) then + flfall = 1.0d0 + else + flfall = (t_wb_k - (clm%tw_threshold - 1.0d0)) / 2.0d0 + endif + endif + + case default ! Case 0: CLM default linear (air temperature) + if (clm%forc_t <= tfrz) then + flfall = 0. + else if (clm%forc_t <= tfrz+2.) then + flfall = -54.632 + 0.2*clm%forc_t + else + flfall = 0.4 + endif + + end select ! Use Alta relationship, Anderson(1976); LaChapelle(1961), ! U.S.Department of Agriculture Forest Service, Project F, diff --git a/pfsimulator/clm/clm_meltfreeze.F90 b/pfsimulator/clm/clm_meltfreeze.F90 index 691a3c180..903c55912 100644 --- a/pfsimulator/clm/clm_meltfreeze.F90 +++ b/pfsimulator/clm/clm_meltfreeze.F90 @@ -62,7 +62,10 @@ subroutine clm_meltfreeze (fact, brr, hs, dhsdT, & temp1 ! temporary variables [kg/m2] real(r8), dimension(clm%snl+1 : nlevsoi) :: wmass0, wice0, wliq0 - real(r8) propor,tinc + real(r8) propor,tinc + + ! @RMM 2025: Thin snow damping variables + real(r8) :: damping_factor ! factor to reduce melt energy for thin snowpacks !=== End Variable List =================================================== @@ -119,6 +122,29 @@ subroutine clm_meltfreeze (fact, brr, hs, dhsdT, & endif enddo +! @RMM 2025: Apply thin snow damping to snow layer melt energy +! This reduces energy available for melting when snowpack is thin, +! preventing premature melt of shallow snowpacks + if (clm%thin_snow_damping > 0.0d0 .and. clm%thin_snow_damping < 1.0d0) then + do j = clm%snl+1, 0 ! Only snow layers (indices <= 0) + if (clm%imelt(j) == 1 .and. hm(j) > 0.0d0) then ! Only for melting + ! Calculate damping factor: linear interpolation from damping at SWE=0 + ! to 1.0 at SWE=threshold + if (clm%h2osno <= 0.0d0) then + damping_factor = clm%thin_snow_damping + else if (clm%h2osno >= clm%thin_snow_threshold) then + damping_factor = 1.0d0 + else + damping_factor = clm%thin_snow_damping + & + (1.0d0 - clm%thin_snow_damping) * & + (clm%h2osno / clm%thin_snow_threshold) + endif + ! Apply damping to melt energy + hm(j) = hm(j) * damping_factor + endif + enddo + endif + ! These two errors were checked carefully. They result from the the computed error ! of "Tridiagonal-Matrix" in subroutine "thermal". diff --git a/pfsimulator/clm/clm_snowalb.F90 b/pfsimulator/clm/clm_snowalb.F90 index b441a1407..6b7503a4e 100644 --- a/pfsimulator/clm/clm_snowalb.F90 +++ b/pfsimulator/clm/clm_snowalb.F90 @@ -2,20 +2,27 @@ subroutine clm_snowalb (clm, coszen, nband, ind, alb) -!----------------------------------------------------------------------- -! -! Purpose: -! Determine snow albedos -! +!----------------------------------------------------------------------- +! +! Purpose: +! Determine snow albedos with configurable parameterization schemes +! +! Schemes available: +! 0 = CLM (default) - age-based decay with configurable parameters +! 1 = VIC - dual cold/warm decay rates based on ground temperature +! 2 = Tarboton - Arrhenius temperature-dependent aging +! !----------------------------------------------------------------------- ! $Id: clm_snowalb.F90,v 1.1.1.1 2006/02/14 23:05:52 kollet Exp $ +! Modified @RMM 2025 - configurable albedo parameters and alternative schemes !----------------------------------------------------------------------- use precision use clmtype + use clm_varcon, only : tfrz implicit none -! ------------------------ arguments ------------------------------ +! ------------------------ arguments ------------------------------ type (clm1d), intent(inout) :: clm !CLM 1-D Module real(r8) , intent(in) :: coszen !cosine solar zenith angle for next time step integer , intent(in) :: nband !number of solar radiation waveband classes @@ -27,13 +34,16 @@ subroutine clm_snowalb (clm, coszen, nband, ind, alb) integer :: ib !waveband class - real(r8) :: snal0 = 0.95 !vis albedo of new snow for sza<60 - real(r8) :: snal1 = 0.65 !nir albedo of new snow for sza<60 - real(r8) :: conn = 0.5 !constant for visible snow alb calculation [-] - real(r8) :: cons = 0.2 !constant (=0.2) for nir snow albedo calculation [-] real(r8) :: sl = 2.0 !factor that helps control alb zenith dependence [-] + real(r8) :: snal0 !vis albedo of new snow (from clm state) + real(r8) :: snal1 !nir albedo of new snow (from clm state) + real(r8) :: conn !constant for visible snow alb calculation (from clm state) + real(r8) :: cons !constant for nir snow albedo calculation (from clm state) + real(r8) :: age !factor to reduce visible snow alb due to snow age [-] + real(r8) :: age_days !snow age in days for VIC/Tarboton schemes + real(r8) :: decay_factor !temperature-dependent decay factor for Tarboton scheme real(r8) :: albs !temporary vis snow albedo real(r8) :: albl !temporary nir snow albedo real(r8) :: cff !snow alb correction factor for zenith angle > 60 [-] @@ -41,6 +51,12 @@ subroutine clm_snowalb (clm, coszen, nband, ind, alb) ! ----------------------------------------------------------------- +! Use configurable parameters from clm state + snal0 = clm%albedo_vis_new ! default 0.95 + snal1 = clm%albedo_nir_new ! default 0.65 + conn = clm%albedo_decay_vis ! default 0.5 + cons = clm%albedo_decay_nir ! default 0.2 + ! zero albedos do ib = 1, nband @@ -48,27 +64,93 @@ subroutine clm_snowalb (clm, coszen, nband, ind, alb) end do ! ========================================================================= -! CLM Albedo for snow cover. -! Snow albedo depends on snow-age, zenith angle, and thickness of snow age -! gives reduction of visible radiation +! Snow albedo calculation based on selected scheme ! ========================================================================= -! Correction for snow age + select case (clm%albedo_scheme) + + case (0) ! CLM default - age-based decay + ! ===================================================================== + ! CLM Albedo for snow cover. + ! Snow albedo depends on snow-age, zenith angle, and thickness of snow age + ! gives reduction of visible radiation + ! ===================================================================== + + ! Correction for snow age + age = 1._r8 - 1._r8/(1._r8 + clm%snowage) + albs = snal0*(1._r8 - cons*age) + albl = snal1*(1._r8 - conn*age) + + case (1) ! VIC - dual cold/warm decay rates + ! ===================================================================== + ! VIC-style albedo decay with different rates for cold vs warm conditions + ! Cold (accumulating): slower decay, albedo_accum_a base + ! Warm (melting): faster decay, albedo_thaw_a base + ! Reference: Andreadis et al. (2009), VIC snow model documentation + ! ===================================================================== + + ! Convert snow age to days (snowage is in seconds in CLM) + age_days = clm%snowage / 86400.0_r8 + age_days = max(age_days, 0.001_r8) ! Prevent zero/negative values + + if (clm%t_grnd < tfrz) then + ! Cold/accumulating conditions - slow decay + albs = snal0 * (clm%albedo_accum_a ** (age_days ** 0.58_r8)) + albl = snal1 * (clm%albedo_accum_a ** (age_days ** 0.58_r8)) + else + ! Warm/melting conditions - fast decay + albs = snal0 * (clm%albedo_thaw_a ** (age_days ** 0.46_r8)) + albl = snal1 * (clm%albedo_thaw_a ** (age_days ** 0.46_r8)) + endif + + case (2) ! Tarboton - Arrhenius temperature dependence + ! ===================================================================== + ! Tarboton-style temperature-dependent albedo decay + ! Aging rate increases exponentially as temperature approaches freezing + ! Reference: Tarboton & Luce (1996), Utah Energy Balance Snow Model + ! ===================================================================== + + ! Temperature-dependent aging rate (faster near melting point) + ! Uses Arrhenius-type formulation + decay_factor = exp(5000.0_r8 * (1.0_r8/tfrz - 1.0_r8/max(clm%t_grnd, 200.0_r8))) + decay_factor = min(decay_factor, 10.0_r8) ! Cap to prevent extreme values + + ! Convert snow age to days + age_days = clm%snowage / 86400.0_r8 + + ! Modified age factor with temperature dependence + age = (age_days * decay_factor) / (1.0_r8 + age_days * decay_factor) + + albs = snal0 * (1.0_r8 - cons * age) + albl = snal1 * (1.0_r8 - conn * age) + + case default + ! Fallback to CLM default + age = 1._r8 - 1._r8/(1._r8 + clm%snowage) + albs = snal0*(1._r8 - cons*age) + albl = snal1*(1._r8 - conn*age) + + end select - age = 1.-1./(1.+clm%snowage) - albs = snal0*(1.-cons*age) - albl = snal1*(1.-conn*age) +! ========================================================================= +! Apply minimum albedo floor (prevents unrealistically low values) +! ========================================================================= + albs = max(clm%albedo_min, albs) + albl = max(clm%albedo_min, albl) - if (ind == 0) then +! ========================================================================= +! Zenith angle correction (applied to all schemes) +! ========================================================================= -! Czf corrects albedo of new snow for solar zenith + if (ind == 0) then - cff = ((1.+1./sl)/(1.+max(dble(0.001),coszen)*2.*sl )- 1./sl) - cff = max(cff,dble(0.)) - czf = 0.4*cff*(1.-albs) - albs = albs+czf - czf = 0.4*cff*(1.-albl) - albl = albl+czf + ! Czf corrects albedo of new snow for solar zenith + cff = ((1._r8 + 1._r8/sl)/(1._r8 + max(0.001_r8, coszen)*2._r8*sl) - 1._r8/sl) + cff = max(cff, 0.0_r8) + czf = 0.4_r8*cff*(1._r8 - albs) + albs = albs + czf + czf = 0.4_r8*cff*(1._r8 - albl) + albl = albl + czf endif diff --git a/pfsimulator/clm/clmtype.F90 b/pfsimulator/clm/clmtype.F90 index 7f61e6902..50f232175 100644 --- a/pfsimulator/clm/clmtype.F90 +++ b/pfsimulator/clm/clmtype.F90 @@ -250,7 +250,23 @@ module clmtype real(r8) :: irr_threshold ! irrigation soil moisture threshold for deficit cycle @IMF integer :: threshold_type ! irrigation threshold type -- top layer, bottom layer, column avg. real(r8) :: irr_flag ! flag for irrigation or non-irrigation for a given day (based on threshold) - + +! Snow parameterization options @RMM 2025 + integer :: snow_partition_type ! rain-snow partition: 0=CLM linear, 1=wetbulb threshold, 2=wetbulb linear + real(r8) :: tw_threshold ! wetbulb temperature threshold for snow [K], default 274.15 + real(r8) :: thin_snow_damping ! damping factor for thin snow energy [0-1], 0=off + real(r8) :: thin_snow_threshold ! SWE threshold for damping [kg/m2 or mm], default 50.0 + +! Snow albedo parameterization options @RMM 2025 + integer :: albedo_scheme ! albedo scheme: 0=CLM, 1=VIC, 2=Tarboton + real(r8) :: albedo_vis_new ! fresh snow VIS albedo [0-1], default 0.95 + real(r8) :: albedo_nir_new ! fresh snow NIR albedo [0-1], default 0.65 + real(r8) :: albedo_min ! minimum albedo floor [0-1], default 0.4 + real(r8) :: albedo_decay_vis ! VIS decay coefficient [0-1], default 0.5 + real(r8) :: albedo_decay_nir ! NIR decay coefficient [0-1], default 0.2 + real(r8) :: albedo_accum_a ! VIC cold-phase decay base, default 0.94 + real(r8) :: albedo_thaw_a ! VIC melt-phase decay base, default 0.82 + real(r8) :: eflx_snomelt ! added to be consistent with lsm hybrid code real(r8) :: eflx_impsoil ! implicit evaporation for soil temperature equation (W/m**2) real(r8) :: eflx_lh_vege ! veg evaporation heat flux (W/m**2) [+ to atm] diff --git a/pfsimulator/parflow_lib/parflow_proto_f.h b/pfsimulator/parflow_lib/parflow_proto_f.h index 61359969a..34fdffdb5 100644 --- a/pfsimulator/parflow_lib/parflow_proto_f.h +++ b/pfsimulator/parflow_lib/parflow_proto_f.h @@ -120,31 +120,35 @@ void SADVECT(double *s, double *sn, #define CLM_LSM clm_lsm_ #endif -#define CALL_CLM_LSM(pressure_data, saturation_data, evap_trans_data, top, bottom, porosity_data, \ - dz_mult_data, istep, dt, t, start_time, dx, dy, dz, ix, iy, nx, ny, nz, \ - nx_f, ny_f, nz_f, nz_rz, ip, p, q, r, gnx, gny, rank, \ - sw_data, lw_data, prcp_data, tas_data, u_data, v_data, patm_data, qatm_data, \ - lai_data, sai_data, z0m_data, displa_data, \ - slope_x_data, slope_y_data, \ - eflx_lh_tot_data, eflx_lwrad_out_data, eflx_sh_tot_data, eflx_soil_grnd_data, \ - qflx_evap_tot_data, qflx_evap_grnd_data, qflx_evap_soi_data, qflx_evap_veg_data, qflx_tran_veg_data, \ - qflx_infl_data, swe_out_data, t_grnd_data, t_soil_data, \ - clm_dump_interval, clm_1d_out, clm_forc_veg, clm_file_dir, clm_file_dir_length, clm_bin_out_dir, write_CLM_binary, slope_accounting_CLM, \ - clm_beta_function, clm_veg_function, clm_veg_wilting, clm_veg_fieldc, clm_res_sat, \ - clm_irr_type, clm_irr_cycle, clm_irr_rate, clm_irr_start, clm_irr_stop, \ - clm_irr_threshold, qirr, qirr_inst, iflag, clm_irr_thresholdtype, soi_z, clm_next, clm_write_logs, clm_last_rst, clm_daily_rst, clm_water_stress_type, clm_nlevsoi, clm_nlevlak) \ - CLM_LSM(pressure_data, saturation_data, evap_trans_data, top, bottom, porosity_data, \ - dz_mult_data, &istep, &dt, &t, &start_time, &dx, &dy, &dz, &ix, &iy, &nx, &ny, &nz, &nx_f, &ny_f, &nz_f, &nz_rz, &ip, &p, &q, &r, &gnx, &gny, &rank, \ - sw_data, lw_data, prcp_data, tas_data, u_data, v_data, patm_data, qatm_data, \ - lai_data, sai_data, z0m_data, displa_data, \ - slope_x_data, slope_y_data, \ - eflx_lh_tot_data, eflx_lwrad_out_data, eflx_sh_tot_data, eflx_soil_grnd_data, \ - qflx_evap_tot_data, qflx_evap_grnd_data, qflx_evap_soi_data, qflx_evap_veg_data, qflx_tran_veg_data, \ - qflx_infl_data, swe_out_data, t_grnd_data, t_soil_data, \ - &clm_dump_interval, &clm_1d_out, &clm_forc_veg, clm_file_dir, &clm_file_dir_length, &clm_bin_out_dir, \ - &write_CLM_binary, &slope_accounting_CLM, &clm_beta_function, &clm_veg_function, &clm_veg_wilting, &clm_veg_fieldc, \ - &clm_res_sat, &clm_irr_type, &clm_irr_cycle, &clm_irr_rate, &clm_irr_start, &clm_irr_stop, \ - &clm_irr_threshold, qirr, qirr_inst, iflag, &clm_irr_thresholdtype, &soi_z, &clm_next, &clm_write_logs, &clm_last_rst, &clm_daily_rst, &clm_water_stress_type, &clm_nlevsoi, &clm_nlevlak); +#define CALL_CLM_LSM(pressure_data, saturation_data, evap_trans_data, top, bottom, porosity_data, \ + dz_mult_data, istep, dt, t, start_time, dx, dy, dz, ix, iy, nx, ny, nz, \ + nx_f, ny_f, nz_f, nz_rz, ip, p, q, r, gnx, gny, rank, \ + sw_data, lw_data, prcp_data, tas_data, u_data, v_data, patm_data, qatm_data, \ + lai_data, sai_data, z0m_data, displa_data, \ + slope_x_data, slope_y_data, \ + eflx_lh_tot_data, eflx_lwrad_out_data, eflx_sh_tot_data, eflx_soil_grnd_data, \ + qflx_evap_tot_data, qflx_evap_grnd_data, qflx_evap_soi_data, qflx_evap_veg_data, qflx_tran_veg_data, \ + qflx_infl_data, swe_out_data, t_grnd_data, t_soil_data, \ + clm_dump_interval, clm_1d_out, clm_forc_veg, clm_file_dir, clm_file_dir_length, clm_bin_out_dir, write_CLM_binary, slope_accounting_CLM, \ + clm_beta_function, clm_veg_function, clm_veg_wilting, clm_veg_fieldc, clm_res_sat, \ + clm_irr_type, clm_irr_cycle, clm_irr_rate, clm_irr_start, clm_irr_stop, \ + clm_irr_threshold, qirr, qirr_inst, iflag, clm_irr_thresholdtype, soi_z, clm_next, clm_write_logs, clm_last_rst, clm_daily_rst, clm_water_stress_type, clm_nlevsoi, clm_nlevlak, \ + clm_snow_partition, clm_tw_threshold, clm_thin_snow_damping, clm_thin_snow_threshold, \ + clm_albedo_scheme, clm_albedo_vis_new, clm_albedo_nir_new, clm_albedo_min, clm_albedo_decay_vis, clm_albedo_decay_nir, clm_albedo_accum_a, clm_albedo_thaw_a) \ + CLM_LSM(pressure_data, saturation_data, evap_trans_data, top, bottom, porosity_data, \ + dz_mult_data, &istep, &dt, &t, &start_time, &dx, &dy, &dz, &ix, &iy, &nx, &ny, &nz, &nx_f, &ny_f, &nz_f, &nz_rz, &ip, &p, &q, &r, &gnx, &gny, &rank, \ + sw_data, lw_data, prcp_data, tas_data, u_data, v_data, patm_data, qatm_data, \ + lai_data, sai_data, z0m_data, displa_data, \ + slope_x_data, slope_y_data, \ + eflx_lh_tot_data, eflx_lwrad_out_data, eflx_sh_tot_data, eflx_soil_grnd_data, \ + qflx_evap_tot_data, qflx_evap_grnd_data, qflx_evap_soi_data, qflx_evap_veg_data, qflx_tran_veg_data, \ + qflx_infl_data, swe_out_data, t_grnd_data, t_soil_data, \ + &clm_dump_interval, &clm_1d_out, &clm_forc_veg, clm_file_dir, &clm_file_dir_length, &clm_bin_out_dir, \ + &write_CLM_binary, &slope_accounting_CLM, &clm_beta_function, &clm_veg_function, &clm_veg_wilting, &clm_veg_fieldc, \ + &clm_res_sat, &clm_irr_type, &clm_irr_cycle, &clm_irr_rate, &clm_irr_start, &clm_irr_stop, \ + &clm_irr_threshold, qirr, qirr_inst, iflag, &clm_irr_thresholdtype, &soi_z, &clm_next, &clm_write_logs, &clm_last_rst, &clm_daily_rst, &clm_water_stress_type, &clm_nlevsoi, &clm_nlevlak, \ + &clm_snow_partition, &clm_tw_threshold, &clm_thin_snow_damping, &clm_thin_snow_threshold, \ + &clm_albedo_scheme, &clm_albedo_vis_new, &clm_albedo_nir_new, &clm_albedo_min, &clm_albedo_decay_vis, &clm_albedo_decay_nir, &clm_albedo_accum_a, &clm_albedo_thaw_a); void CLM_LSM(double *pressure_data, double *saturation_data, double *evap_trans_data, double *top, double *bottom, double *porosity_data, double *dz_mult_data, int *istep, double *dt, double *t, double *start_time, @@ -160,7 +164,10 @@ void CLM_LSM(double *pressure_data, double *saturation_data, double *evap_trans_ int *clm_veg_function, double *clm_veg_wilting, double *clm_veg_fieldc, double *clm_res_sat, int *clm_irr_type, int *clm_irr_cycle, double *clm_irr_rate, double *clm_irr_start, double *clm_irr_stop, double *clm_irr_threshold, double *qirr, double *qirr_inst, double *iflag, int *clm_irr_thresholdtype, int *soi_z, - int *clm_next, int *clm_write_logs, int *clm_last_rst, int *clm_daily_rst, int *clm_water_stress_type, int *clm_nlevsoi, int *clm_nlevlak); + int *clm_next, int *clm_write_logs, int *clm_last_rst, int *clm_daily_rst, int *clm_water_stress_type, int *clm_nlevsoi, int *clm_nlevlak, + int *clm_snow_partition, double *clm_tw_threshold, double *clm_thin_snow_damping, double *clm_thin_snow_threshold, + int *clm_albedo_scheme, double *clm_albedo_vis_new, double *clm_albedo_nir_new, double *clm_albedo_min, + double *clm_albedo_decay_vis, double *clm_albedo_decay_nir, double *clm_albedo_accum_a, double *clm_albedo_thaw_a); /* @RMM CRUNCHFLOW.F90*/ //#define CRUNCHFLOW crunchflow_ diff --git a/pfsimulator/parflow_lib/solver_richards.c b/pfsimulator/parflow_lib/solver_richards.c index c14ea083a..93379fdc5 100644 --- a/pfsimulator/parflow_lib/solver_richards.c +++ b/pfsimulator/parflow_lib/solver_richards.c @@ -197,6 +197,22 @@ typedef struct { double clm_irr_threshold; /* CLM irrigation schedule -- soil moisture threshold for deficit cycle */ int clm_irr_thresholdtype; /* Deficit-based saturation criteria (top, bottom, column avg) */ + /* Snow parameterization options @RMM 2025 */ + int clm_snow_partition; /* CLM snow partition type: 0=linear, 1=wetbulb threshold, 2=wetbulb linear */ + double clm_tw_threshold; /* CLM wetbulb temperature threshold for snow [K] */ + double clm_thin_snow_damping; /* CLM thin snow energy damping factor [0-1] */ + double clm_thin_snow_threshold; /* CLM SWE threshold for damping [kg/m2] */ + + /* Snow albedo parameterization options @RMM 2025 */ + int clm_albedo_scheme; /* CLM albedo scheme: 0=CLM, 1=VIC, 2=Tarboton */ + double clm_albedo_vis_new; /* Fresh snow VIS albedo [0-1] */ + double clm_albedo_nir_new; /* Fresh snow NIR albedo [0-1] */ + double clm_albedo_min; /* Minimum albedo floor [0-1] */ + double clm_albedo_decay_vis; /* VIS decay coefficient [0-1] */ + double clm_albedo_decay_nir; /* NIR decay coefficient [0-1] */ + double clm_albedo_accum_a; /* VIC cold-phase decay base */ + double clm_albedo_thaw_a; /* VIC melt-phase decay base */ + int clm_reuse_count; /* NBE: Number of times to use each CLM input */ int clm_write_logs; /* NBE: Write the processor logs for CLM or not */ int clm_last_rst; /* NBE: Only write/overwrite one rst file or write a lot of them */ @@ -2767,7 +2783,19 @@ AdvanceRichards(PFModule * this_module, double start_time, /* Starting time clm_daily_rst, clm_water_stress_type, public_xtra->clm_nz, - public_xtra->clm_nz); + public_xtra->clm_nz, + public_xtra->clm_snow_partition, + public_xtra->clm_tw_threshold, + public_xtra->clm_thin_snow_damping, + public_xtra->clm_thin_snow_threshold, + public_xtra->clm_albedo_scheme, + public_xtra->clm_albedo_vis_new, + public_xtra->clm_albedo_nir_new, + public_xtra->clm_albedo_min, + public_xtra->clm_albedo_decay_vis, + public_xtra->clm_albedo_decay_nir, + public_xtra->clm_albedo_accum_a, + public_xtra->clm_albedo_thaw_a); break; } @@ -5601,6 +5629,102 @@ SolverRichardsNewPublicXtra(char *name) sprintf(key, "%s.CLM.FieldCapacity", name); public_xtra->clm_veg_fieldc = GetDoubleDefault(key, 1.0); + /* @RMM 2025 Snow parameterization options */ + NameArray snow_switch_na; + snow_switch_na = NA_NewNameArray("CLM WetbulbThreshold WetbulbLinear"); + sprintf(key, "%s.CLM.SnowPartition", name); + switch_name = GetStringDefault(key, "CLM"); + switch_value = NA_NameToIndexExitOnError(snow_switch_na, switch_name, key); + switch (switch_value) + { + case 0: + { + public_xtra->clm_snow_partition = 0; + break; + } + + case 1: + { + public_xtra->clm_snow_partition = 1; + break; + } + + case 2: + { + public_xtra->clm_snow_partition = 2; + break; + } + + default: + { + InputError("Invalid switch value <%s> for key <%s>", switch_name, key); + } + } + NA_FreeNameArray(snow_switch_na); + + sprintf(key, "%s.CLM.WetbulbThreshold", name); + public_xtra->clm_tw_threshold = GetDoubleDefault(key, 274.15); + + sprintf(key, "%s.CLM.ThinSnowDamping", name); + public_xtra->clm_thin_snow_damping = GetDoubleDefault(key, 0.0); + + sprintf(key, "%s.CLM.ThinSnowThreshold", name); + public_xtra->clm_thin_snow_threshold = GetDoubleDefault(key, 50.0); + + /* @RMM 2025 Snow albedo parameterization options */ + NameArray albedo_switch_na; + albedo_switch_na = NA_NewNameArray("CLM VIC Tarboton"); + sprintf(key, "%s.CLM.AlbedoScheme", name); + switch_name = GetStringDefault(key, "CLM"); + switch_value = NA_NameToIndexExitOnError(albedo_switch_na, switch_name, key); + switch (switch_value) + { + case 0: + { + public_xtra->clm_albedo_scheme = 0; + break; + } + + case 1: + { + public_xtra->clm_albedo_scheme = 1; + break; + } + + case 2: + { + public_xtra->clm_albedo_scheme = 2; + break; + } + + default: + { + InputError("Invalid switch value <%s> for key <%s>", switch_name, key); + } + } + NA_FreeNameArray(albedo_switch_na); + + sprintf(key, "%s.CLM.AlbedoVisNew", name); + public_xtra->clm_albedo_vis_new = GetDoubleDefault(key, 0.95); + + sprintf(key, "%s.CLM.AlbedoNirNew", name); + public_xtra->clm_albedo_nir_new = GetDoubleDefault(key, 0.65); + + sprintf(key, "%s.CLM.AlbedoMin", name); + public_xtra->clm_albedo_min = GetDoubleDefault(key, 0.4); + + sprintf(key, "%s.CLM.AlbedoDecayVis", name); + public_xtra->clm_albedo_decay_vis = GetDoubleDefault(key, 0.5); + + sprintf(key, "%s.CLM.AlbedoDecayNir", name); + public_xtra->clm_albedo_decay_nir = GetDoubleDefault(key, 0.2); + + sprintf(key, "%s.CLM.AlbedoAccumA", name); + public_xtra->clm_albedo_accum_a = GetDoubleDefault(key, 0.94); + + sprintf(key, "%s.CLM.AlbedoThawA", name); + public_xtra->clm_albedo_thaw_a = GetDoubleDefault(key, 0.82); + /* IMF Write CLM as Silo (default=False) */ sprintf(key, "%s.WriteSiloCLM", name); switch_name = GetStringDefault(key, "False"); diff --git a/test/correct_output/clm_snow_albedo/clm_snow_albedo.out.alpha.pfb b/test/correct_output/clm_snow_albedo/clm_snow_albedo.out.alpha.pfb new file mode 100644 index 0000000000000000000000000000000000000000..4d9bbe3b627fa7bb59ec74115cb3013747cf4a89 GIT binary patch literal 2100 zcmZQzzzJA^G9b+5(C+|N1f%UAfVm)o5zc}z;Chh79e5zTQF=5SKp`-i4o1_#XgV0p L2czY{&@2Z4Xe~{j literal 0 HcmV?d00001 diff --git a/test/correct_output/clm_snow_albedo/clm_snow_albedo.out.clm_output.00001.C.pfb b/test/correct_output/clm_snow_albedo/clm_snow_albedo.out.clm_output.00001.C.pfb new file mode 100644 index 0000000000000000000000000000000000000000..913ab7f61656e0559f7d99b617fc3972eb97d46a GIT binary patch literal 4700 zcmZQzzzJA^G9WDO(C+|N1f%UAfVm)o5zc}z;Chh79at`Lz345SG34ozsq5XZm)JNA zbvo#Vbe804VT>zr}s^NJxR^tpUF_Ck?Z!M%yuYRx96tI~@Eu z10XKrC_Neu)J+Hb|AT--(XgPOLH#kr={H^we++T@gU~IoKZZE{v3$vMkUxev{c-1o O79pTNhB!Su+9d!I`(ZBt literal 0 HcmV?d00001 diff --git a/test/correct_output/clm_snow_albedo/clm_snow_albedo.out.clm_output.00002.C.pfb b/test/correct_output/clm_snow_albedo/clm_snow_albedo.out.clm_output.00002.C.pfb new file mode 100644 index 0000000000000000000000000000000000000000..a9b8df1c7401283e86b6d5f03337f5ed903e8379 GIT binary patch literal 4700 zcmZQzzzJA^G9WDO(C+|N1f%UAfVm)o5zc}z;Chh79XK*=V~#m!4SBl6+%C_qAaD>B58TIm# z3nF$yo$lCHXT{OWXE(&@ef_*I4$90$m5cPKgl z;xdiWqv1f^bg=(F5d3v08V>Zo(-40Qb=t-i;*TLtHzrtu{V~Mpl{Sa@LH-!x^l?63 O!}mac3~_pRv`YY~9=+HA literal 0 HcmV?d00001 diff --git a/test/correct_output/clm_snow_albedo/clm_snow_albedo.out.clm_output.00003.C.pfb b/test/correct_output/clm_snow_albedo/clm_snow_albedo.out.clm_output.00003.C.pfb new file mode 100644 index 0000000000000000000000000000000000000000..8408cfce56fc0a6e54084c40c6f43f805ea661c1 GIT binary patch literal 4700 zcmZQzzzJA^G9WDO(C+|N1f%UAfVm)o5zc}z;Chh79oWw<%ULnQaLCiemy`?hn`b!; zbvk(EpW8l$xeh~|K9C?${mT51|4^sxHb3go;&6@`^7P6`)8&$N>~^E{;0T8~*N;ZN z6HKrh>U8H>w>ZZ;dUivdwi9tGeY(BDZiv&!b^B0eI~4V#x@$M640U=a#{sh29f}Ts zxXh#UXgE+e9qj)P1RouWh6DX$FT@{1oc^O^0rtlbr~m%E-3Ic<5U2mWx6Awh^v6)A S8y2Wcx(oEj5T}Pny95AFEQ9X= literal 0 HcmV?d00001 diff --git a/test/correct_output/clm_snow_albedo/clm_snow_albedo.out.clm_output.00004.C.pfb b/test/correct_output/clm_snow_albedo/clm_snow_albedo.out.clm_output.00004.C.pfb new file mode 100644 index 0000000000000000000000000000000000000000..e6be9cce25c9251ee2aa886b89ab9d6d0396ace9 GIT binary patch literal 4700 zcmZQzzzJA^G9WDO(C+|N1f%UAfVm)o5zc}z;Chh79oYJMw-hX88uD~;xkEAw_hg5m zPKVe@X|H^%>M+FV1M%l4sKzen9qP2*rt80^E?C?#iTUuscmapOwo(A;C5T}Pny959pUDu!h literal 0 HcmV?d00001 diff --git a/test/correct_output/clm_snow_albedo/clm_snow_albedo.out.clm_output.00005.C.pfb b/test/correct_output/clm_snow_albedo/clm_snow_albedo.out.clm_output.00005.C.pfb new file mode 100644 index 0000000000000000000000000000000000000000..1f5243261b4578bfdab8e9a876e233d1ca110422 GIT binary patch literal 4700 zcmZQzzzJA^G9WDO(C+|N1f%UAfVm)o5zc}z;Chh79hkQ1ioFg#0;La<$cO2J(dhbN z;<#u+>e1zK^NTGS9t3Zl=`hsk;9JKoY<#-JVTjWQ;)SYr?GRfv)M>l*&YovJNp(T# z783a|eJ~nbKTI4KO-MbuJZ}D?v#adS?-jO#(7aHZSUyZ0j7HZF6URjpQjacAh!3-e zkUDgEm_8UyNFJsRMiWwxE{~f(SMa_mfJ(S}B+3gNR2S8lbQF>5? z0~0hphA_SVKM=?|6b%P@feyqUL!7>KVHVgQL!ACpBQqQ1k0DO~4$-#%4fMwlr}r(n QFK|7>p=hYn!=qgS0N=yP_y7O^ literal 0 HcmV?d00001 diff --git a/test/correct_output/clm_snow_albedo/clm_snow_albedo.out.mask.pfb b/test/correct_output/clm_snow_albedo/clm_snow_albedo.out.mask.pfb new file mode 100644 index 0000000000000000000000000000000000000000..caa1ffcc9ae5e14ea209e182ea41063414464787 GIT binary patch literal 2100 zcmZQzzzJA^G9b+5(C+|N1f%UAfVm)o5zc}z;Chh7?LREwpb!{M2czj= NG#!lQgP~Rq004WFLFWJf literal 0 HcmV?d00001 diff --git a/test/correct_output/clm_snow_albedo/clm_snow_albedo.out.perm_x.pfb b/test/correct_output/clm_snow_albedo/clm_snow_albedo.out.perm_x.pfb new file mode 100644 index 0000000000000000000000000000000000000000..614ae8457418bf08b91ae85c3472f747047b5c19 GIT binary patch literal 2100 zcmZQzzzJA^G9b+5(C+|N1f%UAfVm)o5zc}z;Chh7?N81Gfmx&UXgG|fgVB62S`Lhs L1Eb}@&@Bf5p#O&H literal 0 HcmV?d00001 diff --git a/test/correct_output/clm_snow_albedo/clm_snow_albedo.out.perm_y.pfb b/test/correct_output/clm_snow_albedo/clm_snow_albedo.out.perm_y.pfb new file mode 100644 index 0000000000000000000000000000000000000000..614ae8457418bf08b91ae85c3472f747047b5c19 GIT binary patch literal 2100 zcmZQzzzJA^G9b+5(C+|N1f%UAfVm)o5zc}z;Chh7?N81Gfmx&UXgG|fgVB62S`Lhs L1Eb}@&@Bf5p#O&H literal 0 HcmV?d00001 diff --git a/test/correct_output/clm_snow_albedo/clm_snow_albedo.out.perm_z.pfb b/test/correct_output/clm_snow_albedo/clm_snow_albedo.out.perm_z.pfb new file mode 100644 index 0000000000000000000000000000000000000000..614ae8457418bf08b91ae85c3472f747047b5c19 GIT binary patch literal 2100 zcmZQzzzJA^G9b+5(C+|N1f%UAfVm)o5zc}z;Chh7?N81Gfmx&UXgG|fgVB62S`Lhs L1Eb}@&@Bf5p#O&H literal 0 HcmV?d00001 diff --git a/test/correct_output/clm_snow_albedo/clm_snow_albedo.out.porosity.pfb b/test/correct_output/clm_snow_albedo/clm_snow_albedo.out.porosity.pfb new file mode 100644 index 0000000000000000000000000000000000000000..13c2a612151e90033fa181e31aa41d94e2f5e8a5 GIT binary patch literal 2100 zcmZQzzzJA^G9b+5(C+|N1f%UAfVm)o5zc}z;Chh7?QeWN)E}eqZIm7jhtYH}nh!?H Ofzfhcv>X_^|Nj-ES$G|W zIBoy?|39~_fwzY|{rUet-#`^FdkF0brH3B>`Tsu~dQTYGLuh>{O>I8RTo_Gl^9}6J z|Nk7)m)iuTYoT-l8lR9nx_WeeBbs^W@`U(=>~GqCfq}7a&N~h$%|#-glYn}3d2~Ly zeT3xE)g$;X7#OQIoV>grmA-<;N0+~ZCXdcX*H5ZEx_Wf`(apcS{|f`lW#zm4XtW?2 jA6;GmO&*<(uAfwSboJ==qnppa{|`gbWxt7whdK=a2h=1p literal 0 HcmV?d00001 diff --git a/test/correct_output/clm_snow_albedo/clm_snow_albedo.out.press.00002.pfb b/test/correct_output/clm_snow_albedo/clm_snow_albedo.out.press.00002.pfb new file mode 100644 index 0000000000000000000000000000000000000000..bbd842e8be04b3c4fb9342c0fc7f6efa56229efa GIT binary patch literal 2100 zcmdUoze)o^9EA4_F(z*yijN=xu`}#}MPgwSJ8Qw-A`cL;HnojNtgKCakbrG0MD!{J z|8*9zi+5|1+c{<6$Fh*dz~#HSZ)R61@mHLf=~D?$!&wQ`PqR4v3-Y(@CExu09u`gd za&mY6?-`i%wSQC#Gj0iFkIdvfdt~39w8J?yJvPbkY&*7m6v!T#$$R$5zCCG&b7DO* z$^Cxk(z5HBu>*OJ?-E^o_HuqVy|r| z*`p_s_uO-N?(_Y;Z$w|J96wx_EEg@8D7!rO=bc^mm!fZVb?_E6E%yYvytJFwch~*R EAC2*g#{d8T literal 0 HcmV?d00001 diff --git a/test/correct_output/clm_snow_albedo/clm_snow_albedo.out.press.00003.pfb b/test/correct_output/clm_snow_albedo/clm_snow_albedo.out.press.00003.pfb new file mode 100644 index 0000000000000000000000000000000000000000..285dc361e85c4c6c8d479a2a416c9c69dbc7ba39 GIT binary patch literal 2100 zcmbV`y-LGS0EKVGEP}YSyOXcrA|!*G;3f__Hgywmvwes54a6sK?Pf+(9dx%yova8h zI;ri|)_bD;BwTI-$;o%mPhw2z6@5FCdnxH8(~vYCOmXt46mLH-Md$B%64~+T^;#aN zJ><#U3-x_@?&nFxj?Ze{lR#~{Je522#q;LaD>(kJqs_y9J5W0=Pvs7M@w_?qe)-jo zwp!c!@vQEF>HFB%_xa)bkM{k1_qZB|X$^TY_dej^*2#r2m?>a^}u=VJOg zFH`oJ^ZCs4`|PLLn^{>{&3jHSonC?Ii*rw(zPM~(zV`J$b%7 literal 0 HcmV?d00001 diff --git a/test/correct_output/clm_snow_albedo/clm_snow_albedo.out.press.00004.pfb b/test/correct_output/clm_snow_albedo/clm_snow_albedo.out.press.00004.pfb new file mode 100644 index 0000000000000000000000000000000000000000..5df72d5e6a35a46077d8162d84e7c5d6797c1fc6 GIT binary patch literal 2100 zcmdUou}T9$6h$Y+B3KBb*okQC2UyNp7z;(PvCvq?F9=p?tSp6ym4)CRSlEbUZPZfj z6dSu(S-65omYtYnZ;&Hlm@*5ycg~#q-WoIXiGEG@9gmOVUP)=+df5Gx2B-dEF!T35 zj$B?nnBN@vEV+E|CO?ZuJZ;-$Pvi2KVZ-tmeKPljdS9RW z8@Ae%RnEt!)>WS6Uy;5GRRG(ff&*XfX zJeskk<^5)0UZYRuzEJP$bAK~=H&Oj!@dfz`ruVUL-si*jf0?PP$&*Ub&z&N7!Sw2F vqo;e#(~ITRSIUc({6FU_NoGzbKBm`-+$eG#OfP0~o?bj`zr0fW$p7X$N2@~! literal 0 HcmV?d00001 diff --git a/test/correct_output/clm_snow_albedo/clm_snow_albedo.out.press.00005.pfb b/test/correct_output/clm_snow_albedo/clm_snow_albedo.out.press.00005.pfb new file mode 100644 index 0000000000000000000000000000000000000000..9aef45d149a27805398fa1268481fdb7c1d2588b GIT binary patch literal 2100 zcmdUoy-EW?7)2*w6h$mj3I2dZM58IJG&Z}hVP_-e6}0pP?4mCqzCg<8lGqB`SqVZ4 zMGK3KBDiEU*|||p7?yyI3%h5}-1~iFjQ7cZC%tznJP!LMF?i@}?~@z+r9Xq2-{)c8 zc3105o8T7Qm#ZhLC(mM5UpcA0Kj%5yExoj_!+*};( vXeLZBjC*H4)amO+D$38wJ?w`%eYV9w{F#V{{ZOZmZo0WK=EYk3Ax;AT%KI_W literal 0 HcmV?d00001 diff --git a/test/correct_output/clm_snow_albedo/clm_snow_albedo.out.satur.00001.pfb b/test/correct_output/clm_snow_albedo/clm_snow_albedo.out.satur.00001.pfb new file mode 100644 index 0000000000000000000000000000000000000000..e586e46045895d78fe1e933b2fb31864cc69984b GIT binary patch literal 2100 zcmZQzzzJA^G9b+5(C+|N1f%UAfVm)o5zc}z;Chh7?LR zXvX&~WJ$M&&}mSbSUyZ0j7HZF6URjpQjac=n}6L%>8<1&FMCwl6OE59?~Nvp&PUfz zsyw=Sboboqw?LCe=cDT zXvXF4*>k}jLZ8P$qsw1}s)LEc_~`o4$oE4^xMWM%Rz7j*vXMdUX5I^_SS6ZBe@SgL5vFo)4wxpz#UGqpPPjpOF1? P?T>CsSi>j(XQrkx literal 0 HcmV?d00001 diff --git a/test/correct_output/clm_snow_albedo/clm_snow_albedo.out.satur.00003.pfb b/test/correct_output/clm_snow_albedo/clm_snow_albedo.out.satur.00003.pfb new file mode 100644 index 0000000000000000000000000000000000000000..93c3dbc8e607a33c181cc1b67e47fe11d649dd9b GIT binary patch literal 2100 zcmZQzzzJA^G9b+5(C+|N1f%UAfVm)o5zc}z;Chh7?LR zXvXTEWYxAOjn+X^kIqLok5qX=>e0>Dw!dzquy#r^I<1LA99t zXhuJqcJZ1$Y4mk8_2_(b^GKB^q#oV;YxdWT#~s!_0xvFnJgs7mca^=%(xmrrF~55Ly&U6U&FG SgVE^vVdA)GLh8}waq zXhzNayh+F&l@>2fBGK2`}X9~4+-c)mq+KL+e=6uT|I(-bW=+5y_S#m5c&fSnvgs$ebf>s HWdBD10jp1b literal 0 HcmV?d00001 diff --git a/test/correct_output/clm_snow_albedo/clm_snow_albedo.out.specific_storage.pfb b/test/correct_output/clm_snow_albedo/clm_snow_albedo.out.specific_storage.pfb new file mode 100644 index 0000000000000000000000000000000000000000..1297279b9130b6671929aa8adca791975d6420cf GIT binary patch literal 2100 zcmZQzzzJA^G9b+5(C+|N1f%UAfVm)o5zc}z;Chh7?KT|yzF_Oy-cfoq97fZ@Xg(M% O2S&?*(Q;tumID9^V_en% literal 0 HcmV?d00001 diff --git a/test/correct_output/clm_snow_albedo/clm_snow_albedo.out.sres.pfb b/test/correct_output/clm_snow_albedo/clm_snow_albedo.out.sres.pfb new file mode 100644 index 0000000000000000000000000000000000000000..5175c79baa774a4aed3ed1ca995b25e2f6d3b054 GIT binary patch literal 2100 zcmZQzzzJA^G9b+5(C+|N1f%UAfVm)o5zc}z;Chh7?OUoIx~~(d9;HXaVKg0#=7Z64 OV6+?l literal 0 HcmV?d00001 diff --git a/test/correct_output/clm_snow_albedo/clm_snow_albedo.out.ssat.pfb b/test/correct_output/clm_snow_albedo/clm_snow_albedo.out.ssat.pfb new file mode 100644 index 0000000000000000000000000000000000000000..caa1ffcc9ae5e14ea209e182ea41063414464787 GIT binary patch literal 2100 zcmZQzzzJA^G9b+5(C+|N1f%UAfVm)o5zc}z;Chh7?LR04VT>zr}s^NJxR^tpUF_Ck?Z!M%yuYRx96tI~@Eu z10XKrC_Neu)J+Hb|AT--(XgPOLH#kr={H^we++T@gU~IoKZZE{v3$vMkUxev{c-1o O79pTNhB!Su+9d!I`(ZBt literal 0 HcmV?d00001 diff --git a/test/correct_output/clm_snow_defaults/clm_snow_defaults.out.clm_output.00002.C.pfb b/test/correct_output/clm_snow_defaults/clm_snow_defaults.out.clm_output.00002.C.pfb new file mode 100644 index 0000000000000000000000000000000000000000..a9b8df1c7401283e86b6d5f03337f5ed903e8379 GIT binary patch literal 4700 zcmZQzzzJA^G9WDO(C+|N1f%UAfVm)o5zc}z;Chh79XK*=V~#m!4SBl6+%C_qAaD>B58TIm# z3nF$yo$lCHXT{OWXE(&@ef_*I4$90$m5cPKgl z;xdiWqv1f^bg=(F5d3v08V>Zo(-40Qb=t-i;*TLtHzrtu{V~Mpl{Sa@LH-!x^l?63 O!}mac3~_pRv`YY~9=+HA literal 0 HcmV?d00001 diff --git a/test/correct_output/clm_snow_defaults/clm_snow_defaults.out.clm_output.00003.C.pfb b/test/correct_output/clm_snow_defaults/clm_snow_defaults.out.clm_output.00003.C.pfb new file mode 100644 index 0000000000000000000000000000000000000000..8408cfce56fc0a6e54084c40c6f43f805ea661c1 GIT binary patch literal 4700 zcmZQzzzJA^G9WDO(C+|N1f%UAfVm)o5zc}z;Chh79oWw<%ULnQaLCiemy`?hn`b!; zbvk(EpW8l$xeh~|K9C?${mT51|4^sxHb3go;&6@`^7P6`)8&$N>~^E{;0T8~*N;ZN z6HKrh>U8H>w>ZZ;dUivdwi9tGeY(BDZiv&!b^B0eI~4V#x@$M640U=a#{sh29f}Ts zxXh#UXgE+e9qj)P1RouWh6DX$FT@{1oc^O^0rtlbr~m%E-3Ic<5U2mWx6Awh^v6)A S8y2Wcx(oEj5T}Pny95AFEQ9X= literal 0 HcmV?d00001 diff --git a/test/correct_output/clm_snow_defaults/clm_snow_defaults.out.clm_output.00004.C.pfb b/test/correct_output/clm_snow_defaults/clm_snow_defaults.out.clm_output.00004.C.pfb new file mode 100644 index 0000000000000000000000000000000000000000..e6be9cce25c9251ee2aa886b89ab9d6d0396ace9 GIT binary patch literal 4700 zcmZQzzzJA^G9WDO(C+|N1f%UAfVm)o5zc}z;Chh79oYJMw-hX88uD~;xkEAw_hg5m zPKVe@X|H^%>M+FV1M%l4sKzen9qP2*rt80^E?C?#iTUuscmapOwo(A;C5T}Pny959pUDu!h literal 0 HcmV?d00001 diff --git a/test/correct_output/clm_snow_defaults/clm_snow_defaults.out.clm_output.00005.C.pfb b/test/correct_output/clm_snow_defaults/clm_snow_defaults.out.clm_output.00005.C.pfb new file mode 100644 index 0000000000000000000000000000000000000000..1f5243261b4578bfdab8e9a876e233d1ca110422 GIT binary patch literal 4700 zcmZQzzzJA^G9WDO(C+|N1f%UAfVm)o5zc}z;Chh79hkQ1ioFg#0;La<$cO2J(dhbN z;<#u+>e1zK^NTGS9t3Zl=`hsk;9JKoY<#-JVTjWQ;)SYr?GRfv)M>l*&YovJNp(T# z783a|eJ~nbKTI4KO-MbuJZ}D?v#adS?-jO#(7aHZSUyZ0j7HZF6URjpQjacAh!3-e zkUDgEm_8UyNFJsRMiWwxE{~f(SMa_mfJ(S}B+3gNR2S8lbQF>5? z0~0hphA_SVKM=?|6b%P@feyqUL!7>KVHVgQL!ACpBQqQ1k0DO~4$-#%4fMwlr}r(n QFK|7>p=hYn!=qgS0N=yP_y7O^ literal 0 HcmV?d00001 diff --git a/test/correct_output/clm_snow_defaults/clm_snow_defaults.out.mask.pfb b/test/correct_output/clm_snow_defaults/clm_snow_defaults.out.mask.pfb new file mode 100644 index 0000000000000000000000000000000000000000..caa1ffcc9ae5e14ea209e182ea41063414464787 GIT binary patch literal 2100 zcmZQzzzJA^G9b+5(C+|N1f%UAfVm)o5zc}z;Chh7?LREwpb!{M2czj= NG#!lQgP~Rq004WFLFWJf literal 0 HcmV?d00001 diff --git a/test/correct_output/clm_snow_defaults/clm_snow_defaults.out.perm_x.pfb b/test/correct_output/clm_snow_defaults/clm_snow_defaults.out.perm_x.pfb new file mode 100644 index 0000000000000000000000000000000000000000..614ae8457418bf08b91ae85c3472f747047b5c19 GIT binary patch literal 2100 zcmZQzzzJA^G9b+5(C+|N1f%UAfVm)o5zc}z;Chh7?N81Gfmx&UXgG|fgVB62S`Lhs L1Eb}@&@Bf5p#O&H literal 0 HcmV?d00001 diff --git a/test/correct_output/clm_snow_defaults/clm_snow_defaults.out.perm_y.pfb b/test/correct_output/clm_snow_defaults/clm_snow_defaults.out.perm_y.pfb new file mode 100644 index 0000000000000000000000000000000000000000..614ae8457418bf08b91ae85c3472f747047b5c19 GIT binary patch literal 2100 zcmZQzzzJA^G9b+5(C+|N1f%UAfVm)o5zc}z;Chh7?N81Gfmx&UXgG|fgVB62S`Lhs L1Eb}@&@Bf5p#O&H literal 0 HcmV?d00001 diff --git a/test/correct_output/clm_snow_defaults/clm_snow_defaults.out.perm_z.pfb b/test/correct_output/clm_snow_defaults/clm_snow_defaults.out.perm_z.pfb new file mode 100644 index 0000000000000000000000000000000000000000..614ae8457418bf08b91ae85c3472f747047b5c19 GIT binary patch literal 2100 zcmZQzzzJA^G9b+5(C+|N1f%UAfVm)o5zc}z;Chh7?N81Gfmx&UXgG|fgVB62S`Lhs L1Eb}@&@Bf5p#O&H literal 0 HcmV?d00001 diff --git a/test/correct_output/clm_snow_defaults/clm_snow_defaults.out.porosity.pfb b/test/correct_output/clm_snow_defaults/clm_snow_defaults.out.porosity.pfb new file mode 100644 index 0000000000000000000000000000000000000000..13c2a612151e90033fa181e31aa41d94e2f5e8a5 GIT binary patch literal 2100 zcmZQzzzJA^G9b+5(C+|N1f%UAfVm)o5zc}z;Chh7?QeWN)E}eqZIm7jhtYH}nh!?H Ofzfhcv>X_^|Nj-ES$G|W zIBoy?|39~_fwzY|{rUet-#`^FdkF0brH3B>`Tsu~dQTYGLuh>{O>I8RTo_Gl^9}6J z|Nk7)m)iuTYoT-l8lR9nx_WeeBbs^W@`U(=>~GqCfq}7a&N~h$%|#-glYn}3d2~Ly zeT3xE)g$;X7#OQIoV>grmA-<;N0+~ZCXdcX*H5ZEx_Wf`(apcS{|f`lW#zm4XtW?2 jA6;GmO&*<(uAfwSboJ==qnppa{|`gbWxt7whdK=a2h=1p literal 0 HcmV?d00001 diff --git a/test/correct_output/clm_snow_defaults/clm_snow_defaults.out.press.00002.pfb b/test/correct_output/clm_snow_defaults/clm_snow_defaults.out.press.00002.pfb new file mode 100644 index 0000000000000000000000000000000000000000..bbd842e8be04b3c4fb9342c0fc7f6efa56229efa GIT binary patch literal 2100 zcmdUoze)o^9EA4_F(z*yijN=xu`}#}MPgwSJ8Qw-A`cL;HnojNtgKCakbrG0MD!{J z|8*9zi+5|1+c{<6$Fh*dz~#HSZ)R61@mHLf=~D?$!&wQ`PqR4v3-Y(@CExu09u`gd za&mY6?-`i%wSQC#Gj0iFkIdvfdt~39w8J?yJvPbkY&*7m6v!T#$$R$5zCCG&b7DO* z$^Cxk(z5HBu>*OJ?-E^o_HuqVy|r| z*`p_s_uO-N?(_Y;Z$w|J96wx_EEg@8D7!rO=bc^mm!fZVb?_E6E%yYvytJFwch~*R EAC2*g#{d8T literal 0 HcmV?d00001 diff --git a/test/correct_output/clm_snow_defaults/clm_snow_defaults.out.press.00003.pfb b/test/correct_output/clm_snow_defaults/clm_snow_defaults.out.press.00003.pfb new file mode 100644 index 0000000000000000000000000000000000000000..285dc361e85c4c6c8d479a2a416c9c69dbc7ba39 GIT binary patch literal 2100 zcmbV`y-LGS0EKVGEP}YSyOXcrA|!*G;3f__Hgywmvwes54a6sK?Pf+(9dx%yova8h zI;ri|)_bD;BwTI-$;o%mPhw2z6@5FCdnxH8(~vYCOmXt46mLH-Md$B%64~+T^;#aN zJ><#U3-x_@?&nFxj?Ze{lR#~{Je522#q;LaD>(kJqs_y9J5W0=Pvs7M@w_?qe)-jo zwp!c!@vQEF>HFB%_xa)bkM{k1_qZB|X$^TY_dej^*2#r2m?>a^}u=VJOg zFH`oJ^ZCs4`|PLLn^{>{&3jHSonC?Ii*rw(zPM~(zV`J$b%7 literal 0 HcmV?d00001 diff --git a/test/correct_output/clm_snow_defaults/clm_snow_defaults.out.press.00004.pfb b/test/correct_output/clm_snow_defaults/clm_snow_defaults.out.press.00004.pfb new file mode 100644 index 0000000000000000000000000000000000000000..5df72d5e6a35a46077d8162d84e7c5d6797c1fc6 GIT binary patch literal 2100 zcmdUou}T9$6h$Y+B3KBb*okQC2UyNp7z;(PvCvq?F9=p?tSp6ym4)CRSlEbUZPZfj z6dSu(S-65omYtYnZ;&Hlm@*5ycg~#q-WoIXiGEG@9gmOVUP)=+df5Gx2B-dEF!T35 zj$B?nnBN@vEV+E|CO?ZuJZ;-$Pvi2KVZ-tmeKPljdS9RW z8@Ae%RnEt!)>WS6Uy;5GRRG(ff&*XfX zJeskk<^5)0UZYRuzEJP$bAK~=H&Oj!@dfz`ruVUL-si*jf0?PP$&*Ub&z&N7!Sw2F vqo;e#(~ITRSIUc({6FU_NoGzbKBm`-+$eG#OfP0~o?bj`zr0fW$p7X$N2@~! literal 0 HcmV?d00001 diff --git a/test/correct_output/clm_snow_defaults/clm_snow_defaults.out.press.00005.pfb b/test/correct_output/clm_snow_defaults/clm_snow_defaults.out.press.00005.pfb new file mode 100644 index 0000000000000000000000000000000000000000..9aef45d149a27805398fa1268481fdb7c1d2588b GIT binary patch literal 2100 zcmdUoy-EW?7)2*w6h$mj3I2dZM58IJG&Z}hVP_-e6}0pP?4mCqzCg<8lGqB`SqVZ4 zMGK3KBDiEU*|||p7?yyI3%h5}-1~iFjQ7cZC%tznJP!LMF?i@}?~@z+r9Xq2-{)c8 zc3105o8T7Qm#ZhLC(mM5UpcA0Kj%5yExoj_!+*};( vXeLZBjC*H4)amO+D$38wJ?w`%eYV9w{F#V{{ZOZmZo0WK=EYk3Ax;AT%KI_W literal 0 HcmV?d00001 diff --git a/test/correct_output/clm_snow_defaults/clm_snow_defaults.out.satur.00001.pfb b/test/correct_output/clm_snow_defaults/clm_snow_defaults.out.satur.00001.pfb new file mode 100644 index 0000000000000000000000000000000000000000..e586e46045895d78fe1e933b2fb31864cc69984b GIT binary patch literal 2100 zcmZQzzzJA^G9b+5(C+|N1f%UAfVm)o5zc}z;Chh7?LR zXvX&~WJ$M&&}mSbSUyZ0j7HZF6URjpQjac=n}6L%>8<1&FMCwl6OE59?~Nvp&PUfz zsyw=Sboboqw?LCe=cDT zXvXF4*>k}jLZ8P$qsw1}s)LEc_~`o4$oE4^xMWM%Rz7j*vXMdUX5I^_SS6ZBe@SgL5vFo)4wxpz#UGqpPPjpOF1? P?T>CsSi>j(XQrkx literal 0 HcmV?d00001 diff --git a/test/correct_output/clm_snow_defaults/clm_snow_defaults.out.satur.00003.pfb b/test/correct_output/clm_snow_defaults/clm_snow_defaults.out.satur.00003.pfb new file mode 100644 index 0000000000000000000000000000000000000000..93c3dbc8e607a33c181cc1b67e47fe11d649dd9b GIT binary patch literal 2100 zcmZQzzzJA^G9b+5(C+|N1f%UAfVm)o5zc}z;Chh7?LR zXvXTEWYxAOjn+X^kIqLok5qX=>e0>Dw!dzquy#r^I<1LA99t zXhuJqcJZ1$Y4mk8_2_(b^GKB^q#oV;YxdWT#~s!_0xvFnJgs7mca^=%(xmrrF~55Ly&U6U&FG SgVE^vVdA)GLh8}waq zXhzNayh+F&l@>2fBGK2`}X9~4+-c)mq+KL+e=6uT|I(-bW=+5y_S#m5c&fSnvgs$ebf>s HWdBD10jp1b literal 0 HcmV?d00001 diff --git a/test/correct_output/clm_snow_defaults/clm_snow_defaults.out.specific_storage.pfb b/test/correct_output/clm_snow_defaults/clm_snow_defaults.out.specific_storage.pfb new file mode 100644 index 0000000000000000000000000000000000000000..1297279b9130b6671929aa8adca791975d6420cf GIT binary patch literal 2100 zcmZQzzzJA^G9b+5(C+|N1f%UAfVm)o5zc}z;Chh7?KT|yzF_Oy-cfoq97fZ@Xg(M% O2S&?*(Q;tumID9^V_en% literal 0 HcmV?d00001 diff --git a/test/correct_output/clm_snow_defaults/clm_snow_defaults.out.sres.pfb b/test/correct_output/clm_snow_defaults/clm_snow_defaults.out.sres.pfb new file mode 100644 index 0000000000000000000000000000000000000000..5175c79baa774a4aed3ed1ca995b25e2f6d3b054 GIT binary patch literal 2100 zcmZQzzzJA^G9b+5(C+|N1f%UAfVm)o5zc}z;Chh7?OUoIx~~(d9;HXaVKg0#=7Z64 OV6+?l literal 0 HcmV?d00001 diff --git a/test/correct_output/clm_snow_defaults/clm_snow_defaults.out.ssat.pfb b/test/correct_output/clm_snow_defaults/clm_snow_defaults.out.ssat.pfb new file mode 100644 index 0000000000000000000000000000000000000000..caa1ffcc9ae5e14ea209e182ea41063414464787 GIT binary patch literal 2100 zcmZQzzzJA^G9b+5(C+|N1f%UAfVm)o5zc}z;Chh7?LRU zzVQdUp-y)g$#GnJ!(}(b>3!2*r{6QMJ2KR1eW52av0+DfdswvYmVid8tSy&mXyl*2NV|$d3uf2gS+l0t?WkW!4VEKWmJw7 zeBrkn>U77piYp9PneB!+y|16Q({K6Zf}u_$*X=`@?NBr~y_GwA@ldCSavUJL-J$3J zh|4rekA?$v)4~4#K=9Y0XgJXSPDA`L)M*<>h(Crn-5B=+?2jQ%ue6?L0`kWYr;qcz P=~xc*#}KE7N4o?7d|bdV literal 0 HcmV?d00001 diff --git a/test/correct_output/clm_snow_partition/clm_snow_partition.out.clm_output.00003.C.pfb b/test/correct_output/clm_snow_partition/clm_snow_partition.out.clm_output.00003.C.pfb new file mode 100644 index 0000000000000000000000000000000000000000..5cacbb91b3b1717a092a043f4d8aec8e3bc1092b GIT binary patch literal 4700 zcmZQzzzJA^G9WDO(C+|N1f%UAfVm)o5zc}z;Chh79oWy#Y~P-$GUVyvOUh=P$KN^( zbvk(E`x9b4t`0+-K9C?$vdwn$q@hmRZGP04Qpu7sif+bbEOw*x;0T8~*AKJS zs&?27b-MGc$90$m5cPKgl z;xdoYqv1f^bg=(F5PWnf8V>Z2y%2v4ar%#vCfFZCoc{asiXzA#L!AEi-tygApg)E> S-7xn@(;c8chB!Su+9d#be4wQO literal 0 HcmV?d00001 diff --git a/test/correct_output/clm_snow_partition/clm_snow_partition.out.clm_output.00004.C.pfb b/test/correct_output/clm_snow_partition/clm_snow_partition.out.clm_output.00004.C.pfb new file mode 100644 index 0000000000000000000000000000000000000000..f6a1b24203571cb9a7df58924f86ea0682757815 GIT binary patch literal 4700 zcmZQzzzJA^G9WDO(C+|N1f%UAfVm)o5zc}z;Chh79oYJsS=-qPhCE$d?x21A-(`oP zPKVg>al5MtIt+38K>YcpOOqof40YOW)Af%lULRaE@VK>BSG2-Exj1HnOuqTxV45{39uscmM?p8*c|AOAx;mEb_oDWC-k8J literal 0 HcmV?d00001 diff --git a/test/correct_output/clm_snow_partition/clm_snow_partition.out.clm_output.00005.C.pfb b/test/correct_output/clm_snow_partition/clm_snow_partition.out.clm_output.00005.C.pfb new file mode 100644 index 0000000000000000000000000000000000000000..aed1a0a9141b4a52d001bea99352ba734f6992a6 GIT binary patch literal 4700 zcmZQzzzJA^G9WDO(C+|N1f%UAfVm)o5zc}z;Chh79hkPM@Rq(YgVLrrXmoiPADu?m zk1h}6ld2AvKA1UX4#gG?tCuYNKji7)Tf1jiH_JN=ar!{KP=31QuG*na+pTxD+PuD7 z3`&dOpwZ=Fd~_OJKe{}OPpUdx`e5dW*)2Ld$N$g#yHNTL4jNq^#z&{o^`pzf_@t`C zr4MEfOddwV#L;PVc^Ds^#-)!^buj(t=EL}R?Pk2TIi7m0aLCggp{31cvVwL)oz9x4 zJWJR^zz#wy;GogvVSIENT|c@!j8CdMT>4<<2q4$(Lz(SRlzp4$U!mPlr-yPJAiLe6 z=m3bzI!X_!aA1PQ#}KCX{|5qDhoa#?FVKPbV~EqYF7SZ;F~sRl)h`}_{4vDo-yu?A YEEwpb!{M2czj= NG#!lQgP~Rq004WFLFWJf literal 0 HcmV?d00001 diff --git a/test/correct_output/clm_snow_partition/clm_snow_partition.out.perm_x.pfb b/test/correct_output/clm_snow_partition/clm_snow_partition.out.perm_x.pfb new file mode 100644 index 0000000000000000000000000000000000000000..614ae8457418bf08b91ae85c3472f747047b5c19 GIT binary patch literal 2100 zcmZQzzzJA^G9b+5(C+|N1f%UAfVm)o5zc}z;Chh7?N81Gfmx&UXgG|fgVB62S`Lhs L1Eb}@&@Bf5p#O&H literal 0 HcmV?d00001 diff --git a/test/correct_output/clm_snow_partition/clm_snow_partition.out.perm_y.pfb b/test/correct_output/clm_snow_partition/clm_snow_partition.out.perm_y.pfb new file mode 100644 index 0000000000000000000000000000000000000000..614ae8457418bf08b91ae85c3472f747047b5c19 GIT binary patch literal 2100 zcmZQzzzJA^G9b+5(C+|N1f%UAfVm)o5zc}z;Chh7?N81Gfmx&UXgG|fgVB62S`Lhs L1Eb}@&@Bf5p#O&H literal 0 HcmV?d00001 diff --git a/test/correct_output/clm_snow_partition/clm_snow_partition.out.perm_z.pfb b/test/correct_output/clm_snow_partition/clm_snow_partition.out.perm_z.pfb new file mode 100644 index 0000000000000000000000000000000000000000..614ae8457418bf08b91ae85c3472f747047b5c19 GIT binary patch literal 2100 zcmZQzzzJA^G9b+5(C+|N1f%UAfVm)o5zc}z;Chh7?N81Gfmx&UXgG|fgVB62S`Lhs L1Eb}@&@Bf5p#O&H literal 0 HcmV?d00001 diff --git a/test/correct_output/clm_snow_partition/clm_snow_partition.out.porosity.pfb b/test/correct_output/clm_snow_partition/clm_snow_partition.out.porosity.pfb new file mode 100644 index 0000000000000000000000000000000000000000..13c2a612151e90033fa181e31aa41d94e2f5e8a5 GIT binary patch literal 2100 zcmZQzzzJA^G9b+5(C+|N1f%UAfVm)o5zc}z;Chh7?QeWN)E}eqZIm7jhtYH}nh!?H Ofzfhcv>X_^aYBr%$`bg9?h3P_cGVnpH1JwOWOH8kILTHHmj?`N=`=) zZoRMXmugX9UAU}c59EH)xz^+Rsk=`<-MZObAzE_I;o2VNAn)^>bGXKA zf8?jzI~y$*cr0Aju?KP=bB%xPbFTZVpTuW_wp-*WIp=U~4|9{&gXL28TlfI+f$3BRA z?AiXCu_tl=B3a}mxt3!O#2y^XJLewyU^X9{(&KvLISG6c{y(3XgNM7dk0c+*vd`o@ LoWqOmaWC~Rnw}sq literal 0 HcmV?d00001 diff --git a/test/correct_output/clm_snow_partition/clm_snow_partition.out.press.00002.pfb b/test/correct_output/clm_snow_partition/clm_snow_partition.out.press.00002.pfb new file mode 100644 index 0000000000000000000000000000000000000000..bcc3694c4f9f67dff2160d37af809ac7b236382e GIT binary patch literal 2100 zcmdUoJx&5q6ov0#Fo7l5y8$=wCiWI%X(=Qq)^5SpLMvipVnSl?Pvs=K0I;E;qJU^< zOwd9=cnI^(j2v=fhJ*r=;hXb!M@0Q)f0OBx_s{)V2{f&loc?+7Tl5ld{`!8_j@xIO z-L75=9fTI<0Fxz9f5&>9!_KXO}ml8Z1MFI}I>cQ}V$w#U0zPXJlyj~@U4 literal 0 HcmV?d00001 diff --git a/test/correct_output/clm_snow_partition/clm_snow_partition.out.press.00003.pfb b/test/correct_output/clm_snow_partition/clm_snow_partition.out.press.00003.pfb new file mode 100644 index 0000000000000000000000000000000000000000..1a64c62940111fefedc1a3407671dace06936f32 GIT binary patch literal 2100 zcmZQzzzJA^G9b+5(C+|N1f%UAfVm)o5zc}z;Chh79a#VWn|C^15RDc>;}eocS5Iv| zA^Qa#82|rUAo{+2sMGep|NnK#_kKbm{S-|-Iv?FUQsoJ$M>qe8{pbIGy{q?RqtQ8N zd_wZ*>Z#2qWPi5(^Z&orMR@)ulm10OA58uaR2__u%RWN#xb*$DKmY%AXmOw?l=dK! z?*&!o4y9pybp0@SA2jvo_QCYS)Wi7b=HcY;zret1bN_}jnY0T5eK2_^s5%%Qmwklf zap`m3|AK+V{Oxlq8q=0EHs5Oh7Y2SN>0kL!IuA-0qVdt?VSJc67$03fx;%^zqhab{ d`eEWQKDs=N4^s!@=kNc+u4kjRnSspn(7l6qYu2b~bcc`2;pLHWzFxAW=d? zNJ!|QKN{nORbU6qkd3)Zu8;)28@`$Ugs^^`AG7{u)ScRtKsnpQ^-pT|w0+oZe*do= zy{g?*C;y%)y{h-l0+lS63EvOomkx)I-Cjh#nm&{7ut$#DVJd^jH{~^++ zs@@_tDcd}HAbQAP!`wp#ZGHCWfrow}oa?9Zz>mlNA3J!a?S1Iky#4>BIC{yP=lv~@ w4L!>2PpF5?d)$FX2GM7a4A1Z9eclhny*qMT!>onuk(s<_j~umU+u@G;4K*V_ZvX%Q literal 0 HcmV?d00001 diff --git a/test/correct_output/clm_snow_partition/clm_snow_partition.out.press.00005.pfb b/test/correct_output/clm_snow_partition/clm_snow_partition.out.press.00005.pfb new file mode 100644 index 0000000000000000000000000000000000000000..b2c81266f79151b16d05b33f3f4cfbaa41197b81 GIT binary patch literal 2100 zcmdUoy-EW?6oqGnL^lWqZ1MzxHWE;D{b2^5AP^h7bP?=QS=m@w`4ql@q>a<2(@rb| zA%zx#hNKW&GA7x3Hs-*v2sRr?zMT7=dq*knr!Y2?Z^>MnX$jOX(>VDF(tpXHbm#Zn z6x?XN-CD^#12@`u-)fqKrygc`-`;=Cv)*s&Zx^3D+wI(Q;Nr&RRb9{Xn6l5K2cn0Z z#jHL&6L)9xSNHX}de+hNw3ON3iD#mRefG%k=wr@=XYzhW-@D$&#nXZCPbYBYt{3 vXeLZBjC*H4)amO+D$38wJ?w`%eYV9w{F#V{{ZOZmZo0WK=EYk3Ax;AT%KI_W literal 0 HcmV?d00001 diff --git a/test/correct_output/clm_snow_partition/clm_snow_partition.out.satur.00001.pfb b/test/correct_output/clm_snow_partition/clm_snow_partition.out.satur.00001.pfb new file mode 100644 index 0000000000000000000000000000000000000000..8d0b55d862ea01438a408e94e5dfb13577fa6855 GIT binary patch literal 2100 zcmZQzzzJA^G9b+5(C+|N1f%UAfVm)o5zc}z;Chh7?LR zXvX&~WI1gQq0d0+Q)qm2c^Ds^M%NFMhl#`ZxXg#i!)SDUFg~XK>qbg%CEtnIqte1? zd|dJ{bub#H4#tPkq>7{KgXxFSnDS>^H0N!6*lZ7>TcPyO;~(8LxuNEsp8XJ~0kRcA AoB#j- literal 0 HcmV?d00001 diff --git a/test/correct_output/clm_snow_partition/clm_snow_partition.out.satur.00002.pfb b/test/correct_output/clm_snow_partition/clm_snow_partition.out.satur.00002.pfb new file mode 100644 index 0000000000000000000000000000000000000000..9a199137e06024a215260b9169f7daf298f787b2 GIT binary patch literal 2100 zcmZQzzzJA^G9b+5(C+|N1f%UAfVm)o5zc}z;Chh7?LR zXvXF4`5|CWTUwCD<_p+gH&Q&4x-pkTItNWXI-maLoo!LN_dA{49+hTA zXvXTEth#MaTlx--&A)Ab-AG~Wl(+gcrVVIpzP|n07U?gq&Dx-J2b69`p{!^B~Hba@ybrVhq$vp>2iNacI(&Y?~N0Foa}bpQYW literal 0 HcmV?d00001 diff --git a/test/correct_output/clm_snow_partition/clm_snow_partition.out.satur.00004.pfb b/test/correct_output/clm_snow_partition/clm_snow_partition.out.satur.00004.pfb new file mode 100644 index 0000000000000000000000000000000000000000..4fbd1857113cd0f49279c787bec385b101c3bede GIT binary patch literal 2100 zcmZQzzzJA^G9b+5(C+|N1f%UAfVm)o5zc}z;Chh7?LR zXhuJq-gwp?l|F~Y$0ZL_2cu!?V0=O}A@wkMbQ)$ZLj1as{9^8BC+&wieYQn(fx{;y zdkC!tr4`Wl=<=#)>R^0y{V;Q2@-RM(hN(xFhw)+Z==P)QSF%64DSP4;hh`| zXhzNavfS7nLYqKoQ#3xhJWL%f8eKmjJ}z@$;^_9Hn`dl)-AHb6-Q+1ydh(FvpKalv zesV*-J%nz8(luy&booX!bud1l literal 0 HcmV?d00001 diff --git a/test/correct_output/clm_snow_partition/clm_snow_partition.out.ssat.pfb b/test/correct_output/clm_snow_partition/clm_snow_partition.out.ssat.pfb new file mode 100644 index 0000000000000000000000000000000000000000..caa1ffcc9ae5e14ea209e182ea41063414464787 GIT binary patch literal 2100 zcmZQzzzJA^G9b+5(C+|N1f%UAfVm)o5zc}z;Chh7?LR 0: + filename = f"/{run_name}.out.clm_output.{timestep}.C.pfb" + if not pf_test_file( + dir_name + filename, + correct_output_dir_name + filename, + f"Max difference in CLM output for timestep {timestep}", + ): + passed = False + +rm(dir_name) + +if passed: + print(f"{run_name} : PASSED") +else: + print(f"{run_name} : FAILED") + sys.exit(1) diff --git a/test/python/clm/clm_snow_defaults.py b/test/python/clm/clm_snow_defaults.py new file mode 100644 index 000000000..91e73791b --- /dev/null +++ b/test/python/clm/clm_snow_defaults.py @@ -0,0 +1,429 @@ +# ----------------------------------------------------------------------------- +# CLM Snow Defaults - Backward Compatibility Test +# +# This test verifies that when all new CLM snow parameterization keys +# are left at their default values, output is identical to unmodified CLM. +# This ensures backward compatibility for existing workflows. +# +# New keys tested (all at defaults): +# Solver.CLM.SnowPartition = "CLM" +# Solver.CLM.WetbulbThreshold = 274.15 +# Solver.CLM.ThinSnowDamping = 1.0 +# Solver.CLM.ThinSnowThreshold = 50.0 +# Solver.CLM.AlbedoScheme = "CLM" +# Solver.CLM.AlbedoVisNew = 0.95 +# Solver.CLM.AlbedoNirNew = 0.65 +# Solver.CLM.AlbedoMin = 0.4 +# Solver.CLM.AlbedoDecayVis = 0.5 +# Solver.CLM.AlbedoDecayNir = 0.2 +# Solver.CLM.AlbedoAccumA = 0.94 +# Solver.CLM.AlbedoThawA = 0.82 +# ----------------------------------------------------------------------------- + +import sys +import argparse + +from parflow import Run +from parflow.tools.fs import mkdir, cp, get_absolute_path, rm +from parflow.tools.compare import pf_test_file + +run_name = "clm_snow_defaults" +clm = Run(run_name, __file__) + +# ----------------------------------------------------------------------------- +# Making output directories and copying input files +# ----------------------------------------------------------------------------- + +dir_name = get_absolute_path("test_output/" + run_name) +mkdir(dir_name) + +directories = [ + "qflx_evap_grnd", + "eflx_lh_tot", + "qflx_evap_tot", + "qflx_tran_veg", + "correct_output", + "qflx_infl", + "swe_out", + "eflx_lwrad_out", + "t_grnd", + "diag_out", + "qflx_evap_soi", + "eflx_soil_grnd", + "eflx_sh_tot", + "qflx_evap_veg", + "qflx_top_soil", +] + +for directory in directories: + mkdir(dir_name + "/" + directory) + +cp("$PF_SRC/test/tcl/clm/drv_clmin.dat", dir_name) +cp("$PF_SRC/test/tcl/clm/drv_vegm.dat", dir_name) +cp("$PF_SRC/test/tcl/clm/drv_vegp.dat", dir_name) +cp("$PF_SRC/test/tcl/clm/snow_forcing.1hr.txt", dir_name) + +# ----------------------------------------------------------------------------- +# File input version number +# ----------------------------------------------------------------------------- + +clm.FileVersion = 4 + +# ----------------------------------------------------------------------------- +# Process Topology +# ----------------------------------------------------------------------------- + +parser = argparse.ArgumentParser() +parser.add_argument("-p", "--p", default=1) +parser.add_argument("-q", "--q", default=1) +parser.add_argument("-r", "--r", default=1) +args = parser.parse_args() + +clm.Process.Topology.P = args.p +clm.Process.Topology.Q = args.q +clm.Process.Topology.R = args.r + +# ----------------------------------------------------------------------------- +# Computational Grid +# ----------------------------------------------------------------------------- + +clm.ComputationalGrid.Lower.X = 0.0 +clm.ComputationalGrid.Lower.Y = 0.0 +clm.ComputationalGrid.Lower.Z = 0.0 + +clm.ComputationalGrid.DX = 1000.0 +clm.ComputationalGrid.DY = 1000.0 +clm.ComputationalGrid.DZ = 0.5 + +clm.ComputationalGrid.NX = 5 +clm.ComputationalGrid.NY = 5 +clm.ComputationalGrid.NZ = 10 + +# ----------------------------------------------------------------------------- +# The Names of the GeomInputs +# ----------------------------------------------------------------------------- + +clm.GeomInput.Names = "domain_input" + +# ----------------------------------------------------------------------------- +# Domain Geometry Input +# ----------------------------------------------------------------------------- + +clm.GeomInput.domain_input.InputType = "Box" +clm.GeomInput.domain_input.GeomName = "domain" + +# ----------------------------------------------------------------------------- +# Domain Geometry +# ----------------------------------------------------------------------------- + +clm.Geom.domain.Lower.X = 0.0 +clm.Geom.domain.Lower.Y = 0.0 +clm.Geom.domain.Lower.Z = 0.0 + +clm.Geom.domain.Upper.X = 5000.0 +clm.Geom.domain.Upper.Y = 5000.0 +clm.Geom.domain.Upper.Z = 5.0 + +clm.Geom.domain.Patches = "x_lower x_upper y_lower y_upper z_lower z_upper" + +# ----------------------------------------------------------------------------- +# Perm +# ----------------------------------------------------------------------------- + +clm.Geom.Perm.Names = "domain" +clm.Geom.domain.Perm.Type = "Constant" +clm.Geom.domain.Perm.Value = 0.2 + +clm.Perm.TensorType = "TensorByGeom" +clm.Geom.Perm.TensorByGeom.Names = "domain" + +clm.Geom.domain.Perm.TensorValX = 1.0 +clm.Geom.domain.Perm.TensorValY = 1.0 +clm.Geom.domain.Perm.TensorValZ = 1.0 + +# ----------------------------------------------------------------------------- +# Specific Storage +# ----------------------------------------------------------------------------- + +clm.SpecificStorage.Type = "Constant" +clm.SpecificStorage.GeomNames = "domain" +clm.Geom.domain.SpecificStorage.Value = 1.0e-6 + +# ----------------------------------------------------------------------------- +# Phases +# ----------------------------------------------------------------------------- + +clm.Phase.Names = "water" +clm.Phase.water.Density.Type = "Constant" +clm.Phase.water.Density.Value = 1.0 +clm.Phase.water.Viscosity.Type = "Constant" +clm.Phase.water.Viscosity.Value = 1.0 + +# ----------------------------------------------------------------------------- +# Contaminants +# ----------------------------------------------------------------------------- + +clm.Contaminants.Names = "" + +# ----------------------------------------------------------------------------- +# Gravity +# ----------------------------------------------------------------------------- + +clm.Gravity = 1.0 + +# ----------------------------------------------------------------------------- +# Setup timing info +# ----------------------------------------------------------------------------- + +clm.TimingInfo.BaseUnit = 1.0 +clm.TimingInfo.StartCount = 0 +clm.TimingInfo.StartTime = 0.0 +clm.TimingInfo.StopTime = 5 +clm.TimingInfo.DumpInterval = -1 +clm.TimeStep.Type = "Constant" +clm.TimeStep.Value = 1.0 + +# ----------------------------------------------------------------------------- +# Porosity +# ----------------------------------------------------------------------------- + +clm.Geom.Porosity.GeomNames = "domain" +clm.Geom.domain.Porosity.Type = "Constant" +clm.Geom.domain.Porosity.Value = 0.390 + +# ----------------------------------------------------------------------------- +# Domain +# ----------------------------------------------------------------------------- + +clm.Domain.GeomName = "domain" + +# ----------------------------------------------------------------------------- +# Mobility +# ----------------------------------------------------------------------------- + +clm.Phase.water.Mobility.Type = "Constant" +clm.Phase.water.Mobility.Value = 1.0 + +# ----------------------------------------------------------------------------- +# Relative Permeability +# ----------------------------------------------------------------------------- + +clm.Phase.RelPerm.Type = "VanGenuchten" +clm.Phase.RelPerm.GeomNames = "domain" +clm.Geom.domain.RelPerm.Alpha = 3.5 +clm.Geom.domain.RelPerm.N = 2.0 + +# ----------------------------------------------------------------------------- +# Saturation +# ----------------------------------------------------------------------------- + +clm.Phase.Saturation.Type = "VanGenuchten" +clm.Phase.Saturation.GeomNames = "domain" +clm.Geom.domain.Saturation.Alpha = 3.5 +clm.Geom.domain.Saturation.N = 2.0 +clm.Geom.domain.Saturation.SRes = 0.01 +clm.Geom.domain.Saturation.SSat = 1.0 + +# ----------------------------------------------------------------------------- +# Wells +# ----------------------------------------------------------------------------- + +clm.Wells.Names = "" + +# ----------------------------------------------------------------------------- +# Time Cycles +# ----------------------------------------------------------------------------- + +clm.Cycle.Names = "constant" +clm.Cycle.constant.Names = "alltime" +clm.Cycle.constant.alltime.Length = 1 +clm.Cycle.constant.Repeat = -1 + +# ----------------------------------------------------------------------------- +# Boundary Conditions: Pressure +# ----------------------------------------------------------------------------- + +clm.BCPressure.PatchNames = "x_lower x_upper y_lower y_upper z_lower z_upper" + +clm.Patch.x_lower.BCPressure.Type = "FluxConst" +clm.Patch.x_lower.BCPressure.Cycle = "constant" +clm.Patch.x_lower.BCPressure.alltime.Value = 0.0 + +clm.Patch.y_lower.BCPressure.Type = "FluxConst" +clm.Patch.y_lower.BCPressure.Cycle = "constant" +clm.Patch.y_lower.BCPressure.alltime.Value = 0.0 + +clm.Patch.z_lower.BCPressure.Type = "FluxConst" +clm.Patch.z_lower.BCPressure.Cycle = "constant" +clm.Patch.z_lower.BCPressure.alltime.Value = 0.0 + +clm.Patch.x_upper.BCPressure.Type = "FluxConst" +clm.Patch.x_upper.BCPressure.Cycle = "constant" +clm.Patch.x_upper.BCPressure.alltime.Value = 0.0 + +clm.Patch.y_upper.BCPressure.Type = "FluxConst" +clm.Patch.y_upper.BCPressure.Cycle = "constant" +clm.Patch.y_upper.BCPressure.alltime.Value = 0.0 + +clm.Patch.z_upper.BCPressure.Type = "OverlandFlow" +clm.Patch.z_upper.BCPressure.Cycle = "constant" +clm.Patch.z_upper.BCPressure.alltime.Value = 0.0 + +# ----------------------------------------------------------------------------- +# Topo slopes in x-direction +# ----------------------------------------------------------------------------- + +clm.TopoSlopesX.Type = "Constant" +clm.TopoSlopesX.GeomNames = "domain" +clm.TopoSlopesX.Geom.domain.Value = -0.001 + +# ----------------------------------------------------------------------------- +# Topo slopes in y-direction +# ----------------------------------------------------------------------------- + +clm.TopoSlopesY.Type = "Constant" +clm.TopoSlopesY.GeomNames = "domain" +clm.TopoSlopesY.Geom.domain.Value = 0.001 + +# ----------------------------------------------------------------------------- +# Mannings coefficient +# ----------------------------------------------------------------------------- + +clm.Mannings.Type = "Constant" +clm.Mannings.GeomNames = "domain" +clm.Mannings.Geom.domain.Value = 5.52e-6 + +# ----------------------------------------------------------------------------- +# Phase sources +# ----------------------------------------------------------------------------- + +clm.PhaseSources.water.Type = "Constant" +clm.PhaseSources.water.GeomNames = "domain" +clm.PhaseSources.water.Geom.domain.Value = 0.0 + +# ----------------------------------------------------------------------------- +# Exact solution specification for error calculations +# ----------------------------------------------------------------------------- + +clm.KnownSolution = "NoKnownSolution" + +# ----------------------------------------------------------------------------- +# Set solver parameters +# ----------------------------------------------------------------------------- + +clm.Solver = "Richards" +clm.Solver.MaxIter = 500 + +clm.Solver.Nonlinear.MaxIter = 15 +clm.Solver.Nonlinear.ResidualTol = 1e-9 +clm.Solver.Nonlinear.EtaChoice = "EtaConstant" +clm.Solver.Nonlinear.EtaValue = 0.01 +clm.Solver.Nonlinear.UseJacobian = True +clm.Solver.Nonlinear.StepTol = 1e-20 +clm.Solver.Nonlinear.Globalization = "LineSearch" +clm.Solver.Linear.KrylovDimension = 15 +clm.Solver.Linear.MaxRestart = 2 + +clm.Solver.Linear.Preconditioner = "PFMG" +clm.Solver.PrintSubsurf = False +clm.Solver.Drop = 1e-20 +clm.Solver.AbsTol = 1e-9 + +# ----------------------------------------------------------------------------- +# CLM Settings +# ----------------------------------------------------------------------------- + +clm.Solver.LSM = "CLM" +clm.Solver.CLM.MetForcing = "1D" +clm.Solver.CLM.MetFileName = "snow_forcing.1hr.txt" +clm.Solver.CLM.MetFilePath = "." + +clm.Solver.WriteSiloCLM = False +clm.Solver.WriteSiloEvapTrans = False +clm.Solver.WriteSiloOverlandBCFlux = False +clm.Solver.PrintCLM = True + +clm.Solver.CLM.Print1dOut = False +clm.Solver.BinaryOutDir = False +clm.Solver.WriteCLMBinary = False +clm.Solver.CLM.CLMDumpInterval = 1 +clm.Solver.CLM.WriteLogs = False +clm.Solver.CLM.WriteLastRST = True +clm.Solver.CLM.DailyRST = True +clm.Solver.CLM.SingleFile = True + +# ----------------------------------------------------------------------------- +# Snow Parameterization Keys - ALL AT DEFAULTS +# These are explicitly set here to document what we're testing +# ----------------------------------------------------------------------------- + +clm.Solver.CLM.SnowPartition = "CLM" +clm.Solver.CLM.WetbulbThreshold = 274.15 +clm.Solver.CLM.ThinSnowDamping = 1.0 +clm.Solver.CLM.ThinSnowThreshold = 50.0 +clm.Solver.CLM.AlbedoScheme = "CLM" +clm.Solver.CLM.AlbedoVisNew = 0.95 +clm.Solver.CLM.AlbedoNirNew = 0.65 +clm.Solver.CLM.AlbedoMin = 0.4 +clm.Solver.CLM.AlbedoDecayVis = 0.5 +clm.Solver.CLM.AlbedoDecayNir = 0.2 +clm.Solver.CLM.AlbedoAccumA = 0.94 +clm.Solver.CLM.AlbedoThawA = 0.82 + +# ----------------------------------------------------------------------------- +# Initial conditions: water pressure +# ----------------------------------------------------------------------------- + +clm.ICPressure.Type = "HydroStaticPatch" +clm.ICPressure.GeomNames = "domain" +clm.Geom.domain.ICPressure.Value = -2.0 +clm.Geom.domain.ICPressure.RefGeom = "domain" +clm.Geom.domain.ICPressure.RefPatch = "z_upper" + +# ----------------------------------------------------------------------------- +# Run and Unload the ParFlow output files +# ----------------------------------------------------------------------------- + +correct_output_dir_name = get_absolute_path("../../correct_output/" + run_name) + +clm.run(working_directory=dir_name) + +# ----------------------------------------------------------------------------- +# Tests - Compare pressure, saturation, and CLM output +# ----------------------------------------------------------------------------- + +passed = True + +for i in range(6): + timestep = str(i).rjust(5, "0") + filename = f"/{run_name}.out.press.{timestep}.pfb" + if not pf_test_file( + dir_name + filename, + correct_output_dir_name + filename, + f"Max difference in Pressure for timestep {timestep}", + ): + passed = False + filename = f"/{run_name}.out.satur.{timestep}.pfb" + if not pf_test_file( + dir_name + filename, + correct_output_dir_name + filename, + f"Max difference in Saturation for timestep {timestep}", + ): + passed = False + + if i > 0: + filename = f"/{run_name}.out.clm_output.{timestep}.C.pfb" + if not pf_test_file( + dir_name + filename, + correct_output_dir_name + filename, + f"Max difference in CLM output for timestep {timestep}", + ): + passed = False + +rm(dir_name) + +if passed: + print(f"{run_name} : PASSED") +else: + print(f"{run_name} : FAILED") + sys.exit(1) diff --git a/test/python/clm/clm_snow_partition.py b/test/python/clm/clm_snow_partition.py new file mode 100644 index 000000000..7c809c7b9 --- /dev/null +++ b/test/python/clm/clm_snow_partition.py @@ -0,0 +1,410 @@ +# ----------------------------------------------------------------------------- +# CLM Snow Partition Test +# +# This test verifies that the rain-snow partitioning options work correctly. +# It runs with WetbulbThreshold partitioning and compares to reference output. +# +# The wet-bulb partitioning method accounts for evaporative cooling of +# falling hydrometeors, which is important in dry mountain climates where +# snow can persist at air temperatures above 0°C. +# +# Reference: Wang et al. (2019) GRL, doi:10.1029/2019GL085722 +# ----------------------------------------------------------------------------- + +import sys +import argparse + +from parflow import Run +from parflow.tools.fs import mkdir, cp, get_absolute_path, rm +from parflow.tools.compare import pf_test_file + +run_name = "clm_snow_partition" +clm = Run(run_name, __file__) + +# ----------------------------------------------------------------------------- +# Making output directories and copying input files +# ----------------------------------------------------------------------------- + +dir_name = get_absolute_path("test_output/" + run_name) +mkdir(dir_name) + +directories = [ + "qflx_evap_grnd", + "eflx_lh_tot", + "qflx_evap_tot", + "qflx_tran_veg", + "correct_output", + "qflx_infl", + "swe_out", + "eflx_lwrad_out", + "t_grnd", + "diag_out", + "qflx_evap_soi", + "eflx_soil_grnd", + "eflx_sh_tot", + "qflx_evap_veg", + "qflx_top_soil", +] + +for directory in directories: + mkdir(dir_name + "/" + directory) + +cp("$PF_SRC/test/tcl/clm/drv_clmin.dat", dir_name) +cp("$PF_SRC/test/tcl/clm/drv_vegm.dat", dir_name) +cp("$PF_SRC/test/tcl/clm/drv_vegp.dat", dir_name) +cp("$PF_SRC/test/tcl/clm/snow_forcing.1hr.txt", dir_name) + +# ----------------------------------------------------------------------------- +# File input version number +# ----------------------------------------------------------------------------- + +clm.FileVersion = 4 + +# ----------------------------------------------------------------------------- +# Process Topology +# ----------------------------------------------------------------------------- + +parser = argparse.ArgumentParser() +parser.add_argument("-p", "--p", default=1) +parser.add_argument("-q", "--q", default=1) +parser.add_argument("-r", "--r", default=1) +args = parser.parse_args() + +clm.Process.Topology.P = args.p +clm.Process.Topology.Q = args.q +clm.Process.Topology.R = args.r + +# ----------------------------------------------------------------------------- +# Computational Grid +# ----------------------------------------------------------------------------- + +clm.ComputationalGrid.Lower.X = 0.0 +clm.ComputationalGrid.Lower.Y = 0.0 +clm.ComputationalGrid.Lower.Z = 0.0 + +clm.ComputationalGrid.DX = 1000.0 +clm.ComputationalGrid.DY = 1000.0 +clm.ComputationalGrid.DZ = 0.5 + +clm.ComputationalGrid.NX = 5 +clm.ComputationalGrid.NY = 5 +clm.ComputationalGrid.NZ = 10 + +# ----------------------------------------------------------------------------- +# The Names of the GeomInputs +# ----------------------------------------------------------------------------- + +clm.GeomInput.Names = "domain_input" + +# ----------------------------------------------------------------------------- +# Domain Geometry Input +# ----------------------------------------------------------------------------- + +clm.GeomInput.domain_input.InputType = "Box" +clm.GeomInput.domain_input.GeomName = "domain" + +# ----------------------------------------------------------------------------- +# Domain Geometry +# ----------------------------------------------------------------------------- + +clm.Geom.domain.Lower.X = 0.0 +clm.Geom.domain.Lower.Y = 0.0 +clm.Geom.domain.Lower.Z = 0.0 + +clm.Geom.domain.Upper.X = 5000.0 +clm.Geom.domain.Upper.Y = 5000.0 +clm.Geom.domain.Upper.Z = 5.0 + +clm.Geom.domain.Patches = "x_lower x_upper y_lower y_upper z_lower z_upper" + +# ----------------------------------------------------------------------------- +# Perm +# ----------------------------------------------------------------------------- + +clm.Geom.Perm.Names = "domain" +clm.Geom.domain.Perm.Type = "Constant" +clm.Geom.domain.Perm.Value = 0.2 + +clm.Perm.TensorType = "TensorByGeom" +clm.Geom.Perm.TensorByGeom.Names = "domain" + +clm.Geom.domain.Perm.TensorValX = 1.0 +clm.Geom.domain.Perm.TensorValY = 1.0 +clm.Geom.domain.Perm.TensorValZ = 1.0 + +# ----------------------------------------------------------------------------- +# Specific Storage +# ----------------------------------------------------------------------------- + +clm.SpecificStorage.Type = "Constant" +clm.SpecificStorage.GeomNames = "domain" +clm.Geom.domain.SpecificStorage.Value = 1.0e-6 + +# ----------------------------------------------------------------------------- +# Phases +# ----------------------------------------------------------------------------- + +clm.Phase.Names = "water" +clm.Phase.water.Density.Type = "Constant" +clm.Phase.water.Density.Value = 1.0 +clm.Phase.water.Viscosity.Type = "Constant" +clm.Phase.water.Viscosity.Value = 1.0 + +# ----------------------------------------------------------------------------- +# Contaminants +# ----------------------------------------------------------------------------- + +clm.Contaminants.Names = "" + +# ----------------------------------------------------------------------------- +# Gravity +# ----------------------------------------------------------------------------- + +clm.Gravity = 1.0 + +# ----------------------------------------------------------------------------- +# Setup timing info +# ----------------------------------------------------------------------------- + +clm.TimingInfo.BaseUnit = 1.0 +clm.TimingInfo.StartCount = 0 +clm.TimingInfo.StartTime = 0.0 +clm.TimingInfo.StopTime = 5 +clm.TimingInfo.DumpInterval = -1 +clm.TimeStep.Type = "Constant" +clm.TimeStep.Value = 1.0 + +# ----------------------------------------------------------------------------- +# Porosity +# ----------------------------------------------------------------------------- + +clm.Geom.Porosity.GeomNames = "domain" +clm.Geom.domain.Porosity.Type = "Constant" +clm.Geom.domain.Porosity.Value = 0.390 + +# ----------------------------------------------------------------------------- +# Domain +# ----------------------------------------------------------------------------- + +clm.Domain.GeomName = "domain" + +# ----------------------------------------------------------------------------- +# Mobility +# ----------------------------------------------------------------------------- + +clm.Phase.water.Mobility.Type = "Constant" +clm.Phase.water.Mobility.Value = 1.0 + +# ----------------------------------------------------------------------------- +# Relative Permeability +# ----------------------------------------------------------------------------- + +clm.Phase.RelPerm.Type = "VanGenuchten" +clm.Phase.RelPerm.GeomNames = "domain" +clm.Geom.domain.RelPerm.Alpha = 3.5 +clm.Geom.domain.RelPerm.N = 2.0 + +# ----------------------------------------------------------------------------- +# Saturation +# ----------------------------------------------------------------------------- + +clm.Phase.Saturation.Type = "VanGenuchten" +clm.Phase.Saturation.GeomNames = "domain" +clm.Geom.domain.Saturation.Alpha = 3.5 +clm.Geom.domain.Saturation.N = 2.0 +clm.Geom.domain.Saturation.SRes = 0.01 +clm.Geom.domain.Saturation.SSat = 1.0 + +# ----------------------------------------------------------------------------- +# Wells +# ----------------------------------------------------------------------------- + +clm.Wells.Names = "" + +# ----------------------------------------------------------------------------- +# Time Cycles +# ----------------------------------------------------------------------------- + +clm.Cycle.Names = "constant" +clm.Cycle.constant.Names = "alltime" +clm.Cycle.constant.alltime.Length = 1 +clm.Cycle.constant.Repeat = -1 + +# ----------------------------------------------------------------------------- +# Boundary Conditions: Pressure +# ----------------------------------------------------------------------------- + +clm.BCPressure.PatchNames = "x_lower x_upper y_lower y_upper z_lower z_upper" + +clm.Patch.x_lower.BCPressure.Type = "FluxConst" +clm.Patch.x_lower.BCPressure.Cycle = "constant" +clm.Patch.x_lower.BCPressure.alltime.Value = 0.0 + +clm.Patch.y_lower.BCPressure.Type = "FluxConst" +clm.Patch.y_lower.BCPressure.Cycle = "constant" +clm.Patch.y_lower.BCPressure.alltime.Value = 0.0 + +clm.Patch.z_lower.BCPressure.Type = "FluxConst" +clm.Patch.z_lower.BCPressure.Cycle = "constant" +clm.Patch.z_lower.BCPressure.alltime.Value = 0.0 + +clm.Patch.x_upper.BCPressure.Type = "FluxConst" +clm.Patch.x_upper.BCPressure.Cycle = "constant" +clm.Patch.x_upper.BCPressure.alltime.Value = 0.0 + +clm.Patch.y_upper.BCPressure.Type = "FluxConst" +clm.Patch.y_upper.BCPressure.Cycle = "constant" +clm.Patch.y_upper.BCPressure.alltime.Value = 0.0 + +clm.Patch.z_upper.BCPressure.Type = "OverlandFlow" +clm.Patch.z_upper.BCPressure.Cycle = "constant" +clm.Patch.z_upper.BCPressure.alltime.Value = 0.0 + +# ----------------------------------------------------------------------------- +# Topo slopes in x-direction +# ----------------------------------------------------------------------------- + +clm.TopoSlopesX.Type = "Constant" +clm.TopoSlopesX.GeomNames = "domain" +clm.TopoSlopesX.Geom.domain.Value = -0.001 + +# ----------------------------------------------------------------------------- +# Topo slopes in y-direction +# ----------------------------------------------------------------------------- + +clm.TopoSlopesY.Type = "Constant" +clm.TopoSlopesY.GeomNames = "domain" +clm.TopoSlopesY.Geom.domain.Value = 0.001 + +# ----------------------------------------------------------------------------- +# Mannings coefficient +# ----------------------------------------------------------------------------- + +clm.Mannings.Type = "Constant" +clm.Mannings.GeomNames = "domain" +clm.Mannings.Geom.domain.Value = 5.52e-6 + +# ----------------------------------------------------------------------------- +# Phase sources +# ----------------------------------------------------------------------------- + +clm.PhaseSources.water.Type = "Constant" +clm.PhaseSources.water.GeomNames = "domain" +clm.PhaseSources.water.Geom.domain.Value = 0.0 + +# ----------------------------------------------------------------------------- +# Exact solution specification for error calculations +# ----------------------------------------------------------------------------- + +clm.KnownSolution = "NoKnownSolution" + +# ----------------------------------------------------------------------------- +# Set solver parameters +# ----------------------------------------------------------------------------- + +clm.Solver = "Richards" +clm.Solver.MaxIter = 500 + +clm.Solver.Nonlinear.MaxIter = 15 +clm.Solver.Nonlinear.ResidualTol = 1e-9 +clm.Solver.Nonlinear.EtaChoice = "EtaConstant" +clm.Solver.Nonlinear.EtaValue = 0.01 +clm.Solver.Nonlinear.UseJacobian = True +clm.Solver.Nonlinear.StepTol = 1e-20 +clm.Solver.Nonlinear.Globalization = "LineSearch" +clm.Solver.Linear.KrylovDimension = 15 +clm.Solver.Linear.MaxRestart = 2 + +clm.Solver.Linear.Preconditioner = "PFMG" +clm.Solver.PrintSubsurf = False +clm.Solver.Drop = 1e-20 +clm.Solver.AbsTol = 1e-9 + +# ----------------------------------------------------------------------------- +# CLM Settings +# ----------------------------------------------------------------------------- + +clm.Solver.LSM = "CLM" +clm.Solver.CLM.MetForcing = "1D" +clm.Solver.CLM.MetFileName = "snow_forcing.1hr.txt" +clm.Solver.CLM.MetFilePath = "." + +clm.Solver.WriteSiloCLM = False +clm.Solver.WriteSiloEvapTrans = False +clm.Solver.WriteSiloOverlandBCFlux = False +clm.Solver.PrintCLM = True + +clm.Solver.CLM.Print1dOut = False +clm.Solver.BinaryOutDir = False +clm.Solver.WriteCLMBinary = False +clm.Solver.CLM.CLMDumpInterval = 1 +clm.Solver.CLM.WriteLogs = False +clm.Solver.CLM.WriteLastRST = True +clm.Solver.CLM.DailyRST = True +clm.Solver.CLM.SingleFile = True + +# ----------------------------------------------------------------------------- +# Snow Partitioning - Use WetbulbThreshold method +# This exercises the wet-bulb temperature partitioning code path +# ----------------------------------------------------------------------------- + +clm.Solver.CLM.SnowPartition = "WetbulbThreshold" +clm.Solver.CLM.WetbulbThreshold = 274.15 # 1 degree C + +# ----------------------------------------------------------------------------- +# Initial conditions: water pressure +# ----------------------------------------------------------------------------- + +clm.ICPressure.Type = "HydroStaticPatch" +clm.ICPressure.GeomNames = "domain" +clm.Geom.domain.ICPressure.Value = -2.0 +clm.Geom.domain.ICPressure.RefGeom = "domain" +clm.Geom.domain.ICPressure.RefPatch = "z_upper" + +# ----------------------------------------------------------------------------- +# Run and Unload the ParFlow output files +# ----------------------------------------------------------------------------- + +correct_output_dir_name = get_absolute_path("../../correct_output/" + run_name) + +clm.run(working_directory=dir_name) + +# ----------------------------------------------------------------------------- +# Tests - Compare pressure, saturation, and CLM output +# ----------------------------------------------------------------------------- + +passed = True + +for i in range(6): + timestep = str(i).rjust(5, "0") + filename = f"/{run_name}.out.press.{timestep}.pfb" + if not pf_test_file( + dir_name + filename, + correct_output_dir_name + filename, + f"Max difference in Pressure for timestep {timestep}", + ): + passed = False + filename = f"/{run_name}.out.satur.{timestep}.pfb" + if not pf_test_file( + dir_name + filename, + correct_output_dir_name + filename, + f"Max difference in Saturation for timestep {timestep}", + ): + passed = False + + if i > 0: + filename = f"/{run_name}.out.clm_output.{timestep}.C.pfb" + if not pf_test_file( + dir_name + filename, + correct_output_dir_name + filename, + f"Max difference in CLM output for timestep {timestep}", + ): + passed = False + +rm(dir_name) + +if passed: + print(f"{run_name} : PASSED") +else: + print(f"{run_name} : FAILED") + sys.exit(1) diff --git a/test/tcl/clm/snow_forcing.1hr.txt b/test/tcl/clm/snow_forcing.1hr.txt new file mode 100644 index 000000000..45fb67bc3 --- /dev/null +++ b/test/tcl/clm/snow_forcing.1hr.txt @@ -0,0 +1,6 @@ +50.0 280.0 0.0000005 274.0 1.5 -0.5 95000.0 0.0035 +25.0 275.0 0.0000008 273.0 2.0 -1.0 95100.0 0.0030 +0.0 270.0 0.0000010 271.5 1.8 -0.8 95200.0 0.0025 +0.0 268.0 0.0000012 270.0 1.5 -0.5 95300.0 0.0022 +0.0 265.0 0.0000008 269.0 1.2 -0.3 95400.0 0.0020 +25.0 270.0 0.0000005 270.5 1.0 -0.2 95500.0 0.0028 From 0e701738c3cb26875f5ce8845ebe0c30f5892d8a Mon Sep 17 00:00:00 2001 From: reedmaxwell Date: Mon, 2 Feb 2026 16:46:00 -0500 Subject: [PATCH 5/5] Add extended CLM snow parameterization options Feature/clm snow (#698) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Extends CLM snow parameterizations with new rain-snow partitioning methods, SZA-based melt damping, and configurable fractional snow cover. **New Features:** - **Dai (2008) rain-snow partitioning**: Hyperbolic tangent function fitted to 30 years of global observations - **Jennings (2018) rain-snow partitioning**: Bivariate logistic regression using temperature and relative humidity - **SZA-based snow melt damping**: Reduces melt energy at high solar zenith angles where CLM underestimates albedo - **Configurable fractional snow cover**: Adjustable roughness parameter for frac_sno calculation - **Configurable temperature thresholds**: SnowTCrit, SnowTLow, SnowTHigh, SnowTransitionWidth for existing methods **New Keys (16 total):** - `Solver.CLM.SnowTCrit`, `SnowTLow`, `SnowTHigh`, `SnowTransitionWidth` - `Solver.CLM.DaiCoeffA`, `DaiCoeffB`, `DaiCoeffC`, `DaiCoeffD` - `Solver.CLM.JenningsCoeffA`, `JenningsCoeffB`, `JenningsCoeffG` - `Solver.CLM.SZASnowDamping`, `SZADampingCoszenRef`, `SZADampingCoszenMin` - `Solver.CLM.FracSnoScheme`, `FracSnoRoughness` **References:** - Dai (2008) GRL doi:10.1029/2008GL033295 - Jennings et al. (2018) Nature Communications doi:10.1038/s41467-018-03629-7 - Dang et al. (2019) The Cryosphere doi:10.5194/tc-13-2325-2019 ## Test plan - [x] `clm_snow_dai` - Dai sigmoidal partitioning method - [x] `clm_snow_jennings` - Jennings bivariate logistic method - [x] `clm_snow_sza_damping` - SZA-based melt reduction - [x] All existing CLM snow tests pass Run tests with: `ctest -R clm_snow` 🤖 Generated with [Claude Code](https://claude.ai/code) --------- Co-authored-by: Reed Co-authored-by: Claude --- docs/user_manual/keys.rst | 180 +++++++- docs/user_manual/refs.bib | 30 ++ pf-keys/definitions/solver.yaml | 196 +++++++- pfsimulator/clm/clm.F90 | 49 +- pfsimulator/clm/clm_dynvegpar.F90 | 13 +- pfsimulator/clm/clm_hydro_canopy.F90 | 61 ++- pfsimulator/clm/clm_main.F90 | 3 + pfsimulator/clm/clm_meltfreeze.F90 | 62 ++- pfsimulator/clm/clmtype.F90 | 23 +- pfsimulator/clm/drv_getforce.F90 | 5 +- pfsimulator/parflow_lib/parflow_proto_f.h | 19 +- pfsimulator/parflow_lib/solver_richards.c | 121 ++++- .../clm_snow_dai/clm_snow_dai.out.alpha.pfb | Bin 0 -> 2100 bytes .../clm_snow_dai.out.clm_output.00001.C.pfb | Bin 0 -> 4700 bytes .../clm_snow_dai.out.clm_output.00002.C.pfb | Bin 0 -> 4700 bytes .../clm_snow_dai.out.clm_output.00003.C.pfb | Bin 0 -> 4700 bytes .../clm_snow_dai.out.clm_output.00004.C.pfb | Bin 0 -> 4700 bytes .../clm_snow_dai.out.clm_output.00005.C.pfb | Bin 0 -> 4700 bytes .../clm_snow_dai/clm_snow_dai.out.mask.pfb | Bin 0 -> 2100 bytes .../clm_snow_dai/clm_snow_dai.out.n.pfb | Bin 0 -> 2100 bytes .../clm_snow_dai/clm_snow_dai.out.perm_x.pfb | Bin 0 -> 2100 bytes .../clm_snow_dai/clm_snow_dai.out.perm_y.pfb | Bin 0 -> 2100 bytes .../clm_snow_dai/clm_snow_dai.out.perm_z.pfb | Bin 0 -> 2100 bytes .../clm_snow_dai.out.porosity.pfb | Bin 0 -> 2100 bytes .../clm_snow_dai.out.press.00000.pfb | Bin 0 -> 2100 bytes .../clm_snow_dai.out.press.00001.pfb | Bin 0 -> 2100 bytes .../clm_snow_dai.out.press.00002.pfb | Bin 0 -> 2100 bytes .../clm_snow_dai.out.press.00003.pfb | Bin 0 -> 2100 bytes .../clm_snow_dai.out.press.00004.pfb | Bin 0 -> 2100 bytes .../clm_snow_dai.out.press.00005.pfb | Bin 0 -> 2100 bytes .../clm_snow_dai.out.satur.00000.pfb | Bin 0 -> 2100 bytes .../clm_snow_dai.out.satur.00001.pfb | Bin 0 -> 2100 bytes .../clm_snow_dai.out.satur.00002.pfb | Bin 0 -> 2100 bytes .../clm_snow_dai.out.satur.00003.pfb | Bin 0 -> 2100 bytes .../clm_snow_dai.out.satur.00004.pfb | Bin 0 -> 2100 bytes .../clm_snow_dai.out.satur.00005.pfb | Bin 0 -> 2100 bytes .../clm_snow_dai.out.specific_storage.pfb | Bin 0 -> 2100 bytes .../clm_snow_dai/clm_snow_dai.out.sres.pfb | Bin 0 -> 2100 bytes .../clm_snow_dai/clm_snow_dai.out.ssat.pfb | Bin 0 -> 2100 bytes .../clm_snow_jennings.out.alpha.pfb | Bin 0 -> 2100 bytes ...m_snow_jennings.out.clm_output.00001.C.pfb | Bin 0 -> 4700 bytes ...m_snow_jennings.out.clm_output.00002.C.pfb | Bin 0 -> 4700 bytes ...m_snow_jennings.out.clm_output.00003.C.pfb | Bin 0 -> 4700 bytes ...m_snow_jennings.out.clm_output.00004.C.pfb | Bin 0 -> 4700 bytes ...m_snow_jennings.out.clm_output.00005.C.pfb | Bin 0 -> 4700 bytes .../clm_snow_jennings.out.mask.pfb | Bin 0 -> 2100 bytes .../clm_snow_jennings.out.n.pfb | Bin 0 -> 2100 bytes .../clm_snow_jennings.out.perm_x.pfb | Bin 0 -> 2100 bytes .../clm_snow_jennings.out.perm_y.pfb | Bin 0 -> 2100 bytes .../clm_snow_jennings.out.perm_z.pfb | Bin 0 -> 2100 bytes .../clm_snow_jennings.out.porosity.pfb | Bin 0 -> 2100 bytes .../clm_snow_jennings.out.press.00000.pfb | Bin 0 -> 2100 bytes .../clm_snow_jennings.out.press.00001.pfb | Bin 0 -> 2100 bytes .../clm_snow_jennings.out.press.00002.pfb | Bin 0 -> 2100 bytes .../clm_snow_jennings.out.press.00003.pfb | Bin 0 -> 2100 bytes .../clm_snow_jennings.out.press.00004.pfb | Bin 0 -> 2100 bytes .../clm_snow_jennings.out.press.00005.pfb | Bin 0 -> 2100 bytes .../clm_snow_jennings.out.satur.00000.pfb | Bin 0 -> 2100 bytes .../clm_snow_jennings.out.satur.00001.pfb | Bin 0 -> 2100 bytes .../clm_snow_jennings.out.satur.00002.pfb | Bin 0 -> 2100 bytes .../clm_snow_jennings.out.satur.00003.pfb | Bin 0 -> 2100 bytes .../clm_snow_jennings.out.satur.00004.pfb | Bin 0 -> 2100 bytes .../clm_snow_jennings.out.satur.00005.pfb | Bin 0 -> 2100 bytes ...clm_snow_jennings.out.specific_storage.pfb | Bin 0 -> 2100 bytes .../clm_snow_jennings.out.sres.pfb | Bin 0 -> 2100 bytes .../clm_snow_jennings.out.ssat.pfb | Bin 0 -> 2100 bytes .../clm_snow_sza_damping.out.alpha.pfb | Bin 0 -> 2100 bytes ...now_sza_damping.out.clm_output.00001.C.pfb | Bin 0 -> 4700 bytes ...now_sza_damping.out.clm_output.00002.C.pfb | Bin 0 -> 4700 bytes ...now_sza_damping.out.clm_output.00003.C.pfb | Bin 0 -> 4700 bytes ...now_sza_damping.out.clm_output.00004.C.pfb | Bin 0 -> 4700 bytes ...now_sza_damping.out.clm_output.00005.C.pfb | Bin 0 -> 4700 bytes .../clm_snow_sza_damping.out.mask.pfb | Bin 0 -> 2100 bytes .../clm_snow_sza_damping.out.n.pfb | Bin 0 -> 2100 bytes .../clm_snow_sza_damping.out.perm_x.pfb | Bin 0 -> 2100 bytes .../clm_snow_sza_damping.out.perm_y.pfb | Bin 0 -> 2100 bytes .../clm_snow_sza_damping.out.perm_z.pfb | Bin 0 -> 2100 bytes .../clm_snow_sza_damping.out.porosity.pfb | Bin 0 -> 2100 bytes .../clm_snow_sza_damping.out.press.00000.pfb | Bin 0 -> 2100 bytes .../clm_snow_sza_damping.out.press.00001.pfb | Bin 0 -> 2100 bytes .../clm_snow_sza_damping.out.press.00002.pfb | Bin 0 -> 2100 bytes .../clm_snow_sza_damping.out.press.00003.pfb | Bin 0 -> 2100 bytes .../clm_snow_sza_damping.out.press.00004.pfb | Bin 0 -> 2100 bytes .../clm_snow_sza_damping.out.press.00005.pfb | Bin 0 -> 2100 bytes .../clm_snow_sza_damping.out.satur.00000.pfb | Bin 0 -> 2100 bytes .../clm_snow_sza_damping.out.satur.00001.pfb | Bin 0 -> 2100 bytes .../clm_snow_sza_damping.out.satur.00002.pfb | Bin 0 -> 2100 bytes .../clm_snow_sza_damping.out.satur.00003.pfb | Bin 0 -> 2100 bytes .../clm_snow_sza_damping.out.satur.00004.pfb | Bin 0 -> 2100 bytes .../clm_snow_sza_damping.out.satur.00005.pfb | Bin 0 -> 2100 bytes ..._snow_sza_damping.out.specific_storage.pfb | Bin 0 -> 2100 bytes .../clm_snow_sza_damping.out.sres.pfb | Bin 0 -> 2100 bytes .../clm_snow_sza_damping.out.ssat.pfb | Bin 0 -> 2100 bytes test/python/clm/CMakeLists.txt | 5 +- test/python/clm/clm_snow_dai.py | 425 ++++++++++++++++++ test/python/clm/clm_snow_defaults.py | 39 +- test/python/clm/clm_snow_jennings.py | 421 +++++++++++++++++ test/python/clm/clm_snow_sza_damping.py | 420 +++++++++++++++++ 98 files changed, 2015 insertions(+), 57 deletions(-) create mode 100644 test/correct_output/clm_snow_dai/clm_snow_dai.out.alpha.pfb create mode 100644 test/correct_output/clm_snow_dai/clm_snow_dai.out.clm_output.00001.C.pfb create mode 100644 test/correct_output/clm_snow_dai/clm_snow_dai.out.clm_output.00002.C.pfb create mode 100644 test/correct_output/clm_snow_dai/clm_snow_dai.out.clm_output.00003.C.pfb create mode 100644 test/correct_output/clm_snow_dai/clm_snow_dai.out.clm_output.00004.C.pfb create mode 100644 test/correct_output/clm_snow_dai/clm_snow_dai.out.clm_output.00005.C.pfb create mode 100644 test/correct_output/clm_snow_dai/clm_snow_dai.out.mask.pfb create mode 100644 test/correct_output/clm_snow_dai/clm_snow_dai.out.n.pfb create mode 100644 test/correct_output/clm_snow_dai/clm_snow_dai.out.perm_x.pfb create mode 100644 test/correct_output/clm_snow_dai/clm_snow_dai.out.perm_y.pfb create mode 100644 test/correct_output/clm_snow_dai/clm_snow_dai.out.perm_z.pfb create mode 100644 test/correct_output/clm_snow_dai/clm_snow_dai.out.porosity.pfb create mode 100644 test/correct_output/clm_snow_dai/clm_snow_dai.out.press.00000.pfb create mode 100644 test/correct_output/clm_snow_dai/clm_snow_dai.out.press.00001.pfb create mode 100644 test/correct_output/clm_snow_dai/clm_snow_dai.out.press.00002.pfb create mode 100644 test/correct_output/clm_snow_dai/clm_snow_dai.out.press.00003.pfb create mode 100644 test/correct_output/clm_snow_dai/clm_snow_dai.out.press.00004.pfb create mode 100644 test/correct_output/clm_snow_dai/clm_snow_dai.out.press.00005.pfb create mode 100644 test/correct_output/clm_snow_dai/clm_snow_dai.out.satur.00000.pfb create mode 100644 test/correct_output/clm_snow_dai/clm_snow_dai.out.satur.00001.pfb create mode 100644 test/correct_output/clm_snow_dai/clm_snow_dai.out.satur.00002.pfb create mode 100644 test/correct_output/clm_snow_dai/clm_snow_dai.out.satur.00003.pfb create mode 100644 test/correct_output/clm_snow_dai/clm_snow_dai.out.satur.00004.pfb create mode 100644 test/correct_output/clm_snow_dai/clm_snow_dai.out.satur.00005.pfb create mode 100644 test/correct_output/clm_snow_dai/clm_snow_dai.out.specific_storage.pfb create mode 100644 test/correct_output/clm_snow_dai/clm_snow_dai.out.sres.pfb create mode 100644 test/correct_output/clm_snow_dai/clm_snow_dai.out.ssat.pfb create mode 100644 test/correct_output/clm_snow_jennings/clm_snow_jennings.out.alpha.pfb create mode 100644 test/correct_output/clm_snow_jennings/clm_snow_jennings.out.clm_output.00001.C.pfb create mode 100644 test/correct_output/clm_snow_jennings/clm_snow_jennings.out.clm_output.00002.C.pfb create mode 100644 test/correct_output/clm_snow_jennings/clm_snow_jennings.out.clm_output.00003.C.pfb create mode 100644 test/correct_output/clm_snow_jennings/clm_snow_jennings.out.clm_output.00004.C.pfb create mode 100644 test/correct_output/clm_snow_jennings/clm_snow_jennings.out.clm_output.00005.C.pfb create mode 100644 test/correct_output/clm_snow_jennings/clm_snow_jennings.out.mask.pfb create mode 100644 test/correct_output/clm_snow_jennings/clm_snow_jennings.out.n.pfb create mode 100644 test/correct_output/clm_snow_jennings/clm_snow_jennings.out.perm_x.pfb create mode 100644 test/correct_output/clm_snow_jennings/clm_snow_jennings.out.perm_y.pfb create mode 100644 test/correct_output/clm_snow_jennings/clm_snow_jennings.out.perm_z.pfb create mode 100644 test/correct_output/clm_snow_jennings/clm_snow_jennings.out.porosity.pfb create mode 100644 test/correct_output/clm_snow_jennings/clm_snow_jennings.out.press.00000.pfb create mode 100644 test/correct_output/clm_snow_jennings/clm_snow_jennings.out.press.00001.pfb create mode 100644 test/correct_output/clm_snow_jennings/clm_snow_jennings.out.press.00002.pfb create mode 100644 test/correct_output/clm_snow_jennings/clm_snow_jennings.out.press.00003.pfb create mode 100644 test/correct_output/clm_snow_jennings/clm_snow_jennings.out.press.00004.pfb create mode 100644 test/correct_output/clm_snow_jennings/clm_snow_jennings.out.press.00005.pfb create mode 100644 test/correct_output/clm_snow_jennings/clm_snow_jennings.out.satur.00000.pfb create mode 100644 test/correct_output/clm_snow_jennings/clm_snow_jennings.out.satur.00001.pfb create mode 100644 test/correct_output/clm_snow_jennings/clm_snow_jennings.out.satur.00002.pfb create mode 100644 test/correct_output/clm_snow_jennings/clm_snow_jennings.out.satur.00003.pfb create mode 100644 test/correct_output/clm_snow_jennings/clm_snow_jennings.out.satur.00004.pfb create mode 100644 test/correct_output/clm_snow_jennings/clm_snow_jennings.out.satur.00005.pfb create mode 100644 test/correct_output/clm_snow_jennings/clm_snow_jennings.out.specific_storage.pfb create mode 100644 test/correct_output/clm_snow_jennings/clm_snow_jennings.out.sres.pfb create mode 100644 test/correct_output/clm_snow_jennings/clm_snow_jennings.out.ssat.pfb create mode 100644 test/correct_output/clm_snow_sza_damping/clm_snow_sza_damping.out.alpha.pfb create mode 100644 test/correct_output/clm_snow_sza_damping/clm_snow_sza_damping.out.clm_output.00001.C.pfb create mode 100644 test/correct_output/clm_snow_sza_damping/clm_snow_sza_damping.out.clm_output.00002.C.pfb create mode 100644 test/correct_output/clm_snow_sza_damping/clm_snow_sza_damping.out.clm_output.00003.C.pfb create mode 100644 test/correct_output/clm_snow_sza_damping/clm_snow_sza_damping.out.clm_output.00004.C.pfb create mode 100644 test/correct_output/clm_snow_sza_damping/clm_snow_sza_damping.out.clm_output.00005.C.pfb create mode 100644 test/correct_output/clm_snow_sza_damping/clm_snow_sza_damping.out.mask.pfb create mode 100644 test/correct_output/clm_snow_sza_damping/clm_snow_sza_damping.out.n.pfb create mode 100644 test/correct_output/clm_snow_sza_damping/clm_snow_sza_damping.out.perm_x.pfb create mode 100644 test/correct_output/clm_snow_sza_damping/clm_snow_sza_damping.out.perm_y.pfb create mode 100644 test/correct_output/clm_snow_sza_damping/clm_snow_sza_damping.out.perm_z.pfb create mode 100644 test/correct_output/clm_snow_sza_damping/clm_snow_sza_damping.out.porosity.pfb create mode 100644 test/correct_output/clm_snow_sza_damping/clm_snow_sza_damping.out.press.00000.pfb create mode 100644 test/correct_output/clm_snow_sza_damping/clm_snow_sza_damping.out.press.00001.pfb create mode 100644 test/correct_output/clm_snow_sza_damping/clm_snow_sza_damping.out.press.00002.pfb create mode 100644 test/correct_output/clm_snow_sza_damping/clm_snow_sza_damping.out.press.00003.pfb create mode 100644 test/correct_output/clm_snow_sza_damping/clm_snow_sza_damping.out.press.00004.pfb create mode 100644 test/correct_output/clm_snow_sza_damping/clm_snow_sza_damping.out.press.00005.pfb create mode 100644 test/correct_output/clm_snow_sza_damping/clm_snow_sza_damping.out.satur.00000.pfb create mode 100644 test/correct_output/clm_snow_sza_damping/clm_snow_sza_damping.out.satur.00001.pfb create mode 100644 test/correct_output/clm_snow_sza_damping/clm_snow_sza_damping.out.satur.00002.pfb create mode 100644 test/correct_output/clm_snow_sza_damping/clm_snow_sza_damping.out.satur.00003.pfb create mode 100644 test/correct_output/clm_snow_sza_damping/clm_snow_sza_damping.out.satur.00004.pfb create mode 100644 test/correct_output/clm_snow_sza_damping/clm_snow_sza_damping.out.satur.00005.pfb create mode 100644 test/correct_output/clm_snow_sza_damping/clm_snow_sza_damping.out.specific_storage.pfb create mode 100644 test/correct_output/clm_snow_sza_damping/clm_snow_sza_damping.out.sres.pfb create mode 100644 test/correct_output/clm_snow_sza_damping/clm_snow_sza_damping.out.ssat.pfb create mode 100644 test/python/clm/clm_snow_dai.py create mode 100644 test/python/clm/clm_snow_jennings.py create mode 100644 test/python/clm/clm_snow_sza_damping.py diff --git a/docs/user_manual/keys.rst b/docs/user_manual/keys.rst index 5a2ec782c..8f99511ca 100644 --- a/docs/user_manual/keys.rst +++ b/docs/user_manual/keys.rst @@ -6357,23 +6357,77 @@ Reference: :cite:p:`Wang2019`. *string* **Solver.CLM.SnowPartition** CLM Selects the method for partitioning precipitation into rain and snow. The valid types for -this key are **CLM**, **WetbulbThreshold**, **WetbulbLinear**. +this key are **CLM**, **WetbulbThreshold**, **WetbulbLinear**, **Dai**, **Jennings**. **CLM**: Standard air temperature threshold with linear transition (default). + Uses configurable SnowTLow and SnowTHigh thresholds. **WetbulbThreshold**: Sharp threshold at wet-bulb temperature. Better for dry mountain climates. **WetbulbLinear**: Linear transition around wet-bulb temperature threshold. + Uses configurable SnowTransitionWidth for transition zone. + +**Dai**: + Sigmoidal function of air temperature from :cite:p:`Dai2008`. + Uses configurable DaiCoeffA/B/C/D coefficients. + +**Jennings**: + Bivariate logistic regression with air temperature and relative humidity + from :cite:p:`Jennings2018`. Uses configurable JenningsCoeffA/B/G coefficients. + +.. container:: list + + :: + + pfset Solver.CLM.SnowPartition "Dai" ## TCL syntax + .Solver.CLM.SnowPartition = "Dai" ## Python syntax + +*double* **Solver.CLM.SnowTCrit** 2.5 Initial classification threshold +above freezing (K) for determining precipitation type in drv_getforce. +If air temperature exceeds tfrz + SnowTCrit, precipitation is initially +classified as rain. Default 2.5 K matches the hardcoded CLM value. + +.. container:: list + + :: + + pfset Solver.CLM.SnowTCrit 2.5 ## TCL syntax + .Solver.CLM.SnowTCrit = 2.5 ## Python syntax + +*double* **Solver.CLM.SnowTLow** 273.16 CLM method lower temperature +threshold (K) below which all precipitation is snow. Default 273.16 K (freezing). .. container:: list :: - pfset Solver.CLM.SnowPartition "WetbulbThreshold" ## TCL syntax - .Solver.CLM.SnowPartition = "WetbulbThreshold" ## Python syntax + pfset Solver.CLM.SnowTLow 273.16 ## TCL syntax + .Solver.CLM.SnowTLow = 273.16 ## Python syntax + +*double* **Solver.CLM.SnowTHigh** 275.16 CLM method upper temperature +threshold (K) above which the liquid fraction reaches maximum (40%). +Default 275.16 K (tfrz + 2). + +.. container:: list + + :: + + pfset Solver.CLM.SnowTHigh 275.16 ## TCL syntax + .Solver.CLM.SnowTHigh = 275.16 ## Python syntax + +*double* **Solver.CLM.SnowTransitionWidth** 1.0 WetbulbLinear method +half-width (K) of transition zone. Transition spans threshold +/- this value. +Default 1.0 K for 2K total range. + +.. container:: list + + :: + + pfset Solver.CLM.SnowTransitionWidth 1.0 ## TCL syntax + .Solver.CLM.SnowTransitionWidth = 1.0 ## Python syntax *double* **Solver.CLM.WetbulbThreshold** 274.15 Threshold temperature in Kelvin for wetbulb partitioning methods. Default 274.15 K (1°C). Only @@ -6387,6 +6441,48 @@ used when ``Solver.CLM.SnowPartition`` is ``WetbulbThreshold`` or pfset Solver.CLM.WetbulbThreshold 274.15 ## TCL syntax .Solver.CLM.WetbulbThreshold = 274.15 ## Python syntax +**Dai Coefficients** + +The Dai (2008) method uses a hyperbolic tangent function fitted to 30 years +of global weather station observations. Reference: :cite:p:`Dai2008`. + +Formula: F(%) = a * [tanh(b*(T-c)) - d], where T is in Celsius. +Defaults are from Table 1a (Land, annual) of Dai (2008). + +*double* **Solver.CLM.DaiCoeffA** -48.2292 Dai coefficient a (scaling factor, negative). + +*double* **Solver.CLM.DaiCoeffB** 0.7205 Dai coefficient b (slope at half-frequency T). + +*double* **Solver.CLM.DaiCoeffC** 1.1662 Dai coefficient c (half-frequency temperature in C, +where snow probability is ~50%). + +*double* **Solver.CLM.DaiCoeffD** 1.0223 Dai coefficient d (asymmetry parameter, ~1.0). + +.. container:: list + + :: + + pfset Solver.CLM.DaiCoeffA -48.2292 ## TCL syntax + .Solver.CLM.DaiCoeffA = -48.2292 ## Python syntax + +**Jennings Coefficients** + +The Jennings (2018) method uses bivariate logistic regression: +psnow = 1 / (1 + exp(a + b*T + g*RH)), where T is in Celsius and RH in percent. + +*double* **Solver.CLM.JenningsCoeffA** -10.04 Jennings intercept coefficient. + +*double* **Solver.CLM.JenningsCoeffB** 1.41 Jennings temperature coefficient. + +*double* **Solver.CLM.JenningsCoeffG** 0.09 Jennings relative humidity coefficient. + +.. container:: list + + :: + + pfset Solver.CLM.JenningsCoeffA -10.04 ## TCL syntax + .Solver.CLM.JenningsCoeffA = -10.04 ## Python syntax + **Thin Snow Damping** Thin early-season snowpacks can experience spurious melt due to warm @@ -6415,6 +6511,52 @@ threshold in mm below which thin snow damping applies. pfset Solver.CLM.ThinSnowThreshold 50.0 ## TCL syntax .Solver.CLM.ThinSnowThreshold = 50.0 ## Python syntax +**SZA-Based Snow Damping** + +CLM's narrowband snow optical parameters are computed assuming a solar +zenith angle (SZA) of 60 degrees. At higher zenith angles, actual snow +albedo is higher than CLM assumes, meaning less energy should be available +for melt. This is particularly relevant for high-latitude sites during +accumulation season. Reference: :cite:p:`Dang2019`. + +*double* **Solver.CLM.SZASnowDamping** 1.0 Fraction of melt energy +retained at high solar zenith angles. A value of 1.0 means no damping +(default, disabled). A value of 0.8 means 20% energy reduction at high SZA. +Damping varies linearly with cosine of zenith angle between the reference +and minimum thresholds. + +.. container:: list + + :: + + pfset Solver.CLM.SZASnowDamping 0.8 ## TCL syntax + .Solver.CLM.SZASnowDamping = 0.8 ## Python syntax + +*double* **Solver.CLM.SZADampingCoszenRef** 0.5 Reference cosine of solar +zenith angle below which SZA damping applies. Default 0.5 corresponds to +SZA of 60 degrees (matching CLM's assumption for optical parameters). + +.. container:: list + + :: + + pfset Solver.CLM.SZADampingCoszenRef 0.5 ## TCL syntax + .Solver.CLM.SZADampingCoszenRef = 0.5 ## Python syntax + +*double* **Solver.CLM.SZADampingCoszenMin** 0.1 Cosine of solar zenith +angle at which maximum SZA damping applies. Default 0.1 corresponds to +SZA of approximately 84 degrees. Must be less than SZADampingCoszenRef. + +.. container:: list + + :: + + pfset Solver.CLM.SZADampingCoszenMin 0.1 ## TCL syntax + .Solver.CLM.SZADampingCoszenMin = 0.1 ## Python syntax + +Note: Both thin snow damping and SZA damping can be enabled simultaneously. +When both are active, they combine multiplicatively. + **Albedo Schemes** Snow albedo controls the radiation balance and strongly influences melt @@ -6512,6 +6654,38 @@ snow faster. pfset Solver.CLM.AlbedoThawA 0.82 ## TCL syntax .Solver.CLM.AlbedoThawA = 0.82 ## Python syntax +**Fractional Snow Cover** + +The fractional snow covered area (frac_sno) affects surface energy balance +by weighting snow and bare ground contributions. The CLM formulation uses +a tanh-like relationship with snow depth and surface roughness. + +*string* **Solver.CLM.FracSnoScheme** CLM Selects the fractional snow cover +calculation method. Currently only CLM is available; extensible for future +formulations. + +**CLM**: + Standard formulation: frac_sno = snowdp / (10*roughness + snowdp) + +.. container:: list + + :: + + pfset Solver.CLM.FracSnoScheme "CLM" ## TCL syntax + .Solver.CLM.FracSnoScheme = "CLM" ## Python syntax + +*double* **Solver.CLM.FracSnoRoughness** 0.01 Roughness length scale for +fractional snow cover calculation [m]. Default 0.01 m matches CLM's zlnd +parameter for backward compatibility. Larger values reduce snow cover +fraction for a given snow depth. + +.. container:: list + + :: + + pfset Solver.CLM.FracSnoRoughness 0.01 ## TCL syntax + .Solver.CLM.FracSnoRoughness = 0.01 ## Python syntax + .. _ParFlow NetCDF4 Parallel I/O: diff --git a/docs/user_manual/refs.bib b/docs/user_manual/refs.bib index 623389017..e34e9bf7f 100644 --- a/docs/user_manual/refs.bib +++ b/docs/user_manual/refs.bib @@ -256,6 +256,16 @@ @article{Cui14 doi = {10.1016/j.cageo.2014.05.005}, } +@article{Dai2008, +author = {Dai, A.}, +journal = {Geophysical Research Letters}, +pages = {L12802}, +title = {Temperature and pressure dependence of the rain-snow phase transition over land and ocean}, +volume = {35}, +year = {2008}, +doi = {10.1029/2008GL033295}, +} + @article{Dai03, author = {Dai, Y. and X. Zeng and R. E. Dickinson and I. Baker and G. B. Bonan and M. G. Bosilovich and A. S. Denning and P. A. Dirmeyer and P. R., G. Niu and K. W. Oleson and C. A. Schlosser and Z. L. Yang}, journal = {The Bulletin of the American Meteorological Society}, @@ -277,6 +287,16 @@ @article{Danesh-Yazdi2018 url = {https://doi.org/10.1002/hyp.11481}, } +@article{Dang2019, +author = {Dang, C. and Zender, C. S. and Flanner, M. G.}, +journal = {The Cryosphere}, +pages = {2325--2343}, +title = {Intercomparison and improvement of two-stream shortwave radiative transfer schemes in Earth system models for a unified treatment of cryospheric surfaces}, +volume = {13}, +year = {2019}, +doi = {10.5194/tc-13-2325-2019}, +} + @article{DMC10, author = {Daniels, M. H. and Maxwell, R. M. and Chow, F. K.}, journal = {Journal of Hydrologic Engineering}, @@ -557,6 +577,16 @@ @article{Jefferson2017 url = {https://doi.org/10.1175/jhm-d-16-0053.1}, } +@article{Jennings2018, +author = {Jennings, K. S. and Winchell, T. S. and Livneh, B. and Molotch, N. P.}, +journal = {Nature Communications}, +pages = {2831}, +title = {Spatial variation of the rain–snow temperature threshold across the Northern Hemisphere}, +volume = {9}, +year = {2018}, +doi = {10.1038/s41467-018-03629-7}, +} + @article{Jones-Woodward01, author = {Jones, J. E. and Woodward, C. S.}, journal = {Advances in Water Resources}, diff --git a/pf-keys/definitions/solver.yaml b/pf-keys/definitions/solver.yaml index 2a1305dc6..d8cf67bf3 100644 --- a/pf-keys/definitions/solver.yaml +++ b/pf-keys/definitions/solver.yaml @@ -502,7 +502,10 @@ Solver: help: > [Type: string] Selects the rain-snow partitioning method. CLM uses air temperature threshold, while wetbulb methods account for evaporative - cooling of falling hydrometeors. Reference: Wang et al. (2019) GRL. + cooling of falling hydrometeors. Dai (2008) uses a sigmoidal function + of air temperature. Jennings (2018) uses bivariate logistic regression + with air temperature and relative humidity. + References: Wang et al. (2019) GRL, Dai (2008) JAMC, Jennings et al. (2018) Nat Commun. default: CLM domains: EnumDomain: @@ -510,6 +513,134 @@ Solver: - CLM - WetbulbThreshold - WetbulbLinear + - Dai + - Jennings + RequiresModule: CLM + + SnowTCrit: + help: > + [Type: double] Initial classification threshold above tfrz (K) for determining + precipitation type in drv_getforce. Default 2.5 K matches hardcoded CLM value. + If T > tfrz + SnowTCrit, precipitation is initially classified as rain. + Further partitioning occurs in clm_hydro_canopy based on SnowPartition method. + default: 2.5 + domains: + DoubleValue: + min_value: 0.0 + max_value: 5.0 + RequiresModule: CLM + + SnowTLow: + help: > + [Type: double] CLM method lower temperature threshold (K) below which all + precipitation is snow. Default 273.16 K (freezing point). + default: 273.16 + domains: + DoubleValue: + min_value: 268.16 + max_value: 278.16 + RequiresModule: CLM + + SnowTHigh: + help: > + [Type: double] CLM method upper temperature threshold (K) above which + liquid fraction reaches maximum (40%). Default 275.16 K (tfrz + 2). + default: 275.16 + domains: + DoubleValue: + min_value: 270.16 + max_value: 280.16 + RequiresModule: CLM + + SnowTransitionWidth: + help: > + [Type: double] WetbulbLinear method half-width (K) of transition zone. + Transition spans threshold +/- this value. Default 1.0 K for 2K total range. + default: 1.0 + domains: + DoubleValue: + min_value: 0.1 + max_value: 5.0 + RequiresModule: CLM + + DaiCoeffA: + help: > + [Type: double] Dai (2008) coefficient a (scaling factor, negative). + F(%) = a * [tanh(b*(T-c)) - d]. Default -48.2292 from Table 1a (Land, ANN). + Reference: Dai (2008) GRL doi:10.1029/2008GL033295 + default: -48.2292 + domains: + DoubleValue: + min_value: -100.0 + max_value: 0.0 + RequiresModule: CLM + + DaiCoeffB: + help: > + [Type: double] Dai (2008) coefficient b (slope at half-frequency T). + F(%) = a * [tanh(b*(T-c)) - d]. Default 0.7205 from Table 1a (Land, ANN). + default: 0.7205 + domains: + DoubleValue: + min_value: 0.1 + max_value: 2.0 + RequiresModule: CLM + + DaiCoeffC: + help: > + [Type: double] Dai (2008) coefficient c (half-frequency temperature in C). + F(%) = a * [tanh(b*(T-c)) - d]. Default 1.1662 C from Table 1a (Land, ANN). + This is the temperature at which snow probability is approximately 50%. + default: 1.1662 + domains: + DoubleValue: + min_value: -2.0 + max_value: 5.0 + RequiresModule: CLM + + DaiCoeffD: + help: > + [Type: double] Dai (2008) coefficient d (asymmetry parameter). + F(%) = a * [tanh(b*(T-c)) - d]. Default 1.0223 from Table 1a (Land, ANN). + Values close to 1.0 indicate symmetric transition. + default: 1.0223 + domains: + DoubleValue: + min_value: 0.5 + max_value: 1.5 + RequiresModule: CLM + + JenningsCoeffA: + help: > + [Type: double] Jennings et al. (2018) bivariate logistic intercept. + psnow = 1 / (1 + exp(a + b*T + g*RH)). Default from Jennings Table 1. + default: -10.04 + domains: + DoubleValue: + min_value: -20.0 + max_value: 0.0 + RequiresModule: CLM + + JenningsCoeffB: + help: > + [Type: double] Jennings et al. (2018) bivariate logistic temperature coefficient. + psnow = 1 / (1 + exp(a + b*T + g*RH)). T in Celsius. Default from Jennings Table 1. + default: 1.41 + domains: + DoubleValue: + min_value: 0.1 + max_value: 3.0 + RequiresModule: CLM + + JenningsCoeffG: + help: > + [Type: double] Jennings et al. (2018) bivariate logistic relative humidity coefficient. + psnow = 1 / (1 + exp(a + b*T + g*RH)). RH in percent. Default from Jennings Table 1. + default: 0.09 + domains: + DoubleValue: + min_value: 0.01 + max_value: 0.5 RequiresModule: CLM WetbulbThreshold: @@ -546,6 +677,44 @@ Solver: max_value: 500.0 RequiresModule: CLM + SZASnowDamping: + help: > + [Type: double] Fraction of melt energy retained at high solar zenith angles. + 1.0 = no damping (default, disabled), 0.8 = 20% energy reduction at high SZA. + Compensates for CLM underestimating albedo at SZA > 60 degrees. + Physical basis: CLM narrowband parameters assume SZA=60 deg. + Reference: Dang et al. (2019) The Cryosphere. + default: 1.0 + domains: + DoubleValue: + min_value: 0.0 + max_value: 1.0 + RequiresModule: CLM + + SZADampingCoszenRef: + help: > + [Type: double] Reference cosine of solar zenith angle below which SZA damping + applies. Default 0.5 corresponds to SZA=60 degrees (CLM assumption). + Damping scales linearly from 1.0 at this coszen to SZASnowDamping at CoszenMin. + default: 0.5 + domains: + DoubleValue: + min_value: 0.2 + max_value: 1.0 + RequiresModule: CLM + + SZADampingCoszenMin: + help: > + [Type: double] Cosine of solar zenith angle at which maximum SZA damping applies. + Default 0.1 corresponds to SZA of approximately 84 degrees. + Must be less than SZADampingCoszenRef. + default: 0.1 + domains: + DoubleValue: + min_value: 0.0 + max_value: 0.5 + RequiresModule: CLM + AlbedoScheme: help: > [Type: string] Snow albedo calculation method. CLM uses age-based decay, @@ -634,6 +803,31 @@ Solver: max_value: 0.95 RequiresModule: CLM + FracSnoScheme: + help: > + [Type: string] Selects the fractional snow covered area (frac_sno) calculation method. + CLM uses the standard formulation: frac_sno = snowdp / (10*roughness + snowdp). + Additional formulations may be added in future versions. + default: CLM + domains: + EnumDomain: + enum_list: + - CLM + RequiresModule: CLM + + FracSnoRoughness: + help: > + [Type: double] Roughness length scale for fractional snow cover calculation [m]. + Default 0.01 m matches CLM's zlnd parameter for backward compatibility. + Larger values reduce snow cover fraction for a given snow depth. + Formula: frac_sno = snowdp / (10*FracSnoRoughness + snowdp) + default: 0.01 + domains: + DoubleValue: + min_value: 0.001 + max_value: 0.1 + RequiresModule: CLM + # ----------------------------------------------------------------------------- # CLM input variables to write drv_clmin.dat (Input) and drv_vegp.dat (VegParams) files # ----------------------------------------------------------------------------- diff --git a/pfsimulator/clm/clm.F90 b/pfsimulator/clm/clm.F90 index 85a7a83fa..4ae52c07c 100644 --- a/pfsimulator/clm/clm.F90 +++ b/pfsimulator/clm/clm.F90 @@ -12,8 +12,12 @@ subroutine clm_lsm(pressure,saturation,evap_trans,top,bottom,porosity,pf_dz_mult qirr_pf,qirr_inst_pf,irr_flag_pf,irr_thresholdtypepf,soi_z,clm_next,clm_write_logs, & clm_last_rst,clm_daily_rst,rz_water_stress_typepf, pf_nlevsoi, pf_nlevlak, & snow_partition_typepf,tw_thresholdpf,thin_snow_dampingpf,thin_snow_thresholdpf, & +snow_tcritpf,snow_t_lowpf,snow_t_highpf,snow_transition_widthpf, & +dai_apf,dai_bpf,dai_cpf,dai_dpf,jennings_apf,jennings_bpf,jennings_gpf, & +sza_snow_dampingpf,sza_damping_coszen_refpf,sza_damping_coszen_minpf, & albedo_schemepf,albedo_vis_newpf,albedo_nir_newpf,albedo_minpf, & -albedo_decay_vispf,albedo_decay_nirpf,albedo_accum_apf,albedo_thaw_apf) +albedo_decay_vispf,albedo_decay_nirpf,albedo_accum_apf,albedo_thaw_apf, & +frac_sno_typepf,frac_sno_roughnesspf) !========================================================================= ! @@ -151,10 +155,26 @@ subroutine clm_lsm(pressure,saturation,evap_trans,top,bottom,porosity,pf_dz_mult integer :: irr_thresholdtypepf ! irrigation threshold criteria type -- top layer, bottom layer, column avg ! snow parameterization keys @RMM 2025 - integer :: snow_partition_typepf ! rain-snow partition: 0=CLM, 1=wetbulb threshold, 2=wetbulb linear + integer :: snow_partition_typepf ! rain-snow partition: 0=CLM, 1=wb thresh, 2=wb lin, 3=Dai, 4=Jennings real(r8) :: tw_thresholdpf ! wetbulb temperature threshold for snow [K] real(r8) :: thin_snow_dampingpf ! thin snow energy damping factor [0-1] real(r8) :: thin_snow_thresholdpf ! SWE threshold for damping [kg/m2] + real(r8) :: snow_tcritpf ! initial T classification threshold above tfrz [K], default 2.5 + real(r8) :: snow_t_lowpf ! CLM method lower T threshold [K], default 273.16 + real(r8) :: snow_t_highpf ! CLM method upper T threshold [K], default 275.16 + real(r8) :: snow_transition_widthpf ! WetbulbLinear half-width [K], default 1.0 + real(r8) :: dai_apf ! Dai (2008) coefficient a, default -48.2292 + real(r8) :: dai_bpf ! Dai (2008) coefficient b, default 0.7205 + real(r8) :: dai_cpf ! Dai (2008) coefficient c, default 1.1662 + real(r8) :: dai_dpf ! Dai (2008) coefficient d, default 1.0223 + real(r8) :: jennings_apf ! Jennings (2018) intercept, default -10.04 + real(r8) :: jennings_bpf ! Jennings (2018) T coefficient, default 1.41 + real(r8) :: jennings_gpf ! Jennings (2018) RH coefficient, default 0.09 + + ! SZA snow damping keys @RMM 2025 + real(r8) :: sza_snow_dampingpf ! SZA damping factor [0-1], 1.0=disabled + real(r8) :: sza_damping_coszen_refpf ! reference coszen for damping onset + real(r8) :: sza_damping_coszen_minpf ! coszen at max damping ! snow albedo parameterization keys @RMM 2025 integer :: albedo_schemepf ! albedo scheme: 0=CLM, 1=VIC, 2=Tarboton @@ -166,6 +186,10 @@ subroutine clm_lsm(pressure,saturation,evap_trans,top,bottom,porosity,pf_dz_mult real(r8) :: albedo_accum_apf ! VIC cold-phase decay base real(r8) :: albedo_thaw_apf ! VIC melt-phase decay base + ! frac_sno parameterization keys @RMM 2025 + integer :: frac_sno_typepf ! frac_sno scheme: 0=CLM (default), others TBD + real(r8) :: frac_sno_roughnesspf ! roughness length for frac_sno [m] + ! local indices & counters integer :: i,j,k,k1,j1,l1 ! indices for local looping integer :: bj,bl ! indices for local looping !BH @@ -511,6 +535,23 @@ subroutine clm_lsm(pressure,saturation,evap_trans,top,bottom,porosity,pf_dz_mult clm(t)%tw_threshold = tw_thresholdpf clm(t)%thin_snow_damping = thin_snow_dampingpf clm(t)%thin_snow_threshold = thin_snow_thresholdpf + clm(t)%snow_tcrit = snow_tcritpf + clm(t)%snow_t_low = snow_t_lowpf + clm(t)%snow_t_high = snow_t_highpf + clm(t)%snow_transition_width = snow_transition_widthpf + clm(t)%dai_a = dai_apf + clm(t)%dai_b = dai_bpf + clm(t)%dai_c = dai_cpf + clm(t)%dai_d = dai_dpf + clm(t)%jennings_a = jennings_apf + clm(t)%jennings_b = jennings_bpf + clm(t)%jennings_g = jennings_gpf + + ! for SZA snow damping @RMM 2025 + clm(t)%sza_snow_damping = sza_snow_dampingpf + clm(t)%sza_damping_coszen_ref = sza_damping_coszen_refpf + clm(t)%sza_damping_coszen_min = sza_damping_coszen_minpf + clm(t)%coszen = 0.5d0 ! initialize to reference value ! for snow albedo parameterization @RMM 2025 clm(t)%albedo_scheme = albedo_schemepf @@ -522,6 +563,10 @@ subroutine clm_lsm(pressure,saturation,evap_trans,top,bottom,porosity,pf_dz_mult clm(t)%albedo_accum_a = albedo_accum_apf clm(t)%albedo_thaw_a = albedo_thaw_apf + ! for frac_sno parameterization @RMM 2025 + clm(t)%frac_sno_type = frac_sno_typepf + clm(t)%frac_sno_roughness = frac_sno_roughnesspf + ! for irrigation clm(t)%irr_type = irr_typepf clm(t)%irr_cycle = irr_cyclepf diff --git a/pfsimulator/clm/clm_dynvegpar.F90 b/pfsimulator/clm/clm_dynvegpar.F90 index 90fce856b..e713b4e09 100644 --- a/pfsimulator/clm/clm_dynvegpar.F90 +++ b/pfsimulator/clm/clm_dynvegpar.F90 @@ -79,7 +79,18 @@ subroutine clm_dynvegpar (clm,clm_forc_veg) endif ! Fraction of soil covered by snow +! @RMM 2025: Added configurable frac_sno schemes and adjustable roughness parameter - clm%frac_sno = clm%snowdp/(10.*clm%zlnd + clm%snowdp) + select case (clm%frac_sno_type) + + case (0) ! CLM default + ! Use configurable roughness parameter (defaults to zlnd for backward compatibility) + clm%frac_sno = clm%snowdp / (10.0d0 * clm%frac_sno_roughness + clm%snowdp) + + case default ! Future formulations TBD + ! Default to CLM formulation + clm%frac_sno = clm%snowdp / (10.0d0 * clm%frac_sno_roughness + clm%snowdp) + + end select end subroutine clm_dynvegpar diff --git a/pfsimulator/clm/clm_hydro_canopy.F90 b/pfsimulator/clm/clm_hydro_canopy.F90 index 136cf2248..a5b2220ad 100644 --- a/pfsimulator/clm/clm_hydro_canopy.F90 +++ b/pfsimulator/clm/clm_hydro_canopy.F90 @@ -69,6 +69,8 @@ subroutine clm_hydro_canopy (clm) real(r8) :: e_sat ! saturation vapor pressure [Pa] real(r8) :: e_act ! actual vapor pressure [Pa] real(r8) :: q_sat ! saturation specific humidity [kg/kg] + real(r8) :: psnow ! probability/fraction of snow [-] + real(r8) :: exponent ! exponent for logistic function !=== End Variable List =================================================== @@ -171,7 +173,7 @@ subroutine clm_hydro_canopy (clm) dz_snowf = 0. ! rate of snowfall, snow depth/s (m/s) else ! @RMM 2025: Select rain-snow partitioning method - ! snow_partition_type: 0=CLM linear (air temp), 1=wetbulb threshold, 2=wetbulb linear + ! snow_partition_type: 0=CLM linear, 1=wetbulb thresh, 2=wetbulb linear, 3=Dai, 4=Jennings select case (clm%snow_partition_type) case (1, 2) ! Wetbulb-based methods @@ -212,23 +214,60 @@ subroutine clm_hydro_canopy (clm) flfall = 1.0d0 ! all rain endif else ! case 2: wetbulb linear - ! Linear transition over 2K range centered on threshold - if (t_wb_k <= clm%tw_threshold - 1.0d0) then + ! Linear transition over configurable range centered on threshold + if (t_wb_k <= clm%tw_threshold - clm%snow_transition_width) then flfall = 0.0d0 - else if (t_wb_k >= clm%tw_threshold + 1.0d0) then + else if (t_wb_k >= clm%tw_threshold + clm%snow_transition_width) then flfall = 1.0d0 else - flfall = (t_wb_k - (clm%tw_threshold - 1.0d0)) / 2.0d0 + flfall = (t_wb_k - (clm%tw_threshold - clm%snow_transition_width)) / & + (2.0d0 * clm%snow_transition_width) endif endif - case default ! Case 0: CLM default linear (air temperature) - if (clm%forc_t <= tfrz) then - flfall = 0. - else if (clm%forc_t <= tfrz+2.) then - flfall = -54.632 + 0.2*clm%forc_t + case (3) ! Dai (2008) sigmoidal method + ! F(%) = a * [tanh(b*(T-c)) - d], converted to fraction by /100 + ! T in Celsius, coefficients from Table 1a (Land, ANN) + ! Reference: Dai (2008) GRL doi:10.1029/2008GL033295 + t_c = clm%forc_t - tfrz + psnow = (clm%dai_a / 100.0d0) * & + (tanh(clm%dai_b * (t_c - clm%dai_c)) - clm%dai_d) + psnow = max(0.0d0, min(1.0d0, psnow)) + flfall = 1.0d0 - psnow + + case (4) ! Jennings et al. (2018) bivariate logistic method + ! psnow = 1 / (1 + exp(a + b*T + g*RH)) + ! T in Celsius, RH in percent + ! Reference: Jennings et al. (2018) Nat Commun doi:10.1038/s41467-018-03629-7 + t_c = clm%forc_t - tfrz + + ! Calculate saturation vapor pressure using Clausius-Clapeyron + e_sat = es_a * exp(es_b * t_c / (t_c + es_c)) + + ! Calculate saturation specific humidity + q_sat = 0.622d0 * e_sat / (clm%forc_pbot - 0.378d0 * e_sat) + + ! Calculate relative humidity from specific humidity + if (q_sat > 0.0d0) then + rh_pct = min(100.0d0, max(0.0d0, 100.0d0 * clm%forc_q / q_sat)) + else + rh_pct = 100.0d0 + endif + + ! Bivariate logistic regression + exponent = clm%jennings_a + clm%jennings_b * t_c + clm%jennings_g * rh_pct + psnow = 1.0d0 / (1.0d0 + exp(exponent)) + flfall = 1.0d0 - psnow + + case default ! Case 0: CLM default linear (air temperature) with configurable thresholds + ! Now uses configurable snow_t_low and snow_t_high instead of hardcoded values + if (clm%forc_t <= clm%snow_t_low) then + flfall = 0.0d0 + else if (clm%forc_t >= clm%snow_t_high) then + flfall = 0.4d0 else - flfall = 0.4 + flfall = 0.4d0 * (clm%forc_t - clm%snow_t_low) / & + (clm%snow_t_high - clm%snow_t_low) endif end select diff --git a/pfsimulator/clm/clm_main.F90 b/pfsimulator/clm/clm_main.F90 index 30205855c..cc66ec6c0 100644 --- a/pfsimulator/clm/clm_main.F90 +++ b/pfsimulator/clm/clm_main.F90 @@ -184,6 +184,9 @@ subroutine clm_main (clm,day,gmt,clm_forc_veg) call clm_coszen (clm,day,coszen) + ! @RMM 2025: Store coszen for use in meltfreeze SZA damping + clm%coszen = coszen + call clm_surfalb (clm,coszen) ! ----------------------------------------------------------------- diff --git a/pfsimulator/clm/clm_meltfreeze.F90 b/pfsimulator/clm/clm_meltfreeze.F90 index 903c55912..9f95728f6 100644 --- a/pfsimulator/clm/clm_meltfreeze.F90 +++ b/pfsimulator/clm/clm_meltfreeze.F90 @@ -64,8 +64,10 @@ subroutine clm_meltfreeze (fact, brr, hs, dhsdT, & real(r8), dimension(clm%snl+1 : nlevsoi) :: wmass0, wice0, wliq0 real(r8) propor,tinc - ! @RMM 2025: Thin snow damping variables - real(r8) :: damping_factor ! factor to reduce melt energy for thin snowpacks + ! @RMM 2025: Combined thin snow + SZA damping variables + real(r8) :: damping_factor ! combined damping factor + real(r8) :: depth_damping_factor ! depth-based (thin snow) damping component + real(r8) :: sza_damping_factor ! SZA-based damping component !=== End Variable List =================================================== @@ -122,28 +124,54 @@ subroutine clm_meltfreeze (fact, brr, hs, dhsdT, & endif enddo -! @RMM 2025: Apply thin snow damping to snow layer melt energy -! This reduces energy available for melting when snowpack is thin, -! preventing premature melt of shallow snowpacks - if (clm%thin_snow_damping > 0.0d0 .and. clm%thin_snow_damping < 1.0d0) then - do j = clm%snl+1, 0 ! Only snow layers (indices <= 0) - if (clm%imelt(j) == 1 .and. hm(j) > 0.0d0) then ! Only for melting - ! Calculate damping factor: linear interpolation from damping at SWE=0 - ! to 1.0 at SWE=threshold +! @RMM 2025: Apply combined thin-snow and SZA-based damping to snow layer melt energy +! Both effects can be enabled independently and combine multiplicatively: +! - Thin snow damping: reduces melt when SWE is below threshold +! - SZA damping: reduces melt at high solar zenith angles where CLM underestimates albedo + do j = clm%snl+1, 0 ! Only snow layers (indices <= 0) + if (clm%imelt(j) == 1 .and. hm(j) > 0.0d0) then ! Only for melting + + ! Initialize both factors to 1.0 (no damping) + depth_damping_factor = 1.0d0 + sza_damping_factor = 1.0d0 + + ! Calculate depth-based (thin snow) damping + ! Linear interpolation from damping at SWE=0 to 1.0 at threshold + if (clm%thin_snow_damping > 0.0d0 .and. clm%thin_snow_damping < 1.0d0) then if (clm%h2osno <= 0.0d0) then - damping_factor = clm%thin_snow_damping + depth_damping_factor = clm%thin_snow_damping else if (clm%h2osno >= clm%thin_snow_threshold) then - damping_factor = 1.0d0 + depth_damping_factor = 1.0d0 else - damping_factor = clm%thin_snow_damping + & + depth_damping_factor = clm%thin_snow_damping + & (1.0d0 - clm%thin_snow_damping) * & (clm%h2osno / clm%thin_snow_threshold) endif - ! Apply damping to melt energy - hm(j) = hm(j) * damping_factor endif - enddo - endif + + ! Calculate SZA-based damping + ! At coszen >= coszen_ref (low SZA, e.g. 60 deg): no damping + ! At coszen <= coszen_min (high SZA, e.g. 84 deg): maximum damping + ! Linear interpolation between bounds + if (clm%sza_snow_damping > 0.0d0 .and. clm%sza_snow_damping < 1.0d0) then + if (clm%coszen >= clm%sza_damping_coszen_ref) then + sza_damping_factor = 1.0d0 + else if (clm%coszen <= clm%sza_damping_coszen_min) then + sza_damping_factor = clm%sza_snow_damping + else + sza_damping_factor = clm%sza_snow_damping + & + (1.0d0 - clm%sza_snow_damping) * & + (clm%coszen - clm%sza_damping_coszen_min) / & + (clm%sza_damping_coszen_ref - clm%sza_damping_coszen_min) + endif + endif + + ! Combine multiplicatively and apply to melt energy + damping_factor = depth_damping_factor * sza_damping_factor + hm(j) = hm(j) * damping_factor + + endif + enddo ! These two errors were checked carefully. They result from the the computed error ! of "Tridiagonal-Matrix" in subroutine "thermal". diff --git a/pfsimulator/clm/clmtype.F90 b/pfsimulator/clm/clmtype.F90 index 50f232175..649627256 100644 --- a/pfsimulator/clm/clmtype.F90 +++ b/pfsimulator/clm/clmtype.F90 @@ -252,10 +252,27 @@ module clmtype real(r8) :: irr_flag ! flag for irrigation or non-irrigation for a given day (based on threshold) ! Snow parameterization options @RMM 2025 - integer :: snow_partition_type ! rain-snow partition: 0=CLM linear, 1=wetbulb threshold, 2=wetbulb linear + integer :: snow_partition_type ! rain-snow partition: 0=CLM, 1=wetbulb thresh, 2=wetbulb linear, 3=Dai, 4=Jennings real(r8) :: tw_threshold ! wetbulb temperature threshold for snow [K], default 274.15 real(r8) :: thin_snow_damping ! damping factor for thin snow energy [0-1], 0=off real(r8) :: thin_snow_threshold ! SWE threshold for damping [kg/m2 or mm], default 50.0 + real(r8) :: snow_tcrit ! initial T classification threshold above tfrz [K], default 2.5 + real(r8) :: snow_t_low ! CLM method lower T threshold [K], default 273.16 + real(r8) :: snow_t_high ! CLM method upper T threshold [K], default 275.16 + real(r8) :: snow_transition_width ! WetbulbLinear half-width [K], default 1.0 + real(r8) :: dai_a ! Dai (2008) coefficient a, default -48.2292 + real(r8) :: dai_b ! Dai (2008) coefficient b, default 0.7205 + real(r8) :: dai_c ! Dai (2008) coefficient c, default 1.1662 + real(r8) :: dai_d ! Dai (2008) coefficient d, default 1.0223 + real(r8) :: jennings_a ! Jennings (2018) intercept, default -10.04 + real(r8) :: jennings_b ! Jennings (2018) T coefficient, default 1.41 + real(r8) :: jennings_g ! Jennings (2018) RH coefficient, default 0.09 + +! SZA-based snow melt damping parameters @RMM 2025 + real(r8) :: coszen ! current cosine of solar zenith angle (stored from clm_main) + real(r8) :: sza_snow_damping ! damping factor at high SZA [0-1], 1.0=disabled + real(r8) :: sza_damping_coszen_ref ! reference coszen below which damping applies, default 0.5 + real(r8) :: sza_damping_coszen_min ! coszen at which max damping applies, default 0.1 ! Snow albedo parameterization options @RMM 2025 integer :: albedo_scheme ! albedo scheme: 0=CLM, 1=VIC, 2=Tarboton @@ -267,6 +284,10 @@ module clmtype real(r8) :: albedo_accum_a ! VIC cold-phase decay base, default 0.94 real(r8) :: albedo_thaw_a ! VIC melt-phase decay base, default 0.82 +! Fractional snow covered area (frac_sno) options @RMM 2025 + integer :: frac_sno_type ! frac_sno scheme: 0=CLM (default), others TBD + real(r8) :: frac_sno_roughness ! roughness length for frac_sno [m], default=zlnd + real(r8) :: eflx_snomelt ! added to be consistent with lsm hybrid code real(r8) :: eflx_impsoil ! implicit evaporation for soil temperature equation (W/m**2) real(r8) :: eflx_lh_vege ! veg evaporation heat flux (W/m**2) [+ to atm] diff --git a/pfsimulator/clm/drv_getforce.F90 b/pfsimulator/clm/drv_getforce.F90 index ce9f4f477..f9b57cb89 100644 --- a/pfsimulator/clm/drv_getforce.F90 +++ b/pfsimulator/clm/drv_getforce.F90 @@ -33,7 +33,7 @@ subroutine drv_getforce (drv,tile,clm,nx,ny,sw_pf,lw_pf,prcp_pf,tas_pf,u_pf,v_pf use drv_module ! 1-D Land Model Driver variables use drv_tilemodule ! Tile-space variables use clmtype ! 1-D CLM variables - use clm_varcon, only : tfrz, tcrit + use clm_varcon, only : tfrz implicit none !=== Arguments =========================================================== @@ -112,8 +112,9 @@ subroutine drv_getforce (drv,tile,clm,nx,ny,sw_pf,lw_pf,prcp_pf,tas_pf,u_pf,v_pf !(Set upper limit of air temperature for snowfall at 275.65K. ! This cut-off was selected based on Fig. 1, Plate 3-1, of Snow ! Hydrology (1956)). + ! @RMM 2025: Now uses configurable snow_tcrit from clm1d struct if (prcp > 0.) then - if(clm(t)%forc_t > (tfrz + tcrit))then + if(clm(t)%forc_t > (tfrz + clm(t)%snow_tcrit))then clm(t)%itypprc = 1 clm(t)%forc_rain = prcp clm(t)%forc_snow = 0. diff --git a/pfsimulator/parflow_lib/parflow_proto_f.h b/pfsimulator/parflow_lib/parflow_proto_f.h index 34fdffdb5..a57caeefb 100644 --- a/pfsimulator/parflow_lib/parflow_proto_f.h +++ b/pfsimulator/parflow_lib/parflow_proto_f.h @@ -134,7 +134,11 @@ void SADVECT(double *s, double *sn, clm_irr_type, clm_irr_cycle, clm_irr_rate, clm_irr_start, clm_irr_stop, \ clm_irr_threshold, qirr, qirr_inst, iflag, clm_irr_thresholdtype, soi_z, clm_next, clm_write_logs, clm_last_rst, clm_daily_rst, clm_water_stress_type, clm_nlevsoi, clm_nlevlak, \ clm_snow_partition, clm_tw_threshold, clm_thin_snow_damping, clm_thin_snow_threshold, \ - clm_albedo_scheme, clm_albedo_vis_new, clm_albedo_nir_new, clm_albedo_min, clm_albedo_decay_vis, clm_albedo_decay_nir, clm_albedo_accum_a, clm_albedo_thaw_a) \ + clm_snow_tcrit, clm_snow_t_low, clm_snow_t_high, clm_snow_transition_width, \ + clm_dai_a, clm_dai_b, clm_dai_c, clm_dai_d, clm_jennings_a, clm_jennings_b, clm_jennings_g, \ + clm_sza_snow_damping, clm_sza_damping_coszen_ref, clm_sza_damping_coszen_min, \ + clm_albedo_scheme, clm_albedo_vis_new, clm_albedo_nir_new, clm_albedo_min, clm_albedo_decay_vis, clm_albedo_decay_nir, clm_albedo_accum_a, clm_albedo_thaw_a, \ + clm_frac_sno_type, clm_frac_sno_roughness) \ CLM_LSM(pressure_data, saturation_data, evap_trans_data, top, bottom, porosity_data, \ dz_mult_data, &istep, &dt, &t, &start_time, &dx, &dy, &dz, &ix, &iy, &nx, &ny, &nz, &nx_f, &ny_f, &nz_f, &nz_rz, &ip, &p, &q, &r, &gnx, &gny, &rank, \ sw_data, lw_data, prcp_data, tas_data, u_data, v_data, patm_data, qatm_data, \ @@ -148,7 +152,11 @@ void SADVECT(double *s, double *sn, &clm_res_sat, &clm_irr_type, &clm_irr_cycle, &clm_irr_rate, &clm_irr_start, &clm_irr_stop, \ &clm_irr_threshold, qirr, qirr_inst, iflag, &clm_irr_thresholdtype, &soi_z, &clm_next, &clm_write_logs, &clm_last_rst, &clm_daily_rst, &clm_water_stress_type, &clm_nlevsoi, &clm_nlevlak, \ &clm_snow_partition, &clm_tw_threshold, &clm_thin_snow_damping, &clm_thin_snow_threshold, \ - &clm_albedo_scheme, &clm_albedo_vis_new, &clm_albedo_nir_new, &clm_albedo_min, &clm_albedo_decay_vis, &clm_albedo_decay_nir, &clm_albedo_accum_a, &clm_albedo_thaw_a); + &clm_snow_tcrit, &clm_snow_t_low, &clm_snow_t_high, &clm_snow_transition_width, \ + &clm_dai_a, &clm_dai_b, &clm_dai_c, &clm_dai_d, &clm_jennings_a, &clm_jennings_b, &clm_jennings_g, \ + &clm_sza_snow_damping, &clm_sza_damping_coszen_ref, &clm_sza_damping_coszen_min, \ + &clm_albedo_scheme, &clm_albedo_vis_new, &clm_albedo_nir_new, &clm_albedo_min, &clm_albedo_decay_vis, &clm_albedo_decay_nir, &clm_albedo_accum_a, &clm_albedo_thaw_a, \ + &clm_frac_sno_type, &clm_frac_sno_roughness); void CLM_LSM(double *pressure_data, double *saturation_data, double *evap_trans_data, double *top, double *bottom, double *porosity_data, double *dz_mult_data, int *istep, double *dt, double *t, double *start_time, @@ -166,8 +174,13 @@ void CLM_LSM(double *pressure_data, double *saturation_data, double *evap_trans_ double *clm_irr_threshold, double *qirr, double *qirr_inst, double *iflag, int *clm_irr_thresholdtype, int *soi_z, int *clm_next, int *clm_write_logs, int *clm_last_rst, int *clm_daily_rst, int *clm_water_stress_type, int *clm_nlevsoi, int *clm_nlevlak, int *clm_snow_partition, double *clm_tw_threshold, double *clm_thin_snow_damping, double *clm_thin_snow_threshold, + double *clm_snow_tcrit, double *clm_snow_t_low, double *clm_snow_t_high, double *clm_snow_transition_width, + double *clm_dai_a, double *clm_dai_b, double *clm_dai_c, double *clm_dai_d, + double *clm_jennings_a, double *clm_jennings_b, double *clm_jennings_g, + double *clm_sza_snow_damping, double *clm_sza_damping_coszen_ref, double *clm_sza_damping_coszen_min, int *clm_albedo_scheme, double *clm_albedo_vis_new, double *clm_albedo_nir_new, double *clm_albedo_min, - double *clm_albedo_decay_vis, double *clm_albedo_decay_nir, double *clm_albedo_accum_a, double *clm_albedo_thaw_a); + double *clm_albedo_decay_vis, double *clm_albedo_decay_nir, double *clm_albedo_accum_a, double *clm_albedo_thaw_a, + int *clm_frac_sno_type, double *clm_frac_sno_roughness); /* @RMM CRUNCHFLOW.F90*/ //#define CRUNCHFLOW crunchflow_ diff --git a/pfsimulator/parflow_lib/solver_richards.c b/pfsimulator/parflow_lib/solver_richards.c index 93379fdc5..1add8c20d 100644 --- a/pfsimulator/parflow_lib/solver_richards.c +++ b/pfsimulator/parflow_lib/solver_richards.c @@ -198,10 +198,26 @@ typedef struct { int clm_irr_thresholdtype; /* Deficit-based saturation criteria (top, bottom, column avg) */ /* Snow parameterization options @RMM 2025 */ - int clm_snow_partition; /* CLM snow partition type: 0=linear, 1=wetbulb threshold, 2=wetbulb linear */ + int clm_snow_partition; /* CLM snow partition type: 0=CLM, 1=wetbulb thresh, 2=wetbulb linear, 3=Dai, 4=Jennings */ double clm_tw_threshold; /* CLM wetbulb temperature threshold for snow [K] */ double clm_thin_snow_damping; /* CLM thin snow energy damping factor [0-1] */ double clm_thin_snow_threshold; /* CLM SWE threshold for damping [kg/m2] */ + double clm_snow_tcrit; /* Initial T classification threshold above tfrz [K], default 2.5 */ + double clm_snow_t_low; /* CLM method lower T threshold [K], default 273.16 */ + double clm_snow_t_high; /* CLM method upper T threshold [K], default 275.16 */ + double clm_snow_transition_width; /* WetbulbLinear half-width [K], default 1.0 */ + double clm_dai_a; /* Dai (2008) coefficient a, default -48.2292 */ + double clm_dai_b; /* Dai (2008) coefficient b, default 0.7205 */ + double clm_dai_c; /* Dai (2008) coefficient c, default 1.1662 */ + double clm_dai_d; /* Dai (2008) coefficient d, default 1.0223 */ + double clm_jennings_a; /* Jennings (2018) intercept, default -10.04 */ + double clm_jennings_b; /* Jennings (2018) T coefficient, default 1.41 */ + double clm_jennings_g; /* Jennings (2018) RH coefficient, default 0.09 */ + + /* SZA-based snow damping parameters @RMM 2025 */ + double clm_sza_snow_damping; /* SZA damping factor [0-1], 1.0=disabled */ + double clm_sza_damping_coszen_ref; /* Reference coszen for damping onset (0.5 = 60 deg) */ + double clm_sza_damping_coszen_min; /* Coszen at maximum damping (0.1 = 84 deg) */ /* Snow albedo parameterization options @RMM 2025 */ int clm_albedo_scheme; /* CLM albedo scheme: 0=CLM, 1=VIC, 2=Tarboton */ @@ -213,6 +229,10 @@ typedef struct { double clm_albedo_accum_a; /* VIC cold-phase decay base */ double clm_albedo_thaw_a; /* VIC melt-phase decay base */ + /* Fractional snow covered area (frac_sno) options @RMM 2025 */ + int clm_frac_sno_type; /* frac_sno scheme: 0=CLM (default), others TBD */ + double clm_frac_sno_roughness; /* roughness length for frac_sno [m], default=0.01 (zlnd) */ + int clm_reuse_count; /* NBE: Number of times to use each CLM input */ int clm_write_logs; /* NBE: Write the processor logs for CLM or not */ int clm_last_rst; /* NBE: Only write/overwrite one rst file or write a lot of them */ @@ -2788,6 +2808,20 @@ AdvanceRichards(PFModule * this_module, double start_time, /* Starting time public_xtra->clm_tw_threshold, public_xtra->clm_thin_snow_damping, public_xtra->clm_thin_snow_threshold, + public_xtra->clm_snow_tcrit, + public_xtra->clm_snow_t_low, + public_xtra->clm_snow_t_high, + public_xtra->clm_snow_transition_width, + public_xtra->clm_dai_a, + public_xtra->clm_dai_b, + public_xtra->clm_dai_c, + public_xtra->clm_dai_d, + public_xtra->clm_jennings_a, + public_xtra->clm_jennings_b, + public_xtra->clm_jennings_g, + public_xtra->clm_sza_snow_damping, + public_xtra->clm_sza_damping_coszen_ref, + public_xtra->clm_sza_damping_coszen_min, public_xtra->clm_albedo_scheme, public_xtra->clm_albedo_vis_new, public_xtra->clm_albedo_nir_new, @@ -2795,7 +2829,9 @@ AdvanceRichards(PFModule * this_module, double start_time, /* Starting time public_xtra->clm_albedo_decay_vis, public_xtra->clm_albedo_decay_nir, public_xtra->clm_albedo_accum_a, - public_xtra->clm_albedo_thaw_a); + public_xtra->clm_albedo_thaw_a, + public_xtra->clm_frac_sno_type, + public_xtra->clm_frac_sno_roughness); break; } @@ -5631,7 +5667,7 @@ SolverRichardsNewPublicXtra(char *name) /* @RMM 2025 Snow parameterization options */ NameArray snow_switch_na; - snow_switch_na = NA_NewNameArray("CLM WetbulbThreshold WetbulbLinear"); + snow_switch_na = NA_NewNameArray("CLM WetbulbThreshold WetbulbLinear Dai Jennings"); sprintf(key, "%s.CLM.SnowPartition", name); switch_name = GetStringDefault(key, "CLM"); switch_value = NA_NameToIndexExitOnError(snow_switch_na, switch_name, key); @@ -5655,6 +5691,18 @@ SolverRichardsNewPublicXtra(char *name) break; } + case 3: + { + public_xtra->clm_snow_partition = 3; + break; + } + + case 4: + { + public_xtra->clm_snow_partition = 4; + break; + } + default: { InputError("Invalid switch value <%s> for key <%s>", switch_name, key); @@ -5665,12 +5713,55 @@ SolverRichardsNewPublicXtra(char *name) sprintf(key, "%s.CLM.WetbulbThreshold", name); public_xtra->clm_tw_threshold = GetDoubleDefault(key, 274.15); + sprintf(key, "%s.CLM.SnowTCrit", name); + public_xtra->clm_snow_tcrit = GetDoubleDefault(key, 2.5); + + sprintf(key, "%s.CLM.SnowTLow", name); + public_xtra->clm_snow_t_low = GetDoubleDefault(key, 273.16); + + sprintf(key, "%s.CLM.SnowTHigh", name); + public_xtra->clm_snow_t_high = GetDoubleDefault(key, 275.16); + + sprintf(key, "%s.CLM.SnowTransitionWidth", name); + public_xtra->clm_snow_transition_width = GetDoubleDefault(key, 1.0); + + sprintf(key, "%s.CLM.DaiCoeffA", name); + public_xtra->clm_dai_a = GetDoubleDefault(key, -48.2292); + + sprintf(key, "%s.CLM.DaiCoeffB", name); + public_xtra->clm_dai_b = GetDoubleDefault(key, 0.7205); + + sprintf(key, "%s.CLM.DaiCoeffC", name); + public_xtra->clm_dai_c = GetDoubleDefault(key, 1.1662); + + sprintf(key, "%s.CLM.DaiCoeffD", name); + public_xtra->clm_dai_d = GetDoubleDefault(key, 1.0223); + + sprintf(key, "%s.CLM.JenningsCoeffA", name); + public_xtra->clm_jennings_a = GetDoubleDefault(key, -10.04); + + sprintf(key, "%s.CLM.JenningsCoeffB", name); + public_xtra->clm_jennings_b = GetDoubleDefault(key, 1.41); + + sprintf(key, "%s.CLM.JenningsCoeffG", name); + public_xtra->clm_jennings_g = GetDoubleDefault(key, 0.09); + sprintf(key, "%s.CLM.ThinSnowDamping", name); public_xtra->clm_thin_snow_damping = GetDoubleDefault(key, 0.0); sprintf(key, "%s.CLM.ThinSnowThreshold", name); public_xtra->clm_thin_snow_threshold = GetDoubleDefault(key, 50.0); + /* @RMM 2025 SZA-based snow melt damping */ + sprintf(key, "%s.CLM.SZASnowDamping", name); + public_xtra->clm_sza_snow_damping = GetDoubleDefault(key, 1.0); + + sprintf(key, "%s.CLM.SZADampingCoszenRef", name); + public_xtra->clm_sza_damping_coszen_ref = GetDoubleDefault(key, 0.5); + + sprintf(key, "%s.CLM.SZADampingCoszenMin", name); + public_xtra->clm_sza_damping_coszen_min = GetDoubleDefault(key, 0.1); + /* @RMM 2025 Snow albedo parameterization options */ NameArray albedo_switch_na; albedo_switch_na = NA_NewNameArray("CLM VIC Tarboton"); @@ -5725,6 +5816,30 @@ SolverRichardsNewPublicXtra(char *name) sprintf(key, "%s.CLM.AlbedoThawA", name); public_xtra->clm_albedo_thaw_a = GetDoubleDefault(key, 0.82); + /* @RMM 2025 Fractional snow covered area (frac_sno) options */ + NameArray frac_sno_switch_na; + frac_sno_switch_na = NA_NewNameArray("CLM"); + sprintf(key, "%s.CLM.FracSnoScheme", name); + switch_name = GetStringDefault(key, "CLM"); + switch_value = NA_NameToIndexExitOnError(frac_sno_switch_na, switch_name, key); + switch (switch_value) + { + case 0: + { + public_xtra->clm_frac_sno_type = 0; + break; + } + + default: + { + InputError("Invalid switch value <%s> for key <%s>", switch_name, key); + } + } + NA_FreeNameArray(frac_sno_switch_na); + + sprintf(key, "%s.CLM.FracSnoRoughness", name); + public_xtra->clm_frac_sno_roughness = GetDoubleDefault(key, 0.01); + /* IMF Write CLM as Silo (default=False) */ sprintf(key, "%s.WriteSiloCLM", name); switch_name = GetStringDefault(key, "False"); diff --git a/test/correct_output/clm_snow_dai/clm_snow_dai.out.alpha.pfb b/test/correct_output/clm_snow_dai/clm_snow_dai.out.alpha.pfb new file mode 100644 index 0000000000000000000000000000000000000000..4d9bbe3b627fa7bb59ec74115cb3013747cf4a89 GIT binary patch literal 2100 zcmZQzzzJA^G9b+5(C+|N1f%UAfVm)o5zc}z;Chh79e5zTQF=5SKp`-i4o1_#XgV0p L2czY{&@2Z4Xe~{j literal 0 HcmV?d00001 diff --git a/test/correct_output/clm_snow_dai/clm_snow_dai.out.clm_output.00001.C.pfb b/test/correct_output/clm_snow_dai/clm_snow_dai.out.clm_output.00001.C.pfb new file mode 100644 index 0000000000000000000000000000000000000000..94798175ef0196021e2bcc5d142325c62b3fa4b5 GIT binary patch literal 4700 zcmZQzzzJA^G9WDO(C+|N1f%UAfVm)o5zc}z;Chh79at{O@NWNSIppb*sarmtXJ6_t z)ajrbvU!C%+Z={CeIWj7lbAHO{ZOavHl}QfpL6Q;kf&E1m~t-j_dL5%dT@lp6v6oq z=No^p8|rk2ksQahH(Yi@oZdJ6_346hYmW_e8o6#C%4~|PQUSj_+yCEAB1j#{V~MpkL62@K>irw^v9j& PFTMf#V~Eqkqg?_3j~%O- literal 0 HcmV?d00001 diff --git a/test/correct_output/clm_snow_dai/clm_snow_dai.out.clm_output.00002.C.pfb b/test/correct_output/clm_snow_dai/clm_snow_dai.out.clm_output.00002.C.pfb new file mode 100644 index 0000000000000000000000000000000000000000..5c93d3de8dcd6c23e8cf509024c1c5f1d23cd724 GIT binary patch literal 4700 zcmZQzzzJA^G9WDO(C+|N1f%UAfVm)o5zc}z;Chh79XK-6f~Iy)8}f9Cxy!;CQ>7h- zIvrf=@T@HIzQYiw4BWao7s)hgCiVf%4o|U zd-}s}sM8(WTD#WlxN0}V>3#itUkuiICk%BOxo#iIY=@$`S;A_2ZFy2MZBhuYV1Ep8dZnGeBFG;@oIcJ! PdFD-^KZZCxJlZ7yy!4Ug literal 0 HcmV?d00001 diff --git a/test/correct_output/clm_snow_dai/clm_snow_dai.out.clm_output.00003.C.pfb b/test/correct_output/clm_snow_dai/clm_snow_dai.out.clm_output.00003.C.pfb new file mode 100644 index 0000000000000000000000000000000000000000..6a6c90913eb38a748398426148aee5fa0ec29e10 GIT binary patch literal 4700 zcmZQzzzJA^G9WDO(C+|N1f%UAfVm)o5zc}z;Chh79oWxa?9>pA8uE1UB{hkf_jwLO zoeo~5EHA(5x5E&p4RS{JzAM8fy!4VE~uAgC( zV!vQF)alN%!P{n@|a!`&p(yU^65 z^U=*CRi2P~bn|!HEq%B}I9)c6L^>NyJvtxVJW}NesYf@TTt2!z=zMf@NtGw09^L#r zyZLiDzsvdv4tcucd*Q2PTK0BBou0T@xQyG$&W<$N1x-CVAKg4s3l3)9AL{f_jss-3I}{xNaal&`K@|?n(D)d_^#1=qaL}P>IM9#8A^sTR^rzZc pV1Ep8`j>X@1du<5IQ_TJo$DOXA48p%Z+v@aKF}XSoE{$S5&(@G!5aVo literal 0 HcmV?d00001 diff --git a/test/correct_output/clm_snow_dai/clm_snow_dai.out.clm_output.00005.C.pfb b/test/correct_output/clm_snow_dai/clm_snow_dai.out.clm_output.00005.C.pfb new file mode 100644 index 0000000000000000000000000000000000000000..e3d26590cb25423fea1138c4b09575158a4b1978 GIT binary patch literal 4700 zcmZQzzzJA^G9WDO(C+|N1f%UAfVm)o5zc}z;Chh79hkNSg>ar%gVHKEXmojHG<`5W zx_+2Gm^_S+t`1!u#)qlHr4J!rY|*svsB(hCP^W`$eVQWC@yKC_(+A>()~`LAa%HH~ zcI%xxSZ3Qjg3|YJ(75DriNnl6mq+Kr)WK+6^0>qi`WBsi{KM;Lp&f+I#X+OX=RnoL z#9@4N{pj*AKDs)XewaLr4^sydhw;&Am^_R|mxuAur?Y0R6g7Bv!46En7r;(q$SXk9 z!Ns9`bp7b^a6Y;{1kA&!4{FZ43&?f*P-Z(6W#4^%&X<3v(?dB9klpT3bO6L<9i<0V zI50uuV+hmx{{w-nL(y=c7Z^hPF~sRx7gvD&F~sRlHSMk-e++T@cSz7iRiHnHIK6Ml QtMUgA9g2oJJv`ba0Lt745dZ)H literal 0 HcmV?d00001 diff --git a/test/correct_output/clm_snow_dai/clm_snow_dai.out.mask.pfb b/test/correct_output/clm_snow_dai/clm_snow_dai.out.mask.pfb new file mode 100644 index 0000000000000000000000000000000000000000..caa1ffcc9ae5e14ea209e182ea41063414464787 GIT binary patch literal 2100 zcmZQzzzJA^G9b+5(C+|N1f%UAfVm)o5zc}z;Chh7?LREwpb!{M2czj= NG#!lQgP~Rq004WFLFWJf literal 0 HcmV?d00001 diff --git a/test/correct_output/clm_snow_dai/clm_snow_dai.out.perm_x.pfb b/test/correct_output/clm_snow_dai/clm_snow_dai.out.perm_x.pfb new file mode 100644 index 0000000000000000000000000000000000000000..614ae8457418bf08b91ae85c3472f747047b5c19 GIT binary patch literal 2100 zcmZQzzzJA^G9b+5(C+|N1f%UAfVm)o5zc}z;Chh7?N81Gfmx&UXgG|fgVB62S`Lhs L1Eb}@&@Bf5p#O&H literal 0 HcmV?d00001 diff --git a/test/correct_output/clm_snow_dai/clm_snow_dai.out.perm_y.pfb b/test/correct_output/clm_snow_dai/clm_snow_dai.out.perm_y.pfb new file mode 100644 index 0000000000000000000000000000000000000000..614ae8457418bf08b91ae85c3472f747047b5c19 GIT binary patch literal 2100 zcmZQzzzJA^G9b+5(C+|N1f%UAfVm)o5zc}z;Chh7?N81Gfmx&UXgG|fgVB62S`Lhs L1Eb}@&@Bf5p#O&H literal 0 HcmV?d00001 diff --git a/test/correct_output/clm_snow_dai/clm_snow_dai.out.perm_z.pfb b/test/correct_output/clm_snow_dai/clm_snow_dai.out.perm_z.pfb new file mode 100644 index 0000000000000000000000000000000000000000..614ae8457418bf08b91ae85c3472f747047b5c19 GIT binary patch literal 2100 zcmZQzzzJA^G9b+5(C+|N1f%UAfVm)o5zc}z;Chh7?N81Gfmx&UXgG|fgVB62S`Lhs L1Eb}@&@Bf5p#O&H literal 0 HcmV?d00001 diff --git a/test/correct_output/clm_snow_dai/clm_snow_dai.out.porosity.pfb b/test/correct_output/clm_snow_dai/clm_snow_dai.out.porosity.pfb new file mode 100644 index 0000000000000000000000000000000000000000..13c2a612151e90033fa181e31aa41d94e2f5e8a5 GIT binary patch literal 2100 zcmZQzzzJA^G9b+5(C+|N1f%UAfVm)o5zc}z;Chh7?QeWN)E}eqZIm7jhtYH}nh!?H Ofzfhcv>X_^;Ay}Yz?}Onk6FvLn&l#C`{+p z^|H3_7IMhVO9-Y-UcYzlIp;p5#LqoA<>~t~zX~z+x{~G3X>RqU>HM7gF44*GC~Z9* zqSMPx=C_=BN5$FwXwiCRRGg2zfv$Nh5Ph4*J(#!om`DA)zo9&w#<6ZYSCm}@*Qu`g zE)e}UlS5*keeScCa_loX$9>kwGZAOrj6vjKk%_ z9~oPE=%dDWZ9e9|^;UJCyh#z&+#3>Tn~(Yb*0%SHC|6g{y}KL3TY-`<&3O;{s5!@! Jd}-eFz5sRIScd=r literal 0 HcmV?d00001 diff --git a/test/correct_output/clm_snow_dai/clm_snow_dai.out.press.00002.pfb b/test/correct_output/clm_snow_dai/clm_snow_dai.out.press.00002.pfb new file mode 100644 index 0000000000000000000000000000000000000000..2efd5efb0dfafd8894ab38304d4cc072a9f1e1e2 GIT binary patch literal 2100 zcmdUou}%U(6h#LOg$8@$4`4+Cv65mo!PW>iB*c=0Kj06nv8A-7p+ht=o8SjPML{%L zK%le0r9l`3=5EYQUM5@aCc9_voOjf{}XoppL|NQ_clqf1=Vmpmq)v3)IW^0-(`umiTnJ>3 z%;Y|MtpB5qM(>|p%O}fc%Isgr mbLM_j&+~jg@9+A*s#Uu^OIw}`l)Q9~J?uGS4wSrf9`-hNA9Jk$ literal 0 HcmV?d00001 diff --git a/test/correct_output/clm_snow_dai/clm_snow_dai.out.press.00003.pfb b/test/correct_output/clm_snow_dai/clm_snow_dai.out.press.00003.pfb new file mode 100644 index 0000000000000000000000000000000000000000..823c25d4c6977a312706cd6cb8170919a9daaadf GIT binary patch literal 2100 zcmdUou`WYV9L7(j*I*HYm*4?JA~`o9Q8C}Pk(fv%w>{i=iyM0j3HG zgHE_TwD)`^zvSfRj+NBc-}!ytzs3arqF>YhOvH!rpro{q1ML5+48P@HhMWKIapZbu zx2K(v&x-4vPtPpIBcArlWlNjabz}ofU&KZGB47Ughs##C=G!*Un?TZwncUNh%l1M$ z-D%s7`@Bq&2ssX>uX@i^PkVZ?y!v`)$}_o-?45f}HeQ-O*L<#n>FeG%e2TfJ7xTXM z@?zcN?{nWw?@hGul(l^B_}m85?_wwBUORa)@6(HQPdhR9t@PPc=ic9rkjLPVKk+^G z#q?y|4fW-j>i@lW&E(kZ)P7nn4}5>mr#q4P>FJ>>y-{$J!qFYPzTn;4titp)N S%ssuBJ?-Vi>gip~to;C;3RUC) literal 0 HcmV?d00001 diff --git a/test/correct_output/clm_snow_dai/clm_snow_dai.out.press.00004.pfb b/test/correct_output/clm_snow_dai/clm_snow_dai.out.press.00004.pfb new file mode 100644 index 0000000000000000000000000000000000000000..364e0935ecb0ccd6fb0f9a9646590be9b8ad6d66 GIT binary patch literal 2100 zcmdUou}Z^W6h{9JMUaZn3O<0dQ$@!FoSZF=x~PkrI64Rx7Zo2M3ci8Z;-W*u#Zler zU~#LvE@DWu%{@iF@)H~_q$lTo_q%b<`z!w>{bxKp2?t9scpYf}uQYtiUxuCE^RS$y z8+%)k@zg`jai#XjnXBuZ9BPg$qp*~vo3~46|DC~CmOPzbRE;$cHOH0O*Bt-rp0CH9 zYVe*Vo$Z^JvF)Mem$s+WJ~@5dw?_`yqmL_FK`;BfZ$@?FyoZ|OO6`-+6?}(1CHI)) z%6jmS^{%E58pc)Qnz-gGmM&V3IxFLJ%3 o`$f6#tB>{*7fs#GR^pr!9%?>md(3g|YmVz4eR7;V=D4!*4W1@fYybcN literal 0 HcmV?d00001 diff --git a/test/correct_output/clm_snow_dai/clm_snow_dai.out.press.00005.pfb b/test/correct_output/clm_snow_dai/clm_snow_dai.out.press.00005.pfb new file mode 100644 index 0000000000000000000000000000000000000000..56700bd9aeadc340472a355ba79958699e0ccd00 GIT binary patch literal 2100 zcmb7-y-EXN5Jh)|2wGSOf*1=e6~S7Y-Byd3!bT91%1wNRNTS&L5aJUk{-jw+DS~zu zHbM%;M#Kn$OI8!-208E%Hjtd$@64U?Jm)9(YkJ@D@F?sLiNRAp_I~`qzrR0&&j0r? zmv$FU=X~RcgY?QI`}E3L=UhGQ@k2lDF21&F#ytn=l}YyLm9x&ddfHP9zSCsuaXxRH zb&!6>=1BJGm9x(AKKuFLBTdSU_lmLVAiZ)lq%TkM&ai#*>?v1*=k)7gq8J&g4$|+~ z9LYYta@INCXFm$=()QbKr)9h`-Xhaqns@TdiFdO6d0sajI7okJbNfbR_UV;5r@p+h=6GN4V*Lk5 CL^8Pm literal 0 HcmV?d00001 diff --git a/test/correct_output/clm_snow_dai/clm_snow_dai.out.satur.00000.pfb b/test/correct_output/clm_snow_dai/clm_snow_dai.out.satur.00000.pfb new file mode 100644 index 0000000000000000000000000000000000000000..8ea84826c4fb0c2cb55fb0448d2ff04ed9a2a9b7 GIT binary patch literal 2100 zcmZQzzzJA^G9b+5(C+|N1f%UAfVm)o5zc}z;Chh7?LR vXeLZBjC*H4)amO+D$38wJ?w`%eYV9w{F#V{{ZOZmZo0WK=EYk3Ax;AT%KI_W literal 0 HcmV?d00001 diff --git a/test/correct_output/clm_snow_dai/clm_snow_dai.out.satur.00001.pfb b/test/correct_output/clm_snow_dai/clm_snow_dai.out.satur.00001.pfb new file mode 100644 index 0000000000000000000000000000000000000000..df1930a6230d5d97c1c17f6c76c3986516d283fa GIT binary patch literal 2100 zcmZQzzzJA^G9b+5(C+|N1f%UAfVm)o5zc}z;Chh7?LR zXvX&~WPV@|p&#O)(d7y8(ak|uPe>kJJvtv#|8*m!w~{aK*^@`#C7=&o9-WVFFClq! z^$7mi7R`AZVjS!tv=fxJL*o;YM^{g6J|X)Z?2m4mTt8zuFO=rNLF1CgB@QzOT^^ke OQwO7Q$>S1->jMB zXvXF4$$n`MpCgt}ED7_p9jZ1zh4)ri| z(B;wjFm*5*mpm@}Vft3spKVdP_o+|sq*OR(d|b!U&8+A Nri4|Y`(=kZ4FKjEPNe_< literal 0 HcmV?d00001 diff --git a/test/correct_output/clm_snow_dai/clm_snow_dai.out.satur.00003.pfb b/test/correct_output/clm_snow_dai/clm_snow_dai.out.satur.00003.pfb new file mode 100644 index 0000000000000000000000000000000000000000..aef234fcc57aa2fcc0f47bcfe7dd06975f25703e GIT binary patch literal 2100 zcmZQzzzJA^G9b+5(C+|N1f%UAfVm)o5zc}z;Chh7?LR zXvXTE5dUlsp}*mv(dA)$bQ)bhx;%_esybZyVCH zXhuJqs?Tc=p#^Zz=<+Z=I*qO$T^`0KRUIyUFmrhAuN%oP=5&dH(s4Lwba@ybokrJ> zE)V0Ast%Vvm^m@_XIn%U$lC@(=};Urx;!C1x;g0T3CW|YN9SYeKe{QqM{xg9dkB35 l2aPU&7^)5?4&$TiN0*23(bd89!{lLnm^zp^jE_zq1pttE0jK}~ literal 0 HcmV?d00001 diff --git a/test/correct_output/clm_snow_dai/clm_snow_dai.out.satur.00005.pfb b/test/correct_output/clm_snow_dai/clm_snow_dai.out.satur.00005.pfb new file mode 100644 index 0000000000000000000000000000000000000000..1c2f7abc22c083e6189d9c21326333a6e242c13d GIT binary patch literal 2100 zcmeH_Ar8Vo6hw=_U^oG=ZG*sK2vq!@1A*ccXgsnZ!J#p51|*u$ganC%1CYRW|1@Nh z6*$B;Y2VJy%>Ja=DDpndk7M6fzRIf*D7Q-5PorMBMcw({mxoh<;Xl|v5bi;^2Y-DJ z?t|(2*@?w5QKqEX@2PK;(A;Mqn$JfcJ~Ynb`P`4?JnU{4hf;al literal 0 HcmV?d00001 diff --git a/test/correct_output/clm_snow_dai/clm_snow_dai.out.ssat.pfb b/test/correct_output/clm_snow_dai/clm_snow_dai.out.ssat.pfb new file mode 100644 index 0000000000000000000000000000000000000000..caa1ffcc9ae5e14ea209e182ea41063414464787 GIT binary patch literal 2100 zcmZQzzzJA^G9b+5(C+|N1f%UAfVm)o5zc}z;Chh7?LR4SBj`>iWZOn$ZqJ zoesJoJxzEszrzrx55!;1W|Muic&O8M8&j6LN;W?m^7M)WRWp-wuiA~$gCiWK2+n^v z-}r;wP^UYLdS`D2LFA9r3T Oc?tB#5T}Pny95A{$PZZn literal 0 HcmV?d00001 diff --git a/test/correct_output/clm_snow_jennings/clm_snow_jennings.out.clm_output.00002.C.pfb b/test/correct_output/clm_snow_jennings/clm_snow_jennings.out.clm_output.00002.C.pfb new file mode 100644 index 0000000000000000000000000000000000000000..0a08c15dfe5c4de98ba08c4064cd230eaf0aab36 GIT binary patch literal 4700 zcmZQzzzJA^G9WDO(C+|N1f%UAfVm)o5zc}z;Chh79XK+b11}}V4SBl6+;Xwa84e`fNr)^vz{uttPV?rF*A48m8Y4c<)$R9(TKF+5X OZV2?p5T}Pny959khANZ* literal 0 HcmV?d00001 diff --git a/test/correct_output/clm_snow_jennings/clm_snow_jennings.out.clm_output.00003.C.pfb b/test/correct_output/clm_snow_jennings/clm_snow_jennings.out.clm_output.00003.C.pfb new file mode 100644 index 0000000000000000000000000000000000000000..0b0d09e20463d504edb0c8412c0d48a7e5d9d7d5 GIT binary patch literal 4700 zcmZQzzzJA^G9WDO(C+|N1f%UAfVm)o5zc}z;Chh79oWyV@iBEY81i)SCFK?K`@cF2 zbvk$zQytIO?+!zpK9C?$?^u@qW~kG4n;%US63)CgcHj5fys{gm2S+%}xqd9- zvhE_gp-y+6^@!mS*=#q|X*>Q|Dr{~Hc0-&-uG@z)+o7l@bwcRWqeGn@%5i|~c88(^ zATIMLJsJ+wO$Yn`1Hng!qTxXQ*bDK;5U2krS%UpB#Oc33@9YQpV~ErL-rKt<1N|}7 T>4pV6W14{e7~=HsXqNy0g@Dez literal 0 HcmV?d00001 diff --git a/test/correct_output/clm_snow_jennings/clm_snow_jennings.out.clm_output.00004.C.pfb b/test/correct_output/clm_snow_jennings/clm_snow_jennings.out.clm_output.00004.C.pfb new file mode 100644 index 0000000000000000000000000000000000000000..6da55cabc6cb807517a33f0b73add188d8f1cb34 GIT binary patch literal 4700 zcmZQzzzJA^G9WDO(C+|N1f%UAfVm)o5zc}z;Chh79oYINtzcMVGvw*wa)$=TtNIQ@ zoer^+-+x(bk;4$D55%9J7A94&Zm83Co38)c(f%oB$kR(7R@bOM&bJ$-2S+%}pUeF6 z0@Gx>p-y*vPd(eHBV#wz>4}S5mUM2ju^Zwva@{_Z*$ze3=NEZQwjJv9P>us+w>uOa z0C8DH=|L3^%+UB4!u0i8!yDv}Ax{78GYvKZ W`eUfm^3@`;*MR;Q;`H!nmjD1xHK-8) literal 0 HcmV?d00001 diff --git a/test/correct_output/clm_snow_jennings/clm_snow_jennings.out.clm_output.00005.C.pfb b/test/correct_output/clm_snow_jennings/clm_snow_jennings.out.clm_output.00005.C.pfb new file mode 100644 index 0000000000000000000000000000000000000000..e30dd556b66fead50800acefb889e84e73977bcd GIT binary patch literal 4700 zcmZQzzzJA^G9WDO(C+|N1f%UAfVm)o5zc}z;Chh79hkNmdcI}sg3>)WXhQP1^ifNk zko{c_#TE@eeg5bVc{=#k8P=Ozat=eBJ`gX|SQ?Y`VyM%0>zxBGaw`9V(*JPKgyeDQ zqn0=!`+wOjI=f-#gCl}aS_B78NFJ9yYKarFA0`i@aoI~q9+y69i4(G4&~C gkUxev{X4|Kv>ND-Ax`gG@}Ol7FkKCGdU&);0A08~umAu6 literal 0 HcmV?d00001 diff --git a/test/correct_output/clm_snow_jennings/clm_snow_jennings.out.mask.pfb b/test/correct_output/clm_snow_jennings/clm_snow_jennings.out.mask.pfb new file mode 100644 index 0000000000000000000000000000000000000000..caa1ffcc9ae5e14ea209e182ea41063414464787 GIT binary patch literal 2100 zcmZQzzzJA^G9b+5(C+|N1f%UAfVm)o5zc}z;Chh7?LREwpb!{M2czj= NG#!lQgP~Rq004WFLFWJf literal 0 HcmV?d00001 diff --git a/test/correct_output/clm_snow_jennings/clm_snow_jennings.out.perm_x.pfb b/test/correct_output/clm_snow_jennings/clm_snow_jennings.out.perm_x.pfb new file mode 100644 index 0000000000000000000000000000000000000000..614ae8457418bf08b91ae85c3472f747047b5c19 GIT binary patch literal 2100 zcmZQzzzJA^G9b+5(C+|N1f%UAfVm)o5zc}z;Chh7?N81Gfmx&UXgG|fgVB62S`Lhs L1Eb}@&@Bf5p#O&H literal 0 HcmV?d00001 diff --git a/test/correct_output/clm_snow_jennings/clm_snow_jennings.out.perm_y.pfb b/test/correct_output/clm_snow_jennings/clm_snow_jennings.out.perm_y.pfb new file mode 100644 index 0000000000000000000000000000000000000000..614ae8457418bf08b91ae85c3472f747047b5c19 GIT binary patch literal 2100 zcmZQzzzJA^G9b+5(C+|N1f%UAfVm)o5zc}z;Chh7?N81Gfmx&UXgG|fgVB62S`Lhs L1Eb}@&@Bf5p#O&H literal 0 HcmV?d00001 diff --git a/test/correct_output/clm_snow_jennings/clm_snow_jennings.out.perm_z.pfb b/test/correct_output/clm_snow_jennings/clm_snow_jennings.out.perm_z.pfb new file mode 100644 index 0000000000000000000000000000000000000000..614ae8457418bf08b91ae85c3472f747047b5c19 GIT binary patch literal 2100 zcmZQzzzJA^G9b+5(C+|N1f%UAfVm)o5zc}z;Chh7?N81Gfmx&UXgG|fgVB62S`Lhs L1Eb}@&@Bf5p#O&H literal 0 HcmV?d00001 diff --git a/test/correct_output/clm_snow_jennings/clm_snow_jennings.out.porosity.pfb b/test/correct_output/clm_snow_jennings/clm_snow_jennings.out.porosity.pfb new file mode 100644 index 0000000000000000000000000000000000000000..13c2a612151e90033fa181e31aa41d94e2f5e8a5 GIT binary patch literal 2100 zcmZQzzzJA^G9b+5(C+|N1f%UAfVm)o5zc}z;Chh7?QeWN)E}eqZIm7jhtYH}nh!?H Ofzfhcv>X_^g#$IORs|{6T#}{3YicZkxk7$n$*8Io!tU{z~nt^@=AoeZyNFb0GI|Ze!d$ z=XRf|ZU24rA~jzPZ*|Op+`sqS*5myb|5mF;uNz3s(6C9)dE4YILpb+2hx2~)k;Ac% z@8^CH{RpS`xqLFr8y3hphcmg)Io!4%If#Ai*ZsM0-uhqJNHb^nNX|LtY~()A#pOKD h_j5lRtwm~FUU)>GJTB+?|Ms~1y=W^sD5ub2 zh|~7J|NlKd%OZBj)1UwUy%yCPY(LcL=l_2nmJBSkCyg#bQ;*K4zj^2XzhC$;Z=*eg z-VCJ)@o}j`7sq7|x;VN%7#~KXtH}h8r?i}b%f*zsYloU zc>fCqrdwr}YtiWSXnb_}b!hVFe02S!%A>1Cw;$d7wfny?aNfG9_7h6~z(Es|$EA;2 U;)Lw~x&IHts#Mdwk3*dX0I#{QRsaA1 literal 0 HcmV?d00001 diff --git a/test/correct_output/clm_snow_jennings/clm_snow_jennings.out.press.00003.pfb b/test/correct_output/clm_snow_jennings/clm_snow_jennings.out.press.00003.pfb new file mode 100644 index 0000000000000000000000000000000000000000..0f734e50df4faa3391ecc854abc77696ff5afa32 GIT binary patch literal 2100 zcmZQzzzJA^G9b+5(C+|N1f%UAfVm)o5zc}z;Chh79a#VWn|E4hHIuoCt4HT!>VN+K*Sc`k3-%EDJd_@K{PX``hvvPwX%C^Vc&HWb`SZyBcv4zrhIA}uhxb#s=oRIyt`(H4yn7`Yj1f>YU7{zagAnnqjOWmqIM8})HLA&?{?F)!zadC0=0h~hV*r7wAh>*4jLKk&& z5uA);MG@4{+ds)^>Vc1o(j|f9mvhhWexFj}<1CuxTW~Mjl?3YNRa|~@>%aJCeP{FB zbRp#N71!-9(Q^Q(tGDq4~N! z|K8QVS*9%Y@IZLTHrjmjOw3Ks?@ZXQ3_3byJE2e6*0aCuY}?-n?$u7UHrXQ{P?q}r z#A6?sd(N0E`RJMO)ALv5oc6qN5ObLDhO*R;1RwXg=gj-0UfPfQ>TJKCXhk}Cz z2M0R{hF)#+Kc$@VCkE_bAUXG!^Kp$)pZt$AehcAcI0->*e}dzWH$5eXX=nC-n0KRM z$GiUb>bcS8+e10brn{79=Dnp<14O9@*S)--mD)DZ3(+Pq`*rbKqG0Z` z59WRJ;e)Zq_jA7;^^Cv0de;!Gi#CbbZ&44-ee~did7phS_Rs@!zYz`0a%a6(6+IC> zCD#0kl1HC?X1-7JwfoVpMnki)wKM37_I}Balml}gyYRtK vXeLZBjC*H4)amO+D$38wJ?w`%eYV9w{F#V{{ZOZmZo0WK=EYk3Ax;AT%KI_W literal 0 HcmV?d00001 diff --git a/test/correct_output/clm_snow_jennings/clm_snow_jennings.out.satur.00001.pfb b/test/correct_output/clm_snow_jennings/clm_snow_jennings.out.satur.00001.pfb new file mode 100644 index 0000000000000000000000000000000000000000..33bea6a2df61fd2f4417a6d58b76313951934b81 GIT binary patch literal 2100 zcmZQzzzJA^G9b+5(C+|N1f%UAfVm)o5zc}z;Chh7?LR zXvX&~WQnmyrK8dK=<Pi4(J<6$0N2zP8~^|S literal 0 HcmV?d00001 diff --git a/test/correct_output/clm_snow_jennings/clm_snow_jennings.out.satur.00002.pfb b/test/correct_output/clm_snow_jennings/clm_snow_jennings.out.satur.00002.pfb new file mode 100644 index 0000000000000000000000000000000000000000..698810f1868ca57dfb5234a9f6a0a29b9320c47f GIT binary patch literal 2100 zcmZQzzzJA^G9b+5(C+|N1f%UAfVm)o5zc}z;Chh7?LR zXvXF4*?P-vMc}X;RbUwO%QsvRrquY;ezLfph7NvXN^HZR7 cDh`^EJT86I5+`JTiv7_|39GMeZyV|~03H@q0ssI2 literal 0 HcmV?d00001 diff --git a/test/correct_output/clm_snow_jennings/clm_snow_jennings.out.satur.00003.pfb b/test/correct_output/clm_snow_jennings/clm_snow_jennings.out.satur.00003.pfb new file mode 100644 index 0000000000000000000000000000000000000000..f8a475223a63cef48a766350fe89a6cda30b726a GIT binary patch literal 2100 zcmZQzzzJA^G9b+5(C+|N1f%UAfVm)o5zc}z;Chh7?LR zXvXTEc-~__)amO+3Tvm>71=}RVkljJ#z&VgM3YD7qw6PC9$h`U{pjWw*`IBZ{_>bL wk4!qBfIf72bUwPhgyhlHBlt%*1t}-F<=T@+=Mm6{E|1Pfx0jGSx_USt0IPB%0RR91 literal 0 HcmV?d00001 diff --git a/test/correct_output/clm_snow_jennings/clm_snow_jennings.out.satur.00004.pfb b/test/correct_output/clm_snow_jennings/clm_snow_jennings.out.satur.00004.pfb new file mode 100644 index 0000000000000000000000000000000000000000..f1378c6abe2ac3f56734c94af3aae325388447e2 GIT binary patch literal 2100 zcmZQzzzJA^G9b+5(C+|N1f%UAfVm)o5zc}z;Chh7?LR zXhuJqHv5Y`D*YLak4qk=4o1V&!T5w|Lh51i=rqh+g!pwM`NdpM-`hjzk5Kw88XuSZ z2OR2Q`qAal`RMlHlE-B~O#gfPvn`?vOawoXNq;7w4_zLek8Up^d35y%{?Sd@{cBZa U>>;!q4w{fWE`8JzCuF}20ACtDDgXcg literal 0 HcmV?d00001 diff --git a/test/correct_output/clm_snow_jennings/clm_snow_jennings.out.satur.00005.pfb b/test/correct_output/clm_snow_jennings/clm_snow_jennings.out.satur.00005.pfb new file mode 100644 index 0000000000000000000000000000000000000000..8ddb7318f1facaceda6c9b7bd5f34659cf47ee94 GIT binary patch literal 2100 zcmZQzzzJA^G9b+5(C+|N1f%UAfVm)o5zc}z;Chh7?LR zXhzNa9JkaSLN9~Tg!oI))WP`Z`eEk4>;!Z4w{fWE`8JzCuF}906G~V>i_@% literal 0 HcmV?d00001 diff --git a/test/correct_output/clm_snow_jennings/clm_snow_jennings.out.specific_storage.pfb b/test/correct_output/clm_snow_jennings/clm_snow_jennings.out.specific_storage.pfb new file mode 100644 index 0000000000000000000000000000000000000000..1297279b9130b6671929aa8adca791975d6420cf GIT binary patch literal 2100 zcmZQzzzJA^G9b+5(C+|N1f%UAfVm)o5zc}z;Chh7?KT|yzF_Oy-cfoq97fZ@Xg(M% O2S&?*(Q;tumID9^V_en% literal 0 HcmV?d00001 diff --git a/test/correct_output/clm_snow_jennings/clm_snow_jennings.out.sres.pfb b/test/correct_output/clm_snow_jennings/clm_snow_jennings.out.sres.pfb new file mode 100644 index 0000000000000000000000000000000000000000..5175c79baa774a4aed3ed1ca995b25e2f6d3b054 GIT binary patch literal 2100 zcmZQzzzJA^G9b+5(C+|N1f%UAfVm)o5zc}z;Chh7?OUoIx~~(d9;HXaVKg0#=7Z64 OV6+?l literal 0 HcmV?d00001 diff --git a/test/correct_output/clm_snow_jennings/clm_snow_jennings.out.ssat.pfb b/test/correct_output/clm_snow_jennings/clm_snow_jennings.out.ssat.pfb new file mode 100644 index 0000000000000000000000000000000000000000..caa1ffcc9ae5e14ea209e182ea41063414464787 GIT binary patch literal 2100 zcmZQzzzJA^G9b+5(C+|N1f%UAfVm)o5zc}z;Chh7?LRPDW9vtB?MR5MZ z`NkjYhC1D0B*$^>4VT>zr}s^NJxNWQHDjpL$aVWrW;+zE+jG+;LT{+kLpctR-R@9y z0K{b+rANboy6Is5e-Lme8W!|3s6U1{{l*L8k0DNf5V{5S#}KDKmM?h@^2ZRTKkmHH OA`JA$5T}Pny95C1R{;J1 literal 0 HcmV?d00001 diff --git a/test/correct_output/clm_snow_sza_damping/clm_snow_sza_damping.out.clm_output.00002.C.pfb b/test/correct_output/clm_snow_sza_damping/clm_snow_sza_damping.out.clm_output.00002.C.pfb new file mode 100644 index 0000000000000000000000000000000000000000..2d123743599188740764e550a90bd2653aaab72d GIT binary patch literal 4700 zcmZQzzzJA^G9WDO(C+|N1f%UAfVm)o5zc}z;Chh79XK*=V-A{~8S-?Axm})J@ezli zP6yX&yH4^@br|CGfdswnB|8PmhdOPyC8aG|At-pr(`&4L{Aydb$8MA!9N{oiM!mfF z^;Nr}PIqjpvtm2aZ#Ts0ef_*I4=>KF80s`~-9D7r4n=b_^n#Ob4t07c#{sh29f}Ts zxJ;w;XgE+e9qj)P1b-chh6DZYG{hf6owjj>_+yCEjR}@ue++SYrOjbJkUxeveVmWi Oa01XDL!2HS?GgaLWSqSK literal 0 HcmV?d00001 diff --git a/test/correct_output/clm_snow_sza_damping/clm_snow_sza_damping.out.clm_output.00003.C.pfb b/test/correct_output/clm_snow_sza_damping/clm_snow_sza_damping.out.clm_output.00003.C.pfb new file mode 100644 index 0000000000000000000000000000000000000000..3e8b97b126bf692f2eeab465ebb66d4b4632c7b0 GIT binary patch literal 4700 zcmZQzzzJA^G9WDO(C+|N1f%UAfVm)o5zc}z;Chh79oWw<%UKc{1f@f8(CG3oK01xA zA6*{CCsiFTeK2!^9EvX~7v|@G8}fAU%0IW=?-w}?ar!`lMD;7vgx^D*w%h!uM~h_( zACwlrL8HsV_~(<8#@~xqdYAt&iG}r#sKO#W@7-v>WQQorqJV z^EL)MFuer_jV=%4qtod6(dA)$Qq|$o2Qz0Ia@9Vh*$zcLsqWg|9YdWS(vg7Vc!#0` zARhB5JsJ+wO$Yn`1Hng!qTxXQ*bDK;5U2krS%Cd9#Oc33Z?}Q`F~sSA@9i?*0sS%5 T>4pU=lT?BJ7~=HsXqNy0`Ok_f literal 0 HcmV?d00001 diff --git a/test/correct_output/clm_snow_sza_damping/clm_snow_sza_damping.out.clm_output.00004.C.pfb b/test/correct_output/clm_snow_sza_damping/clm_snow_sza_damping.out.clm_output.00004.C.pfb new file mode 100644 index 0000000000000000000000000000000000000000..2e86d500887a865bceccaa9027eb03f66b7727bc GIT binary patch literal 4700 zcmZQzzzJA^G9WDO(C+|N1f%UAfVm)o5zc}z;Chh79oYJMx8%yILTODLG`hS7R2@2v zt{+_<#wS%By8Y<-RUL}U9g>-U1`K&R#7;_k>FXqiAxBHi6m!~X)(radl{Ww3P zbI8*j-;-ZU2Hvw9>h#3LQ|yg`&)Grf12|}O`6Ezu=rp>1ba@z`RCVa~qw7D1T(=Kp zwnI_%`Ivp|*+ZQk%5i|~c88(^ATG-&J*dKg85$o$nBM;%2o5?F4F~#>D8wH_oc>hn p4EDzmr+;ZX7lHgS#Oc3%MmKH){V~*O`6?cvUZ6jQI6XYtB>=(K)6oC` literal 0 HcmV?d00001 diff --git a/test/correct_output/clm_snow_sza_damping/clm_snow_sza_damping.out.clm_output.00005.C.pfb b/test/correct_output/clm_snow_sza_damping/clm_snow_sza_damping.out.clm_output.00005.C.pfb new file mode 100644 index 0000000000000000000000000000000000000000..c01978cc5ed44a7a2bb87b01a22294d39c5e890b GIT binary patch literal 4700 zcmZQzzzJA^G9WDO(C+|N1f%UAfVm)o5zc}z;Chh79hkQ1ioFPrg3{4AXmoiPADu?m zk1h}6ld2AvKA1UC4#gG?4}zDN40$^E*0Brg3~C&PIDH^qsCw5H7UrQ&+pTx@Jo8pI z0ZJ#~pwZ=Fd~_OJKe{}OPpUdx`e5cH*eyD{%Ko&H2$UAXL8HsV_~CKEZpLeSDd%r9hCJO7TBR{rTiI@?(^+#} z=4z`fu!GPIIB0Zv7$2QR*N-j_Ewpb!{M2czj= NG#!lQgP~Rq004WFLFWJf literal 0 HcmV?d00001 diff --git a/test/correct_output/clm_snow_sza_damping/clm_snow_sza_damping.out.perm_x.pfb b/test/correct_output/clm_snow_sza_damping/clm_snow_sza_damping.out.perm_x.pfb new file mode 100644 index 0000000000000000000000000000000000000000..614ae8457418bf08b91ae85c3472f747047b5c19 GIT binary patch literal 2100 zcmZQzzzJA^G9b+5(C+|N1f%UAfVm)o5zc}z;Chh7?N81Gfmx&UXgG|fgVB62S`Lhs L1Eb}@&@Bf5p#O&H literal 0 HcmV?d00001 diff --git a/test/correct_output/clm_snow_sza_damping/clm_snow_sza_damping.out.perm_y.pfb b/test/correct_output/clm_snow_sza_damping/clm_snow_sza_damping.out.perm_y.pfb new file mode 100644 index 0000000000000000000000000000000000000000..614ae8457418bf08b91ae85c3472f747047b5c19 GIT binary patch literal 2100 zcmZQzzzJA^G9b+5(C+|N1f%UAfVm)o5zc}z;Chh7?N81Gfmx&UXgG|fgVB62S`Lhs L1Eb}@&@Bf5p#O&H literal 0 HcmV?d00001 diff --git a/test/correct_output/clm_snow_sza_damping/clm_snow_sza_damping.out.perm_z.pfb b/test/correct_output/clm_snow_sza_damping/clm_snow_sza_damping.out.perm_z.pfb new file mode 100644 index 0000000000000000000000000000000000000000..614ae8457418bf08b91ae85c3472f747047b5c19 GIT binary patch literal 2100 zcmZQzzzJA^G9b+5(C+|N1f%UAfVm)o5zc}z;Chh7?N81Gfmx&UXgG|fgVB62S`Lhs L1Eb}@&@Bf5p#O&H literal 0 HcmV?d00001 diff --git a/test/correct_output/clm_snow_sza_damping/clm_snow_sza_damping.out.porosity.pfb b/test/correct_output/clm_snow_sza_damping/clm_snow_sza_damping.out.porosity.pfb new file mode 100644 index 0000000000000000000000000000000000000000..13c2a612151e90033fa181e31aa41d94e2f5e8a5 GIT binary patch literal 2100 zcmZQzzzJA^G9b+5(C+|N1f%UAfVm)o5zc}z;Chh7?QeWN)E}eqZIm7jhtYH}nh!?H Ofzfhcv>X_^|Nj-ES$G|W zIBoy?|39~_fwzY|{rUet-#`^FdkF0brH3B>`Tsu~dQTYGLuh>{O>I8RTo_Gl^9}6J z|Nk7)m)iuTYoT-l8lR9nx_WeeBbs^W@`U(=>~GqCfq}7a&N~h$%|#-glYn}3d2~Ly zeT3xE)g$;X7#OQIoV>grmA-<;N0+~ZCXdcX*H5ZEx_Wf`(apcS{|f`lW#zkkP?{e~ l3!?GS(fR25NtH)ek8VG@`F#8TFeF{}n^->7X#iqIAL9T3 literal 0 HcmV?d00001 diff --git a/test/correct_output/clm_snow_sza_damping/clm_snow_sza_damping.out.press.00002.pfb b/test/correct_output/clm_snow_sza_damping/clm_snow_sza_damping.out.press.00002.pfb new file mode 100644 index 0000000000000000000000000000000000000000..d7be441b48d1774bde631f89cc42af22b4aca2ce GIT binary patch literal 2100 zcmcJHy-LGS7=}-k_;&|l^%4ZJI4KDT+Ql1i7P>ol0gAKEE>(1L);VCowGJYhNx{FZ z6y0ijw9R>nyl_Y|G>|-bzV~}SW4urBm-OG6=qMU2!SH#Y{ZDE5m;MYp|L0L)d#{K0 z^%3)Q#P{n_$@boEcGnzN3pzcheda!9?Q8D!uw%Q;ll8>$si4z?+Gp-#*1qO`|FYeO z?c<9u<+)>CXAf$hxsO@;R7 zT=Nx|Gsm^BIj;BVljGcDj%&XX-)W#9g5$in=5x}+wNH;6*ZZ2|+@ptUzZ`#> zv1D(t>bRNHH)LO3?-k9n_Vs=>{x;Le<5eJ>6xV#pt)CMg}-T}y=y z3WAGHt$JJa94Q}Mju#@tFTZ?$j#Ad2_vdV%f*VaQl}#yzO5Oo&Vo`&ukuF z&r?Tzfjlzqx%yHb_ftP-Hc#b#$59_3kBob+zLdxPj{Y(0+U+PJjV&tqwq91>rTdXy znDs^XvOyYHRPt@TtiDV48+u~Cd-eO0qn06$jC-!Wl*j#&elxSHyVDWr$C3O+xldAN z?4!?&_qfkKGwxYAc~@R_9JNVrj^q~QLQ-bzqtA@@xX(T_?%8>&@|AfUCVOp>O5B7z zGWK14DUbVMvQ)*BvvdHO0!u#Tvu5f3AX%yBVYeTU)-8(uHRVvTk3O?&4)0?>_yv{2 BNRa>l literal 0 HcmV?d00001 diff --git a/test/correct_output/clm_snow_sza_damping/clm_snow_sza_damping.out.press.00004.pfb b/test/correct_output/clm_snow_sza_damping/clm_snow_sza_damping.out.press.00004.pfb new file mode 100644 index 0000000000000000000000000000000000000000..8968a8d9867acfbff494d2110d95f16d3ccaabc1 GIT binary patch literal 2100 zcmdUoJxjwt0EVwa5F7+4xQXcM4{%Hzs;(|Ay6Z0xCl@D2p`utE1pk49i=yXJ9o4Pf z-0I}eb}5FOw%%Lhfp7#|8c5ze_kNr)xli=t4BzSGI2nbc@%spepVIj6_h;Psd!9rt z+c{j^oOms{Z1*v{OeS38FPFYu%{9F~;2bx=>BHhb7^aDp1z>-FC21DO6Da$@$iBVR04p1xQ vXeLZBjC*H4)amO+D$38wJ?w`%eYV9w{F#V{{ZOZmZo0WK=EYk3Ax;AT%KI_W literal 0 HcmV?d00001 diff --git a/test/correct_output/clm_snow_sza_damping/clm_snow_sza_damping.out.satur.00001.pfb b/test/correct_output/clm_snow_sza_damping/clm_snow_sza_damping.out.satur.00001.pfb new file mode 100644 index 0000000000000000000000000000000000000000..ad5d27fc8158e71489af4a9cbf5d1a06cb7ff2d7 GIT binary patch literal 2100 zcmZQzzzJA^G9b+5(C+|N1f%UAfVm)o5zc}z;Chh7?LR zXvX&~WJ$M&&}mSbSUyZ0j7HZF6URjpQjac=n}6L%>8<1&FMCwl6OE59?~Nvp&PUfz zsyw=SboGJ( H4Rsm-7~vkv literal 0 HcmV?d00001 diff --git a/test/correct_output/clm_snow_sza_damping/clm_snow_sza_damping.out.satur.00002.pfb b/test/correct_output/clm_snow_sza_damping/clm_snow_sza_damping.out.satur.00002.pfb new file mode 100644 index 0000000000000000000000000000000000000000..9a50c030c066553e3a24f63f96dd63a202dd5dc7 GIT binary patch literal 2100 zcmZQzzzJA^G9b+5(C+|N1f%UAfVm)o5zc}z;Chh7?LR zXvXF4*>k}jLZ8P$qsw1}s)LEc_~`o4&$cMt`@y*Yjh>6fN0*-im7fo#VSJc6n0|D57#}7N yQxDS*lZWwP^5`_W{ss0&Hzlm$Q*eaR&Nygvc^Ds^M%Rxn595=n4wpWdIgS9dOEHQ7 literal 0 HcmV?d00001 diff --git a/test/correct_output/clm_snow_sza_damping/clm_snow_sza_damping.out.satur.00003.pfb b/test/correct_output/clm_snow_sza_damping/clm_snow_sza_damping.out.satur.00003.pfb new file mode 100644 index 0000000000000000000000000000000000000000..352a757737ba839cb3532d9ed7203999308ec253 GIT binary patch literal 2100 zcmZQzzzJA^G9b+5(C+|N1f%UAfVm)o5zc}z;Chh7?LR zXvXTEWYxAOjn+X^kIqLok5qX=>e0>Dw!dzquy#r^sk9E7K6F01d8En{Qjc!Fw*A=_ z=`T+XpF*RLqwxvJqpPPjpOF2h?2m2=Qa zXhuJqcJZ1$guV`?3Gr{Bse|#+^~218$;0?C8m1my9>#~sqpQc{UpJCp%&lT$Pa18D zrXHP-ZXT)fgw&&(Z)1P9MRb9=k`I*jgVLU8d~|ttG<`5Wx_+2Gm^_S+Za%s^j1Myp rrVqwP*Y9J0bW`>O)9h(bdIk;}T^`0qr_uGJ%ftAjs>7uZX3jJKsd^P1 literal 0 HcmV?d00001 diff --git a/test/correct_output/clm_snow_sza_damping/clm_snow_sza_damping.out.satur.00005.pfb b/test/correct_output/clm_snow_sza_damping/clm_snow_sza_damping.out.satur.00005.pfb new file mode 100644 index 0000000000000000000000000000000000000000..94732cfcc6ad947e26406b097bebd746eeffc760 GIT binary patch literal 2100 zcmZQzzzJA^G9b+5(C+|N1f%UAfVm)o5zc}z;Chh7?LR zXhzNayh+F&LJQ-d(d9+Z)S>gy%^_7DT|K(}=;jOAUpJClT-&>zM0x|7dUQUzd8En{ zQjc!_di%32{L}aFJ%`dSq4YB}KDzuvG<`5Wx_+2Gm^_S+Za%s^j1MyprVqwP*Z!Rw literal 0 HcmV?d00001 diff --git a/test/correct_output/clm_snow_sza_damping/clm_snow_sza_damping.out.specific_storage.pfb b/test/correct_output/clm_snow_sza_damping/clm_snow_sza_damping.out.specific_storage.pfb new file mode 100644 index 0000000000000000000000000000000000000000..1297279b9130b6671929aa8adca791975d6420cf GIT binary patch literal 2100 zcmZQzzzJA^G9b+5(C+|N1f%UAfVm)o5zc}z;Chh7?KT|yzF_Oy-cfoq97fZ@Xg(M% O2S&?*(Q;tumID9^V_en% literal 0 HcmV?d00001 diff --git a/test/correct_output/clm_snow_sza_damping/clm_snow_sza_damping.out.sres.pfb b/test/correct_output/clm_snow_sza_damping/clm_snow_sza_damping.out.sres.pfb new file mode 100644 index 0000000000000000000000000000000000000000..5175c79baa774a4aed3ed1ca995b25e2f6d3b054 GIT binary patch literal 2100 zcmZQzzzJA^G9b+5(C+|N1f%UAfVm)o5zc}z;Chh7?OUoIx~~(d9;HXaVKg0#=7Z64 OV6+?l literal 0 HcmV?d00001 diff --git a/test/correct_output/clm_snow_sza_damping/clm_snow_sza_damping.out.ssat.pfb b/test/correct_output/clm_snow_sza_damping/clm_snow_sza_damping.out.ssat.pfb new file mode 100644 index 0000000000000000000000000000000000000000..caa1ffcc9ae5e14ea209e182ea41063414464787 GIT binary patch literal 2100 zcmZQzzzJA^G9b+5(C+|N1f%UAfVm)o5zc}z;Chh7?LR 0: + filename = f"/{run_name}.out.clm_output.{timestep}.C.pfb" + if not pf_test_file( + dir_name + filename, + correct_output_dir_name + filename, + f"Max difference in CLM output for timestep {timestep}", + ): + passed = False + +rm(dir_name) + +if passed: + print(f"{run_name} : PASSED") +else: + print(f"{run_name} : FAILED") + sys.exit(1) diff --git a/test/python/clm/clm_snow_defaults.py b/test/python/clm/clm_snow_defaults.py index 91e73791b..8e8d977e6 100644 --- a/test/python/clm/clm_snow_defaults.py +++ b/test/python/clm/clm_snow_defaults.py @@ -6,18 +6,33 @@ # This ensures backward compatibility for existing workflows. # # New keys tested (all at defaults): -# Solver.CLM.SnowPartition = "CLM" -# Solver.CLM.WetbulbThreshold = 274.15 -# Solver.CLM.ThinSnowDamping = 1.0 -# Solver.CLM.ThinSnowThreshold = 50.0 -# Solver.CLM.AlbedoScheme = "CLM" -# Solver.CLM.AlbedoVisNew = 0.95 -# Solver.CLM.AlbedoNirNew = 0.65 -# Solver.CLM.AlbedoMin = 0.4 -# Solver.CLM.AlbedoDecayVis = 0.5 -# Solver.CLM.AlbedoDecayNir = 0.2 -# Solver.CLM.AlbedoAccumA = 0.94 -# Solver.CLM.AlbedoThawA = 0.82 +# Rain-snow partitioning: +# Solver.CLM.SnowPartition = "CLM" +# Solver.CLM.WetbulbThreshold = 274.15 +# Solver.CLM.SnowTCrit = 2.5 +# Solver.CLM.SnowTLow = 273.16 +# Solver.CLM.SnowTHigh = 275.16 +# Solver.CLM.SnowTransitionWidth = 1.0 +# Solver.CLM.DaiCoeffA/B/C/D (Dai 2008 method) +# Solver.CLM.JenningsCoeffA/B/G (Jennings 2018 method) +# Melt damping: +# Solver.CLM.ThinSnowDamping = 0.0 +# Solver.CLM.ThinSnowThreshold = 50.0 +# Solver.CLM.SZASnowDamping = 1.0 (disabled) +# Solver.CLM.SZADampingCoszenRef = 0.5 +# Solver.CLM.SZADampingCoszenMin = 0.1 +# Albedo: +# Solver.CLM.AlbedoScheme = "CLM" +# Solver.CLM.AlbedoVisNew = 0.95 +# Solver.CLM.AlbedoNirNew = 0.65 +# Solver.CLM.AlbedoMin = 0.4 +# Solver.CLM.AlbedoDecayVis = 0.5 +# Solver.CLM.AlbedoDecayNir = 0.2 +# Solver.CLM.AlbedoAccumA = 0.94 +# Solver.CLM.AlbedoThawA = 0.82 +# Fractional snow cover: +# Solver.CLM.FracSnoScheme = "CLM" +# Solver.CLM.FracSnoRoughness = 0.01 # ----------------------------------------------------------------------------- import sys diff --git a/test/python/clm/clm_snow_jennings.py b/test/python/clm/clm_snow_jennings.py new file mode 100644 index 000000000..ff7f1c09c --- /dev/null +++ b/test/python/clm/clm_snow_jennings.py @@ -0,0 +1,421 @@ +# ----------------------------------------------------------------------------- +# CLM Snow Jennings Test +# +# This test verifies that the Jennings (2018) rain-snow partitioning method works. +# It uses bivariate logistic regression with temperature and relative humidity: +# psnow = 1 / (1 + exp(a + b*T + g*RH)) +# +# The Jennings method accounts for both temperature and humidity effects on +# precipitation phase, which is important for accurate snow accumulation +# prediction across diverse climates. +# +# Reference: Jennings et al. (2018) Nature Communications, doi:10.1038/s41467-018-03629-7 +# +# New keys tested: +# Solver.CLM.SnowPartition = "Jennings" +# Solver.CLM.JenningsCoeffA = -10.04 +# Solver.CLM.JenningsCoeffB = 1.41 +# Solver.CLM.JenningsCoeffG = 0.09 +# ----------------------------------------------------------------------------- + +import sys +import argparse + +from parflow import Run +from parflow.tools.fs import mkdir, cp, get_absolute_path, rm +from parflow.tools.compare import pf_test_file + +run_name = "clm_snow_jennings" +clm = Run(run_name, __file__) + +# ----------------------------------------------------------------------------- +# Making output directories and copying input files +# ----------------------------------------------------------------------------- + +dir_name = get_absolute_path("test_output/" + run_name) +mkdir(dir_name) + +directories = [ + "qflx_evap_grnd", + "eflx_lh_tot", + "qflx_evap_tot", + "qflx_tran_veg", + "correct_output", + "qflx_infl", + "swe_out", + "eflx_lwrad_out", + "t_grnd", + "diag_out", + "qflx_evap_soi", + "eflx_soil_grnd", + "eflx_sh_tot", + "qflx_evap_veg", + "qflx_top_soil", +] + +for directory in directories: + mkdir(dir_name + "/" + directory) + +cp("$PF_SRC/test/tcl/clm/drv_clmin.dat", dir_name) +cp("$PF_SRC/test/tcl/clm/drv_vegm.dat", dir_name) +cp("$PF_SRC/test/tcl/clm/drv_vegp.dat", dir_name) +cp("$PF_SRC/test/tcl/clm/snow_forcing.1hr.txt", dir_name) + +# ----------------------------------------------------------------------------- +# File input version number +# ----------------------------------------------------------------------------- + +clm.FileVersion = 4 + +# ----------------------------------------------------------------------------- +# Process Topology +# ----------------------------------------------------------------------------- + +parser = argparse.ArgumentParser() +parser.add_argument("-p", "--p", default=1) +parser.add_argument("-q", "--q", default=1) +parser.add_argument("-r", "--r", default=1) +args = parser.parse_args() + +clm.Process.Topology.P = args.p +clm.Process.Topology.Q = args.q +clm.Process.Topology.R = args.r + +# ----------------------------------------------------------------------------- +# Computational Grid +# ----------------------------------------------------------------------------- + +clm.ComputationalGrid.Lower.X = 0.0 +clm.ComputationalGrid.Lower.Y = 0.0 +clm.ComputationalGrid.Lower.Z = 0.0 + +clm.ComputationalGrid.DX = 1000.0 +clm.ComputationalGrid.DY = 1000.0 +clm.ComputationalGrid.DZ = 0.5 + +clm.ComputationalGrid.NX = 5 +clm.ComputationalGrid.NY = 5 +clm.ComputationalGrid.NZ = 10 + +# ----------------------------------------------------------------------------- +# The Names of the GeomInputs +# ----------------------------------------------------------------------------- + +clm.GeomInput.Names = "domain_input" + +# ----------------------------------------------------------------------------- +# Domain Geometry Input +# ----------------------------------------------------------------------------- + +clm.GeomInput.domain_input.InputType = "Box" +clm.GeomInput.domain_input.GeomName = "domain" + +# ----------------------------------------------------------------------------- +# Domain Geometry +# ----------------------------------------------------------------------------- + +clm.Geom.domain.Lower.X = 0.0 +clm.Geom.domain.Lower.Y = 0.0 +clm.Geom.domain.Lower.Z = 0.0 + +clm.Geom.domain.Upper.X = 5000.0 +clm.Geom.domain.Upper.Y = 5000.0 +clm.Geom.domain.Upper.Z = 5.0 + +clm.Geom.domain.Patches = "x_lower x_upper y_lower y_upper z_lower z_upper" + +# ----------------------------------------------------------------------------- +# Perm +# ----------------------------------------------------------------------------- + +clm.Geom.Perm.Names = "domain" +clm.Geom.domain.Perm.Type = "Constant" +clm.Geom.domain.Perm.Value = 0.2 + +clm.Perm.TensorType = "TensorByGeom" +clm.Geom.Perm.TensorByGeom.Names = "domain" + +clm.Geom.domain.Perm.TensorValX = 1.0 +clm.Geom.domain.Perm.TensorValY = 1.0 +clm.Geom.domain.Perm.TensorValZ = 1.0 + +# ----------------------------------------------------------------------------- +# Specific Storage +# ----------------------------------------------------------------------------- + +clm.SpecificStorage.Type = "Constant" +clm.SpecificStorage.GeomNames = "domain" +clm.Geom.domain.SpecificStorage.Value = 1.0e-6 + +# ----------------------------------------------------------------------------- +# Phases +# ----------------------------------------------------------------------------- + +clm.Phase.Names = "water" +clm.Phase.water.Density.Type = "Constant" +clm.Phase.water.Density.Value = 1.0 +clm.Phase.water.Viscosity.Type = "Constant" +clm.Phase.water.Viscosity.Value = 1.0 + +# ----------------------------------------------------------------------------- +# Contaminants +# ----------------------------------------------------------------------------- + +clm.Contaminants.Names = "" + +# ----------------------------------------------------------------------------- +# Gravity +# ----------------------------------------------------------------------------- + +clm.Gravity = 1.0 + +# ----------------------------------------------------------------------------- +# Setup timing info +# ----------------------------------------------------------------------------- + +clm.TimingInfo.BaseUnit = 1.0 +clm.TimingInfo.StartCount = 0 +clm.TimingInfo.StartTime = 0.0 +clm.TimingInfo.StopTime = 5 +clm.TimingInfo.DumpInterval = -1 +clm.TimeStep.Type = "Constant" +clm.TimeStep.Value = 1.0 + +# ----------------------------------------------------------------------------- +# Porosity +# ----------------------------------------------------------------------------- + +clm.Geom.Porosity.GeomNames = "domain" +clm.Geom.domain.Porosity.Type = "Constant" +clm.Geom.domain.Porosity.Value = 0.390 + +# ----------------------------------------------------------------------------- +# Domain +# ----------------------------------------------------------------------------- + +clm.Domain.GeomName = "domain" + +# ----------------------------------------------------------------------------- +# Mobility +# ----------------------------------------------------------------------------- + +clm.Phase.water.Mobility.Type = "Constant" +clm.Phase.water.Mobility.Value = 1.0 + +# ----------------------------------------------------------------------------- +# Relative Permeability +# ----------------------------------------------------------------------------- + +clm.Phase.RelPerm.Type = "VanGenuchten" +clm.Phase.RelPerm.GeomNames = "domain" +clm.Geom.domain.RelPerm.Alpha = 3.5 +clm.Geom.domain.RelPerm.N = 2.0 + +# ----------------------------------------------------------------------------- +# Saturation +# ----------------------------------------------------------------------------- + +clm.Phase.Saturation.Type = "VanGenuchten" +clm.Phase.Saturation.GeomNames = "domain" +clm.Geom.domain.Saturation.Alpha = 3.5 +clm.Geom.domain.Saturation.N = 2.0 +clm.Geom.domain.Saturation.SRes = 0.01 +clm.Geom.domain.Saturation.SSat = 1.0 + +# ----------------------------------------------------------------------------- +# Wells +# ----------------------------------------------------------------------------- + +clm.Wells.Names = "" + +# ----------------------------------------------------------------------------- +# Time Cycles +# ----------------------------------------------------------------------------- + +clm.Cycle.Names = "constant" +clm.Cycle.constant.Names = "alltime" +clm.Cycle.constant.alltime.Length = 1 +clm.Cycle.constant.Repeat = -1 + +# ----------------------------------------------------------------------------- +# Boundary Conditions: Pressure +# ----------------------------------------------------------------------------- + +clm.BCPressure.PatchNames = "x_lower x_upper y_lower y_upper z_lower z_upper" + +clm.Patch.x_lower.BCPressure.Type = "FluxConst" +clm.Patch.x_lower.BCPressure.Cycle = "constant" +clm.Patch.x_lower.BCPressure.alltime.Value = 0.0 + +clm.Patch.y_lower.BCPressure.Type = "FluxConst" +clm.Patch.y_lower.BCPressure.Cycle = "constant" +clm.Patch.y_lower.BCPressure.alltime.Value = 0.0 + +clm.Patch.z_lower.BCPressure.Type = "FluxConst" +clm.Patch.z_lower.BCPressure.Cycle = "constant" +clm.Patch.z_lower.BCPressure.alltime.Value = 0.0 + +clm.Patch.x_upper.BCPressure.Type = "FluxConst" +clm.Patch.x_upper.BCPressure.Cycle = "constant" +clm.Patch.x_upper.BCPressure.alltime.Value = 0.0 + +clm.Patch.y_upper.BCPressure.Type = "FluxConst" +clm.Patch.y_upper.BCPressure.Cycle = "constant" +clm.Patch.y_upper.BCPressure.alltime.Value = 0.0 + +clm.Patch.z_upper.BCPressure.Type = "OverlandFlow" +clm.Patch.z_upper.BCPressure.Cycle = "constant" +clm.Patch.z_upper.BCPressure.alltime.Value = 0.0 + +# ----------------------------------------------------------------------------- +# Topo slopes in x-direction +# ----------------------------------------------------------------------------- + +clm.TopoSlopesX.Type = "Constant" +clm.TopoSlopesX.GeomNames = "domain" +clm.TopoSlopesX.Geom.domain.Value = -0.001 + +# ----------------------------------------------------------------------------- +# Topo slopes in y-direction +# ----------------------------------------------------------------------------- + +clm.TopoSlopesY.Type = "Constant" +clm.TopoSlopesY.GeomNames = "domain" +clm.TopoSlopesY.Geom.domain.Value = 0.001 + +# ----------------------------------------------------------------------------- +# Mannings coefficient +# ----------------------------------------------------------------------------- + +clm.Mannings.Type = "Constant" +clm.Mannings.GeomNames = "domain" +clm.Mannings.Geom.domain.Value = 5.52e-6 + +# ----------------------------------------------------------------------------- +# Phase sources +# ----------------------------------------------------------------------------- + +clm.PhaseSources.water.Type = "Constant" +clm.PhaseSources.water.GeomNames = "domain" +clm.PhaseSources.water.Geom.domain.Value = 0.0 + +# ----------------------------------------------------------------------------- +# Exact solution specification for error calculations +# ----------------------------------------------------------------------------- + +clm.KnownSolution = "NoKnownSolution" + +# ----------------------------------------------------------------------------- +# Set solver parameters +# ----------------------------------------------------------------------------- + +clm.Solver = "Richards" +clm.Solver.MaxIter = 500 + +clm.Solver.Nonlinear.MaxIter = 15 +clm.Solver.Nonlinear.ResidualTol = 1e-9 +clm.Solver.Nonlinear.EtaChoice = "EtaConstant" +clm.Solver.Nonlinear.EtaValue = 0.01 +clm.Solver.Nonlinear.UseJacobian = True +clm.Solver.Nonlinear.StepTol = 1e-20 +clm.Solver.Nonlinear.Globalization = "LineSearch" +clm.Solver.Linear.KrylovDimension = 15 +clm.Solver.Linear.MaxRestart = 2 + +clm.Solver.Linear.Preconditioner = "PFMG" +clm.Solver.PrintSubsurf = False +clm.Solver.Drop = 1e-20 +clm.Solver.AbsTol = 1e-9 + +# ----------------------------------------------------------------------------- +# CLM Settings +# ----------------------------------------------------------------------------- + +clm.Solver.LSM = "CLM" +clm.Solver.CLM.MetForcing = "1D" +clm.Solver.CLM.MetFileName = "snow_forcing.1hr.txt" +clm.Solver.CLM.MetFilePath = "." + +clm.Solver.WriteSiloCLM = False +clm.Solver.WriteSiloEvapTrans = False +clm.Solver.WriteSiloOverlandBCFlux = False +clm.Solver.PrintCLM = True + +clm.Solver.CLM.Print1dOut = False +clm.Solver.BinaryOutDir = False +clm.Solver.WriteCLMBinary = False +clm.Solver.CLM.CLMDumpInterval = 1 +clm.Solver.CLM.WriteLogs = False +clm.Solver.CLM.WriteLastRST = True +clm.Solver.CLM.DailyRST = True +clm.Solver.CLM.SingleFile = True + +# ----------------------------------------------------------------------------- +# Snow Partitioning - Use Jennings (2018) bivariate logistic method +# psnow = 1 / (1 + exp(a + b*T + g*RH)) +# T in Celsius, RH in percent +# These are the default coefficients from Jennings et al. (2018) +# ----------------------------------------------------------------------------- + +clm.Solver.CLM.SnowPartition = "Jennings" +clm.Solver.CLM.JenningsCoeffA = -10.04 +clm.Solver.CLM.JenningsCoeffB = 1.41 +clm.Solver.CLM.JenningsCoeffG = 0.09 + +# ----------------------------------------------------------------------------- +# Initial conditions: water pressure +# ----------------------------------------------------------------------------- + +clm.ICPressure.Type = "HydroStaticPatch" +clm.ICPressure.GeomNames = "domain" +clm.Geom.domain.ICPressure.Value = -2.0 +clm.Geom.domain.ICPressure.RefGeom = "domain" +clm.Geom.domain.ICPressure.RefPatch = "z_upper" + +# ----------------------------------------------------------------------------- +# Run ParFlow +# ----------------------------------------------------------------------------- + +clm.run(working_directory=dir_name) + +# ----------------------------------------------------------------------------- +# Tests - Compare pressure, saturation, and CLM output +# ----------------------------------------------------------------------------- + +correct_output_dir_name = get_absolute_path("$PF_SRC/test/correct_output/" + run_name) + +passed = True + +for i in range(6): + timestep = str(i).rjust(5, "0") + filename = f"/{run_name}.out.press.{timestep}.pfb" + if not pf_test_file( + dir_name + filename, + correct_output_dir_name + filename, + f"Max difference in Pressure for timestep {timestep}", + ): + passed = False + filename = f"/{run_name}.out.satur.{timestep}.pfb" + if not pf_test_file( + dir_name + filename, + correct_output_dir_name + filename, + f"Max difference in Saturation for timestep {timestep}", + ): + passed = False + + if i > 0: + filename = f"/{run_name}.out.clm_output.{timestep}.C.pfb" + if not pf_test_file( + dir_name + filename, + correct_output_dir_name + filename, + f"Max difference in CLM output for timestep {timestep}", + ): + passed = False + +rm(dir_name) + +if passed: + print(f"{run_name} : PASSED") +else: + print(f"{run_name} : FAILED") + sys.exit(1) diff --git a/test/python/clm/clm_snow_sza_damping.py b/test/python/clm/clm_snow_sza_damping.py new file mode 100644 index 000000000..3b1b5daae --- /dev/null +++ b/test/python/clm/clm_snow_sza_damping.py @@ -0,0 +1,420 @@ +# ----------------------------------------------------------------------------- +# CLM Snow SZA Damping Test +# +# This test verifies that the solar zenith angle (SZA) based snow melt damping +# works correctly. At high solar zenith angles (low sun), CLM underestimates +# snow albedo, causing excessive melt. This damping compensates by reducing +# melt energy at high SZA. +# +# The damping varies linearly from 1.0 (no damping) at coszen >= reference +# to the damping factor at coszen <= minimum. +# +# Reference: Dang et al. (2019) The Cryosphere, doi:10.5194/tc-13-2325-2019 +# +# New keys tested: +# Solver.CLM.SZASnowDamping = 0.8 # 20% energy reduction at high SZA +# Solver.CLM.SZADampingCoszenRef = 0.5 # Reference coszen (60 deg) +# Solver.CLM.SZADampingCoszenMin = 0.1 # Min coszen (84 deg) +# ----------------------------------------------------------------------------- + +import sys +import argparse + +from parflow import Run +from parflow.tools.fs import mkdir, cp, get_absolute_path, rm +from parflow.tools.compare import pf_test_file + +run_name = "clm_snow_sza_damping" +clm = Run(run_name, __file__) + +# ----------------------------------------------------------------------------- +# Making output directories and copying input files +# ----------------------------------------------------------------------------- + +dir_name = get_absolute_path("test_output/" + run_name) +mkdir(dir_name) + +directories = [ + "qflx_evap_grnd", + "eflx_lh_tot", + "qflx_evap_tot", + "qflx_tran_veg", + "correct_output", + "qflx_infl", + "swe_out", + "eflx_lwrad_out", + "t_grnd", + "diag_out", + "qflx_evap_soi", + "eflx_soil_grnd", + "eflx_sh_tot", + "qflx_evap_veg", + "qflx_top_soil", +] + +for directory in directories: + mkdir(dir_name + "/" + directory) + +cp("$PF_SRC/test/tcl/clm/drv_clmin.dat", dir_name) +cp("$PF_SRC/test/tcl/clm/drv_vegm.dat", dir_name) +cp("$PF_SRC/test/tcl/clm/drv_vegp.dat", dir_name) +cp("$PF_SRC/test/tcl/clm/snow_forcing.1hr.txt", dir_name) + +# ----------------------------------------------------------------------------- +# File input version number +# ----------------------------------------------------------------------------- + +clm.FileVersion = 4 + +# ----------------------------------------------------------------------------- +# Process Topology +# ----------------------------------------------------------------------------- + +parser = argparse.ArgumentParser() +parser.add_argument("-p", "--p", default=1) +parser.add_argument("-q", "--q", default=1) +parser.add_argument("-r", "--r", default=1) +args = parser.parse_args() + +clm.Process.Topology.P = args.p +clm.Process.Topology.Q = args.q +clm.Process.Topology.R = args.r + +# ----------------------------------------------------------------------------- +# Computational Grid +# ----------------------------------------------------------------------------- + +clm.ComputationalGrid.Lower.X = 0.0 +clm.ComputationalGrid.Lower.Y = 0.0 +clm.ComputationalGrid.Lower.Z = 0.0 + +clm.ComputationalGrid.DX = 1000.0 +clm.ComputationalGrid.DY = 1000.0 +clm.ComputationalGrid.DZ = 0.5 + +clm.ComputationalGrid.NX = 5 +clm.ComputationalGrid.NY = 5 +clm.ComputationalGrid.NZ = 10 + +# ----------------------------------------------------------------------------- +# The Names of the GeomInputs +# ----------------------------------------------------------------------------- + +clm.GeomInput.Names = "domain_input" + +# ----------------------------------------------------------------------------- +# Domain Geometry Input +# ----------------------------------------------------------------------------- + +clm.GeomInput.domain_input.InputType = "Box" +clm.GeomInput.domain_input.GeomName = "domain" + +# ----------------------------------------------------------------------------- +# Domain Geometry +# ----------------------------------------------------------------------------- + +clm.Geom.domain.Lower.X = 0.0 +clm.Geom.domain.Lower.Y = 0.0 +clm.Geom.domain.Lower.Z = 0.0 + +clm.Geom.domain.Upper.X = 5000.0 +clm.Geom.domain.Upper.Y = 5000.0 +clm.Geom.domain.Upper.Z = 5.0 + +clm.Geom.domain.Patches = "x_lower x_upper y_lower y_upper z_lower z_upper" + +# ----------------------------------------------------------------------------- +# Perm +# ----------------------------------------------------------------------------- + +clm.Geom.Perm.Names = "domain" +clm.Geom.domain.Perm.Type = "Constant" +clm.Geom.domain.Perm.Value = 0.2 + +clm.Perm.TensorType = "TensorByGeom" +clm.Geom.Perm.TensorByGeom.Names = "domain" + +clm.Geom.domain.Perm.TensorValX = 1.0 +clm.Geom.domain.Perm.TensorValY = 1.0 +clm.Geom.domain.Perm.TensorValZ = 1.0 + +# ----------------------------------------------------------------------------- +# Specific Storage +# ----------------------------------------------------------------------------- + +clm.SpecificStorage.Type = "Constant" +clm.SpecificStorage.GeomNames = "domain" +clm.Geom.domain.SpecificStorage.Value = 1.0e-6 + +# ----------------------------------------------------------------------------- +# Phases +# ----------------------------------------------------------------------------- + +clm.Phase.Names = "water" +clm.Phase.water.Density.Type = "Constant" +clm.Phase.water.Density.Value = 1.0 +clm.Phase.water.Viscosity.Type = "Constant" +clm.Phase.water.Viscosity.Value = 1.0 + +# ----------------------------------------------------------------------------- +# Contaminants +# ----------------------------------------------------------------------------- + +clm.Contaminants.Names = "" + +# ----------------------------------------------------------------------------- +# Gravity +# ----------------------------------------------------------------------------- + +clm.Gravity = 1.0 + +# ----------------------------------------------------------------------------- +# Setup timing info +# ----------------------------------------------------------------------------- + +clm.TimingInfo.BaseUnit = 1.0 +clm.TimingInfo.StartCount = 0 +clm.TimingInfo.StartTime = 0.0 +clm.TimingInfo.StopTime = 5 +clm.TimingInfo.DumpInterval = -1 +clm.TimeStep.Type = "Constant" +clm.TimeStep.Value = 1.0 + +# ----------------------------------------------------------------------------- +# Porosity +# ----------------------------------------------------------------------------- + +clm.Geom.Porosity.GeomNames = "domain" +clm.Geom.domain.Porosity.Type = "Constant" +clm.Geom.domain.Porosity.Value = 0.390 + +# ----------------------------------------------------------------------------- +# Domain +# ----------------------------------------------------------------------------- + +clm.Domain.GeomName = "domain" + +# ----------------------------------------------------------------------------- +# Mobility +# ----------------------------------------------------------------------------- + +clm.Phase.water.Mobility.Type = "Constant" +clm.Phase.water.Mobility.Value = 1.0 + +# ----------------------------------------------------------------------------- +# Relative Permeability +# ----------------------------------------------------------------------------- + +clm.Phase.RelPerm.Type = "VanGenuchten" +clm.Phase.RelPerm.GeomNames = "domain" +clm.Geom.domain.RelPerm.Alpha = 3.5 +clm.Geom.domain.RelPerm.N = 2.0 + +# ----------------------------------------------------------------------------- +# Saturation +# ----------------------------------------------------------------------------- + +clm.Phase.Saturation.Type = "VanGenuchten" +clm.Phase.Saturation.GeomNames = "domain" +clm.Geom.domain.Saturation.Alpha = 3.5 +clm.Geom.domain.Saturation.N = 2.0 +clm.Geom.domain.Saturation.SRes = 0.01 +clm.Geom.domain.Saturation.SSat = 1.0 + +# ----------------------------------------------------------------------------- +# Wells +# ----------------------------------------------------------------------------- + +clm.Wells.Names = "" + +# ----------------------------------------------------------------------------- +# Time Cycles +# ----------------------------------------------------------------------------- + +clm.Cycle.Names = "constant" +clm.Cycle.constant.Names = "alltime" +clm.Cycle.constant.alltime.Length = 1 +clm.Cycle.constant.Repeat = -1 + +# ----------------------------------------------------------------------------- +# Boundary Conditions: Pressure +# ----------------------------------------------------------------------------- + +clm.BCPressure.PatchNames = "x_lower x_upper y_lower y_upper z_lower z_upper" + +clm.Patch.x_lower.BCPressure.Type = "FluxConst" +clm.Patch.x_lower.BCPressure.Cycle = "constant" +clm.Patch.x_lower.BCPressure.alltime.Value = 0.0 + +clm.Patch.y_lower.BCPressure.Type = "FluxConst" +clm.Patch.y_lower.BCPressure.Cycle = "constant" +clm.Patch.y_lower.BCPressure.alltime.Value = 0.0 + +clm.Patch.z_lower.BCPressure.Type = "FluxConst" +clm.Patch.z_lower.BCPressure.Cycle = "constant" +clm.Patch.z_lower.BCPressure.alltime.Value = 0.0 + +clm.Patch.x_upper.BCPressure.Type = "FluxConst" +clm.Patch.x_upper.BCPressure.Cycle = "constant" +clm.Patch.x_upper.BCPressure.alltime.Value = 0.0 + +clm.Patch.y_upper.BCPressure.Type = "FluxConst" +clm.Patch.y_upper.BCPressure.Cycle = "constant" +clm.Patch.y_upper.BCPressure.alltime.Value = 0.0 + +clm.Patch.z_upper.BCPressure.Type = "OverlandFlow" +clm.Patch.z_upper.BCPressure.Cycle = "constant" +clm.Patch.z_upper.BCPressure.alltime.Value = 0.0 + +# ----------------------------------------------------------------------------- +# Topo slopes in x-direction +# ----------------------------------------------------------------------------- + +clm.TopoSlopesX.Type = "Constant" +clm.TopoSlopesX.GeomNames = "domain" +clm.TopoSlopesX.Geom.domain.Value = -0.001 + +# ----------------------------------------------------------------------------- +# Topo slopes in y-direction +# ----------------------------------------------------------------------------- + +clm.TopoSlopesY.Type = "Constant" +clm.TopoSlopesY.GeomNames = "domain" +clm.TopoSlopesY.Geom.domain.Value = 0.001 + +# ----------------------------------------------------------------------------- +# Mannings coefficient +# ----------------------------------------------------------------------------- + +clm.Mannings.Type = "Constant" +clm.Mannings.GeomNames = "domain" +clm.Mannings.Geom.domain.Value = 5.52e-6 + +# ----------------------------------------------------------------------------- +# Phase sources +# ----------------------------------------------------------------------------- + +clm.PhaseSources.water.Type = "Constant" +clm.PhaseSources.water.GeomNames = "domain" +clm.PhaseSources.water.Geom.domain.Value = 0.0 + +# ----------------------------------------------------------------------------- +# Exact solution specification for error calculations +# ----------------------------------------------------------------------------- + +clm.KnownSolution = "NoKnownSolution" + +# ----------------------------------------------------------------------------- +# Set solver parameters +# ----------------------------------------------------------------------------- + +clm.Solver = "Richards" +clm.Solver.MaxIter = 500 + +clm.Solver.Nonlinear.MaxIter = 15 +clm.Solver.Nonlinear.ResidualTol = 1e-9 +clm.Solver.Nonlinear.EtaChoice = "EtaConstant" +clm.Solver.Nonlinear.EtaValue = 0.01 +clm.Solver.Nonlinear.UseJacobian = True +clm.Solver.Nonlinear.StepTol = 1e-20 +clm.Solver.Nonlinear.Globalization = "LineSearch" +clm.Solver.Linear.KrylovDimension = 15 +clm.Solver.Linear.MaxRestart = 2 + +clm.Solver.Linear.Preconditioner = "PFMG" +clm.Solver.PrintSubsurf = False +clm.Solver.Drop = 1e-20 +clm.Solver.AbsTol = 1e-9 + +# ----------------------------------------------------------------------------- +# CLM Settings +# ----------------------------------------------------------------------------- + +clm.Solver.LSM = "CLM" +clm.Solver.CLM.MetForcing = "1D" +clm.Solver.CLM.MetFileName = "snow_forcing.1hr.txt" +clm.Solver.CLM.MetFilePath = "." + +clm.Solver.WriteSiloCLM = False +clm.Solver.WriteSiloEvapTrans = False +clm.Solver.WriteSiloOverlandBCFlux = False +clm.Solver.PrintCLM = True + +clm.Solver.CLM.Print1dOut = False +clm.Solver.BinaryOutDir = False +clm.Solver.WriteCLMBinary = False +clm.Solver.CLM.CLMDumpInterval = 1 +clm.Solver.CLM.WriteLogs = False +clm.Solver.CLM.WriteLastRST = True +clm.Solver.CLM.DailyRST = True +clm.Solver.CLM.SingleFile = True + +# ----------------------------------------------------------------------------- +# SZA-Based Snow Melt Damping +# At high solar zenith angles (low sun), CLM underestimates snow albedo. +# This damping reduces melt energy to compensate. +# coszen_ref = 0.5 corresponds to SZA = 60 degrees (CLM assumption) +# coszen_min = 0.1 corresponds to SZA = 84 degrees +# ----------------------------------------------------------------------------- + +clm.Solver.CLM.SZASnowDamping = 0.8 # 20% reduction at high SZA +clm.Solver.CLM.SZADampingCoszenRef = 0.5 # Start damping below SZA=60 deg +clm.Solver.CLM.SZADampingCoszenMin = 0.1 # Max damping at SZA=84 deg + +# ----------------------------------------------------------------------------- +# Initial conditions: water pressure +# ----------------------------------------------------------------------------- + +clm.ICPressure.Type = "HydroStaticPatch" +clm.ICPressure.GeomNames = "domain" +clm.Geom.domain.ICPressure.Value = -2.0 +clm.Geom.domain.ICPressure.RefGeom = "domain" +clm.Geom.domain.ICPressure.RefPatch = "z_upper" + +# ----------------------------------------------------------------------------- +# Run ParFlow +# ----------------------------------------------------------------------------- + +clm.run(working_directory=dir_name) + +# ----------------------------------------------------------------------------- +# Tests - Compare pressure, saturation, and CLM output +# ----------------------------------------------------------------------------- + +correct_output_dir_name = get_absolute_path("$PF_SRC/test/correct_output/" + run_name) + +passed = True + +for i in range(6): + timestep = str(i).rjust(5, "0") + filename = f"/{run_name}.out.press.{timestep}.pfb" + if not pf_test_file( + dir_name + filename, + correct_output_dir_name + filename, + f"Max difference in Pressure for timestep {timestep}", + ): + passed = False + filename = f"/{run_name}.out.satur.{timestep}.pfb" + if not pf_test_file( + dir_name + filename, + correct_output_dir_name + filename, + f"Max difference in Saturation for timestep {timestep}", + ): + passed = False + + if i > 0: + filename = f"/{run_name}.out.clm_output.{timestep}.C.pfb" + if not pf_test_file( + dir_name + filename, + correct_output_dir_name + filename, + f"Max difference in CLM output for timestep {timestep}", + ): + passed = False + +rm(dir_name) + +if passed: + print(f"{run_name} : PASSED") +else: + print(f"{run_name} : FAILED") + sys.exit(1)