# (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
DMC_START_VALUE = os.environ.get("DMC_START_VALUE", 6)
DMC_LAG_TIME = os.environ.get("DMC_LAG_TIME", 15)
[docs]
class DuffMoistureCode(IterativeFireWeatherBase):
"""
Plugin to calculate the Duff Moisture Code (DMC).
The DMC is a numerical rating of the average moisture content of loosely
compacted organic layers of moderate depth. It indicates fuel consumption
in moderate duff layers and medium-size woody material.
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).
Page 6, Equations 11-16.
Expected input units:
- Temperature: degrees Celsius
- Precipitation: mm (24-hour accumulation)
- Relative humidity: percentage (0-100)
- Previous DMC: dimensionless
- Month: integer (1-12) for day length factor lookup
"""
STARTING_VALUE = DMC_START_VALUE
LAG_TIME = DMC_LAG_TIME
METADATA_SOURCE_CUBE = "duff_moisture_code"
INPUT_CUBE_NAMES = [
"air_temperature",
"lwe_thickness_of_precipitation_amount",
"relative_humidity",
METADATA_SOURCE_CUBE,
]
OUTPUT_CUBE_NAME = "duff_moisture_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, 400)
REQUIRES_MONTH = True
# Disambiguate input DMC (yesterday's value) from output DMC (today's calculated value)
INPUT_ATTRIBUTE_MAPPINGS = {"duff_moisture_code": "input_dmc"}
temperature: Cube
precipitation: Cube
relative_humidity: Cube
input_dmc: Cube
month: int
previous_dmc: np.ndarray
# Day length factors for DMC calculation (L_e values from Table 3)
# Index 0 is unused, indices 1-12 correspond to months January-December
DMC_DAY_LENGTH_FACTORS = [
0.0, # Placeholder for index 0
6.5, # January
7.5, # February
9.0, # March
12.8, # April
13.9, # May
13.9, # June
12.4, # July
10.9, # August
9.4, # September
8.0, # October
7.0, # November
6.0, # December
]
[docs]
def _calculate(self) -> np.ndarray:
"""Calculate the Duff Moisture Code (DMC).
Returns:
np.ndarray: The calculated DMC values for the current day.
"""
# Step 1: Set today's DMC value to the previous day's DMC value
self.previous_dmc = self.input_dmc.data.copy()
# Step 2: Perform rainfall adjustment, if precipitation > 1.5 mm
self._perform_rainfall_adjustment()
# Steps 3 & 4: Calculate drying rate
drying_rate = self._calculate_drying_rate()
# Step 5: Calculate DMC from adjusted previous DMC and drying rate
dmc = self._calculate_dmc(drying_rate)
return dmc
[docs]
def _calculate_drying_rate(self) -> np.ndarray:
"""Calculates the drying rate for DMC. This is multiplied by 100 for
computational efficiency in the final DMC calculation. The original
algorithm calculates K and then multiplies it by 100 in the DMC equation.
Temperature is bounded to a minimum of -1.1°C. Uses the day length factor
for the current month from DMC_DAY_LENGTH_FACTORS.
From Van Wagner and Pickett (1985), Page 6: Equation 16, Steps 3 & 4.
Returns:
The drying rate (dimensionless). Shape matches input cube data
shape. This value is added to previous DMC to get today's DMC.
"""
# Apply temperature lower bound of -1.1°C
temp_adjusted = np.maximum(self.temperature.data, -1.1)
# Step 3: Get day length factor for current month
day_length_factor = self.DMC_DAY_LENGTH_FACTORS[self.month]
# Step 4: Calculate drying rate via Equation 16
drying_rate = (
1.894
* (temp_adjusted + 1.1)
* (100.0 - self.relative_humidity.data)
* day_length_factor
* 1e-4
)
return drying_rate
[docs]
def _calculate_dmc(self, drying_rate: np.ndarray) -> np.ndarray:
"""Calculates the Duff Moisture Code from previous DMC and drying rate.
Note that the drying rate is expected to be pre-multiplied by 100
for computational efficiency. This mathematically matches the original
algorithm, but is more efficient to implement this way.
From Van Wagner and Pickett (1985), Page 6: Equation 16.
Args:
drying_rate:
The drying rate, represented by 'K' in the original equations.
Returns:
The calculated DMC value.
"""
# Equation 16: Calculate DMC
dmc = self.previous_dmc + drying_rate
# Apply lower bound of 0
dmc = np.maximum(dmc, 0.0)
return dmc