Skip to content

Commit

Permalink
Merge pull request #251 from cerfacs-globc/adapt-to-xclim0-40
Browse files Browse the repository at this point in the history
Adapt to xclim 0.40
  • Loading branch information
bzah authored Feb 7, 2023
2 parents f57df73 + a2a7a44 commit de8fb71
Show file tree
Hide file tree
Showing 14 changed files with 105 additions and 25 deletions.
2 changes: 1 addition & 1 deletion doc/source/_static/logo_icclim_colored__displayed.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion doc/source/_static/logo_icclim_grey__displayed.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion doc/source/_static/logo_icclim_white__displayed.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 7 additions & 0 deletions doc/source/references/release_notes.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
Release history
===============

6.2.0 (unreleased)
------------------
* [maint] Upgrade and adapt to xclim 0.40.
Moved PercentileDataArray from xclim to icclim.
Adapted the unit cenversion to use the hydro context.


6.1.5
-----
* [fix] Bug fix: not assuming longitude and latitude are lon and lat with respect to output metadata. Fix needed to work on E-OBS and other datasets.
Expand Down
2 changes: 1 addition & 1 deletion environment.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ channels:
dependencies:
# Core dependencies
- python>=3.8
- xclim>=0.39
- xclim>=0.40
- numpy
- xarray>=2022.6
- cf_xarray>=0.7.4
Expand Down
11 changes: 6 additions & 5 deletions icclim/generic_indices/generic_indicators.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ def postprocess(
out_unit: str | None,
):
if out_unit is not None:
result = convert_units_to(result, out_unit)
result = convert_units_to(result, out_unit, context="hydro")
if self.missing != "skip" and indexer is not None:
# reference variable is a subset of the studied variable,
# so no need to check it.
Expand Down Expand Up @@ -231,9 +231,10 @@ def preprocess( # noqa signature != from super
for climate_var in climate_vars:
current_unit = climate_var.studied_data.attrs.get(UNITS_KEY, None)
if current_unit is not None and not _is_amount_unit(current_unit):
climate_var.studied_data = rate2amount(
climate_var.studied_data, out_units=output_unit
)
with xc_units.context("hydro"):
climate_var.studied_data = rate2amount(
climate_var.studied_data, out_units=output_unit
)
if coef is not None:
for climate_var in climate_vars:
climate_var.studied_data = coef * climate_var.studied_data
Expand Down Expand Up @@ -903,7 +904,7 @@ def get_couple_of_var(
)
study = climate_vars[0].studied_data
ref = climate_vars[1].studied_data
study = convert_units_to(study, ref)
study = convert_units_to(study, ref, context="hydro")
return study, ref


Expand Down
18 changes: 11 additions & 7 deletions icclim/generic_indices/threshold.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
from xclim.core.calendar import build_climatology_bounds, percentile_doy, resample_doy
from xclim.core.units import convert_units_to, str2pint
from xclim.core.units import units as xc_units
from xclim.core.utils import PercentileDataArray, calc_perc
from xclim.core.utils import calc_perc

from icclim.generic_indices.threshold_templates import (
EN_THRESHOLD_TEMPLATE,
Expand All @@ -37,6 +37,7 @@
QuantileInterpolationRegistry,
)
from icclim.pre_processing.input_parsing import (
PercentileDataArray,
build_reference_da,
find_standard_vars,
get_name_of_first_var,
Expand Down Expand Up @@ -439,7 +440,9 @@ def unit(self) -> str | None:
def unit(self, unit: str | xr.DataArray | pint.Quantity | pint.Unit):
if self.is_ready:
if self.value.attrs.get(UNITS_KEY, None) is not None and unit is not None:
self._prepared_value = convert_units_to(self._prepared_value, unit)
self._prepared_value = convert_units_to(
self._prepared_value, unit, context="hydro"
)
self.value.attrs[UNITS_KEY] = unit

@property
Expand Down Expand Up @@ -645,7 +648,7 @@ def unit(self) -> str | None:
@unit.setter
def unit(self, unit):
if self.value.attrs.get(UNITS_KEY, None) is not None and unit is not None:
self.value = convert_units_to(self.value, unit)
self.value = convert_units_to(self.value, unit, context="hydro")
self.value.attrs[UNITS_KEY] = unit

def __init__(
Expand Down Expand Up @@ -690,7 +693,7 @@ def __init__(
else:
raise NotImplementedError(f"Cannot build threshold from a {type(value)}.")
if unit is not None:
built_value = convert_units_to(built_value, unit)
built_value = convert_units_to(built_value, unit, context="hydro")
self.operator = operator
self.value = built_value
self.unit = unit
Expand Down Expand Up @@ -1049,9 +1052,10 @@ def _must_build_bounded_threshold(input: ThresholdBuilderInput) -> bool:
def _apply_min_value(thresh_da: DataArray, min_value: pint.Quantity | None):
if min_value is not None:
if min_value.dimensionless:
min_value = convert_units_to(min_value.m, thresh_da)
# We assume min_value use the same unit as thresh_da if it's dimensionless
min_value = min_value.m
else:
min_value = convert_units_to(str(min_value), thresh_da)
min_value = convert_units_to(str(min_value), thresh_da, context="hydro")
built_value = thresh_da.where(thresh_da > min_value, np.nan)
return built_value
else:
Expand Down Expand Up @@ -1095,6 +1099,6 @@ def _build_per_thresh_from_dataset(
)
if unit is not None:
if built_value.attrs.get(UNITS_KEY, None) is not None:
built_value = convert_units_to(built_value, unit)
built_value = convert_units_to(built_value, unit, context="hydro")
built_value.attrs[UNITS_KEY] = unit
return built_value, DOY_COORDINATE in built_value.coords
2 changes: 1 addition & 1 deletion icclim/models/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
# fmt: off
# flake8: noqa

ICCLIM_VERSION = "6.1.5"
ICCLIM_VERSION = "6.2.0"

# placeholders for user_index
PERCENTILE_THRESHOLD_STAMP = "p"
Expand Down
72 changes: 69 additions & 3 deletions icclim/pre_processing/input_parsing.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
from xarray.core.dataarray import DataArray
from xarray.core.dataset import Dataset
from xclim.core.units import convert_units_to
from xclim.core.utils import PercentileDataArray

from icclim.generic_indices.standard_variable import (
StandardVariable,
Expand All @@ -26,6 +25,69 @@
DEFAULT_INPUT_FREQUENCY = "days"


class PercentileDataArray(xr.DataArray):
"""Wrap xarray DataArray for percentiles values."""

__slots__ = ()

@classmethod
def is_compatible(cls, source: xr.DataArray) -> bool:
"""Evaluate whether PecentileDataArray is conformant with expected fields.
A PercentileDataArray must have climatology_bounds attributes and either a
quantile or percentiles coordinate, the window is not mandatory.
"""
return (
isinstance(source, xr.DataArray)
and source.attrs.get("climatology_bounds", None) is not None
and ("quantile" in source.coords or "percentiles" in source.coords)
)

@classmethod
def from_da(
cls, source: xr.DataArray, climatology_bounds: list[str] = None
) -> PercentileDataArray:
"""Create a PercentileDataArray from a xarray.DataArray.
Parameters
----------
source : xr.DataArray
A DataArray with its content containing percentiles values.
It must also have a coordinate variable percentiles or quantile.
climatology_bounds : list[str]
Optional. A List of size two which contains the period on which the
percentiles were computed. See
`xclim.core.calendar.build_climatology_bounds`
to build this list from a DataArray.
Returns
-------
PercentileDataArray
The initial `source` DataArray but wrap by PercentileDataArray class.
The data is unchanged and only climatology_bounds attributes is overridden
if q new value is given in inputs.
"""
if (
climatology_bounds is None
and source.attrs.get("climatology_bounds", None) is None
):
raise ValueError("PercentileDataArray needs a climatology_bounds.")
per = cls(source)
# handle case where da was created with `quantile()` method
if "quantile" in source.coords:
per = per.rename({"quantile": "percentiles"})
per.coords["percentiles"] = per.coords["percentiles"] * 100
clim_bounds = source.attrs.get("climatology_bounds", climatology_bounds)
per.attrs["climatology_bounds"] = clim_bounds
if "percentiles" in per.coords:
return per
raise ValueError(
f"DataArray {source.name} could not be turned into"
f" PercentileDataArray. The DataArray must have a"
f" 'percentiles' coordinate variable."
)


def guess_var_names(
ds: Dataset,
var_names: str | Sequence[str] | None,
Expand Down Expand Up @@ -300,7 +362,9 @@ def read_threshold_DataArray(
else:
if threshold_min_value:
if isinstance(threshold_min_value, str):
threshold_min_value = convert_units_to(threshold_min_value, thresh_da)
threshold_min_value = convert_units_to(
threshold_min_value, thresh_da, context="hydro"
)
# todo in prcptot the replacing value (np.nan) needs to be 0
built_value = thresh_da.where(thresh_da > threshold_min_value, np.nan)
else:
Expand Down Expand Up @@ -330,6 +394,8 @@ def build_reference_da(
if only_leap_years:
reference = reduce_only_leap_years(original_da)
if percentile_min_value is not None:
percentile_min_value = convert_units_to(str(percentile_min_value), reference)
percentile_min_value = convert_units_to(
str(percentile_min_value), reference, context="hydro"
)
reference = reference.where(reference >= percentile_min_value, np.nan)
return reference
2 changes: 1 addition & 1 deletion icclim/tests/test_input_parsing.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@
import pandas as pd
import pytest
import xarray as xr
from xclim.core.utils import PercentileDataArray

from icclim.ecad.ecad_indices import EcadIndexRegistry
from icclim.icclim_exceptions import InvalidIcclimArgumentError
from icclim.models.constants import UNITS_KEY
from icclim.pre_processing.input_parsing import (
PercentileDataArray,
guess_var_names,
read_dataset,
update_to_standard_coords,
Expand Down
2 changes: 2 additions & 0 deletions icclim/tests/test_threshold.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
from icclim.models.constants import UNITS_KEY
from icclim.models.logical_link import LogicalLinkRegistry
from icclim.models.operator import OperatorRegistry
from icclim.pre_processing.input_parsing import PercentileDataArray


def test_value_error():
Expand Down Expand Up @@ -295,6 +296,7 @@ def test_threshold_min_value__number_from_file(self):

def test_build_percentile_threshold__from_file(self):
doys = percentile_doy(self.data)
doys = PercentileDataArray.from_da(doys)
doys.to_netcdf(path=self.IN_FILE_PATH)
res = build_threshold(operator=">=", value=self.IN_FILE_PATH)
assert isinstance(res, PercentileThreshold)
Expand Down
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,5 @@ sphinx_codeautolink
sphinx_copybutton
sphinx_lfs_content
xarray>=2022.6
xclim~=0.39.0
xclim~=0.40
zarr
2 changes: 1 addition & 1 deletion requirements_dev.txt
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,5 @@ sphinx_copybutton
sphinx_lfs_content
twine
xarray>=2022.6
xclim~=0.39.0
xclim~=0.40
zarr
4 changes: 2 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
MINIMAL_REQUIREMENTS = [
"numpy>=1.16",
"xarray>=2022.6",
"xclim>=0.39",
"xclim>=0.40",
"cf_xarray>=0.7.4",
"cftime>=1.4.1",
"dask[array]",
Expand All @@ -21,7 +21,7 @@

setup(
name="icclim",
version="6.1.5",
version="6.2.0",
packages=find_packages(),
author="Christian P.",
author_email="[email protected]",
Expand Down

0 comments on commit de8fb71

Please sign in to comment.