From b211f51d6e72b2dc0cfb043fcbdad21f400ce0d1 Mon Sep 17 00:00:00 2001 From: Dante Castro Garro Date: Sat, 3 Feb 2024 08:45:08 +0100 Subject: [PATCH 1/6] Option to cap low winds to 0.5 m/s --- .zenodo.json | 4 ++++ AUTHORS.rst | 1 + CHANGES.rst | 3 ++- xclim/indices/_conversion.py | 8 +++++++- 4 files changed, 14 insertions(+), 2 deletions(-) diff --git a/.zenodo.json b/.zenodo.json index 6d8446f7f..9242df632 100644 --- a/.zenodo.json +++ b/.zenodo.json @@ -125,6 +125,10 @@ "name": "Braun, Marco", "affiliation": "Ouranos Consortium, Montréal, Québec, Canada", "orcid": "0000-0001-5061-3217" + }, + { + "name": "Castro, Dante", + "affiliation": "Helmholtz-Zentrum Hereon, Geesthacht, Germany" } ], "keywords": [ diff --git a/AUTHORS.rst b/AUTHORS.rst index 46208a9c8..2f2e29442 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -40,3 +40,4 @@ Contributors * Ag Stephens `@agstephens `_ * Maliko Tanguy `@malngu `_ * Christopher Whelan `@qwhelan `_ +* Dante Castro `@profesorpaiche `_ diff --git a/CHANGES.rst b/CHANGES.rst index dd83cfc13..df35adb76 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,7 +4,7 @@ Changelog v0.48 (unreleased) ------------------ -Contributors to this version: Juliette Lavoie (:user:`juliettelavoie`), Pascal Bourgault (:user:`aulemahal`), Trevor James Smith (:user:`Zeitsperre`), David Huard (:user:`huard`), Éric Dupuis (:user:`coxipi`). +Contributors to this version: Juliette Lavoie (:user:`juliettelavoie`), Pascal Bourgault (:user:`aulemahal`), Trevor James Smith (:user:`Zeitsperre`), David Huard (:user:`huard`), Éric Dupuis (:user:`coxipi`), Dante Castro (:user:`profesorpaiche`). Announcements ^^^^^^^^^^^^^ @@ -21,6 +21,7 @@ New features and enhancements * Support ``indexer`` keyword in YAML indicator description. (:issue:`1522`, :pull:`1561`). * New ``xclim.core.calendar.stack_periods`` and ``unstack_periods`` for performing ``rolling(time=...).construct(..., stride=...)`` but with non-uniform temporal periods like years or months. They replace ``xclim.sdba.processing.construct_moving_yearly_window`` and ``unpack_moving_yearly_window`` which are deprecated and will be removed in a future release. * New ``as_dataset`` options for ``xclim.set_options``. When True, indicators will output Datasets instead of DataArrays. (:issue:`1257`, :pull:`1625`). +* Added new option for UTCI calculation to cap low wind velocities to a minimum of 0.5 m/s following Bröde (2012) guidelines. Breaking changes ^^^^^^^^^^^^^^^^ diff --git a/xclim/indices/_conversion.py b/xclim/indices/_conversion.py index 8483fb880..d0d47b9f7 100644 --- a/xclim/indices/_conversion.py +++ b/xclim/indices/_conversion.py @@ -1781,6 +1781,7 @@ def universal_thermal_climate_index( rlus: xr.DataArray | None = None, stat: str = "sunlit", mask_invalid: bool = True, + wind_cap_min: bool = False, ) -> xr.DataArray: r"""Universal thermal climate index (UTCI). @@ -1818,6 +1819,9 @@ def universal_thermal_climate_index( If True (default), UTCI values are NaN where any of the inputs are outside their validity ranges : -50°C < tas < 50°C, -30°C < tas - mrt < 30°C and 0.5 m/s < sfcWind < 17.0 m/s. + wind_cap_min: bool + If True, low wind velocities are capped to a minimum of 0.5 m/s. This ensures + UTCI calculation for low winds. Default value False. Returns ------- @@ -1842,6 +1846,8 @@ def universal_thermal_climate_index( e_sat = saturation_vapor_pressure(tas=tas, method="its90") tas = convert_units_to(tas, "degC") sfcWind = convert_units_to(sfcWind, "m/s") + if wind_cap_min: + sfcWind = sfcWind.clip(0.5, None) if mrt is None: mrt = mean_radiant_temperature( rsds=rsds, rsus=rsus, rlds=rlds, rlus=rlus, stat=stat @@ -1868,7 +1874,7 @@ def universal_thermal_climate_index( & (tas < 50.0) & (-30 < delta) & (delta < 30) - & (0.5 < sfcWind) + & (0.5 <= sfcWind) & (sfcWind < 17.0) ) return utci From 76252b0abc1d92664d1550df6a0306572c367046 Mon Sep 17 00:00:00 2001 From: Dante Castro Garro Date: Mon, 5 Feb 2024 17:56:08 +0100 Subject: [PATCH 2/6] Update CHANGES.rst Co-authored-by: Trevor James Smith <10819524+Zeitsperre@users.noreply.github.com> --- CHANGES.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES.rst b/CHANGES.rst index df35adb76..f86f97581 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -21,7 +21,7 @@ New features and enhancements * Support ``indexer`` keyword in YAML indicator description. (:issue:`1522`, :pull:`1561`). * New ``xclim.core.calendar.stack_periods`` and ``unstack_periods`` for performing ``rolling(time=...).construct(..., stride=...)`` but with non-uniform temporal periods like years or months. They replace ``xclim.sdba.processing.construct_moving_yearly_window`` and ``unpack_moving_yearly_window`` which are deprecated and will be removed in a future release. * New ``as_dataset`` options for ``xclim.set_options``. When True, indicators will output Datasets instead of DataArrays. (:issue:`1257`, :pull:`1625`). -* Added new option for UTCI calculation to cap low wind velocities to a minimum of 0.5 m/s following Bröde (2012) guidelines. +* Added new option for UTCI calculation to cap low wind velocities to a minimum of 0.5 m/s following Bröde (2012) guidelines. (:issue:`1634`, :pull:`1635`). Breaking changes ^^^^^^^^^^^^^^^^ From 99e836da4429d730924e7b44e1e11d31846a4303 Mon Sep 17 00:00:00 2001 From: Dante Castro Garro Date: Tue, 6 Feb 2024 14:18:17 +0100 Subject: [PATCH 3/6] Expanding UTCI test to check wind_cap_min option --- tests/test_indices.py | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/tests/test_indices.py b/tests/test_indices.py index 80cb29d35..6f0a95f03 100644 --- a/tests/test_indices.py +++ b/tests/test_indices.py @@ -3481,26 +3481,30 @@ def test_simple(self, pr_series): np.testing.assert_allclose(out, [4 / 31, 0, 0, 2 / 31, 0, 0, 0, 0, 0, 0, 0, 0]) +@pytest.mark.parametrize( + "wind_cap_min,expected", + [(False, [17.70, np.nan]), (True, [17.70, 17.76])], +) def test_universal_thermal_climate_index( tas_series, hurs_series, sfcWind_series, + wind_cap_min, + expected, ): - tas = tas_series(np.array([16]) + K2C) - hurs = hurs_series(np.array([36])) - sfcWind = sfcWind_series(np.array([2])) - mrt = tas_series(np.array([22]) + K2C) - - # Expected values - utci_exp = [17.7] + tas = tas_series(np.array([16, 16]) + K2C) + hurs = hurs_series(np.array([36, 36])) + sfcWind = sfcWind_series(np.array([2, 1])) + mrt = tas_series(np.array([22, 22]) + K2C) utci = xci.universal_thermal_climate_index( tas=tas, hurs=hurs, sfcWind=sfcWind, mrt=mrt, + wind_cap_min=wind_cap_min, ) - np.testing.assert_allclose(utci, utci_exp, rtol=1e-03) + np.testing.assert_allclose(utci, expected, rtol=1e-03) @pytest.mark.parametrize("stat,expected", [("sunlit", 295.0), ("instant", 294.9)]) From 3f4c9b4d6252356ef8f5898c504685dde255def3 Mon Sep 17 00:00:00 2001 From: Dante Castro Garro Date: Tue, 6 Feb 2024 15:03:45 +0100 Subject: [PATCH 4/6] Improved UTCI test It checks 3 conditions: 1. If the UTCI is similar to the expected value at wind velocity higher than 0.5 m/s (original test) 2. If the UTCI is `NaN` when the wind is lower than 0.5 m/s and `wind_cap_min` is set to `False` 3. If the UTCI is similar to the expected value when the wind velocity is lower than 0.5 m/s and `wind_cap_min` is set to `True` NOTE: Previous commit perfomed redundant tests --- tests/test_indices.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/tests/test_indices.py b/tests/test_indices.py index 6f0a95f03..e815b1766 100644 --- a/tests/test_indices.py +++ b/tests/test_indices.py @@ -3482,20 +3482,21 @@ def test_simple(self, pr_series): @pytest.mark.parametrize( - "wind_cap_min,expected", - [(False, [17.70, np.nan]), (True, [17.70, 17.76])], + "wind_cap_min,wind,expected", + [(False, 2, 17.70), (False, 1, np.nan), (True, 1, 17.76)], ) def test_universal_thermal_climate_index( tas_series, hurs_series, sfcWind_series, wind_cap_min, + wind, expected, ): - tas = tas_series(np.array([16, 16]) + K2C) - hurs = hurs_series(np.array([36, 36])) - sfcWind = sfcWind_series(np.array([2, 1])) - mrt = tas_series(np.array([22, 22]) + K2C) + tas = tas_series(np.array([16]) + K2C) + hurs = hurs_series(np.array([36])) + sfcWind = sfcWind_series(np.array([wind])) + mrt = tas_series(np.array([22]) + K2C) utci = xci.universal_thermal_climate_index( tas=tas, From 9887307d61f594d655797d7f0d1403e7849220b6 Mon Sep 17 00:00:00 2001 From: Dante Castro Garro Date: Wed, 7 Feb 2024 10:42:05 +0100 Subject: [PATCH 5/6] Including Brode 2012 article in the reference and documentation --- docs/references.bib | 16 ++++++++++++++++ xclim/indices/_conversion.py | 7 ++++--- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/docs/references.bib b/docs/references.bib index 2da431ffe..579a3ef56 100644 --- a/docs/references.bib +++ b/docs/references.bib @@ -2098,3 +2098,19 @@ @article{thom_1958 abstract = {Abstract The general properties of the gamma distribution, which has several applications in meteorology, are discussed. A short review of the general properties of good statistical estimators is given. This is applied to the gamma distribution to show that the maximum likelihood estimators are jointly sufficient. A new, simple approximation of the likelihood solutions is given, and the efficiency of the fitting procedure is computed.}, chapter = {Monthly Weather Review}, } + +@article{brode_utci_2012, + author = {Bröde, Peter and Fiala, Dusan and Błażejczyk, Krzysztof and Holmér, Ingvar and Jendritzky, Gerd and Kampmann, Bernhard and Tinz, Birger and Havenith, George}, + title = {Deriving the operational procedure for the Universal Thermal Climate Index (UTCI)}, + journal = {International Journal of Biometeorology}, + year = {2012}, + month = {May}, + day = {01}, + volume = {56}, + number = {3}, + pages = {481-494}, + abstract = {The Universal Thermal Climate Index (UTCI) aimed for a one-dimensional quantity adequately reflecting the human physiological reaction to the multi-dimensionally defined actual outdoor thermal environment. The human reaction was simulated by the UTCI-Fiala multi-node model of human thermoregulation, which was integrated with an adaptive clothing model. Following the concept of an equivalent temperature, UTCI for a given combination of wind speed, radiation, humidity and air temperature was defined as the air temperature of the reference environment, which according to the model produces an equivalent dynamic physiological response. Operationalising this concept involved (1) the definition of a reference environment with 50{\%} relative humidity (but vapour pressure capped at 20 hPa), with calm air and radiant temperature equalling air temperature and (2) the development of a one-dimensional representation of the multivariate model output at different exposure times. The latter was achieved by principal component analyses showing that the linear combination of 7 parameters of thermophysiological strain (core, mean and facial skin temperatures, sweat production, skin wettedness, skin blood flow, shivering) after 30 and 120 min exposure time accounted for two-thirds of the total variation in the multi-dimensional dynamic physiological response. The operational procedure was completed by a scale categorising UTCI equivalent temperature values in terms of thermal stress, and by providing simplified routines for fast but sufficiently accurate calculation, which included look-up tables of pre-calculated UTCI values for a grid of all relevant combinations of climate parameters and polynomial regression equations predicting UTCI over the same grid. The analyses of the sensitivity of UTCI to humidity, radiation and wind speed showed plausible reactions in the heat as well as in the cold, and indicate that UTCI may in this regard be universally useable in the major areas of research and application in human biometeorology.}, + issn = {1432-1254}, + doi = {10.1007/s00484-011-0454-1}, + url = {https://doi.org/10.1007/s00484-011-0454-1} +} diff --git a/xclim/indices/_conversion.py b/xclim/indices/_conversion.py index d0d47b9f7..0c6798cb6 100644 --- a/xclim/indices/_conversion.py +++ b/xclim/indices/_conversion.py @@ -1820,8 +1820,9 @@ def universal_thermal_climate_index( their validity ranges : -50°C < tas < 50°C, -30°C < tas - mrt < 30°C and 0.5 m/s < sfcWind < 17.0 m/s. wind_cap_min: bool - If True, low wind velocities are capped to a minimum of 0.5 m/s. This ensures - UTCI calculation for low winds. Default value False. + If True, wind velocities are capped to a minimum of 0.5 m/s following + :cite:t:`brode_utci_2012` usage guidalines. This ensures UTCI calculation + for low winds. Default value False. Returns ------- @@ -1841,7 +1842,7 @@ def universal_thermal_climate_index( References ---------- - :cite:cts:`brode_utci_2009,blazejczyk_introduction_2013` + :cite:cts:`brode_utci_2009,brode_utci_2012,blazejczyk_introduction_2013` """ e_sat = saturation_vapor_pressure(tas=tas, method="its90") tas = convert_units_to(tas, "degC") From 85cafb124601bbe397b3992ba1edfcc2a2ae86d3 Mon Sep 17 00:00:00 2001 From: "bumpversion[bot]" Date: Wed, 7 Feb 2024 17:54:43 +0000 Subject: [PATCH 6/6] =?UTF-8?q?Bump=20version:=200.47.5-dev.14=20=E2=86=92?= =?UTF-8?q?=200.47.5-dev.15?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pyproject.toml | 2 +- xclim/__init__.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 593c555ff..f67af13c2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -124,7 +124,7 @@ target-version = [ ] [tool.bumpversion] -current_version = "0.47.5-dev.14" +current_version = "0.47.5-dev.15" commit = true commit_args = "--no-verify" tag = false diff --git a/xclim/__init__.py b/xclim/__init__.py index 9511fa073..361b0ed32 100644 --- a/xclim/__init__.py +++ b/xclim/__init__.py @@ -16,7 +16,7 @@ __author__ = """Travis Logan""" __email__ = "logan.travis@ouranos.ca" -__version__ = "0.47.5-dev.14" +__version__ = "0.47.5-dev.15" _module_data = _files("xclim.data")