Source code for improver.fire_weather.build_up_index
# (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 numpy as np
from iris.cube import Cube
from improver.fire_weather import FireWeatherBase
[docs]
class BuildUpIndex(FireWeatherBase):
"""
Plugin to calculate the Build Up Index (BUI).
The BUI is a numerical rating of the total amount of fuel available
for combustion. It combines the Duff Moisture Code (DMC) and the
Drought Code (DC) to represent the fuel buildup.
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 7, Equations 27a-27b.
Expected input units:
- Duff Moisture Code (DMC): dimensionless
- Drought Code (DC): dimensionless
"""
METADATA_SOURCE_CUBE = "drought_code"
INPUT_CUBE_NAMES = ["duff_moisture_code", METADATA_SOURCE_CUBE]
OUTPUT_CUBE_NAME = "build_up_index"
# 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, 500)
# Map input cube names to internal attribute names for consistency
INPUT_ATTRIBUTE_MAPPINGS = {
"duff_moisture_code": "input_dmc",
"drought_code": "input_dc",
}
input_dmc: Cube
input_dc: Cube
[docs]
def _calculate(self) -> np.ndarray:
"""Calculates the Build Up Index (BUI) from DMC and DC.
From Van Wagner and Pickett (1985), Page 7: Equations 27a-27b.
Returns:
The calculated BUI values.
"""
dmc_data = self.input_dmc.data
dc_data = self.input_dc.data
# Condition 1: If both DMC and DC are zero, set BUI = 0
both_zero = np.isclose(dmc_data, 0.0, atol=1e-7) & np.isclose(
dc_data, 0.0, atol=1e-7
)
# Condition 2: If DMC <= 0.4 * DC use equation 27a
use_27a = dmc_data <= 0.4 * dc_data
# Calculate equations, suppressing divide-by-zero warnings
# (the np.where will handle the zero cases correctly)
with np.errstate(divide="ignore", invalid="ignore"):
bui_27a = (0.8 * dmc_data * dc_data) / (dmc_data + 0.4 * dc_data)
# Condition 3: If DMC > 0.4 * DC use equation 27b
bui_27b = dmc_data - (
1.0 - (0.8 * dc_data / (dmc_data + 0.4 * dc_data))
) * (0.92 + (0.0114 * dmc_data) ** 1.7)
# Apply conditions using np.where:
# 1. If both_zero: BUI = 0
# 2. Elif use_27a (DMC <= 0.4*DC): BUI = bui_27a
# 3. Else (DMC > 0.4*DC): BUI = bui_27b
bui = np.where(both_zero, 0.0, np.where(use_27a, bui_27a, bui_27b))
# Ensure BUI is never negative
bui = np.clip(bui, 0.0, None)
return bui