Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
6b29b53
Python tools are now smart about longitude format.
samsrabin Mar 20, 2025
e5bbb14
subset_data: Don't require --lon-type if lons are unambiguous.
samsrabin Apr 3, 2025
cb06cda
subset_data: Fix help text for --lon[1,2]? args.
samsrabin Apr 4, 2025
4c004e7
Fix handling of longitude ranges that cross Prime Meridian.
samsrabin Apr 4, 2025
f107e09
Fix convert_lon_0to360().
samsrabin Apr 4, 2025
e649b0a
subset_data: Refer to ESCOMP/CTSM#2017 in lon1>=lon2 error msg.
samsrabin Apr 4, 2025
ade5baf
subset_data: Improve lon1>=lon2 error msg and test.
samsrabin Apr 4, 2025
95ae84a
Fix submodule versions.
samsrabin Apr 4, 2025
73c399e
Fix verbiage re: International Date Line vs. Prime Meridian.
samsrabin Apr 4, 2025
8b905d4
subset_data: Functionize adding --lon-type arg.
samsrabin Apr 4, 2025
251e389
Reformat with black.
samsrabin Apr 4, 2025
ef5db45
Add previous commit to .git-blame-ignore-revs.
samsrabin Apr 4, 2025
4483a90
Resolve a pylint line-too-long complaint.
samsrabin Apr 4, 2025
293ac54
Check longitudes in RegionalCase.
samsrabin Apr 4, 2025
27d45c9
Check RegionalCase in test_unit_subset_data; fix exposed bugs.
samsrabin Apr 4, 2025
3860a60
Undo some unneeded changes to subset_data args.
samsrabin Apr 4, 2025
59863ad
Restore plon_type. As original, but don't convert.
samsrabin Apr 4, 2025
7ca9ad0
Restore plon_type tests.
samsrabin Apr 4, 2025
06577f8
Remove an unneeded print().
samsrabin Apr 4, 2025
73058a4
test_sys_mesh_modifier: Set self._lon_type to 360.
samsrabin Apr 4, 2025
e65a59a
User's Guide: Fix a heading.
samsrabin Apr 4, 2025
00ef05a
User's Guide: Improve formatting of a note.
samsrabin Apr 4, 2025
c64bc2e
User's Guide, subset_data: Add info about --lon-type.
samsrabin Apr 4, 2025
8cabe07
Merge remote-tracking branch 'escomp/b4b-dev' into fix-python-tools-l…
samsrabin Apr 15, 2025
52a300e
Python: Add Longitude class.
samsrabin Apr 15, 2025
62001d5
Improve erroring around longitude type detection.
samsrabin Apr 16, 2025
cc7e951
Replace International Date Line language with 180th Meridian.
samsrabin Apr 16, 2025
e9add42
Add "Must be unambiguous" comment at pt_parser --lon.
samsrabin Apr 16, 2025
8ec5013
Reformat with black.
samsrabin Apr 16, 2025
b4b4c53
Add previous commit to .git-blame-ignore-revs.
samsrabin Apr 16, 2025
4148342
Resolve pylint complaints.
samsrabin Apr 16, 2025
b97904f
Merge branch 'b4b-dev' into fix-python-tools-longitude-format
samsrabin Apr 16, 2025
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
24 changes: 0 additions & 24 deletions python/ctsm/args_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@
import logging
import argparse

from ctsm.config_utils import lon_range_0_to_360

logger = logging.getLogger(__name__)


Expand All @@ -29,25 +27,3 @@ def plat_type(plat):
if plat_out < -90 or plat_out > 90:
raise argparse.ArgumentTypeError("ERROR: Latitude should be between -90 and 90.")
return plat_out


def plon_type(plon):
"""
Function to define lon type for the parser and
convert negative longitudes to 0-360 and
raise error if lon is not between -180 and 360.

Args:
plon (str): longitude
Raises:
Error (ArgumentTypeError): when longitude is <-180 and >360.
Returns:
plon_out (float): converted longitude between 0 and 360
"""
plon_float = float(plon)
if plon_float < -180 or plon_float > 360:
raise argparse.ArgumentTypeError(
"ERROR: Longitude should be between 0 and 360 or -180 and 180."
)
plon_out = lon_range_0_to_360(plon_float)
return plon_out
23 changes: 11 additions & 12 deletions python/ctsm/config_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,22 +18,21 @@
_CONFIG_UNSET = "UNSET"


def lon_range_0_to_360(lon_in):
def convert_lon_0to360(lon_in):
Comment thread
samsrabin marked this conversation as resolved.
Outdated
"""
Description
-----------
Restrict longitude to 0 to 360 when given as -180 to 180.
Convert a longitude from [-180, 180] format (i.e., centered around Prime Meridian) to [0, 360]
format (i.e., centered around International Date Line).
Comment thread
samsrabin marked this conversation as resolved.
Outdated
"""
if -180 <= lon_in < 0:
raise NotImplementedError(
"A negative longitude suggests you input longitudes in the range [-180, 0)---"
"i.e., centered around the Prime Meridian. This code requires longitudes in the "
"range [0, 360)---i.e., starting at the International Date Line."
)
if not (0 <= lon_in <= 360 or lon_in is None):
errmsg = "lon_in needs to be in the range 0 to 360"
abort(errmsg)
lon_out = lon_in
if not -180 <= lon_in <= 180:
raise ValueError(f"lon_in needs to be in the range [-180, 180]: {lon_in}")
lon_out = 180 + lon_in
logger.info(
"Converting longitude from [-180, 180] to [0, 360]: %s to %s",
str(lon_in),
str(lon_out),
)

