Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
6b171e8
Some updates to CMakeLists that get closer, but aren't quite working …
ekluzek Jun 25, 2025
9ffd4d3
Merge branch 'b4b-dev' into add_decomp_init_unittest
ekluzek Oct 4, 2025
d00588b
Add some basic unit tests of vegtype_str2int(). One fails.
samsrabin Oct 10, 2025
471086a
vegtype_str2int(): Fix for single string inputs.
samsrabin Oct 10, 2025
5bd6297
grid_one_variable: Replace np.NaN with np.nan.
samsrabin Oct 10, 2025
e0bac6d
When calling vegtype_str2int for a single string, return a single int…
samsrabin Oct 10, 2025
11a471c
Add some basic unit tests of create_filled_array(). 1 failing.
samsrabin Oct 10, 2025
c3f9be2
create_filled_array(): Fix behavior with fill_value=0.
samsrabin Oct 10, 2025
a73eb84
grid_one_variable(): Fix behavior with fill_value=0.
samsrabin Oct 10, 2025
a728bda
generate_gdd20_baseline: Move time slice from _parse_args() to genera…
samsrabin Oct 10, 2025
fd55e31
generate_gdd20_baseline: Simplify time slice, assuming modern ctsm_py…
samsrabin Oct 10, 2025
9c9a313
generate_gdd20_baseline.py: Refactor to new function _get_time_slice().
samsrabin Oct 29, 2025
5bf0c52
test_unit_grid_one_variable: Add unit_testing.setup_for_tests().
samsrabin Oct 29, 2025
6278885
Update to cime version from Jin that solves the check_input_data --do…
ekluzek Dec 4, 2025
6dde609
Add FSA_U
olyson Dec 16, 2025
dd22ced
Add RH2M_U
olyson Dec 16, 2025
df0bd4c
Add SoilAlpha_U
olyson Dec 16, 2025
f1d99ae
Add TG_U
olyson Dec 16, 2025
d22ae07
Add TSA_U and TSA_R
olyson Dec 16, 2025
9e8b414
Add TREFMNAV_U and TREFMNAV_R
olyson Dec 16, 2025
8d99015
Add TREFMXAV_U and TREFMXAV_R
olyson Dec 16, 2025
4fa4b45
Add FSM_U
olyson Dec 16, 2025
0da0e6b
Add FIRA_U
olyson Dec 16, 2025
42b6c80
Add FIRA_U
olyson Dec 16, 2025
68a15de
Add FSH_U
olyson Dec 16, 2025
ec16a9a
Add EFLX_LH_TOT_U
olyson Dec 16, 2025
825320d
Add FGR_U
olyson Dec 16, 2025
9820d4b
Add QRUNOFF_U
olyson Dec 16, 2025
04014d2
Add RH2M_R
olyson Dec 16, 2025
b60c83a
Merge branch 'b4b-dev' into fix-generate_gdd20_baseline
samsrabin Dec 19, 2025
2304bda
Merge tag 'ctsm5.4.006' into merge-master-20260107
ekluzek Jan 8, 2026
4069318
Merge pull request #3682 from ekluzek/merge-master-20260107
ekluzek Jan 8, 2026
df89960
Merge branch 'b4b-dev' into URhistfields
slevis-lmwg Jan 12, 2026
e29e129
Merge pull request #3667 from olyson/URhistfields
slevis-lmwg Jan 13, 2026
5629699
Update cime submodule URL and fxtag version
ekluzek Jan 15, 2026
59ce6d3
Merge branch 'b4b-dev' into cime_download_update
ekluzek Jan 15, 2026
e75078a
Merge branch 'b4b-dev' into add_decomp_init_unittest
ekluzek Jan 15, 2026
d42e9df
Merge pull request #3647 from ekluzek/cime_download_update
ekluzek Jan 16, 2026
195c3d9
Merge remote-tracking branch 'escomp/b4b-dev' into add_decomp_init_un…
ekluzek Jan 16, 2026
e56de4c
Get a simple test of DecompMod for get_proc_bounds and get_clump_boun…
ekluzek Jan 17, 2026
0461ea5
These changes build in using mpi-serial, on Derecho it requires setti…
ekluzek Jan 18, 2026
5e4fc9a
Revert "These changes build in using mpi-serial, on Derecho it requir…
ekluzek Jan 18, 2026
b70cab7
Merge branch 'b4b-dev' into fix-generate_gdd20_baseline
samsrabin Jan 18, 2026
8b97e2a
Merge pull request #3543 from samsrabin/fix-generate_gdd20_baseline
samsrabin Jan 19, 2026
394eb0e
Merge remote-tracking branch 'escomp/b4b-dev' into decompMod_unittest
ekluzek Jan 20, 2026
1297aa1
Update src/main/test/decomp_test/test_decompMod.pf
ekluzek Jan 21, 2026
e744db5
Update src/main/test/decomp_test/test_decompMod.pf
ekluzek Jan 21, 2026
5ac060d
Remove the addition of FATES directories as it causes a fail on the f…
ekluzek Jan 22, 2026
92a9125
Merge pull request #3699 from ekluzek/decompMod_unittest
ekluzek Jan 22, 2026
18f3b74
Merge tag 'ctsm5.4.010' into merge-b4bdev-20250122
slevis-lmwg Jan 22, 2026
0cf11e0
Update ChangeLog/Sum
slevis-lmwg Jan 22, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 63 additions & 0 deletions doc/ChangeLog
Original file line number Diff line number Diff line change
@@ -1,4 +1,67 @@
===============================================================
Tag name: ctsm5.4.011
Originator(s): slevis (Samuel Levis,UCAR/TSS,303-665-1310)
Date: Thu Jan 22 04:01:52 PM MST 2026
One-line Summary: Merge b4b-dev to master

