# (C) Crown Copyright, Met Office. All rights reserved.
#
# This file is part of 'IMPROVER' and is released under the BSD 3-Clause license.
# See LICENSE in the root of the repository for full licensing details.
import os
import numpy as np
from iris.cube import Cube
from improver.fire_weather import IterativeFireWeatherBase
DC_START_VALUE = os.environ.get("DC_START_VALUE", 15)
DC_LAG_TIME = os.environ.get("DC_LAG_TIME", 53)
[docs]
class DroughtCode(IterativeFireWeatherBase):
"""
Plugin to calculate the Drought Code (DC).
The DC is a numerical rating of the average moisture content of deep,
compact organic layers. It is a useful indicator of seasonal drought
effects on forest fuels and the amount of smoldering in deep duff layers
and large logs. Higher values indicate drier conditions.
This process is adapted directly from:
Equations and FORTRAN Program for the
Canadian Forest Fire Weather Index System
(C.E. Van Wagner and T.L. Pickett, 1985).
Pages 6-7, Equations 18-23.
Expected input units:
- Temperature: degrees Celsius
- Precipitation: mm (24-hour accumulation)
- Previous DC: dimensionless
- Month: integer (1-12) for day length factor lookup
"""
STARTING_VALUE = DC_START_VALUE
LAG_TIME = DC_LAG_TIME
METADATA_SOURCE_CUBE = "drought_code"
INPUT_CUBE_NAMES = [
"air_temperature",
"lwe_thickness_of_precipitation_amount",
METADATA_SOURCE_CUBE,
]
OUTPUT_CUBE_NAME = "drought_code"
# Valid output ranges for warning checks (output_name: (min, max))
# Minimum and maximum feasible values for each output index are drawn from
# values reported in:
# Wang, X., Oliver, J., Swystun, T., Hanes, C.C., Erni, S. and Flannigan,
# M.D., 2023. Critical fire weather conditions during active fire spread
# days in Canada. Science of the total environment, 869, p.161831.
VALID_OUTPUT_RANGE = (0.0, 1000)
REQUIRES_MONTH = True
# Disambiguate input DC (yesterday's value) from output DC (today's calculated value)
INPUT_ATTRIBUTE_MAPPINGS = {"drought_code": "input_dc"}
temperature: Cube
precipitation: Cube
input_dc: Cube
month: int
previous_dc: np.ndarray
# Day length factors for DC calculation (L_f values from Table 4)
# Index 0 is unused, indices 1-12 correspond to months January-December
DC_DAY_LENGTH_FACTORS = [
0.0, # Placeholder for index 0
-1.6, # January
-1.6, # February
-1.6, # March
0.9, # April
3.8, # May
5.8, # June
6.4, # July
5.0, # August
2.4, # September
0.4, # October
-1.6, # November
-1.6, # December
]
[docs]
def _calculate(self) -> np.ndarray:
"""Calculate the Drought Code (DC).
Returns:
The calculated DC values for the current day.
"""
# Step 1: Set today's DC value to the previous day's DC value
self.previous_dc = self.input_dc.data.copy()
# Step 2: Perform rainfall adjustment, if precipitation > 2.8 mm
self._perform_rainfall_adjustment()
# Steps 3 & 4: Calculate potential evapotranspiration
potential_evapotranspiration = self._calculate_potential_evapotranspiration()
# Step 5: Calculate DC from adjusted previous DC and potential evapotranspiration
dc = self._calculate_dc(potential_evapotranspiration)
return dc
[docs]
def _calculate_potential_evapotranspiration(self) -> np.ndarray:
"""Calculates the potential evapotranspiration adjusted for day length.
This represents the moisture loss from deep layers due to evaporation
and transpiration.
From Van Wagner and Pickett (1985), Pages 6-7: Equation 22, Steps 3 & 4.
Returns:
The potential evapotranspiration value.
"""
# Apply temperature lower bound of -2.8°C
temp_adjusted = np.maximum(self.temperature.data, -2.8)
# Step 3: Get day length factor for current month
day_length_factor = self.DC_DAY_LENGTH_FACTORS[self.month]
# Step 4: Calculate potential evapotranspiration via Equation 22
potential_evapotranspiration = 0.36 * (temp_adjusted + 2.8) + day_length_factor
return potential_evapotranspiration
[docs]
def _calculate_dc(self, potential_evapotranspiration: np.ndarray) -> np.ndarray:
"""Calculates the Drought Code from previous DC and potential evapotranspiration.
From Van Wagner and Pickett (1985), Page 7: Equation 23.
Args:
potential_evapotranspiration:
The potential evapotranspiration.
Returns:
The calculated DC value.
"""
# Equation 23: Calculate DC
dc = self.previous_dc + 0.5 * potential_evapotranspiration
# Apply lower bound of 0
dc = np.maximum(dc, 0.0)
return dc