Skip to content

Commit

Permalink
Merge pull request monocongo#497 from WeatherGod/use-abs-tolerance
Browse files Browse the repository at this point in the history
Force get_tolerance() to always be greater than zero.
  • Loading branch information
monocongo authored Feb 8, 2023
2 parents 7f9ad05 + 612634c commit db85494
Show file tree
Hide file tree
Showing 3 changed files with 40 additions and 11 deletions.
17 changes: 6 additions & 11 deletions src/climate_indices/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,11 +88,6 @@ def _validate_args(args):
("division", "time"),
("division")]

# dynamic threshold absolute tolerance parameter np.allclose
# derived from (smallest) grid size along dimension dim
def get_tolerance(dim):
return np.diff(dim).min() / 10

# all indices except PET require a precipitation file
if args.index != "pet":

Expand Down Expand Up @@ -218,11 +213,11 @@ def get_tolerance(dim):
raise ValueError(msg)

# verify that the coordinate variables match with those of the precipitation dataset
if not np.allclose(lats_precip, dataset_pet["lat"][:], atol=get_tolerance(lats_precip)):
if not np.allclose(lats_precip, dataset_pet["lat"][:], atol=utils.get_tolerance(lats_precip)):
msg = "Precipitation and PET variables contain non-matching latitudes"
_logger.error(msg)
raise ValueError(msg)
elif not np.allclose(lons_precip, dataset_pet["lon"][:], atol=get_tolerance(lons_precip)):
elif not np.allclose(lons_precip, dataset_pet["lon"][:], atol=utils.get_tolerance(lons_precip)):
msg = "Precipitation and PET variables contain non-matching longitudes"
_logger.error(msg)
raise ValueError(msg)
Expand Down Expand Up @@ -300,11 +295,11 @@ def get_tolerance(dim):
raise ValueError(msg)

# verify that the coordinate variables match with those of the precipitation dataset
if not np.allclose(lats_precip, dataset_temp["lat"][:], atol=get_tolerance(lats_precip)):
if not np.allclose(lats_precip, dataset_temp["lat"][:], atol=utils.get_tolerance(lats_precip)):
msg = "Precipitation and temperature variables contain non-matching latitudes"
_logger.error(msg)
raise ValueError(msg)
elif not np.allclose(lons_precip, dataset_temp["lon"][:], atol=get_tolerance(lons_precip)):
elif not np.allclose(lons_precip, dataset_temp["lon"][:], atol=utils.get_tolerance(lons_precip)):
msg = "Precipitation and temperature variables contain non-matching longitudes"
_logger.error(msg)
raise ValueError(msg)
Expand Down Expand Up @@ -378,11 +373,11 @@ def get_tolerance(dim):
raise ValueError(msg)

# verify that the coordinate variables match with those of the precipitation dataset
if not np.allclose(lats_precip, dataset_awc["lat"][:], atol=get_tolerance(lats_precip)):
if not np.allclose(lats_precip, dataset_awc["lat"][:], atol=utils.get_tolerance(lats_precip)):
msg = "Precipitation and AWC variables contain non-matching latitudes"
_logger.error(msg)
raise ValueError(msg)
elif not np.allclose(lons_precip, dataset_awc["lon"][:], atol=get_tolerance(lons_precip)):
elif not np.allclose(lons_precip, dataset_awc["lon"][:], atol=utils.get_tolerance(lons_precip)):
msg = "Precipitation and AWC variables contain non-matching longitudes"
_logger.error(msg)
raise ValueError(msg)
Expand Down
11 changes: 11 additions & 0 deletions src/climate_indices/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -489,4 +489,15 @@ def count_zeros_and_non_missings(
return zeros, non_missings


# ------------------------------------------------------------------------------
def get_tolerance(dim: np.ndarray) -> float:
"""
dynamic threshold absolute tolerance parameter np.allclose
derived from (smallest) absolute grid size along dimension dim.
Always greater than zero.
"""
tol = np.abs(np.diff(dim).min() / 10)
return max(tol, np.finfo(tol.dtype).resolution)


_logger = get_logger(__name__, logging.DEBUG)
23 changes: 23 additions & 0 deletions tests/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -488,3 +488,26 @@ def test_transform_to_366day():
values_365,
1972,
24)

def test_tolerance():
lons, dlon = np.linspace(-180.0, 180.0, 250, retstep=True)
tol = utils.get_tolerance(lons)
assert tol > 0, "Tolerance must always be greater than zero"
assert tol < abs(dlon), "Tolerance must always come out smaller than the coordinate delta"

lats, dlat = np.linspace(25.0, -15.0, 50, retstep=True)
tol = utils.get_tolerance(lats)
assert tol > 0, "Tolerance must always be greater than zero"
assert tol < abs(dlat), "Tolerance must always come out smaller than the coordinate delta"

meshlat, meshlon = np.meshgrid(lats, lons)
tol = utils.get_tolerance(meshlat)
assert tol > 0, "Tolerance must always be greater than zero"
assert tol < abs(dlat), "Tolerance must always come out smaller than the coordinate delta"
# Tricky situation because np.diff() on this grid returns all zeros.
# Not the greatest situation, but we can at least allow a tiny bit of tolerance.
# Would be nice to be smarter about this situation.
tol = utils.get_tolerance(meshlon)
assert tol > 0, "Tolerance must always be greater than zero"
assert tol < abs(dlon), "Tolerance must always come out smaller than the coordinate delta"

0 comments on commit db85494

Please sign in to comment.