Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions improver/api/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
"ApplyRainForestsCalibration": "improver.calibration.rainforest_calibration",
"ApplyReliabilityCalibration": "improver.calibration.reliability_calibration",
"BaseNeighbourhoodProcessing": "improver.nbhood.nbhood",
"BuildUpIndex": "improver.fire_weather.build_up_index",
"CalculateForecastBias": "improver.calibration.simple_bias_correction",
"CalculateWindChill": "improver.temperature.feels_like_temperature",
"CalibratedForecastDistributionParameters": "improver.calibration.emos_calibration",
Expand Down
4 changes: 2 additions & 2 deletions improver/fire_weather/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ class FireWeatherIndexBase(BasePlugin):
The Canadian Forest Fire Weather Index System requires specific units
for all calculations. These are fixed and cannot be overridden:

- Temperature: degrees Celsius (degC)
- Temperature: degrees Celsius (Celsius)
- Precipitation: millimeters (mm)
- Relative humidity: dimensionless fraction (1)
- Wind speed: kilometers per hour (km/h)
Expand All @@ -50,7 +50,7 @@ class FireWeatherIndexBase(BasePlugin):
# Fixed unit conversions for all cube types used in fire weather calculations
# These units are required by the Canadian FWI System and cannot be changed
_REQUIRED_UNITS: dict[str, str] = {
"temperature": "degC",
"temperature": "Celsius",
"precipitation": "mm",
"relative_humidity": "1",
"wind_speed": "km/h",
Expand Down
78 changes: 78 additions & 0 deletions improver/fire_weather/build_up_index.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# (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 FireWeatherIndexBase


class BuildUpIndex(FireWeatherIndexBase):
"""
Plugin to calculate the Build Up Index (BUI) following
the Canadian Forest Fire Weather Index System.

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
"""

INPUT_CUBE_NAMES = ["duff_moisture_code", "drought_code"]
OUTPUT_CUBE_NAME = "build_up_index"
# 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

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:
np.ndarray: 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 = (dmc_data == 0.0) & (dc_data == 0.0)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Floating point equalities are flaky. It would be better to use np.isclose(dmc_data, 0.0, atol=1e-7), or use the if statement in the Fortran: dmc_data < 1e-7


# Condition 2: If DMC <= 0.4 * DC use equation 27a
use_27a = dmc_data <= 0.4 * dc_data
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Fortran has < instead of <=. I doubt this will have any noticeable impact.


# 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)
Comment on lines +75 to +76
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that if condition 1 is changed to match the Fortran inequality, it will mean that bui can never be negative and this line becomes superfluous.


return bui
2 changes: 1 addition & 1 deletion improver_tests/fire_weather/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ def make_input_cubes(
Example:
>>> cubes = make_input_cubes(
... [
... ("air_temperature", 20.0, "degC", False),
... ("air_temperature", 20.0, "Celsius", False),
... ("lwe_thickness_of_precipitation_amount", 1.0, "mm", True),
... ]
... )
Expand Down
Loading