diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs index c02e20b678..8960800172 100644 --- a/.git-blame-ignore-revs +++ b/.git-blame-ignore-revs @@ -1,4 +1,5 @@ # Ran python directory through black python formatter +d89c86e7776bbf7451860b60038b4725fe7a0560 b429b63824e09f82e95d2982f14311cbbd8e4a37 d229b5c6689efc4c2a6cef077515c4ccd5c18ff6 4cd83cb3ee6d85eb909403487abf5eeaf4d98911 @@ -54,3 +55,6 @@ aa04d1f7d86cc2503b98b7e2b2d84dbfff6c316b 753fda3ff0147837231a73c9c728dd9ce47b5997 f112ba0bbf96a61d5a4d354dc0dcbd8b0c68145c bd535c710db78420b8e8b9d71d88d8339e899c59 +4b20bbd7003e6f77dab4e3268cc4a43f9b5a3b5d +cf433215b58ba8776ec5edfb0b0d80c0836ed3a0 +16d57ff37859b34dab005693e3085d64e2bcd95a diff --git a/.gitignore b/.gitignore index e24a481063..879298d910 100644 --- a/.gitignore +++ b/.gitignore @@ -89,8 +89,18 @@ unit_test_build /tools/site_and_regional/listing.csv /tools/site_and_regional/????/ /tools/site_and_regional/????.ad/ +/tools/site_and_regional/????.*.ad/ /tools/site_and_regional/????.postad/ +/tools/site_and_regional/????.*.postad/ /tools/site_and_regional/????.transient/ +/tools/site_and_regional/????.*.transient/ +/tools/site_and_regional/??-???/ +/tools/site_and_regional/??-???.ad/ +/tools/site_and_regional/??-???.*.ad/ +/tools/site_and_regional/??-???.postad/ +/tools/site_and_regional/??-???.*.postad/ +/tools/site_and_regional/??-???.transient/ +/tools/site_and_regional/??-???.*.transient/ /tools/site_and_regional/archive/ # build output diff --git a/.gitmodules b/.gitmodules index bc66fe1682..f8bb22ffd1 100644 --- a/.gitmodules +++ b/.gitmodules @@ -28,7 +28,7 @@ [submodule "fates"] path = src/fates url = https://github.com/NGEET/fates -fxtag = sci.1.80.4_api.37.0.0 +fxtag = sci.1.81.0_api.37.1.0 fxrequired = AlwaysRequired # Standard Fork to compare to with "git fleximod test" to ensure personal forks aren't committed fxDONOTUSEurl = https://github.com/NCAR/fates-release @@ -36,7 +36,7 @@ fxDONOTUSEurl = https://github.com/NCAR/fates-release [submodule "cism"] path = components/cism url = https://github.com/ESCOMP/CISM-wrapper -fxtag = cismwrap_2_2_002 +fxtag = cismwrap_2_2_005 fxrequired = ToplevelRequired # Standard Fork to compare to with "git fleximod test" to ensure personal forks aren't committed fxDONOTUSEurl = https://github.com/ESCOMP/CISM-wrapper @@ -44,7 +44,7 @@ fxDONOTUSEurl = https://github.com/ESCOMP/CISM-wrapper [submodule "rtm"] path = components/rtm url = https://github.com/ESCOMP/RTM -fxtag = rtm1_0_84 +fxtag = rtm1_0_86 fxrequired = ToplevelRequired # Standard Fork to compare to with "git fleximod test" to ensure personal forks aren't committed fxDONOTUSEurl = https://github.com/ESCOMP/RTM @@ -52,7 +52,7 @@ fxDONOTUSEurl = https://github.com/ESCOMP/RTM [submodule "mosart"] path = components/mosart url = https://github.com/ESCOMP/MOSART -fxtag = mosart1.1.06 +fxtag = mosart1.1.08 fxrequired = ToplevelRequired # Standard Fork to compare to with "git fleximod test" to ensure personal forks aren't committed fxDONOTUSEurl = https://github.com/ESCOMP/MOSART @@ -60,7 +60,7 @@ fxDONOTUSEurl = https://github.com/ESCOMP/MOSART [submodule "mizuRoute"] path = components/mizuRoute url = https://github.com/ESCOMP/mizuRoute -fxtag = cesm-coupling.n02_v2.1.3 +fxtag = cesm-coupling.n03_v2.2.0 fxrequired = ToplevelRequired # Standard Fork to compare to with "git fleximod test" to ensure personal forks aren't committed fxDONOTUSEurl = https://github.com/ESCOMP/mizuRoute @@ -68,7 +68,7 @@ fxDONOTUSEurl = https://github.com/ESCOMP/mizuRoute [submodule "ccs_config"] path = ccs_config url = https://github.com/ESMCI/ccs_config_cesm.git -fxtag = ccs_config_cesm1.0.10 +fxtag = ccs_config_cesm1.0.20 fxrequired = ToplevelRequired # Standard Fork to compare to with "git fleximod test" to ensure personal forks aren't committed fxDONOTUSEurl = https://github.com/ESMCI/ccs_config_cesm.git @@ -76,7 +76,7 @@ fxDONOTUSEurl = https://github.com/ESMCI/ccs_config_cesm.git [submodule "cime"] path = cime url = https://github.com/ESMCI/cime -fxtag = cime6.1.49 +fxtag = cime6.1.59 fxrequired = ToplevelRequired # Standard Fork to compare to with "git fleximod test" to ensure personal forks aren't committed fxDONOTUSEurl = https://github.com/ESMCI/cime @@ -84,7 +84,7 @@ fxDONOTUSEurl = https://github.com/ESMCI/cime [submodule "cmeps"] path = components/cmeps url = https://github.com/ESCOMP/CMEPS.git -fxtag = cmeps1.0.32 +fxtag = cmeps1.0.33 fxrequired = ToplevelRequired # Standard Fork to compare to with "git fleximod test" to ensure personal forks aren't committed fxDONOTUSEurl = https://github.com/ESCOMP/CMEPS.git @@ -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.6 +fxtag = share1.1.7 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/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md deleted file mode 100644 index cd478587d1..0000000000 --- a/CODE_OF_CONDUCT.md +++ /dev/null @@ -1,107 +0,0 @@ -# Contributor Code of Conduct -_The Contributor Code of Conduct is for participants in our software projects and community._ - -## Our Pledge -We, as contributors, creators, stewards, and maintainers (participants), of **Community Terrestrial Systems Model (CTSM)** pledge to make participation in -our software, system or hardware project and community a safe, productive, welcoming and inclusive experience for everyone. -All participants are required to abide by this Code of Conduct. -This includes respectful treatment of everyone regardless of age, body size, disability, ethnicity, gender identity or expression, -level of experience, nationality, political affiliation, veteran status, pregnancy, genetic information, physical appearance, race, -religion, or sexual orientation, as well as any other characteristic protected under applicable US federal or state law. - -## Our Standards -Examples of behaviors that contribute to a positive environment include: - -* All participants are treated with respect and consideration, valuing a diversity of views and opinions -* Be considerate, respectful, and collaborative -* Communicate openly with respect for others, critiquing ideas rather than individuals and gracefully accepting criticism -* Acknowledging the contributions of others -* Avoid personal attacks directed toward other participants -* Be mindful of your surroundings and of your fellow participants -* Alert UCAR staff and suppliers/vendors if you notice a dangerous situation or someone in distress -* Respect the rules and policies of the project and venue - -Examples of unacceptable behavior include, but are not limited to: - -* Harassment, intimidation, or discrimination in any form -* Physical, verbal, or written abuse by anyone to anyone, including repeated use of pronouns other than those requested -* Unwelcome sexual attention or advances -* Personal attacks directed at other guests, members, participants, etc. -* Publishing others' private information, such as a physical or electronic address, without explicit permission -* Alarming, intimidating, threatening, or hostile comments or conduct -* Inappropriate use of nudity and/or sexual images -* Threatening or stalking anyone, including a participant -* Other conduct which could reasonably be considered inappropriate in a professional setting - -## Scope -This Code of Conduct applies to all spaces managed by the Project whether they be physical, online or face-to-face. -This includes project code, code repository, associated web pages, documentation, mailing lists, project websites and wiki pages, -issue tracker, meetings, telecons, events, project social media accounts, and any other forums created by the project team which the -community uses for communication. -In addition, violations of this Code of Conduct outside these spaces may affect a person's ability to participate within them. -Representation of a project may be further defined and clarified by project maintainers. - -## Community Responsibilities -Everyone in the community is empowered to respond to people who are showing unacceptable behavior. -They can talk to them privately or publicly. -Anyone requested to stop unacceptable behavior is expected to comply immediately. -If the behavior continues concerns may be brought to the project administrators or to any other party listed in the -[Reporting](#reporting) section below. - -## Project Administrator Responsibilities -Project administrators are responsible for clarifying the standards of acceptable behavior and are encouraged to model appropriate -behavior and provide support when people in the community point out inappropriate behavior. -Project administrator(s) are normally the ones that would be tasked to carry out the actions in the [Consequences](#consequences) -section below. - -Project administrators are also expected to keep this Code of Conduct updated with the main one housed at UCAR, as listed below in -the [Attribution](#attribution) section. - -## Reporting -Instances of unacceptable behavior can be brought to the attention of the project administrator(s) who may take any action as -outlined in the [Consequences](#consequences) section below. -However, making a report to a project administrator is not considered an 'official report' to UCAR. - -Instances of unacceptable behavior may also be reported directly to UCAR pursuant to [UCAR's Harassment Reporting and Complaint -Procedure](https://www2.fin.ucar.edu/procedures/hr/harassment-reporting-and-complaint-procedure), or anonymously through [UCAR's -EthicsPoint Hotline](https://www2.fin.ucar.edu/ethics/anonymous-reporting). - -Complaints received by UCAR will be handled pursuant to the procedures outlined in UCAR's Harassment Reporting and Complaint -Procedure. -Complaints to UCAR will be held as confidential as practicable under the circumstances, and retaliation against a person who -initiates a complaint or an inquiry about inappropriate behavior will not be tolerated. - -Any Contributor can use these reporting methods even if they are not directly affiliated with UCAR. -The Frequently Asked Questions (FAQ) page for reporting is [here](https://www2.fin.ucar.edu/procedures/hr/reporting-faqs). - -## Consequences -Upon receipt of a complaint, the project administrator(s) may take any action deemed necessary and appropriate under the -circumstances. -Such action can include things such as: removing, editing, or rejecting comments, commits, code, wiki edits, email, issues, and -other contributions that are not aligned to this Code of Conduct, or banning temporarily or permanently any contributor for other -behaviors that are deemed inappropriate, threatening, offensive, or harmful. -Project administrators also have the right to report violations to UCAR HR and/or UCAR's Office of Diversity, Equity and Inclusion -(ODEI), as well as a participant's home institution and/or law enforcement. -In the event an incident is reported to UCAR, UCAR will follow its Harassment Reporting and Complaint Procedure. - -## Process for Changes -All UCAR managed projects are required to adopt this Contributor Code of Conduct. -Adoption is assumed even if not expressly stated in the repository. -Projects should fill in sections where prompted with project-specific information, including, project name and adoption date. - -Projects that adopt this Code of Conduct need to stay up to date with UCAR's Contributor Code of Conduct, linked with a DOI in the -[Attribution](#attribution) section below. -Projects can make limited substantive changes to the Code of Conduct, however, the changes must be limited in scope and may not -contradict the UCAR Contributor Code of Conduct. - -## Attribution -This Code of Conduct was originally adapted from the [Contributor Covenant](http://contributor-covenant.org/version/1/4), version -1.4. -We then aligned it with the UCAR Participant Code of Conduct, which also borrows from the American Geophysical Union (AGU) Code of -Conduct. -The UCAR Participant Code of Conduct applies to both UCAR employees as well as participants in activities run by UCAR. -The original version of this for all software projects that have strong management from UCAR or UCAR staff is available on the UCAR -website at https://doi.org/10.5065/6w2c-a132. -The date that it was adopted by this project was **Feb/13/2018**. -When responding to complaints, UCAR HR and ODEI will do so based on the latest published version. -Therefore, any project-specific changes should follow the [Process for Changes](#process-for-changes) section above. diff --git a/WhatsNewInCTSM5.3.md b/WhatsNewInCTSM5.3.md index b1f753081b..4717deac30 100644 --- a/WhatsNewInCTSM5.3.md +++ b/WhatsNewInCTSM5.3.md @@ -1,53 +1,108 @@ -Purpose and description of changes since ctsm5.2.005 ----------------------------------------------------- - -Bring in updates needed for the CESM3.0 science capability/functionality "chill". Most importantly bringing -in: CN Matrix to speed up spinup for the BGC model, updated surface datasets, updated Leung 2023 dust emissions, -explicit Air Conditioning for the Urban model, updates to crop calendars. For clm6_0 physics these options are now -default turned on in addition to Sturm snow, and excess ice. - -Changes to CTSM Infrastructure: -=============================== - - - manage_externals removed and replaced by git-fleximod - - Ability to handle CAM7 in LND_TUNING_MODE - -Changes to CTSM Answers: -======================== - - Changes to defaults for clm6_0 physics: - - Urban explicit A/C turned on - - Snow thermal conductivity is now Sturm_1997 - - New IC file for f09 1850 - - New crop calendars - - Dust emissions is now Leung_2023 - - Excess ice is turned on - - Updates to MEGAN for BVOC's - - Updates to BGC fire method - - Changes for all physics versions: - - - Parameter files updated - - FATES parameter file updated - - Glacier region 1 is now undefined - - Update in FATES transient Land use - - Pass active glacier (CISM) runoff directly to river model (MOSART) - - Add the option for using matrix for Carbon/Nitrogen BGC spinup - -New surface datasets: -===================== - -- With new surface datasets the following GLC fields have region "1" set to UNSET: - glacier_region_behavior, glacier_region_melt_behavior, glacier_region_ice_runoff_behavior -- Updates to allow creating transient landuse timeseries files going back to 1700. -- Fix an important bug on soil fields that was there since ctsm5.2.0. This results in mksurfdata_esmf now giving identical answers with a change in number of processors, as it should. -- Add in creation of ne0np4.POLARCAP.ne30x4 surface datasets. -- Add version to the surface datasets. -- Remove the --hires_pft option from mksurfdata_esmf as we don't have the datasets for it. -- Remove VIC fields from surface datasets. - -New input datasets to mksurfdata_esmf: -====================================== - -- Updates in PFT/LAI/soil-color raw datasets (now from the TRENDY2024 timeseries that ends in 2023), as well as two fire datasets (AG fire, peatland), and the glacier behavior dataset. +# What's new in CTSM 5.3 (tag `ctsm5.3.021`) +## Purpose and description of changes since CTSM 5.2 (tag `ctsm5.2.005`) + +### New features + +* `manage_externals` replaced by [`git-fleximod`](https://github.com/ESMCI/git-fleximod/blob/main/README.md). ([PR \#2559](https://github.com/ESCOMP/CTSM/pull/2559)) +* No longer runs the 0th time step in first segment of startup and hybrid runs; branch and continue runs never had this 0th time step. ([PR \#2084](https://github.com/ESCOMP/CTSM/pull/2084)) +* New CN Matrix method speeds up spinup for the BGC model. ([PR \#640](https://github.com/ESCOMP/CTSM/pull/640); [Liao et al. 2023](https://agupubs.onlinelibrary.wiley.com/doi/10.1029/2023MS003625)). +* New `Leung_2023` dust emissions. ([PR \#1897](https://github.com/ESCOMP/CTSM/pull/1897); [Leung et al 2023](https://doi.org/10.5194/acp-23-6487-2023), [Leung et al. 2024](https://doi.org/10.5194/acp-24-2287-2024)) +* Explicit air conditioning for the urban model. ([PR \#2275](https://github.com/ESCOMP/CTSM/pull/2275); [Li et al. 2024](https://agupubs.onlinelibrary.wiley.com/share/NY4AYPREB8Y8BUDP7DXD?target=10.1029/2023MS004107)) +* FATES compsets can now be run with transient land use; off by default. ([PR \#2507](https://github.com/ESCOMP/CTSM/pull/2507)) +* Ability to handle CAM7 in `LND_TUNING_MODE`. ([PR \#2632](https://github.com/ESCOMP/CTSM/pull/2632)) + +### Answer changes + +Changes to defaults for `clm6_0` physics: + +* Urban explicit A/C turned on (links above). +* Snow thermal conductivity method is now `Sturm1997`. ([PR \#2348](https://github.com/ESCOMP/CTSM/pull/2348); see also [discussion \#1960](https://github.com/ESCOMP/CTSM/discussions/1960)) +* New initial conditions files for f09 ("1-degree" 1850, 2000), f19 (“2-degree” 1850), and ne30 (1850, 1979, 2000) resolutions. +* New crop calendars. ([PR \#2664](https://github.com/ESCOMP/CTSM/pull/2664); informed by [Rabin et al., 2023](https://gmd.copernicus.org/articles/16/7253/2023/gmd-16-7253-2023.html)) +* Dust emissions method is now `Leung_2023` (links above). +* Excess ice is turned on. ([PR \#1787](https://github.com/ESCOMP/CTSM/pull/1787)) +* Updates to MEGAN for BVOCs. ([PR \#2588](https://github.com/ESCOMP/CTSM/pull/2588)) +* New BGC fire method `li2024crujra`: Avoid crop fires during growing season; allow lightning ignitions in tropical closed forests; add effect of landscape fragmentation on ignitions and duration; recalibrate against GFED5 burned area and with CRU-JRA climate. ([PR \#2684](https://github.com/ESCOMP/CTSM/pull/2684), [PR \#2711](https://github.com/ESCOMP/CTSM/pull/2711), [PR \#2715](https://github.com/ESCOMP/CTSM/issues/2715)) + +Changes for all physics versions: + +* Parameters updated for CRU-JRA forcing: PPE-based modifications were made to the parameters `leafcn`, `slatop`, `froot_leaf`, `medlynslope`, and `kmax`. Lowers LAI and biomass in boreal and tropical forests, without reducing latent heat in the tropics. Affected PFTs: NET temperate, NET boreal, BET tropical, BDS boreal, C3 arctic grass. ([PR \#2500](https://github.com/ESCOMP/CTSM/pull/2500)) +* FATES parameter file updated (see section below). +* Pass active glacier (CISM) runoff directly to river model (MOSART) ([MOSART PR \#94](https://github.com/ESCOMP/MOSART/pull/94)) +* New surface datasets and landuse timeseries files (see section below). +* CNMatrix is new default spinup method (links above). + +### Heads up + +* Small glacier changes mean that you can’t use a 5.3 surface dataset with pre-5.3 code and vice versa anymore. (Merged with [PR \#2500](https://github.com/ESCOMP/CTSM/pull/2500)) +* Updates the definition of history variable “time” from *end* of `time_bounds` to *middle* of `time_bounds`. ([PR \#2838](https://github.com/ESCOMP/CTSM/pull/2838); see section below) +* Standardizes history variable attributes and a history dimension name. ([PR \#2052](https://github.com/ESCOMP/CTSM/pull/2052); see section below) + +## + +## Additional detail + +### Changes related to time and history files + +(Note that the same information in this section applies to MOSART and RTM.) + +Startup and hybrid runs no longer run the 0th time step, consistent with the same change in CAM. (Branch and continue runs never had this 0th time step.) This means you will not get an extraneous initial history file anymore. In some circumstances this may also affect the names of history files. + +In most cases, the history `time` variable is now defined as the middle of a history file’s `time_bounds` instead of the end, for consistency with the same change in CAM. The exception is if you specify `hist_avgflag_pertape = 'I'` for that file, in which case it will be treated as an “instantaneous” file. Instantaneous history files (a) have their `time` coordinate set to the end of the last timestep (as did all history files before this tag) and (b) do not include `time_bounds`. + +The history dimension name `hist_interval` (of output variable `time_bounds`) is standardized to be `nbnd`. History variables `time_bounds`, `mcdate`, `mcsec`, `mdcur`, and `mscur` are standardized to include the calendar attribute. + +### New surface datasets and landuse timeseries files ([PR \#2500](https://github.com/ESCOMP/CTSM/pull/2500)) + +* Transient landuse timeseries files going back to 1700 now possible (and made for f09). +* Fix an important bug on soil fields that was there since `ctsm5.2.0`. This has the side effect of `mksurfdata_esmf` now giving identical answers with a change in number of processors, as it should. ([Issue \#2744](https://github.com/ESCOMP/CTSM/issues/2744)) +* Surface datasets now provided for the `ne0np4.POLARCAP.ne30x4` grid. ([PR \#2716](https://github.com/ESCOMP/CTSM/pull/2716), [issue \#2720](https://github.com/ESCOMP/CTSM/issues/2720)) +* Surface datasets now have their version number embedded to prevent mismatch of surface dataset and CTSM version. ([Issue \#2723](https://github.com/ESCOMP/CTSM/issues/2723)) +* Remove outdated hydrology `VIC` (Variable Infiltration Capacity Hydrology model) fields from surface datasets. +* Updates to input datasets: + * PFT/LAI/soil-color raw datasets; now from the TRENDY2024 timeseries that ends in 2023. (Issues [\#2570](https://github.com/ESCOMP/CTSM/issues/2570) and [\#2452](https://github.com/ESCOMP/CTSM/issues/2452)) + * Two fire datasets: crop fire peak month and peatland fraction. (Issue [\#2618](https://github.com/ESCOMP/CTSM/issues/2618)) + * Glacier behavior dataset (related to how non-Greenland glaciers are handled). (Issue [\#423](https://github.com/ESCOMP/CTSM/issues/423)) + +### Changes to FATES parameter file + +* [PR \#2507](https://github.com/ESCOMP/CTSM/pull/2507) ([ctsm5.2.013](https://github.com/ESCOMP/CTSM/releases/tag/ctsm5.2.013)) / [FATES PR \#1116](https://github.com/NGEET/fates/pull/1116) ([sci.1.77.1\_api.36.0.0](https://github.com/NGEET/fates/releases/tag/sci.1.77.0_api.36.0.0)) + * Adds new parameters for new land use harvest mode and land use by PFT capabilities +* [PR \#2700](https://github.com/ESCOMP/CTSM/pull/2700) ([ctsm5.3.003](https://github.com/ESCOMP/CTSM/releases/tag/ctsm5.3.003)) / [FATES PR \#1255](https://github.com/NGEET/fates/pull/1255) ([sci.1.78.3\_api.36.1.0](https://github.com/NGEET/fates/releases/tag/sci.1.78.3_api.36.1.0)) + * Adds two arctic shrub PFTs, increasing the number of default PFTs to 14 and update arctic grass parameters ([FATES PR\#1236](https://github.com/NGEET/fates/pull/1236)) + * Splits `fates_turnover_leaf` parameter into canopy and understory specific turnover rates and provides new values for both ([FATES PR\#1136](https://github.com/NGEET/fates/pull/1136)) + * Updates grass allometry parameters ([FATES PR\#1206](https://github.com/NGEET/fates/pull/1206)) + * Changes the prescribe nutrient uptake defaults from 1 to 0 for all PFTs (only relevant to ELM-FATES) + +### Changes to rpointer files + +The rpointer files are simple text files that CESM uses to keep track of how far simulations have progressed, pointing to the filename of the latest restart file for that component. There is one such file for each component, so for CTSM `I` cases that's `lnd`, `cpl`, and `atm` (and `rof` if it's active). Normally, when the user is just extending the length of simulations, there’s no need to worry about these files. + +However, if there was a problem when a simulation shut down, it's possible that different components will have mismatched restarts and rpointer files. In the past, this meant figuring out what restart file should be pointed to in each component rpointer file and correcting it by hand in an editor. There was only the final set of rpointer files that was kept for a case. + +Now, with this update, the `lnd`, `cpl`, and `atm` rpointer files have the simulation date in the filenames, so it's easy to spot if the restarts are mismatched for one of the components. Also, since there are matching rpointer files for each time restarts are created, it's now easier to (a) make sure restarts and rpointer files are all correctly matched and (b) for a user to take a set of restarts and matching rpointer files to start up from for any part of an existing simulation. This means you don't have to hand-edit the rpointer files, making sure you don't make a mistake when you do. + +Old rpointer filenames: + +* `rpointer.atm` +* `rpointer.cpl` +* `rpointer.lnd` + +New names: + +* `rpointer.atm.YYYY-MM-DD-SSSSS` +* `rpointer.cpl.YYYY-MM-DD-SSSSS` +* `rpointer.lnd.YYYY-MM-DD-SSSSS` + +Where `YYYY-MM-DD-SSSSS` is the year, month, day, and seconds into the day for the simulation timestamp. For example, `rpointer.lnd.2000-01-01-00000` for a rpointer file for starting at midnight (beginning of the day) on January 1st, 2000\. + +Note that this is backwards-compatible, so for all the components you can use either the new or old format for the rpointer filenames. Thus, if you are restarting from an existing case before `ctsm5.3.016`, you CAN use the rpointer filenames that don't have the timestamps in the name. + +## Simulations supporting this release + +- f19 `Clm60Bgc` 16pft: [https://github.com/NCAR/LMWG\_dev/issues/70](https://github.com/NCAR/LMWG_dev/issues/70) +- f09 with `Clm60BgcCrop`: [https://github.com/NCAR/LMWG\_dev/issues/69](https://github.com/NCAR/LMWG_dev/issues/69) +- ne30 with `Clm60BgcCrop`: [https://github.com/NCAR/LMWG\_dev/issues/68](https://github.com/NCAR/LMWG_dev/issues/68) + +Note: Dust emissions in CTSM 5.3 will be different from the above simulations because of a tuning update that came in after those. \ No newline at end of file diff --git a/bld/CLMBuildNamelist.pm b/bld/CLMBuildNamelist.pm index e846c23d9e..53fff12861 100755 --- a/bld/CLMBuildNamelist.pm +++ b/bld/CLMBuildNamelist.pm @@ -2122,17 +2122,21 @@ sub setup_logic_roughness_methods { my ($opts, $nl_flags, $definition, $defaults, $nl, $physv) = @_; add_default($opts, $nl_flags->{'inputdata_rootdir'}, $definition, $defaults, $nl, 'z0param_method', - 'phys'=>$nl_flags->{'phys'} ); + 'phys'=>$nl_flags->{'phys'} , 'use_fates'=>$nl_flags->{'use_fates'}); my $var = remove_leading_and_trailing_quotes( $nl->get_value("z0param_method") ); if ( $var ne "Meier2022" && $var ne "ZengWang2007" ) { $log->fatal_error("$var is incorrect entry for the namelist variable z0param_method; expected Meier2022 or ZengWang2007"); } my $phys = $physv->as_string(); - if ( $phys eq "clm4_5" || $phys eq "clm5_0" ) { - if ( $var eq "Meier2022" ) { + if ( $var eq "Meier2022" ) { + if ( $phys eq "clm4_5" || $phys eq "clm5_0" ) { $log->fatal_error("z0param_method = $var and phys = $phys, but this method has been tested only with clm6_0 and later versions; to use with earlier versions, disable this error, and add Meier2022 parameters to the corresponding params file"); } + # Make sure that fates and meier2022 are not both active due to issue #2932 + if ( &value_is_true($nl_flags->{'use_fates'}) ) { + $log->fatal_error("z0param_method = $var and use_fates currently are not compatible. Please update the z0param_method to ZengWang2007. See issue #2932 for more information.") + } } } #------------------------------------------------------------------------------- diff --git a/bld/namelist_files/namelist_defaults_ctsm.xml b/bld/namelist_files/namelist_defaults_ctsm.xml index e6aaa53b62..62353e310b 100644 --- a/bld/namelist_files/namelist_defaults_ctsm.xml +++ b/bld/namelist_files/namelist_defaults_ctsm.xml @@ -532,7 +532,7 @@ attributes from the config_cache.xml file (with keys converted to upper-case). -lnd/clm2/paramdata/fates_params_api.36.1.0_14pft_c241003.nc +lnd/clm2/paramdata/fates_params_api.37.1.0_14pft_c250214.nc @@ -540,7 +540,7 @@ attributes from the config_cache.xml file (with keys converted to upper-case). ZengWang2007 -Meier2022 +Meier2022 .true. .false. diff --git a/bld/unit_testers/build-namelist_test.pl b/bld/unit_testers/build-namelist_test.pl index d42487cfdb..a3630f3286 100755 --- a/bld/unit_testers/build-namelist_test.pl +++ b/bld/unit_testers/build-namelist_test.pl @@ -163,10 +163,10 @@ sub cat_and_create_namelistinfile { # # Figure out number of tests that will run # -my $ntests = 3263; +my $ntests = 3264; if ( defined($opts{'compare'}) ) { - $ntests += 1979; + $ntests += 1980; } plan( tests=>$ntests ); @@ -1185,6 +1185,10 @@ sub cat_and_create_namelistinfile { namelst=>"use_hydrstress=.true.", phys=>"clm5_0", }, + "useMeierwithFATES" =>{ options=>"-bgc fates -envxml_dir . -no-megan", + namelst=>"z0param_method=Meier2022", + phys=>"clm5_0", + }, "noanthro_w_crop" =>{ options=>"-envxml_dir . -res 0.9x1.25 -bgc bgc -crop -use_case 1850_noanthro_control", namelst=>"", phys=>"clm5_0", diff --git a/ccs_config b/ccs_config index 6e77e7ee17..77fd32c6c4 160000 --- a/ccs_config +++ b/ccs_config @@ -1 +1 @@ -Subproject commit 6e77e7ee1748a4d3b2497d7ad3943498a7cec2aa +Subproject commit 77fd32c6c4b57bb195239753f4c7838f05c1dfe8 diff --git a/cime b/cime index b2f271b174..addf9e67ce 160000 --- a/cime +++ b/cime @@ -1 +1 @@ -Subproject commit b2f271b1742533715a05701b1bdd80a24bb2ad77 +Subproject commit addf9e67ceeb0e056de33ee793d67491176dd3a5 diff --git a/cime_config/SystemTests/rxcropmaturity.py b/cime_config/SystemTests/rxcropmaturity.py index fb254c408f..a0eced83c5 100644 --- a/cime_config/SystemTests/rxcropmaturity.py +++ b/cime_config/SystemTests/rxcropmaturity.py @@ -106,7 +106,7 @@ def __init__(self, case): # Which conda environment should we use? self._get_conda_env() - def _run_phase(self, skip_gen=False): + def _run_phase(self, skip_gen=False, h1_inst=False): # Modeling this after the SSP test, we create a clone to be the case whose outputs we don't # want to be saved as baseline. @@ -129,7 +129,7 @@ def _run_phase(self, skip_gen=False): self._set_active_case(case_gddgen) # Set up stuff that applies to both tests - self._setup_all() + self._setup_all(h1_inst) # Add stuff specific to GDD-Generating run logger.info("RXCROPMATURITY log: modify user_nl files: generate GDDs") @@ -196,7 +196,7 @@ def _run_phase(self, skip_gen=False): self._set_active_case(case_rxboth) # Set up stuff that applies to both tests - self._setup_all() + self._setup_all(h1_inst) # Add stuff specific to Prescribed Calendars run logger.info("RXCROPMATURITY log: modify user_nl files: Prescribed Calendars") @@ -264,7 +264,7 @@ def _get_rx_dates(self): logger.error(error_message) raise RuntimeError(error_message) - def _setup_all(self): + def _setup_all(self, h1_inst): logger.info("RXCROPMATURITY log: _setup_all start") # Get some info @@ -274,7 +274,7 @@ def _setup_all(self): # Set sowing dates file (and other crop calendar settings) for all runs logger.info("RXCROPMATURITY log: modify user_nl files: all tests") - self._modify_user_nl_allruns() + self._modify_user_nl_allruns(h1_inst) logger.info("RXCROPMATURITY log: _setup_all done") # Make a surface dataset that has every crop in every gridcell @@ -399,7 +399,7 @@ def _run_check_rxboth_run(self, skip_gen): tool_path, ) - def _modify_user_nl_allruns(self): + def _modify_user_nl_allruns(self, h1_inst): nl_additions = [ "cropcals_rx = .true.", "cropcals_rx_adapt = .false.", @@ -417,6 +417,8 @@ def _modify_user_nl_allruns(self): "hist_type1d_pertape(2) = 'PFTS'", "hist_dov2xy(2) = .false.", ] + if h1_inst: + nl_additions.append("hist_avgflag_pertape(2) = 'I'") self._append_to_user_nl_clm(nl_additions) def _run_generate_gdds(self, case_gddgen): diff --git a/cime_config/SystemTests/rxcropmaturityinst.py b/cime_config/SystemTests/rxcropmaturityinst.py new file mode 100644 index 0000000000..bf8bf7750b --- /dev/null +++ b/cime_config/SystemTests/rxcropmaturityinst.py @@ -0,0 +1,6 @@ +from rxcropmaturity import RXCROPMATURITYSHARED + + +class RXCROPMATURITYINST(RXCROPMATURITYSHARED): + def run_phase(self): + self._run_phase(h1_inst=True) diff --git a/cime_config/SystemTests/rxcropmaturityskipgeninst.py b/cime_config/SystemTests/rxcropmaturityskipgeninst.py new file mode 100644 index 0000000000..4cab9bd7c0 --- /dev/null +++ b/cime_config/SystemTests/rxcropmaturityskipgeninst.py @@ -0,0 +1,6 @@ +from rxcropmaturity import RXCROPMATURITYSHARED + + +class RXCROPMATURITYSKIPGENINST(RXCROPMATURITYSHARED): + def run_phase(self): + self._run_phase(skip_gen=True, h1_inst=True) diff --git a/cime_config/SystemTests/ssp.py b/cime_config/SystemTests/ssp.py index bd554aeae9..92cab0a961 100644 --- a/cime_config/SystemTests/ssp.py +++ b/cime_config/SystemTests/ssp.py @@ -81,7 +81,7 @@ def run_phase(self): ) refsec = "00000" - # obtain rpointer files and necessary restart files from short term archiving directory + # obtain necessary restart files from short term archiving directory rundir = self._case.get_value("RUNDIR") rest_path = os.path.join(dout_sr, "rest", "{}-{}".format(refdate, refsec)) @@ -96,9 +96,6 @@ def run_phase(self): else: os.symlink(item, link_name) - for item in glob.glob("{}/*rpointer*".format(rest_path)): - shutil.copy(item, rundir) - self._case.set_value("CLM_ACCELERATED_SPINUP", "off") self._case.set_value("RUN_TYPE", "hybrid") self._case.set_value("GET_REFCASE", False) diff --git a/cime_config/SystemTests/sspmatrixcn.py b/cime_config/SystemTests/sspmatrixcn.py index 29b6dce8e6..98a580a8db 100644 --- a/cime_config/SystemTests/sspmatrixcn.py +++ b/cime_config/SystemTests/sspmatrixcn.py @@ -13,6 +13,7 @@ Step 4: matrix Spinup off """ import shutil, glob, os, sys +from datetime import datetime if __name__ == "__main__": CIMEROOT = os.environ.get("CIMEROOT") @@ -24,6 +25,7 @@ else: from CIME.status import append_testlog +from CIME.case import Case from CIME.XML.standard_module_setup import * from CIME.SystemTests.system_tests_common import SystemTestsCommon from CIME.SystemTests.test_utils import user_nl_utils @@ -186,6 +188,26 @@ def append_user_nl(self, caseroot, n=0): caseroot=caseroot, component=self.comp, contents=contents_to_append ) + def run_indv(self, nstep, st_archive=True): + """ + Individual run of a given step + """ + suffix = "step{}".format(self.steps[nstep]) + if isinstance(self._case, Case): + super().run_indv(suffix, st_archive=True) + else: + caseroot = self._case.get_value("CASEROOT") + dout_sr = self._case.get_value("DOUT_S_ROOT") + rest_r = os.path.join(dout_sr, "rest") + nyear = 1851 + nstep + rundate = "%s-01-01-00000" % nyear + restdir = os.path.join(rest_r, rundate) + os.mkdir(restdir) + rpoint = os.path.join(restdir, "rpointer.clm." + rundate) + os.mknod(rpoint) + rpoint = os.path.join(restdir, "rpointer.cpl." + rundate) + os.mknod(rpoint) + def run_phase(self): "Run phase" @@ -225,6 +247,7 @@ def run_phase(self): self.append_user_nl(clone_path, n) dout_sr = clone.get_value("DOUT_S_ROOT") + ninst = self._case.get_value("NINST") self._skip_pnl = False # @@ -247,14 +270,24 @@ def run_phase(self): os.makedirs(rundir) os.symlink(item, linkfile) - for item in glob.glob("{}/*rpointer*".format(rest_path)): - shutil.copy(item, rundir) + # For a branch the cpl rpointer file needs to be handled + if self.runtyp[n] == "branch": + + drvrest = "rpointer.cpl" + if ninst > 1: + drvrest += "_0001" + drvrest += rest_time + self._set_drv_restart_pointer(drvrest) + try: + shutil.copy(drvrest, rundir) + except shutil.SameFileError: + pass # # Run the case (Archiving on) # self._case.flush() - self.run_indv(suffix="step{}".format(self.steps[n]), st_archive=True) + self.run_indv(nstep=n, st_archive=True) # # Get the reference case from this step for the next step @@ -267,6 +300,7 @@ def run_phase(self): ) refsec = "00000" rest_path = os.path.join(dout_sr, "rest", "{}-{}".format(refdate, refsec)) + rest_time = "." + refdate + "-" + refsec # # Last step in original case @@ -292,10 +326,22 @@ def run_phase(self): linkfile = os.path.join(rundir, os.path.basename(item)) if os.path.exists(linkfile): os.remove(linkfile) + expect(True, os.path.exists(item), "expected file does NOT exist = " + item) os.symlink(item, linkfile) - for item in glob.glob("{}/*rpointer*".format(rest_path)): - shutil.copy(item, rundir) + # For a branch the cpl rpointer file needs to be handled + if self.runtyp[n] == "branch": + + drvrest = "rpointer.cpl" + if ninst > 1: + drvrest += "_0001" + drvrest += rest_time + + self._set_drv_restart_pointer(drvrest) + try: + shutil.copy(os.path.join(rest_path, drvrest), rundir) + except shutil.SameFileError: + pass self.append_user_nl(clone_path, n) # @@ -306,66 +352,4 @@ def run_phase(self): # Run the case (short term archiving is off) # self._case.flush() - self.run_indv(suffix="step{}".format(self.steps[n]), st_archive=False) - - -# -# Unit testing for above -# -import unittest -from CIME.case import Case -from CIME.utils import _LessThanFilter -from argparse import RawTextHelpFormatter - - -class test_ssp_matrixcn(unittest.TestCase): - def setUp(self): - self.ssp = SSPMATRIXCN() - - def test_logger(self): - # Test the logger - stream_handler = logging.StreamHandler(sys.stdout) - logger.addHandler(stream_handler) - logger.level = logging.DEBUG - logger.info("nyr_forcing = {}".format(self.ssp.nyr_forcing)) - for n in range(self.ssp.n_steps()): - self.ssp.__logger__(n) - if self.ssp.spin[n] == "sasu": - logger.info(" SASU spinup is .true.") - if self.ssp.sasu[n] != -999: - logger.info(" nyr_sasu = {}".format(self.ssp.sasu[n])) - if self.ssp.iloop[n] != -999: - logger.info(" iloop_avg = {}".format(self.ssp.iloop[n])) - - logger.info("Total number of years {}".format(self.ssp.total_years())) - logger.removeHandler(stream_handler) - - def test_n_steps(self): - self.assertTrue(self.ssp.n_steps() == 3) - - def test_valid_n(self): - for n in range(self.ssp.n_steps()): - self.ssp.check_n(n) - - def test_negative_n(self): - self.assertRaises(SystemExit, self.ssp.check_n, -1) - - def test_n_too_big(self): - self.assertRaises(SystemExit, self.ssp.check_n, self.ssp.n_steps()) - - def test_append_user_nl_step2(self): - ufile = "user_nl_clm" - if not os.path.exists(ufile): - os.mknod(ufile) - else: - expect(0, ufile + " file already exists, not overwritting it") - - self.ssp.append_user_nl(caseroot=".", n=2) - print(ufile + " for step 2") - log = open(ufile, "r").read() - print(log) - os.remove(ufile) - - -if __name__ == "__main__": - unittest.main() + self.run_indv(nstep=n, st_archive=False) diff --git a/cime_config/config_tests.xml b/cime_config/config_tests.xml index 12859b9131..ee80087a08 100644 --- a/cime_config/config_tests.xml +++ b/cime_config/config_tests.xml @@ -145,6 +145,16 @@ This defines various CTSM-specific system tests $STOP_N + + As RXCROPMATURITY but ensure instantaneous h1. Can be removed once instantaneous and other variables are on separate files. + 1 + FALSE + FALSE + never + $STOP_OPTION + $STOP_N + + As RXCROPMATURITY but don't actually generate GDDs. Allows short testing with existing GDD inputs. 1 @@ -155,6 +165,16 @@ This defines various CTSM-specific system tests $STOP_N + + As RXCROPMATURITYSKIPGEN but ensure instantaneous h1. Can be removed once instantaneous and other variables are on separate files. + 1 + FALSE + FALSE + never + $STOP_OPTION + $STOP_N + + - - - - FAIL - #2916 - - - - - - - FAIL - #2916 - - - - - - - FAIL - #2916 - - - - - - - FAIL - #2916 - - - - - - - FAIL - #2916 - - - - - - - FAIL - #2916 - - - - - - - FAIL - #2916 - - - - - - - FAIL - #2916 - - - - - - - FAIL - #2916 - - - - - - - FAIL - #2916 - - - - - - - FAIL - #2916 - - - - - - - FAIL - #2916 - - - - - - - FAIL - #2916 - - - - - - - FAIL - #2916 - - - - - - - FAIL - #2916 - - - - - - - FAIL - #2916 - - - - - - - FAIL - #2916 - - - - - - - FAIL - #2916 - - - - - - - FAIL - #2916 - - - - - - - FAIL - #2916 - - - - - - - FAIL - #2916 - - - - - - - FAIL - #2916 - - - - - - FAIL - #2916 - - - - - FAIL @@ -423,23 +186,12 @@ - - FAIL - #2454 - FAIL #2325 - - - FAIL - #2454 - - - FAIL diff --git a/cime_config/testdefs/testlist_clm.xml b/cime_config/testdefs/testlist_clm.xml index 200468cf53..2845bf8d7c 100644 --- a/cime_config/testdefs/testlist_clm.xml +++ b/cime_config/testdefs/testlist_clm.xml @@ -12,6 +12,7 @@ hillslope: Experimental test list used for the hillslope option of the model rxcropmaturity: Short tests to be run during development related to prescribed crop calendars matrixcn: Tests exercising the matrix-CN capability + aux_clm_mpi_serial: aux_clm tests using mpi-serial. Useful for redoing tests that failed due to https://github.com/ESCOMP/CTSM/issues/2916, after having replaced libraries/mpi-serial with a fresh copy. --> @@ -905,6 +906,7 @@ + @@ -1149,6 +1151,7 @@ + @@ -1158,6 +1161,7 @@ + @@ -1701,6 +1705,7 @@ + @@ -1718,6 +1723,7 @@ + @@ -1727,6 +1733,7 @@ + @@ -1736,6 +1743,7 @@ + @@ -1746,6 +1754,7 @@ + @@ -1755,6 +1764,7 @@ + @@ -1764,6 +1774,7 @@ + @@ -1774,6 +1785,7 @@ + @@ -1794,6 +1806,7 @@ + @@ -1868,6 +1881,7 @@ + @@ -1878,6 +1892,7 @@ + @@ -1890,6 +1905,7 @@ + @@ -2257,6 +2273,7 @@ + @@ -2266,9 +2283,12 @@ + + + @@ -2366,6 +2386,7 @@ + @@ -2377,7 +2398,9 @@ + + @@ -2388,8 +2411,10 @@ + + @@ -2401,7 +2426,9 @@ + + @@ -2412,7 +2439,9 @@ + + @@ -2436,8 +2465,10 @@ + + @@ -2449,8 +2480,10 @@ + + @@ -2481,6 +2514,7 @@ + @@ -2526,6 +2560,7 @@ + @@ -2672,6 +2707,7 @@ + @@ -2681,6 +2717,7 @@ + @@ -2732,6 +2769,7 @@ + @@ -2836,7 +2874,9 @@ + + @@ -2845,6 +2885,7 @@ + @@ -2863,6 +2904,7 @@ + @@ -3175,6 +3217,7 @@ + @@ -3417,6 +3460,7 @@ + @@ -3427,6 +3471,7 @@ + @@ -3493,6 +3538,7 @@ + @@ -3603,6 +3649,17 @@ + + + + + + + + + + + @@ -3615,6 +3672,17 @@ + + + + + + + + + + + @@ -3637,6 +3705,7 @@ + diff --git a/cime_config/testdefs/testmods_dirs/clm/FatesColdAllVars/user_nl_clm b/cime_config/testdefs/testmods_dirs/clm/FatesColdAllVars/user_nl_clm index 92434df000..203aa8f717 100644 --- a/cime_config/testdefs/testmods_dirs/clm/FatesColdAllVars/user_nl_clm +++ b/cime_config/testdefs/testmods_dirs/clm/FatesColdAllVars/user_nl_clm @@ -8,7 +8,7 @@ use_fates_tree_damage = .true. hist_ndens = 1 hist_fincl1 = 'FATES_TLONGTERM', 'FATES_TGROWTH','FATES_SEEDS_IN_GRIDCELL_PF','FATES_SEEDS_OUT_GRIDCELL_PF','FATES_NCL_AP', -'FATES_NPATCH_AP','FATES_VEGC_AP','FATES_SECONDAREA_ANTHRODIST_AP','FATES_SECONDAREA_DIST_AP', +'FATES_NPATCH_AP','FATES_VEGC_AP','FATES_SECONDARY_ANTHRODISTAGE_AP','FATES_SECONDARY_AREA_AP', 'FATES_FUEL_AMOUNT_APFC','FATES_STOREC_TF_USTORY_SZPF','FATES_STOREC_TF_CANOPY_SZPF', 'FATES_CROWNAREA_CLLL','FATES_ABOVEGROUND_MORT_SZPF', 'FATES_ABOVEGROUND_PROD_SZPF','FATES_NPLANT_SZAP','FATES_NPLANT_CANOPY_SZAP', @@ -56,4 +56,5 @@ hist_fincl1 = 'FATES_TLONGTERM', 'FATES_PARSUN_CL','FATES_PARSHA_CL','FATES_LAISUN_CLLL','FATES_LAISHA_CLLL','FATES_LAISUN_CLLLPF', 'FATES_LAISHA_CLLLPF','FATES_PARPROF_DIR_CLLLPF','FATES_PARPROF_DIF_CLLLPF','FATES_LAISUN_CL','FATES_LAISHA_CL', 'FATES_PARPROF_DIR_CLLL','FATES_PARPROF_DIF_CLLL','FATES_NET_C_UPTAKE_CLLL','FATES_CROWNFRAC_CLLLPF', -'FATES_LBLAYER_COND_AP','FATES_STOMATAL_COND_AP' +'FATES_LBLAYER_COND_AP','FATES_STOMATAL_COND_AP','FATES_TLONGTERM','FATES_PRIMARY_AREA_AP','FATES_NPP_LU', +'FATES_GPP_LU' diff --git a/components/cism b/components/cism index c84cc9f5b3..41843ef8fe 160000 --- a/components/cism +++ b/components/cism @@ -1 +1 @@ -Subproject commit c84cc9f5b3103766a35d0a7ddd5e9dbd7deae762 +Subproject commit 41843ef8fed91fcf60e2ea217c4f6f2ee5133c5d diff --git a/components/cmeps b/components/cmeps index a91cedfe58..4b636c6f79 160000 --- a/components/cmeps +++ b/components/cmeps @@ -1 +1 @@ -Subproject commit a91cedfe58658a9fc391195481137a2d83372c25 +Subproject commit 4b636c6f794ca02d854d15c620e26644751b449b diff --git a/components/mizuRoute b/components/mizuRoute index 2ff305a029..362bee329b 160000 --- a/components/mizuRoute +++ b/components/mizuRoute @@ -1 +1 @@ -Subproject commit 2ff305a0292cb06789de6cfea7ad3cc0d6173493 +Subproject commit 362bee329bd6bf1fd45c8f36e006b9c4294bb8ca diff --git a/components/mosart b/components/mosart index a246344e9b..00a87c9084 160000 --- a/components/mosart +++ b/components/mosart @@ -1 +1 @@ -Subproject commit a246344e9b28e4bb42313749094fa20d45e2b212 +Subproject commit 00a87c9084af1af0d2b14d14e3d432f6808681f9 diff --git a/components/rtm b/components/rtm index 6899b55816..26e96f500b 160000 --- a/components/rtm +++ b/components/rtm @@ -1 +1 @@ -Subproject commit 6899b55816ee4d9b7cf983d74ba2997b97a13c4d +Subproject commit 26e96f500b9500b32a870db20eed6b1bd37587ea diff --git a/doc/ChangeLog b/doc/ChangeLog index f1cf556e41..b544c1e004 100644 --- a/doc/ChangeLog +++ b/doc/ChangeLog @@ -1,4 +1,456 @@ =============================================================== +Tag name: ctsm5.3.025 +Originator(s): glemieux (Gregory Lemieux, LBNL, glemieux@lbl.gov) +Date: Tue Feb 18 02:02:34 PM PST 2025 +One-line Summary: FATES default parameter file update + +Purpose and description of changes +---------------------------------- + +This tag updates the default parameter file for FATES bringing in a number of updates: + - adds parameters for land use grazing + - updates the FATES z0mr turbulence parameters for consistency with CLM + - adds FATES pft-dependent btran model switches for a forthcoming update + - updates the default hydro solver switch to Picard 2D + +This also updates the default FATES tag which includes two issue fixes and introduces +the aforementioned land use grazing capability. + +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 FATES#1316 -- z0mr parameters are all the same in the default parameter file + Fixes FATES#773 -- update the default hydraulics solver? + +Notes of particular relevance for users +--------------------------------------- +Changes to CTSM's user interface (e.g., new/renamed XML or namelist variables): + The FATES tag update includes new history outputs associated with land use grazing. + +Changes made to namelist defaults (e.g., changed parameter values): + The fates default parameter file has been updated. See the associated FATES pull + requests for more detail. + + +Testing summary: +---------------- + + regular tests (aux_clm: https://github.com/ESCOMP/CTSM/wiki/System-Testing-Guide#pre-merge-system-testing): + + derecho ----- + izumi ------- + + 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: Yes, for FATES tests only + + Summarize any changes to answers, i.e., + - what code configurations: + - what platforms/compilers: + - nature of change (roundoff; larger than roundoff/same climate; new climate): + + +Other details +------------- +List any git submodules updated (cime, rtm, mosart, cism, fates, etc.): + fates: sci.1.80.11_api.37.0.0 -> sci.1.81.0_api.37.1.0 + +Pull Requests that document the changes (include PR ids): +(https://github.com/ESCOMP/ctsm/pull) + https://github.com/ESCOMP/CTSM/pull/2965 + https://github.com/NGEET/fates/pull/1334 + +=============================================================== +=============================================================== +Tag name: ctsm5.3.024 +Originator(s): xinchang (Cathy Xinchang Li, U of Illinois - Urbana-Champaign) +Date: Tue Feb 11 09:56:24 MST 2025 +One-line Summary: Change choice of pressure in CLMU building energy model + +Purpose and description of changes +---------------------------------- + + In the CLMU Building Energy Model (BEM), the choice of pressure for calculating indoor air density is changed from standard pressure to bottom layer of atmosphere pressure of the corresponding grid cell. + +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 #2755 + +Notes of particular relevance for users +--------------------------------------- +Changes to documentation: + From the PR: I have updated the inline documentation in the code where it's needed + +Testing summary: +---------------- + [PASS means all tests PASS; OK means tests PASS other than expected fails.] + + 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: Yes + + Summarize any changes to answers, i.e., + - what code configurations: all + - what platforms/compilers: all + - nature of change: larger than roundoff/same climate + + From the PR: Some urban variables such as urban 2m air temperature, 2m relative humidity, urban air-conditioning and heating energy fluxes will change slightly. See #2755 for details. + + slevis from running aux_clm: I see larger differences that seem justified as simulations typically diverge from small initial differences. + +Other details +------------- +Pull Requests that document the changes (include PR ids): + https://github.com/ESCOMP/ctsm/pull/2758 + +=============================================================== +=============================================================== +Tag name: ctsm5.3.023 +Originator(s): afoster (Adrianna Foster) +Date: Sat Feb 8 08:36:00 MST 2025 +One-line Summary: merge b4b-dev + +Purpose and description of changes +---------------------------------- + +[Merging b4b-dev and ctsm5.3.022. Fixes SSP tests and adds PLUMBER tower run capabilities + + +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): +Fixes #2913 +Fixes #2920 +Fixes #1487 +Fixes #2739 +Fixes #2410 + +Notes of particular relevance for users +--------------------------------------- + +Changes to CTSM's user interface (e.g., new/renamed XML or namelist variables): Can now run PLUMBER towers + + +Testing summary: +---------------- + + regular tests (aux_clm: https://github.com/ESCOMP/CTSM/wiki/System-Testing-Guide#pre-merge-system-testing): + + derecho ----- OK + izumi ------- OK + + +Other details +------------- + +Pull Requests that document the changes (include PR ids): +- ESCOMP/CTSM#2957: ctsm5.3.023: Merge b4bdev 20250206 (https://github.com/ESCOMP/CTSM/pull/2957) + +=============================================================== +=============================================================== +Tag name: ctsm5.3.022 +Originator(s): glemieux (Gregory Lemieux, LBNL, glemieux@lbl.gov) +Date: Thu Feb 6 11:22:48 MST 2025 +One-line Summary: Update FATES namelist build to avoid Meier2022 + +Purpose and description of changes +---------------------------------- + +This tag brings in three updates, the primary of which is an update to the namelist +build to avoid running FATES with roughness method Meier2022 in light of recently +discovered incompatibility between FATES and this method. FATES will now used +ZengWang2007 for all run mode. + +The other two updates are fairly minor changes. One is a B4B change to the order in +which the use_fates_luh flag is passed to FATES. The other is an update to history +outputs for the FatesColdAllVars testmod per a recent FATES-side update. + + +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 +---------- + + #2932 - 'Meier 2022' z0 parameterization causes errors with FATES + +Testing summary: +---------------- + + [PASS means all tests PASS; OK means tests PASS other than expected fails.] + + build-namelist tests (if CLMBuildNamelist.pm has changed): + + derecho - PASS + + 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 + +If the tag used for baseline comparisons was NOT the previous tag, note that here: + + fates suite tested against fates-sci.1.80.10_api.37.0.0-ctsm5.3.021 + +Answer changes +-------------- + +Changes answers relative to baseline: Yes, only some FATES tests + + The FATES tag has been updated from sci.1.80.4_api.37.0.0 to sci.1.80.11_api.37.0.0, + which includes a number of bug fixes some of which result in changes to some baselines. + Additional, all tests using Clm60Fates based compsets have DIFFs due to the roughness + method update. + +Other details +------------- +List any git submodules updated (cime, rtm, mosart, cism, fates, etc.): + fates: sci.1.80.4_api.37.0.0 -> sci.1.80.11_api.37.0.0 + +Pull Requests that document the changes (include PR ids): +(https://github.com/ESCOMP/ctsm/pull) + https://github.com/ESCOMP/CTSM/pull/2934 + https://github.com/ESCOMP/CTSM/pull/2936 + https://github.com/ESCOMP/CTSM/pull/2898 + https://github.com/NGEET/FATES/pull/1273 + +=============================================================== +=============================================================== +Tag name: ctsm5.3.021 +Originator(s): samrabin (Sam Rabin, UCAR/TSS) +Date: Wed 29 Jan 2025 04:21:40 PM MST +One-line Summary: Standardize time metadata (release tag for ctsm5.3) + +Purpose and description of changes +---------------------------------- + Standardizes a dimension name of output variable time_bounds from hist_interval to nbnd. + Standardizes attributes for time_bounds, mcdate, mcsec, mdcur, and mscur. + +Contributors +------------ + Also Bill Sacks and Erik Kluzek. + +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 #1693 + Fixes #2923 + Resolves https://github.com/ESCOMP/MOSART/pull/66 + Resolves https://github.com/ESCOMP/RTM/pull/35 + +Notes of particular relevance for users +--------------------------------------- +Changes to documentation: + This is the release tag for ctsm5.3, so it includes the file WhatsNewInCTSM5.3.md. + +Notes of particular relevance for developers: +--------------------------------------------- +Caveats for developers (e.g., code that is duplicated that requires double maintenance): + Same or similar changes needed in all three: clm, rtm, mosart. + +Testing summary: +---------------- + + [PASS means all tests PASS; OK means tests PASS other than expected fails.] + + build-namelist tests (if CLMBuildNamelist.pm has changed): + + derecho - PASS + + 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 + + fates tests + derecho ----- OK against baseline fates-sci.1.80.4_api.37.0.0-tmp-241219.n02.ctsm5.3.016 + izumi ------- OK against baseline fates-sci.1.80.4_api.37.0.0-ctsm5.3.014 + + ctsm_sci against baseline ctsm_sci-ctsm5.3.016 + derecho ---- OK + + rtm tests against baseline rtm1_0_86-ctsm5.3.019 + derecho ----- PASS + + mosart tests against baseline mosart1.1.08-ctsm5.3.019 + derecho ----- OK + izumi ------- PASS + + +Answer changes +-------------- + +Changes answers relative to baseline: No + Just new metadata in history. + +Other details +------------- +List any git submodules updated (cime, rtm, mosart, cism, fates, etc.): + mosart1.1.07 to mosart1.1.08 + ccs_config_cesm1.0.16 to ccs_config_cesm1.0.20 to gain access to f09_t232 grid + rtm was already updated to rtm1_0_86 in an earlier tag + +Pull Requests that document the changes (include PR ids): + https://github.com/ESCOMP/ctsm/pull/2052 + https://github.com/ESCOMP/MOSART/pull/66 + https://github.com/ESCOMP/RTM/pull/35 + +=============================================================== +=============================================================== +Tag name: ctsm5.3.020 +Originator(s): samrabin (Sam Rabin, UCAR/TSS, samrabin@ucar.edu) +Date: Fri Jan 17 12:21:24 MST 2025 +One-line Summary: Merge b4b-dev + +Purpose and description of changes +---------------------------------- + +Merging b4b-dev and ctsm5.3.019. Includes some improvements to test list / expected failures. + + +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 + + +Notes of particular relevance for developers: +--------------------------------------------- + +Changes to tests or testing: +- New test suite: Useful for redoing tests that failed due to https://github.com/ESCOMP/CTSM/issues/2916, after having replaced libraries/mpi-serial with a fresh copy. + + +Testing summary: +---------------- + + regular tests (aux_clm: https://github.com/ESCOMP/CTSM/wiki/System-Testing-Guide#pre-merge-system-testing): + + derecho ----- OK + izumi ------- OK + + +Other details +------------- +List any git submodules updated (cime, rtm, mosart, cism, fates, etc.): + +Pull Requests that document the changes (include PR ids): +- ESCOMP/CTSM#2938: ctsm5.3.020: Merge b4b-dev 2025-01-16 (https://github.com/ESCOMP/CTSM/pull/2938) + +=============================================================== +=============================================================== Tag name: ctsm5.3.019 Originator(s): olyson (Keith Oleson, UCAR/TSS) Date: Tue 14 Jan 2025 02:46:11 PM MST @@ -10,7 +462,7 @@ Purpose and description of changes Contributors ------------ - Bill Sacks. In the final steps, also slevis. + Also Bill Sacks and, in the final steps, slevis. Significant changes to scientifically-supported configurations -------------------------------------------------------------- @@ -3919,7 +4371,7 @@ Pull Requests that document the changes (include PR ids): =============================================================== =============================================================== Tag name: ctsm5.2.010 -Originator(s): @cathyxinchangli (Cathy Xinchang Li, U of Illinois - Urbana-Champaign), @Face2sea, @olyson, @fang-bowen, @keerzhang1 +Originator(s): xinchang (Cathy Xinchang Li, U of Illinois - Urbana-Champaign), @Face2sea, @olyson, @fang-bowen, @keerzhang1 Date: Thu 11 Jul 2024 11:57:15 AM MDT One-line Summary: Explicit A/C adoption diff --git a/doc/ChangeSum b/doc/ChangeSum index bf027e98fc..4bfc0ce86a 100644 --- a/doc/ChangeSum +++ b/doc/ChangeSum @@ -1,5 +1,11 @@ Tag Who Date Summary ============================================================================================================================ + ctsm5.3.025 glemieux 02/18/2025 FATES default parameter file update + ctsm5.3.024 xinchang 02/11/2025 Change choice of pressure in CLMU building energy model + ctsm5.3.023 afoster 02/08/2025 merge b4b-dev + ctsm5.3.022 glemieux 02/06/2025 Update FATES namelist build to avoid Meier2022 + ctsm5.3.021 samrabin 01/29/2025 Standardize time metadata (release tag for ctsm5.3) + ctsm5.3.020 samrabin 01/17/2025 Merge b4b-dev ctsm5.3.019 olyson 01/14/2025 Stop running 0th time step ctsm5.3.018 slevis 01/10/2025 Change history time to be the middle of the time bounds ctsm5.3.017 slevis 01/09/2025 Merge tmp-241219 branch to master diff --git a/parse_cime.cs.status b/parse_cime.cs.status index 264ba0708f..daaaef2293 100755 --- a/parse_cime.cs.status +++ b/parse_cime.cs.status @@ -332,7 +332,9 @@ sub print_categories { if ( ! -f $expectedfailfile ) { $expectedfailfile = "$scrdir/cime_config/testdefs/ExpectedTestFails.xml"; } - my @failfiles = ( $expectedfailfile, "$scrdir/components/mizuRoute/cime_config/testdefs/ExpectedTestFails.xml" ); + my @failfiles = ( $expectedfailfile, "$scrdir/components/mizuRoute/cime_config/testdefs/ExpectedTestFails.xml", + #"$scrdir/components/mosart/cime_config/testdefs/ExpectedTestFails.xml", + "$scrdir/components/cmeps/cime_config/ExpectedTestFails.xml" ); my @passes; my @fails; my @pendings; diff --git a/python/ctsm/crop_calendars/convert_axis_time2gs.py b/python/ctsm/crop_calendars/convert_axis_time2gs.py index d48514370d..004dca5518 100644 --- a/python/ctsm/crop_calendars/convert_axis_time2gs.py +++ b/python/ctsm/crop_calendars/convert_axis_time2gs.py @@ -5,6 +5,7 @@ import sys import numpy as np import xarray as xr +from ctsm.crop_calendars.cropcal_utils import get_integer_years try: import pandas as pd @@ -85,7 +86,7 @@ def set_up_ds_with_gs_axis(ds_in): if not any(x in ["mxsowings", "mxharvests"] for x in ds_in[var].dims): data_vars[var] = ds_in[var] # Set up the new dataset - gs_years = [t.year - 1 for t in ds_in.time.values[:-1]] + gs_years = get_integer_years(ds_in)[:-1] coords = ds_in.coords coords["gs"] = gs_years ds_out = xr.Dataset(data_vars=data_vars, coords=coords, attrs=ds_in.attrs) diff --git a/python/ctsm/crop_calendars/cropcal_figs_module.py b/python/ctsm/crop_calendars/cropcal_figs_module.py index f26b5975d4..519f28d98a 100644 --- a/python/ctsm/crop_calendars/cropcal_figs_module.py +++ b/python/ctsm/crop_calendars/cropcal_figs_module.py @@ -13,6 +13,7 @@ from matplotlib import cm import matplotlib.collections as mplcol +# pylint: disable=abstract-class-instantiated # Colormaps (maps) cropcal_colors = { diff --git a/python/ctsm/crop_calendars/cropcal_module.py b/python/ctsm/crop_calendars/cropcal_module.py index 3ea084e1d2..5118186cca 100644 --- a/python/ctsm/crop_calendars/cropcal_module.py +++ b/python/ctsm/crop_calendars/cropcal_module.py @@ -12,6 +12,7 @@ from ctsm.crop_calendars.check_rx_obeyed import check_rx_obeyed from ctsm.crop_calendars.cropcal_constants import DEFAULT_GDD_MIN from ctsm.crop_calendars.import_ds import import_ds +from ctsm.utils import is_instantaneous MISSING_RX_GDD_VAL = -1 @@ -20,28 +21,23 @@ def check_and_trim_years(year_1, year_n, ds_in): """ After importing a file, restrict it to years of interest. """ - ### In annual outputs, file with name Y is actually results from year Y-1. - ### Note that time values refer to when it was SAVED. So 1981-01-01 is for year 1980. - - def get_year_from_cftime(cftime_date): - # Subtract 1 because the date for annual files is when it was SAVED - return cftime_date.year - 1 # Check that all desired years are included - if get_year_from_cftime(ds_in.time.values[0]) > year_1: - raise RuntimeError( - f"Requested year_1 is {year_1} but first year in outputs is " - + f"{get_year_from_cftime(ds_in.time.values[0])}" - ) - if get_year_from_cftime(ds_in.time.values[-1]) < year_1: - raise RuntimeError( - f"Requested year_n is {year_n} but last year in outputs is " - + f"{get_year_from_cftime(ds_in.time.values[-1])}" - ) + year = utils.get_timestep_year(ds_in, ds_in.time.values[0]) + if year > year_1: + raise RuntimeError(f"Requested year_1 is {year_1} but first year in outputs is {year}") + year = utils.get_timestep_year(ds_in, ds_in.time.values[-1]) + if year < year_1: + raise RuntimeError(f"Requested year_n is {year_n} but last year in outputs is {year}") # Remove years outside range of interest ### Include an extra year at the end to finish out final seasons. - ds_in = utils.safer_timeslice(ds_in, slice(f"{year_1+1}-01-01", f"{year_n+2}-01-01")) + slice_yr_1 = year_1 + slice_yr_n = year_n + 1 + if is_instantaneous(ds_in["time"]): + slice_yr_1 += 1 + slice_yr_n += 1 + ds_in = utils.safer_timeslice(ds_in, slice(f"{slice_yr_1}-01-01", f"{slice_yr_n}-12-31")) # Make sure you have the expected number of timesteps (including extra year) n_years_expected = year_n - year_1 + 2 @@ -464,9 +460,7 @@ def convert_time_to_int_year(filename, this_ds, this_ds_gs): # time_bounds saved. After that PR (and before the segregation of instantaneous and other # variables onto separate files), files with an instantaneous variable first in their list # do not get time_bounds saved. - this_ds_gs = this_ds_gs.assign_coords( - {"cftime": this_ds["time_bounds"].isel({"hist_interval": 0})} - ) + this_ds_gs = this_ds_gs.assign_coords({"cftime": this_ds["time_bounds"].isel({"nbnd": 0})}) this_ds_gs = this_ds_gs.assign_coords( {"time": [t.year for t in this_ds_gs["cftime"].values]} ) diff --git a/python/ctsm/crop_calendars/cropcal_utils.py b/python/ctsm/crop_calendars/cropcal_utils.py index 584046edee..4eaff2c94f 100644 --- a/python/ctsm/crop_calendars/cropcal_utils.py +++ b/python/ctsm/crop_calendars/cropcal_utils.py @@ -5,6 +5,8 @@ import numpy as np import xarray as xr +from ctsm.utils import is_instantaneous + def define_pftlist(): """ @@ -317,11 +319,12 @@ def safer_timeslice(ds_in, time_slice, time_var="time"): """ ctsm_pylib can't handle time slicing like Dataset.sel(time=slice("1998-01-01", "2005-12-31")) for some reason. This function tries to fall back to slicing by integers. It should work with - both Datasets and DataArrays. + both Datasets and DataArrays. NOTE: This isn't a problem for more modern Python environments. + Even npl-2022b can use the straightforward slicing in the "try" block. """ try: ds_in = ds_in.sel({time_var: time_slice}) - except: # pylint: disable=bare-except + except Exception as this_exception: # pylint: disable=broad-except # If the issue might have been slicing using strings, try to fall back to integer slicing can_try_integer_slicing = ( isinstance(time_slice.start, str) @@ -337,15 +340,15 @@ def safer_timeslice(ds_in, time_slice, time_var="time"): if can_try_integer_slicing: fileyears = np.array([x.year for x in ds_in.time.values]) if len(np.unique(fileyears)) != len(fileyears): - print("Could not fall back to integer slicing of years: Time axis not annual") - raise + msg = "Could not fall back to integer slicing of years: Time axis not annual" + raise RuntimeError(msg) from this_exception y_start = int(time_slice.start.split("-")[0]) y_stop = int(time_slice.stop.split("-")[0]) where_in_timeslice = np.where((fileyears >= y_start) & (fileyears <= y_stop))[0] ds_in = ds_in.isel({time_var: where_in_timeslice}) else: - print(f"Could not fall back to integer slicing for time_slice {time_slice}") - raise + msg = f"Could not fall back to integer slicing for time_slice {time_slice}" + raise RuntimeError(msg) from this_exception return ds_in @@ -430,3 +433,38 @@ def make_lon_increasing(xr_obj): raise RuntimeError("Unable to rearrange longitude axis so it's monotonically increasing") return xr_obj.roll(lon=shift, roll_coords=True) + + +def get_beg_inst_timestep_year(timestep): + """ + Get year associated with the BEGINNING of a timestep in an + instantaneous file + """ + year = timestep.year + + is_jan1 = timestep.dayofyr == 1 + is_midnight = timestep.hour == timestep.minute == timestep.second == 0 + if is_jan1 and is_midnight: + year -= 1 + + return year + + +def get_timestep_year(dsa, timestep): + """ + Get the year associated with a timestep, with different handling + depending on whether the file is instantaneous + """ + if is_instantaneous(dsa["time"]): + year = get_beg_inst_timestep_year(timestep) + else: + year = timestep.year + return year + + +def get_integer_years(dsa): + """ + Convert time axis to numpy array of integer years + """ + out_array = [get_timestep_year(dsa, t) for t in dsa["time"].values] + return out_array diff --git a/python/ctsm/crop_calendars/generate_gdds.py b/python/ctsm/crop_calendars/generate_gdds.py index 49226e6f75..4c08f6a614 100644 --- a/python/ctsm/crop_calendars/generate_gdds.py +++ b/python/ctsm/crop_calendars/generate_gdds.py @@ -43,6 +43,7 @@ def main( unlimited_season_length=False, skip_crops=None, logger=None, + no_pickle=None, ): # pylint: disable=missing-function-docstring,too-many-statements # Directories to save output files and figures @@ -109,7 +110,7 @@ def main( pickle_file = os.path.join(output_dir, f"{first_season}-{last_season}.pickle") h2_ds_file = os.path.join(output_dir, f"{first_season}-{last_season}.h2_ds.nc") - if os.path.exists(pickle_file): + if os.path.exists(pickle_file) and not no_pickle: with open(pickle_file, "rb") as file: ( first_season, @@ -144,6 +145,7 @@ def main( else: mxmats = None + h1_instantaneous = None for yr_index, this_yr in enumerate(np.arange(first_season + 1, last_season + 3)): if this_yr <= pickle_year: continue @@ -160,6 +162,7 @@ def main( incl_vegtypes_str, incl_patches1d_itype_veg, mxsowings, + h1_instantaneous, ) = gddfn.import_and_process_1yr( first_season, last_season, @@ -180,6 +183,7 @@ def main( skip_crops, outdir_figs, logger, + h1_instantaneous, ) gddfn.log(logger, f" Saving pickle file ({pickle_file})...") @@ -470,6 +474,12 @@ def add_attrs_to_map_ds( type=str, default="", ) + parser.add_argument( + "--no-pickle", + help="Don't read from existing pickle file; instead, overwrite. For troubleshooting.", + action="store_true", + default=False, + ) # Get arguments args = parser.parse_args(sys.argv[1:]) @@ -493,4 +503,5 @@ def add_attrs_to_map_ds( last_land_use_year=args.last_land_use_year, unlimited_season_length=args.unlimited_season_length, skip_crops=args.skip_crops, + no_pickle=args.no_pickle, ) diff --git a/python/ctsm/crop_calendars/generate_gdds_functions.py b/python/ctsm/crop_calendars/generate_gdds_functions.py index e83eb7bb50..41b1627ed7 100644 --- a/python/ctsm/crop_calendars/generate_gdds_functions.py +++ b/python/ctsm/crop_calendars/generate_gdds_functions.py @@ -10,6 +10,7 @@ import numpy as np import xarray as xr +from ctsm.utils import is_instantaneous import ctsm.crop_calendars.cropcal_utils as utils import ctsm.crop_calendars.cropcal_module as cc from ctsm.crop_calendars.xr_flexsel import xr_flexsel @@ -258,6 +259,7 @@ def import_and_process_1yr( skip_crops, outdir_figs, logger, + h1_instantaneous, ): """ Import one year of CLM output data for GDD generation @@ -287,12 +289,19 @@ def import_and_process_1yr( else: crops_to_read = utils.define_mgdcrop_list_withgrasses() - print(h1_filelist) + # Are h1 files instantaneous? + if h1_instantaneous is None: + h1_instantaneous = is_instantaneous(xr.open_dataset(h1_filelist[0])["time"]) + + if h1_instantaneous: + slice_year = this_year + else: + slice_year = this_year - 1 dates_ds = import_ds( h1_filelist, my_vars=["SDATES", "HDATES"], my_vegtypes=crops_to_read, - time_slice=slice(f"{this_year}-01-01", f"{this_year}-12-31"), + time_slice=slice(f"{slice_year}-01-01", f"{slice_year}-12-31"), chunks=chunks, ) for timestep in dates_ds["time"].values: @@ -553,8 +562,8 @@ def import_and_process_1yr( clm_gdd_var = "GDDACCUM" my_vars = [clm_gdd_var, "GDDHARV"] patterns = [f"*h2.{this_year-1}-01*.nc", f"*h2.{this_year-1}-01*.nc.base"] - for p in patterns: - pattern = os.path.join(indir, p) + for pat in patterns: + pattern = os.path.join(indir, pat) h2_files = glob.glob(pattern) if h2_files: break @@ -810,6 +819,7 @@ def import_and_process_1yr( incl_vegtypes_str, incl_patches1d_itype_veg, mxsowings, + h1_instantaneous, ) diff --git a/python/ctsm/crop_calendars/import_ds.py b/python/ctsm/crop_calendars/import_ds.py index 486757492f..181d34a9cf 100644 --- a/python/ctsm/crop_calendars/import_ds.py +++ b/python/ctsm/crop_calendars/import_ds.py @@ -10,6 +10,7 @@ from importlib.util import find_spec import numpy as np import xarray as xr +from ctsm.utils import is_instantaneous import ctsm.crop_calendars.cropcal_utils as utils from ctsm.crop_calendars.xr_flexsel import xr_flexsel @@ -23,7 +24,8 @@ def compute_derived_vars(ds_in, var): and "HDATES" in ds_in and ds_in.HDATES.dims == ("time", "mxharvests", "patch") ): - year_list = np.array([np.float32(x.year - 1) for x in ds_in.time.values]) + year_adj = 1 if is_instantaneous(ds_in["time"]) else 0 + year_list = np.array([np.float32(x.year - year_adj) for x in ds_in.time.values]) hyears = ds_in["HDATES"].copy() hyears.values = np.tile( np.expand_dims(year_list, (1, 2)), diff --git a/python/ctsm/path_utils.py b/python/ctsm/path_utils.py index 0015822c03..e8cf2989ce 100644 --- a/python/ctsm/path_utils.py +++ b/python/ctsm/path_utils.py @@ -96,6 +96,16 @@ def add_cime_lib_to_path(standalone_only=False): return cime_path +def add_ctsm_systests_to_path(standalone_only=False): + """Adds the CTSM python SystemTests to the python path, to allow importing + modules from that library + """ + cime_path = path_to_cime(standalone_only=standalone_only) + ctsm_systest_dir = os.path.join(cime_path, os.pardir, "cime_config") + prepend_to_python_path(ctsm_systest_dir) + sys.path.insert(1, ctsm_systest_dir) + + # ======================================================================== # Private functions # ======================================================================== diff --git a/python/ctsm/site_and_regional/neon_site.py b/python/ctsm/site_and_regional/neon_site.py index 73264719ae..df8a514ea3 100755 --- a/python/ctsm/site_and_regional/neon_site.py +++ b/python/ctsm/site_and_regional/neon_site.py @@ -1,5 +1,6 @@ """ -This module contains the NeonSite class and class functions which are used in run_neon.py +This module contains the NeonSite class and class functions which extend the tower_site class for +things that are specific just for NEON sites. """ # Import libraries @@ -59,7 +60,6 @@ def run_case( base_case_root, run_type, prism, - run_length, user_version, tower_type=None, user_mods_dirs=None, @@ -80,8 +80,6 @@ def run_case( transient, post_ad, or ad case, default transient prism: bool, opt if True, use PRISM precipitation, default False - run_length: str, opt - length of run, default '4Y' user_version: str, opt default 'latest' overwrite: bool, opt @@ -104,7 +102,6 @@ def run_case( base_case_root, run_type, prism, - run_length, user_version, tower_type, user_mods_dirs, diff --git a/python/ctsm/site_and_regional/plumber_site.py b/python/ctsm/site_and_regional/plumber_site.py new file mode 100755 index 0000000000..3f06f2949b --- /dev/null +++ b/python/ctsm/site_and_regional/plumber_site.py @@ -0,0 +1,120 @@ +""" +This module contains the Plumber2Site class and class functions that extend the tower_site class for +things that are specific just for PLUMBER2 sites. +""" + +# Import libraries +import logging +import os +import sys + +# Get the ctsm util tools and then the cime tools. +_CTSM_PYTHON = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "python")) +sys.path.insert(1, _CTSM_PYTHON) + +# -- import local classes for this script +# pylint: disable=wrong-import-position +from ctsm.site_and_regional.tower_site import TowerSite + +# pylint: disable=wrong-import-position, import-error, unused-import, wrong-import-order +from ctsm import add_cime_to_path +from ctsm.path_utils import path_to_ctsm_root + +from CIME import build +from CIME.case import Case +from CIME.utils import safe_copy, expect, symlink_force + +logger = logging.getLogger(__name__) + + +# pylint: disable=too-many-instance-attributes +class Plumber2Site(TowerSite): + """ + A class for encapsulating plumber sites. + """ + + def build_base_case( + self, + cesmroot, + output_root, + res, + compset, + user_mods_dirs=None, + overwrite=False, + setup_only=False, + ): + if user_mods_dirs is None: + user_mods_dirs = [ + os.path.join( + self.cesmroot, "cime_config", "usermods_dirs", "clm", "PLUMBER2", self.name + ) + ] + case_path = super().build_base_case(cesmroot, output_root, res, compset, user_mods_dirs) + + return case_path + + # pylint: disable=too-many-statements + def run_case( + self, + base_case_root, + run_type, + prism, + user_version, + tower_type=None, + user_mods_dirs=None, + overwrite=False, + setup_only=False, + no_batch=False, + rerun=False, + experiment=False, + ): + """ + Run case. + + Args: + self + base_case_root: str, opt + file path of base case + run_type: str, opt + transient, post_ad, or ad case, default ad + (ad case is default because PLUMBER requires spinup) + prism: bool, opt + if True, use PRISM precipitation, default False + Note: only supported for NEON sites + user_version: str, opt + default 'latest'; this could be useful later + This is currently only implemented with neon (not plumber) sites + overwrite: bool, opt + default False + setup_only: bool, opt + default False; if True, set up but do not run case + no_batch: bool, opt + default False + rerun: bool, opt + default False + experiment: str, opt + name of experiment, default False + """ + user_mods_dirs = [ + os.path.join( + self.cesmroot, "cime_config", "usermods_dirs", "clm", "PLUMBER2", self.name + ) + ] + tower_type = "PLUMBER" + super().run_case( + base_case_root, + run_type, + prism, + user_version, + tower_type, + user_mods_dirs, + overwrite, + setup_only, + no_batch, + rerun, + experiment, + ) + + def set_ref_case(self, case): + super().set_ref_case(case) + return True ### Check if super returns false, if this will still return True? diff --git a/python/ctsm/site_and_regional/run_neon.py b/python/ctsm/site_and_regional/run_tower.py similarity index 62% rename from python/ctsm/site_and_regional/run_neon.py rename to python/ctsm/site_and_regional/run_tower.py index ac72554d46..e65e26a064 100755 --- a/python/ctsm/site_and_regional/run_neon.py +++ b/python/ctsm/site_and_regional/run_tower.py @@ -5,18 +5,18 @@ |--------------------- Instructions -----------------------------| |------------------------------------------------------------------| This is a wrapper script for running CTSM simulation for one or more -neon sites. +tower (neon or plumber) sites. -This script is only for neon site and we will develop a more general -code later. +This script is only for supported tower sites and we will develop a +more general code later. This script first creates and builds a generic base case. -Next, it will clone the base_case for different neon sites and run +Next, it will clone the base_case for different tower sites and run types to reduce the need to build ctsm everytime. This script will do the following: 1) Create a generic base case for cloning. - 2) Make the case for the specific neon site(s). + 2) Make the case for the specific neon or plumber site(s). 3) Make changes to the case, for: a. AD spinup b. post-AD spinup @@ -33,11 +33,10 @@ ------------------------------------------------------------------- To see the available options: - ./run_neon.py --help + ./run_tower.py --help ------------------------------------------------------------------- """ # TODO (NS) -# - [ ] # - [ ] Case dependency and the ability to check case status # - [ ] If Case dependency works we don't need finidat given explicilty for post-ad and transient. @@ -64,8 +63,9 @@ # pylint: disable=wrong-import-position from ctsm.path_utils import path_to_ctsm_root from ctsm.download_utils import download_file -from ctsm.site_and_regional.neon_arg_parse import get_parser +from ctsm.site_and_regional.tower_arg_parse import get_parser from ctsm.site_and_regional.neon_site import NeonSite +from ctsm.site_and_regional.plumber_site import Plumber2Site # pylint: disable=import-error, wildcard-import, wrong-import-order from standard_script_setup import * @@ -130,7 +130,7 @@ def parse_neon_listing(listing_file, valid_neon_sites): # versions = tmp_df[7].unique() # print ("all versions available for ", site_name,":", *versions) latest_version = tmp_df[7].iloc[-1] - # print ("latests version available for ", site_name,":", latest_version) + # print ("latest version available for ", site_name,":", latest_version) tmp_df = tmp_df[tmp_df[7].str.contains(latest_version)] # -- remove .nc from the file names @@ -167,9 +167,41 @@ def parse_neon_listing(listing_file, valid_neon_sites): return available_list +def setup_plumber_data(valid_plumber_sites): + """ + A function to set up plumber site objects + with dummy start and end years and months. + This allows us to use the list of plumber site objects. + + Returns: + available_list : + list of plumber_site objects that is found + """ + + available_list = [] + + for site_name in valid_plumber_sites: + + # start_year and end_year are set in shell commands, so these get overwritten + start_year = 8888 + end_year = 9999 + start_month = 1 + end_month = 12 + + logger.debug("Valid plumber site %s found!", site_name) + finidat = None + + plumber_site = Plumber2Site( + site_name, start_year, end_year, start_month, end_month, finidat + ) + available_list.append(plumber_site) + + return available_list + + def main(description): """ - Determine valid neon sites. Make an output directory if it does not exist. + Determine valid tower sites. Make an output directory if it does not exist. Loop through requested sites and run CTSM at that site. """ cesmroot = path_to_ctsm_root() @@ -181,21 +213,28 @@ def main(description): ) valid_neon_sites = sorted([v.split("/")[-1] for v in valid_neon_sites]) + # Get the list of supported plumber sites from usermods + valid_plumber_sites = glob.glob( + os.path.join(cesmroot, "cime_config", "usermods_dirs", "clm", "PLUMBER2", "[!d]*") + ) + + valid_plumber_sites = sorted([v.split("/")[-1] for v in valid_plumber_sites]) + ( - site_list, + neon_site_list, + plumber_site_list, output_root, run_type, experiment, prism, overwrite, - run_length, base_case_root, run_from_postad, setup_only, no_batch, rerun, user_version, - ) = get_parser(sys.argv, description, valid_neon_sites) + ) = get_parser(sys.argv, description, valid_neon_sites, valid_plumber_sites) if output_root: logger.debug("output_root : %s", output_root) @@ -206,7 +245,7 @@ def main(description): available_list = check_neon_listing(valid_neon_sites) # ================================= - # -- all neon sites can be cloned from one generic case + # -- all tower sites can be cloned from one generic case # -- so no need to define a base_case for every site. res = "CLM_USRDAT" @@ -216,28 +255,55 @@ def main(description): compset = "I1PtClm60Bgc" # -- Looping over neon sites + if neon_site_list: + for neon_site in available_list: + if neon_site.name in neon_site_list: + if run_from_postad: + neon_site.finidat = None + if not base_case_root: + user_mods_dirs = None + base_case_root = neon_site.build_base_case( + cesmroot, output_root, res, compset, user_mods_dirs, overwrite, setup_only + ) + logger.info("-----------------------------------") + logger.info("Running CTSM for neon site : %s", neon_site.name) + + neon_site.run_case( + base_case_root, + run_type, + prism, + user_version, + overwrite=overwrite, + setup_only=setup_only, + no_batch=no_batch, + rerun=rerun, + experiment=experiment, + ) - for neon_site in available_list: - if neon_site.name in site_list: - if run_from_postad: - neon_site.finidat = None - if not base_case_root: - user_mods_dirs = None - base_case_root = neon_site.build_base_case( - cesmroot, output_root, res, compset, user_mods_dirs, overwrite, setup_only + # -- check for available plumber data: + available_plumber_list = setup_plumber_data(valid_plumber_sites) + + # -- Looping over plumber sites + if plumber_site_list: + for plumber_site in available_plumber_list: + if plumber_site.name in plumber_site_list: + if run_from_postad: + plumber_site.finidat = None + if not base_case_root: + user_mods_dirs = None + base_case_root = plumber_site.build_base_case( + cesmroot, output_root, res, compset, user_mods_dirs, overwrite, setup_only + ) + logger.info("-----------------------------------") + logger.info("Running CTSM for plumber site : %s", plumber_site.name) + plumber_site.run_case( + base_case_root, + run_type, + prism, + user_version, + overwrite=overwrite, + setup_only=setup_only, + no_batch=no_batch, + rerun=rerun, + experiment=experiment, ) - logger.info("-----------------------------------") - logger.info("Running CTSM for neon site : %s", neon_site.name) - - neon_site.run_case( - base_case_root, - run_type, - prism, - run_length, - user_version, - overwrite=overwrite, - setup_only=setup_only, - no_batch=no_batch, - rerun=rerun, - experiment=experiment, - ) diff --git a/python/ctsm/site_and_regional/neon_arg_parse.py b/python/ctsm/site_and_regional/tower_arg_parse.py similarity index 77% rename from python/ctsm/site_and_regional/neon_arg_parse.py rename to python/ctsm/site_and_regional/tower_arg_parse.py index 99f184dd62..c4491ffa99 100644 --- a/python/ctsm/site_and_regional/neon_arg_parse.py +++ b/python/ctsm/site_and_regional/tower_arg_parse.py @@ -1,5 +1,5 @@ """ -Argument parser to use throughout run_neon.py +Argument parser to use throughout run_tower.py """ import argparse @@ -18,7 +18,9 @@ from CIME.utils import setup_standard_logging_options -def get_parser(args, description, valid_neon_sites): +# TODO: Refactor to shorten this and remove this pylint disable! +# pylint: disable=too-many-statements +def get_parser(args, description, valid_neon_sites, valid_plumber_sites): """ Get parser object for this script. """ @@ -35,9 +37,20 @@ def get_parser(args, description, valid_neon_sites): help="4-letter neon site code.", action="store", required=False, - choices=valid_neon_sites + ["all"], + choices=valid_neon_sites + ["all"] + [None], dest="neon_sites", - default=["OSBS"], + default=None, + nargs="+", + ) + + parser.add_argument( + "--plumber-sites", + help="six character PLUMBER2 site code (eg, AR-SLu)", + action="store", + required=False, + choices=valid_plumber_sites + ["all"] + [None], + dest="plumber_sites", + default=None, nargs="+", ) @@ -122,7 +135,7 @@ def get_parser(args, description, valid_neon_sites): [default: %(default)s] """, choices=["ad", "postad", "transient"], # , "sasu"], - default="transient", + # default ad for plumber, transient for neon ) parser.add_argument( @@ -149,17 +162,6 @@ def get_parser(args, description, valid_neon_sites): default=None, ) - parser.add_argument( - "--run-length", - help=""" - How long to run (modified ISO 8601 duration) - [default: %(default)s] - """, - required=False, - type=str, - default="0Y", - ) - parser.add_argument( "--run-from-postad", help=""" @@ -185,31 +187,36 @@ def get_parser(args, description, valid_neon_sites): args = parse_args_and_handle_standard_logging_options(args, parser) - if "all" in args.neon_sites: - neon_sites = valid_neon_sites + if args.neon_sites: + if "all" in args.neon_sites: + neon_sites = valid_neon_sites + else: + neon_sites = args.neon_sites + for site in neon_sites: + if site not in valid_neon_sites: + raise ValueError("Invalid neon site name {}".format(site)) + if args.run_type is None: + args.run_type = "transient" else: - neon_sites = args.neon_sites - for site in neon_sites: - if site not in valid_neon_sites: - raise ValueError("Invalid site name {}".format(site)) - - if "CIME_OUTPUT_ROOT" in args.output_root: - args.output_root = None - - if args.run_length == "0Y": - if args.run_type == "ad": - run_length = "100Y" - elif args.run_type == "postad": - run_length = "100Y" + neon_sites = None + if args.plumber_sites: + if "all" in args.plumber_sites: + plumber_sites = valid_plumber_sites else: - # The transient run length is set by cdeps atm buildnml to - # the last date of the available tower data - # this value is not used - run_length = "4Y" + plumber_sites = args.plumber_sites + for site in plumber_sites: + if site not in valid_plumber_sites: + raise ValueError("Invalid plumber site name {}".format(site)) + if ( + not args.neon_sites + ): # default to not changing neon behavior if both neon and plumber are present + if args.run_type is None: + args.run_type = "ad" else: - run_length = args.run_length + plumber_sites = None - run_length = parse_isoduration(run_length) + if "CIME_OUTPUT_ROOT" in args.output_root: + args.output_root = None base_case_root = None if args.base_case_root: @@ -225,12 +232,12 @@ def get_parser(args, description, valid_neon_sites): return ( neon_sites, + plumber_sites, args.output_root, args.run_type, args.experiment, args.prism, args.overwrite, - run_length, base_case_root, args.run_from_postad, args.setup_only, diff --git a/python/ctsm/site_and_regional/tower_site.py b/python/ctsm/site_and_regional/tower_site.py index 2ff8999408..8a2c55c64d 100644 --- a/python/ctsm/site_and_regional/tower_site.py +++ b/python/ctsm/site_and_regional/tower_site.py @@ -69,16 +69,20 @@ def __str__(self): ), ) + # TODO: Refactor to shorten this so the disable can be removed + # pylint: disable=too-many-statements def build_base_case( self, cesmroot, output_root, res, compset, user_mods_dirs, overwrite=False, setup_only=False ): """ Function for building a base_case to clone. - To spend less time on building ctsm for the neon cases, + To spend less time on building ctsm for the neon and plumber cases, all the other cases are cloned from this case Args: self: - The NeonSite object + The TowerSite object + base_root (str): + root of the base_case CIME cesmroot (str): root of the CESM code to run output_root (str): @@ -109,9 +113,9 @@ def build_base_case( abort("Input setup_only is NOT a boolean as expected: " + str(setup_only)) if not isinstance(user_mods_dirs, list): abort("Input user_mods_dirs is NOT a list as expected: " + str(user_mods_dirs)) - for dir in user_mods_dirs: - if not os.path.isdir(dir): - abort("Input user_mods_dirs directory does NOT exist: " + str(dir)) + for dirtree in user_mods_dirs: + if not os.path.isdir(dirtree): + abort("Input user_mods_dirs dirtreetory does NOT exist: " + str(dirtree)) print("---- building a base case -------") # pylint: disable=attribute-defined-outside-init @@ -217,6 +221,8 @@ def modify_user_nl(self, case_root, run_type, rundir, site_lines=None): "finidat = '{}/inputdata/lnd/ctsm/initdata/{}'".format(rundir, self.finidat) ] else: + if not site_lines: + site_lines = [] user_nl_lines = [ "hist_fincl2 = ''", "hist_mfilt = 20", @@ -282,7 +288,6 @@ def run_case( base_case_root, run_type, prism, - run_length, user_version, tower_type, user_mods_dirs, @@ -300,11 +305,10 @@ def run_case( base_case_root: str, opt file path of base case run_type: str, opt - transient, post_ad, or ad case, default transient + transient, post_ad, or ad case + default transient for neon cases and ad for plumber cases prism: bool, opt if True, use PRISM precipitation, default False - run_length: str, opt - length of run, default '4Y' user_version: str, opt default 'latest' overwrite: bool, opt @@ -409,10 +413,14 @@ def run_case( if run_type == "ad": case.set_value("CLM_FORCE_COLDSTART", "on") case.set_value("CLM_ACCELERATED_SPINUP", "on") + # This was originally set to 18 for NEON cases, which typically start in 2018. + # AD cases, would start in 0018, followed by postAD in 1018. + # PLUMBER cases all start in different years, but not expected to cause issues. case.set_value("RUN_REFDATE", "0018-01-01") case.set_value("RUN_STARTDATE", "0018-01-01") case.set_value("RESUBMIT", 1) - case.set_value("STOP_N", run_length) + # this case.setup() is necessary to create the case.run batch job + case.case_setup() else: case.set_value("CLM_FORCE_COLDSTART", "off") @@ -422,7 +430,6 @@ def run_case( if run_type == "postad": case.case_setup() self.set_ref_case(case) - case.set_value("STOP_N", run_length) # For transient cases STOP will be set in the user_mod_directory if run_type == "transient": diff --git a/python/ctsm/test/test_sys_modify_singlept_site_neon.py b/python/ctsm/test/test_sys_modify_singlept_site_neon.py index ed7e52ed38..41147ef5b9 100755 --- a/python/ctsm/test/test_sys_modify_singlept_site_neon.py +++ b/python/ctsm/test/test_sys_modify_singlept_site_neon.py @@ -58,11 +58,11 @@ def test_modify_site(self): path_to_ctsm_root() + "/ctsm/cime_config/usermods_dirs/clm/NEON/ABBY", ] # TODO: the above requires a full path instead of site name - # because of how run_neon is configured. - # This needs to be fixed in run_neon. + # because of how run_tower is configured. + # This needs to be fixed in run_tower. with self.assertRaises(SystemExit): print( - """This should currently fail due to directory structure in run_neon + """This should currently fail due to directory structure in run_tower and the directory structure listed in sys.argv""" ) main() diff --git a/python/ctsm/test/test_sys_run_neon.py b/python/ctsm/test/test_sys_run_neon.py deleted file mode 100755 index f4c417ea51..0000000000 --- a/python/ctsm/test/test_sys_run_neon.py +++ /dev/null @@ -1,71 +0,0 @@ -#!/usr/bin/env python3 - -"""System tests for run_neon - -""" - -import glob -import os -import unittest -import tempfile -import shutil -import sys - -from ctsm import unit_testing -from ctsm.site_and_regional.run_neon import main -from ctsm.path_utils import path_to_ctsm_root - -# Allow test names that pylint doesn't like; otherwise hard to make them -# readable -# pylint: disable=invalid-name - - -class TestSysRunNeon(unittest.TestCase): - """System tests for run_neon""" - - def setUp(self): - """ - Make /_tempdir for use by these tests. - Check tempdir for history files - """ - self._previous_dir = os.getcwd() - self._tempdir = tempfile.mkdtemp() - os.chdir(self._tempdir) # cd to tempdir - - def tearDown(self): - """ - Remove temporary directory - """ - os.chdir(self._previous_dir) - shutil.rmtree(self._tempdir, ignore_errors=True) - - def test_one_site(self): - """ - This test specifies a site to run - Run the tool, check that file structure is set up correctly - """ - - # run the run_neon tool - sys.argv = [ - os.path.join(path_to_ctsm_root(), "tools", "site_and_regional", "run_neon"), - "--neon-sites", - "BART", - "--setup-only", - "--output-root", - self._tempdir, - ] - main("") - - # assert that BART directories were created during setup - self.assertTrue("BART" in glob.glob(self._tempdir + "/BART*")[0]) - - # TODO: Would also be useful to test the following items: - # It might be good to ensure the log files are working as expected? - # Test running transient, ad and post ad cases. - # Test use of base case root. - # Test for using prism? - - -if __name__ == "__main__": - unit_testing.setup_for_tests() - unittest.main() diff --git a/python/ctsm/test/test_sys_run_tower.py b/python/ctsm/test/test_sys_run_tower.py new file mode 100755 index 0000000000..b1eef6c3a9 --- /dev/null +++ b/python/ctsm/test/test_sys_run_tower.py @@ -0,0 +1,121 @@ +#!/usr/bin/env python3 + +"""System tests for run_tower + +""" + +import glob +import os +import unittest +import tempfile +import shutil +import sys + +from ctsm import unit_testing +from ctsm.site_and_regional.run_tower import main +from ctsm.path_utils import path_to_ctsm_root + +# Allow test names that pylint doesn't like; otherwise hard to make them +# readable +# pylint: disable=invalid-name + + +class TestSysRunTower(unittest.TestCase): + """ + System tests for run_tower + + TODO: Would also be useful to test the following items: + Ensure the log files are working as expected? + Test use of base case root. + Test for using prism + """ + + def setUp(self): + """ + Make /_tempdir for use by these tests. + Check tempdir for history files + """ + self._previous_dir = os.getcwd() + self._tempdir = tempfile.mkdtemp() + os.chdir(self._tempdir) # cd to tempdir + + def tearDown(self): + """ + Remove temporary directory + """ + os.chdir(self._previous_dir) + shutil.rmtree(self._tempdir, ignore_errors=True) + + def test_one_site(self): + """ + This test specifies a site to run a default case with experiment label 'TEST' + Run the tool, check that file structure is set up correctly + """ + + # run the run_tower tool + print("about to run tower tool") + sys.argv = [ + os.path.join(path_to_ctsm_root(), "tools", "site_and_regional", "run_tower"), + "--neon-sites", + "BART", + "--setup-only", + "--experiment", + "TEST", + "--output-root", + self._tempdir, + ] + print(sys.argv) + main("") + + # assert that BART directories were created during setup + self.assertTrue("BART" in glob.glob(self._tempdir + "/BART*")[0]) + print(glob.glob(self._tempdir)) + + def test_ad_site(self): + """ + This test specifies a site to run an 'ad' case for + Run the tool, check that file structure is set up correctly + """ + + # run the run_tower tool + sys.argv = [ + os.path.join(path_to_ctsm_root(), "tools", "site_and_regional", "run_tower"), + "--neon-sites", + "ABBY", + "--setup-only", + "--run-type", + "ad", + "--output-root", + self._tempdir, + ] + main("") + + # assert that ABBY directories were created during setup + self.assertTrue("ABBY" in glob.glob(self._tempdir + "/ABBY*")[0]) + + def test_plumber_site(self): + """ + This test specifies a site to run a default plumber AD case with + experiment label 'TEST'. Run the tool, check that file structure is set up. + """ + + # run the run_tower tool for plumber site + sys.argv = [ + os.path.join(path_to_ctsm_root(), "tools", "site_and_regional", "run_tower"), + "--plumber-sites", + "AR-SLu", + "--setup-only", + "--experiment", + "TEST", + "--output-root", + self._tempdir, + ] + main("") + + # assert that AR-SLu directories were created during setup + self.assertTrue("AR-SLu" in glob.glob(self._tempdir + "/AR-SLu*")[0]) + + +if __name__ == "__main__": + unit_testing.setup_for_tests() + unittest.main() diff --git a/python/ctsm/test/test_unit_neon_arg_parse.py b/python/ctsm/test/test_unit_neon_arg_parse.py deleted file mode 100755 index 141b41fbc7..0000000000 --- a/python/ctsm/test/test_unit_neon_arg_parse.py +++ /dev/null @@ -1,76 +0,0 @@ -#!/usr/bin/env python3 -""" -Unit tests for neon_arg_parse - -You can run this by: - python -m unittest test_unit_neon_arg_parse.py -""" - -import unittest -import tempfile -import shutil -import os -import sys -import glob - -# -- add python/ctsm to path (needed if we want to run the test stand-alone) -_CTSM_PYTHON = os.path.join(os.path.dirname(os.path.realpath(__file__)), os.pardir, os.pardir) -sys.path.insert(1, _CTSM_PYTHON) - -# pylint: disable=wrong-import-position -from ctsm import unit_testing -from ctsm.site_and_regional.neon_arg_parse import get_parser -from ctsm.path_utils import path_to_ctsm_root - -# pylint: disable=invalid-name - - -class Test_neon_arg_parse(unittest.TestCase): - """ - Basic class for testing neon_arg_parse.py. - """ - - def setUp(self): - """ - Make /_tempdir for use by these tests. - """ - self._previous_dir = os.getcwd() - self._tempdir = tempfile.mkdtemp() - - def tearDown(self): - """ - Remove temporary directory - """ - os.chdir(self._previous_dir) - shutil.rmtree(self._tempdir, ignore_errors=True) - - def test_function(self): - """ - Test that neon_arg_parse is properly reading arguments... - """ - sys.argv = [ - "neon_arg_parse", - "--neon-sites", - "ABBY", - "--experiment", - "test", - "--run-type", - "ad", - ] - description = "" - cesmroot = path_to_ctsm_root() - valid_neon_sites = glob.glob( - os.path.join(cesmroot, "cime_config", "usermods_dirs", "clm", "NEON", "[!d]*") - ) - valid_neon_sites = sorted([v.split("/")[-1] for v in valid_neon_sites]) - parsed_arguments = get_parser(sys.argv, description, valid_neon_sites) - - self.assertEqual(parsed_arguments[0][0], "ABBY", "arguments not processed as expected") - self.assertEqual(parsed_arguments[3], "test", "arguments not processed as expected") - self.assertEqual(parsed_arguments[4], False, "arguments not processed as expected") - self.assertEqual(parsed_arguments[2], "ad", "arguments not processed as expected") - - -if __name__ == "__main__": - unit_testing.setup_for_tests() - unittest.main() diff --git a/python/ctsm/test/test_unit_neon_site.py b/python/ctsm/test/test_unit_neon_site.py index 8ef6034f94..909bd3e3c0 100755 --- a/python/ctsm/test/test_unit_neon_site.py +++ b/python/ctsm/test/test_unit_neon_site.py @@ -26,7 +26,7 @@ class TestNeonSite(unittest.TestCase): """ - Basic class for testing NeonSite.py. + Basic class for testing neon_site.py. """ def setUp(self): diff --git a/python/ctsm/test/test_unit_run_neon.py b/python/ctsm/test/test_unit_run_tower.py similarity index 91% rename from python/ctsm/test/test_unit_run_neon.py rename to python/ctsm/test/test_unit_run_tower.py index 904db885a9..26099cf8ee 100755 --- a/python/ctsm/test/test_unit_run_neon.py +++ b/python/ctsm/test/test_unit_run_tower.py @@ -1,9 +1,9 @@ #!/usr/bin/env python3 """ -Unit tests for run_neon +Unit tests for run_tower You can run this by: - python -m unittest test_unit_run_neon.py + python -m unittest test_unit_run_tower.py """ import unittest @@ -18,14 +18,14 @@ # pylint: disable=wrong-import-position from ctsm import unit_testing -from ctsm.site_and_regional.run_neon import check_neon_listing +from ctsm.site_and_regional.run_tower import check_neon_listing # pylint: disable=invalid-name -class TestRunNeon(unittest.TestCase): +class TestRunTower(unittest.TestCase): """ - Basic class for testing run_neon.py. + Basic class for testing run_tower.py. """ def setUp(self): diff --git a/python/ctsm/test/test_unit_sspmatrix.py b/python/ctsm/test/test_unit_sspmatrix.py new file mode 100755 index 0000000000..1b1bc60185 --- /dev/null +++ b/python/ctsm/test/test_unit_sspmatrix.py @@ -0,0 +1,185 @@ +#!/usr/bin/env python3 +""" +Unit tests for the SystemTest sspmatrix.py +""" + +import unittest +import os +import sys +import tempfile +import shutil +import logging +from pathlib import Path + +from ctsm.path_utils import add_cime_lib_to_path, add_ctsm_systests_to_path +from ctsm import unit_testing + +add_ctsm_systests_to_path() +add_cime_lib_to_path() + +# pylint: disable=import-error +# pylint: disable=wrong-import-position +# pylint: disable=wrong-import-order +from SystemTests.sspmatrixcn import SSPMATRIXCN + +# pylint: disable=import-error +# pylint: disable=wrong-import-position +# pylint: disable=wrong-import-order +from CIME.tests.case_fake import CaseFake + +# pylint: disable=invalid-name + +logger = logging.getLogger(__name__) + + +class SSPCaseFake(CaseFake): + """ + Extend the CaseFake class with a couple things needed here + """ + + def __init__(self, case_root, tempdir, create_case_root=True): + """ + Initialization handling the tempdir + """ + super().__init__(case_root, create_case_root) + self._tempdir = tempdir + + def create_clone( + self, + newcase, + keepexe=False, + ): + """ + Extend to handle creation of user_nl_clm file + """ + clone = super().create_clone(newcase, keepexe=keepexe) + os.mknod(os.path.join(newcase, "user_nl_clm")) + # Also make the needed case directories + clone.make_case_dirs(self._tempdir) + return clone + + def make_case_dirs(self, tempdir): + """ + Create the directories needed for the CASE + """ + casename = self.get_value("CASE") + dout_s_root = Path(tempdir, "archive", casename) + dout_s_root.mkdir(parents=True) + self.set_value("DOUT_S_ROOT", str(dout_s_root)) + rest_root = Path(dout_s_root, "rest") + rest_root.mkdir() + rundir = Path(tempdir, casename, "run") + rundir.mkdir() + self.set_value("RUNDIR", rundir) + + def __str__(self): + """ + String method + """ + return "caseroot=%s" % (self.get_value("CASEROOT")) + + +class TestSSPMatrix(unittest.TestCase): + """ + Basic class for testing the sspmatrix.py SystemTest + """ + + def setUp(self): + """ + Setup test directory + """ + self._previous_dir = os.getcwd() + self._tempdir = tempfile.mkdtemp() + + # Set up the testing SSPCaseFake + caseroot = os.path.join(self._tempdir, "ssptestcase") + self._case = SSPCaseFake(caseroot, tempdir=self._tempdir, create_case_root=True) + os.chdir(caseroot) + + # Set XML variables that will be needed in the case + self._case.set_value("DATM_YR_START", 2000) + self._case.set_value("DATM_YR_END", 2001) + self._case.set_value("COMP_LND", "clm") + self._case.set_value("NINST", 1) + + self.ssp = SSPMATRIXCN(self._case) + self._case.make_case_dirs(self._tempdir) + + def tearDown(self): + """ + Remove temporary directory + """ + os.chdir(self._previous_dir) + shutil.rmtree(self._tempdir, ignore_errors=True) + + def test_logger(self): + """ + Test the logger + """ + stream_handler = logging.StreamHandler(sys.stdout) + logger.addHandler(stream_handler) + logger.level = logging.DEBUG + logger.info("nyr_forcing = %s", self.ssp.nyr_forcing) + for n in range(self.ssp.n_steps()): + self.ssp.__logger__(n) + if self.ssp.spin[n] == "sasu": + logger.info(" SASU spinup is .true.") + if self.ssp.sasu[n] != -999: + logger.info(" nyr_sasu = %s", self.ssp.sasu[n]) + if self.ssp.iloop[n] != -999: + logger.info(" iloop_avg = %s", self.ssp.iloop[n]) + + logger.info("Total number of years %s", self.ssp.total_years()) + logger.removeHandler(stream_handler) + + def test_n_steps(self): + """ + Test that n_steps is as expected + """ + self.assertTrue(self.ssp.n_steps() == 3) + + def test_valid_n(self): + """ + Test that check of n-step is good for the range it runs in + """ + for n in range(self.ssp.n_steps()): + self.ssp.check_n(n) + + def test_negative_n(self): + """ + Test that fails when n-step is negative + """ + self.assertRaises(SystemExit, self.ssp.check_n, -1) + + def test_n_too_big(self): + """ + Test that fails when n-step is too big + """ + self.assertRaises(SystemExit, self.ssp.check_n, self.ssp.n_steps()) + + def test_append_user_nl_step2(self): + """ + Test appending to user_nl_clm file for step 2 + """ + ufile = "user_nl_clm" + if os.path.exists(ufile): + os.remove(ufile) + + os.mknod(ufile) + + expect = "\nhist_nhtfrq = -8760, hist_mfilt = 2\n" + self.ssp.append_user_nl(caseroot=".", n=2) + log = open(ufile, "r").read() + self.assertEqual(expect, log, "Append user_nl_clm file NOT as expected for step 2") + os.remove(ufile) + + def test_run_phase(self): + """ + Test doing the standard run_phase, that does each step + """ + self.ssp.run_phase() + + +if __name__ == "__main__": + unit_testing.setup_for_tests() + unittest.main() diff --git a/python/ctsm/test/test_unit_tower_arg_parse.py b/python/ctsm/test/test_unit_tower_arg_parse.py new file mode 100755 index 0000000000..1321cd31dd --- /dev/null +++ b/python/ctsm/test/test_unit_tower_arg_parse.py @@ -0,0 +1,107 @@ +#!/usr/bin/env python3 +""" +Unit tests for tower_arg_parse + +You can run this by: + python -m unittest test_unit_tower_arg_parse.py +""" + +import unittest +import tempfile +import shutil +import os +import sys +import glob + +# -- add python/ctsm to path (needed if we want to run the test stand-alone) +_CTSM_PYTHON = os.path.join(os.path.dirname(os.path.realpath(__file__)), os.pardir, os.pardir) +sys.path.insert(1, _CTSM_PYTHON) + +# pylint: disable=wrong-import-position +from ctsm import unit_testing +from ctsm.site_and_regional.tower_arg_parse import get_parser +from ctsm.path_utils import path_to_ctsm_root + +# pylint: disable=invalid-name + + +class Test_tower_arg_parse(unittest.TestCase): + """ + Basic class for testing tower_arg_parse.py. + """ + + def setUp(self): + """ + Make /_tempdir for use by these tests. + """ + self._previous_dir = os.getcwd() + self._tempdir = tempfile.mkdtemp() + + def tearDown(self): + """ + Remove temporary directory + """ + os.chdir(self._previous_dir) + shutil.rmtree(self._tempdir, ignore_errors=True) + + def test_neon(self): + """ + Test that tower_arg_parse is properly reading arguments for neon... + """ + sys.argv = [ + "tower_arg_parse", + "--neon-sites", + "ABBY", + "--experiment", + "test", + "--run-type", + "ad", + ] + description = "" + cesmroot = path_to_ctsm_root() + valid_neon_sites = glob.glob( + os.path.join(cesmroot, "cime_config", "usermods_dirs", "clm", "NEON", "[!Fd]*") + ) + valid_neon_sites = sorted([v.split("/")[-1] for v in valid_neon_sites]) + + valid_plumber_sites = glob.glob( + os.path.join(cesmroot, "cime_config", "usermods_dirs", "clm", "PLUMBER2", "[!d]*") + ) + valid_plumber_sites = sorted([v.split("/")[-1] for v in valid_plumber_sites]) + + parsed_arguments = get_parser(sys.argv, description, valid_neon_sites, valid_plumber_sites) + + self.assertEqual(parsed_arguments[0][0], "ABBY", "arguments not processed as expected") + self.assertEqual(parsed_arguments[4], "test", "arguments not processed as expected") + self.assertEqual(parsed_arguments[5], False, "arguments not processed as expected") + self.assertEqual(parsed_arguments[3], "ad", "arguments not processed as expected") + + def test_plumber(self): + """ + Test that tower_arg_parse is properly reading arguments for plumber... + """ + sys.argv = [ + "tower_arg_parse", + "--plumber-sites", + "AU-Emr", + ] + description = "" + cesmroot = path_to_ctsm_root() + + valid_neon_sites = glob.glob( + os.path.join(cesmroot, "cime_config", "usermods_dirs", "clm", "NEON", "[!Fd]*") + ) + valid_neon_sites = sorted([v.split("/")[-1] for v in valid_neon_sites]) + + valid_plumber_sites = glob.glob( + os.path.join(cesmroot, "cime_config", "usermods_dirs", "clm", "PLUMBER2", "[!d]*") + ) + valid_plumber_sites = sorted([v.split("/")[-1] for v in valid_plumber_sites]) + + parsed_arguments = get_parser(sys.argv, description, valid_neon_sites, valid_plumber_sites) + self.assertEqual(parsed_arguments[1][0], "AU-Emr", "arguments not processed as expected") + + +if __name__ == "__main__": + unit_testing.setup_for_tests() + unittest.main() diff --git a/python/ctsm/utils.py b/python/ctsm/utils.py index a5a02a5c9d..e224c5e06e 100644 --- a/python/ctsm/utils.py +++ b/python/ctsm/utils.py @@ -237,3 +237,15 @@ def parse_isoduration(iso_string): # Convert all to timedelta delta_t = timedelta(days=int(days) + 365 * int(years) + 30 * int(months)) return int(delta_t.total_seconds() / 86400) + + +def is_instantaneous(time_var): + """ + Check whether a time variable came from an instantaneous file + """ + long_name = time_var.attrs["long_name"] + if "time at end of" in long_name: + return True + if "time at exact middle" in long_name: + return False + raise RuntimeError(f"Does this long_name mean instantaneous or not? {long_name}") diff --git a/share b/share index a48ff8790a..c5e7603c29 160000 --- a/share +++ b/share @@ -1 +1 @@ -Subproject commit a48ff8790a21d3831873ed9f023a43c606a1ef03 +Subproject commit c5e7603c29ea5e2fe93ca16d88bc9c7f16175bcd diff --git a/src/biogeophys/SoilTemperatureMod.F90 b/src/biogeophys/SoilTemperatureMod.F90 index d6c9660b96..11b84e49d8 100644 --- a/src/biogeophys/SoilTemperatureMod.F90 +++ b/src/biogeophys/SoilTemperatureMod.F90 @@ -541,7 +541,7 @@ subroutine SoilTemperature(bounds, num_urbanl, filter_urbanl, num_urbanc, filter if ( IsProgBuildTemp() )then call BuildingTemperature(bounds, num_urbanl, filter_urbanl, num_nolakec, filter_nolakec, & tk(bounds%begc:bounds%endc, :), urbanparams_inst, & - temperature_inst, energyflux_inst, urbantv_inst) + temperature_inst, energyflux_inst, urbantv_inst, atm2lnd_inst) end if do fc = 1,num_nolakec diff --git a/src/biogeophys/UrbBuildTempOleson2015Mod.F90 b/src/biogeophys/UrbBuildTempOleson2015Mod.F90 index c680b13306..3081948f6b 100644 --- a/src/biogeophys/UrbBuildTempOleson2015Mod.F90 +++ b/src/biogeophys/UrbBuildTempOleson2015Mod.F90 @@ -16,6 +16,7 @@ module UrbBuildTempOleson2015Mod use UrbanTimeVarType , only : urbantv_type use EnergyFluxType , only : energyflux_type use TemperatureType , only : temperature_type + use atm2lndType , only : atm2lnd_type use LandunitType , only : lun use ColumnType , only : col ! @@ -42,7 +43,7 @@ module UrbBuildTempOleson2015Mod ! !INTERFACE: subroutine BuildingTemperature (bounds, num_urbanl, filter_urbanl, num_nolakec, & filter_nolakec, tk, urbanparams_inst, temperature_inst, & - energyflux_inst, urbantv_inst) + energyflux_inst, urbantv_inst, atm2lnd_inst) ! ! !DESCRIPTION: ! Solve for t_building, inner surface temperatures of roof, sunw, shdw, and floor temperature @@ -202,7 +203,7 @@ subroutine BuildingTemperature (bounds, num_urbanl, filter_urbanl, num_nolakec, ! !USES: use shr_kind_mod , only : r8 => shr_kind_r8 use clm_time_manager, only : get_step_size_real - use clm_varcon , only : rair, pstd, cpair, sb, hcv_roof, hcv_roof_enhanced, & + use clm_varcon , only : rair, cpair, sb, hcv_roof, hcv_roof_enhanced, & hcv_floor, hcv_floor_enhanced, hcv_sunw, hcv_shdw, & em_roof_int, em_floor_int, em_sunw_int, em_shdw_int, & dz_floor, dens_floor, cp_floor, vent_ach @@ -224,10 +225,11 @@ subroutine BuildingTemperature (bounds, num_urbanl, filter_urbanl, num_nolakec, type(temperature_type), intent(inout) :: temperature_inst ! temperature variables type(energyflux_type) , intent(inout) :: energyflux_inst ! energy flux variables type(urbantv_type) , intent(in) :: urbantv_inst ! urban time varying variables + type(atm2lnd_type) , intent(in) :: atm2lnd_inst ! forcing variables from atmosphere ! ! !LOCAL VARIABLES: integer, parameter :: neq = 5 ! number of equation/unknowns - integer :: fc,fl,c,l ! indices + integer :: fc,fl,c,l,g ! indices real(r8) :: dtime ! land model time step (s) real(r8) :: building_hwr(bounds%begl:bounds%endl) ! building height to building width ratio (-) real(r8) :: t_roof_inner_bef(bounds%begl:bounds%endl) ! roof inside surface temperature at previous time step (K) @@ -310,6 +312,7 @@ subroutine BuildingTemperature (bounds, num_urbanl, filter_urbanl, num_nolakec, ctype => col%itype , & ! Input: [integer (:)] column type zi => col%zi , & ! Input: [real(r8) (:,:)] interface level below a "z" level (m) z => col%z , & ! Input: [real(r8) (:,:)] layer thickness (m) + forc_pbot => atm2lnd_inst%forc_pbot_not_downscaled_grc, & ! Input: [real(r8) (:)] atmospheric pressure (Pa) ht_roof => lun%ht_roof , & ! Input: [real(r8) (:)] height of urban roof (m) canyon_hwr => lun%canyon_hwr , & ! Input: [real(r8) (:)] ratio of building height to street hwidth (-) @@ -348,6 +351,7 @@ subroutine BuildingTemperature (bounds, num_urbanl, filter_urbanl, num_nolakec, ! 5. Calculate building height to building width ratio do fl = 1,num_urbanl l = filter_urbanl(fl) + g = lun%gridcell(l) if (urbpoi(l)) then t_roof_inner_bef(l) = t_roof_inner(l) t_sunw_inner_bef(l) = t_sunw_inner(l) @@ -376,8 +380,8 @@ subroutine BuildingTemperature (bounds, num_urbanl, filter_urbanl, num_nolakec, cp_floori(l) = cp_floor ! Intermediate calculation for concrete floor (W m-2 K-1) cv_floori(l) = (dz_floori(l) * cp_floori(l)) / dtime - ! Density of dry air at standard pressure and t_building (kg m-3) - rho_dair(l) = pstd / (rair*t_building_bef(l)) + ! Density of dry air at surface pressure and t_building (kg m-3) + rho_dair(l) = forc_pbot(g) / (rair*t_building_bef(l)) ! Building height to building width ratio building_hwr(l) = canyon_hwr(l)*(1._r8-wtlunit_roof(l))/wtlunit_roof(l) end if diff --git a/src/cpl/lilac/lnd_comp_esmf.F90 b/src/cpl/lilac/lnd_comp_esmf.F90 index a2424a128f..0aa0fc9f81 100644 --- a/src/cpl/lilac/lnd_comp_esmf.F90 +++ b/src/cpl/lilac/lnd_comp_esmf.F90 @@ -374,7 +374,7 @@ subroutine lnd_init(comp, import_state, export_state, clock, rc) !-------------------------------- ! Finish initializing ctsm !-------------------------------- - call initialize2(ni,nj) + call initialize2(ni,nj, currTime) call ESMF_LogWrite(subname//"ctsm initialize2 done...", ESMF_LOGMSG_INFO) !-------------------------------- @@ -694,41 +694,41 @@ subroutine lnd_run(gcomp, import_state, export_state, clock, rc) call update_rad_dtime(doalb) !-------------------------------- - ! Determine if time to write restart + ! Determine if time to stop !-------------------------------- - call ESMF_ClockGetAlarm(clock, alarmname='lilac_restart_alarm', alarm=alarm, rc=rc) + call ESMF_ClockGetAlarm(clock, alarmname='lilac_stop_alarm', alarm=alarm, rc=rc) if (ChkErr(rc,__LINE__,u_FILE_u)) return if (ESMF_AlarmIsRinging(alarm, rc=rc)) then if (ChkErr(rc,__LINE__,u_FILE_u)) return - rstwr = .true. + nlend = .true. call ESMF_AlarmRingerOff( alarm, rc=rc ) if (ChkErr(rc,__LINE__,u_FILE_u)) return else - rstwr = .false. + nlend = .false. endif if (masterproc) then - write(iulog,*)' restart alarm is ',rstwr + write(iulog,*)' stop alarm is ',nlend end if !-------------------------------- - ! Determine if time to stop + ! Determine if time to write restart !-------------------------------- - call ESMF_ClockGetAlarm(clock, alarmname='lilac_stop_alarm', alarm=alarm, rc=rc) + call ESMF_ClockGetAlarm(clock, alarmname='lilac_restart_alarm', alarm=alarm, rc=rc) if (ChkErr(rc,__LINE__,u_FILE_u)) return if (ESMF_AlarmIsRinging(alarm, rc=rc)) then if (ChkErr(rc,__LINE__,u_FILE_u)) return - nlend = .true. + rstwr = .true. call ESMF_AlarmRingerOff( alarm, rc=rc ) if (ChkErr(rc,__LINE__,u_FILE_u)) return else - nlend = .false. + rstwr = .false. endif if (masterproc) then - write(iulog,*)' stop alarm is ',nlend + write(iulog,*)' restart alarm is ',rstwr end if !-------------------------------- diff --git a/src/cpl/nuopc/lnd_comp_nuopc.F90 b/src/cpl/nuopc/lnd_comp_nuopc.F90 index 33fc8e6f54..8ee6c2014e 100644 --- a/src/cpl/nuopc/lnd_comp_nuopc.F90 +++ b/src/cpl/nuopc/lnd_comp_nuopc.F90 @@ -1031,9 +1031,10 @@ subroutine ModelSetRunClock(gcomp, rc) if (ChkErr(rc,__LINE__,u_FILE_u)) return call ESMF_LogWrite(subname//'setting alarms for ' // trim(name), ESMF_LOGMSG_INFO) - !---------------- + !---------------------------------------------------------------------------------- ! Stop alarm - !---------------- + ! MUST be set before the restart alarm in case restarts happen at the stop alarm + !---------------------------------------------------------------------------------- call NUOPC_CompAttributeGet(gcomp, name="stop_option", value=stop_option, rc=rc) if (ChkErr(rc,__LINE__,u_FILE_u)) return @@ -1055,9 +1056,10 @@ subroutine ModelSetRunClock(gcomp, rc) call ESMF_AlarmSet(stop_alarm, clock=mclock, rc=rc) if (ChkErr(rc,__LINE__,u_FILE_u)) return - !---------------- + !---------------------------------------------------------------------------------- ! Restart alarm - !---------------- + ! MUST be set after the stop alarm in case restarts happen at the stop alarm + !---------------------------------------------------------------------------------- call NUOPC_CompAttributeGet(gcomp, name="restart_option", value=restart_option, rc=rc) if (ChkErr(rc,__LINE__,u_FILE_u)) return diff --git a/src/fates b/src/fates index 296e1d6a45..ccc1de04ab 160000 --- a/src/fates +++ b/src/fates @@ -1 +1 @@ -Subproject commit 296e1d6a45f05a800073d376286d0537d2290e96 +Subproject commit ccc1de04abcc14dcc8a24bb06f271bc04e2bc48c diff --git a/src/main/controlMod.F90 b/src/main/controlMod.F90 index 93d3d46b92..6b5a994aec 100644 --- a/src/main/controlMod.F90 +++ b/src/main/controlMod.F90 @@ -527,6 +527,11 @@ subroutine control_init(dtime) errMsg(sourcefile, __LINE__)) end if + if (z0param_method == 'Meier2022') then + call endrun(msg=' ERROR: Surface roughness parameterization Meier2022 is not compatible with FATES.'//& + errMsg(sourcefile, __LINE__)) + end if + else ! These do default to false anyway, but this emphasizes they diff --git a/src/main/histFileMod.F90 b/src/main/histFileMod.F90 index da7fb67c5d..f5147559d9 100644 --- a/src/main/histFileMod.F90 +++ b/src/main/histFileMod.F90 @@ -147,7 +147,7 @@ module histFileMod public :: hist_addfld1d ! Add a 1d single-level field to the list of all history fields public :: hist_addfld2d ! Add a 2d multi-level field to the list of all history fields public :: hist_addfld_decomp ! Add a 1d/2d field based on patch or column data - public :: hist_add_subscript ! Add a 2d subscript dimension + public :: hist_printflds ! Print summary of list of all history fields public :: htapes_fieldlist ! Finalize history file field lists, intersecting allhistfldlist with @@ -196,10 +196,6 @@ module histFileMod ! is 255. But this can't be increased until all hard ! coded values throughout the i/o stack are updated. integer, parameter :: max_chars = 199 ! max chars for char variables - integer, parameter :: max_subs = 100 ! max number of subscripts - integer :: num_subs = 0 ! actual number of subscripts - character(len=32) :: subs_name(max_subs) ! name of subscript - integer :: subs_dim(max_subs) ! dimension of subscript ! type2d value for a field without a level dimension. This value is important for the ! following reasons (as of 2023-08-21): @@ -256,8 +252,11 @@ end subroutine copy_entry_interface end interface ! Additional per-field metadata. See also history_entry. - ! These values are specified in hist_addfld* calls but then can be - ! overridden by namelist params like hist_fincl1. + ! For the primary history tape, some fields are enabled here (inside hist_addfld* + ! call) but then can be overridden by namelist params (like hist_fincl1). The + ! fields for other history tapes are theoretically settable here but in + ! practice are all disabled. Fields for those tapes have to be specified + ! explicitly and manually via hist_fincl2 et al. type, extends(entry_base) :: allhistfldlist_entry logical :: actflag(max_tapes) ! which history tapes to write to. character(len=avgflag_strlen) :: avgflag(max_tapes) ! type of time averaging @@ -307,8 +306,8 @@ end subroutine copy_entry_interface type (clmpoint_ra) :: clmptr_ra(max_mapflds) ! Real array data (2D) ! ! History field metadata including which history tapes (if any) it should be output to, and - ! type of accumulation to perform. The field ordering is arbitrary, depending on the order of - ! hist_addfld* calls in the code. + ! type of accumulation to perform. This list contains all possible fields, and their field ordering + ! is arbitrary, as it depends on the order of hist_addfld* calls in the code. ! For the field data itself, see 'tape'. ! type (allhistfldlist_entry) :: allhistfldlist(max_flds) ! list of all history fields @@ -341,7 +340,7 @@ end subroutine copy_entry_interface type(file_desc_t), target :: nfid(max_tapes) ! file ids type(file_desc_t), target :: ncid_hist(max_tapes) ! file ids for history restart files integer :: time_dimid ! time dimension id - integer :: hist_interval_dimid ! time bounds dimension id + integer :: nbnd_dimid ! time bounds dimension id integer :: strlen_dimid ! string dimension id ! ! Time Constant variable names and filename @@ -2504,9 +2503,6 @@ subroutine htape_create (t, histrest) ! (although on the history file it will go 1:(nec+1) rather than 0:nec) call ncd_defdim(lnfid, 'elevclas' , maxpatch_glc + 1, dimid) - do n = 1,num_subs - call ncd_defdim(lnfid, subs_name(n), subs_dim(n), dimid) - end do call ncd_defdim(lnfid, 'string_length', hist_dim_name_length, strlen_dimid) call ncd_defdim(lnfid, 'scale_type_string_length', scale_type_strlen, dimid) call ncd_defdim( lnfid, 'levdcmp', nlevdecomp_full, dimid) @@ -2542,7 +2538,7 @@ subroutine htape_create (t, histrest) end if if ( .not. lhistrest )then - call ncd_defdim(lnfid, 'hist_interval', 2, hist_interval_dimid) + call ncd_defdim(lnfid, 'nbnd', 2, nbnd_dimid) call ncd_defdim(lnfid, 'time', ncd_unlimited, time_dimid) if (masterproc)then write(iulog,*) trim(subname), & @@ -3396,6 +3392,7 @@ subroutine htape_timeconst(t, mode) long_name = 'current date (YYYYMMDD) at end of ' // step_or_bounds call ncd_defvar(nfid(t) , 'mcdate', ncd_int, 1, dim1id , varid, & long_name = long_name) + call ncd_putatt(nfid(t), varid, 'calendar', caldesc) ! ! add global attribute time_period_freq ! @@ -3425,19 +3422,24 @@ subroutine htape_timeconst(t, mode) long_name = 'current seconds of current date at end of ' // step_or_bounds call ncd_defvar(nfid(t) , 'mcsec' , ncd_int, 1, dim1id , varid, & long_name = long_name, units='s') + call ncd_putatt(nfid(t), varid, 'calendar', caldesc) long_name = 'current day (from base day) at end of ' // step_or_bounds call ncd_defvar(nfid(t) , 'mdcur' , ncd_int, 1, dim1id , varid, & long_name = long_name) + call ncd_putatt(nfid(t), varid, 'calendar', caldesc) long_name = 'current seconds of current day at end of ' // step_or_bounds call ncd_defvar(nfid(t) , 'mscur' , ncd_int, 1, dim1id , varid, & long_name = long_name) + call ncd_putatt(nfid(t), varid, 'calendar', caldesc) call ncd_defvar(nfid(t) , 'nstep' , ncd_int, 1, dim1id , varid, & long_name = 'time step') - dim2id(1) = hist_interval_dimid; dim2id(2) = time_dimid + dim2id(1) = nbnd_dimid; dim2id(2) = time_dimid if (hist_avgflag_pertape(t) /= 'I') then ! NOT instantaneous fields tape call ncd_defvar(nfid(t), 'time_bounds', ncd_double, 2, dim2id, varid, & - long_name = 'history time interval endpoints') + long_name = 'time interval endpoints', & + units = str) + call ncd_putatt(nfid(t), varid, 'calendar', caldesc) end if dim2id(1) = strlen_dimid; dim2id(2) = time_dimid @@ -5967,31 +5969,6 @@ integer function next_history_pointer_index () end function next_history_pointer_index - !----------------------------------------------------------------------- - subroutine hist_add_subscript(name, dim) - ! - ! !DESCRIPTION: - ! Add a history variable to the output history tape. - ! - ! !ARGUMENTS: - character(len=*), intent(in) :: name ! name of subscript - integer , intent(in) :: dim ! dimension of subscript - ! - ! !LOCAL VARIABLES: - character(len=*),parameter :: subname = 'hist_add_subscript' - !----------------------------------------------------------------------- - - num_subs = num_subs + 1 - if (num_subs > max_subs) then - write(iulog,*) trim(subname),' ERROR: ',& - ' num_subs = ',num_subs,' greater than max_subs= ',max_subs - call endrun(msg=errMsg(sourcefile, __LINE__)) - endif - subs_name(num_subs) = name - subs_dim(num_subs) = dim - - end subroutine hist_add_subscript - !----------------------------------------------------------------------- subroutine strip_null(str) diff --git a/src/utils/clm_time_manager.F90 b/src/utils/clm_time_manager.F90 index 3e3d1a8c45..f606c2832b 100644 --- a/src/utils/clm_time_manager.F90 +++ b/src/utils/clm_time_manager.F90 @@ -19,9 +19,9 @@ module clm_time_manager public ::& set_timemgr_init, &! setup startup values - timemgr_init, &! time manager initialization + timemgr_init, &! time manager initialization, called always timemgr_restart_io, &! read/write time manager restart info and restart time manager - timemgr_restart, &! restart the time manager using info from timemgr_restart + timemgr_restart, &! check that time manager is setup coorectly upcon restart timemgr_datediff, &! calculate difference between two time instants advance_timestep, &! increment timestep number get_curr_ESMF_Time, &! get current time in terms of the ESMF_Time @@ -157,7 +157,7 @@ subroutine set_timemgr_init( calendar_in, start_ymd_in, start_tod_in, r ! character(len=*), parameter :: sub = 'clm::set_timemgr_init' - if ( timemgr_set ) call shr_sys_abort( sub//":: timemgr_init or timemgr_restart already called" ) + if ( timemgr_set ) call shr_sys_abort( sub//":: timemgr_init already called" ) if (present(calendar_in) ) calendar = trim(calendar_in) if (present(start_ymd_in) ) start_ymd = start_ymd_in if (present(start_tod_in) ) start_tod = start_tod_in @@ -513,7 +513,10 @@ end subroutine timemgr_restart_io subroutine timemgr_restart() !--------------------------------------------------------------------------------- - ! Restart the ESMF time manager using the synclock for ending date. + ! On restart do some checkcing to make sure time is synchronized with the clock from CESM. + ! Set a couple of variables, and advance the clock, so time is aligned properly. + ! + ! timemgr_init MIST be called before this ! character(len=*), parameter :: sub = 'clm::timemgr_restart' @@ -525,13 +528,10 @@ subroutine timemgr_restart() type(ESMF_TimeInterval) :: day_step_size ! day step size type(ESMF_TimeInterval) :: step_size ! timestep size !--------------------------------------------------------------------------------- - call timemgr_spmdbcast( ) - - ! Initialize calendar from restart info - - call init_calendar() + ! Check that timemgr_init was already called + if ( .not. check_timemgr_initialized(sub) ) return - ! Initialize the timestep from restart info + ! Initialize the timestep dtime = rst_step_sec @@ -563,12 +563,6 @@ subroutine timemgr_restart() tm_first_restart_step = .true. - ! Print configuration summary to log file (stdout). - - if (masterproc) call timemgr_print() - - timemgr_set = .true. - end subroutine timemgr_restart !========================================================================================= diff --git a/src/utils/clmfates_interfaceMod.F90 b/src/utils/clmfates_interfaceMod.F90 index e98633ee18..13815a0722 100644 --- a/src/utils/clmfates_interfaceMod.F90 +++ b/src/utils/clmfates_interfaceMod.F90 @@ -310,10 +310,11 @@ subroutine CLMFatesGlobals1(surf_numpft,surf_numcft,maxsoil_patches) integer,intent(in) :: surf_numpft integer,intent(in) :: surf_numcft integer,intent(out) :: maxsoil_patches - integer :: pass_biogeog - integer :: pass_nocomp - integer :: pass_sp + integer :: pass_use_fixed_biogeog + integer :: pass_use_nocomp + integer :: pass_use_sp integer :: pass_masterproc + integer :: pass_use_luh2 logical :: verbose_output type(fates_param_reader_ctsm_impl) :: var_reader @@ -330,25 +331,25 @@ subroutine CLMFatesGlobals1(surf_numpft,surf_numcft,maxsoil_patches) ! Send parameters individually if(use_fates_fixed_biogeog)then - pass_biogeog = 1 + pass_use_fixed_biogeog = 1 else - pass_biogeog = 0 + pass_use_fixed_biogeog = 0 end if - call set_fates_ctrlparms('use_fixed_biogeog',ival=pass_biogeog) + call set_fates_ctrlparms('use_fixed_biogeog',ival=pass_use_fixed_biogeog) if(use_fates_nocomp)then - pass_nocomp = 1 + pass_use_nocomp = 1 else - pass_nocomp = 0 + pass_use_nocomp = 0 end if - call set_fates_ctrlparms('use_nocomp',ival=pass_nocomp) + call set_fates_ctrlparms('use_nocomp',ival=pass_use_nocomp) if(use_fates_sp)then - pass_sp = 1 + pass_use_sp = 1 else - pass_sp = 0 + pass_use_sp = 0 end if - call set_fates_ctrlparms('use_sp',ival=pass_sp) + call set_fates_ctrlparms('use_sp',ival=pass_use_sp) if(masterproc)then pass_masterproc = 1 @@ -357,6 +358,14 @@ subroutine CLMFatesGlobals1(surf_numpft,surf_numcft,maxsoil_patches) end if call set_fates_ctrlparms('masterproc',ival=pass_masterproc) + ! FATES landuse modes + if(use_fates_luh) then + pass_use_luh2 = 1 + else + pass_use_luh2 = 0 + end if + call set_fates_ctrlparms('use_luh2',ival=pass_use_luh2) + end if @@ -404,7 +413,6 @@ subroutine CLMFatesGlobals2() integer :: pass_is_restart integer :: pass_cohort_age_tracking integer :: pass_tree_damage - integer :: pass_use_luh integer :: pass_use_potentialveg integer :: pass_num_luh_states integer :: pass_num_luh_transitions @@ -625,16 +633,12 @@ subroutine CLMFatesGlobals2() ! FATES landuse modes if(use_fates_luh) then - pass_use_luh = 1 pass_num_luh_states = num_landuse_state_vars pass_num_luh_transitions = num_landuse_transition_vars else - pass_use_luh = 0 pass_num_luh_states = 0 pass_num_luh_transitions = 0 end if - - call set_fates_ctrlparms('use_luh2',ival=pass_use_luh) call set_fates_ctrlparms('num_luh2_states',ival=pass_num_luh_states) call set_fates_ctrlparms('num_luh2_transitions',ival=pass_num_luh_transitions) diff --git a/tools/contrib/test_rxcropmaturity_python.sh b/tools/contrib/test_rxcropmaturity_python.sh new file mode 100755 index 0000000000..69e9b00a04 --- /dev/null +++ b/tools/contrib/test_rxcropmaturity_python.sh @@ -0,0 +1,107 @@ +#!/usr/bin/env bash +set -e + +# This script is designed to easily test updated versions of the code in python/ctsm/crop_calendars/ +# on outputs of the rxcropmaturity test suite. For RXCROPMATURITYSKIPGEN tests (i.e., skipping the +# GDD-generating step, it will rerun check_rxboth_run.py. It will also do this for RXCROPMATURITY +# tests whose call of generate_gdds.py completed successfully. For RXCROPMATURITY tests where that +# failed, it will retry the generate_gdds.py call. Tests are performed with both the ctsm_pylib +# and npl conda environments. +# +# Note that the python/ctsm/crop_calendars/ this test uses will be in the same CTSM directory as +# you used to start the tests. +# +# The script takes one positional input: +# suite_dir: The directory where your test suite was performed. E.g., $SCRATCH/tests_0123-142858de. +# +# Output will look something like this (✅ for success, 🔴 for failure): +# ctsm_pylib +# RXCROPMATURITYINST_Lm61.f10_f10_mg37.IHist check_rxboth_run ✅ +# RXCROPMATURITY_Lm61.f10_f10_mg37.IHist generate_gdds 🔴 +# RXCROPMATURITYSKIPGENINST_Ld1097.f10_f10_mg37.IHist check_rxboth_run ✅ +# RXCROPMATURITYSKIPGEN_Ld1097.f10_f10_mg37.IHist check_rxboth_run 🔴 +# npl +# RXCROPMATURITYINST_Lm61.f10_f10_mg37.IHist check_rxboth_run ✅ +# RXCROPMATURITY_Lm61.f10_f10_mg37.IHist generate_gdds ✅ +# RXCROPMATURITYSKIPGENINST_Ld1097.f10_f10_mg37.IHist check_rxboth_run ✅ +# RXCROPMATURITYSKIPGEN_Ld1097.f10_f10_mg37.IHist check_rxboth_run ✅ +# +# Log files for each will be saved as TEST_SHORTNAME.CONDA_ENV.log. + +# Process input arguments +suite_dir="$1" +if [[ ! -d "${suite_dir}" ]]; then + echo "You must provide suite_dir" >&2 + exit 1 +fi + +# Where do we save log files? +log_dir="$PWD" + +cd "${suite_dir}" +test_dir_list="$(ls -d RXCROPMATURITY*/ | grep -v gddgen)" + +for conda_env in ctsm_pylib npl; do + echo "${conda_env}" + for d in ${test_dir_list}; do + + # Get test shortname + t="$(echo $d | grep -oE ".*IHist")" + echo -n " $t " + + # Set up + pushd $d 1>/dev/null 2>&1 + logfile="${log_dir}/${t}.${conda_env}.log" + + # Get python command to test + script="check_rxboth_run" + set +e + cmd="$(grep -h "${script}.py" *int.o* | grep -oE "python3.*" | tail -n 1)" + set -e + if [[ "${cmd}" == "" ]]; then + # check_rxboth_run.py wasn't run. Look for generate_gdds.py command. + script="generate_gdds" + set +e + cmd="$(grep -h "${script}.py" *int.o* | grep -oE "python3.*" | tail -n 1)" + set -e + + # Neither were found + if [[ "${cmd}" == "" ]]; then + echo -e "\n Command not found" >&2 + popd 1>/dev/null 2>&1 + continue + fi + + cd ../$t*gddgen + fi + + # Strip extraneous text off command + cmd="$(echo ${cmd} | sed "s/ returned non-zero exit status 1.//")" + cmd="$(echo ${cmd} | sed -E "s/ '$//")" + cmd="$(echo ${cmd} | sed -E "s/'$//")" + + # generate_gdds.py should be run with --no-pickle for troubleshooting + if [[ "${cmd}" == *"generate_gdds.py "* ]]; then + cmd="${cmd} --no-pickle" + fi + + # Run the command + echo -n "${script} " + set +e + conda run -n ${conda_env} ${cmd} 1>"${logfile}" 2>&1 + result=$? + set -e + + # Print emoji to indicate result + if [[ ${result} -eq 0 ]]; then + echo ✅ + else + echo 🔴 + fi + + # Return to test suite directory + popd 1>/dev/null 2>&1 + done +done + +exit 0 diff --git a/tools/site_and_regional/run_neon b/tools/site_and_regional/run_tower similarity index 86% rename from tools/site_and_regional/run_neon rename to tools/site_and_regional/run_tower index ffc3be2af7..096c649302 100755 --- a/tools/site_and_regional/run_neon +++ b/tools/site_and_regional/run_tower @@ -1,12 +1,12 @@ #!/usr/bin/env python3 """ This is a just top-level skeleton script that calls -run_neon.py. -The original code (run_neon.py) is located under +run_tower.py. +The original code (run_tower.py) is located under python/ctsm/site_and_regional folder. For full instructions on how to run the code and different options, -please check python/ctsm/site_and_regional/run_neon.py file. +please check python/ctsm/site_and_regional/run_tower.py file. This script first creates and builds a generic base case. Next, it will clone the base_case for different neon sites and run @@ -25,7 +25,7 @@ This script will do the following: ---------------------------------------------------------------- To see all available options for running tower sites: - ./run_neon --help + ./run_tower --help ---------------------------------------------------------------- Instructions for running using conda python environments: ../../py_env_create @@ -42,7 +42,7 @@ _CTSM_PYTHON = os.path.join( sys.path.insert(1, _CTSM_PYTHON) # pylint: disable=import-error, wrong-import-position -from ctsm.site_and_regional.run_neon import main +from ctsm.site_and_regional.run_tower import main if __name__ == "__main__": main(__doc__)