diff --git a/.github/workflows/docs-build-and-deploy.yml b/.github/workflows/docs-build-and-deploy.yml index a802a0743a..55ad033ed7 100644 --- a/.github/workflows/docs-build-and-deploy.yml +++ b/.github/workflows/docs-build-and-deploy.yml @@ -33,6 +33,10 @@ concurrency: jobs: build-and-deploy: + + # Only run on upstream repository + if: ${{ github.repository == 'ESCOMP/CTSM' }} + environment: name: github-pages url: ${{ steps.deployment.outputs.page_url }} diff --git a/.gitmodules b/.gitmodules index 2bc9a4a25a..d2e42aeb09 100644 --- a/.gitmodules +++ b/.gitmodules @@ -28,7 +28,7 @@ [submodule "fates"] path = src/fates url = https://github.com/NGEET/fates -fxtag = sci.1.87.2_api.41.0.0 +fxtag = sci.1.88.0_api.42.0.0 fxrequired = AlwaysRequired # Standard Fork to compare to with "git fleximod test" to ensure personal forks aren't committed fxDONOTUSEurl = https://github.com/NGEET/fates @@ -100,7 +100,7 @@ fxDONOTUSEurl = https://github.com/ESCOMP/CDEPS.git [submodule "share"] path = share url = https://github.com/ESCOMP/CESM_share -fxtag = share1.1.9 +fxtag = share1.1.15 fxrequired = ToplevelRequired # Standard Fork to compare to with "git fleximod test" to ensure personal forks aren't committed fxDONOTUSEurl = https://github.com/ESCOMP/CESM_share diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 70bf041ba7..a0a98d507e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -45,4 +45,4 @@ Code conventions: https://github.com/ESCOMP/ctsm/wiki/CTSM-coding-guidelines #### Code of Conduct: -See the `CODE_OF_CONDUCT.md` file for expectations of how to work in the community. +See https://github.com/ESCOMP/CTSM?tab=coc-ov-file for expectations of how to work in the community. diff --git a/README b/README index b752d07660..deca3cd8d2 100644 --- a/README +++ b/README @@ -1,6 +1,6 @@ -$CTSMROOT/README 09/05/2024 +$CTSMROOT/README 11/24/2025 -Community Terrestrial Systems Model (CTSM) science version 5.3 series -- source code, tools, +Community Terrestrial Systems Model (CTSM) science version 5.4 series -- source code, tools, offline-build and test scripts. This gives you everything you need to run CTSM with CESM with the CMEPS driver and CDEPS data models to provide CRUJRA or GSWP3 forcing data (some older options also available) in place of a modeled atmosphere. @@ -15,6 +15,10 @@ For lists of current bugs (issues) and current development see the CTSM GitHub p https://github.com/ESCOMP/CTSM +For Code of Conduct (how to work with each other on the CTSM project): + +https://github.com/ESCOMP/CTSM?tab=coc-ov-file + INFORMATION ON THE CMEPS DRIVER: https://escomp.github.io/CMEPS @@ -76,8 +80,7 @@ README ------------------- This file README.md ---------------- File that displays on github under https::/github.com/ESCOMP/CTSM.git README.rst --------------- File that displays under the project in github README_GITFLEXIMOD.rst --- Information on how to work with git-fleximod for CTSM -WhatsNewInCTSM5.3.md ----- Overview document of the changes between ctsm5.2.0 and ctsm5.3.0 -CODE_OF_CONDUCT.md ------- Code of Conduct for how to work with each other on the CTSM project +WhatsNewInCTSM5.4.md ----- Overview document of the changes between ctsm5.3 and ctsm5.4 Copyright ---------------- CESM Copyright file doc/UpdateChangeLog.pl --- Script to add documentation on a tag to the ChangeLog/ChangeSum files @@ -168,7 +171,7 @@ src/unit_test_stubs Unit test stubs that replicate CTSM code simpler cd $CIMEROOT/scripts ./create_newcase # get help on how to run create_newcase - ./create_newcase --case testI --res f19_g17_gl4 --compset I2000Clm60BgcCrop + ./create_newcase --case testI --res f09_t232 --compset I2000Clm60BgcCrop # create new "I" case for default machine at 1.9x2.5_gx1v7 # "I2000Clm60BgcCrop" case is clm6_0 physics, CDEPS, and inactive ice/ocn/glc # and MOSART for river-routing diff --git a/WhatsNewInCTSM5.4.md b/WhatsNewInCTSM5.4.md new file mode 100755 index 0000000000..fe721ae312 --- /dev/null +++ b/WhatsNewInCTSM5.4.md @@ -0,0 +1,150 @@ +# What's new in CTSM 5.4 (tag `ctsm5.4.0xx`) + +# Purpose and description of changes since CTSM 5.3 (tag `ctsm5.3.021`) + +## New features + +* New surface datasets from CMIP7 data including PFT and urban distributions, land use transitions, population density, and atmospheric C isotopes. These data are only available through the historical record (1850-2023), and + * are not available for future periods (presently known as SSP), + * for future periods and N deposition we continue to use CMIP6 data from CESM2. +* Option to use CRUJRA2024 atmospheric driver data with clm6 and clm5 physics options ([PR #2956](https://github.com/ESCOMP/ctsm/pull/2956)), this is the default data-atmosphere (DATM) for clm6. This CRUJRA dataset covers 1901-2023, whereas previous GSWP3 only covers 1901-2014. +* Capability to run single-point PLUMBER tower sites, similar to the NEON tower capability ([issue #1487](https://github.com/ESCOMP/CTSM/issues/1487)). Initial conditions are not provided for PLUMBER sites. +* New CLM\_CMIP\_ERA flag in env\_run.xml. Valid options are cmip7 and cmip6. Defaults to cmip7 except in compsets containing SSP for which it defaults to cmip6 because there are no future-period datasets yet available for CMIP7. +* Automatic, more flexible use of anomaly forcings for CMIP6 ISSP cases, which also use the cmip6 CLM\_CMIP\_ERA flag: [Documentation](https://escomp.github.io/CTSM/users_guide/running-special-cases/Running-with-anomaly-forcing.html) + +* Unsupported script that checks for spinup equilibrium in `tools/contrib/` for spectral element grids ([PR #2991](https://github.com/ESCOMP/ctsm/pull/2991)). +* New paramfile tools that allow users to query and modify CLM parameter files ([documentation](https://escomp.github.io/CTSM/users_guide/using-clm-tools/paramfile-tools.html)) +* Optional time-evolving \`leafcn\_target\`. More under “Additional detail” below. +* New vertical movement scheme for soil nitrate, which is off by default (PR [#2992](https://github.com/ESCOMP/CTSM/pull/2992)). +* Documentation improvements and new URL: https://escomp.github.io/CTSM/index.html. +* FATES: + * Grazing ([sci.1.81.0\_api.37.1.0](https://github.com/NGEET/fates/releases/tag/sci.1.81.0_api.37.1.0)). + * Johnson and Berry 2021 electron transport model ([sci.1.85.0\_api.40.0.0](https://github.com/NGEET/fates/releases/tag/sci.1.85.0_api.40.0.0)). + * Managed Fire ([sci.1.87.0\_api.41.0.0](https://github.com/NGEET/fates/releases/tag/sci.1.87.0_api.41.0.0)). + +## Answer changes + +Changes to defaults for \`clm6\` physics: + +* New CMIP7 surface and landuse timeseries datasets (see in Additional Details below). +* New namelist variables \`snow\_thermal\_cond\_glc\_method\` and \`snow\_thermal\_cond\_lake\_method\` ([PR #3072](https://github.com/ESCOMP/CTSM/pull/3072)). Snow thermal conductivity uses Jordan1991 over glaciers to reduce Greenland melt rates by default and Sturm over land and lake land units. +* Bytnerowicz is now the default nfix\_method for clm6 (https://github.com/ESCOMP/ctsm/pull/2972) which revises the temperature function for nitrogen fixation, replacing the Houlton *et al.* function. +* Updates to MEGAN for BVOCs (https://github.com/ESCOMP/CTSM/pull/3065 https://github.com/ESCOMP/CTSM/pull/3309). Removes dependence on soil moisture from clm6 physics. +* New model parameter values that were calibrated to improve carbon cycle representation with CRUJRA. +* New model parameter values that were calibrated to improve the fire model. Now using li2024 fire code. +* New initial conditions files for f09 ("1-degree" 1850, 2000), f19 (“2-degree” 1850), and ne30 (1850, 1979, 2000\) resolutions. +* Change default for glcmec\_downscale\_longwave to FALSE for clm6 physics as turning off the LW downscaling improves the melt and runoff biases. +* See “Changes to FATES and the FATES parameter file” below. +* Namelist defaults change so that + * use\_c13/use\_c14 are on only for HistClm60Bgc compsets with CRUJRA2024 or CAM7 forcing; examples of when use\_c13/use\_c14 are now off include SSP and single-point compsets, as well as cases using older forcings, such as CAM6, GSWP3v1, Qian, and CRUv7 + * when use\_c13 or use\_c14 is on, turn on the corresponding time series file + * irrigation is on for transient cases (1850-2000, 1850-2100, but not for clm4\_5). + +Changes for all physics versions: + +* Parameters updated: Added MIMICS parameter \`mimics\_fi\` (fraction of litter inputs that bypass litter pools, directly contributing to SOM) and updated other MIMICS parameters (https://github.com/ESCOMP/CTSM/pull/2365) to remove NPP control on turnover, fix density dependent control on turnover, add litterfall fluxes that bypass litter pools and contribute directly to soil organic matter. +* FATES parameter file updated: ([PR \#2965](https://github.com/ESCOMP/CTSM/pull/2965), [PR \#2904](https://github.com/ESCOMP/CTSM/pull/2904), [PR \#1344](https://github.com/NGEET/fates/pull/1344), [PR \#3087](https://github.com/ESCOMP/CTSM/pull/3087)). See “FATES parameter file” section below for details. +* New surface datasets and landuse timeseries files (see “surface datasets” section below). + +## Heads up + +* History tapes now split into two files from hX to hXi and hXa, where X is the tape number (e.g. h0i/h0a) and where "i" stands for history file containing instantaneous fields, while "a" stands for history file containing non-instantaneous fields. Details in the “history files” section below and in the PRs https://github.com/ESCOMP/ctsm/pull/2445 https://github.com/ESCOMP/MOSART/pull/117 https://github.com/ESCOMP/RTM/pull/61 and the corresponding issues. +* Adding time to 1d weighting fields in transient simulations PR https://github.com/ESCOMP/CTSM/pull/3328 +* Regarding CMIP7 vs. CMIP6 inputs: + * We supply only CMIP7 C13/C14 isotope datasets, so these get used regardless of CLM\_CMIP\_ERA setting. + * We supply only CMIP7 population density with clm6 physics in non-SSP cases, because the fire model is calibrated to that; conversely, we supply only CMIP6 population density for pre-clm6 physics and for SSP cases. + * We supply only CESM2 nitrogen deposition (ndep), so this gets used regardless of CLM\_CMIP\_ERA setting. + * For DATM we supply only CMIP6 aerosols. + * For DATM we supply only CMIP6 CO2. +* Issue with DOUT\_S\_SAVE\_INTERIM\_REST [https://github.com/ESCOMP/CTSM/issues/3351](https://github.com/ESCOMP/CTSM/issues/3351) was fixed. +* As of ctsm5.3.040, the new ctsm\_pylib conda environment is incompatible with our tools from before ctsm5.3.040 and vice versa. More under “Additional detail” below. + +# Additional detail + +## Changes related to history files + +(Note 1: The same information in this section applies to MOSART and RTM. +Note 2: The gist of the information in this section also appears in the [CTSM User’s Guide](https://escomp.github.io/CTSM/users_guide/setting-up-and-running-a-case/customizing-the-clm-namelist.html#various-ways-to-change-history-output-averaging-flags)). + +Following ctsm5.3.018 "Change history time to be the middle of the time bounds" and keeping CLM history consistent with CAM history, the CTSM5.4 change intends to prevent confusion associated with the time corresponding to instantaneous history fields by putting them on separate files than non-instantaneous fields. + +The now separate instantaneous history files represent the exact time step when they were written and do not include a time\_bounds variable. Conversely, non-instantaneous history files represent the period of their time\_bounds variable. As a result, time data on non-instantaneous history files are now read correctly during post processing (e.g. by xarray). Special handling may still be needed for instantaneous history files, whose timestamps represent the date and time at the END of the history timestep. So, e.g., an instantaneous variable saved at the end of year 2023 will get the timestamp 2024-01-01 00:00:00. + +Users will now see: + +1\) Two history files per clm, mosart, and rtm history tape: + tape h0 becomes h0a and h0i + tape h1 becomes h1a and h1i + ... + tape hX becomes hXa and hXi + +2\) Two history-restart files per history restart tape: + rh0 becomes rh0a and rh0i + rh1 becomes rh1a and rh1i + ... + rhX becomes rhXa and rhXi + +The CLM handles empty history (and corresponding history-restart) files by not generating them, while rtm and mosart give an error. Instead of refactoring rtm and mosart to behave like the clm (considered out of scope), we have introduced one active instantaneous field in mosart and one in rtm to bypass the "empty file" error. + +## New surface datasets and landuse timeseries files (https://github.com/ESCOMP/CTSM/pull/3482) + +* Transient landuse timeseries files going back to 1700 made for f09 and 360x720 grids. +* New resolutions now supported: ne3np4.pg3, mpasa30, ne0np4.NATL.ne30x8 (https://github.com/ESCOMP/CTSM/pull/3482) +* Updates to input datasets (also referred to as raw datasets): + * PFT/LAI/soil-color raw datasets; now from the CMIP7 timeseries that ends in 2023 (Issue [\#2851](https://github.com/ESCOMP/CTSM/issues/2851)). + * Two fire datasets: crop fire peak month and population density (https://github.com/ESCOMP/CTSM/issues/2701 https://github.com/ESCOMP/CTSM/issues/3302). + * Transient (historical) urban datasets are now based on CMIP7 urban data, partitioned into TBD, HD, and MD classes in proportion to GaoOneill present day classification. + +## Changes to FATES and the FATES parameter file + +* See [HLM-FATES compatibility table](https://fates-users-guide.readthedocs.io/en/latest/user/release-tags-compat-table.html) in the FATES user’s guide for all FATES tags associated with CTSM tag updates +* FATES answer changing updates + * The default hydro solver is updated to 2D Picard from 1D Taylor ([ctsm5.3.027](https://github.com/ESCOMP/CTSM/releases/tag/ctsm5.3.027)) + * Simplified leaf sun-shade fraction for two-stream radiation ([sci.1.83.0\_api.39.0.0](https://github.com/NGEET/fates/releases/tag/sci.1.83.0_api.39.0.0)) + * Default maximum canopy layer updated from 2 to 3 ([sci.1.87.1\_api.41.0.0](https://github.com/NGEET/fates/releases/tag/sci.1.87.1_api.41.0.0)) + * Various bug fixes (see compatibility table) +* FATES Parameter File Updates + * ctsm5.3.025 (API 37\) + * Adds pft-dependent btran model switches + * Adds parameters for land use grazing + * Updates the FATES z0mr turbulence parameters for consistency with CLM + * ctsm5.3.027 (API 38\) + * Migrates a number of global parameter file variables to the namelist + * Adds \`fates\_leaf\_fnps\` parameter for the electron transport model + * \`fates\_leaf\_theta\_cj\_c3\` and \`fates\_leaf\_theta\_cj\_c4\` depricated + * ctsm5.3.045 (API 40\) + * Changes to the default competitive exclusion parameter from probabilistic to rank-ordered sorting of cohorts by default + * Sets the logging default to clear cut + * Refactors the pft-specific phenology habit selection into a single parameter + * ctsm5.3.070 (API 41\) + * Add parameters for the managed fire feature addition + * Corrects the fates landuse crop pft to c3 cool grass + +## New ctsm\_pylib conda environment + +If you have a ctsm\_pylib conda environment installed from before ctsm5.3.040, you may want to keep that under a different name. We suggest the following command for doing this in a local copy of ctsm5.3.040 or later: + +```shell +./py_env_create -r ctsm_pylib_old +``` + +This first renames your existing ctsm\_pylib to ctsm\_pylib\_old and then installs the Python 3.13.2 version as ctsm\_pylib. If you are unsure whether you already have ctsm\_pylib installed, use the same command regardless, as it will skip the renaming step if necessary. + +Information about additional py\_env\_create options — including how to install a fresh copy of the old conda environment — is available as follows: + +```shell +./py_env_create --help +``` + +## Potentially time-evolving \`leafcn\_target\` replaces time-constant \`leafcn\` + +The former is calculated as a function of the latter and can be time-evolving depending on new paramfile parameter \`leafcn\_co2\_slope\` https://github.com/ESCOMP/ctsm/pull/1654. The time-evolving effect defaults to off with \`leafcn\_co2\_slope\` \= 0 on the parameter file. + +# Simulations supporting this release by providing initial conditions + +* f19 \`Clm60BgcCruJra\` 16pft: https://github.com/NCAR/LMWG_dev/issues/125 +* f09 with \`Clm60BgcCropCruJra\`: https://github.com/NCAR/LMWG_dev/issues/124 +* ne30 with \`Clm60BgcCropCruJra\`: https://github.com/NCAR/LMWG_dev/issues/123 (123\_HIST\_popDens) +* ne30 SP https://github.com/NCAR/LMWG_dev/issues/126 +* f09 SP https://github.com/NCAR/LMWG_dev/issues/127 + diff --git a/bld/CLMBuildNamelist.pm b/bld/CLMBuildNamelist.pm index 642bd7c2e4..80cb585e31 100755 --- a/bld/CLMBuildNamelist.pm +++ b/bld/CLMBuildNamelist.pm @@ -3770,32 +3770,58 @@ sub setup_logic_c_isotope { $log->warning("use_c14 is ONLY scientifically validated with the bgc=BGC configuration" ); } } + my $use_c14_bombspike = $nl->get_value('use_c14_bombspike'); + my $stream_fldfilename_atm_c14 = $nl->get_value('stream_fldfilename_atm_c14'); + my $atm_c14_filename = $nl->get_value('atm_c14_filename'); if ( &value_is_true($use_c14) ) { add_default($opts, $nl_flags->{'inputdata_rootdir'}, $definition, $defaults, $nl, 'use_c14_bombspike', 'use_c14'=>$use_c14 ); - my $use_c14_bombspike = $nl->get_value('use_c14_bombspike'); + $use_c14_bombspike = $nl->get_value('use_c14_bombspike'); if ( &value_is_true($use_c14_bombspike) ) { - add_default($opts, $nl_flags->{'inputdata_rootdir'}, $definition, $defaults, $nl, 'atm_c14_filename', - 'use_c14'=>$use_c14, 'use_cn'=>$nl_flags->{'use_cn'}, 'use_c14_bombspike'=>$nl->get_value('use_c14_bombspike'), - 'ssp_rcp'=>$nl_flags->{'ssp_rcp'} ); + if ( defined($stream_fldfilename_atm_c14) ) { + setup_logic_c14_streams($opts, $nl_flags, $definition, $defaults, $nl); + } else { + add_default($opts, $nl_flags->{'inputdata_rootdir'}, $definition, $defaults, $nl, 'atm_c14_filename', + 'use_c14'=>$use_c14, 'use_cn'=>$nl_flags->{'use_cn'}, 'use_c14_bombspike'=>$nl->get_value('use_c14_bombspike'), + 'ssp_rcp'=>$nl_flags->{'ssp_rcp'} ); + } + $stream_fldfilename_atm_c14 = $nl->get_value('stream_fldfilename_atm_c14'); + $atm_c14_filename = $nl->get_value('atm_c14_filename'); + if ( defined($stream_fldfilename_atm_c14) && defined($atm_c14_filename) ) { + $log->fatal_error("Both stream_fldfilename_atm_c14 and atm_c14_filename set, only one should be set"); + } } } else { - if ( defined($nl->get_value('use_c14_bombspike')) || - defined($nl->get_value('atm_c14_filename')) ) { - $log->fatal_error("use_c14 is FALSE and use_c14_bombspike or atm_c14_filename set"); + if ( defined($use_c14_bombspike) || + defined($stream_fldfilename_atm_c14) || + defined($atm_c14_filename) ) { + $log->fatal_error("use_c14 is FALSE and use_c14_bombspike, stream_fldfilename_atm_c14 or atm_c14_filename set"); } } + my $use_c13_timeseries = $nl->get_value('use_c13_timeseries'); + my $stream_fldfilename_atm_c13 = $nl->get_value('stream_fldfilename_atm_c13'); + my $atm_c13_filename = $nl->get_value('atm_c13_filename'); if ( &value_is_true($use_c13) ) { add_default($opts, $nl_flags->{'inputdata_rootdir'}, $definition, $defaults, $nl, 'use_c13_timeseries', 'use_c13'=>$use_c13 ); - my $use_c13_timeseries = $nl->get_value('use_c13_timeseries'); + $use_c13_timeseries = $nl->get_value('use_c13_timeseries'); if ( &value_is_true($use_c13_timeseries) ) { - add_default($opts, $nl_flags->{'inputdata_rootdir'}, $definition, $defaults, $nl, 'atm_c13_filename', - 'use_c13'=>$use_c13, 'use_cn'=>$nl_flags->{'use_cn'}, 'use_c13_timeseries'=>$nl->get_value('use_c13_timeseries'), - 'ssp_rcp'=>$nl_flags->{'ssp_rcp'} ); + if ( defined($stream_fldfilename_atm_c13) ) { + setup_logic_c13_streams($opts, $nl_flags, $definition, $defaults, $nl); + } else { + add_default($opts, $nl_flags->{'inputdata_rootdir'}, $definition, $defaults, $nl, 'atm_c13_filename', + 'use_c13'=>$use_c13, 'use_cn'=>$nl_flags->{'use_cn'}, 'use_c13_timeseries'=>$nl->get_value('use_c13_timeseries'), + 'ssp_rcp'=>$nl_flags->{'ssp_rcp'} ); + } + $stream_fldfilename_atm_c13 = $nl->get_value('stream_fldfilename_atm_c13'); + $atm_c13_filename = $nl->get_value('atm_c13_filename'); + if ( defined($stream_fldfilename_atm_c13) && defined($atm_c13_filename) ) { + $log->fatal_error("Both stream_fldfilename_atm_c13 and atm_c13_filename set, only one should be set"); + } } } else { if ( defined($nl->get_value('use_c13_timeseries')) || + defined($nl->get_value('stream_fldfilename_atm_c13')) || defined($nl->get_value('atm_c13_filename')) ) { - $log->fatal_error("use_c13 is FALSE and use_c13_timeseries or atm_c13_filename set"); + $log->fatal_error("use_c13 is FALSE and use_c13_timeseries, stream_fldfilename_atm_c13 or atm_c13_filename set"); } } } else { @@ -3803,8 +3829,10 @@ sub setup_logic_c_isotope { &value_is_true($use_c14) || &value_is_true($nl->get_value('use_c14_bombspike')) || defined($nl->get_value('atm_c14_filename')) || + defined($nl->get_value('stream_fldfilename_atm_c14')) || &value_is_true($nl->get_value('use_c13_timeseries')) || - defined($nl->get_value('atm_c13_filename')) ) { + defined($nl->get_value('atm_c13_filename')) || + defined($nl->get_value('stream_fldfilename_atm_c13')) ) { $log->fatal_error("bgc=sp and C isotope namelist variables were set, both can't be used at the same time"); } } @@ -3812,6 +3840,24 @@ sub setup_logic_c_isotope { #------------------------------------------------------------------------------- +sub setup_logic_c13_streams { + my ($opts, $nl_flags, $definition, $defaults, $nl) = @_; + # + # C13 stream file settings + # +} + +#------------------------------------------------------------------------------- + +sub setup_logic_c14_streams { + my ($opts, $nl_flags, $definition, $defaults, $nl) = @_; + # + # C14 stream file settings + # +} + +#------------------------------------------------------------------------------- + sub setup_logic_nitrogen_deposition { my ($opts, $nl_flags, $definition, $defaults, $nl) = @_; @@ -5334,6 +5380,9 @@ sub write_output_files { push @groups, "clm_canopy_inparm"; push @groups, "prigentroughness"; push @groups, "zendersoilerod"; + if ( &value_is_true($nl_flags->{'use_cn'}) ) { + push @groups, "carbon_isotope_streams"; + } if (remove_leading_and_trailing_quotes($nl->get_value('snow_cover_fraction_method')) eq 'SwensonLawrence2012') { push @groups, "scf_swenson_lawrence_2012_inparm"; } diff --git a/bld/namelist_files/namelist_defaults_ctsm.xml b/bld/namelist_files/namelist_defaults_ctsm.xml index 068d57d23e..51a926b061 100644 --- a/bld/namelist_files/namelist_defaults_ctsm.xml +++ b/bld/namelist_files/namelist_defaults_ctsm.xml @@ -70,21 +70,19 @@ attributes from the config_cache.xml file (with keys converted to upper-case). .true. .true. 'TOTECOSYSC','TOTECOSYSN','TOTSOMC','TOTSOMN','TOTVEGC','TOTVEGN','TLAI','GPP','NPP','TWS','TSAI','HTOP','HBOT' +>'TOTECOSYSC','TOTECOSYSN','TOTSOMC','TOTSOMN','TOTVEGC','TOTVEGN','TLAI','GPP','NPP','TWS','TSAI','HTOP','HBOT','H2OSNO' 'TOTECOSYSC','TOTECOSYSN','TOTSOMC','TOTSOMN','TOTVEGC','TOTVEGN','TLAI','GPP','CPOOL','NPP','TWS' +>'TOTECOSYSC','TOTECOSYSN','TOTSOMC','TOTSOMN','TOTVEGC','TOTVEGN','TLAI','GPP','CPOOL','NPP','TWS','H2OSNO' 'TOTSOMC','TOTSOMN','TLAI','GPP','NPP','TWS' +>'TOTSOMC','TOTSOMN','TLAI','GPP','NPP','TWS','H2OSNO' 'TLAI','TWS' +>'TLAI','TWS','H2OSNO' 'TOTECOSYSC','TOTECOSYSN','TOTSOMC','TOTSOMN','TOTVEGC','TOTVEGN','TLAI','GPP','NPP','TWS','TSAI','HTOP','HBOT' +>'TOTECOSYSC','TOTECOSYSN','TOTSOMC','TOTSOMN','TOTVEGC','TOTVEGN','TLAI','GPP','NPP','TWS','TSAI','HTOP','HBOT','H2OSNO' 'TOTECOSYSC','TOTECOSYSN','TOTSOMC','TOTSOMN','TOTVEGC','TOTVEGN','TLAI','GPP','CPOOL','NPP','TWS' +>'TOTECOSYSC','TOTECOSYSN','TOTSOMC','TOTSOMN','TOTVEGC','TOTVEGN','TLAI','GPP','CPOOL','NPP','TWS','H2OSNO' 'TOTSOMC','TOTSOMN','TLAI','GPP','NPP','TWS' -'TLAI','TWS' +>'TOTSOMC','TOTSOMN','TLAI','GPP','NPP','TWS','H2OSNO' -8760 -8760 20 @@ -1326,6 +1324,12 @@ attributes from the config_cache.xml file (with keys converted to upper-case). lnd_tuning_mode="clm5_0_GSWP3v1" use_init_interp=".true." >lnd/clm2/initdata_esmf/ctsm5.3/clmi.interp_from.I1850Clm50BgcCrop-ciso.1366-01-01.0.9x1.25_gx1v7_simyr1850_c240223.nc +lnd/clm2/initdata_esmf/ctsm5.4/clmi.f09_interp_from.ctsm5.4.CMIP7_ciso_ctsm5.3.075_f09_124_pSASU.clm2.r.0161_c251118.nc + lnd/clm2/initdata_esmf/ctsm5.4/clmi.f19_twiceinterp_from.I1850Clm50BgcCrop-ciso.1366-01-01.0.9x1.25_gx1v7_simyr1850_c251030.nc - -lnd/clm2/initdata_esmf/ctsm5.4/clmi.f19_twiceinterp_from.I1850Clm50BgcCrop-ciso.1366-01-01.0.9x1.25_gx1v7_simyr1850_c251030.nc diff --git a/bld/namelist_files/namelist_definition_ctsm.xml b/bld/namelist_files/namelist_definition_ctsm.xml index 69a243bd27..7a032b901f 100644 --- a/bld/namelist_files/namelist_definition_ctsm.xml +++ b/bld/namelist_files/namelist_definition_ctsm.xml @@ -1806,6 +1806,51 @@ Mapping method from Nitrogen deposition input file to the model resolution none = no interpolation + + + +First year to loop over for atmospheric C14 isotope delta data + + + +Last year to loop over for data atmospheric C14 isotope delta data + + + +Simulation year that aligns with stream_year_first_atm_c14 value + + + +Filename of input stream data for atmospheric C14 isotope delta data + + + +First year to loop over for atmospheric C13 isotope delta data + + + +Last year to loop over for data atmospheric C13 isotope delta data + + + +Simulation year that aligns with stream_year_first_atm_c13 value + + + +Filename of input stream data for atmospheric C13 isotope delta data + + diff --git a/bld/unit_testers/build-namelist_test.pl b/bld/unit_testers/build-namelist_test.pl index 725a5defff..2795ef6f76 100755 --- a/bld/unit_testers/build-namelist_test.pl +++ b/bld/unit_testers/build-namelist_test.pl @@ -163,7 +163,7 @@ sub cat_and_create_namelistinfile { # # Figure out number of tests that will run # -my $ntests = 3396; +my $ntests = 3402; if ( defined($opts{'compare'}) ) { $ntests += 2061; @@ -821,6 +821,30 @@ sub cat_and_create_namelistinfile { namelst=>"use_c14_bombspike=.true.", phys=>"clm5_0", }, + "bombspike file and stream" =>{ options=>"-bgc bgc -envxml_dir .", + namelst=>"use_c14=TRUE use_c14_bombspike=.true. stream_fldfilename_atm_c14='/dev/null', atm_c14_filename='/dev/null'", + phys=>"clm6_0", + }, + "c13 file and stream" =>{ options=>"-bgc bgc -envxml_dir .", + namelst=>"use_c13_timeseries=.true. stream_fldfilename_atm_c13='/dev/null', atm_c13_filename='/dev/null'", + phys=>"clm6_0", + }, + "c13 off, but stream" =>{ options=>"-bgc bgc -envxml_dir .", + namelst=>"use_c13=.false. stream_fldfilename_atm_c13='/dev/null'", + phys=>"clm6_0", + }, + "c14 off, but stream" =>{ options=>"-bgc bgc -envxml_dir .", + namelst=>"use_c14=.false. stream_fldfilename_atm_c14='/dev/null'", + phys=>"clm6_0", + }, + "sp, but c13 stream" =>{ options=>"-bgc sp -envxml_dir .", + namelst=>"stream_fldfilename_atm_c13='/dev/null'", + phys=>"clm6_0", + }, + "sp, but c14 stream" =>{ options=>"-bgc sp -envxml_dir .", + namelst=>"stream_fldfilename_atm_c14='/dev/null'", + phys=>"clm6_0", + }, "lightres no cn" =>{ options=>"-bgc sp -envxml_dir . -light_res 360x720", namelst=>"", phys=>"clm5_0", diff --git a/cime_config/testdefs/ExpectedTestFails.xml b/cime_config/testdefs/ExpectedTestFails.xml index 5d4d51e123..f7f4e8ba24 100644 --- a/cime_config/testdefs/ExpectedTestFails.xml +++ b/cime_config/testdefs/ExpectedTestFails.xml @@ -29,13 +29,6 @@ - - - FAIL - #3529 - We will generate new fsurdat files with the new raw lai file to resolve this issue. - - FAIL @@ -69,12 +62,6 @@ - - - FAIL - #3495 - - FAIL @@ -247,6 +234,15 @@ + + FAIL @@ -329,22 +325,6 @@ - - - FAIL - #3252 - This should be resolved for the 5.4 release. - - - - - - FAIL - #3252 - This should be resolved for the 5.4 release. - - - diff --git a/cime_config/testdefs/testlist_clm.xml b/cime_config/testdefs/testlist_clm.xml index 913580c77b..15e395fe6d 100644 --- a/cime_config/testdefs/testlist_clm.xml +++ b/cime_config/testdefs/testlist_clm.xml @@ -195,6 +195,25 @@ + + + + + + + + + + + + + + + + + + + @@ -4445,6 +4464,7 @@ + diff --git a/cime_config/testdefs/testmods_dirs/clm/ciso_cmip7_monthly_2013Start/include_user_mods b/cime_config/testdefs/testmods_dirs/clm/ciso_cmip7_monthly_2013Start/include_user_mods new file mode 100644 index 0000000000..5f7ca15ec0 --- /dev/null +++ b/cime_config/testdefs/testmods_dirs/clm/ciso_cmip7_monthly_2013Start/include_user_mods @@ -0,0 +1 @@ +../ciso_monthly_2013Start diff --git a/cime_config/testdefs/testmods_dirs/clm/ciso_cmip7_monthly_2013Start/user_nl_clm b/cime_config/testdefs/testmods_dirs/clm/ciso_cmip7_monthly_2013Start/user_nl_clm new file mode 100644 index 0000000000..df0189c2e6 --- /dev/null +++ b/cime_config/testdefs/testmods_dirs/clm/ciso_cmip7_monthly_2013Start/user_nl_clm @@ -0,0 +1,6 @@ + stream_fldfilename_atm_c13 = '$DIN_LOC_ROOT/lnd/clm2/isotopes/ctsmforc.Graven.atm_delta_C13_CMIP7_global_1700-2023_yearly_v3.0_c251013.nc' + stream_fldfilename_atm_c14 = '$DIN_LOC_ROOT/lnd/clm2/isotopes/ctsmforc.Graven.atm_delta_C14_CMIP7_4x1_global_1700-2023_yearly_v3.0_c251013.nc' + stream_year_first_atm_c14 = 2013 + stream_model_year_align_atm_c14 = 2013 + stream_year_first_atm_c13 = 2013 + stream_model_year_align_atm_c13 = 2013 diff --git a/cime_config/testdefs/testmods_dirs/clm/ciso_monthly_2013Start/include_user_mods b/cime_config/testdefs/testmods_dirs/clm/ciso_monthly_2013Start/include_user_mods new file mode 100644 index 0000000000..2cc5720115 --- /dev/null +++ b/cime_config/testdefs/testmods_dirs/clm/ciso_monthly_2013Start/include_user_mods @@ -0,0 +1 @@ +../ciso_monthly diff --git a/cime_config/testdefs/testmods_dirs/clm/ciso_monthly_2013Start/shell_commands b/cime_config/testdefs/testmods_dirs/clm/ciso_monthly_2013Start/shell_commands new file mode 100644 index 0000000000..035842f982 --- /dev/null +++ b/cime_config/testdefs/testmods_dirs/clm/ciso_monthly_2013Start/shell_commands @@ -0,0 +1 @@ +./xmlchange RUN_STARTDATE=2013-01-01 diff --git a/cime_config/testdefs/testmods_dirs/clm/ciso_monthly_2013Start/user_nl_clm b/cime_config/testdefs/testmods_dirs/clm/ciso_monthly_2013Start/user_nl_clm new file mode 100644 index 0000000000..b0129f7f6e --- /dev/null +++ b/cime_config/testdefs/testmods_dirs/clm/ciso_monthly_2013Start/user_nl_clm @@ -0,0 +1,3 @@ +! Add C13/C14 output to validate the incoming data +hist_fincl1 += 'RC13_CANAIR', 'RC14_CANAIR' +hist_fincl2 = 'RC13_CANAIR', 'RC14_CANAIR' diff --git a/cime_config/testdefs/testmods_dirs/clm/smallville_dynlakes_monthly/user_nl_clm b/cime_config/testdefs/testmods_dirs/clm/smallville_dynlakes_monthly/user_nl_clm index 6223cc203f..46680fd36d 100644 --- a/cime_config/testdefs/testmods_dirs/clm/smallville_dynlakes_monthly/user_nl_clm +++ b/cime_config/testdefs/testmods_dirs/clm/smallville_dynlakes_monthly/user_nl_clm @@ -6,7 +6,7 @@ do_transient_lakes = .true. ! Key points are that lake area starts as 0, increases after the first year, then decreases after the second year. ! PCT_CROP is also changed so that PCT_LAKE + PCT_CROP <= 100. (Here, PCT_CROP increases and decreases at the same time as PCT_LAKE in order to exercise the simultaneous increase or decrease of two landunits, but that isn't a critical part of this test.) ! Note that the use of this file means that this testmod can only be used with the 1x1_smallvilleIA grid. -flanduse_timeseries = '$DIN_LOC_ROOT/lnd/clm2/surfdata_esmf/ctsm5.3.0/synthetic/landuse.timeseries_1x1_smallvilleIA_synth_SSP2-4.5_1850-1855_78pfts_dynLakes_c240912.nc' +flanduse_timeseries = '$DIN_LOC_ROOT/lnd/clm2/surfdata_esmf/ctsm5.4.0/synthetic/landuse.timeseries_1x1_smallvilleIA_synth_1850-1855_78pfts_dynLakes_c251023.nc' ! NOTE slevis (2024/2/23) Adding option for tests to pass. In the long term ! ensure that subset_data generates fsurdat and landuse files consistent with diff --git a/cime_config/testdefs/testmods_dirs/clm/smallville_dynurban_monthly/user_nl_clm b/cime_config/testdefs/testmods_dirs/clm/smallville_dynurban_monthly/user_nl_clm index 958265cffc..bb18638158 100644 --- a/cime_config/testdefs/testmods_dirs/clm/smallville_dynurban_monthly/user_nl_clm +++ b/cime_config/testdefs/testmods_dirs/clm/smallville_dynurban_monthly/user_nl_clm @@ -7,7 +7,7 @@ do_transient_urban = .true. ! Medium density urban is set to zero to test the memory-saving behavior of PCT_URBAN_MAX. ! PCT_CROP is also changed so that PCT_URBAN + PCT_CROP <= 100. (Here, PCT_CROP increases and decreases at the same time as PCT_URBAN in order to exercise the simultaneous increase or decrease of two landunits, but that isn't a critical part of this test.) ! Note that the use of this file means that this testmod can only be used with the 1x1_smallvilleIA grid. -flanduse_timeseries = '$DIN_LOC_ROOT/lnd/clm2/surfdata_esmf/ctsm5.3.0/synthetic/landuse.timeseries_1x1_smallvilleIA_synth_SSP2-4.5_1850-1855_78pfts_dynUrban_c240912.nc' +flanduse_timeseries = '$DIN_LOC_ROOT/lnd/clm2/surfdata_esmf/ctsm5.4.0/synthetic/landuse.timeseries_1x1_smallvilleIA_synth_1850-1855_78pfts_dynUrban_c251023.nc' ! NOTE slevis (2024/2/23) Adding option for tests to pass. In the long term ! ensure that subset_data generates fsurdat and landuse files consistent with diff --git a/cime_config/usermods_dirs/clm/PLUMBER2/defaults/user_nl_clm b/cime_config/usermods_dirs/clm/PLUMBER2/defaults/user_nl_clm index 5216afb381..ff20dae0b4 100644 --- a/cime_config/usermods_dirs/clm/PLUMBER2/defaults/user_nl_clm +++ b/cime_config/usermods_dirs/clm/PLUMBER2/defaults/user_nl_clm @@ -19,6 +19,6 @@ !---------------------------------------------------------------------------------- flanduse_timeseries = ' ' ! This isn't needed for a non transient case, but will be once we start using transient compsets -fsurdat = "$DIN_LOC_ROOT/lnd/clm2/surfdata_esmf/PLUMBER2/ctsm5.3.0/surfdata_1x1_PLUMBER2_${PLUMBER2SITE}_hist_2000_16pfts_c240912.nc" +fsurdat = "$DIN_LOC_ROOT/lnd/clm2/surfdata_esmf/PLUMBER2/ctsm5.4.0/surfdata_1x1_PLUMBER2_${PLUMBER2SITE}_hist_2000_16pfts_c251023.nc" ! custom namelist changes for each site / case diff --git a/doc/ChangeLog b/doc/ChangeLog index 551de3ae12..655b56af19 100644 --- a/doc/ChangeLog +++ b/doc/ChangeLog @@ -1,4 +1,227 @@ =============================================================== +Tag name: ctsm5.3.085 +Originator(s): slevis (Samuel Levis,UCAR/TSS,303-665-1310) +Date: Fri Nov 14 11:08:54 AM MST 2025 +One-line Summary: Merge b4b-dev to master + +Purpose and description of changes +---------------------------------- + + PRs + #3581 by Bill Sacks: Generalize some paths so unit testing works in a CESM checkout, previously assumed standalone CTSM checkout. + #3561 by Erik Kluzek: Start adding streams infrastructure for carbon isotopes. + +Significant changes to scientifically-supported configurations +-------------------------------------------------------------- + +Does this tag change answers significantly for any of the following physics configurations? +(Details of any changes will be given in the "Answer changes" section below.) + + [Put an [X] in the box for any configuration with significant answer changes.] + +[ ] clm6_0 + +[ ] clm5_0 + +[ ] ctsm5_0-nwp + +[ ] clm4_5 + + +Bugs fixed +---------- +List of CTSM issues fixed (include CTSM Issue # and description) [one per line]: + Resolves #2837 + Resolves #3502 + Design notes in #3546 + Some work on #3346 + +Testing summary: +---------------- + [PASS means all tests PASS; OK means tests PASS other than expected fails.] + + python testing (if python code has changed; see instructions in python/README.md; document testing done): + + derecho - PASS + + regular tests (aux_clm: https://github.com/ESCOMP/CTSM/wiki/System-Testing-Guide#pre-merge-system-testing): + + derecho ----- OK + izumi ------- OK + +Answer changes +-------------- + +Changes answers relative to baseline: + No + +Other details +------------- +Pull Requests that document the changes (include PR ids): + https://github.com/ESCOMP/ctsm/pull/3609 + +=============================================================== +=============================================================== +Tag name: ctsm5.3.084 +Originator(s): erik (Erik Kluzek,UCAR/TSS,303-497-1326) +Date: Fri 31 Oct 2025 01:12:26 PM MDT +One-line Summary: Merge b4b-dev to master + +Purpose and description of changes +---------------------------------- + +Bring changes on b4b-dev to master + +- Fix FUNIT testing on Mac's +- Some fixes to set_paramfile +- Don't auto build documentation on personal forks, only on ESCOMP + +Significant changes to scientifically-supported configurations +-------------------------------------------------------------- + +Does this tag change answers significantly for any of the following physics configurations? +(Details of any changes will be given in the "Answer changes" section below.) + + [Put an [X] in the box for any configuration with significant answer changes.] + +[ ] clm6_0 + +[ ] clm5_0 + +[ ] ctsm5_0-nwp + +[ ] clm4_5 + + +Bugs fixed +---------- +List of CTSM issues fixed (include CTSM Issue # and description) [one per line]: + Fixes #3571 -- set_paramfile ordering of PFT's from user doesn't have to match paramfile order + Fixes #3559 -- Correct Ndims error + Fixes #3369 -- Docs build and deploy only runs on ESCOMP not personal forks + +Notes of particular relevance for users +--------------------------------------- + +Changes to documentation: + Some changes to set_paramfile documentation + +Notes of particular relevance for developers: +--------------------------------------------- + +Testing summary: regular +---------------- + + [PASS means all tests PASS; OK means tests PASS other than expected fails.] + + build-namelist tests (if CLMBuildNamelist.pm has changed): + + derecho - OK + + python testing (if python code has changed; see instructions in python/README.md; document testing done): + + derecho - PASS + + regular tests (aux_clm: https://github.com/ESCOMP/CTSM/wiki/System-Testing-Guide#pre-merge-system-testing): + + derecho ----- OK + izumi ------- OK + +If the tag used for baseline comparisons was NOT the previous tag, note that here: + + +Answer changes +-------------- + +Changes answers relative to baseline: No bit-for-bit + +Other details +------------- + +Pull Requests that document the changes (include PR ids): +(https://github.com/ESCOMP/ctsm/pull) + + -- #3577 unit testing on Mac + -- #3572 set_paramfile ordering + -- #3560 Fix Ndim error in set_paramfile + -- #3557 doc/build/run/deploy only on ESCOMP + +=============================================================== +=============================================================== +Tag name: ctsm5.3.083 +Originator(s): rgknox (Ryan Knox,LAWRENCE BERKELEY NATIONAL LABORATORY,510-495-2153) +Date: Wed 29 Oct 2025 03:35:50 PM MDT +One-line Summary: Changes to coupling of supplementation status with FATES. + +Purpose and description of changes +---------------------------------- + +Supplementation status is now passed to FATES as a run-time boundary condition. This set of changes is synchronized with FATES-side changes that were oriented around how supplementation status impacts whether or not fine-root proportions are allowed to change during CNP runs. + + +Significant changes to scientifically-supported configurations +-------------------------------------------------------------- + +Does this tag change answers significantly for any of the following physics configurations? No + +Bugs fixed +---------- + +No bugs were fixed. + +Notes of particular relevance for users +--------------------------------------- + +When CTSM-FATES has nutrient coupling, note that FATES plants will default to not changing L2FR when supplementing nitrogen. + +Caveats for users (e.g., need to interpolate initial conditions): None + +Changes to CTSM's user interface (e.g., new/renamed XML or namelist variables): None + +Changes made to namelist defaults (e.g., changed parameter values): None + +Changes to the datasets (e.g., parameter, surface or initial files): None + +Changes to documentation: None necessary. + +Substantial timing or memory changes: None + +Notes of particular relevance for developers: +--------------------------------------------- + +We had been passing a stealth namelist variable from CTSM to FATES, it gave FATES information about what types of mineralized nutrient were available (ie NO3 or NH4), but FATES has never used this information, so it was removed. + +Caveats for developers (e.g., code that is duplicated that requires double maintenance): None + +Changes to tests or testing: None + + +Testing summary: +---------------- + + regular tests (aux_clm: https://github.com/ESCOMP/CTSM/wiki/System-Testing-Guide#pre-merge-system-testing): + + derecho ----- OK + izumi ------- OK + + fates tests: (give name of baseline if different from CTSM tagname, normally fates baselines are fates--) + derecho ----- OK + izumi ------- OK + + +Answer changes +-------------- + +Changes answers relative to baseline: No answer changes with one exception. Some FATES-specific variables related to radiation diagnostics and error tracking changed with updating the FATES tag. These were diagnostic only changes, and not related to FATES internals changing in any way. + +ther details +------------- +Pull Requests that document the changes (include PR ids): + https://github.com/ESCOMP/CTSM/pull/3348 + + +=============================================================== +=============================================================== Tag name: ctsm5.3.082 Originator(s): slevis (Samuel Levis,UCAR/TSS,303-665-1310) Date: Fri 24 Oct 2025 11:17:41 AM MDT diff --git a/doc/ChangeSum b/doc/ChangeSum index e19584ae9d..135ae3ac13 100644 --- a/doc/ChangeSum +++ b/doc/ChangeSum @@ -1,5 +1,8 @@ Tag Who Date Summary ============================================================================================================================ + ctsm5.3.085 slevis 11/14/2025 Merge b4b-dev to master + ctsm5.3.084 erik 10/31/2025 Merge b4b-dev to master + ctsm5.3.083 rgknox 10/29/2025 Changes to coupling of supplementation status with FATES. ctsm5.3.082 slevis 10/24/2025 Update to CMIP7 population density file for non-SSP cases ctsm5.3.081 erik 10/22/2025 Change defaults for when Carbon isotopes are turned on, and turn on irrigate for Sp/Bgc cases for clm6_0 historical transient cases ctsm5.3.080 samrabin 10/16/2025 Merge b4b-dev to master diff --git a/WhatsNewInCTSM5.3.md b/doc/WhatsNewInCTSM5.3.md similarity index 100% rename from WhatsNewInCTSM5.3.md rename to doc/WhatsNewInCTSM5.3.md diff --git a/doc/source/users_guide/running-single-points/supported-tower-sites.rst b/doc/source/users_guide/running-single-points/supported-tower-sites.rst index 3e080a3ee1..db0e8e83fb 100644 --- a/doc/source/users_guide/running-single-points/supported-tower-sites.rst +++ b/doc/source/users_guide/running-single-points/supported-tower-sites.rst @@ -60,9 +60,9 @@ To run CTSM at a NEON site, change directories to where the run_tower tool is lo When a simulation completes, the data are stored in the archive directory under ``CTSM/tools/site_and_regional/archive``. In this directory you will find files that include data for every day of the simulation, as well as files that average model variables monthly. The output file names are automatically generated and are composed of the simulation name, which includes the site name, type of simulation (eg, ``transient``), and the date of simulated data. The tower simulations generate two types of files: -1) ``h0`` Variables that are averaged monthly. One file is available for every month of the simulation. These files include hundreds of variables. +1) ``h0a`` Variables that are averaged monthly. One file is available for every month of the simulation. These files include hundreds of variables. -2) ``h1`` Variables that are recorded every 30 minutes. Values are aggregated into one file for each day of the simulation. Each file includes 48 data points for selected variables. +2) ``h1a`` Variables that are recorded every 30 minutes. Values are aggregated into one file for each day of the simulation. Each file includes 48 data points for selected variables. ========================================= PLUMBER Tower Single Point Simulations diff --git a/doc/source/users_guide/running-special-cases/Running-with-custom-crop-calendars.rst b/doc/source/users_guide/running-special-cases/Running-with-custom-crop-calendars.rst index 22009add51..b03e73066b 100644 --- a/doc/source/users_guide/running-special-cases/Running-with-custom-crop-calendars.rst +++ b/doc/source/users_guide/running-special-cases/Running-with-custom-crop-calendars.rst @@ -59,18 +59,18 @@ In a GDD-generating run, crops are planted on the specified sowing dates and are generate_crop_gdds = .true. use_mxmat = .false. - ! (h0) Save default variables monthly instead of daily to save space + ! (h0a) Save default variables monthly instead of daily to save space hist_nhtfrq = 0 hist_mfilt = 12 - ! (h1) Annual outputs for GDD generation + ! (h1i) Annual outputs for GDD generation hist_fincl2 = 'GRAINC_TO_FOOD_PERHARV', 'GRAINC_TO_FOOD_ANN', 'SDATES', 'SDATES_PERHARV', 'SYEARS_PERHARV', 'HDATES', 'GDDHARV_PERHARV', 'GDDACCUM_PERHARV', 'HUI_PERHARV', 'SOWING_REASON_PERHARV', 'HARVEST_REASON_PERHARV' hist_nhtfrq(2) = 17520 hist_mfilt(2) = 999 hist_type1d_pertape(2) = 'PFTS' hist_dov2xy(2) = .false. - ! (h2) Daily outputs for GDD generation + ! (h2a) Daily outputs for GDD generation hist_fincl3 = 'GDDACCUM', 'GDDHARV' hist_nhtfrq(3) = -24 hist_mfilt(3) = 365 diff --git a/doc/source/users_guide/setting-up-and-running-a-case/customizing-the-clm-namelist.rst b/doc/source/users_guide/setting-up-and-running-a-case/customizing-the-clm-namelist.rst index a4333a2b29..f4c28da9a8 100644 --- a/doc/source/users_guide/setting-up-and-running-a-case/customizing-the-clm-namelist.rst +++ b/doc/source/users_guide/setting-up-and-running-a-case/customizing-the-clm-namelist.rst @@ -145,7 +145,9 @@ There are two ways to change the averaging of output history fields. The first i - ``X`` Maximum, over the output interval. - ``M`` Minimum, over the output interval. -The default averaging depends on the specific fields, but for most fields is an average. A sample user namelist ``user_nl_clm`` making the monthly output fields all averages (except ``TSOI`` for the first two streams and ``FIRE`` for the 5th stream), and adding auxiliary file streams for instantaneous (6-hourly), maximum (daily), minimum (daily), and average (daily). For some of the fields we diverge from the per-tape value given and customize to some different type of optimization. +The default averaging depends on the specific fields, but for most fields is an average. A sample user namelist ``user_nl_clm`` follows making the monthly output fields all averages (except ``TSOI``), and adding auxiliary file streams for instantaneous (6-hourly), maximum (daily), minimum (daily), and average (daily). For some of the fields we diverge from the per-tape value given and customize to some different type of averaging. + +.. note:: As of ctsm5.4, history files that used to be labeled with hX (where X is an integer from 0 to 4 in the example) will be labeled with hXi and hXa (as separate files) to indicate instantaneous versus non-instantaneous (average, etc.) files. The change intends to prevent confusion associated with the time corresponding to instantaneous history fields by now putting them on separate files than non-instantaneous fields. The separate instantaneous history files represent the exact time step when they were written and do not include a time_bounds variable. Conversely, non-instantaneous history files represent the period of their time_bounds variable. As a result, time data on non-instantaneous history files are now read correctly during post processing (e.g. by xarray). Special handling may still be needed for instantaneous history files, whose timestamps represent the date and time at the END of the history timestep. So, e.g., an instantaneous variable saved at the end of year 2023 will get the timestamp 2024-01-01 00:00:00. Example: user_nl_clm namelist with various ways to average history fields ------------------------------------------------------------------------------------- @@ -162,12 +164,12 @@ Example: user_nl_clm namelist with various ways to average history fields 'EFLX_LH_TOT', 'WT' hist_fincl5 = 'TSOI', 'TG', 'TV', 'FIRE:I', 'FSR', 'FSH', 'EFLX_LH_TOT', 'WT' - hist_avgflag_pertape = 'A', 'I', 'X', 'M', 'A' + hist_avgflag_pertape = 'A', 'I', 'X', 'M', 'A' hist_nhtfrq = 0, -6, -24, -24, -24 -In the example we put the same list of fields on each of the tapes: soil-temperature, ground temperature, vegetation temperature, emitted longwave radiation, reflected solar radiation, sensible heat, total latent-heat, and total water storage. We also modify the soil temperature for the primary and secondary auxiliary tapes by outputting them for a maximum instead of the prescribed per-tape of average and instantaneous respectively. For the tertiary auxiliary tape we output ground temperature instantaneous instead of as a maximum, and for the fourth auxiliary tape we output vegetation temperature instantaneous instead of as a minimum. Finally, for the fifth auxiliary tapes we output ``FIRE`` instantaneously instead of as an average. +In the example we put the same list of fields on each of the tapes: soil-temperature, ground temperature, vegetation temperature, emitted longwave radiation, reflected solar radiation, sensible heat, total latent-heat, and total water storage. We also modify the soil temperature for the primary and secondary auxiliary tapes by outputting them for a maximum instead of the prescribed per-tape of average and instantaneous respectively. For the tertiary auxiliary tape we output ground temperature as instantaneous instead of as maximum, and for the fourth auxiliary tape we output vegetation temperature as instantaneous instead of as minimum. Finally, for the fifth auxiliary tapes we output ``FIRE`` as instantaneous instead of as average. -.. note:: We also use ``hist_empty_htapes`` as in the previous example, so we can list ONLY the fields that we want on the primary history tapes. +.. note:: We also use ``hist_empty_htapes`` as in the previous example, so we can list ONLY the fields that we want on the primary history tape. Outputting history files as a vector in order to analyze the plant function types within gridcells -------------------------------------------------------------------------------------------------- diff --git a/doc/source/users_guide/setting-up-and-running-a-case/history_fields_fates.rst b/doc/source/users_guide/setting-up-and-running-a-case/history_fields_fates.rst index 84caf92465..ea964fc433 100644 --- a/doc/source/users_guide/setting-up-and-running-a-case/history_fields_fates.rst +++ b/doc/source/users_guide/setting-up-and-running-a-case/history_fields_fates.rst @@ -3,6 +3,8 @@ CTSM History Fields (fates) ============================= CAUTION: Not all variables are relevant / present for all CTSM cases. +NOTE: Instantaneous fields will appear in history files labeled with h0i, h1i, ... and non-instantaneous fields will appear in history files labeled with h0a, h1a, ... + Key flags used in this CTSM case: use_cn = F use_crop = F diff --git a/doc/source/users_guide/setting-up-and-running-a-case/history_fields_nofates.rst b/doc/source/users_guide/setting-up-and-running-a-case/history_fields_nofates.rst index 67868f75b1..5322ae79a2 100644 --- a/doc/source/users_guide/setting-up-and-running-a-case/history_fields_nofates.rst +++ b/doc/source/users_guide/setting-up-and-running-a-case/history_fields_nofates.rst @@ -3,6 +3,8 @@ CTSM History Fields (nofates) ============================= CAUTION: Not all variables are relevant / present for all CTSM cases. +NOTE: Instantaneous fields will appear in history files labeled with h0i, h1i, ... and non-instantaneous fields will appear in history files labeled with h0a, h1a, ... + Key flags used in this CTSM case: use_cn = T use_crop = T diff --git a/doc/source/users_guide/using-clm-tools/paramfile-tools.md b/doc/source/users_guide/using-clm-tools/paramfile-tools.md index 71d881e362..aaee301bcb 100644 --- a/doc/source/users_guide/using-clm-tools/paramfile-tools.md +++ b/doc/source/users_guide/using-clm-tools/paramfile-tools.md @@ -57,9 +57,14 @@ Change a one-dimensional parameter (`mimics_fmet` has the `segment` dimension, l tools/param_utils/set_paramfile -i paramfile.nc -o output.nc mimics_fmet=0.1,0.2,0.3,0.4 ``` +Change a one-dimensional parameter to be all one value (`mxmat` has the `pft` dimension, length 79): +```bash +tools/param_utils/set_paramfile -i paramfile.nc -o output.nc mxmat=360 +``` + Change a parameter for specific PFTs: ```bash -tools/param_utils/set_paramfile -i paramfile.nc -o output.nc -p needleleaf_evergreen_temperate_tree,c4_grass medlynintercept=99.9,100.1 medlynslope=2.99,1.99 +tools/param_utils/set_paramfile -i paramfile.nc -o output.nc -p needleleaf_evergreen_temperate_tree,c4_grass medlynintercept=99.9,100.1 medlynslope=2.99,1.99 mxmat=199 ``` Set a parameter to the fill value: diff --git a/python/ctsm/param_utils/paramfile_shared.py b/python/ctsm/param_utils/paramfile_shared.py index b9860be0db..d12773dbdb 100644 --- a/python/ctsm/param_utils/paramfile_shared.py +++ b/python/ctsm/param_utils/paramfile_shared.py @@ -85,7 +85,9 @@ def get_selected_pft_indices(selected_pfts, pft_names): list of int Indices of selected PFTs. """ - indices = [i for i, name in enumerate(pft_names) if name in selected_pfts] + if isinstance(selected_pfts, str): + selected_pfts = [selected_pfts] + indices = [pft_names.index(pft) for pft in selected_pfts] return indices diff --git a/python/ctsm/param_utils/set_paramfile.py b/python/ctsm/param_utils/set_paramfile.py index 2ceb52069b..6efa491846 100644 --- a/python/ctsm/param_utils/set_paramfile.py +++ b/python/ctsm/param_utils/set_paramfile.py @@ -130,7 +130,7 @@ def check_correct_ndims(da, new_value, throw_error=False): """ expected = da.ndim actual = np.array(new_value).ndim - is_ndim_correct = expected == actual + is_ndim_correct = actual in (0, expected) # If actual 0, apply it to all if throw_error and not is_ndim_correct: raise RuntimeError(f"Incorrect N dims: Expected {expected}, got {actual}") return is_ndim_correct @@ -323,11 +323,12 @@ def apply_new_value_to_parameter(args, ds_out, var, new_value, var_encoding, *, # Ensure that any NaNs are replaced with the fill value new_value = _replace_nans_with_fill(var_encoding, new_value, chg=chg) - # This can be needed if, e.g., you're selecting and changing just one PFT + # This can be needed if, (a) you're selecting and changing just one PFT or (b) you're changing + # all values in a dimensioned parameter to match one value. if ds_out[var].values.ndim > 0 and new_value.ndim == 0: - new_value = np.atleast_1d(new_value) - - ds_out[var].values = new_value + ds_out[var].values[:] = new_value + else: + ds_out[var].values = new_value return ds_out diff --git a/python/ctsm/test/test_sys_set_paramfile.py b/python/ctsm/test/test_sys_set_paramfile.py index 7d82a32302..f65c8e0d50 100755 --- a/python/ctsm/test/test_sys_set_paramfile.py +++ b/python/ctsm/test/test_sys_set_paramfile.py @@ -121,8 +121,11 @@ def test_set_paramfile_changeparams_scalar_errors_given_list(self): with self.assertRaisesRegex(RuntimeError, "Incorrect N dims"): sp.main() - def test_set_paramfile_changeparam_1d_errors_given_scalar(self): - """Test that set_paramfile errors if given a scalar for a 1-d parameter""" + def test_set_paramfile_changeparam_1d_given_scalar(self): + """ + Test that set_paramfile works correctly if given a scalar for a 1-d parameter. We want it + to set all members of the 1d array to the given scalar. + """ output_path = os.path.join(self.tempdir, "output.nc") sys.argv = [ "set_paramfile", @@ -130,10 +133,53 @@ def test_set_paramfile_changeparam_1d_errors_given_scalar(self): PARAMFILE, "-o", output_path, - "xl=0.724", + "mxmat=1987", ] - with self.assertRaisesRegex(RuntimeError, "Incorrect N dims"): - sp.main() + sp.main() + self.assertTrue(os.path.exists(output_path)) + ds_in = open_paramfile(PARAMFILE) + ds_out = open_paramfile(output_path) + + for var in ds_in.variables: + # Check that all variables/coords are equal except the ones we changed, which should be + # set to what we asked + if var == "mxmat": + self.assertTrue(np.all(ds_out[var].values == 1987)) + else: + self.assertTrue(are_paramfile_dataarrays_identical(ds_in[var], ds_out[var])) + + def test_set_paramfile_changeparam_1d_given_scalar_and_pftlist(self): + """ + Test that set_paramfile works correctly if given a scalar for a 1-d parameter. We want it + to set all members of the 1d array to the given scalar. As + test_set_paramfile_changeparam_1d_given_scalar, but here we give a pft list. + """ + output_path = os.path.join(self.tempdir, "output.nc") + sys.argv = [ + "set_paramfile", + "-i", + PARAMFILE, + "-o", + output_path, + "-p", + "temperate_corn,irrigated_temperate_corn", + "mxmat=1987", + ] + sp.main() + self.assertTrue(os.path.exists(output_path)) + ds_in = open_paramfile(PARAMFILE) + ds_out = open_paramfile(output_path) + + for var in ds_in.variables: + # Check that all variables/coords are equal except the ones we changed, which should be + # set to what we asked + if var == "mxmat": + # First, check that they weren't 1987 before + self.assertFalse(np.any(ds_in[var].values[17:18] == 1987)) + # Now check that they are 1987 + self.assertTrue(np.all(ds_out[var].values[17:18] == 1987)) + else: + self.assertTrue(are_paramfile_dataarrays_identical(ds_in[var], ds_out[var])) def test_set_paramfile_changeparams_scalar_double(self): """Test that set_paramfile can copy to a new file with some scalar double params changed""" @@ -732,6 +778,46 @@ def test_set_paramfile_double_ok_given_int(self): ds_out = open_paramfile(output_path) self.assertFalse(sp.is_integer(ds_out[param_name].values)) + def test_set_paramfile_pft_order(self): + """ + Test that set_paramfile gives the same result regardless of the order you specify the PFTs + """ + + # First order + pfts_to_include = ["rice", "irrigated_rice"] + output0_path = os.path.join(self.tempdir, "output0.nc") + sys.argv = [ + "set_paramfile", + "-i", + PARAMFILE, + "-o", + output0_path, + "-p", + ",".join(pfts_to_include), + "mxmat=100,200", + ] + sp.main() + + # Reverse order + pfts_to_include.reverse() + output1_path = os.path.join(self.tempdir, "output1.nc") + sys.argv = [ + "set_paramfile", + "-i", + PARAMFILE, + "-o", + output1_path, + "-p", + ",".join(pfts_to_include), + "mxmat=200,100", + ] + sp.main() + + # These files should be identical + ds0 = open_paramfile(output0_path) + ds1 = open_paramfile(output1_path) + self.assertTrue(ds0.equals(ds1)) + if __name__ == "__main__": unit_testing.setup_for_tests() diff --git a/python/ctsm/test/test_unit_paramfile_shared.py b/python/ctsm/test/test_unit_paramfile_shared.py new file mode 100755 index 0000000000..e0adebc7c2 --- /dev/null +++ b/python/ctsm/test/test_unit_paramfile_shared.py @@ -0,0 +1,57 @@ +#!/usr/bin/env python3 + +"""Unit tests for paramfile_shared""" + +import unittest + +from ctsm import unit_testing + +from ctsm.param_utils import paramfile_shared as ps + +# Allow names that pylint doesn't like, because otherwise I find it hard +# to make readable unit test names +# pylint: disable=invalid-name + + +class TestUnitGetSelectedPftIndices(unittest.TestCase): + """Unit tests of get_selected_pft_indices""" + + def test_get_selected_pft_indices_1strselected_onlyinlist(self): + """Check get_selected_pft_indices() given the only one in the list, as a string""" + selected_pfts = "rice" + pft_names = ["rice"] + result = ps.get_selected_pft_indices(selected_pfts=selected_pfts, pft_names=pft_names) + self.assertListEqual(result, [0]) + + def test_get_selected_pft_indices_1selected_onlyinlist(self): + """Check get_selected_pft_indices() given the only one in the list, as a list""" + selected_pfts = ["rice"] + pft_names = ["rice"] + result = ps.get_selected_pft_indices(selected_pfts=selected_pfts, pft_names=pft_names) + self.assertListEqual(result, [0]) + + def test_get_selected_pft_indices_2selected_sameorder(self): + """Check get_selected_pft_indices() given 2 selected in the same order as the list""" + pft_names = ["rice", "irrigated_rice"] + result = ps.get_selected_pft_indices(selected_pfts=pft_names, pft_names=pft_names) + self.assertListEqual(result, [0, 1]) + + def test_get_selected_pft_indices_2selected_difforder(self): + """Check get_selected_pft_indices() given 2 selected NOT in the same order as the list""" + pft_names = ["rice", "irrigated_rice"] + result = ps.get_selected_pft_indices( + selected_pfts=list(reversed(pft_names)), pft_names=pft_names + ) + self.assertListEqual(result, [1, 0]) + + def test_get_selected_pft_indices_missing_valueerror(self): + """Check get_selected_pft_indices() given selected pft NOT in the list""" + selected_pfts = ["wheat"] + pft_names = ["rice", "irrigated_rice"] + with self.assertRaises(ValueError): + ps.get_selected_pft_indices(selected_pfts=selected_pfts, pft_names=pft_names) + + +if __name__ == "__main__": + unit_testing.setup_for_tests() + unittest.main() diff --git a/python/ctsm/test/test_unit_set_paramfile.py b/python/ctsm/test/test_unit_set_paramfile.py index 6b091f8967..13be015c67 100755 --- a/python/ctsm/test/test_unit_set_paramfile.py +++ b/python/ctsm/test/test_unit_set_paramfile.py @@ -65,14 +65,14 @@ def test_checkcorrectndims_0d_int_np(self): self.assertTrue(sp.check_correct_ndims(da, np.int32(1))) def test_checkcorrectndims_1d_int(self): - """Check False when given a standard int for a 0d parameter""" + """Check True when given a standard int for a 0d parameter""" da = xr.DataArray(data=[1, 2]) - self.assertFalse(sp.check_correct_ndims(da, 1)) + self.assertTrue(sp.check_correct_ndims(da, 1)) def test_checkcorrectndims_1d_int_np(self): - """Check False when given a numpy int for a 0d parameter""" + """Check True when given a numpy int for a 0d parameter""" da = xr.DataArray(data=[1, 2]) - self.assertFalse(sp.check_correct_ndims(da, np.int32(1))) + self.assertTrue(sp.check_correct_ndims(da, np.int32(1))) def test_checkcorrectndims_0d_list(self): """Check False when given a list for a 0d parameter""" diff --git a/python/ctsm/test/test_unit_subset_data.py b/python/ctsm/test/test_unit_subset_data.py index a127a282e0..81b4fb3281 100755 --- a/python/ctsm/test/test_unit_subset_data.py +++ b/python/ctsm/test/test_unit_subset_data.py @@ -107,7 +107,7 @@ def test_inputdata_setup_files_basic(self): files = setup_files(self.args, self.defaults, self.cesmroot, testing=True) self.assertEqual( files["fsurf_in"], - "surfdata_0.9x1.25_hist_2000_16pfts_c240908.nc", + "surfdata_0.9x1.25_hist_2000_16pfts_c251022.nc", "fsurf_in filename not whats expected", ) self.assertEqual( @@ -117,7 +117,7 @@ def test_inputdata_setup_files_basic(self): ) self.assertEqual( files["main_dir"], - "/glade/campaign/cesm/cesmdata/cseg/inputdata", + "/glade/campaign/cesm/cesmdata/inputdata", "main_dir directory not whats expected", ) diff --git a/python/ctsm/test/testinputs/default_data.cfg b/python/ctsm/test/testinputs/default_data.cfg index 60c012561c..a220a0ee90 100644 --- a/python/ctsm/test/testinputs/default_data.cfg +++ b/python/ctsm/test/testinputs/default_data.cfg @@ -15,16 +15,16 @@ precname = CLMCRUJRA2024.Precip tpqwname = CLMCRUJRA2024.TPQW [surfdat] -dir = lnd/clm2/surfdata_esmf/ctsm5.3.0 -surfdat_16pft = surfdata_0.9x1.25_hist_2000_16pfts_c240908.nc -surfdat_78pft = surfdata_0.9x1.25_hist_2000_78pfts_c240908.nc +dir = lnd/clm2/surfdata_esmf/ctsm5.4.0 +surfdat_16pft = surfdata_0.9x1.25_hist_2000_16pfts_c251022.nc +surfdat_78pft = surfdata_0.9x1.25_hist_2000_78pfts_c251022.nc mesh_dir = share/meshes/ mesh_surf = fv0.9x1.25_141008_ESMFmesh.nc [landuse] -dir = lnd/clm2/surfdata_esmf/ctsm5.3.0 -landuse_16pft = landuse.timeseries_0.9x1.25_SSP2-4.5_1850-2100_78pfts_c240908.nc -landuse_78pft = landuse.timeseries_0.9x1.25_SSP2-4.5_1850-2100_78pfts_c240908.nc +dir = lnd/clm2/surfdata_esmf/ctsm5.4.0 +landuse_16pft = landuse.timeseries_0.9x1.25_hist_1850-2023_16pfts_c251022.nc +landuse_78pft = landuse.timeseries_0.9x1.25_hist_1850-2023_78pfts_c251022.nc [domain] file = share/domains/domain.lnd.fv0.9x1.25_gx1v7.151020.nc diff --git a/python/ctsm/test/testinputs/default_data_gswp3.cfg b/python/ctsm/test/testinputs/default_data_gswp3.cfg index 09e1463eb2..a7da53cea3 100644 --- a/python/ctsm/test/testinputs/default_data_gswp3.cfg +++ b/python/ctsm/test/testinputs/default_data_gswp3.cfg @@ -15,16 +15,16 @@ precname = CLMGSWP3v1.Precip tpqwname = CLMGSWP3v1.TPQW [surfdat] -dir = lnd/clm2/surfdata_esmf/ctsm5.3.0 -surfdat_16pft = surfdata_0.9x1.25_hist_2000_16pfts_c240908.nc -surfdat_78pft = surfdata_0.9x1.25_hist_2000_78pfts_c240908.nc +dir = lnd/clm2/surfdata_esmf/ctsm5.4.0 +surfdat_16pft = surfdata_0.9x1.25_hist_2000_16pfts_c251022.nc +surfdat_78pft = surfdata_0.9x1.25_hist_2000_78pfts_c251022.nc mesh_dir = share/meshes/ mesh_surf = fv0.9x1.25_141008_ESMFmesh.nc [landuse] -dir = lnd/clm2/surfdata_esmf/ctsm5.3.0 -landuse_16pft = landuse.timeseries_0.9x1.25_SSP2-4.5_1850-2100_78pfts_c240908.nc -landuse_78pft = landuse.timeseries_0.9x1.25_SSP2-4.5_1850-2100_78pfts_c240908.nc +dir = lnd/clm2/surfdata_esmf/ctsm5.4.0 +landuse_16pft = landuse.timeseries_0.9x1.25_hist_1850-2023_16pfts_c251022.nc +landuse_78pft = landuse.timeseries_0.9x1.25_hist_1850-2023_78pfts_c251022.nc [domain] file = share/domains/domain.lnd.fv0.9x1.25_gx1v7.151020.nc diff --git a/share b/share index 14338bef3f..ec0475f05b 160000 --- a/share +++ b/share @@ -1 +1 @@ -Subproject commit 14338bef3fa604d49160e376257264db1d3313e5 +Subproject commit ec0475f05bde376bab73c74d09450549ec746d86 diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 2682775ca5..bc925b7dd1 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,17 +1,18 @@ -cmake_minimum_required(VERSION 2.8) +cmake_minimum_required(VERSION 3.10) list(APPEND CMAKE_MODULE_PATH ${CIME_CMAKE_MODULE_DIRECTORY}) include(CIME_initial_setup) -#list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/../tools/mksurfdata_esmf/cmake") -list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/../share/cmake") -list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/../components/cmeps/cmake") +set(SRCROOT "${CIMEROOT}/..") +set(CLM_ROOT "..") + +list(APPEND CMAKE_MODULE_PATH "${SRCROOT}/share/cmake") +list(APPEND CMAKE_MODULE_PATH "${SRCROOT}/components/cmeps/cmake") project(clm_tests Fortran C) include(CIME_utils) -set(CLM_ROOT "..") # find needed external packages # NetCDF is required -- because PIO and NetCDF are required by the standard default ESMF libraries @@ -38,15 +39,15 @@ link_libraries(${ESMF_INTERFACE_LINK_LIBRARIES}) # done first, so that in case of name collisions, the CLM versions take # precedence (when there are two files with the same name, the one added later # wins). -add_subdirectory(${CLM_ROOT}/share/src csm_share) -add_subdirectory(${CLM_ROOT}/share/unit_test_stubs/util csm_share_stubs) +add_subdirectory(${SRCROOT}/share/src csm_share) +add_subdirectory(${SRCROOT}/share/unit_test_stubs/util csm_share_stubs) # Add files needed from CMEPS list ( APPEND drv_sources_needed - ${CLM_ROOT}/components/cmeps/cesm/nuopc_cap_share/glc_elevclass_mod.F90 - ${CLM_ROOT}/components/cmeps/cesm/nuopc_cap_share/shr_dust_emis_mod.F90 - ${CLM_ROOT}/components/cmeps/cesm/nuopc_cap_share/shr_expr_parser_mod.F90 - ${CLM_ROOT}/components/cmeps/cesm/nuopc_cap_share/shr_fire_emis_mod.F90 + ${SRCROOT}/components/cmeps/cesm/nuopc_cap_share/glc_elevclass_mod.F90 + ${SRCROOT}/components/cmeps/cesm/nuopc_cap_share/shr_dust_emis_mod.F90 + ${SRCROOT}/components/cmeps/cesm/nuopc_cap_share/shr_expr_parser_mod.F90 + ${SRCROOT}/components/cmeps/cesm/nuopc_cap_share/shr_fire_emis_mod.F90 ) # Add CLM source directories @@ -93,10 +94,10 @@ add_library(csm_share ${share_sources} ${drv_sources_needed}) declare_generated_dependencies(csm_share "${share_genf90_sources}") add_library(clm ${clm_sources}) declare_generated_dependencies(clm "${clm_genf90_sources}") -add_dependencies(clm csm_share esmf) +add_dependencies(clm csm_share ESMF) # We need to look for header files here, in order to pick up shr_assert.h -include_directories(${CLM_ROOT}/share/include) +include_directories(${SRCROOT}/share/include) # Tell cmake to look for libraries & mod files here, because this is where we built libraries include_directories(${CMAKE_CURRENT_BINARY_DIR}) diff --git a/src/biogeochem/AtmCarbonIsotopeStreamType.F90 b/src/biogeochem/AtmCarbonIsotopeStreamType.F90 new file mode 100644 index 0000000000..0a49ac8061 --- /dev/null +++ b/src/biogeochem/AtmCarbonIsotopeStreamType.F90 @@ -0,0 +1,238 @@ +module AtmCarbonIsotopeStreamType + ! + ! Description: + ! + ! This extends the stream base type to implement streams for atmospheric + ! Carbon isotope ratios that are read in from streams datasets (delta C13 and delta C14). + ! + use shr_kind_mod , only : r8 => shr_kind_r8 + use clm_varctl , only : iulog + use abortutils , only : endrun + use decompMod , only : bounds_type + use CTSMForce2DStreamBaseType, only : ctsm_force_2DStream_base_type + + implicit none + private + + !----------------------------------------------------------------------- + ! Atmospheric Delta C13 Stream Type + !----------------------------------------------------------------------- + character(len=*), parameter :: varname_c13 = 'delta13co2_in_air' + type, public, extends(ctsm_force_2DStream_base_type) :: atm_delta_c13_stream_type + private + real(r8), public, allocatable :: atm_delta_c13(:) ! delta C13 data array + contains + + ! Public Methods + procedure, public :: C13Init ! C13 initialization + procedure, public :: Init => C13Init ! Generic name for the initialization + procedure, public :: C13Interp ! C13 Interp method to fill the local data array + procedure, public :: Interp => C13Interp ! Generic name for the Interp method + procedure, public :: C13ClassClean ! C13 clean method as a class method + procedure, public :: Clean => C13ClassClean ! Generic name for the clean method + final :: C13TypeClean ! This clean method may be called by the compiler when the type goes out of scope + ! Private methods + procedure, private :: C13InitAllocate ! Allocate the local C13 data + + end type atm_delta_c13_stream_type + + !----------------------------------------------------------------------- + ! Atmospheric Delta C14 Stream Type + !----------------------------------------------------------------------- + character(len=*), parameter :: varname_c14 = 'Delta14co2_in_air' + type, public, extends(ctsm_force_2DStream_base_type) :: atm_delta_c14_stream_type + private + real(r8), public, allocatable :: atm_delta_c14(:) ! delta c14 data array + contains + + ! Public Methods + procedure, public :: C14Init ! C14 initialization + procedure, public :: Init => C14Init ! Generic name for the initialization + procedure, public :: C14Interp ! C14 Interp method to fill the local data array + procedure, public :: Interp => C14Interp ! Generic name for the Interp method + procedure, public :: C14ClassClean ! C14 clean method as a class method + procedure, public :: Clean => C14ClassClean ! Generic name for the clean method + final :: C14TypeClean ! This clean method may be called by the compiler when the type goes out of scope + ! Private methods + procedure, private :: C14InitAllocate ! Allocate the local C14 data + + end type atm_delta_c14_stream_type + + character(len=*), parameter, private :: sourcefile = & + __FILE__ + + !----------------------------------------------------------------------- + contains + !----------------------------------------------------------------------- + + !------------------------------------------------------------------------------------- + + subroutine C13Init( this, bounds, fldfilename, meshfile, mapalgo, tintalgo, taxmode, & + year_first, year_last, model_year_align ) + ! + ! Initialize the atmospheric delta C13 stream type + ! + ! Arguments: + class(atm_delta_c13_stream_type), intent(inout) :: this + type(bounds_type), intent(in) :: bounds + character(*), intent(in) :: fldfilename ! stream data filename (full pathname) (single file) + character(*), intent(in) :: meshfile ! full pathname to stream mesh file (none for global data) + character(*), intent(in) :: mapalgo ! stream mesh -> model mesh mapping type + character(*), intent(in) :: tintalgo ! time interpolation algorithm + character(*), intent(in) :: taxMode ! time axis mode + integer, intent(in) :: year_first ! first year to use + integer, intent(in) :: year_last ! last year to use + integer, intent(in) :: model_year_align ! align yearFirst with this model year + + call this%InitBase( bounds, varnames = (/ varname_c13 /), fldfilename=fldfilename, meshfile=meshfile, & + mapalgo=mapalgo, tintalgo=tintalgo, taxmode=taxmode, name=varname_c13, & + year_first=year_first, year_last=year_last, model_year_align=model_year_align ) + call this%C13InitAllocate( bounds ) + + end subroutine C13Init + + !------------------------------------------------------------------------------------- + + subroutine C13InitAllocate( this, bounds ) + ! Allocate memory for the delta C13 data array + use shr_infnan_mod , only : nan => shr_infnan_nan, assignment(=) + class(atm_delta_c13_stream_type), intent(inout) :: this + type(bounds_type), intent(in) :: bounds + + integer :: begg, endg + + begg = bounds%begg; endg = bounds%endg + allocate( this%atm_delta_c13( bounds%begg : bounds%endg ) ); this%atm_delta_c13 = nan + end subroutine C13InitAllocate + + !------------------------------------------------------------------------------------- + + subroutine C13ClassClean( this ) + ! Clean up memory for the C13 stream type as a class method + class(atm_delta_c13_stream_type), intent(inout) :: this + + call C13TypeClean( this ) + + end subroutine C13ClassClean + + !------------------------------------------------------------------------------------- + + subroutine C13TypeClean( this ) + ! Clean up memory for the C13 stream type for this specific type + type(atm_delta_c13_stream_type), intent(inout) :: this + + deallocate( this%atm_delta_c13 ) + call this%CleanBase() + + end subroutine C13TypeClean + + !------------------------------------------------------------------------------------- + + subroutine C13Interp( this, bounds ) + ! + ! Fill the local CTSM grid delta C13 array with data from the stream + ! + ! Arguments + class(atm_delta_c13_stream_type), intent(inout) :: this + type(bounds_type), intent(in) :: bounds + + ! Local Variables + integer :: g + real(r8), pointer :: dataptr1d(:) + integer :: rc ! error return code + + ! Get pointer for stream data that is time and spatially interpolated to model time and grid + call this%GetPtr1D( varname_c13, dataptr1d ) + + do g = bounds%begg, bounds%endg + this%atm_delta_c13(g) = dataptr1d(g) + end do + end subroutine C13Interp + + !------------------------------------------------------------------------------------- + + subroutine C14Init( this, bounds, fldfilename, meshfile, mapalgo, tintalgo, taxmode, & + year_first, year_last, model_year_align ) + ! + ! Initialize the atmospheric delta C14 stream type + ! + ! Arguments: + class(atm_delta_c14_stream_type), intent(inout) :: this + type(bounds_type), intent(in) :: bounds + character(*), intent(in) :: fldfilename ! stream data filename (full pathname) (single file) + character(*), intent(in) :: meshfile ! full pathname to stream mesh file (none for global data) + character(*), intent(in) :: mapalgo ! stream mesh -> model mesh mapping type + character(*), intent(in) :: tintalgo ! time interpolation algorithm + character(*), intent(in) :: taxMode ! time axis mode + integer, intent(in) :: year_first ! first year to use + integer, intent(in) :: year_last ! last year to use + integer, intent(in) :: model_year_align ! align yearFirst with this model year + + call this%InitBase( bounds, varnames = (/ varname_c14 /), fldfilename=fldfilename, meshfile=meshfile, & + mapalgo=mapalgo, tintalgo=tintalgo, taxmode=taxmode, name=varname_c14, & + year_first=year_first, year_last=year_last, model_year_align=model_year_align ) + call this%C14InitAllocate( bounds ) + + end subroutine C14Init + + !------------------------------------------------------------------------------------- + + subroutine C14InitAllocate( this, bounds ) + ! Allocate memory for the delta C14 data array + use shr_infnan_mod , only : nan => shr_infnan_nan, assignment(=) + ! Arguments + class(atm_delta_c14_stream_type), intent(inout) :: this + type(bounds_type), intent(in) :: bounds + + integer :: begg, endg + + begg = bounds%begg; endg = bounds%endg + allocate( this%atm_delta_c14( bounds%begg : bounds%endg ) ); this%atm_delta_c14 = nan + end subroutine C14InitAllocate + + !------------------------------------------------------------------------------------- + + subroutine C14Interp( this, bounds ) + ! Fill the local CTSM grid delta C13 array with data from the stream + ! + ! Arguments: + class(atm_delta_c14_stream_type), intent(inout) :: this + type(bounds_type), intent(in) :: bounds + + ! Local Variables + integer :: g + real(r8), pointer :: dataptr1d(:) + integer :: rc ! error return code + + ! Get pointer for stream data that is time and spatially interpolated to model time and grid + call this%GetPtr1D( varname_c14, dataptr1d ) + + do g = bounds%begg, bounds%endg + this%atm_delta_c14(g) = dataptr1d(g) + end do + end subroutine C14Interp + + !------------------------------------------------------------------------------------- + + subroutine C14ClassClean( this ) + ! Clean up memory for the C14 stream type as a class method + class(atm_delta_c14_stream_type), intent(inout) :: this + + call C14TypeClean( this ) + + end subroutine C14ClassClean + + !------------------------------------------------------------------------------------- + + subroutine C14TypeClean( this ) + ! Clean up memory for the C14 stream type for this specific type + type(atm_delta_c14_stream_type), intent(inout) :: this + + deallocate( this%atm_delta_c14 ) + call this%CleanBase() + + end subroutine C14TypeClean + + !------------------------------------------------------------------------------------- + +end module AtmCarbonIsotopeStreamType \ No newline at end of file diff --git a/src/biogeochem/CMakeLists.txt b/src/biogeochem/CMakeLists.txt index 3da0a2eab6..393e4e4db2 100644 --- a/src/biogeochem/CMakeLists.txt +++ b/src/biogeochem/CMakeLists.txt @@ -28,6 +28,7 @@ list(APPEND clm_sources CNVegNitrogenStateType.F90 CNVegNitrogenFluxType.F90 CNCIsoAtmTimeSeriesReadMod.F90 + AtmCarbonIsotopeStreamType.F90 CNVegComputeSeedMod.F90 FATESFireBase.F90 FATESFireDataMod.F90 diff --git a/src/biogeochem/CNCIsoAtmTimeSeriesReadMod.F90 b/src/biogeochem/CNCIsoAtmTimeSeriesReadMod.F90 index 529a547e88..736924fd4f 100644 --- a/src/biogeochem/CNCIsoAtmTimeSeriesReadMod.F90 +++ b/src/biogeochem/CNCIsoAtmTimeSeriesReadMod.F90 @@ -1,22 +1,27 @@ module CIsoAtmTimeseriesMod +#include "shr_assert.h" + !----------------------------------------------------------------------- ! Module for transient atmospheric boundary to the c13 and c14 codes ! ! !USES: - use shr_kind_mod , only : r8 => shr_kind_r8 + use shr_kind_mod , only : r8 => shr_kind_r8, CL => shr_kind_CL use clm_time_manager , only : get_curr_date, get_curr_yearfrac - use clm_varcon , only : c14ratio, secspday + use clm_varcon , only : c13ratio, c14ratio, secspday use shr_const_mod , only : SHR_CONST_PDB ! Ratio of C13/C12 - use clm_varctl , only : iulog + use clm_varctl , only : iulog, use_c13, use_c14 use abortutils , only : endrun use spmdMod , only : masterproc use shr_log_mod , only : errMsg => shr_log_errMsg + use AtmCarbonIsotopeStreamType, only : atm_delta_c13_stream_type, atm_delta_c14_stream_type + use decompMod , only : bounds_type ! implicit none private ! ! !PUBLIC MEMBER FUNCTIONS: + public:: CIsoAtmReadNML ! Read namelist for atmospheric C14/C13 isotope time series public:: C14BombSpike ! Time series for C14 data public:: C14_init_BombSpike ! Initialize C14 data series and read data in public:: C13Timeseries ! Time series for C13 data @@ -27,91 +32,439 @@ module CIsoAtmTimeseriesMod character(len=256) , public :: atm_c14_filename = ' ' ! file name of C14 input data logical , public :: use_c13_timeseries = .false. ! do we use time-varying atmospheric C13? character(len=256) , public :: atm_c13_filename = ' ' ! file name of C13 input data - integer, parameter , public :: nsectors_c14 = 3 ! Number of latitude sectors the C14 data has + real(r8), allocatable, public, protected :: rc14_atm_grc(:) ! Ratio of C14 C12 data on gridcell + real(r8), allocatable, public, protected :: rc13_atm_grc(:) ! Ratio of C13 C12 data on gridcell ! ! !PRIVATE MEMBER FUNCTIONS: private:: check_units ! Check the units of the data on the input file + ! Private subroutines only made public for unit testing + public:: CIsoCheckNMLInputs ! Check that the namelist inputs are valid + public:: CIsoSetNMLInputs ! Set the namelist inputs for unit testing + public:: CIsoSetControl ! Set the control variables for Carbon Isotopes + public:: CIsoLogControl ! Write out the control settings to the logfile + + type(atm_delta_c13_stream_type), private :: atm_c13_stream ! Atmospheric C13 stream object + type(atm_delta_c14_stream_type), private :: atm_c14_stream ! Atmospheric C14 stream object + ! !PRIVATE TYPES: + integer, parameter , private :: nsectors_c14 = 3 ! Number of latitude sectors the C14 data has real(r8), allocatable, private :: atm_c14file_time(:) ! time for C14 data - real(r8), allocatable, private :: atm_delta_c14(:,:) ! Delta C14 data + real(r8), allocatable, private :: atm_delta_c14(:,:) ! Delta C14 data (time,nsectors) + real(r8), allocatable, private :: atm_delta_c14_grc(:) ! Delta C14 data on gridcell real(r8), allocatable, private :: atm_c13file_time(:) ! time for C13 data - real(r8), allocatable, private :: atm_delta_c13(:) ! Delta C13 data + real(r8), allocatable, private :: atm_delta_c13(:) ! Delta C13 data (time) + real(r8), allocatable, private :: atm_delta_c13_grc(:) ! Delta C13 data on gridcell real(r8), parameter :: time_axis_offset = 1850.0_r8 ! Offset in years of time on file + logical, private :: use_c13_streams = .false. ! By default read in the CMIP6 file format for C13 + logical, private :: use_c14_streams = .false. ! By default read in the CMIP6 file format for C14 + + ! Private data for the control namelist: + character(len=CL), private :: stream_fldfilename_atm_c14 = ' ' + character(len=CL), private :: stream_fldfilename_atm_c13 = ' ' + integer, private :: stream_year_first_atm_c14 = 1850 + integer, private :: stream_year_last_atm_c14 = 2023 + integer, private :: stream_model_year_align_atm_c14 = 1850 + integer, private :: stream_year_first_atm_c13 = 1850 + integer, private :: stream_year_last_atm_c13 = 2023 + integer, private :: stream_model_year_align_atm_c13 = 1850 + character(len=CL), private :: stream_mapalgo_atm_c14 = 'nn' + character(len=CL), private :: stream_tintalgo_atm_c14 = 'linear' + character(len=CL), private :: stream_taxmode_atm_c14 = 'extend' + character(len=CL), private :: stream_mapalgo_atm_c13 = 'nn' + character(len=CL), private :: stream_tintalgo_atm_c13 = 'linear' + character(len=CL), private :: stream_taxmode_atm_c13 = 'extend' + character(len=*), parameter, private :: sourcefile = & - __FILE__ + __FILE__ !----------------------------------------------------------------------- contains !----------------------------------------------------------------------- - subroutine C14BombSpike( rc14_atm ) + subroutine CIsoAtmReadNML( NLFilename ) + ! + ! !DESCRIPTION: + ! Read in the namelist for atmospheric C14/C13 isotope time series + ! + ! Uses: + use shr_nl_mod , only : shr_nl_find_group_name + use spmdMod , only : masterproc, mpicom + use shr_mpi_mod, only : shr_mpi_bcast + + ! Arguments: + character(len=*), intent(in) :: NLFilename ! Namelist filename to read + + ! !LOCAL VARIABLES: + integer :: ierr ! error code + integer :: unitn ! unit for namelist file + character(len=*), parameter :: nml_name = 'carbon_isotope_streams' ! MUST agree with name in namelist and read + + namelist /carbon_isotope_streams/ stream_fldfilename_atm_c14, & + stream_fldfilename_atm_c13, stream_year_first_atm_c14, & + stream_year_last_atm_c14, stream_model_year_align_atm_c14, & + stream_year_first_atm_c13, stream_year_last_atm_c13, & + stream_model_year_align_atm_c13 + + ! Read in the namelist on the main task + if (masterproc) then + open( newunit=unitn, file=trim(NLFilename), status='old', iostat=ierr ) + write(iulog,*) 'Read in '//nml_name//' namelist' + call shr_nl_find_group_name(unitn, nml_name, status=ierr) + if (ierr == 0) then + read(unitn, nml=carbon_isotope_streams, iostat=ierr) + if (ierr /= 0) then + call endrun(msg="ERROR reading "//nml_name//"namelist", file=sourcefile, line=__LINE__) + return + end if + else + call endrun(msg="ERROR could NOT find "//nml_name//"namelist", file=sourcefile, line=__LINE__) + return + end if + close( unitn ) + end if + ! Broadcast namelist values to all tasks + call shr_mpi_bcast( stream_fldfilename_atm_c14, mpicom ) + call shr_mpi_bcast( stream_year_first_atm_c14, mpicom ) + call shr_mpi_bcast( stream_year_last_atm_c14, mpicom ) + call shr_mpi_bcast( stream_model_year_align_atm_c14, mpicom ) + call shr_mpi_bcast( stream_fldfilename_atm_c13, mpicom ) + call shr_mpi_bcast( stream_year_first_atm_c13, mpicom ) + call shr_mpi_bcast( stream_year_last_atm_c13, mpicom ) + call shr_mpi_bcast( stream_model_year_align_atm_c13, mpicom ) + + ! Do some error checking of input namelist items, set control flags, and write to the log + call CIsoCheckNMLInputs() + + call CIsoSetControl() + call CIsoLogControl() + + end subroutine CIsoAtmReadNML + + !----------------------------------------------------------------------- + subroutine CIsoSetControl() + ! Set control settings based on the namelist inputs + ! Also do some assert checks to make sure other settings are as expected + ! + if ( use_c13_timeseries )then + ! Decide if C14/C13 streams are going to be used or the old method + if ( len_trim(stream_fldfilename_atm_c13) /= 0 ) then + use_c13_streams = .true. + else + use_c13_streams = .false. + call shr_assert( len_trim(atm_c13_filename) /= 0 , & + msg="ERROR: use_c13_timeseries is true but atm_c13_filename is blank", file=sourcefile, line=__LINE__) + call shr_assert( .not. use_c13_streams , & + msg="ERROR: stream_fldfilename_atm_c13 is blank but use_c13_streams is not TRUE", file=sourcefile, line=__LINE__) + end if + else + use_c13_streams = .false. + call shr_assert( .not. use_c13_streams , & + msg="ERROR: use_c13_timeseries is false, but use_c13_streams is TRUE", file=sourcefile, line=__LINE__) + call shr_assert( len_trim(atm_c13_filename) == 0 , & + msg="ERROR: use_c13_timeseries is false but atm_c13_filename is NOT blank", file=sourcefile, line=__LINE__) + call shr_assert( len_trim(stream_fldfilename_atm_c13) == 0 , & + msg="ERROR: use_c13_timeseries is false but stream_fldfilename_atm_c13 is NOT blank", file=sourcefile, line=__LINE__) + end if + if ( use_c14_bombspike )then + if ( len_trim(stream_fldfilename_atm_c14) /= 0 ) then + use_c14_streams = .true. + else + use_c14_streams = .false. + call shr_assert( len_trim(atm_c14_filename) /= 0 , & + msg="ERROR: use_c14_bombspike is true but atm_c14_filename is blank", file=sourcefile, line=__LINE__) + call shr_assert( .not. use_c14_streams , & + msg="ERROR: stream_fldfilename_atm_c14 is blank but use_c14_streams is not TRUE", & + file=sourcefile, line=__LINE__) + end if + else + use_c14_streams = .false. + call shr_assert( .not. use_c14_streams , & + msg="ERROR: use_c14_bombspike is false, but use_c14_streams is TRUE", file=sourcefile, line=__LINE__) + call shr_assert( len_trim(atm_c14_filename) == 0, & + msg="ERROR: use_c14_bombspike is false but atm_c14_filename is NOT blank", file=sourcefile, line=__LINE__) + call shr_assert( len_trim(stream_fldfilename_atm_c14) == 0 , & + msg="ERROR: use_c14_bombspike is false but stream_fldfilename_atm_c14 is NOT blank", & + file=sourcefile, line=__LINE__) + end if + + end subroutine CIsoSetControl + + !----------------------------------------------------------------------- + subroutine CIsoLogControl() + ! Log namelist and control settings to output to display what behavior will be + ! + if ( use_c13_timeseries )then + if ( use_c13_streams ) then + call shr_assert( len_trim(stream_fldfilename_atm_c13) /= 0 , & + msg="use_c13_streams is TRUE but stream_fldfilename is blank", file=sourcefile, line=__LINE__) + write(iulog,*) 'C13 atmospheric data will be read in using the streams method from file: '// & + trim(stream_fldfilename_atm_c13) + else if ( len_trim(atm_c13_filename) /= 0 ) then + write(iulog,*) 'C13 atmospheric data will be read in using the CMIP6 time series method from file: '// & + trim(atm_c13_filename) + else + call endrun(msg="use_c13_timeseries is true but use_c13_streams=FALSE and atm_c13_filename is blank", & + file=sourcefile, line=__LINE__) + end if + else + call shr_assert( len_trim(stream_fldfilename_atm_c13) == 0 , & + msg="use_c13_timeseries is FALSE but stream_fldfilename is NOT blank", file=sourcefile, line=__LINE__) + call shr_assert( len_trim(atm_c13_filename) == 0 , & + msg="use_c13_timeseries is FALSE but stream_fldfilename is NOT blank", file=sourcefile, line=__LINE__) + write(iulog,*) 'C13 atmospheric data will be the global constant pre-industrial level' + end if + if ( use_c14_bombspike )then + if ( use_c14_streams ) then + write(iulog,*) 'C14 atmospheric data will be read in using the streams method from file: '// & + trim(stream_fldfilename_atm_c14) + else if ( len_trim(atm_c14_filename) /= 0 ) then + write(iulog,*) 'C14 atmospheric data will be read in using the CMIP6 time series method from file: '// & + trim(atm_c14_filename) + else + call endrun(msg="use_c14_bombspike is true but use_c14_streams=FALSE and atm_c14_filename is blank", & + file=sourcefile, line=__LINE__) + end if + else + call shr_assert( len_trim(stream_fldfilename_atm_c14) == 0 , & + msg="use_c14_bombspike is FALSE but stream_fldfilename is blank", file=sourcefile, line=__LINE__) + call shr_assert( len_trim(atm_c14_filename) == 0 , & + msg="use_c14_bombspike is FALSE but stream_fldfilename is blank", file=sourcefile, line=__LINE__) + write(iulog,*) 'C14 atmospheric data will be global constant pre-industrial level' + end if + + end subroutine CIsoLogControl + + !----------------------------------------------------------------------- + subroutine CIsoCheckNMLInputs() + ! + ! !DESCRIPTION: + ! Check that the namelist inputs are valid + ! + ! + ! !LOCAL VARIABLES: + !----------------------------------------------------------------------- + ! When carbon isotopes are off nothing should be set + if ( .not. use_c13 )then + if ( use_c13_timeseries ) then + call endrun( msg="use_c13 is false but use_c13_timeseries is TRUE " // & + "(use_c13_timeseries can only be TRUE if use_c13 is TRUE)", file=sourcefile, line=__LINE__) + return + end if + end if + if ( .not. use_c14 )then + if ( use_c14_bombspike ) then + call endrun( msg="use_c14 is false but use_c14_bombspike is TRUE " // & + "(use_c14_bombspike can only be TRUE if use_c14 is TRUE)", & + file=sourcefile, line=__LINE__) + return + end if + end if + + ! + ! Check C14 stream namelist inputs + ! + if ( use_c14_bombspike ) then + if ( len_trim(atm_c14_filename) /= 0 .and. len_trim(stream_fldfilename_atm_c14) /= 0 ) then + call endrun(msg="use_c14_bombspike TRUE but both atm_c14_filename AND stream_fldfilename_atm_c14 are set and only one should be", & + file=sourcefile, line=__LINE__) + return + end if + if ( len_trim(atm_c14_filename) == 0 .and. len_trim(stream_fldfilename_atm_c14) == 0 ) then + call endrun(msg="use_c14_bombspike TRUE but neither atm_c14_filename nor stream_fldfilename_atm_c14 are set and one or the other needs to be", & + file=sourcefile, line=__LINE__) + return + end if + else + if ( len_trim(atm_c14_filename) /= 0 .or. len_trim(stream_fldfilename_atm_c14) /= 0 ) then + call endrun(msg="use_c14_bombspike false but either atm_c14_filename or stream_fldfilename_atm_c14 is set and neither should be", & + file=sourcefile, line=__LINE__) + return + end if + end if + ! + ! Check C13 stream namelist inputs + ! + if ( use_c13_timeseries ) then + if ( len_trim(atm_c13_filename) /= 0 .and. len_trim(stream_fldfilename_atm_c13) /= 0 ) then + call endrun(msg="use_c13_timeseries TRUE but both atm_c13_filename AND stream_fldfilename_atm_c13 are set and only one should be", & + file=sourcefile, line=__LINE__) + return + end if + if ( len_trim(atm_c13_filename) == 0 .and. len_trim(stream_fldfilename_atm_c13) == 0 ) then + call endrun(msg="use_c13_timeseries TRUE but neither atm_c13_filename nor stream_fldfilename_atm_c13 are set and one or the other needs to be", & + file=sourcefile, line=__LINE__) + return + end if + else + if ( len_trim(atm_c13_filename) /= 0 .or. len_trim(stream_fldfilename_atm_c13) /= 0 ) then + call endrun(msg="use_c13_timeseries is false but either atm_c13_filename or stream_fldfilename_atm_c13 are set and neither should be", & + file=sourcefile, line=__LINE__) + return + end if + end if + + end subroutine CIsoCheckNMLInputs + + !----------------------------------------------------------------------- + subroutine CIsoSetNMLInputs( stream_fldfilename_atm_c13_in, stream_fldfilename_atm_c14_in, & + use_c13_streams_in, use_c14_streams_in ) + ! + ! !DESCRIPTION: + ! Set the namelist inputs for unit testing + ! + ! Arguments: + character(len=*), intent(in), optional :: stream_fldfilename_atm_c13_in + character(len=*), intent(in), optional :: stream_fldfilename_atm_c14_in + logical, intent(in), optional :: use_c13_streams_in + logical, intent(in), optional :: use_c14_streams_in + ! + ! !LOCAL VARIABLES: + !----------------------------------------------------------------------- + if ( present(stream_fldfilename_atm_c13_in) ) then + stream_fldfilename_atm_c13 = stream_fldfilename_atm_c13_in + end if + if ( present(stream_fldfilename_atm_c14_in) ) then + stream_fldfilename_atm_c14 = stream_fldfilename_atm_c14_in + end if + if ( present(use_c13_streams_in) ) then + use_c13_streams = use_c13_streams_in + end if + if ( present(use_c14_streams_in) ) then + use_c14_streams = use_c14_streams_in + end if + + end subroutine CIsoSetNMLInputs + + + !----------------------------------------------------------------------- + subroutine C14BombSpike( bounds ) ! ! !DESCRIPTION: ! for transient simulation, read in an atmospheric timeseries file to impose bomb spike ! + use GridcellType , only : grc ! !ARGUMENTS: implicit none - real(r8), intent(out) :: rc14_atm(nsectors_c14) ! Ratio of C14 to C12 + type(bounds_type), intent(in) :: bounds ! ! !LOCAL VARIABLES: integer :: yr, mon, day, tod ! year, month, day, time-of-day real(r8) :: dateyear ! Date converted to year real(r8) :: delc14o2_atm(nsectors_c14) ! C14 delta units - integer :: fp, p, nt ! Indices + real(r8) :: rc14_atm(nsectors_c14) ! C14 ratio C14 C12units + integer :: fp, p, nt, g ! Indices integer :: ind_below ! Time index below current time integer :: ntim_atm_ts ! Number of times on file real(r8) :: twt_1, twt_2 ! weighting fractions for interpolating integer :: l ! Loop index of sectors !----------------------------------------------------------------------- - ! get current date - call get_curr_date(yr, mon, day, tod) - dateyear = real(yr) + get_curr_yearfrac() - - ! find points in atm timeseries to interpolate between - ntim_atm_ts = size(atm_c14file_time) - ind_below = 0 - do nt = 1, ntim_atm_ts - if ((dateyear - time_axis_offset) >= atm_c14file_time(nt) ) then - ind_below = ind_below+1 - endif - end do + ! + ! If the bombspike timeseries file is being used, read the file in + ! + if ( use_c14_bombspike )then + + if ( use_c14_streams )then + call C14Streams( bounds ) + RETURN + end if + ! get current date + call get_curr_date(yr, mon, day, tod) + dateyear = real(yr) + get_curr_yearfrac() + + ! find points in atm timeseries to interpolate between + ntim_atm_ts = size(atm_c14file_time) + ind_below = 0 + do nt = 1, ntim_atm_ts + if ((dateyear - time_axis_offset) >= atm_c14file_time(nt) ) then + ind_below = ind_below+1 + endif + end do + + ! loop over lat bands to pass all three to photosynthesis + do l = 1,nsectors_c14 + ! interpolate between nearest two points in atm c14 timeseries + if (ind_below .eq. 0 ) then + delc14o2_atm(l) = atm_delta_c14(l,1) + elseif (ind_below .eq. ntim_atm_ts ) then + delc14o2_atm(l) = atm_delta_c14(l,ntim_atm_ts) + else + twt_2 = min(1._r8, max(0._r8,((dateyear - time_axis_offset)-atm_c14file_time(ind_below)) & + / (atm_c14file_time(ind_below+1)-atm_c14file_time(ind_below)))) + twt_1 = 1._r8 - twt_2 + delc14o2_atm(l) = atm_delta_c14(l,ind_below) * twt_1 + atm_delta_c14(l,ind_below+1) * twt_2 + endif + + ! change delta units to ratio + rc14_atm(l) = (delc14o2_atm(l) * 1.e-3_r8 + 1._r8) * c14ratio + end do + ! + ! When not using a time series file -- use the constant preindustrial value + ! + else + rc14_atm(:) = c14ratio + delc14o2_atm(:) = (rc14_atm(1)/c14ratio -1.0_r8)*1000.0_r8 + endif + ! + ! Now map to the gridcell from the sectors + ! - ! loop over lat bands to pass all three to photosynthesis - do l = 1,nsectors_c14 - ! interpolate between nearest two points in atm c14 timeseries - if (ind_below .eq. 0 ) then - delc14o2_atm(l) = atm_delta_c14(l,1) - elseif (ind_below .eq. ntim_atm_ts ) then - delc14o2_atm(l) = atm_delta_c14(l,ntim_atm_ts) + do g = bounds%begg, bounds%endg + ! determine latitute sector for radiocarbon bomb spike inputs + if ( grc%latdeg(g) >= 30._r8 ) then + l = 1 + else if ( grc%latdeg(g) >= -30._r8 ) then + l = 2 else - twt_2 = min(1._r8, max(0._r8,((dateyear - time_axis_offset)-atm_c14file_time(ind_below)) & - / (atm_c14file_time(ind_below+1)-atm_c14file_time(ind_below)))) - twt_1 = 1._r8 - twt_2 - delc14o2_atm(l) = atm_delta_c14(l,ind_below) * twt_1 + atm_delta_c14(l,ind_below+1) * twt_2 + l = 3 endif - - ! change delta units to ratio - rc14_atm(l) = (delc14o2_atm(l) * 1.e-3_r8 + 1._r8) * c14ratio + atm_delta_c14_grc(g) = delc14o2_atm(l) + rc14_atm_grc(g) = rc14_atm(l) end do - + end subroutine C14BombSpike !----------------------------------------------------------------------- - subroutine C14_init_BombSpike() + subroutine C14Streams( bounds ) + ! Description: + ! + ! Use the streams method to read in atmospheric C14 bomb spike data + ! + ! !ARGUMENTS: + type(bounds_type), intent(in) :: bounds + ! + ! !LOCAL VARIABLES: + !----------------------------------------------------------------------- + integer :: g ! Indices + + call atm_c14_stream%Advance( ) + call atm_c14_stream%Interp( bounds) + + do g = bounds%begg, bounds%endg + atm_delta_c14_grc(g) = atm_c14_stream%atm_delta_c14(g) + rc14_atm_grc(g) = (atm_delta_c14_grc(g) * 1.e-3_r8 + 1._r8) * c14ratio + end do + + end subroutine C14Streams + + !----------------------------------------------------------------------- + subroutine C14_init_BombSpike( bounds ) ! ! !DESCRIPTION: - ! read netcdf file containing a timeseries of atmospheric delta C14 values; save in module-level array + ! read netcdf file containing a timeseries of atmospheric delta C14 values; save in module-level array ! ! !USES: use ncdio_pio , only : ncd_pio_openfile, ncd_pio_closefile, file_desc_t, ncd_inqdlen, ncd_io use fileutils , only : getfil + use shr_infnan_mod, only : nan => shr_infnan_nan, assignment(=) + implicit none + ! Arguments: + type(bounds_type), intent(in) :: bounds ! ! !LOCAL VARIABLES: - implicit none character(len=256) :: locfn ! local file name type(file_desc_t) :: ncid ! netcdf id integer :: dimid,varid ! input netCDF id's @@ -122,116 +475,239 @@ subroutine C14_init_BombSpike() character(len=*), parameter :: vname = 'Delta14co2_in_air' ! Variable name on file !----------------------------------------------------------------------- - call getfil(atm_c14_filename, locfn, 0) - - if ( masterproc ) then - write(iulog, *) 'C14_init_BombSpike: preparing to open file:' - write(iulog, *) trim(locfn) - endif - - call ncd_pio_openfile (ncid, trim(locfn), 0) + ! Allocate the gridcell arrays + ! TODO: This should be below within the use_c14_bombspike if block + allocate(atm_delta_c14_grc(bounds%begg:bounds%endg)) + allocate(rc14_atm_grc(bounds%begg:bounds%endg)) + atm_delta_c14_grc(:) = nan + rc14_atm_grc(:) = nan + ! + ! If the bombspike timeseries file is being used, read the file in + ! + if ( use_c14_bombspike )then + + if ( use_c14_streams )then + write(iulog,*) 'Read in atmospheric C14 data from streams' + call C14StreamsInit( bounds ) + RETURN + end if + if ( .not. use_c14_streams .and. len_trim(atm_c14_filename) == 0 )then + write(iulog,*) 'Use constant preindustrial atmospheric C14 data' + RETURN + end if + + if ( masterproc ) then + write(iulog, *) 'C14_init_BombSpike: preparing to open file:' + write(iulog, *) trim(locfn) + endif - call ncd_inqdlen(ncid,dimid,ntim,'time') - call ncd_inqdlen(ncid,dimid,nsec,'sector') - if ( nsec /= nsectors_c14 )then - call endrun(msg="ERROR: number of sectors on file not what's expected"//errMsg(sourcefile, __LINE__)) + call getfil(atm_c14_filename, locfn, 0) + + call ncd_pio_openfile (ncid, trim(locfn), 0) + + call ncd_inqdlen(ncid,dimid,ntim,'time') + call ncd_inqdlen(ncid,dimid,nsec,'sector') + if ( nsec /= nsectors_c14 )then + call endrun(msg="ERROR: number of sectors on file not what's expected"//errMsg(sourcefile, __LINE__)) + end if + + !! allocate arrays based on size of netcdf timeseries + allocate(atm_c14file_time(ntim)) + allocate(atm_delta_c14(nsectors_c14,ntim)) + atm_delta_c14(:,:) = 0.0_r8 + call ncd_io(ncid=ncid, varname='time', flag='read', data=atm_c14file_time, & + readvar=readvar) + if ( .not. readvar ) then + call endrun(msg="ERROR: time not on file"//errMsg(sourcefile, __LINE__)) + end if + + call ncd_io(ncid=ncid, varname=vname, flag='read', data=atm_delta_c14, & + readvar=readvar) + if ( .not. readvar ) then + call endrun(msg="ERROR: '//vname//' not on file"//errMsg(sourcefile, __LINE__)) + end if + ! Check units + call check_units( ncid, vname, "Modern" ) + call ncd_pio_closefile(ncid) + + ! check to make sure that time dimension is well behaved + do t = 2, ntim + if ( atm_c14file_time(t) - atm_c14file_time(t-1) <= 0._r8 ) then + write(iulog, *) 'C14_init_BombSpike: error. time axis must be monotonically increasing' + call endrun(msg=errMsg(sourcefile, __LINE__)) + endif + end do end if - !! allocate arrays based on size of netcdf timeseries - allocate(atm_c14file_time(ntim)) - allocate(atm_delta_c14(nsectors_c14,ntim)) - atm_delta_c14(:,:) = 0.0_r8 - - call ncd_io(ncid=ncid, varname='time', flag='read', data=atm_c14file_time, & - readvar=readvar) - if ( .not. readvar ) then - call endrun(msg="ERROR: time not on file"//errMsg(sourcefile, __LINE__)) - end if + end subroutine C14_init_BombSpike - call ncd_io(ncid=ncid, varname=vname, flag='read', data=atm_delta_c14, & - readvar=readvar) - if ( .not. readvar ) then - call endrun(msg="ERROR: '//vname//' not on file"//errMsg(sourcefile, __LINE__)) - end if - ! Check units - call check_units( ncid, vname, "Modern" ) - call ncd_pio_closefile(ncid) - - ! check to make sure that time dimension is well behaved - do t = 2, ntim - if ( atm_c14file_time(t) - atm_c14file_time(t-1) <= 0._r8 ) then - write(iulog, *) 'C14_init_BombSpike: error. time axis must be monotonically increasing' - call endrun(msg=errMsg(sourcefile, __LINE__)) - endif - end do - end subroutine C14_init_BombSpike + !----------------------------------------------------------------------- + subroutine C14StreamsInit( bounds ) + ! Description: + ! + ! Initialize the streams method to read in atmospheric C14 bomb spike data + ! + ! !ARGUMENTS: + type(bounds_type), intent(in) :: bounds + ! + ! !LOCAL VARIABLES: + !----------------------------------------------------------------------- + if ( masterproc ) then + write(iulog, *) 'C14StreamsInit: Initializing C14 streams with file:' + write(iulog, *) trim(stream_fldfilename_atm_c14) + end if + ! Streams method + call atm_c14_stream%Init( bounds, & + fldfilename=stream_fldfilename_atm_c14, & + meshfile= 'none', & + mapalgo=stream_mapalgo_atm_c14, & + tintalgo=stream_tintalgo_atm_c14, & + taxmode=stream_taxmode_atm_c14, & + year_first=stream_year_first_atm_c14, & + year_last=stream_year_last_atm_c14, & + model_year_align=stream_model_year_align_atm_c14 ) + call atm_c14_stream%Advance( ) + call atm_c14_stream%Interp( bounds ) + + end subroutine C14StreamsInit !----------------------------------------------------------------------- - subroutine C13TimeSeries( rc13_atm ) + subroutine C13TimeSeries( bounds, atm2lnd_inst ) ! ! !DESCRIPTION: ! for transient pulse simulation, impose a time-varying atm boundary condition ! + use GridcellType , only : grc + use clm_varcon , only : preind_atm_del13c + use atm2lndType, only : atm2lnd_Type ! !ARGUMENTS: implicit none - real(r8), intent(out) :: rc13_atm ! Ratio of C13 to C12 + type(bounds_type), intent(in) :: bounds + type(atm2lnd_Type), intent(in) :: atm2lnd_inst ! ! !LOCAL VARIABLES: + real(r8) :: rc13_atm ! Ratio of C13 to C12 integer :: yr, mon, day, tod ! year, month, day, time-of-day real(r8) :: dateyear ! date translated to year real(r8) :: delc13o2_atm ! Delta C13 - integer :: fp, p, nt ! Indices + integer :: fp, p, nt, g ! Indices integer :: ind_below ! Index of time in file before current time integer :: ntim_atm_ts ! Number of times on file real(r8) :: twt_1, twt_2 ! weighting fractions for interpolating !----------------------------------------------------------------------- - ! get current date - call get_curr_date(yr, mon, day, tod) - dateyear = real(yr) + get_curr_yearfrac() - - ! find points in atm timeseries to interpolate between - ntim_atm_ts = size(atm_c13file_time) - ind_below = 0 - do nt = 1, ntim_atm_ts - if ((dateyear - time_axis_offset) >= atm_c13file_time(nt) ) then - ind_below = ind_below+1 - endif - end do + ! + ! If the timeseries file is being used, read the file in + ! + if ( use_c13_timeseries )then + + if ( use_c13_streams )then + call C13Streams( bounds ) + RETURN + end if + if ( .not. use_c13_streams .and. len_trim(atm_c13_filename) == 0 )then + write(iulog,*) 'Use constant preindustrial atmospheric C13 data' + RETURN + end if + ! get current date + call get_curr_date(yr, mon, day, tod) + dateyear = real(yr) + get_curr_yearfrac() + + ! find points in atm timeseries to interpolate between + ntim_atm_ts = size(atm_c13file_time) + ind_below = 0 + do nt = 1, ntim_atm_ts + if ((dateyear - time_axis_offset) >= atm_c13file_time(nt) ) then + ind_below = ind_below+1 + endif + end do + + ! interpolate between nearest two points in atm c13 timeseries + ! cdknotes. for now and for simplicity, just use the northern hemisphere values (sector 1) + if (ind_below .eq. 0 ) then + delc13o2_atm = atm_delta_c13(1) + elseif (ind_below .eq. ntim_atm_ts ) then + delc13o2_atm = atm_delta_c13(ntim_atm_ts) + else + twt_2 = min(1._r8, max(0._r8,((dateyear - time_axis_offset)-atm_c13file_time(ind_below)) & + / (atm_c13file_time(ind_below+1)-atm_c13file_time(ind_below)))) + twt_1 = 1._r8 - twt_2 + delc13o2_atm = atm_delta_c13(ind_below) * twt_1 + atm_delta_c13(ind_below+1) * twt_2 + endif - ! interpolate between nearest two points in atm c13 timeseries - ! cdknotes. for now and for simplicity, just use the northern hemisphere values (sector 1) - if (ind_below .eq. 0 ) then - delc13o2_atm = atm_delta_c13(1) - elseif (ind_below .eq. ntim_atm_ts ) then - delc13o2_atm = atm_delta_c13(ntim_atm_ts) + ! + ! When not using a time series file -- use the constant value + ! else - twt_2 = min(1._r8, max(0._r8,((dateyear - time_axis_offset)-atm_c13file_time(ind_below)) & - / (atm_c13file_time(ind_below+1)-atm_c13file_time(ind_below)))) - twt_1 = 1._r8 - twt_2 - delc13o2_atm = atm_delta_c13(ind_below) * twt_1 + atm_delta_c13(ind_below+1) * twt_2 - endif + rc13_atm = c13ratio + delc13o2_atm = (rc13_atm/SHR_CONST_PDB - 1.0_r8)*1000.0_r8 + end if ! change delta units to ratio, put on patch loop rc13_atm = (delc13o2_atm * 1.e-3_r8 + 1._r8) * SHR_CONST_PDB + ! + ! Copy to the gridcell arrays + ! + do g = bounds%begg, bounds%endg + + associate( & + forc_pco2 => atm2lnd_inst%forc_pco2_grc , & ! Input: [real(r8) (:) ] partial pressure co2 (Pa) + forc_pc13o2 => atm2lnd_inst%forc_pc13o2_grc & ! Input: [real(r8) (:) ] partial pressure c13o2 (Pa) + ) + rc13_atm_grc(g) = rc13_atm + atm_delta_c13_grc(g) = delc13o2_atm + + ! Currently when C13 is fixed, it's dependent on CO2 levels and changes with pressure + ! NOTE: This duplicates code in lnd_import_export.F90 + if ( .not. use_c13_timeseries )then + rc13_atm_grc(g) = forc_pc13o2(g)/(forc_pco2(g) - forc_pc13o2(g)) + atm_delta_c13_grc(g) = (rc13_atm_grc(g) / SHR_CONST_PDB - 1.0_r8)*1000.0_r8 + end if + end associate + end do + end subroutine C13TimeSeries !----------------------------------------------------------------------- - subroutine C13_init_TimeSeries() + subroutine C13Streams( bounds ) + ! Description: + ! + ! Use the streams method to read in atmospheric C13 data + ! + ! !ARGUMENTS: + type(bounds_type), intent(in) :: bounds + ! + ! !LOCAL VARIABLES: + integer :: g ! Indices + + call atm_c13_stream%Interp( bounds) + + do g = bounds%begg, bounds%endg + atm_delta_c13_grc(g) = atm_c13_stream%atm_delta_c13(g) + rc13_atm_grc(g) = (atm_delta_c13_grc(g) * 1.e-3_r8 + 1._r8) * SHR_CONST_PDB + end do + + end subroutine C13Streams + + !----------------------------------------------------------------------- + subroutine C13_init_TimeSeries( bounds ) ! ! !DESCRIPTION: - ! read netcdf file containing a timeseries of atmospheric delta C13 values; save in module-level array + ! read netcdf file containing a timeseries of atmospheric delta C13 values; save in module-level array ! ! !USES: use ncdio_pio , only : ncd_pio_openfile, ncd_pio_closefile, file_desc_t, ncd_inqdlen, ncd_io use fileutils , only : getfil + use shr_infnan_mod, only : nan => shr_infnan_nan, assignment(=) + implicit none ! + ! Arguments: + type(bounds_type), intent(in) :: bounds ! !LOCAL VARIABLES: - implicit none character(len=256) :: locfn ! local file name type(file_desc_t) :: ncid ! netcdf id integer :: dimid,varid ! input netCDF id's @@ -241,47 +717,96 @@ subroutine C13_init_TimeSeries() character(len=*), parameter :: vname = 'delta13co2_in_air' ! Variable name on file !----------------------------------------------------------------------- - call getfil(atm_c13_filename, locfn, 0) + ! TODO: This should be below within the use_c13_timeseries if block + ! Allocate the gridcell arrays + allocate(atm_delta_c13_grc(bounds%begg:bounds%endg) ) + allocate(rc13_atm_grc(bounds%begg:bounds%endg) ) + atm_delta_c13_grc(:) = nan + rc13_atm_grc(:) = nan + ! + ! If the timeseries file is being used, read the file in + ! + if ( use_c13_timeseries )then - if ( masterproc ) then - write(iulog, *) 'C13_init_TimeSeries: preparing to open file:' - write(iulog, *) trim(locfn) - endif + if ( use_c13_streams )then + call C13StreamsInit( bounds ) + RETURN + end if - call ncd_pio_openfile (ncid, trim(locfn), 0) + call getfil(atm_c13_filename, locfn, 0) - call ncd_inqdlen(ncid,dimid,ntim,'time') + if ( masterproc ) then + write(iulog, *) 'C13_init_TimeSeries: preparing to open file:' + write(iulog, *) trim(locfn) + endif - !! allocate arrays based on size of netcdf timeseries - allocate(atm_c13file_time(ntim)) - allocate(atm_delta_c13(ntim)) + call ncd_pio_openfile (ncid, trim(locfn), 0) - call ncd_io(ncid=ncid, varname='time', flag='read', data=atm_c13file_time, & - readvar=readvar) - if ( .not. readvar ) then - call endrun(msg="ERROR: time not on file"//errMsg(sourcefile, __LINE__)) - end if + call ncd_inqdlen(ncid,dimid,ntim,'time') - call ncd_io(ncid=ncid, varname=vname, flag='read', data=atm_delta_c13, & - readvar=readvar) - if ( .not. readvar ) then - call endrun(msg="ERROR: '//vname//' not on file"//errMsg(sourcefile, __LINE__)) - end if + !! allocate arrays based on size of netcdf timeseries + allocate(atm_c13file_time(ntim)) + allocate(atm_delta_c13(ntim)) - ! Check units - call check_units( ncid, vname, "VPDB" ) - call ncd_pio_closefile(ncid) - ! check to make sure that time dimension is well behaved - do t = 2, ntim - if ( atm_c13file_time(t) - atm_c13file_time(t-1) <= 0._r8 ) then - write(iulog, *) 'C13_init_TimeSeries: error. time axis must be monotonically increasing' - call endrun(msg=errMsg(sourcefile, __LINE__)) - endif - end do + call ncd_io(ncid=ncid, varname='time', flag='read', data=atm_c13file_time, & + readvar=readvar) + if ( .not. readvar ) then + call endrun(msg="ERROR: time not on file"//errMsg(sourcefile, __LINE__)) + end if + + call ncd_io(ncid=ncid, varname=vname, flag='read', data=atm_delta_c13, & + readvar=readvar) + if ( .not. readvar ) then + call endrun(msg="ERROR: '//vname//' not on file"//errMsg(sourcefile, __LINE__)) + end if + + ! Check units + call check_units( ncid, vname, "VPDB" ) + call ncd_pio_closefile(ncid) + + ! check to make sure that time dimension is well behaved + do t = 2, ntim + if ( atm_c13file_time(t) - atm_c13file_time(t-1) <= 0._r8 ) then + write(iulog, *) 'C13_init_TimeSeries: error. time axis must be monotonically increasing' + call endrun(msg=errMsg(sourcefile, __LINE__)) + endif + end do + end if end subroutine C13_init_TimeSeries + !----------------------------------------------------------------------- + subroutine C13StreamsInit( bounds ) + ! Description: + ! + ! Initialize the streams method to read in atmospheric C13 data + ! + ! !ARGUMENTS: + type(bounds_type), intent(in) :: bounds + ! + ! !LOCAL VARIABLES: + !----------------------------------------------------------------------- + + if ( masterproc ) then + write(iulog, *) 'C13StreamsInit: Initializing C13 streams with file:' + write(iulog, *) trim(stream_fldfilename_atm_c13) + end if + ! Streams method + call atm_c13_stream%Init( bounds, & + fldfilename=stream_fldfilename_atm_c13, & + meshfile= 'none', & + mapalgo=stream_mapalgo_atm_c13, & + tintalgo=stream_tintalgo_atm_c13, & + taxmode=stream_taxmode_atm_c13, & + year_first=stream_year_first_atm_c13, & + year_last=stream_year_last_atm_c13, & + model_year_align=stream_model_year_align_atm_c13 ) + call atm_c13_stream%Advance( ) + call atm_c13_stream%Interp( bounds ) + + end subroutine C13StreamsInit + !----------------------------------------------------------------------- subroutine check_units( ncid, vname, relativeto ) ! diff --git a/src/biogeochem/test/CIsoAtmTimeSeries_test/CMakeLists.txt b/src/biogeochem/test/CIsoAtmTimeSeries_test/CMakeLists.txt new file mode 100644 index 0000000000..9a9885508c --- /dev/null +++ b/src/biogeochem/test/CIsoAtmTimeSeries_test/CMakeLists.txt @@ -0,0 +1,11 @@ +set(pfunit_sources + test_CIsoAtmTimeSeries.pf) + +add_pfunit_ctest(CIsoAtmTimeSeries + TEST_SOURCES "${pfunit_sources}" + LINK_LIBRARIES clm csm_share) + +# I don't think we need esmf here +# LINK_LIBRARIES clm csm_share esmf +# EXTRA_FINALIZE unittest_finalize_esmf +# EXTRA_USE unittestInitializeAndFinalize) diff --git a/src/biogeochem/test/CIsoAtmTimeSeries_test/test_CIsoAtmTimeSeries.pf b/src/biogeochem/test/CIsoAtmTimeSeries_test/test_CIsoAtmTimeSeries.pf new file mode 100644 index 0000000000..01866b7617 --- /dev/null +++ b/src/biogeochem/test/CIsoAtmTimeSeries_test/test_CIsoAtmTimeSeries.pf @@ -0,0 +1,277 @@ +module test_CIsoAtmTimeSeries + + ! Tests of CNCIsoAtmTimeSeriesReadMod + + use funit + use CIsoAtmTimeSeriesMod + use shr_kind_mod , only : r8 => shr_kind_r8 + use clm_varctl, only : use_c13, use_c14 + + implicit none + + @TestCase + type, extends(TestCase) :: TestCIsoAtmTimeSeries + contains + procedure :: setUp + procedure :: tearDown + end type TestCIsoAtmTimeSeries + + character(len=200) :: expected_msg + +contains + + subroutine setUp(this) + class(TestCIsoAtmTimeSeries), intent(inout) :: this + + use_c13 = .true. + use_c14 = .true. + use_c13_timeseries = .true. + use_c14_bombspike = .true. + ! Set these filenames to /dev/null as it's a file guaranteed to exist on Linux systems + atm_c13_filename = '/dev/null' + atm_c14_filename = '/dev/null' + + end subroutine setUp + + subroutine tearDown(this) + class(TestCIsoAtmTimeSeries), intent(inout) :: this + + end subroutine tearDown + + @Test + subroutine check_both_timeseries_on(this) + ! Check that it works when both timeseries are on without using streams + class(TestCIsoAtmTimeSeries), intent(inout) :: this + + call CIsoCheckNMLInputs() + call CIsoSetControl() + call CIsoLogControl() + + end subroutine check_both_timeseries_on + + @Test + subroutine check_both_timeseries_streams_on(this) + ! Check that it works when both timeseries are on and using streams + class(TestCIsoAtmTimeSeries), intent(inout) :: this + + atm_c13_filename = '' + atm_c14_filename = '' + call CIsoSetNMLInputs( stream_fldfilename_atm_c13_in = '/dev/null', & + stream_fldfilename_atm_c14_in = '/dev/null' ) + call CIsoCheckNMLInputs() + call CIsoSetControl() + call CIsoLogControl() + + end subroutine check_both_timeseries_streams_on + + @Test + subroutine check_both_timeseries_off(this) + ! Check that it works when both timeseries are off + class(TestCIsoAtmTimeSeries), intent(inout) :: this + + atm_c13_filename = '' + atm_c14_filename = '' + use_c13_timeseries = .false. + use_c14_bombspike = .false. + call CIsoSetNMLInputs( stream_fldfilename_atm_c13_in = ' ', & + stream_fldfilename_atm_c14_in = ' ' ) + call CIsoCheckNMLInputs() + call CIsoSetControl() + call CIsoLogControl() + + end subroutine check_both_timeseries_off + + @Test + subroutine check_ciso_off(this) + ! Check that it works when both carbon isotopes are off + class(TestCIsoAtmTimeSeries), intent(inout) :: this + + atm_c13_filename = '' + atm_c14_filename = '' + use_c13_timeseries = .false. + use_c14_bombspike = .false. + use_c13 = .false. + use_c14 = .false. + call CIsoSetNMLInputs( stream_fldfilename_atm_c13_in = ' ', & + stream_fldfilename_atm_c14_in = ' ' ) + call CIsoCheckNMLInputs() + call CIsoSetControl() + call CIsoLogControl() + + end subroutine check_ciso_off + + @Test + subroutine abort_if_both_c13_plain_and_stream_timeseries_files_set(this) + ! Check that it aborts if both the plain and stream C13 timeseries files are set + class(TestCIsoAtmTimeSeries), intent(inout) :: this + + @assertTrue(use_c13) + @assertTrue(use_c13_timeseries) + atm_c13_filename = 'plain_c13_timeseries_file' + @assertTrue(use_c14) + @assertTrue(use_c14_bombspike) + @assertTrue(len_trim(atm_c14_filename) /= 0) + call CIsoSetNMLInputs( stream_fldfilename_atm_c13_in = 'stream_c13_timeseries_file', stream_fldfilename_atm_c14_in = ' ') + call CIsoCheckNMLInputs() + expected_msg = "ABORTED: use_c13_timeseries TRUE but both atm_c13_filename AND stream_fldfilename_atm_c13 are set and only one should be" + @assertExceptionRaised(expected_msg) + + end subroutine abort_if_both_c13_plain_and_stream_timeseries_files_set + + @Test + subroutine abort_if_both_c14_plain_and_stream_timeseries_files_set(this) + ! Check that it aborts if both the plain and stream C14 timeseries files are set + class(TestCIsoAtmTimeSeries), intent(inout) :: this + + @assertTrue(use_c14) + @assertTrue(use_c14_bombspike) + atm_c14_filename = 'plain_c14_timeseries_file' + call CIsoSetNMLInputs( stream_fldfilename_atm_c14_in = 'stream_c14_timeseries_file' ) + call CIsoCheckNMLInputs() + expected_msg = "ABORTED: use_c14_bombspike TRUE but both atm_c14_filename AND stream_fldfilename_atm_c14 are set and only one should be" + @assertExceptionRaised(expected_msg) + + end subroutine abort_if_both_c14_plain_and_stream_timeseries_files_set + + @Test + subroutine abort_if_both_c13_plain_and_stream_timeseries_files_blank(this) + ! Check that it aborts if both the plain and stream C13 timeseries files are blank + class(TestCIsoAtmTimeSeries), intent(inout) :: this + + @assertTrue(use_c13) + @assertTrue(use_c13_timeseries) + atm_c13_filename = ' ' + @assertTrue(use_c14) + @assertTrue(use_c14_bombspike) + @assertTrue(len_trim(atm_c14_filename) /= 0) + call CIsoSetNMLInputs( stream_fldfilename_atm_c13_in = ' ', stream_fldfilename_atm_c14_in = ' ') + call CIsoCheckNMLInputs() + expected_msg = "ABORTED: use_c13_timeseries TRUE but neither atm_c13_filename nor stream_fldfilename_atm_c13 are set and one or the other needs to be" + @assertExceptionRaised(expected_msg) + + end subroutine abort_if_both_c13_plain_and_stream_timeseries_files_blank + + @Test + subroutine abort_if_both_c14_plain_and_stream_timeseries_files_blank(this) + ! Check that it aborts if both the plain and stream C14 timeseries files are blank + class(TestCIsoAtmTimeSeries), intent(inout) :: this + @assertTrue(use_c14) + @assertTrue(use_c14_bombspike) + atm_c14_filename = ' ' + call CIsoSetNMLInputs( stream_fldfilename_atm_c14_in = ' ' ) + call CIsoCheckNMLInputs() + expected_msg = "ABORTED: use_c14_bombspike TRUE but neither atm_c14_filename nor stream_fldfilename_atm_c14 are set and one or the other needs to be" + @assertExceptionRaised(expected_msg) + + end subroutine abort_if_both_c14_plain_and_stream_timeseries_files_blank + + @Test + subroutine abort_if_c13_timeseries_off_and_plain_timeseries_file_set(this) + ! Check that it aborts if c13 timeseries off, but the plain timeseries file is set + class(TestCIsoAtmTimeSeries), intent(inout) :: this + + @assertTrue(use_c13) + use_c13_timeseries = .false. + @assertFalse(use_c13_timeseries) + atm_c13_filename = 'plain_c13_timeseries_file' + call CIsoSetNMLInputs( stream_fldfilename_atm_c13_in = ' ' ) + call CIsoCheckNMLInputs() + expected_msg = "ABORTED: use_c13_timeseries is false but either atm_c13_filename or stream_fldfilename_atm_c13 are set and neither should be" + @assertExceptionRaised(expected_msg) + + end subroutine abort_if_c13_timeseries_off_and_plain_timeseries_file_set + + @Test + subroutine abort_if_c13_timeseries_off_and_stream_timeseries_file_set(this) + ! Check that it aborts if c13 timeseries off, but the streamplain timeseries file is set + class(TestCIsoAtmTimeSeries), intent(inout) :: this + + @assertTrue(use_c13) + use_c13_timeseries = .false. + @assertFalse(use_c13_timeseries) + atm_c13_filename = ' ' + call CIsoSetNMLInputs( stream_fldfilename_atm_c13_in = 'stream_c13_filename ' ) + call CIsoCheckNMLInputs() + expected_msg = "ABORTED: use_c13_timeseries is false but either atm_c13_filename or stream_fldfilename_atm_c13 are set and neither should be" + @assertExceptionRaised(expected_msg) + + end subroutine abort_if_c13_timeseries_off_and_stream_timeseries_file_set + + @Test + subroutine abort_if_c14_bombspike_off_and_plain_timeseries_file_set(this) + ! Check that it aborts if c14 timeseries off, but the pain timeseries file is set + class(TestCIsoAtmTimeSeries), intent(inout) :: this + integer :: i + + ! This should be true whether C14 is on or off + do i = 1, 2 + if ( i == 1 ) then + @assertTrue(use_c14) + else + use_c14 = .false. + end if + use_c14_bombspike = .false. + atm_c14_filename = 'plain_c14_timeseries_file' + call CIsoSetNMLInputs( stream_fldfilename_atm_c14_in = ' ' ) + call CIsoCheckNMLInputs() + expected_msg = "ABORTED: use_c14_bombspike false but either atm_c14_filename or stream_fldfilename_atm_c14 is set and neither should be" + @assertExceptionRaised(expected_msg) + end do + + end subroutine abort_if_c14_bombspike_off_and_plain_timeseries_file_set + + @Test + subroutine abort_if_c14_bombspike_off_and_stream_timeseries_file_set(this) + ! Check that it aborts if c14 timeseries off, but the stream timeseries file is set + class(TestCIsoAtmTimeSeries), intent(inout) :: this + integer :: i + + ! This should be true whether C14 is on or off + do i = 1, 2 + if ( i == 1 ) then + @assertTrue(use_c14) + else + use_c14 = .false. + end if + use_c14_bombspike = .false. + atm_c14_filename = ' ' + call CIsoSetNMLInputs( stream_fldfilename_atm_c14_in = 'stream_c14_filename' ) + call CIsoCheckNMLInputs() + expected_msg = "ABORTED: use_c14_bombspike false but either atm_c14_filename or stream_fldfilename_atm_c14 is set and neither should be" + @assertExceptionRaised(expected_msg) + end do + + end subroutine abort_if_c14_bombspike_off_and_stream_timeseries_file_set + + @Test + subroutine abort_if_c13_off_but_c13_timeseries_on(this) + ! Check that it aborts if c13 is off, but the c13 timeseries is on + class(TestCIsoAtmTimeSeries), intent(inout) :: this + + use_c13 = .false. + use_c13_timeseries = .true. + atm_c13_filename = ' ' + call CIsoSetNMLInputs( stream_fldfilename_atm_c13_in = ' ' ) + call CIsoCheckNMLInputs() + expected_msg = "ABORTED: use_c13 is false but use_c13_timeseries is TRUE (use_c13_timeseries can only be TRUE if use_c13 is TRUE)" + @assertExceptionRaised(expected_msg) + + end subroutine abort_if_c13_off_but_c13_timeseries_on + + @Test + subroutine abort_if_c14_off_but_c14_bombspike_on(this) + ! Check that it aborts if c14 is off, but the c14 bombspike is on + class(TestCIsoAtmTimeSeries), intent(inout) :: this + + use_c14 = .false. + @assertFalse(use_c14) + use_c14_bombspike = .true. + atm_c14_filename = ' ' + call CIsoSetNMLInputs( stream_fldfilename_atm_c14_in = ' ' ) + call CIsoCheckNMLInputs() + expected_msg = "ABORTED: use_c14 is false but use_c14_bombspike is TRUE (use_c14_bombspike can only be TRUE if use_c14 is TRUE)" + @assertExceptionRaised(expected_msg) + + end subroutine abort_if_c14_off_but_c14_bombspike_on + +end module test_CIsoAtmTimeSeries diff --git a/src/biogeochem/test/CMakeLists.txt b/src/biogeochem/test/CMakeLists.txt index 2ebe27c76f..e47e2429b6 100644 --- a/src/biogeochem/test/CMakeLists.txt +++ b/src/biogeochem/test/CMakeLists.txt @@ -1,4 +1,5 @@ add_subdirectory(Species_test) +add_subdirectory(CIsoAtmTimeSeries_test) add_subdirectory(CNVegComputeSeed_test) add_subdirectory(CNPhenology_test) add_subdirectory(Latbaset_test) diff --git a/src/biogeophys/CanopyFluxesMod.F90 b/src/biogeophys/CanopyFluxesMod.F90 index afe665e6d1..3b0d990b21 100644 --- a/src/biogeophys/CanopyFluxesMod.F90 +++ b/src/biogeophys/CanopyFluxesMod.F90 @@ -13,7 +13,7 @@ module CanopyFluxesMod use shr_kind_mod , only : r8 => shr_kind_r8 use shr_log_mod , only : errMsg => shr_log_errMsg use abortutils , only : endrun - use clm_varctl , only : iulog, use_cn, use_lch4, use_c13, use_c14, use_cndv, use_fates, & + use clm_varctl , only : iulog, use_cn, use_lch4, use_c13, use_cndv, use_fates, & use_luna, use_hydrstress, use_biomass_heat_storage, z0param_method use clm_varpar , only : nlevgrnd, nlevsno, nlevcan, mxpft use pftconMod , only : pftcon @@ -229,7 +229,7 @@ subroutine CanopyFluxes(bounds, num_exposedvegp, filter_exposedvegp, use clm_time_manager , only : get_step_size_real, get_prev_date, is_near_local_noon use clm_varcon , only : sb, cpair, hvap, vkc, grav, denice, c_to_b use clm_varcon , only : denh2o, tfrz, tlsai_crit, alpha_aero - use clm_varcon , only : c14ratio, spval + use clm_varcon , only : spval use clm_varcon , only : c_water, c_dry_biomass, c_to_b use clm_varcon , only : nu_param, cd1_param use perf_mod , only : t_startf, t_stopf @@ -354,7 +354,6 @@ subroutine CanopyFluxes(bounds, num_exposedvegp, filter_exposedvegp, real(r8) :: err(bounds%begp:bounds%endp) ! balance error real(r8) :: erre ! balance error real(r8) :: co2(bounds%begp:bounds%endp) ! atmospheric co2 partial pressure (pa) - real(r8) :: c13o2(bounds%begp:bounds%endp) ! atmospheric c13o2 partial pressure (pa) real(r8) :: o2(bounds%begp:bounds%endp) ! atmospheric o2 partial pressure (pa) real(r8) :: svpts(bounds%begp:bounds%endp) ! saturation vapor pressure at t_veg (pa) real(r8) :: eah(bounds%begp:bounds%endp) ! canopy air vapor pressure (pa) @@ -479,7 +478,6 @@ subroutine CanopyFluxes(bounds, num_exposedvegp, filter_exposedvegp, forc_u => atm2lnd_inst%forc_u_grc , & ! Input: [real(r8) (:) ] atmospheric wind speed in east direction (m/s) forc_v => atm2lnd_inst%forc_v_grc , & ! Input: [real(r8) (:) ] atmospheric wind speed in north direction (m/s) forc_pco2 => atm2lnd_inst%forc_pco2_grc , & ! Input: [real(r8) (:) ] partial pressure co2 (Pa) - forc_pc13o2 => atm2lnd_inst%forc_pc13o2_grc , & ! Input: [real(r8) (:) ] partial pressure c13o2 (Pa) forc_po2 => atm2lnd_inst%forc_po2_grc , & ! Input: [real(r8) (:) ] partial pressure o2 (Pa) tc_ref2m => humanindex_inst%tc_ref2m_patch , & ! Output: [real(r8) (:) ] 2 m height surface air temperature (C) @@ -961,10 +959,6 @@ subroutine CanopyFluxes(bounds, num_exposedvegp, filter_exposedvegp, co2(p) = forc_pco2(g) o2(p) = forc_po2(g) - if ( use_c13 ) then - c13o2(p) = forc_pc13o2(g) - end if - ! Initialize flux profile nmozsgn(p) = 0 @@ -1645,7 +1639,7 @@ subroutine CanopyFluxes(bounds, num_exposedvegp, filter_exposedvegp, ! Determine total photosynthesis - call PhotosynthesisTotal(fn, filterp, & + call PhotosynthesisTotal(bounds, fn, filterp, & atm2lnd_inst, canopystate_inst, photosyns_inst) ! Calculate water use efficiency diff --git a/src/biogeophys/CanopyStateType.F90 b/src/biogeophys/CanopyStateType.F90 index 81dc3947b1..66804e1549 100644 --- a/src/biogeophys/CanopyStateType.F90 +++ b/src/biogeophys/CanopyStateType.F90 @@ -296,11 +296,11 @@ subroutine InitHistory(this, bounds) this%vegwp_ln_patch(begp:endp,:) = spval call hist_addfld2d (fname='VEGWPLN', units='mm', type2d='nvegwcs', & avgflag='A', long_name='vegetation water matric potential for sun/sha canopy,xyl,root at local noon', & - ptr_patch=this%vegwp_ln_patch, default='active') + ptr_patch=this%vegwp_ln_patch, default='inactive') this%vegwp_pd_patch(begp:endp,:) = spval call hist_addfld2d (fname='VEGWPPD', units='mm', type2d='nvegwcs', avgflag='A', & long_name='predawn vegetation water matric potential for sun/sha canopy,xyl,root', & - ptr_patch=this%vegwp_pd_patch, default='active') + ptr_patch=this%vegwp_pd_patch, default='inactive') end if end subroutine InitHistory diff --git a/src/biogeophys/PhotosynthesisMod.F90 b/src/biogeophys/PhotosynthesisMod.F90 index b88fb170c7..098144446d 100644 --- a/src/biogeophys/PhotosynthesisMod.F90 +++ b/src/biogeophys/PhotosynthesisMod.F90 @@ -1,4 +1,4 @@ -module PhotosynthesisMod +module PhotosynthesisMod #include "shr_assert.h" @@ -21,7 +21,6 @@ module PhotosynthesisMod use decompMod , only : bounds_type, subgrid_level_patch use QuadraticMod , only : quadratic use pftconMod , only : pftcon - use CIsoAtmTimeseriesMod, only : C14BombSpike, use_c14_bombspike, C13TimeSeries, use_c13_timeseries, nsectors_c14 use atm2lndType , only : atm2lnd_type use CanopyStateType , only : canopystate_type use CNVegnitrogenstateType, only : cnveg_nitrogenstate_type @@ -1257,7 +1256,7 @@ subroutine Photosynthesis ( bounds, fn, filterp, & use clm_varcon , only : rgas, tfrz, spval use GridcellType , only : grc use clm_time_manager , only : get_step_size_real, is_near_local_noon - use clm_varctl , only : cnallocate_carbon_only + use clm_varctl , only : allocate_carbon_only use clm_varctl , only : lnc_opt, reduce_dayl_factor, vcmax_opt use pftconMod , only : nbrdlf_dcd_tmp_shrub @@ -1642,7 +1641,7 @@ subroutine Photosynthesis ( bounds, fn, filterp, & if (.not. use_cn) then vcmax25top = vcmax25top * fnitr(patch%itype(p)) else - if ( CNAllocate_Carbon_only() ) vcmax25top = vcmax25top * fnitr(patch%itype(p)) + if ( Allocate_Carbon_only() ) vcmax25top = vcmax25top * fnitr(patch%itype(p)) end if else if (vcmax_opt == 3) then vcmax25top = ( i_vcad(patch%itype(p)) + s_vcad(patch%itype(p)) * lnc(p) ) * dayl_factor(p) @@ -2063,12 +2062,15 @@ subroutine Photosynthesis ( bounds, fn, filterp, & end subroutine Photosynthesis !------------------------------------------------------------------------------ - subroutine PhotosynthesisTotal (fn, filterp, & + subroutine PhotosynthesisTotal (bounds, fn, filterp, & atm2lnd_inst, canopystate_inst, photosyns_inst) ! ! Determine total photosynthesis ! + use CIsoAtmTimeseriesMod, only : C14BombSpike, C13TimeSeries + use CIsoAtmTimeseriesMod, only : rc13_atm_grc, rc14_atm_grc ! !ARGUMENTS: + type(bounds_type) , intent(in) :: bounds integer , intent(in) :: fn ! size of pft filter integer , intent(in) :: filterp(fn) ! patch filter type(atm2lnd_type) , intent(in) :: atm2lnd_inst @@ -2078,13 +2080,10 @@ subroutine PhotosynthesisTotal (fn, filterp, & ! !LOCAL VARIABLES: integer :: f,fp,p,l,g ! indices - real(r8) :: rc14_atm(nsectors_c14), rc13_atm - integer :: sector_c14 !----------------------------------------------------------------------- associate( & forc_pco2 => atm2lnd_inst%forc_pco2_grc , & ! Input: [real(r8) (:) ] partial pressure co2 (Pa) - forc_pc13o2 => atm2lnd_inst%forc_pc13o2_grc , & ! Input: [real(r8) (:) ] partial pressure c13o2 (Pa) forc_po2 => atm2lnd_inst%forc_po2_grc , & ! Input: [real(r8) (:) ] partial pressure o2 (Pa) laisun => canopystate_inst%laisun_patch , & ! Input: [real(r8) (:) ] sunlit leaf area @@ -2114,19 +2113,10 @@ subroutine PhotosynthesisTotal (fn, filterp, & fpsn_wp => photosyns_inst%fpsn_wp_patch & ! Output: [real(r8) (:) ] product-limited photosynthesis (umol CO2 /m**2 /s) ) - if ( use_c14 ) then - if (use_c14_bombspike) then - call C14BombSpike(rc14_atm) - else - rc14_atm(:) = c14ratio - end if - end if - - if ( use_c13 ) then - if (use_c13_timeseries) then - call C13TimeSeries(rc13_atm) - end if - end if + ! Get the current C13/C14 ratio in the atmosphere from timeseries data or the fixed values + ! These calls fill the data: rc13_atm_grc and rc14_atm_grc + if ( use_c14 ) call C14BombSpike(bounds) + if ( use_c13 ) call C13TimeSeries(bounds, atm2lnd_inst) do f = 1, fn p = filterp(f) @@ -2141,11 +2131,7 @@ subroutine PhotosynthesisTotal (fn, filterp, & if (use_cn) then if ( use_c13 ) then - if (use_c13_timeseries) then - rc13_canair(p) = rc13_atm - else - rc13_canair(p) = forc_pc13o2(g)/(forc_pco2(g) - forc_pc13o2(g)) - endif + rc13_canair(p) = rc13_atm_grc(g) rc13_psnsun(p) = rc13_canair(p)/alphapsnsun(p) rc13_psnsha(p) = rc13_canair(p)/alphapsnsha(p) c13_psnsun(p) = psnsun(p) * (rc13_psnsun(p)/(1._r8+rc13_psnsun(p))) @@ -2157,19 +2143,10 @@ subroutine PhotosynthesisTotal (fn, filterp, & endif if ( use_c14 ) then - ! determine latitute sector for radiocarbon bomb spike inputs - if ( grc%latdeg(g) .ge. 30._r8 ) then - sector_c14 = 1 - else if ( grc%latdeg(g) .ge. -30._r8 ) then - sector_c14 = 2 - else - sector_c14 = 3 - endif - - rc14_canair(p) = rc14_atm(sector_c14) + rc14_canair(p) = rc14_atm_grc(g) - c14_psnsun(p) = rc14_atm(sector_c14) * psnsun(p) - c14_psnsha(p) = rc14_atm(sector_c14) * psnsha(p) + c14_psnsun(p) = rc14_atm_grc(g) * psnsun(p) + c14_psnsha(p) = rc14_atm_grc(g) * psnsha(p) endif end if @@ -2750,7 +2727,7 @@ subroutine PhotosynthesisHydraulicStress ( bounds, fn, filterp, & use clm_varcon , only : rgas, tfrz, rpi, spval use GridcellType , only : grc use clm_time_manager , only : get_step_size_real, is_near_local_noon - use clm_varctl , only : cnallocate_carbon_only + use clm_varctl , only : allocate_carbon_only use clm_varctl , only : lnc_opt, reduce_dayl_factor, vcmax_opt use clm_varpar , only : nlevsoi use pftconMod , only : nbrdlf_dcd_tmp_shrub @@ -3267,7 +3244,7 @@ subroutine PhotosynthesisHydraulicStress ( bounds, fn, filterp, & if (.not. use_cn) then vcmax25top = vcmax25top * fnitr(patch%itype(p)) else - if ( CNAllocate_Carbon_only() ) vcmax25top = vcmax25top * fnitr(patch%itype(p)) + if ( Allocate_Carbon_only() ) vcmax25top = vcmax25top * fnitr(patch%itype(p)) end if else if (vcmax_opt == 3) then vcmax25top = ( i_vcad(patch%itype(p)) + s_vcad(patch%itype(p)) * lnc(p) ) * dayl_factor(p) diff --git a/src/cpl/share_esmf/CTSMForce2DStreamBaseType.F90 b/src/cpl/share_esmf/CTSMForce2DStreamBaseType.F90 new file mode 100644 index 0000000000..174fa3554e --- /dev/null +++ b/src/cpl/share_esmf/CTSMForce2DStreamBaseType.F90 @@ -0,0 +1,269 @@ +module CTSMForce2DStreamBaseType + +! +! Description: +! +! Base module to handle 2D streams in CTSM. Specific streams extend this object +! for the details needed to handle a specific stream file. +! +! Having this base type allows the ESMF specific streams implementation to be isolated +! from the CTSM code. This allows the streams code this is based on to change in one place. +! It also makes it easier to unit-test extensions of this type as they become pretty standard +! CTSM code and there is a unit-tester stub for this code. +! + +#include "shr_assert.h" + + use ESMF, only : ESMF_LogFoundError, ESMF_LOGERR_PASSTHRU + use dshr_strdata_mod , only : shr_strdata_type + use shr_kind_mod , only : r8 => shr_kind_r8, CL => shr_kind_CL + use clm_varctl , only : iulog + use spmdMod , only : masterproc, mpicom, iam + use abortutils , only : endrun + use decompMod , only : bounds_type + use clm_varctl, only : FL => fname_len + + implicit none + private + + !----------------------------------------------------------------------- + ! Base 2D streams type + !----------------------------------------------------------------------- + type, abstract, public :: ctsm_force_2DStream_base_type + private + type(shr_strdata_type) :: sdat ! Stream data type + character(len=FL) :: stream_filename ! The stream data filename (also in sdat) + character(len=CL) :: stream_name ! The stream name (also in sdat) + contains + + ! PUBLIC METHODS + procedure(Init_interface) , public, deferred :: Init ! Initiale the extended type + procedure, public, non_overridable :: InitBase ! Initialize and read data in the streams + procedure(Clean_interface), public, deferred :: Clean ! Clean and deallocate the object class method + procedure, public, non_overridable :: CleanBase ! Clean method for the base type + procedure, public, non_overridable :: Advance ! Advance the streams data to the current model date + procedure, public :: GetPtr1D ! Get pointer to the 1D data array + procedure(Interp_interface), public, deferred :: Interp ! method in extensions to turn stream data into CTSM data + + end type ctsm_force_2DStream_base_type + !----------------------------------------------------------------------- + + !----------------------------------------------------------------------- + ! Interfaces that will be deferred to the extended type + !----------------------------------------------------------------------- + abstract interface + + !----------------------------------------------------------------------- + + subroutine Init_interface( this, bounds, fldfilename, meshfile, mapalgo, tintalgo, taxmode, & + year_first, year_last, model_year_align ) + ! Description: + ! + ! Initialize the specific stream type that extends the base type + ! Normally the extended type will call the InitBase as well as doing other initialization needed + ! + ! Uses: + use decompMod , only : bounds_type + import :: ctsm_force_2DStream_base_type + + ! Arguments: + class(ctsm_force_2DStream_base_type), intent(inout) :: this + type(bounds_type), intent(in) :: bounds + character(*), intent(in) :: fldfilename ! stream data filename (full pathname) (single file) + ! NOTE: fldfilename could be expanded to an array if needed, but currently we only have one file + character(*), intent(in) :: meshfile ! full pathname to stream mesh file (none for global data) + character(*), intent(in) :: mapalgo ! stream mesh -> model mesh mapping type + character(*), intent(in) :: tintalgo ! time interpolation algorithm + character(*), intent(in) :: taxMode ! time axis mode + integer, intent(in) :: year_first ! first year to use + integer, intent(in) :: year_last ! last year to use + integer, intent(in) :: model_year_align ! align yearFirst with this model year + end subroutine Init_interface + + !----------------------------------------------------------------------- + + subroutine Clean_interface(this) + ! Description: + ! Clean up any memory allocated in the specific stream type that extends the base type + ! Normally the extended type will call the CleanBase method as well as other things needed. + ! Uses: + import :: ctsm_force_2DStream_base_type + ! + ! Arguments: + class(ctsm_force_2DStream_base_type), intent(inout) :: this + end subroutine Clean_interface + + !----------------------------------------------------------------------- + + subroutine Interp_interface(this, bounds) + ! Description: + ! Get the current time data from the streams and put it into the data of the extension. + ! What this looks like may vary with the streams extension, but in general it will use + ! The GetPtr1D method to get the streams data. + ! Uses: + use decompMod , only : bounds_type + import :: ctsm_force_2DStream_base_type + ! + ! Arguments: + class(ctsm_force_2DStream_base_type), intent(inout) :: this + type(bounds_type), intent(in) :: bounds + end subroutine Interp_interface + + end interface + !----------------------------------------------------------------------- + + character(len=*), parameter, private :: sourcefile = & + __FILE__ + + !----------------------------------------------------------------------- + contains + !----------------------------------------------------------------------- + + !----------------------------------------------------------------------- + + subroutine InitBase( this, bounds, varnames, fldfilename, meshfile, mapalgo, tintalgo, taxmode, name, & + year_first, year_last, model_year_align ) + ! + ! Description: + ! + ! Initialization of the base type. Extended types will normally call this as part of their initialization. + ! + ! Uses: + use lnd_comp_shr , only : mesh, model_clock + use dshr_strdata_mod , only : shr_strdata_init_from_inline + use decompMod , only : bounds_level_proc + use shr_log_mod , only : errMsg => shr_log_errMsg + ! Arguments: + class(ctsm_force_2DStream_base_type), intent(inout) :: this + type(bounds_type), intent(in) :: bounds + character(*), intent(in) :: varnames(:) ! variable names to read from stream file + character(*), intent(in) :: fldfilename ! stream data filename (full pathname) (single file) + ! NOTE: fldfilename could be expanded to an array if needed, but currently we only have one file + character(*), intent(in) :: meshfile ! full pathname to stream mesh file (none for global data) + character(*), intent(in) :: mapalgo ! stream mesh -> model mesh mapping type + character(*), intent(in) :: tintalgo ! time interpolation algorithm + character(*), intent(in) :: taxMode ! time axis mode + character(*), intent(in) :: name ! name of stream + integer, intent(in) :: year_first ! first year to use + integer, intent(in) :: year_last ! last year to use + integer, intent(in) :: model_year_align ! align yearFirst with this model year + + ! Local variables + integer, parameter :: offset = 0 ! time offset in seconds of stream data + integer :: rc ! error return code + + ! Some error checking... + SHR_ASSERT( bounds%level == bounds_level_proc, "InitBase should have a processor bounds, so we can do some checking"//errMsg( sourcefile, __LINE__) ) + SHR_ASSERT( bounds%begg == 1, "Make sure the starting bounds index is 1 so we know the mapping to gridcells is correct"//errMsg( sourcefile, __LINE__) ) + if ( len(fldfilename) >= FL )then + call endrun( 'stream field filename is too long:'//trim(fldfilename), file=sourcefile, line=__LINE__ ) + end if + this%stream_filename = fldfilename + this%stream_name = name + call shr_strdata_init_from_inline(this%sdat, & + my_task = iam, & + logunit = iulog, & + compname = 'LND', & + model_clock = model_clock,& + model_mesh = mesh, & + stream_meshfile = trim(meshfile), & + stream_lev_dimname = 'null', & + stream_mapalgo = mapalgo, & + stream_filenames = (/trim(fldfilename)/), & + stream_fldlistFile = varnames, & + stream_fldListModel = varnames, & + stream_yearFirst = year_first, & + stream_yearLast = year_last, & + stream_yearAlign = model_year_align, & + stream_offset = offset, & + stream_taxmode = taxmode, & + stream_dtlimit = 1.0e30_r8, & + stream_tintalgo = tintalgo, & + stream_name = name, & + rc = rc) + if (ESMF_LogFoundError(rcToCheck=rc, msg=ESMF_LOGERR_PASSTHRU, line=__LINE__, file=sourcefile)) then + write(iulog,*) ' Streams initialization failing for ', trim(name), ' stream file = ', trim(fldfilename) + call endrun( 'CTSM forcing Streams initialization failing', file=sourcefile, line=__LINE__ ) + end if + end subroutine InitBase + + !----------------------------------------------------------------------- + + subroutine CleanBase( this ) + ! Description: + ! Clean up any memory in the base type as needed. + ! Normally types that extend this base type will call this as part of their clean operation + ! + ! Arguments: + class(ctsm_force_2DStream_base_type) , intent(inout) :: this + + integer :: ierr ! error code + + ! Currently no data to deallocate other than the stream data type + ! The stream data type doesn't have a clean method right now + ! So doing a few things manually here + end subroutine CleanBase + + !----------------------------------------------------------------------- + + subroutine Advance(this) + ! + ! Description: + ! + ! Advance the stream to the current time-step + ! + ! Uses: + use clm_time_manager , only : get_curr_date + use dshr_strdata_mod , only : shr_strdata_advance + ! + ! Arguments: + class(ctsm_force_2DStream_base_type), intent(inout) :: this + ! !LOCAL VARIABLES: + integer :: year ! year (0, ...) for nstep+1 + integer :: mon ! month (1, ..., 12) for nstep+1 + integer :: day ! day of month (1, ..., 31) for nstep+1 + integer :: sec ! seconds into current date for nstep+1 + integer :: mcdate ! Current model date (yyyymmdd) + integer :: rc ! Error return code + + ! Advance sdat stream + call get_curr_date(year, mon, day, sec) + mcdate = year*10000 + mon*100 + day + call shr_strdata_advance(this%sdat, ymd=mcdate, tod=sec, logunit=iulog, istr='CTSMForce2DStreamBase', rc=rc) + if (ESMF_LogFoundError(rcToCheck=rc, msg=ESMF_LOGERR_PASSTHRU, line=__LINE__, file=__FILE__)) then + write(iulog,*) ' Streams advance failing for ', trim(this%stream_name), ' stream file = ', trim(this%stream_filename) + call endrun( 'CTSM forcing Streams advance failing', file=sourcefile, line=__LINE__ ) + end if + end subroutine Advance + + !----------------------------------------------------------------------- + + subroutine GetPtr1D(this, fldname, dataptr1d) + ! + ! Description: + ! + ! Get the pointer to the 1D data array for the given field name + ! Normally stream extensions will use this in the Interp method to + ! save the stream data locally. + ! + ! Uses: + use dshr_methods_mod , only : dshr_fldbun_getfldptr + ! Arguments: + class(ctsm_force_2DStream_base_type), intent(inout) :: this + character(*), intent(in) :: fldname ! field name to get pointer for + real(r8), pointer :: dataptr1d(:) ! Pointer to the 1D data + + ! Local variables + integer :: rc ! error return code + + ! Get pointer for stream data that is time and spatially interpolated to model time and grid + call dshr_fldbun_getFldPtr(this%sdat%pstrm(1)%fldbun_model, fldname=fldname, fldptr1=dataptr1d, rc=rc) + if (ESMF_LogFoundError(rcToCheck=rc, msg=ESMF_LOGERR_PASSTHRU, line=__LINE__, file=sourcefile)) then + call endrun( 'Error getting field pointer for '//trim(fldname)//' from stream data', file=sourcefile, line=__LINE__ ) + end if + + end subroutine GetPtr1D + + !----------------------------------------------------------------------- + +end module CTSMForce2DStreamBaseType diff --git a/src/cpl/share_esmf/FireDataBaseType.F90 b/src/cpl/share_esmf/FireDataBaseType.F90 index fd8b140c95..aa9395b770 100644 --- a/src/cpl/share_esmf/FireDataBaseType.F90 +++ b/src/cpl/share_esmf/FireDataBaseType.F90 @@ -338,6 +338,7 @@ subroutine hdm_interp( this, bounds) ig = 0 do g = bounds%begg,bounds%endg ig = ig+1 + SHR_ASSERT_FL( ig == g, sourcefile, __LINE__ ) this%forc_hdm(g) = dataptr1d(ig) end do @@ -506,6 +507,7 @@ subroutine lnfm_interp(this, bounds ) ig = 0 do g = bounds%begg,bounds%endg ig = ig+1 + SHR_ASSERT_FL( ig == g, sourcefile, __LINE__ ) this%forc_lnfm(g) = dataptr1d(ig) end do diff --git a/src/cpl/share_esmf/PrigentRoughnessStreamType.F90 b/src/cpl/share_esmf/PrigentRoughnessStreamType.F90 index afc65f2ece..158c75e434 100644 --- a/src/cpl/share_esmf/PrigentRoughnessStreamType.F90 +++ b/src/cpl/share_esmf/PrigentRoughnessStreamType.F90 @@ -1,5 +1,6 @@ module PrigentRoughnessStreamType +#include "shr_assert.h" !----------------------------------------------------------------------- ! !DESCRIPTION: @@ -151,6 +152,7 @@ subroutine Init(this, bounds, NLFilename) ig = 0 do g = bounds%begg,bounds%endg ig = ig+1 + SHR_ASSERT_FL( ig == g, sourcefile, __LINE__ ) this%prigent_rghn(g) = dataptr1d(ig) end do diff --git a/src/cpl/share_esmf/cropcalStreamMod.F90 b/src/cpl/share_esmf/cropcalStreamMod.F90 index 591476e59e..df48ef5e98 100644 --- a/src/cpl/share_esmf/cropcalStreamMod.F90 +++ b/src/cpl/share_esmf/cropcalStreamMod.F90 @@ -480,6 +480,7 @@ subroutine cropcal_advance( bounds ) do g = begg,endg ig = ig+1 g_to_ig(g) = ig + SHR_ASSERT_FL( ig == g, sourcefile, __LINE__ ) end do end if diff --git a/src/cpl/share_esmf/ndepStreamMod.F90 b/src/cpl/share_esmf/ndepStreamMod.F90 index f8edd96ffc..860dd35641 100644 --- a/src/cpl/share_esmf/ndepStreamMod.F90 +++ b/src/cpl/share_esmf/ndepStreamMod.F90 @@ -1,5 +1,7 @@ module ndepStreamMod +#include "shr_assert.h" + !----------------------------------------------------------------------- ! !DESCRIPTION: ! Contains methods for reading in nitrogen deposition data file @@ -256,12 +258,14 @@ subroutine ndep_interp(bounds, atm2lnd_inst) dayspyr = get_curr_days_per_year( ) do g = bounds%begg,bounds%endg ig = ig+1 + SHR_ASSERT_FL( ig == g, sourcefile, __LINE__ ) atm2lnd_inst%forc_ndep_grc(g) = dataptr1d(ig) / (secspday * dayspyr) end do else ig = 0 do g = bounds%begg,bounds%endg ig = ig+1 + SHR_ASSERT_FL( ig == g, sourcefile, __LINE__ ) atm2lnd_inst%forc_ndep_grc(g) = dataptr1d(ig) end do end if diff --git a/src/fates b/src/fates index c1dfc21c50..5bb36cba29 160000 --- a/src/fates +++ b/src/fates @@ -1 +1 @@ -Subproject commit c1dfc21c505b5c8b29d4592b7d4a5e058239f6fb +Subproject commit 5bb36cba29b95295aca5aedc13f348ef1e001b99 diff --git a/src/main/clm_initializeMod.F90 b/src/main/clm_initializeMod.F90 index 1c26b55cfd..4530fda860 100644 --- a/src/main/clm_initializeMod.F90 +++ b/src/main/clm_initializeMod.F90 @@ -157,6 +157,7 @@ subroutine initialize2(ni,nj, currtime) use clm_time_manager , only : get_curr_date, get_nstep, advance_timestep use clm_time_manager , only : timemgr_init, timemgr_restart_io, timemgr_restart, is_restart use CIsoAtmTimeseriesMod , only : C14_init_BombSpike, use_c14_bombspike, C13_init_TimeSeries, use_c13_timeseries + use CIsoAtmTimeseriesMod , only : CIsoAtmReadNML use DaylengthMod , only : InitDaylength use dynSubgridDriverMod , only : dynSubgrid_init use dynConsBiogeophysMod , only : dyn_hwcontent_set_baselines @@ -475,11 +476,12 @@ subroutine initialize2(ni,nj, currtime) ! Also do this for FATES see below call SatellitePhenologyInit(bounds_proc) end if - if ( use_c14 .and. use_c14_bombspike ) then - call C14_init_BombSpike() + if ( use_c13 .or. use_c14 ) call CIsoAtmReadNML( NLFilename ) + if ( use_c14 ) then + call C14_init_BombSpike( bounds_proc ) end if - if ( use_c13 .and. use_c13_timeseries ) then - call C13_init_TimeSeries() + if ( use_c13 ) then + call C13_init_TimeSeries( bounds_proc ) end if else ! FATES OR Satellite phenology diff --git a/src/main/clm_varcon.F90 b/src/main/clm_varcon.F90 index 985660142a..234b89c797 100644 --- a/src/main/clm_varcon.F90 +++ b/src/main/clm_varcon.F90 @@ -131,23 +131,23 @@ module clm_varcon !!! C13 real(r8), public, parameter :: preind_atm_del13c = -6.0_r8 ! preindustrial value for atmospheric del13C - real(r8), public, parameter :: preind_atm_ratio = SHR_CONST_PDB + (preind_atm_del13c * SHR_CONST_PDB)/1000.0_r8 ! 13C/12C + real(r8), private, parameter :: preind_atm_ratio = SHR_CONST_PDB + (preind_atm_del13c * SHR_CONST_PDB)/1000.0_r8 ! 13C/12C real(r8), public :: c13ratio = preind_atm_ratio/(1.0_r8+preind_atm_ratio) ! 13C/(12+13)C preind atmosphere ! typical del13C for C3 photosynthesis (permil, relative to PDB) - real(r8), public, parameter :: c3_del13c = -28._r8 + real(r8), private, parameter :: c3_del13c = -28._r8 ! typical del13C for C4 photosynthesis (permil, relative to PDB) - real(r8), public, parameter :: c4_del13c = -13._r8 + real(r8), private, parameter :: c4_del13c = -13._r8 ! isotope ratio (13c/12c) for C3 photosynthesis - real(r8), public, parameter :: c3_r1 = SHR_CONST_PDB + ((c3_del13c*SHR_CONST_PDB)/1000._r8) + real(r8), private, parameter :: c3_r1 = SHR_CONST_PDB + ((c3_del13c*SHR_CONST_PDB)/1000._r8) ! isotope ratio (13c/[12c+13c]) for C3 photosynthesis real(r8), public, parameter :: c3_r2 = c3_r1/(1._r8 + c3_r1) ! isotope ratio (13c/12c) for C4 photosynthesis - real(r8), public, parameter :: c4_r1 = SHR_CONST_PDB + ((c4_del13c*SHR_CONST_PDB)/1000._r8) + real(r8), private, parameter :: c4_r1 = SHR_CONST_PDB + ((c4_del13c*SHR_CONST_PDB)/1000._r8) ! isotope ratio (13c/[12c+13c]) for C4 photosynthesis real(r8), public, parameter :: c4_r2 = c4_r1/(1._r8 + c4_r1) diff --git a/src/main/clm_varctl.F90 b/src/main/clm_varctl.F90 index 9a0d2901b9..83133acf2b 100644 --- a/src/main/clm_varctl.F90 +++ b/src/main/clm_varctl.F90 @@ -11,8 +11,8 @@ module clm_varctl ! !PUBLIC MEMBER FUNCTIONS: implicit none public :: clm_varctl_set ! Set variables - public :: cnallocate_carbon_only_set - public :: cnallocate_carbon_only + public :: allocate_carbon_only_set + public :: allocate_carbon_only ! private save @@ -585,15 +585,15 @@ subroutine clm_varctl_set( caseid_in, ctitle_in, brnch_retain_casename_in, & end subroutine clm_varctl_set - ! Set module carbon_only flag - subroutine cnallocate_carbon_only_set(carbon_only_in) + ! Set module carbon_only flag (applies to both CN and FATES) + subroutine allocate_carbon_only_set(carbon_only_in) logical, intent(in) :: carbon_only_in carbon_only = carbon_only_in - end subroutine cnallocate_carbon_only_set + end subroutine allocate_carbon_only_set - ! Get module carbon_only flag - logical function CNAllocate_Carbon_only() - cnallocate_carbon_only = carbon_only - end function CNAllocate_Carbon_only + ! Get module carbon_only flag (applies to both CN and FATES) + logical function Allocate_Carbon_only() + allocate_carbon_only = carbon_only + end function Allocate_Carbon_only end module clm_varctl diff --git a/src/soilbiogeochem/SoilBiogeochemCompetitionMod.F90 b/src/soilbiogeochem/SoilBiogeochemCompetitionMod.F90 index 57bc82984e..041f6ee740 100644 --- a/src/soilbiogeochem/SoilBiogeochemCompetitionMod.F90 +++ b/src/soilbiogeochem/SoilBiogeochemCompetitionMod.F90 @@ -76,7 +76,7 @@ subroutine readParams ( ncid ) type(file_desc_t),intent(inout) :: ncid ! pio netCDF file id ! ! !LOCAL VARIABLES: - character(len=32) :: subname = 'CNAllocParamsType' + character(len=32) :: subname = 'readParams' character(len=100) :: errCode = '-Error reading in parameters file:' logical :: readv ! has variable been read in or not real(r8) :: tempr ! temporary to read in parameter @@ -130,7 +130,7 @@ subroutine SoilBiogeochemCompetitionInit ( bounds) ! !USES: use clm_varcon , only: secspday use clm_time_manager, only: get_step_size_real - use clm_varctl , only: iulog, cnallocate_carbon_only_set + use clm_varctl , only: iulog, allocate_carbon_only_set use shr_infnan_mod , only: nan => shr_infnan_nan, assignment(=) ! ! !ARGUMENTS: @@ -160,7 +160,7 @@ subroutine SoilBiogeochemCompetitionInit ( bounds) errMsg(sourcefile, __LINE__)) end select - call cnallocate_carbon_only_set(carbon_only) + call allocate_carbon_only_set(carbon_only) end subroutine SoilBiogeochemCompetitionInit @@ -175,7 +175,7 @@ subroutine SoilBiogeochemCompetition (bounds, num_bgc_soilc, filter_bgc_soilc,nu soilbiogeochem_nitrogenflux_inst,canopystate_inst) ! ! !USES: - use clm_varctl , only: cnallocate_carbon_only, iulog + use clm_varctl , only: allocate_carbon_only, iulog use clm_varpar , only: nlevdecomp, ndecomp_cascade_transitions use clm_varpar , only: i_cop_mic, i_oli_mic use clm_varcon , only: nitrif_n2o_loss_frac @@ -338,7 +338,7 @@ subroutine SoilBiogeochemCompetition (bounds, num_bgc_soilc, filter_bgc_soilc,nu fpi_vr(c,j) = 1.0_r8 actual_immob_vr(c,j) = potential_immob_vr(c,j) sminn_to_plant_vr(c,j) = plant_ndemand(c) * nuptake_prof(c,j) - else if ( cnallocate_carbon_only()) then !.or. & + else if ( allocate_carbon_only()) then !.or. & ! this code block controls the addition of N to sminn pool ! to eliminate any N limitation, when Carbon_Only is set. This lets the ! model behave essentially as a carbon-only model, but with the @@ -729,7 +729,7 @@ subroutine SoilBiogeochemCompetition (bounds, num_bgc_soilc, filter_bgc_soilc,nu ! eliminate N limitations, so there is still a diagnostic quantity ! that describes the degree of N limitation at steady-state. - if ( cnallocate_carbon_only()) then !.or. & + if ( allocate_carbon_only()) then !.or. & if ( fpi_no3_vr(c,j) + fpi_nh4_vr(c,j) < 1._r8 ) then fpi_nh4_vr(c,j) = 1.0_r8 - fpi_no3_vr(c,j) supplement_to_sminn_vr(c,j) = (potential_immob_vr(c,j) & diff --git a/src/unit_test_stubs/share_esmf/CMakeLists.txt b/src/unit_test_stubs/share_esmf/CMakeLists.txt index 368601dcc8..c68bb17a98 100644 --- a/src/unit_test_stubs/share_esmf/CMakeLists.txt +++ b/src/unit_test_stubs/share_esmf/CMakeLists.txt @@ -1,6 +1,7 @@ list(APPEND clm_sources ExcessIceStreamType.F90 FireDataBaseType.F90 + CTSMForce2DStreamBaseType.F90 laiStreamMod.F90 PrigentRoughnessStreamType.F90 ZenderSoilErodStreamType.F90 diff --git a/src/unit_test_stubs/share_esmf/CTSMForce2DStreamBaseType.F90 b/src/unit_test_stubs/share_esmf/CTSMForce2DStreamBaseType.F90 new file mode 100644 index 0000000000..59e6a4d551 --- /dev/null +++ b/src/unit_test_stubs/share_esmf/CTSMForce2DStreamBaseType.F90 @@ -0,0 +1,120 @@ +module CTSMForce2DStreamBaseType + + use shr_kind_mod , only : r8 => shr_kind_r8, CL => shr_kind_CL + use abortutils , only : endrun + use decompMod , only : bounds_type + use clm_varctl, only : FL => fname_len + + implicit none + private + + type, abstract, public :: ctsm_force_2DStream_base_type + private + character(len=FL) :: stream_filename ! The stream data filename (also in sdat) + character(len=CL) :: stream_name ! The stream name (also in sdat) + contains + + ! PUBLIC METHODS + procedure(Init_interface) , public, deferred :: Init + procedure, public, non_overridable :: InitBase ! Initialize and read data in the streams, , store the g_to_ig index array + procedure(Clean_interface), public, deferred :: Clean ! Clean and deallocate the object class method + procedure, public, non_overridable :: CleanBase ! Clean method for the base type + procedure, public, non_overridable :: Advance ! Advance the streams data to the current model date + procedure, public :: GetPtr1D ! Get pointer to the 1D data array + procedure(Interp_interface), public, deferred :: Interp ! method in extensions to turn stream data into output data + + end type ctsm_force_2DStream_base_type + + abstract interface + + subroutine Init_interface( this, bounds, fldfilename, meshfile, mapalgo, tintalgo, taxmode, & + year_first, year_last, model_year_align ) + ! Uses: + use decompMod , only : bounds_type + import :: ctsm_force_2DStream_base_type + + ! Arguments: + class(ctsm_force_2DStream_base_type), intent(inout) :: this + type(bounds_type), intent(in) :: bounds + character(*), intent(in) :: fldfilename ! stream data filename (full pathname) (single file) + ! NOTE: fldfilename could be expanded to an array if needed, but currently we only have one file + character(*), intent(in) :: meshfile ! full pathname to stream mesh file (none for global data) + character(*), intent(in) :: mapalgo ! stream mesh -> model mesh mapping type + character(*), intent(in) :: tintalgo ! time interpolation algorithm + character(*), intent(in) :: taxMode ! time axis mode + integer, intent(in) :: year_first ! first year to use + integer, intent(in) :: year_last ! last year to use + integer, intent(in) :: model_year_align ! align yearFirst with this model year + end subroutine Init_interface + + subroutine Clean_interface(this) + ! Uses: + import :: ctsm_force_2DStream_base_type + ! + ! Arguments: + class(ctsm_force_2DStream_base_type), intent(inout) :: this + end subroutine Clean_interface + + subroutine Interp_interface(this, bounds) + ! Uses: + use decompMod , only : bounds_type + import :: ctsm_force_2DStream_base_type + ! + ! Arguments: + class(ctsm_force_2DStream_base_type), intent(inout) :: this + type(bounds_type), intent(in) :: bounds + end subroutine Interp_interface + + end interface + + character(len=*), parameter, private :: sourcefile = & + __FILE__ + + contains + + subroutine InitBase( this, bounds, varnames, fldfilename, meshfile, mapalgo, tintalgo, taxmode, name, & + year_first, year_last, model_year_align ) + ! Uses: + ! Arguments: + class(ctsm_force_2DStream_base_type), intent(inout) :: this + type(bounds_type), intent(in) :: bounds + character(*), intent(in) :: varnames(:) ! variable names to read from stream file + character(*), intent(in) :: fldfilename ! stream data filename (full pathname) (single file) + ! NOTE: fldfilename could be expanded to an array if needed, but currently we only have one file + character(*), intent(in) :: meshfile ! full pathname to stream mesh file (none for global data) + character(*), intent(in) :: mapalgo ! stream mesh -> model mesh mapping type + character(*), intent(in) :: tintalgo ! time interpolation algorithm + character(*), intent(in) :: taxMode ! time axis mode + character(*), intent(in) :: name ! name of stream + integer, intent(in) :: year_first ! first year to use + integer, intent(in) :: year_last ! last year to use + integer, intent(in) :: model_year_align ! align yearFirst with this model year + + end subroutine InitBase + + subroutine CleanBase( this ) + class(ctsm_force_2DStream_base_type) , intent(inout) :: this + + call endrun('CTSMForce2DStreamBaseType: CleanBase method not implemented in stub') + + end subroutine CleanBase + + subroutine Advance(this) + ! Arguments: + class(ctsm_force_2DStream_base_type), intent(inout) :: this + + call endrun('CTSMForce2DStreamBaseType: Advance method not implemented in stub') + + end subroutine Advance + + subroutine GetPtr1D(this, fldname, dataptr1d) + ! Get the pointer to the 1D data array for the given field name + ! Uses: + ! Arguments: + class(ctsm_force_2DStream_base_type), intent(inout) :: this + character(*), intent(in) :: fldname ! field name to get pointer for + real(r8), pointer :: dataptr1d(:) ! Pointer to the 1D data + + end subroutine GetPtr1D + +end module CTSMForce2DStreamBaseType diff --git a/src/utils/clmfates_interfaceMod.F90 b/src/utils/clmfates_interfaceMod.F90 index da43b30b18..81f8a78b8e 100644 --- a/src/utils/clmfates_interfaceMod.F90 +++ b/src/utils/clmfates_interfaceMod.F90 @@ -80,6 +80,7 @@ module CLMFatesInterfaceMod use clm_varctl , only : use_lch4 use clm_varctl , only : fates_history_dimlevel use clm_varctl , only : nsrest, nsrBranch + use clm_varctl , only : Allocate_Carbon_only use clm_varcon , only : tfrz use clm_varcon , only : spval use clm_varcon , only : denice @@ -474,23 +475,6 @@ subroutine CLMFatesGlobals2() end if call set_fates_ctrlparms('use_tree_damage',ival=pass_tree_damage) - ! These may be in a non-limiting status (ie when supplements) - ! are added, but they are always allocated and cycled non-the less - ! FATES may want to interact differently with other models - ! that don't even have these arrays allocated. - ! FATES also checks that if NO3 is cycled in ELM, then - ! any plant affinity parameters are checked. - - if(use_nitrif_denitrif) then - call set_fates_ctrlparms('nitrogen_spec',ival=1) - else - call set_fates_ctrlparms('nitrogen_spec',ival=2) - end if - - ! Phosphorus is not tracked in CLM - call set_fates_ctrlparms('phosphorus_spec',ival=0) - - ! Pass spitfire mode values call set_fates_ctrlparms('spitfire_mode',ival=fates_spitfire_mode) call set_fates_ctrlparms('sf_nofire_def',ival=no_fire) @@ -1161,6 +1145,12 @@ subroutine dynamics_driv(this, nc, bounds_clump, & real(r8) :: s_node, smp_node ! local for relative water content and potential logical :: after_start_of_harvest_ts integer :: iharv + logical :: nitr_suppl ! true -> CLM is supplementing Nitrogen + logical, parameter :: phos_dummy_suppl = .true. ! true -> Phosphorus is NOT limited (i.e. supplemented) + ! This argument is needed for FATES + ! to specify if phosphorus is being + ! supplemented, Phosphorous is not limited in CLM + ! so we set it to TRUE !----------------------------------------------------------------------- ! --------------------------------------------------------------------------------- @@ -1342,10 +1332,16 @@ subroutine dynamics_driv(this, nc, bounds_clump, & end do + if(Allocate_Carbon_only())then + nitr_suppl = .true. + else + nitr_suppl = .false. + end if + ! Nutrient uptake fluxes have been accumulating with each short ! timestep, here, we unload them from the boundary condition ! structures into the cohort structures. - call UnPackNutrientAquisitionBCs(this%fates(nc)%sites, this%fates(nc)%bc_in) + call UnPackNutrientAquisitionBCs(this%fates(nc)%sites, this%fates(nc)%bc_in, nitr_suppl, phos_dummy_suppl) ! Distribute any seeds from neighboring gridcells into the current gridcell ! Global seed availability array populated by WrapGlobalSeedDispersal call diff --git a/src/utils/fileutils.F90 b/src/utils/fileutils.F90 index e7146468e7..f67cf91af0 100644 --- a/src/utils/fileutils.F90 +++ b/src/utils/fileutils.F90 @@ -21,6 +21,9 @@ module fileutils public :: getavu !Get next available Fortran unit number !----------------------------------------------------------------------- + character(len=*), parameter, private :: sourcefile = & + __FILE__ + contains !----------------------------------------------------------------------- @@ -76,7 +79,7 @@ subroutine getfil (fulpath, locfn, iflag) write(iulog,'(a)')'(GETFIL): full pathname is '//trim(fulpath) write(iulog,'(a)')'(GETFIL): local filename has zero length' end if - call shr_sys_abort + call shr_sys_abort('GETFIL: local filename has zero length', file=sourcefile, line=__LINE__) else if (masterproc) then write(iulog,'(a)')'(GETFIL): attempting to find local file ',trim(locfn) @@ -105,7 +108,7 @@ subroutine getfil (fulpath, locfn, iflag) write(iulog,'(a)')'(GETFIL): failed getting file from full path: '//fulpath end if if (iflag==0) then - call shr_sys_abort ('GETFIL: FAILED to get '//trim(fulpath)) + call shr_sys_abort ('GETFIL: FAILED to get '//trim(fulpath), file=sourcefile, line=__LINE__) else RETURN endif @@ -131,7 +134,7 @@ subroutine opnfil (locfn, iun, form) if (len_trim(locfn) == 0) then write(iulog,*)'(OPNFIL): local filename has zero length' - call shr_sys_abort + call shr_sys_abort('OPNFIL: local filename has zero length', file=sourcefile, line=__LINE__) endif if (form=='u' .or. form=='U') then ft = 'unformatted' @@ -142,7 +145,7 @@ subroutine opnfil (locfn, iun, form) if (ioe /= 0) then write(iulog,*)'(OPNFIL): failed to open file ',trim(locfn), & & ' on unit ',iun,' ierr=',ioe - call shr_sys_abort + call shr_sys_abort('OPNFIL: failed to open '//trim(locfn), rc=ioe, file=sourcefile, line=__LINE__) else if ( masterproc )then write(iulog,*)'(OPNFIL): Successfully opened file ',trim(locfn), & & ' on unit= ',iun diff --git a/tools/contrib/SpinupStability_BGC_v11.ncl b/tools/contrib/SpinupStability_BGC_v11.ncl index d81b38ad9c..92a06e123e 100644 --- a/tools/contrib/SpinupStability_BGC_v11.ncl +++ b/tools/contrib/SpinupStability_BGC_v11.ncl @@ -36,7 +36,7 @@ begin region = "Global" ; Global, Arctic, or SPT (single point) subper = 20 ; Subsampling period in years paleo = False ; Use paleo map - hist_ext = "h0" ; Set to either h0 (ctsm5.3.061 or earlier) or h0a (ctsm5.3.062 or later) + hist_ext = "h0a" ; Valid options: h0a, h0 ; SPT (single point) EXAMPLE ; caseid = "clm50_release-clm5.0.15_SPT_GSWP3V1_1850spin" @@ -45,7 +45,7 @@ begin ; region = "SPT" ; Global, Arctic, or SPT (single point) ; subper = 20 ; Subsampling period in years ; paleo = False ; Use paleo map -; hist_ext = "h0" ; Set to either h0 (ctsm5.3.061 or earlier) or h0a (ctsm5.3.062 or later) +; hist_ext = "h0a" ; Valid options: h0a, h0 do_plot = True ; do_plot = False diff --git a/tools/contrib/SpinupStability_BGC_v12_SE.ncl b/tools/contrib/SpinupStability_BGC_v12_SE.ncl index aa044b9c17..25e909b696 100644 --- a/tools/contrib/SpinupStability_BGC_v12_SE.ncl +++ b/tools/contrib/SpinupStability_BGC_v12_SE.ncl @@ -41,7 +41,7 @@ begin region = "Global" ; Global subper = 20 ; Subsampling period in years paleo = False ; Use paleo map ; UNTESTED IN THIS VERSION - hist_ext = "h0" ; Set to either h0 (ctsm5.3.061 or earlier) or h0a (ctsm5.3.062 or later) + hist_ext = "h0a" ; Valid options: h0a, h0 ;--- ARH mods --- map_method = "conserve" ;bilinear,conserve or patch diff --git a/tools/contrib/SpinupStability_SP_v10.ncl b/tools/contrib/SpinupStability_SP_v10.ncl index 6b3d310234..97eccd8183 100644 --- a/tools/contrib/SpinupStability_SP_v10.ncl +++ b/tools/contrib/SpinupStability_SP_v10.ncl @@ -32,7 +32,7 @@ begin subper = 20 h2osoi_layer = 8 ; Desired soil layer (layer 8 is about 1m) tsoi_layer = 10 ; Desired soil layer (layer 10 is about 3m) - hist_ext = "h0" ; Set to either h0 (ctsm5.3.061 or earlier) or h0a (ctsm5.3.062 or later) + hist_ext = "h0a" ; Valid options: h0a, h0 do_plot = "True" ;=======================================================================; diff --git a/tools/contrib/run_clm_historical.v11.csh b/tools/contrib/run_clm_historical.v11.csh index 04e22aa31a..7398487a13 100755 --- a/tools/contrib/run_clm_historical.v11.csh +++ b/tools/contrib/run_clm_historical.v11.csh @@ -72,6 +72,8 @@ # ./run_clm_historical.v10.csh ! > & run_clm_historical.out & # - Modify to look for either h0 or h0a files - Keith Oleson July, 2025 # ./run_clm_historical.v11.csh ! > & run_clm_historical.out & +# - Update the comment about h0a/h0 files - slevis Nov, 2025 +# ./run_clm_historical.v11.csh ! > & run_clm_historical.out & ######################################################################################### ######################################################################################### @@ -85,8 +87,8 @@ # --- CASENAME is your case name set CASENAME = 'ctsm530_f19_PPE_TESTv11_hist' -# --- Set to either h0 (ctsm5.3.061 or earlier) or h0a (ctsm5.3.062 or later) -set HIST_EXT = 'h0' +# HIST_EXT valid options: h0a, h0 +set HIST_EXT = 'h0a' # --- Set the user namelist file. cp original_user_nl_clm user_nl_clm