Skip to content

Commit

Permalink
Add DMC standalone (#1712)
Browse files Browse the repository at this point in the history
### What kind of change does this PR introduce?

* Adds an indicator entry point for computing the duff moisture code
(DMC) alone. Alike what is already done for the drought code (DC).
* I added tests for explicitly testing the two standalone indicators.
* Minor adjustment for a better error message when passing unneeded
parameters to DC and DMC.

### Does this PR introduce a breaking change?
No.
  • Loading branch information
Zeitsperre authored Apr 18, 2024
2 parents 5202b15 + e5b2370 commit b501107
Show file tree
Hide file tree
Showing 6 changed files with 162 additions and 5 deletions.
1 change: 1 addition & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ Announcements
New indicators
^^^^^^^^^^^^^^
* New ``snw_season_length`` and ``snd_season_length`` computing the duration between the start and the end of the snow season, both defined as the first day of a continuous period with snow above/under a threshold. Previous versions of these indicators were renamed ``snw_days_above`` and ``snd_days_above`` to better reflect what they computed : the number of days with snow above a given threshold (with no notion of continuity). (:issue:`1703`, :pull:`1708`).
* Added ``atmos.duff_moisture_code``, part of the Canadian Forest Fire Weather Index System. It was already an output of the `atmos.cffwis_indices`, but now has its own standalone indicator. (:issue:`1698`, :pull:`1712`).

Breaking changes
^^^^^^^^^^^^^^^^
Expand Down
44 changes: 44 additions & 0 deletions tests/test_cffwis.py
Original file line number Diff line number Diff line change
Expand Up @@ -458,3 +458,47 @@ def test_gfwed_and_indicators(self, open_dataset):
[ds2.DC, ds2.DMC, ds2.FFMC, ds2.ISI, ds2.BUI, ds2.FWI], outs
):
np.testing.assert_allclose(out, exp, rtol=0.03)

def test_gfwed_drought_code(self, open_dataset):
# Also tests passing parameters as quantity strings
ds = open_dataset("FWI/GFWED_sample_2017.nc")

out = atmos.drought_code(
tas=ds.tas,
pr=ds.prbc,
snd=ds.snow_depth,
lat=ds.lat,
season_method="GFWED",
overwintering=False,
dry_start="GFWED",
temp_condition_days=3,
snow_condition_days=3,
temp_start_thresh="6 degC",
temp_end_thresh="6 degC",
)

np.testing.assert_allclose(
out.isel(loc=[0, 1]), ds.DC.isel(loc=[0, 1]), rtol=0.03
)

def test_gfwed_duff_moisture_code(self, open_dataset):
# Also tests passing parameters as quantity strings
ds = open_dataset("FWI/GFWED_sample_2017.nc")

out = atmos.duff_moisture_code(
tas=ds.tas,
pr=ds.prbc,
hurs=ds.rh,
snd=ds.snow_depth,
lat=ds.lat,
season_method="GFWED",
dry_start="GFWED",
temp_condition_days=3,
snow_condition_days=3,
temp_start_thresh="6 degC",
temp_end_thresh="6 degC",
)

np.testing.assert_allclose(
out.isel(loc=[0, 1]), ds.DMC.isel(loc=[0, 1]), rtol=0.03
)
6 changes: 6 additions & 0 deletions xclim/data/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -799,6 +799,12 @@
"long_name": "Indice forêt météo",
"description": "Évaluation numérique de l'intensité du feu."
},
"DMC": {
"long_name": "Indice de l'humus",
"description": "Évaluation numérique de la teneur moyenne en eau des couches organiques peu tassées de moyenne épaisseur.",
"title": "Indice de l'humus (IH)",
"abstract": "Évaluation numérique de la teneur moyenne en eau des couches organiques peu tassées de moyenne épaisseur."
},
"WIND_POWER_POTENTIAL": {
"long_name": "Potentiel de production éolienne",
"description": "Potentiel de production éolienne estimé par une courbe de puissance semi-idéalisée d'une turbine, paramétrée par une vitesse minimale de démarrage {cut_in}, la vitesse nominale {rated}, et la vitesse d'arrêt {cut_out}.",
Expand Down
10 changes: 9 additions & 1 deletion xclim/indicators/atmos/_precip.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
"dry_spell_max_length",
"dry_spell_total_length",
"dryness_index",
"duff_moisture_code",
"first_snowfall",
"fraction_over_precip_doy_thresh",
"fraction_over_precip_thresh",
Expand Down Expand Up @@ -394,7 +395,6 @@ class HrPrecip(Hourly):
title="Daily drought code",
identifier="dc",
units="",
standard_name="drought_code",
long_name="Drought Code",
description="Numerical code estimating the average moisture content of organic layers.",
abstract="The Drought Index is part of the Canadian Forest-Weather Index system. "
Expand All @@ -403,6 +403,14 @@ class HrPrecip(Hourly):
missing="skip",
)

