Skip to content

Commit 2c44dbe

Browse files
authored
Merge pull request #3578 from ekluzek/merge-b4bdev-20251030
ctsm5.3.084: Merge b4bdev 20251030
2 parents f4b5699 + 817afc9 commit 2c44dbe

File tree

10 files changed

+260
-18
lines changed

10 files changed

+260
-18
lines changed

.github/workflows/docs-build-and-deploy.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,10 @@ concurrency:
3333
jobs:
3434

3535
build-and-deploy:
36+
37+
# Only run on upstream repository
38+
if: ${{ github.repository == 'ESCOMP/CTSM' }}
39+
3640
environment:
3741
name: github-pages
3842
url: ${{ steps.deployment.outputs.page_url }}

doc/ChangeLog

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,90 @@
11
===============================================================
2+
Tag name: ctsm5.3.084
3+
Originator(s): erik (Erik Kluzek,UCAR/TSS,303-497-1326)
4+
Date: Fri 31 Oct 2025 01:12:26 PM MDT
5+
One-line Summary: Merge b4b-dev to master
6+
7+
Purpose and description of changes
8+
----------------------------------
9+
10+
Bring changes on b4b-dev to master
11+
12+
- Fix FUNIT testing on Mac's
13+
- Some fixes to set_paramfile
14+
- Don't auto build documentation on personal forks, only on ESCOMP
15+
16+
Significant changes to scientifically-supported configurations
17+
--------------------------------------------------------------
18+
19+
Does this tag change answers significantly for any of the following physics configurations?
20+
(Details of any changes will be given in the "Answer changes" section below.)
21+
22+
[Put an [X] in the box for any configuration with significant answer changes.]
23+
24+
[ ] clm6_0
25+
26+
[ ] clm5_0
27+
28+
[ ] ctsm5_0-nwp
29+
30+
[ ] clm4_5
31+
32+
33+
Bugs fixed
34+
----------
35+
List of CTSM issues fixed (include CTSM Issue # and description) [one per line]:
36+
Fixes #3571 -- set_paramfile ordering of PFT's from user doesn't have to match paramfile order
37+
Fixes #3559 -- Correct Ndims error
38+
Fixes #3369 -- Docs build and deploy only runs on ESCOMP not personal forks
39+
40+
Notes of particular relevance for users
41+
---------------------------------------
42+
43+
Changes to documentation:
44+
Some changes to set_paramfile documentation
45+
46+
Notes of particular relevance for developers:
47+
---------------------------------------------
48+
49+
Testing summary: regular
50+
----------------
51+
52+
[PASS means all tests PASS; OK means tests PASS other than expected fails.]
53+
54+
build-namelist tests (if CLMBuildNamelist.pm has changed):
55+
56+
derecho - OK
57+
58+
python testing (if python code has changed; see instructions in python/README.md; document testing done):
59+
60+
derecho - PASS
61+
62+
regular tests (aux_clm: https://github.com/ESCOMP/CTSM/wiki/System-Testing-Guide#pre-merge-system-testing):
63+
64+
derecho ----- OK
65+
izumi ------- OK
66+
67+
If the tag used for baseline comparisons was NOT the previous tag, note that here:
68+
69+
70+
Answer changes
71+
--------------
72+
73+
Changes answers relative to baseline: No bit-for-bit
74+
75+
Other details
76+
-------------
77+
78+
Pull Requests that document the changes (include PR ids):
79+
(https://github.com/ESCOMP/ctsm/pull)
80+
81+
-- #3577 unit testing on Mac
82+
-- #3572 set_paramfile ordering
83+
-- #3560 Fix Ndim error in set_paramfile
84+
-- #3557 doc/build/run/deploy only on ESCOMP
85+
86+
===============================================================
87+
===============================================================
288
Tag name: ctsm5.3.083
389
Originator(s): rgknox (Ryan Knox,LAWRENCE BERKELEY NATIONAL LABORATORY,510-495-2153)
490
Date: Wed 29 Oct 2025 03:35:50 PM MDT

doc/ChangeSum

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
Tag Who Date Summary
22
============================================================================================================================
3+
ctsm5.3.084 erik 10/31/2025 Merge b4b-dev to master
34
ctsm5.3.083 rgknox 10/29/2025 Changes to coupling of supplementation status with FATES.
45
ctsm5.3.082 slevis 10/24/2025 Update to CMIP7 population density file for non-SSP cases
56
ctsm5.3.081 erik 10/22/2025 Change defaults for when Carbon isotopes are turned on, and turn on irrigate for Sp/Bgc cases for clm6_0 historical transient cases

doc/source/users_guide/using-clm-tools/paramfile-tools.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,9 +57,14 @@ Change a one-dimensional parameter (`mimics_fmet` has the `segment` dimension, l
5757
tools/param_utils/set_paramfile -i paramfile.nc -o output.nc mimics_fmet=0.1,0.2,0.3,0.4
5858
```
5959

60+
Change a one-dimensional parameter to be all one value (`mxmat` has the `pft` dimension, length 79):
61+
```bash
62+
tools/param_utils/set_paramfile -i paramfile.nc -o output.nc mxmat=360
63+
```
64+
6065
Change a parameter for specific PFTs:
6166
```bash
62-
tools/param_utils/set_paramfile -i paramfile.nc -o output.nc -p needleleaf_evergreen_temperate_tree,c4_grass medlynintercept=99.9,100.1 medlynslope=2.99,1.99
67+
tools/param_utils/set_paramfile -i paramfile.nc -o output.nc -p needleleaf_evergreen_temperate_tree,c4_grass medlynintercept=99.9,100.1 medlynslope=2.99,1.99 mxmat=199
6368
```
6469

6570
Set a parameter to the fill value:

python/ctsm/param_utils/paramfile_shared.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,9 @@ def get_selected_pft_indices(selected_pfts, pft_names):
8585
list of int
8686
Indices of selected PFTs.
8787
"""
88-
indices = [i for i, name in enumerate(pft_names) if name in selected_pfts]
88+
if isinstance(selected_pfts, str):
89+
selected_pfts = [selected_pfts]
90+
indices = [pft_names.index(pft) for pft in selected_pfts]
8991
return indices
9092

9193

python/ctsm/param_utils/set_paramfile.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ def check_correct_ndims(da, new_value, throw_error=False):
130130
"""
131131
expected = da.ndim
132132
actual = np.array(new_value).ndim
133-
is_ndim_correct = expected == actual
133+
is_ndim_correct = actual in (0, expected) # If actual 0, apply it to all
134134
if throw_error and not is_ndim_correct:
135135
raise RuntimeError(f"Incorrect N dims: Expected {expected}, got {actual}")
136136
return is_ndim_correct
@@ -323,11 +323,12 @@ def apply_new_value_to_parameter(args, ds_out, var, new_value, var_encoding, *,
323323
# Ensure that any NaNs are replaced with the fill value
324324
new_value = _replace_nans_with_fill(var_encoding, new_value, chg=chg)
325325

326-
# This can be needed if, e.g., you're selecting and changing just one PFT
326+
# This can be needed if, (a) you're selecting and changing just one PFT or (b) you're changing
327+
# all values in a dimensioned parameter to match one value.
327328
if ds_out[var].values.ndim > 0 and new_value.ndim == 0:
328-
new_value = np.atleast_1d(new_value)
329-
330-
ds_out[var].values = new_value
329+
ds_out[var].values[:] = new_value
330+
else:
331+
ds_out[var].values = new_value
331332
return ds_out
332333

333334

python/ctsm/test/test_sys_set_paramfile.py

Lines changed: 91 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -121,19 +121,65 @@ def test_set_paramfile_changeparams_scalar_errors_given_list(self):
121121
with self.assertRaisesRegex(RuntimeError, "Incorrect N dims"):
122122
sp.main()
123123

124-
def test_set_paramfile_changeparam_1d_errors_given_scalar(self):
125-
"""Test that set_paramfile errors if given a scalar for a 1-d parameter"""
124+
def test_set_paramfile_changeparam_1d_given_scalar(self):
125+
"""
126+
Test that set_paramfile works correctly if given a scalar for a 1-d parameter. We want it
127+
to set all members of the 1d array to the given scalar.
128+
"""
126129
output_path = os.path.join(self.tempdir, "output.nc")
127130
sys.argv = [
128131
"set_paramfile",
129132
"-i",
130133
PARAMFILE,
131134
"-o",
132135
output_path,
133-
"xl=0.724",
136+
"mxmat=1987",
134137
]
135-
with self.assertRaisesRegex(RuntimeError, "Incorrect N dims"):
136-
sp.main()
138+
sp.main()
139+
self.assertTrue(os.path.exists(output_path))
140+
ds_in = open_paramfile(PARAMFILE)
141+
ds_out = open_paramfile(output_path)
142+
143+
for var in ds_in.variables:
144+
# Check that all variables/coords are equal except the ones we changed, which should be
145+
# set to what we asked
146+
if var == "mxmat":
147+
self.assertTrue(np.all(ds_out[var].values == 1987))
148+
else:
149+
self.assertTrue(are_paramfile_dataarrays_identical(ds_in[var], ds_out[var]))
150+
151+
def test_set_paramfile_changeparam_1d_given_scalar_and_pftlist(self):
152+
"""
153+
Test that set_paramfile works correctly if given a scalar for a 1-d parameter. We want it
154+
to set all members of the 1d array to the given scalar. As
155+
test_set_paramfile_changeparam_1d_given_scalar, but here we give a pft list.
156+
"""
157+
output_path = os.path.join(self.tempdir, "output.nc")
158+
sys.argv = [
159+
"set_paramfile",
160+
"-i",
161+
PARAMFILE,
162+
"-o",
163+
output_path,
164+
"-p",
165+
"temperate_corn,irrigated_temperate_corn",
166+
"mxmat=1987",
167+
]
168+
sp.main()
169+
self.assertTrue(os.path.exists(output_path))
170+
ds_in = open_paramfile(PARAMFILE)
171+
ds_out = open_paramfile(output_path)
172+
173+
for var in ds_in.variables:
174+
# Check that all variables/coords are equal except the ones we changed, which should be
175+
# set to what we asked
176+
if var == "mxmat":
177+
# First, check that they weren't 1987 before
178+
self.assertFalse(np.any(ds_in[var].values[17:18] == 1987))
179+
# Now check that they are 1987
180+
self.assertTrue(np.all(ds_out[var].values[17:18] == 1987))
181+
else:
182+
self.assertTrue(are_paramfile_dataarrays_identical(ds_in[var], ds_out[var]))
137183

138184
def test_set_paramfile_changeparams_scalar_double(self):
139185
"""Test that set_paramfile can copy to a new file with some scalar double params changed"""
@@ -732,6 +778,46 @@ def test_set_paramfile_double_ok_given_int(self):
732778
ds_out = open_paramfile(output_path)
733779
self.assertFalse(sp.is_integer(ds_out[param_name].values))
734780

781+
def test_set_paramfile_pft_order(self):
782+
"""
783+
Test that set_paramfile gives the same result regardless of the order you specify the PFTs
784+
"""
785+
786+
# First order
787+
pfts_to_include = ["rice", "irrigated_rice"]
788+
output0_path = os.path.join(self.tempdir, "output0.nc")
789+
sys.argv = [
790+
"set_paramfile",
791+
"-i",
792+
PARAMFILE,
793+
"-o",
794+
output0_path,
795+
"-p",
796+
",".join(pfts_to_include),
797+
"mxmat=100,200",
798+
]
799+
sp.main()
800+
801+
# Reverse order
802+
pfts_to_include.reverse()
803+
output1_path = os.path.join(self.tempdir, "output1.nc")
804+
sys.argv = [
805+
"set_paramfile",
806+
"-i",
807+
PARAMFILE,
808+
"-o",
809+
output1_path,
810+
"-p",
811+
",".join(pfts_to_include),
812+
"mxmat=200,100",
813+
]
814+
sp.main()
815+
816+
# These files should be identical
817+
ds0 = open_paramfile(output0_path)
818+
ds1 = open_paramfile(output1_path)
819+
self.assertTrue(ds0.equals(ds1))
820+
735821

736822
if __name__ == "__main__":
737823
unit_testing.setup_for_tests()
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
#!/usr/bin/env python3
2+
3+
"""Unit tests for paramfile_shared"""
4+
5+
import unittest
6+
7+
from ctsm import unit_testing
8+
9+
from ctsm.param_utils import paramfile_shared as ps
10+
11+
# Allow names that pylint doesn't like, because otherwise I find it hard
12+
# to make readable unit test names
13+
# pylint: disable=invalid-name
14+
15+
16+
class TestUnitGetSelectedPftIndices(unittest.TestCase):
17+
"""Unit tests of get_selected_pft_indices"""
18+
19+
def test_get_selected_pft_indices_1strselected_onlyinlist(self):
20+
"""Check get_selected_pft_indices() given the only one in the list, as a string"""
21+
selected_pfts = "rice"
22+
pft_names = ["rice"]
23+
result = ps.get_selected_pft_indices(selected_pfts=selected_pfts, pft_names=pft_names)
24+
self.assertListEqual(result, [0])
25+
26+
def test_get_selected_pft_indices_1selected_onlyinlist(self):
27+
"""Check get_selected_pft_indices() given the only one in the list, as a list"""
28+
selected_pfts = ["rice"]
29+
pft_names = ["rice"]
30+
result = ps.get_selected_pft_indices(selected_pfts=selected_pfts, pft_names=pft_names)
31+
self.assertListEqual(result, [0])
32+
33+
def test_get_selected_pft_indices_2selected_sameorder(self):
34+
"""Check get_selected_pft_indices() given 2 selected in the same order as the list"""
35+
pft_names = ["rice", "irrigated_rice"]
36+
result = ps.get_selected_pft_indices(selected_pfts=pft_names, pft_names=pft_names)
37+
self.assertListEqual(result, [0, 1])
38+
39+
def test_get_selected_pft_indices_2selected_difforder(self):
40+
"""Check get_selected_pft_indices() given 2 selected NOT in the same order as the list"""
41+
pft_names = ["rice", "irrigated_rice"]
42+
result = ps.get_selected_pft_indices(
43+
selected_pfts=list(reversed(pft_names)), pft_names=pft_names
44+
)
45+
self.assertListEqual(result, [1, 0])
46+
47+
def test_get_selected_pft_indices_missing_valueerror(self):
48+
"""Check get_selected_pft_indices() given selected pft NOT in the list"""
49+
selected_pfts = ["wheat"]
50+
pft_names = ["rice", "irrigated_rice"]
51+
with self.assertRaises(ValueError):
52+
ps.get_selected_pft_indices(selected_pfts=selected_pfts, pft_names=pft_names)
53+
54+
55+
if __name__ == "__main__":
56+
unit_testing.setup_for_tests()
57+
unittest.main()

python/ctsm/test/test_unit_set_paramfile.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -65,14 +65,14 @@ def test_checkcorrectndims_0d_int_np(self):
6565
self.assertTrue(sp.check_correct_ndims(da, np.int32(1)))
6666

6767
def test_checkcorrectndims_1d_int(self):
68-
"""Check False when given a standard int for a 0d parameter"""
68+
"""Check True when given a standard int for a 0d parameter"""
6969
da = xr.DataArray(data=[1, 2])
70-
self.assertFalse(sp.check_correct_ndims(da, 1))
70+
self.assertTrue(sp.check_correct_ndims(da, 1))
7171

7272
def test_checkcorrectndims_1d_int_np(self):
73-
"""Check False when given a numpy int for a 0d parameter"""
73+
"""Check True when given a numpy int for a 0d parameter"""
7474
da = xr.DataArray(data=[1, 2])
75-
self.assertFalse(sp.check_correct_ndims(da, np.int32(1)))
75+
self.assertTrue(sp.check_correct_ndims(da, np.int32(1)))
7676

7777
def test_checkcorrectndims_0d_list(self):
7878
"""Check False when given a list for a 0d parameter"""

src/CMakeLists.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
cmake_minimum_required(VERSION 2.8)
1+
cmake_minimum_required(VERSION 3.10)
22

33
list(APPEND CMAKE_MODULE_PATH ${CIME_CMAKE_MODULE_DIRECTORY})
44
include(CIME_initial_setup)
@@ -93,7 +93,7 @@ add_library(csm_share ${share_sources} ${drv_sources_needed})
9393
declare_generated_dependencies(csm_share "${share_genf90_sources}")
9494
add_library(clm ${clm_sources})
9595
declare_generated_dependencies(clm "${clm_genf90_sources}")
96-
add_dependencies(clm csm_share esmf)
96+
add_dependencies(clm csm_share ESMF)
9797

9898
# We need to look for header files here, in order to pick up shr_assert.h
9999
include_directories(${CLM_ROOT}/share/include)

0 commit comments

Comments
 (0)