return lon_out

Expand Down
20 changes: 19 additions & 1 deletion python/ctsm/modify_input_files/fsurdat_modifier.py
Original file line number Diff line number Diff line change
Expand Up @@ -498,6 +498,13 @@ def read_cfg_required_basic_opts(config, section, cfg_path):
file_path=cfg_path,
convert_to_type=float,
)
lon_type = get_config_value(
config=config,
section=section,
item="lon_type",
file_path=cfg_path,
convert_to_type=int,
)

landmask_file = get_config_value(
config=config,
Expand All @@ -513,7 +520,16 @@ def read_cfg_required_basic_opts(config, section, cfg_path):
lon_dimname = get_config_value(
config=config, section=section, item="lon_dimname", file_path=cfg_path, can_be_unset=True
)
return (lnd_lat_1, lnd_lat_2, lnd_lon_1, lnd_lon_2, landmask_file, lat_dimname, lon_dimname)
return (
lnd_lat_1,
lnd_lat_2,
lnd_lon_1,
lnd_lon_2,
landmask_file,
lat_dimname,
lon_dimname,
lon_type,
)


def fsurdat_modifier(parser):
Expand Down Expand Up @@ -568,6 +584,7 @@ def fsurdat_modifier(parser):
landmask_file,
lat_dimname,
lon_dimname,
lon_type,
) = read_cfg_required_basic_opts(config, section, cfg_path)
# Create ModifyFsurdat object
modify_fsurdat = ModifyFsurdat.init_from_file(
Expand All @@ -579,6 +596,7 @@ def fsurdat_modifier(parser):
landmask_file,
lat_dimname,
lon_dimname,
lon_type,
)

# Read control information about the optional sections
Expand Down
3 changes: 2 additions & 1 deletion python/ctsm/modify_input_files/mesh_mask_modifier.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,10 +66,11 @@ def mesh_mask_modifier(cfg_path):
lon_varname = get_config_value(
config=config, section=section, item="lon_varname", file_path=cfg_path
)
lon_type = get_config_value(config=config, section=section, item="lon_type", file_path=cfg_path)

# Create ModifyMeshMask object
modify_mesh_mask = ModifyMeshMask.init_from_file(
mesh_mask_in, landmask_file, lat_dimname, lon_dimname, lat_varname, lon_varname
mesh_mask_in, landmask_file, lat_dimname, lon_dimname, lat_varname, lon_varname, lon_type
)

# If output file exists, abort before starting work
Expand Down
30 changes: 23 additions & 7 deletions python/ctsm/modify_input_files/modify_fsurdat.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

from ctsm.utils import abort, update_metadata
from ctsm.git_utils import get_ctsm_git_short_hash
from ctsm.config_utils import lon_range_0_to_360
from ctsm.config_utils import convert_lon_0to360

logger = logging.getLogger(__name__)