Purpose and description of changes
----------------------------------
Change some _U and _R history fields to be on by default PR #3667 by Keith Oleson
Update to cime version that fixes the check_input_data --download issue PR #3647 by Erik Kluzek; #3647 updated to cime6.1.145, while updating b4b-dev to master in this PR gets us to cime6.1.146.
Initial fixes to generate_gdd20_baseline PR #3543
Decomp mod unittest PR #3699

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]:
No issues were opened for the PRs listed above.

Testing summary:
----------------

[PASS means all tests PASS; OK means tests PASS other than expected fails.]

build-namelist tests (if CLMBuildNamelist.pm has changed):

derecho - OK

python testing (if python code has changed; see instructions in python/README.md; document testing done):

derecho - OK

regular tests (aux_clm: https://github.com/ESCOMP/CTSM/wiki/System-Testing-Guide#pre-merge-system-testing):

derecho ----- OK
izumi ------- OK

Answer changes
--------------
Changes answers relative to baseline: No

Other details
-------------
Pull Requests that document the changes (include PR ids):
https://github.com/ESCOMP/ctsm/pull/3709

===============================================================
===============================================================
Tag name: ctsm5.4.010
Originator(s): erik (Erik Kluzek,UCAR/TSS,303-497-1326)
Date: Wed Jan 21 11:49:59 AM MST 2026
Expand Down
1 change: 1 addition & 0 deletions doc/ChangeSum
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
Tag Who Date Summary
============================================================================================================================
ctsm5.4.011 slevis 01/22/2026 Merge b4b-dev to master
ctsm5.4.010 erik 01/21/2026 Update cime to version that changes answers for ERI tests
ctsm5.4.009 olyson 01/19/2026 Dewpoint Temperature check for bare ground
ctsm5.4.008 swensosc 01/12/2026 Add a correction for oversaturated soil layers in SoilWaterMovement (erik)
Expand Down
2 changes: 1 addition & 1 deletion python/ctsm/crop_calendars/check_rx_obeyed.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ def check_rx_obeyed(
continue
ds_thisveg = dates_ds.isel(patch=thisveg_patches)

vegtype_int = utils.vegtype_str2int(vegtype_str)[0]
vegtype_int = utils.vegtype_str2int(vegtype_str)
rx_da = rx_ds[f"gs1_{vegtype_int}"]
rx_array = rx_da.values[
ds_thisveg.patches1d_jxy.values.astype(int) - 1,
Expand Down
6 changes: 6 additions & 0 deletions python/ctsm/crop_calendars/cropcal_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,8 @@ def vegtype_str2int(vegtype_str, vegtype_mainlist=None):
convert_to_ndarray = not isinstance(vegtype_str, np.ndarray)
if convert_to_ndarray:
vegtype_str = np.array(vegtype_str)
was_0d = vegtype_str.ndim == 0
vegtype_str = np.atleast_1d(vegtype_str)

if isinstance(vegtype_mainlist, xr.Dataset):
vegtype_mainlist = vegtype_mainlist.vegtype_str.values
Expand All @@ -289,6 +291,10 @@ def vegtype_str2int(vegtype_str, vegtype_mainlist=None):
indices[np.where(vegtype_str == vegtype_str_2)] = vegtype_mainlist.index(vegtype_str_2)
if convert_to_ndarray:
indices = [int(x) for x in indices]

if was_0d:
indices = indices[0]

return indices


Expand Down
45 changes: 24 additions & 21 deletions python/ctsm/crop_calendars/generate_gdd20_baseline.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,22 +109,7 @@ def _parse_args():
if not os.path.exists(filename):
raise FileNotFoundError(f"Input file not found: {filename}")

# Process time slice
# Assumes CESM behavior where data for e.g. 1987 is saved as 1988-01-01.
# It would be more robust, accounting for upcoming behavior (where timestamp for a year is the
# middle of that year), to do slice("YEAR1-01-03", "YEARN-01-02"), but that's not compatible
# with ctsm_pylib as of the version using python 3.7.9. See safer_timeslice() in cropcal_utils.
if args.first_year is not None:
date_1 = f"{args.first_year+1}-01-01"
else:
date_1 = "0000-01-01"
if args.last_year is not None:
date_n = f"{args.last_year+1}-01-01"
else:
date_n = "9999-12-31"
time_slice = slice(date_1, date_n)

return args, time_slice
return args


def _get_cft_list(crop_list):
Expand Down Expand Up @@ -182,7 +167,7 @@ def _get_gddn_for_cft(cft_str, variable):


def _get_output_varname(cft_str):
cft_int = utils.vegtype_str2int(cft_str)[0]
cft_int = utils.vegtype_str2int(cft_str)
return f"gdd20bl_{cft_int}"


Expand Down Expand Up @@ -232,7 +217,23 @@ def setup_output_dataset(input_files, author, variable, year_args, ds_in):
return ds_out


def generate_gdd20_baseline(input_files, output_file, author, time_slice, variable, year_args):
def _get_time_slice(year_args):
"""
Based on years from input arguments, return a time slice for selecting from dataset
"""
first_year = year_args[0]
last_year = year_args[1]
date_1 = f"{first_year}-01-01"
date_n = f"{last_year}-12-31"
if first_year is None:
date_1 = "0000-01-01"
if last_year is None:
date_n = "9999-12-31"
time_slice = slice(date_1, date_n)
return time_slice


def generate_gdd20_baseline(input_files, output_file, author, variable, year_args):
"""
Generate stream_fldFileName_gdd20_baseline file from CTSM outputs
"""
Expand All @@ -252,6 +253,9 @@ def generate_gdd20_baseline(input_files, output_file, author, time_slice, variab
input_files = list(set(input_files))
input_files.sort()

# Process time slice
time_slice = _get_time_slice(year_args)

# Import history files and ensure they have lat/lon dims
ds_in = import_ds(input_files, my_vars=var_list_in + GRIDDING_VAR_LIST, time_slice=time_slice)
if not all(x in ds_in.dims for x in ["lat", "lon"]):
Expand All @@ -275,7 +279,7 @@ def generate_gdd20_baseline(input_files, output_file, author, time_slice, variab
# Process all crops
encoding_dict = {}
for cft_str in MGDCROP_LIST:
cft_int = utils.vegtype_str2int(cft_str)[0]
cft_int = utils.vegtype_str2int(cft_str)
print(f"{cft_str} ({cft_int})")

# Which GDDN history variable does this crop use? E.g., GDD0, GDD10
Expand Down Expand Up @@ -323,12 +327,11 @@ def main():
"""
main() function for calling generate_gdd20_baseline.py from command line.
"""
args, time_slice = _parse_args()
args = _parse_args()
generate_gdd20_baseline(
args.input_files,
args.output_file,
args.author,
time_slice,
args.variable,
[args.first_year, args.last_year],
)
4 changes: 2 additions & 2 deletions python/ctsm/crop_calendars/generate_gdds_functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -591,7 +591,7 @@ def import_and_process_1yr(
log(logger, f"SKIPPING {vegtype_str}")
continue

vegtype_int = utils.vegtype_str2int(vegtype_str)[0]
vegtype_int = utils.vegtype_str2int(vegtype_str)
this_crop_full_patchlist = list(xr_flexsel(h2_ds, vegtype=vegtype_str).patch.values)

# Get time series for each patch of this type
Expand Down Expand Up @@ -1152,7 +1152,7 @@ def make_figures(
raise RuntimeError(f"If mapping {vegtype_str}, you must provide land use dataset")
else:
vegtypes_str = [x for x in incl_vegtypes_str if vegtype_str.lower() in x]
vegtypes_int = [utils.vegtype_str2int(x)[0] for x in vegtypes_str]
vegtypes_int = [utils.vegtype_str2int(x) for x in vegtypes_str]

# Crop fraction map (for masking and weighting)
if lu_ds:
Expand Down
11 changes: 6 additions & 5 deletions python/ctsm/crop_calendars/grid_one_variable.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,10 @@ def create_filled_array(this_ds, fill_value, thisvar_da, new_dims):
"""
Create a Numpy array to be filled with gridded data
"""

if fill_value is None:
fill_value = np.nan

dim_size_list = []
for dim in new_dims:
if dim == "ivt_str":
Expand All @@ -97,10 +101,7 @@ def create_filled_array(this_ds, fill_value, thisvar_da, new_dims):
dim_size = this_ds.sizes[dim]
dim_size_list = dim_size_list + [dim_size]
thisvar_gridded = np.empty(dim_size_list)
if fill_value:
thisvar_gridded[:] = fill_value
else:
thisvar_gridded[:] = np.NaN
thisvar_gridded[:] = fill_value
return thisvar_gridded


Expand Down Expand Up @@ -160,7 +161,7 @@ def grid_one_variable(this_ds, var, fill_value=None, **kwargs):
# Get DataArrays needed for gridding
thisvar_da, vt_da, spatial_unit, ixy_da, jxy_da = get_ixy_jxy_das(this_ds, var)

if not fill_value and "_FillValue" in thisvar_da.attrs:
if fill_value is None and "_FillValue" in thisvar_da.attrs:
fill_value = thisvar_da.attrs["_FillValue"]

# Renumber vt_da to work as indices on new ivt dimension, if needed.
Expand Down
38 changes: 38 additions & 0 deletions python/ctsm/test/test_unit_cropcal_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#!/usr/bin/env python3

"""Unit tests for cropcal_utils.py"""

import unittest

from ctsm import unit_testing
from ctsm.crop_calendars import cropcal_utils as ccu

# Allow names that pylint doesn't like, because otherwise I find it hard
# to make readable unit test names
# pylint: disable=invalid-name


class TestCropCalUtils(unittest.TestCase):
"""Tests of cropcal_utils.py"""

def setUp(self):
self.vegtype_mainlist = ["crop_1", "crop_2", "crop_3"]

def test_vegtype_str2int_1string(self):
"""
Tests vegtype_str2int() for a single string. Result should be an int.
"""
result = ccu.vegtype_str2int("crop_1", vegtype_mainlist=self.vegtype_mainlist)
self.assertEqual(result, 0)

def test_vegtype_str2int_2strings(self):
"""
Tests vegtype_str2int() for two strings. result should be a list of ints.
"""
result = ccu.vegtype_str2int(["crop_1", "crop_3"], vegtype_mainlist=self.vegtype_mainlist)
self.assertListEqual(result, [0, 2])


if __name__ == "__main__":
unit_testing.setup_for_tests()
unittest.main()
91 changes: 91 additions & 0 deletions python/ctsm/test/test_unit_grid_one_variable.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
#!/usr/bin/env python3

"""
Unit tests for grid_one_variable
"""

import unittest

import numpy as np
import xarray as xr

from ctsm import unit_testing
from ctsm.crop_calendars import grid_one_variable as g1v

# Allow test names that pylint doesn't like; otherwise hard to make them
# readable
# pylint: disable=invalid-name

# pylint: disable=protected-access

## Too many instant variables as part of the class (too many self.<varible> in the SetUp)
# pylint: disable=too-many-instance-attributes


class TestCreateFilledArray(unittest.TestCase):
"""Unit tests for create_filled_array"""

def setUp(self):
# Set up this_ds, which will provide us with sizes of dimensions in most cases
lat_vals = [55.0, 56.0, 57.0]
lat_da = xr.DataArray(
data=lat_vals,
dims=["lat"],
coords={"lat": lat_vals},
)
lon_vals = [255.0, 256.0, 257.0]
lon_da = xr.DataArray(
data=lon_vals,
dims=["lon"],
coords={"lon": lon_vals},
)
self.this_ds = xr.Dataset(
data_vars={
"lat": lat_da,
"lon": lon_da,
}
)

def test_create_filled_array_fillNone(self):
"""
Test create_filled_array() with fill_value None: Should be filled with NaN
"""

fill_value = None
thisvar_da_dummy = xr.DataArray()
new_dims = ["lat", "lon"]

result = g1v.create_filled_array(self.this_ds, fill_value, thisvar_da_dummy, new_dims)

self.assertTrue(np.all(np.isnan(result)))

def test_create_filled_array_fill1(self):
"""
Test create_filled_array() with fill_value 1: Should be filled with 1
"""

fill_value = 1.0
thisvar_da_dummy = xr.DataArray()
new_dims = ["lat", "lon"]

result = g1v.create_filled_array(self.this_ds, fill_value, thisvar_da_dummy, new_dims)

self.assertTrue(np.all(result == fill_value))

def test_create_filled_array_fill0(self):
"""
Test create_filled_array() with fill_value 0: Should be filled with 0
"""

fill_value = 0.0
thisvar_da_dummy = xr.DataArray()
new_dims = ["lat", "lon"]

result = g1v.create_filled_array(self.this_ds, fill_value, thisvar_da_dummy, new_dims)

self.assertTrue(np.all(result == fill_value))


if __name__ == "__main__":
unit_testing.setup_for_tests()
unittest.main()
Loading
Loading