Skip to content

Commit 426e977

Browse files
authored
Merge pull request #3259 from samsrabin/subset_data-lon-fixes
subset_data point: Fix --create-datm and Longitude TypeErrors
2 parents b530ef9 + ecbe0fe commit 426e977

32 files changed

+425
-134
lines changed

.git-blame-ignore-revs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,3 +67,4 @@ cdf40d265cc82775607a1bf25f5f527bacc97405
6767
3b7a2876933263f8986e4069f5d23bd45635756f
6868
3dd489af7ebe06566e2c6a1c7ade18550f1eb4ba
6969
742cfa606039ab89602fde5fef46458516f56fd4
70+
4ad46f46de7dde753b4653c15f05326f55116b73

doc/source/users_guide/running-single-points/generic-single-point-regional.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ You can also have the script subset land-use data. See the help (``tools/site_an
4141
.. note::
4242
This script defaults to subsetting specific surface data, land-use timeseries, and the CRUJRA2024 DATM data. It can currently only be run as-is on Derecho. If you're not on Derecho, use ``--inputdata-dir`` to specify where the top level of your CESM input data is.
4343

44-
Also, to subset GSWP3 instead of CRUJRA2024 DATM data, you currently need to hardwire ``datm_type = "datm_gswp3"`` (instead of the default ``"datm_crujra"``) in ``python/ctsm/subset_data.py``.
44+
Using ``--create-datm`` with GSWP3 data is no longer supported; see `CTSM issue #3269 <https://github.com/ESCOMP/CTSM/issues/3269>`_.
4545

4646

4747

python/ctsm/longitude.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ def _convert_lon_type_180_to_360(lon_in):
5858
return lon_out
5959

6060

61-
def _detect_lon_type(lon_in):
61+
def detect_lon_type(lon_in):
6262
"""
6363
Detect longitude type of a given numeric. If lon_in contains more than one number (as in a list
6464
or Numpy array), this function will assume all members are of the same type if (a) there is at

python/ctsm/site_and_regional/regional_case.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
from ctsm.utils import add_tag_to_filename
2020
from ctsm.utils import abort
2121
from ctsm.config_utils import check_lon1_lt_lon2
22-
from ctsm.longitude import Longitude, _detect_lon_type
22+
from ctsm.longitude import Longitude, detect_lon_type
2323

2424
logger = logging.getLogger(__name__)
2525

@@ -142,7 +142,7 @@ def _subset_lon_lat(self, x_dim, y_dim, f_in):
142142

143143
# Detect longitude type (180 or 360) of input file, throwing a helpful error if it can't be
144144
# determined.
145-
f_lon_type = _detect_lon_type(lon)
145+
f_lon_type = detect_lon_type(lon)
146146
lon1_type = self.lon1.lon_type()
147147
lon2_type = self.lon2.lon_type()
148148
if lon1_type != lon2_type:

python/ctsm/site_and_regional/single_point_case.py

Lines changed: 69 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -15,17 +15,14 @@
1515
# -- import local classes for this script
1616
from ctsm.site_and_regional.base_case import BaseCase, USRDAT_DIR, DatmFiles
1717
from ctsm.utils import add_tag_to_filename, ensure_iterable
18+
from ctsm.longitude import detect_lon_type
1819

1920
logger = logging.getLogger(__name__)
2021

2122
NAT_PFT = 15 # natural pfts
2223
NUM_PFT = 17 # for runs with generic crops
2324
MAX_PFT = 78 # for runs with explicit crops
2425

25-
# -- constants to represent months of year
26-
FIRST_MONTH = 1
27-
LAST_MONTH = 12
28-
2926

3027
class SinglePointCase(BaseCase):
3128
"""
@@ -151,6 +148,26 @@ def __init__(
151148
# self.check_nonveg()
152149
self.check_pct_pft()
153150

151+
def convert_plon_to_filetype_if_needed(self, lon_da):
152+
"""
153+
Check that point and input file longitude types are equal. If not, convert point to match
154+
file.
155+
"""
156+
plon_in = self.plon
157+
f_lon_type = detect_lon_type(lon_da)
158+
plon_type = plon_in.lon_type()
159+
if f_lon_type == plon_type:
160+
plon_out = plon_in.get(plon_type)
161+
else:
162+
plon_orig = plon_in.get(plon_type)
163+
plon_out = plon_in.get(f_lon_type)
164+
if plon_orig != plon_out:
165+
print(
166+
f"Converted plon from type {plon_type} (value {plon_orig}) "
167+
f"to type {f_lon_type} (value {plon_out})"
168+
)
169+
return plon_out
170+
154171
def create_tag(self):
155172
"""
156173
Create a tag for single point which is the site name
@@ -363,8 +380,11 @@ def create_landuse_at_point(self, indir, file, user_mods_dir):
363380
# create 1d coordinate variables to enable sel() method
364381
f_in = self.create_1d_coord(fluse_in, "LONGXY", "LATIXY", "lsmlon", "lsmlat")
365382

383+
# get point longitude, converting to match file type if needed
384+
plon_float = self.convert_plon_to_filetype_if_needed(f_in["lsmlon"])
385+
366386
# extract gridcell closest to plon/plat
367-
f_out = f_in.sel(lsmlon=self.plon, lsmlat=self.plat, method="nearest")
387+
f_out = f_in.sel(lsmlon=plon_float, lsmlat=self.plat, method="nearest")
368388

369389
# expand dimensions
370390
f_out = f_out.expand_dims(["lsmlat", "lsmlon"])
@@ -498,8 +518,11 @@ def create_surfdata_at_point(self, indir, file, user_mods_dir, specify_fsurf_out
498518
# create 1d coordinate variables to enable sel() method
499519
f_in = self.create_1d_coord(fsurf_in, "LONGXY", "LATIXY", "lsmlon", "lsmlat")
500520

521+
# get point longitude, converting to match file type if needed
522+
plon_float = self.convert_plon_to_filetype_if_needed(f_in["lsmlon"])
523+
501524
# extract gridcell closest to plon/plat
502-
f_tmp = f_in.sel(lsmlon=self.plon, lsmlat=self.plat, method="nearest")
525+
f_tmp = f_in.sel(lsmlon=plon_float, lsmlat=self.plat, method="nearest")
503526

504527
# expand dimensions
505528
f_tmp = f_tmp.expand_dims(["lsmlat", "lsmlon"]).copy(deep=True)
@@ -525,10 +548,10 @@ def create_surfdata_at_point(self, indir, file, user_mods_dir, specify_fsurf_out
525548
# update lsmlat and lsmlon to match site specific instead of the nearest point
526549
# we do this so that if we create user_mods the PTS_LON and PTS_LAT in CIME match
527550
# the surface data coordinates - which is required
528-
f_out["lsmlon"] = np.atleast_1d(self.plon)
551+
f_out["lsmlon"] = np.atleast_1d(plon_float)
529552
f_out["lsmlat"] = np.atleast_1d(self.plat)
530553
f_out["LATIXY"][:, :] = self.plat
531-
f_out["LONGXY"][:, :] = self.plon
554+
f_out["LONGXY"][:, :] = plon_float
532555

533556
# update attributes
534557
self.update_metadata(f_out)
@@ -568,8 +591,11 @@ def create_datmdomain_at_point(self, datm_tuple: DatmFiles):
568591
# create 1d coordinate variables to enable sel() method
569592
f_in = self.create_1d_coord(fdatmdomain_in, "xc", "yc", "ni", "nj")
570593

594+
# get point longitude, converting to match file type if needed
595+
plon_float = self.convert_plon_to_filetype_if_needed(f_in["lon"])
596+
571597
# extract gridcell closest to plon/plat
572-
f_out = f_in.sel(ni=self.plon, nj=self.plat, method="nearest")
598+
f_out = f_in.sel(ni=plon_float, nj=self.plat, method="nearest")
573599

574600
# expand dimensions
575601
f_out = f_out.expand_dims(["nj", "ni"])
@@ -591,14 +617,17 @@ def extract_datm_at(self, file_in, file_out):
591617
# create 1d coordinate variables to enable sel() method
592618
f_in = self.create_1d_coord(file_in, "LONGXY", "LATIXY", "lon", "lat")
593619

620+
# get point longitude, converting to match file type if needed
621+
plon_float = self.convert_plon_to_filetype_if_needed(f_in["lon"])
622+
594623
# extract gridcell closest to plon/plat
595-
f_out = f_in.sel(lon=self.plon, lat=self.plat, method="nearest")
624+
f_out = f_in.sel(lon=plon_float, lat=self.plat, method="nearest")
596625

597626
# expand dimensions
598627
f_out = f_out.expand_dims(["lat", "lon"])
599628

600629
# specify dimension order
601-
f_out = f_out.transpose("scalar", "time", "lat", "lon")
630+
f_out = f_out.transpose("time", "lat", "lon")
602631

603632
# update attributes
604633
self.update_metadata(f_out)
@@ -653,46 +682,36 @@ def create_datm_at_point(self, datm_tuple: DatmFiles, datm_syr, datm_eyr, datm_s
653682
tpqwfiles = []
654683
for year in range(datm_syr, datm_eyr + 1):
655684
ystr = str(year)
656-
for month in range(FIRST_MONTH, LAST_MONTH + 1):
657-
mstr = str(month)
658-
if month < 10:
659-
mstr = "0" + mstr
660-
661-
dtag = ystr + "-" + mstr
662685

663-
fsolar = os.path.join(
664-
datm_tuple.indir,
665-
datm_tuple.dir_solar,
666-
"{}{}.nc".format(datm_tuple.tag_solar, dtag),
667-
)
668-
fsolar2 = "{}{}.{}.nc".format(datm_tuple.tag_solar, self.tag, dtag)
669-
fprecip = os.path.join(
670-
datm_tuple.indir,
671-
datm_tuple.dir_prec,
672-
"{}{}.nc".format(datm_tuple.tag_prec, dtag),
673-
)
674-
fprecip2 = "{}{}.{}.nc".format(datm_tuple.tag_prec, self.tag, dtag)
675-
ftpqw = os.path.join(
676-
datm_tuple.indir,
677-
datm_tuple.dir_tpqw,
678-
"{}{}.nc".format(datm_tuple.tag_tpqw, dtag),
679-
)
680-
ftpqw2 = "{}{}.{}.nc".format(datm_tuple.tag_tpqw, self.tag, dtag)
681-
682-
outdir = os.path.join(self.out_dir, datm_tuple.outdir)
683-
infile += [fsolar, fprecip, ftpqw]
684-
outfile += [
685-
os.path.join(outdir, fsolar2),
686-
os.path.join(outdir, fprecip2),
687-
os.path.join(outdir, ftpqw2),
688-
]
689-
solarfiles.append(
690-
os.path.join("${}".format(USRDAT_DIR), datm_tuple.outdir, fsolar2)
691-
)
692-
precfiles.append(
693-
os.path.join("${}".format(USRDAT_DIR), datm_tuple.outdir, fprecip2)
694-
)
695-
tpqwfiles.append(os.path.join("${}".format(USRDAT_DIR), datm_tuple.outdir, ftpqw2))
686+
fsolar = os.path.join(
687+
datm_tuple.indir,
688+
datm_tuple.dir_solar,
689+
"{}{}.nc".format(datm_tuple.tag_solar, ystr),
690+
)
691+
fsolar2 = "{}{}.{}.nc".format(datm_tuple.tag_solar, self.tag, ystr)
692+
fprecip = os.path.join(
693+
datm_tuple.indir,
694+
datm_tuple.dir_prec,
695+
"{}{}.nc".format(datm_tuple.tag_prec, ystr),
696+
)
697+
fprecip2 = "{}{}.{}.nc".format(datm_tuple.tag_prec, self.tag, ystr)
698+
ftpqw = os.path.join(
699+
datm_tuple.indir,
700+
datm_tuple.dir_tpqw,
701+
"{}{}.nc".format(datm_tuple.tag_tpqw, ystr),
702+
)
703+
ftpqw2 = "{}{}.{}.nc".format(datm_tuple.tag_tpqw, self.tag, ystr)
704+
705+
outdir = os.path.join(self.out_dir, datm_tuple.outdir)
706+
infile += [fsolar, fprecip, ftpqw]
707+
outfile += [
708+
os.path.join(outdir, fsolar2),
709+
os.path.join(outdir, fprecip2),
710+
os.path.join(outdir, ftpqw2),
711+
]
712+
solarfiles.append(os.path.join("${}".format(USRDAT_DIR), datm_tuple.outdir, fsolar2))
713+
precfiles.append(os.path.join("${}".format(USRDAT_DIR), datm_tuple.outdir, fprecip2))
714+
tpqwfiles.append(os.path.join("${}".format(USRDAT_DIR), datm_tuple.outdir, ftpqw2))
696715

697716
for idx, out_f in enumerate(outfile):
698717
logger.debug(out_f)

python/ctsm/subset_data.py

Lines changed: 30 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@
6969
from ctsm.path_utils import path_to_ctsm_root
7070
from ctsm.utils import abort
7171
from ctsm.config_utils import check_lon1_lt_lon2
72-
from ctsm.longitude import Longitude, _detect_lon_type
72+
from ctsm.longitude import Longitude, detect_lon_type
7373

7474
# -- import ctsm logging flags
7575
from ctsm.ctsm_logging import (
@@ -629,32 +629,41 @@ def setup_files(args, defaults, cesmroot):
629629
file_dict = {"main_dir": clmforcingindir}
630630

631631
# DATM data
632-
# TODO Issue #2960: Make datm_type a user option at the command
633-
# line. For reference, this option affects three .cfg files:
634-
# tools/site_and_regional/default_data_1850.cfg
635-
# tools/site_and_regional/default_data_2000.cfg
636-
# python/ctsm/test/testinputs/default_data.cfg
632+
# To find the affected files, from the top level of ctsm, do:
633+
# grep "\[datm\]" $(find . -type f -name "*cfg")
637634
if args.create_datm:
638-
datm_type = "datm_crujra" # also available: datm_type = "datm_gswp3"
635+
datm_cfg_section = "datm"
636+
637+
# Issue #3269: Changes in PR #3259 mean that --create-datm won't work with GSWP3
638+
settings_to_check_for_gswp3 = ["solartag", "prectag", "tpqwtag"]
639+
for setting in settings_to_check_for_gswp3:
640+
value = defaults.get(datm_cfg_section, setting)
641+
if "gswp3" in value.lower():
642+
msg = (
643+
"--create-datm is no longer supported for GSWP3 data; "
644+
"see https://github.com/ESCOMP/CTSM/issues/3269"
645+
)
646+
raise NotImplementedError(msg)
647+
639648
dir_output_datm = "datmdata"
640-
dir_input_datm = os.path.join(clmforcingindir, defaults.get(datm_type, "dir"))
649+
dir_input_datm = os.path.join(clmforcingindir, defaults.get(datm_cfg_section, "dir"))
641650
if not os.path.isdir(os.path.join(args.out_dir, dir_output_datm)):
642651
os.mkdir(os.path.join(args.out_dir, dir_output_datm))
643652
logger.info("dir_input_datm : %s", dir_input_datm)
644653
logger.info("dir_output_datm: %s", os.path.join(args.out_dir, dir_output_datm))
645654
file_dict["datm_tuple"] = DatmFiles(
646655
dir_input_datm,
647656
dir_output_datm,
648-
defaults.get(datm_type, "domain"),
649-
defaults.get(datm_type, "solardir"),
650-
defaults.get(datm_type, "precdir"),
651-
defaults.get(datm_type, "tpqwdir"),
652-
defaults.get(datm_type, "solartag"),
653-
defaults.get(datm_type, "prectag"),
654-
defaults.get(datm_type, "tpqwtag"),
655-
defaults.get(datm_type, "solarname"),
656-
defaults.get(datm_type, "precname"),
657-
defaults.get(datm_type, "tpqwname"),
657+
defaults.get(datm_cfg_section, "domain"),
658+
defaults.get(datm_cfg_section, "solardir"),
659+
defaults.get(datm_cfg_section, "precdir"),
660+
defaults.get(datm_cfg_section, "tpqwdir"),
661+
defaults.get(datm_cfg_section, "solartag"),
662+
defaults.get(datm_cfg_section, "prectag"),
663+
defaults.get(datm_cfg_section, "tpqwtag"),
664+
defaults.get(datm_cfg_section, "solarname"),
665+
defaults.get(datm_cfg_section, "precname"),
666+
defaults.get(datm_cfg_section, "tpqwname"),
658667
)
659668

660669
# if the crop flag is on - we need to use a different land use and surface data file
@@ -833,10 +842,10 @@ def process_args(args):
833842
if any(lon_arg_values):
834843
if args.lon_type is None:
835844
if hasattr(args, "plon"):
836-
args.lon_type = _detect_lon_type(args.plon)
845+
args.lon_type = detect_lon_type(args.plon)
837846
else:
838-
lon1_type = _detect_lon_type(args.lon1)
839-
lon2_type = _detect_lon_type(args.lon2)
847+
lon1_type = detect_lon_type(args.lon1)
848+
lon2_type = detect_lon_type(args.lon2)
840849
if lon1_type != lon2_type:
841850
raise argparse.ArgumentTypeError(
842851
"--lon1 and --lon2 seem to be of different types"

0 commit comments

Comments
 (0)