Expand All @@ -26,7 +26,7 @@ class ModifyFsurdat:
"""

def __init__(
self, my_data, lon_1, lon_2, lat_1, lat_2, landmask_file, lat_dimname, lon_dimname
self, my_data, lon_1, lon_2, lat_1, lat_2, landmask_file, lat_dimname, lon_dimname, lon_type
):

self.numurbl = 3 # Number of urban density types
Expand All @@ -36,13 +36,15 @@ def __init__(
else:
abort("numurbl is not a dimension on the input surface dataset file and needs to be")

self.lon_type = lon_type
self.rectangle = self._get_rectangle(
lon_1=lon_1,
lon_2=lon_2,
lat_1=lat_1,
lat_2=lat_2,
longxy=self.file.LONGXY,
latixy=self.file.LATIXY,
lon_type=self.lon_type,
)

if landmask_file is not None:
Expand Down Expand Up @@ -72,23 +74,37 @@ def __init__(

@classmethod
def init_from_file(
cls, fsurdat_in, lon_1, lon_2, lat_1, lat_2, landmask_file, lat_dimname, lon_dimname
cls,
fsurdat_in,
lon_1,
lon_2,
lat_1,
lat_2,
landmask_file,
lat_dimname,
lon_dimname,
lon_type,
):
"""Initialize a ModifyFsurdat object from file fsurdat_in"""
logger.info("Opening fsurdat_in file to be modified: %s", fsurdat_in)
my_file = xr.open_dataset(fsurdat_in)
return cls(my_file, lon_1, lon_2, lat_1, lat_2, landmask_file, lat_dimname, lon_dimname)
return cls(
my_file, lon_1, lon_2, lat_1, lat_2, landmask_file, lat_dimname, lon_dimname, lon_type
)

@staticmethod
def _get_rectangle(lon_1, lon_2, lat_1, lat_2, longxy, latixy):
def _get_rectangle(lon_1, lon_2, lat_1, lat_2, longxy, latixy, lon_type):
"""
Description
-----------
"""

# ensure that lon ranges 0-360 in case user entered -180 to 180
lon_1 = lon_range_0_to_360(lon_1)
lon_2 = lon_range_0_to_360(lon_2)
if lon_type == 180:
lon_1 = convert_lon_0to360(lon_1)
lon_2 = convert_lon_0to360(lon_2)
elif lon_type != 360:
raise ValueError("lon_type must be either 180 or 360")

# determine the rectangle(s)
# TODO This is not really "nearest" for the edges but isel didn't work
Expand Down
19 changes: 12 additions & 7 deletions python/ctsm/modify_input_files/modify_mesh_mask.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
import xarray as xr

from ctsm.utils import abort
from ctsm.config_utils import lon_range_0_to_360
from ctsm.config_utils import convert_lon_0to360

logger = logging.getLogger(__name__)

Expand All @@ -29,7 +29,9 @@ class ModifyMeshMask:
# /glade/work/slevis/git/mksurfdata_toolchain/tools/modify_input_files ...
# ... /islas_examples/modify_fsurdat/fill_indian_ocean/
# Read mod_lnd_props here only for consistency checks
def __init__(self, my_data, landmask_file, lat_dimname, lon_dimname, lat_varname, lon_varname):
def __init__(
self, my_data, landmask_file, lat_dimname, lon_dimname, lat_varname, lon_varname, lon_type
):

self.file = my_data

Expand All @@ -46,6 +48,7 @@ def __init__(self, my_data, landmask_file, lat_dimname, lon_dimname, lat_varname
self.lonvar = self._landmask_file[lon_varname][..., :]
self.lsmlat = self._landmask_file.dims[lat_dimname]
self.lsmlon = self._landmask_file.dims[lon_dimname]
self.lon_type = lon_type

lonvar_first = self.lonvar[..., 0].data.max()
lonvar_last = self.lonvar[..., -1].data.max()
Expand Down Expand Up @@ -75,12 +78,14 @@ def __init__(self, my_data, landmask_file, lat_dimname, lon_dimname, lat_varname

@classmethod
def init_from_file(
cls, file_in, landmask_file, lat_dimname, lon_dimname, lat_varname, lon_varname
cls, file_in, landmask_file, lat_dimname, lon_dimname, lat_varname, lon_varname, lon_type
):
"""Initialize a ModifyMeshMask object from file_in"""
logger.info("Opening file to be modified: %s", file_in)
my_file = xr.open_dataset(file_in)
return cls(my_file, landmask_file, lat_dimname, lon_dimname, lat_varname, lon_varname)
return cls(
my_file, landmask_file, lat_dimname, lon_dimname, lat_varname, lon_varname, lon_type
)

def set_mesh_mask(self, var):
"""
Expand Down Expand Up @@ -134,13 +139,13 @@ def set_mesh_mask(self, var):
+ f"{len(self.latvar.sizes)}"
)
abort(errmsg)
# ensure lon range of 0-360 rather than -180 to 180
lonvar_scalar = lon_range_0_to_360(lonvar_scalar)
# lon and lat from the mesh file
lat_mesh = float(self.file["centerCoords"][ncount, 1])
lon_mesh = float(self.file["centerCoords"][ncount, 0])
# ensure lon range of 0-360 rather than -180 to 180
lon_mesh = lon_range_0_to_360(lon_mesh)
if self.lon_type == 180:
lonvar_scalar = convert_lon_0to360(lonvar_scalar)
lon_mesh = convert_lon_0to360(lon_mesh)
Comment thread
samsrabin marked this conversation as resolved.
Outdated

errmsg = (
"Must be equal: "
Expand Down
27 changes: 14 additions & 13 deletions python/ctsm/site_and_regional/regional_case.py
Original file line number Diff line number Diff line change
Expand Up @@ -154,19 +154,20 @@ def check_region_lons(self):
Check for the regional lon bounds
"""
if self.lon1 >= self.lon2:
err_msg = """
\n
ERROR: lon1 is bigger than lon2.
lon1 points to the westernmost longitude of the region. {}
lon2 points to the easternmost longitude of the region. {}
Please make sure lon1 is smaller than lon2.

Please note that if longitude in -180-0, the code automatically
convert it to 0-360.
""".format(
self.lon1, self.lon2
)
raise argparse.ArgumentTypeError(err_msg)
pass
# err_msg = """
# \n
# ERROR: lon1 is bigger than lon2.
# lon1 points to the westernmost longitude of the region. {}
# lon2 points to the easternmost longitude of the region. {}
# Please make sure lon1 is smaller than lon2.

# Please note that if longitude in -180-0, the code automatically
# convert it to 0-360.
# """.format(
# self.lon1, self.lon2
# )
# raise argparse.ArgumentTypeError(err_msg)

def check_region_lats(self):
"""
Expand Down
Loading