Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Growing Season operators #1796

Merged
merged 6 commits into from
Jun 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
8 changes: 8 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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`).
Expand Down
3 changes: 3 additions & 0 deletions tests/test_indicators.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"),
Zeitsperre marked this conversation as resolved.
Show resolved Hide resolved
}:
raise ValueError(
f"The following indicator/parameter couple {problems} use types not listed in InputKind."
Expand Down
6 changes: 3 additions & 3 deletions xclim/data/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -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."
},
Expand Down Expand Up @@ -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."
Expand Down
20 changes: 14 additions & 6 deletions xclim/indicators/atmos/_temperature.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -996,15 +996,19 @@ 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(
title="Growing season end",
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 "
Expand All @@ -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(
Expand Down
20 changes: 15 additions & 5 deletions xclim/indices/_threshold.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from __future__ import annotations

import warnings
from typing import Literal

import numpy as np
import xarray
Expand Down Expand Up @@ -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.

Expand All @@ -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
-------
Expand All @@ -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

Expand All @@ -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.

Expand All @@ -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
-------
Expand All @@ -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,
Expand Down Expand Up @@ -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::

Expand Down