duff_moisture_code = FireWeather(
identifier="dmc",
units="",
long_name="Duff Moisture Code",
description="Numeric rating of the average moisture content of loosely compacted organic layers of moderate depth.",
compute=indices.duff_moisture_code,
missing="skip",
)

cffwis_indices = FireWeather(
identifier="cffwis",
Expand Down
1 change: 1 addition & 0 deletions xclim/indices/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from .fire import (
cffwis_indices,
drought_code,
duff_moisture_code,
fire_season,
griffiths_drought_factor,
keetch_byram_drought_index,
Expand Down
105 changes: 101 additions & 4 deletions xclim/indices/fire/_cffwis.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@
"cffwis_indices",
"daily_severity_rating",
"drought_code",
"duff_moisture_code",
"fire_season",
"fire_weather_index",
"fire_weather_ufunc",
Expand Down Expand Up @@ -1247,12 +1248,13 @@ def overwintering_drought_code(
return wDC


# TODO: The `noqa` can be removed when this function is no longer public
def _convert_parameters(params: dict[str, int | float]) -> dict[str, int | float]:
def _convert_parameters(
params: dict[str, int | float], funcname: str = "fire weather indices"
) -> dict[str, int | float]:
for param, value in params.copy().items():
if param not in default_params:
raise ValueError(
f"{param} is not a valid parameter for fire weather indices. See list in xc.indices.fire.default_params."
f"{param} is not a valid parameter for {funcname}. See the docstring of the function and the list in xc.indices.fire.default_params."
)
if isinstance(default_params[param], tuple):
params[param] = convert_units_to(value, default_params[param][1])
Expand Down Expand Up @@ -1469,12 +1471,107 @@ def drought_code(
overwintering=overwintering,
dry_start=dry_start,
initial_start_up=initial_start_up,
**_convert_parameters(params),
**_convert_parameters(params, "drought_code"),
)
out["DC"].attrs["units"] = ""
return out["DC"]


@declare_units(
tas="[temperature]",
pr="[precipitation]",
hurs="[]",
lat="[]",
snd="[length]",
dmc0="[]",
season_mask="[]",
)
def duff_moisture_code(
tas: xr.DataArray,
pr: xr.DataArray,
hurs: xr.DataArray,
lat: xr.DataArray,
snd: xr.DataArray | None = None,
dmc0: xr.DataArray | None = None,
season_mask: xr.DataArray | None = None,
season_method: str | None = None,
dry_start: str | None = None,
initial_start_up: bool = True,
**params,
) -> xr.DataArray:
r"""Duff moisture code (FWI component).
The duff moisture code is part of the Canadian Forest Fire Weather Index System.
It is a numeric rating of the average moisture content of loosely compacted organic layers of moderate depth.
Parameters
----------
tas : xr.DataArray
Noon temperature.
pr : xr.DataArray
Rain fall in open over previous 24 hours, at noon.
hurs : xr.DataArray
Noon relative humidity.
lat : xr.DataArray
Latitude coordinate
snd : xr.DataArray
Noon snow depth.
dmc0 : xr.DataArray
Initial values of the duff moisture code.
season_mask : xr.DataArray, optional
Boolean mask, True where/when the fire season is active.
season_method : {None, "WF93", "LA08", "GFWED"}
How to compute the start-up and shutdown of the fire season.
If "None", no start-ups or shutdowns are computed, similar to the R fire function.
Ignored if `season_mask` is given.
dry_start : {None, "CFS", 'GFWED'}
Whether to activate the DC and DMC "dry start" mechanism and which method to use.
See :py:func:`fire_weather_ufunc`.
initial_start_up : bool
If True (default), grid points where the fire season is active on the first timestep go through a start_up phase
for that time step. Otherwise, previous codes must be given as a continuing fire season is assumed for those
points.
params
Any other keyword parameters as defined in `xclim.indices.fire.fire_weather_ufunc` and in :py:data:`default_params`.
Returns
-------
xr.DataArray, [dimensionless]
Duff moisture code
Notes
-----
See :cite:cts:`code-natural_resources_canada_data_nodate`, the :py:mod:`xclim.indices.fire` module documentation,
and the docstring of :py:func:`fire_weather_ufunc` for more information. This algorithm follows the official R code
released by the CFS, which contains revisions from the original 1982 Fortran code.
References
----------
:cite:cts:`fire-wang_updated_2015`
"""
tas = convert_units_to(tas, "C")
pr = convert_units_to(pr, "mm/day")
if snd is not None:
snd = convert_units_to(snd, "m")

out = fire_weather_ufunc(
tas=tas,
pr=pr,
hurs=hurs,
lat=lat,
dmc0=dmc0,
snd=snd,
indexes=["DMC"],
season_mask=season_mask,
season_method=season_method,
dry_start=dry_start,
initial_start_up=initial_start_up,
**_convert_parameters(params, "duff_moisture_code"),
)
out["DMC"].attrs["units"] = ""
return out["DMC"]


@declare_units(
tas="[temperature]",
snd="[length]",
Expand Down

0 comments on commit b501107

Please sign in to comment.