From e17ce492e026be63d11ecb674f3e0882838d06c8 Mon Sep 17 00:00:00 2001 From: Trevor James Smith <10819524+Zeitsperre@users.noreply.github.com> Date: Tue, 25 Jun 2024 17:37:17 -0400 Subject: [PATCH 1/4] Add operators to indices and indicators of growing season start and end --- xclim/indicators/atmos/_temperature.py | 20 ++++++++++++++------ xclim/indices/_threshold.py | 20 +++++++++++++++----- 2 files changed, 29 insertions(+), 11 deletions(-) diff --git a/xclim/indicators/atmos/_temperature.py b/xclim/indicators/atmos/_temperature.py index dfcddf5ac..54caf2c0c 100644 --- a/xclim/indicators/atmos/_temperature.py +++ b/xclim/indicators/atmos/_temperature.py @@ -971,13 +971,13 @@ class TempWithIndexing(ResamplingIndicatorWithIndexing): identifier="growing_season_start", units="", standard_name="day_of_year", - long_name="First day of the first series of {window} days with mean daily temperature above or equal to {thresh}", + long_name="First day of the first series of {window} days with mean daily temperature {op} {thresh}", description="Day of the year marking the beginning of the growing season, defined as the first day of the first " - "series of {window} days with mean daily temperature above or equal to {thresh}.", + "series of {window} days with mean daily temperature {op} {thresh}.", abstract="The first day when the temperature exceeds a certain threshold for a given number of consecutive days.", cell_methods="", compute=indices.growing_season_start, - parameters={"thresh": {"default": "5.0 degC"}}, + parameters={"thresh": {"default": "5.0 degC"}, "op": {"default": ">="}}, ) growing_season_length = Temp( @@ -996,7 +996,11 @@ class TempWithIndexing(ResamplingIndicatorWithIndexing): "threshold, occurring after a given calendar date.", cell_methods="", compute=indices.growing_season_length, - parameters={"thresh": {"default": "5.0 degC"}, "mid_date": {"default": "07-01"}}, + parameters={ + "thresh": {"default": "5.0 degC"}, + "op": {"default": ">="}, + "mid_date": {"default": "07-01"}, + }, ) growing_season_end = Temp( @@ -1004,7 +1008,7 @@ class TempWithIndexing(ResamplingIndicatorWithIndexing): identifier="growing_season_end", units="", standard_name="day_of_year", - long_name="First day of the first series of {window} days with mean daily temperature below {thresh}, " + long_name="First day of the first series of {window} days with mean daily temperature {op} {thresh}, " "occurring after {mid_date}", description="Day of year of end of growing season, defined as the first day of consistent inferior threshold " "temperature of {thresh} after a run of {window} days superior to threshold temperature, occurring after " @@ -1013,7 +1017,11 @@ class TempWithIndexing(ResamplingIndicatorWithIndexing): "after a given calendar date.", cell_methods="", compute=indices.growing_season_end, - parameters={"thresh": {"default": "5.0 degC"}, "mid_date": {"default": "07-01"}}, + parameters={ + "thresh": {"default": "5.0 degC"}, + "op": {"default": "<"}, + "mid_date": {"default": "07-01"}, + }, ) tropical_nights = TempWithIndexing( diff --git a/xclim/indices/_threshold.py b/xclim/indices/_threshold.py index 4f60a51d5..ecf8d1ee3 100644 --- a/xclim/indices/_threshold.py +++ b/xclim/indices/_threshold.py @@ -2,6 +2,7 @@ from __future__ import annotations import warnings +from typing import Literal import numpy as np import xarray @@ -978,6 +979,7 @@ def growing_season_start( thresh: Quantified = "5.0 degC", window: int = 5, freq: str = "YS", + op: Literal[">", ">=", "gt", "ge"] = ">=", ) -> xarray.DataArray: r"""Start of the growing season. @@ -994,6 +996,8 @@ def growing_season_start( Minimum number of days with temperature above threshold needed for evaluation. freq : str Resampling frequency. + op : {">", ">=", "gt", "ge"} + Comparison operation. Default: ">=". Returns ------- @@ -1014,8 +1018,9 @@ def growing_season_start( and :math:`[P]` is 1 if :math:`P` is true, and 0 if false. """ thresh = convert_units_to(thresh, tas) - over = tas >= thresh - out = over.resample(time=freq).map(rl.first_run, window=window, coord="dayofyear") + cond = compare(tas, op, thresh, constrain=(">=", ">")) + + out = cond.resample(time=freq).map(rl.first_run, window=window, coord="dayofyear") out.attrs.update(units="", is_dayofyear=np.int32(1), calendar=get_calendar(tas)) return out @@ -1027,6 +1032,7 @@ def growing_season_end( mid_date: DayOfYearStr = "07-01", window: int = 5, freq: str = "YS", + op: Literal["<", "<=", "lt", "le"] = "<", ) -> xarray.DataArray: r"""End of the growing season. @@ -1049,6 +1055,8 @@ def growing_season_end( Minimum number of days with temperature below threshold needed for evaluation. freq : str Resampling frequency. + op : {"<", "<=", "lt", "le"} + Comparison operation. Default: "<". Returns ------- @@ -1070,7 +1078,9 @@ def growing_season_end( and :math:`[P]` is 1 if :math:`P` is true, and 0 if false. """ thresh = convert_units_to(thresh, tas) - cond = tas >= thresh + + # Note: The following operation is inverted here so that there is less confusion for users. + cond = ~compare(tas, op, thresh, constrain=("<=", "<")) out = cond.resample(time=freq).map( rl.run_end_after_date, @@ -1130,9 +1140,9 @@ def growing_season_length( .. math:: - TG_{ij} > 5 ℃ + TG_{ij} >= 5 ℃ - and the first occurrence after 1 July of at least 6 consecutive days with: + and the first occurrence after 1 July of at least six (6) consecutive days with: .. math:: From 6dcf5ac86ffeeee70afd4181a577bacac5cc648f Mon Sep 17 00:00:00 2001 From: Trevor James Smith <10819524+Zeitsperre@users.noreply.github.com> Date: Tue, 25 Jun 2024 17:47:43 -0400 Subject: [PATCH 2/4] modify french translation --- xclim/data/fr.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/xclim/data/fr.json b/xclim/data/fr.json index be0cc4a14..5f6b7d189 100644 --- a/xclim/data/fr.json +++ b/xclim/data/fr.json @@ -651,8 +651,8 @@ "abstract": "Nombre de jours entre la première occurrence d'une série de jours ayant une température moyenne quotidienne au-dessus d'un seuil et la première occurrence d'une série de jours avec une température moyenne quotidienne sous ce même seuil, survenant après une date de calendrier donnée." }, "GROWING_SEASON_START": { - "long_name": "Premier jour de la première série de {window} jours ayant une température moyenne quotidienne égale ou au-dessus de {thresh}", - "description": "Jour de l’année marquant le début de la saison de croissance, défini comme le premier jour de la première série de {window} jours ayant une température moyenne quotidienne au-dessus ou égale à {thresh}", + "long_name": "Premier jour de la première série de {window} jours ayant une température moyenne quotidienne {op} {thresh}", + "description": "Jour de l’année marquant le début de la saison de croissance, défini comme le premier jour de la première série de {window} jours ayant une température moyenne quotidienne {op} {thresh}", "title": "Jour de l'année du début de la saison de croissance", "abstract": "Premier jour où la température moyenne quotidienne est au-dessus d'un seuil donné depuis un certain nombre de jours consécutifs." }, @@ -759,7 +759,7 @@ "abstract": "Nombre maximal de jours consécutifs sans gel où la température minimale quotidienne est au-dessus ou égale à 0°C." }, "GROWING_SEASON_END": { - "long_name": "Premier jour de la première série de {window} jours ayant une température journalière moyenne sous {thresh}, survenant après {mid_date}", + "long_name": "Premier jour de la première série de {window} jours ayant une température journalière moyenne {op} {thresh}, survenant après {mid_date}", "description": "Jour de l'année de la fin de la saison de croissance, défini comme le premier jour après {mid_date} ayant une température moyenne quotidienne au-dessus ou égale à {thresh} après une série de {window} jours ayant une température moyenne quotidienne sous {thresh}.", "title": "Fin de la saison de croissance", "abstract": "Le premier jour où la température moyenne quotidienne est sous un seuil donné pendant un certain nombre de jours consécutifs après une date de calendrier donnée." From ae6564bf79bcfcf1d6e7db4cd0b4655ec35c1f8d Mon Sep 17 00:00:00 2001 From: Trevor James Smith <10819524+Zeitsperre@users.noreply.github.com> Date: Tue, 25 Jun 2024 17:53:47 -0400 Subject: [PATCH 3/4] modify test expectation --- tests/test_indicators.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/test_indicators.py b/tests/test_indicators.py index 76263e05b..1395f214c 100644 --- a/tests/test_indicators.py +++ b/tests/test_indicators.py @@ -488,6 +488,9 @@ def test_all_parameters_understood(official_indicators): if problems - { ("COOL_NIGHT_INDEX", "lat"), ("DRYNESS_INDEX", "lat"), + # TODO: How should we handle the case of Literal[str]? + ("GROWING_SEASON_END", "op"), + ("GROWING_SEASON_START", "op"), }: raise ValueError( f"The following indicator/parameter couple {problems} use types not listed in InputKind." From a6259a3cac37ff28bda24af52d0b5273af950a4d Mon Sep 17 00:00:00 2001 From: Zeitsperre <10819524+Zeitsperre@users.noreply.github.com> Date: Wed, 26 Jun 2024 15:57:10 -0400 Subject: [PATCH 4/4] update CHANGES.rst --- CHANGES.rst | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 42ff6c252..a352c6ee7 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -6,6 +6,14 @@ v0.51.0 (unreleased) -------------------- Contributors to this version: Trevor James Smith (:user:`Zeitsperre`). +New features and enhancements +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +* Added the `op` keyword to the `growing_season_{start|end}` indices and indicators, allowing for customizable threshold operators using `indices.generic.compare()`. (:issue:`1794`, :pull:`1796`). + +Bug fixes +^^^^^^^^^ +* Clarified a typo in the docstring formula for `xclim.indices.growing_season_length`. (:pull:`1796`). + Internal changes ^^^^^^^^^^^^^^^^ * GitHub repository now uses Rulesets for branch protection. (:pull:`1790`).