import numpy as np
from pint import Quantity
from ..tools.check_data_quality import extract_magnitudes
from ..tools.input_info import ParameterType
from ..tools.namespace import StandardVariableNames as svn
from ..tools.rate_of_spread_model import RateOfSpreadModel
from ..tools.units import ureg
[docs]
class Rothermel_SFIRE(RateOfSpreadModel):
"""
A class to represent the Rothermel's model for fire spread rate calculation used in SFIRE code.
This class provides metadata for various fuel properties and a static method to compute the rate of spread (ROS).
The metadata includes descriptions, units, and acceptable ranges for each property.
Metadata
--------
The model uses the following fuel parameters:
- ``fgi``
- Standard name: ``FUEL_LOAD_DRY_TOTAL``
- Units: ``kilogram / meter ** 2``
- Range: ``0 to inf``
- Type: ``input``
- ``fueldepthm``
- Standard name: ``FUEL_HEIGHT``
- Units: ``meter``
- Range: ``0 to inf``
- Type: ``input``
- ``fueldens``
- Standard name: ``FUEL_DENSITY``
- Units: ``pound / foot ** 3``
- Range: ``0 to inf``
- Type: ``input``
- ``savr``
- Standard name: ``FUEL_SURFACE_AREA_VOLUME_RATIO``
- Units: ``1 / foot``
- Range: ``0 to inf``
- Type: ``input``
- ``fuelmce``
- Standard name: ``FUEL_MOISTURE_EXTINCTION``
- Units: ``percent``
- Range: ``0 to inf``
- Type: ``input``
- ``st``
- Standard name: ``FUEL_MINERAL_CONTENT_TOTAL``
- Units: ``dimensionless``
- Range: ``0 to 1``
- Type: ``input``
- ``se``
- Standard name: ``FUEL_MINERAL_CONTENT_EFFECTIVE``
- Units: ``dimensionless``
- Range: ``0 to 1``
- Type: ``input``
- ``ichap``
- Standard name: ``FUEL_CHAPARRAL_FLAG``
- Units: ``dimensionless``
- Range: ``0 to 1``
- Type: ``input``
- ``wind``
- Standard name: ``WIND_SPEED``
- Units: ``meter / second``
- Range: ``-inf to inf``
- Type: ``input``
- ``slope``
- Standard name: ``SLOPE_ANGLE``
- Units: ``degree``
- Range: ``-90 to 90``
- Type: ``input``
- ``fmc``
- Standard name: ``FUEL_MOISTURE_CONTENT``
- Units: ``percent``
- Range: ``0 to 200``
- Type: ``input``
- ``rate_of_spread``
- Standard name: ``RATE_OF_SPREAD``
- Units: ``meter / second``
- Range: ``0 to inf``
- Type: ``output``
""" # pylint: disable=line-too-long
metadata = {
"fgi": {
"std_name": svn.FUEL_LOAD_DRY_TOTAL,
"units": ureg.kilogram / ureg.meter**2,
"range": (0, np.inf),
"type": ParameterType.input,
},
"fueldepthm": {
"std_name": svn.FUEL_HEIGHT,
"units": ureg.meter,
"range": (0, np.inf),
"type": ParameterType.input,
},
"fueldens": {
"std_name": svn.FUEL_DENSITY,
"units": ureg.pound / ureg.foot**3,
"range": (0, np.inf),
"type": ParameterType.input,
},
"savr": {
"std_name": svn.FUEL_SURFACE_AREA_VOLUME_RATIO,
"units": 1 / ureg.foot,
"range": (0, np.inf),
"type": ParameterType.input,
},
"fuelmce": {
"std_name": svn.FUEL_MOISTURE_EXTINCTION,
"units": ureg.percent,
"range": (0, np.inf),
"type": ParameterType.input,
},
"st": {
"std_name": svn.FUEL_MINERAL_CONTENT_TOTAL,
"units": ureg.dimensionless,
"range": (0, 1),
"type": ParameterType.input,
},
"se": {
"std_name": svn.FUEL_MINERAL_CONTENT_EFFECTIVE,
"units": ureg.dimensionless,
"range": (0, 1),
"type": ParameterType.input,
},
"ichap": {
"std_name": svn.FUEL_CHAPARRAL_FLAG,
"units": ureg.dimensionless,
"range": (0, 1),
"type": ParameterType.input,
},
"wind": {
"std_name": svn.WIND_SPEED,
"units": ureg.meter / ureg.second,
"range": (-np.inf, np.inf),
"type": ParameterType.input,
},
"slope": {
"std_name": svn.SLOPE_ANGLE,
"units": ureg.degree,
"range": (-90, 90),
"type": ParameterType.input,
},
"fmc": {
"std_name": svn.FUEL_MOISTURE_CONTENT,
"units": ureg.percent,
"range": (0, 200),
"type": ParameterType.input,
},
"rate_of_spread": {
"std_name": svn.RATE_OF_SPREAD,
"units": ureg.meter / ureg.second,
"range": (0, np.inf),
"type": ParameterType.output,
},
}
[docs]
@staticmethod
def rothermel(
fgi: float,
fueldepthm: float,
fueldens: float,
savr: float,
fuelmce: float,
st: float,
se: float,
ichap: int,
wind: float,
slope: float,
fmc: float,
) -> float:
"""
Compute the rate of spread using Rothermel's model.
This function calculates the forward rate of spread of a fire in a uniform fuel bed using
Rothermel's (1972) fire spread model. The model considers fuel properties, wind speed, slope,
and fuel moisture content to estimate the rate at which a fire will spread through wildland fuels.
Parameters
----------
fgi : float
Total fuel load (dry weight) per unit area [kg m-2].
Represents the total mass of combustible material available to the fire.
fueldepthm : float
Fuel bed depth [m].
The vertical depth of the fuel bed perpendicular to the ground surface.
fueldens : float
Oven-dry fuel particle density [lb ft-3].
The density of individual fuel particles when completely dry.
savr : float
Surface-area-to-volume ratio [ft-1].
A measure of the fineness of the fuel particles; higher values indicate finer fuels.
fuelmce : float
Moisture content of extinction [%].
The fuel moisture content at which a fire will no longer spread.
st : float
Total mineral content [unitless, between 0 and 1].
The proportion of the fuel that is composed of mineral (inorganic) material.
se : float
Effective mineral content [unitless, between 0 and 1].
The proportion of mineral content that effectively absorbs heat.
ichap : int
Chaparral flag (0 or 1).
Indicator for chaparral fuel type: set to 1 if the fuel is chaparral, otherwise 0.
wind : float
Wind speed at midflame height or average wind over flame height [m s-1].
The wind speed influencing the fire spread at the height of the flames.
slope : float
Slope steepness [degrees].
The angle of the terrain slope; positive for uphill, negative for downhill.
fmc : float
Fuel moisture content [%].
The actual moisture content of the fuel affecting combustion.
**opt : dict, optional
Additional optional parameters.
Returns
-------
float
Rate of spread [m/s].
The estimated forward rate of spread of the fire.
References
----------
Rothermel, R. C. (1972).
*A mathematical model for predicting fire spread in wildland fuels*.
USDA Forest Service Research Paper INT-115. Ogden, UT.
""" # pylint: disable=line-too-long
# Fuel category values
cmbcnst = 17.433e06 # [J/kg]
tanphi = np.tan(np.deg2rad(slope))
fuelmc_g = fmc * 0.01
fuelheat = cmbcnst * 4.30e-04 # Convert J/kg to BTU/lb
# Convert units
fuelload = fgi * 0.3048**2 * 2.205 # to lb/ft^2
fueldepth = fueldepthm / 0.3048 # to ft
betafl = fuelload / (fueldepth * fueldens) # Packing ratio
# Calculate various coefficients
betaop = 3.348 * savr ** (-0.8189) # Optimum packing ratio
qig = 250.0 + 1116.0 * fuelmc_g # Heat of preignition, BTU/lb
epsilon = np.exp(-138.0 / savr) # Effective heating number
rhob = fuelload / fueldepth # Ovendry bulk density, lb/ft^3
c = 7.47 * np.exp(-0.133 * savr**0.55) # Wind coefficient constant
bbb = 0.02526 * savr**0.54 # Wind coefficient constant
e = 0.715 * np.exp(-3.59e-4 * savr) # Wind coefficient constant
phiwc = c * (betafl / betaop) ** (-e)
rtemp2 = savr**1.5
gammax = rtemp2 / (495.0 + 0.0594 * rtemp2) # Maximum reaction velocity, 1/min
a = 1.0 / (4.774 * savr**0.1 - 7.27) # Coefficient for optimum reaction velocity
ratio = betafl / betaop
gamma = gammax * (ratio**a) * np.exp(a * (1.0 - ratio)) # Optimum reaction velocity, 1/min
wn = fuelload / (1.0 + st) # Net fuel loading, lb/ft^2
rtemp1 = fuelmc_g / fuelmce
etam = min(
1, max(0, 1.0 - 2.59 * rtemp1 + 5.11 * rtemp1**2 - 3.52 * rtemp1**3)
) # Moisture damping coefficient
etas = 0.174 * se ** (-0.19) # Mineral damping coefficient
ir = gamma * wn * fuelheat * etam * etas # Reaction intensity, BTU/ft^2 min
irm = ir * 1055.0 / (0.3048**2 * 60.0) * 1e-6 # For MW/m^2 (set but not used)
xifr = np.exp((0.792 + 0.681 * savr**0.5) * (betafl + 0.1)) / (
192.0 + 0.2595 * savr
) # Propagating flux ratio
r_0 = ir * xifr / (rhob * epsilon * qig) # Default spread rate in ft/min
if ichap == 0:
# If wind is 0 or into fireline, phiw = 0, and this reduces to backing rate of spread.
spdms = max(wind, 0.0)
umidm = min(spdms, 30.0) # Max input wind speed is 30 m/s
umid = umidm * 196.850 # m/s to ft/min
phiw = umid**bbb * phiwc # Wind coefficient
phis = 5.275 * betafl ** (-0.3) * max(0.0, tanphi) ** 2 # Slope factor
ros = r_0 * (1.0 + phiw + phis) * 0.00508 # Spread rate, m/s
else:
# Spread rate has no dependency on fuel character, only windspeed
spdms = max(wind, 0.0)
ros = max(0.03333, 1.2974 * spdms**1.41) # Spread rate, m/s
# Default
return min(ros, 6.0)
[docs]
@staticmethod
def compute_ros(
input_dict: dict[str, float | int | list[float] | list[int]],
fuel_cat: int = 0,
**opt,
) -> float:
"""
Compute the rate of spread of fire using ``Rothermel``'s model.
This function processes input fuel properties, optionally selects a specific fuel category,
and calculates the ROS. Input data must be provided in standard units without ``pint.Quantity`` objects.
For unit-aware calculations, use `compute_ros_with_units`.
Parameters
----------
input_dict : dict
Dictionary containing the input data for various fuel properties.
The keys should be the standard variable names as defined in ``Rothermel_SFIRE.metadata``.
Each value can be a single float/int or a list/array of floats/ints.
fuel_cat : int, optional
Fuel category index (one-based). If provided, fuel properties are expected to be lists or arrays,
and the function will extract the properties corresponding to the specified fuel category.
If not provided, fuel properties are expected to be scalar values.
**opt : dict
Additional optional parameters to be passed to the ``rothermel`` method.
Returns
-------
float
The computed rate of spread of fire.
Notes
-----
- ``fuel_cat`` uses one-based indexing to align with natural fuel category numbering.
When accessing lists or arrays in `input_dict`, the index is adjusted accordingly (i.e., `index = fuel_cat - 1`).
- This function assumes ``input_dict`` contains values in standard units (e.g., no ``pint.Quantity`` objects), compliant with units specified in the metadata dictionary.
Examples
--------
**Example with scalar fuel properties:**
.. code-block:: python
input_data = {
svn.FUEL_LOAD_DRY_TOTAL: 0.5, # fgi
svn.FUEL_HEIGHT: 0.3, # fueldepthm
svn.FUEL_DENSITY: 32.0, # fueldens
svn.FUEL_SURFACE_AREA_VOLUME_RATIO: 2000.0, # savr
svn.FUEL_MOISTURE_EXTINCTION: 30.0, # fuelmce
svn.FUEL_MINERAL_CONTENT_TOTAL: 0.0555, # st
svn.FUEL_MINERAL_CONTENT_EFFECTIVE: 0.01, # se
svn.FUEL_CHAPARRAL_FLAG: 0, # ichap
svn.WIND_SPEED: 2.0,
svn.SLOPE_ANGLE: 15.0,
svn.FUEL_MOISTURE_CONTENT: 10.0,
}
ros = Rothermel_SFIRE.compute_ros(input_data)
print(f"The rate of spread is {ros:.4f}")
**Example with fuel categories:**
.. code-block:: python
input_data = {
svn.FUEL_LOAD_DRY_TOTAL: [0.4, 0.5, 0.6], # fgi
svn.FUEL_HEIGHT: [0.2, 0.3, 0.4], # fueldepthm
svn.FUEL_DENSITY: [30.0, 32.0, 34.0], # fueldens
svn.FUEL_SURFACE_AREA_VOLUME_RATIO: [1800.0, 2000.0, 2200.0], # savr
svn.FUEL_MOISTURE_EXTINCTION: [25.0, 30.0, 35.0], # fuelmce
svn.FUEL_MINERAL_CONTENT_TOTAL: [0.05, 0.0555, 0.06], # st
svn.FUEL_MINERAL_CONTENT_EFFECTIVE: [0.009, 0.01, 0.011], # se
svn.FUEL_CHAPARRAL_FLAG: [0, 0, 1], # ichap
svn.WIND_SPEED: 2.0,
svn.SLOPE_ANGLE: 15.0,
svn.FUEL_MOISTURE_CONTENT: 10.0,
}
fuel_category = 2 # One-based index
ros = Rothermel_SFIRE.compute_ros(input_data, fuel_cat=fuel_category)
print(f"The rate of spread for fuel category {fuel_category} is {ros:.4f}")
""" # pylint: disable=line-too-long
# Prepare fuel properties using the base class method
fuel_properties = RateOfSpreadModel.prepare_fuel_properties(
input_dict=input_dict, metadata=Rothermel_SFIRE.metadata, fuel_cat=fuel_cat
)
# Calculate the rate of spread
return Rothermel_SFIRE.rothermel(**fuel_properties)
[docs]
@staticmethod
def compute_ros_with_units(
input_dict: dict[str, float | int | list[float] | list[int] | Quantity],
fuel_cat: int = 0,
**opt,
) -> Quantity:
"""
Compute the rate of spread (ROS) of fire using Rothermel's model with unit handling.
This function extracts magnitudes from input data (removing `pint.Quantity` wrappers),
computes the ROS using `compute_ros`, and attaches the appropriate unit to the result.
Parameters
----------
input_dict : dict
Dictionary containing input fuel properties as `pint.Quantity` objects or standard values.
Keys should match the variable names defined in `Rothermel_SFIRE.metadata`.
fuel_cat : int, optional
One-based index for selecting a specific fuel category from lists in `input_dict`.
Defaults to 0, indicating scalar inputs.
**opt : dict
Additional optional parameters passed to `compute_ros`.
Returns
-------
ureg.Quantity
Computed rate of spread (ROS) with units (e.g., meters per second).
Notes
-----
- Use this function when working with `pint.Quantity` objects in `input_dict`.
- Units for the ROS are defined in `Rothermel_SFIRE.metadata["rate_of_spread"]["units"]`.
""" # pylint: disable=line-too-long
input_dict_no_units = extract_magnitudes(input_dict)
return ureg.Quantity(
Rothermel_SFIRE.compute_ros(input_dict_no_units, fuel_cat, **opt),
Rothermel_SFIRE.metadata["rate_of_spread"]["units"],
)
[docs]
class Balbi_2022_fixed_SFIRE(RateOfSpreadModel):
"""
A class to represent the Balbi's model for fire spread rate calculation used in SFIRE code.
This version is based on Chatelon et al. 2022.
To prevent negative value of rate of spread, the following modifications have been applied:
- the tile angle gamma is bouded to 0
- the radiative contribution to ros Rc is bounded to 0
Metadata
--------
The model uses the following fuel parameters:
- ``dead_fuel_ratio``
- Standard name: ``FUEL_LOAD_DEAD_RATIO``
- Units: ``dimensionless``
- Range: ``0 to 1``
- Type: ``input``
- ``fgi``
- Standard name: ``FUEL_LOAD_DRY_TOTAL``
- Units: ``kilogram / meter ** 2``
- Range: ``0 to inf``
- Type: ``input``
- ``fueldepthm``
- Standard name: ``FUEL_HEIGHT``
- Units: ``meter``
- Range: ``0 to inf``
- Type: ``input``
- ``fueldens``
- Standard name: ``FUEL_DENSITY``
- Units: ``kilogram / meter ** 3``
- Range: ``0 to inf``
- Type: ``input``
- ``savr``
- Standard name: ``FUEL_SURFACE_AREA_VOLUME_RATIO``
- Units: ``1 / meter``
- Range: ``0 to inf``
- Type: ``input``
- ``w0``
- Standard name: ``IGNITION_LENGTH``
- Units: ``meter``
- Range: ``0 to inf``
- Type: ``optional``
- Default: ``50``
- ``temp_ign``
- Standard name: ``FUEL_TEMPERATURE_IGNITION``
- Units: ``kelvin``
- Range: ``0 to inf``
- Type: ``optional``
- Default: ``600``
- ``temp_air``
- Standard name: ``AIR_TEMPERATURE``
- Units: ``kelvin``
- Range: ``0 to inf``
- Type: ``optional``
- Default: ``300``
- ``dens_air``
- Standard name: ``AIR_DENSITY``
- Units: ``kilogram / meter ** 3``
- Range: ``0 to inf``
- Type: ``optional``
- Default: ``1.125``
- ``wind``
- Standard name: ``WIND_SPEED``
- Units: ``meter / second``
- Range: ``-inf to inf``
- Type: ``input``
- ``slope``
- Standard name: ``SLOPE_ANGLE``
- Units: ``degree``
- Range: ``-90 to 90``
- Type: ``input``
- ``fmc``
- Standard name: ``FUEL_MOISTURE_CONTENT``
- Units: ``percent``
- Range: ``0 to 200``
- Type: ``input``
- ``rate_of_spread``
- Standard name: ``RATE_OF_SPREAD``
- Units: ``meter / second``
- Range: ``0 to inf``
- Type: ``output``
""" # pylint: disable=line-too-long
metadata = {
"dead_fuel_ratio": {
"std_name": svn.FUEL_LOAD_DEAD_RATIO,
"units": ureg.dimensionless,
"range": (0, 1),
"type": ParameterType.input,
},
"fgi": {
"std_name": svn.FUEL_LOAD_DRY_TOTAL,
"units": ureg.kilogram / ureg.meter**2,
"range": (0, np.inf),
"type": ParameterType.input,
},
"fueldepthm": {
"std_name": svn.FUEL_HEIGHT,
"units": ureg.meter,
"range": (0, np.inf),
"type": ParameterType.input,
},
"fueldens": {
"std_name": svn.FUEL_DENSITY,
"units": ureg.kilogram / ureg.meter**3,
"range": (0, np.inf),
"type": ParameterType.input,
},
"savr": {
"std_name": svn.FUEL_SURFACE_AREA_VOLUME_RATIO,
"units": 1 / ureg.meter,
"range": (0, np.inf),
"type": ParameterType.input,
},
"w0": {
"std_name": svn.IGNITION_LENGTH,
"units": ureg.meter,
"range": (0, np.inf),
"type": ParameterType.optional,
"default": 50,
},
"temp_ign": {
"std_name": svn.FUEL_TEMPERATURE_IGNITION,
"units": ureg.kelvin,
"range": (0, np.inf),
"type": ParameterType.optional,
"default": 600,
},
"temp_air": {
"std_name": svn.AIR_TEMPERATURE,
"units": ureg.kelvin,
"range": (0, np.inf),
"type": ParameterType.optional,
"default": 300,
},
"dens_air": {
"std_name": svn.AIR_DENSITY,
"units": ureg.kilogram / ureg.meter**3,
"range": (0, np.inf),
"type": ParameterType.optional,
"default": 1.125,
},
"wind": {
"std_name": svn.WIND_SPEED,
"units": ureg.meter / ureg.second,
"range": (-np.inf, np.inf),
"type": ParameterType.input,
},
"slope": {
"std_name": svn.SLOPE_ANGLE,
"units": ureg.degree,
"range": (-90, 90),
"type": ParameterType.input,
},
"fmc": {
"std_name": svn.FUEL_MOISTURE_CONTENT,
"units": ureg.percent,
"range": (0, 200),
"type": ParameterType.input,
},
"rate_of_spread": {
"std_name": svn.RATE_OF_SPREAD,
"units": ureg.meter / ureg.second,
"range": (0, np.inf),
"type": ParameterType.output,
},
}
[docs]
@staticmethod
def balbi_2022_fixed(
dead_fuel_ratio: float,
fgi: float,
fueldepthm: float,
fueldens: float,
temp_ign: float,
temp_air: float,
dens_air: float,
savr: float,
w0: float,
wind: float,
slope: float,
fmc: float,
**opt,
) -> float:
"""
Compute the rate of spread using the Balbi's model from SFIRE code.
Parameters
----------
wind : float
Wind speed in the normal direction at 6.1m (20ft) [m/s].
slope : float
Slope angle [degrees].
fmc : float
Fuel moisture content [%].
w0 : float
Ignition line width [m].
Optional Parameters
-------------------
max_ite : int, optional
maximum number of iteration for the fixed point method.
Returns
-------
float
Rate of spread [m/s]
""" # pylint: disable=line-too-long
## Physical parameters
boltz = 5.670373e-8 # Stefan-Boltzman constant [W m-2 K-4]
tau0 = 75591.0 # Anderson's residence time coefficient [s m-1]
Cpa = 1150.0 # Specific heat of air [J kg-1 K-1]
Tvap = 373.15 # Liquid water evaporation temperature [K]
g = 9.81 # Gravitational acceleration [m s-2]
Cp = 1200 # Specific heat of fuel [J kg-1 K-1]
Cpw = 4180.0 # Specific heat of liquid water [J kg-1 K-1]
delta_h = 2.3e6 # Heat of latent evaporation [J kg-1]
delta_H = 1.7433e07 # Heat of combustion [J kg-1]
## Model parameter
st = 17.0 # Stoichiometric coefficient [-]
scal_am = 0.025 # scaling factor am [-]
tol = 1e-4 # tolerance for fixed point method [-]
r00 = 2.5e-5 # Model parameter
chi0 = 0.3 # Radiative factor [-]
# fmc from percent to real
fmc *= 0.01
# Add moisture to oven dry fuel load
sigma_t = fgi * (1 + fmc)
# dead fuel load
sigma_d = sigma_t * dead_fuel_ratio
# max number of iteration
maxite = opt.get("max_ite", 20)
## preliminary
alpha_rad = np.deg2rad(slope)
# Packing ratios
beta = sigma_d / (fueldepthm * fueldens) # dead fuel
beta_t = sigma_t / (fueldepthm * fueldens) # total fuel
# Leaf areas
lai = savr * fueldepthm * beta # dead fuel
lai_t = savr * fueldepthm * beta_t # total fuel
## Heat sink
q = Cp * (temp_ign - temp_air) + fmc * (delta_h + Cpw * (Tvap - temp_air))
# Flame temperature
Tflame = temp_air + (delta_H * (1 - chi0)) / (Cpa * (1 + st))
# Base flame radiation
Rb = min(lai_t / (2 * np.pi), 1) * (lai / lai_t) ** 2 * boltz * Tflame**4 / (beta * fueldens * q)
# Radiant factor
A = min(lai / (2 * np.pi), lai / lai_t) * chi0 * delta_H / (4 * q)
# vertical velocity
u0 = (
2
* (st + 1)
* fueldens
* Tflame
* min(lai, 2 * np.pi * lai / lai_t)
/ (tau0 * dens_air * temp_air)
)
# tilt angle
gamma = max(0, np.arctan(np.tan(alpha_rad) + wind / u0))
# flame height and length
flame_height = (u0**2) / (g * (Tflame / temp_air - 1) * np.cos(alpha_rad) ** 2)
flame_length = flame_height / np.cos(gamma - alpha_rad)
# view factor
view_factor = 1 - np.sin(gamma) + np.cos(gamma)
# Rr denom term
denom_term_Rr = np.cos(gamma) / (savr * r00)
# main term Rc
main_term_Rc = (
scal_am
* min(w0 / 50, 1)
* delta_H
* dens_air
* temp_air
* savr
* np.sqrt(fueldepthm)
/ (2 * q * (1 + st) * fueldens * Tflame)
)
# second term
scd_term_Rc = (
min(2 * np.pi * lai / lai_t, lai)
* np.tan(gamma)
* (1 + st)
* fueldens
* Tflame
/ (dens_air * temp_air * tau0)
)
# exp term
exp_term_Rc = -beta_t / (min(w0 / 50, 1))
# Solve the fixed point algo with starting point Rb
ros = Rb
failstatus = True
for i in range(maxite):
# compute Rr
Rr = A * ros * view_factor / (1 + ros * denom_term_Rr)
# Compute Rc
Rc = max(0, main_term_Rc * (scd_term_Rc + wind * np.exp(exp_term_Rc * ros)))
# Update ros
tmp_ros = Rb + Rc + Rr
err = abs(tmp_ros - ros)
ros = tmp_ros
# Check for convergence
if err < tol:
failstatus = False
break
# Set ros to NaN if the algorithm did not converge
if failstatus:
ros = 0
return min(ros, 6.0)
[docs]
@staticmethod
def compute_ros(
input_dict: dict[str, float | int | list[float] | list[int]],
fuel_cat: int = 0,
**opt,
) -> float:
"""
Compute the rate of spread of fire using the ``Balbi's 2022`` model.
This function processes input fuel properties, optionally selects a specific fuel category,
and calculates the rate of spread (ROS) of fire using the ``balbi_2022_fixed`` method.
Input data must be provided in standard units without ``pint.Quantity`` objects.
For unit-aware calculations, use ``compute_ros_with_units``.
Parameters
----------
input_dict : dict
Dictionary containing the input data for various fuel properties.
The keys should be the standard variable names as defined in ``Balbi_2022_fixed_SFIRE.metadata``.
Each value can be a single float/int or a list/array of floats/ints.
fuel_cat : int, optional
Fuel category index (one-based). If provided, fuel properties are expected to be lists or arrays,
and the function will extract the properties corresponding to the specified fuel category.
If not provided, fuel properties are expected to be scalar values.
**opt : dict
Additional optional parameters to be passed to the ``balbi_2022_fixed`` method.
Returns
-------
float
The computed rate of spread of fire.
Notes
-----
- ``fuel_cat`` uses one-based indexing to align with natural fuel category numbering.
When accessing lists or arrays in ``input_dict``, the index is adjusted accordingly (i.e., ``index = fuel_cat - 1``).
- This function assumes ``input_dict`` contains values in standard units (e.g., no ``pint.Quantity`` objects),
compliant with units specified in the metadata dictionary.
""" # pylint: disable=line-too-long
# Prepare fuel properties using the base class method
fuel_properties_dict = RateOfSpreadModel.prepare_fuel_properties(
input_dict=input_dict, metadata=Balbi_2022_fixed_SFIRE.metadata, fuel_cat=fuel_cat
)
return Balbi_2022_fixed_SFIRE.balbi_2022_fixed(
**fuel_properties_dict,
**opt,
)
[docs]
@staticmethod
def compute_ros_with_units(
input_dict: dict[str, float | int | list[float] | list[int] | Quantity],
fuel_cat: int = 0,
**opt,
) -> Quantity:
"""
Compute the rate of spread (ROS) of fire using Balbi's 2022 model with unit handling.
This function extracts magnitudes from input data (removing `pint.Quantity` wrappers),
computes the ROS using `compute_ros`, and attaches the appropriate unit to the result.
Parameters
----------
input_dict : dict
Dictionary containing input fuel properties as `pint.Quantity` objects or standard values.
Keys should match the variable names defined in `Balbi_2022_fixed_SFIRE.metadata`.
fuel_cat : int, optional
One-based index for selecting a specific fuel category from lists in `input_dict`.
Defaults to 0, indicating scalar inputs.
**opt : dict
Additional optional parameters passed to `compute_ros`.
Returns
-------
ureg.Quantity
Computed rate of spread (ROS) with units (e.g., meters per second).
Notes
-----
- Use this function when working with `pint.Quantity` objects in `input_dict`.
- Units for the ROS are defined in `Balbi_2022_fixed_SFIRE.metadata["rate_of_spread"]["units"]`.
""" # pylint: disable=line-too-long
input_dict_no_units = extract_magnitudes(input_dict)
return ureg.Quantity(
Balbi_2022_fixed_SFIRE.compute_ros(input_dict_no_units, fuel_cat, **opt),
Balbi_2022_fixed_SFIRE.metadata["rate_of_spread"]["units"],
)
[docs]
class Santoni_2011(RateOfSpreadModel):
"""
A class to represent the Santoni's model for fire spread rate calculation.
This version is based on Santoni et al. 2011.
Metadata
--------
The model uses the following fuel parameters:
- ``fuel_load_dry_total``
- Standard name: ``FUEL_LOAD_DRY_TOTAL``
- Units: ``kilogram / meter ** 2``
- Range: ``0 to inf``
- Type: ``input``
- ``dead_fuel_ratio``
- Standard name: ``FUEL_LOAD_DEAD_RATIO``
- Units: ``dimensionless``
- Range: ``0 to 1``
- Type: ``input``
- ``fueldepth``
- Standard name: ``FUEL_HEIGHT``
- Units: ``meter``
- Range: ``0 to inf``
- Type: ``input``
- ``fuel_dens_dead``
- Standard name: ``FUEL_DENSITY_DEAD``
- Units: ``kilogram / meter ** 3``
- Range: ``0 to inf``
- Type: ``input``
- ``fuel_dens_live``
- Standard name: ``FUEL_DENSITY_LIVE``
- Units: ``kilogram / meter ** 3``
- Range: ``0 to inf``
- Type: ``input``
- ``temp_ign``
- Standard name: ``FUEL_TEMPERATURE_IGNITION``
- Units: ``kelvin``
- Range: ``0 to inf``
- Type: ``optional``
- Default: ``600``
- ``temp_air``
- Standard name: ``AIR_TEMPERATURE``
- Units: ``kelvin``
- Range: ``0 to inf``
- Type: ``optional``
- Default: ``300``
- ``dens_air``
- Standard name: ``AIR_DENSITY``
- Units: ``kilogram / meter ** 3``
- Range: ``0 to inf``
- Type: ``optional``
- Default: ``1.125``
- ``savr_dead``
- Standard name: ``FUEL_SURFACE_AREA_VOLUME_RATIO_DEAD``
- Units: ``1 / meter``
- Range: ``0 to inf``
- Type: ``input``
- ``savr_live``
- Standard name: ``FUEL_SURFACE_AREA_VOLUME_RATIO_LIVE``
- Units: ``1 / meter``
- Range: ``0 to inf``
- Type: ``input``
- ``wind``
- Standard name: ``WIND_SPEED``
- Units: ``meter / second``
- Range: ``-inf to inf``
- Type: ``input``
- ``slope``
- Standard name: ``SLOPE_ANGLE``
- Units: ``degree``
- Range: ``-90 to 90``
- Type: ``input``
- ``fmc_dead``
- Standard name: ``FUEL_MOISTURE_CONTENT_DEAD``
- Units: ``percent``
- Range: ``0 to 200``
- Type: ``input``
- ``fmc_live``
- Standard name: ``FUEL_MOISTURE_CONTENT_LIVE``
- Units: ``percent``
- Range: ``0 to 500``
- Type: ``input``
- ``rate_of_spread``
- Standard name: ``RATE_OF_SPREAD``
- Units: ``meter / second``
- Range: ``0 to inf``
- Type: ``output``
""" # pylint: disable=line-too-long
metadata = {
"fuel_load_dry_total": {
"std_name": svn.FUEL_LOAD_DRY_TOTAL,
"units": ureg.kilogram / ureg.meter**2,
"range": (0, np.inf),
"type": ParameterType.input,
},
"dead_fuel_ratio": {
"std_name": svn.FUEL_LOAD_DEAD_RATIO,
"units": ureg.dimensionless,
"range": (0, 1),
"type": ParameterType.input,
},
"fueldepth": {
"std_name": svn.FUEL_HEIGHT,
"units": ureg.meter,
"range": (0, np.inf),
"type": ParameterType.input,
},
"fuel_dens_dead": {
"std_name": svn.FUEL_DENSITY_DEAD,
"units": ureg.kilogram / ureg.meter**3,
"range": (0, np.inf),
"type": ParameterType.input,
},
"fuel_dens_live": {
"std_name": svn.FUEL_DENSITY_LIVE,
"units": ureg.kilogram / ureg.meter**3,
"range": (0, np.inf),
"type": ParameterType.input,
},
"temp_ign": {
"std_name": svn.FUEL_TEMPERATURE_IGNITION,
"units": ureg.kelvin,
"range": (0, np.inf),
"type": ParameterType.optional,
"default": 600,
},
"temp_air": {
"std_name": svn.AIR_TEMPERATURE,
"units": ureg.kelvin,
"range": (0, np.inf),
"type": ParameterType.optional,
"default": 300,
},
"dens_air": {
"std_name": svn.AIR_DENSITY,
"units": ureg.kilogram / ureg.meter**3,
"range": (0, np.inf),
"type": ParameterType.optional,
"default": 1.125,
},
"savr_dead": {
"std_name": svn.FUEL_SURFACE_AREA_VOLUME_RATIO_DEAD,
"units": 1 / ureg.meter,
"range": (0, np.inf),
"type": ParameterType.input,
},
"savr_live": {
"std_name": svn.FUEL_SURFACE_AREA_VOLUME_RATIO_LIVE,
"units": 1 / ureg.meter,
"range": (0, np.inf),
"type": ParameterType.input,
},
"wind": {
"std_name": svn.WIND_SPEED,
"units": ureg.meter / ureg.second,
"range": (-np.inf, np.inf),
"type": ParameterType.input,
},
"slope": {
"std_name": svn.SLOPE_ANGLE,
"units": ureg.degree,
"range": (-90, 90),
"type": ParameterType.input,
},
"fmc_dead": {
"std_name": svn.FUEL_MOISTURE_CONTENT_DEAD,
"units": ureg.percent,
"range": (0, 200),
"type": ParameterType.input,
},
"fmc_live": {
"std_name": svn.FUEL_MOISTURE_CONTENT_LIVE,
"units": ureg.percent,
"range": (0, 500),
"type": ParameterType.input,
},
"rate_of_spread": {
"std_name": svn.RATE_OF_SPREAD,
"units": ureg.meter / ureg.second,
"range": (0, np.inf),
"type": ParameterType.output,
},
}
[docs]
@staticmethod
def santoni_2011(
fuel_load_dry_total: float,
dead_fuel_ratio: float,
fueldepth: float,
fuel_dens_dead: float,
fuel_dens_live: float,
temp_ign: float,
temp_air: float,
dens_air: float,
savr_dead: float,
savr_live: float,
wind: float,
slope: float,
fmc_dead: float,
fmc_live: float,
) -> float:
"""
Compute the rate of spread using the Santoni's model.
Parameters
----------
fuel_load_dry_total : float
total dry fuel load [kg m-2]
dead_fuel_ratio : float
ratio of dead fuel load to total fuel load [-]
fuel_dens_dead : float
dead fuel density [kg m-3]
fuel_dens_live : float
live fuel density [kg m-3]
temp_ign : float
fuel ignition temperature [K]
temp_air : float
ambiant air temperature [K]
dens_air : float
ambiant air density [kg m-3]
savr_dead : float
dead fuel surface area to volume ratio [m-1]
savr_live : float
live fuel surface area to volume ratio [m-1]
wind : float
midflame wind speed [m s-1]
slope : float
slope angle [degrees]
fmc_dead : float
dead fuel moisture content [%]
fmc_live : float
live fuel moisture content [%]
Returns
-------
float
Rate of spread [m s-1]
""" # pylint: disable=line-too-long
## Physical parameters
boltz = 5.670373e-8 # Stefan-Boltzman constant [W m-2 K-4]
tau0 = 75591.0 # Anderson's residence time coefficient [s m-1]
Cpa = 1150.0 # Specific heat of air [J kg-1 K-1]
Cp = 1200 # Specific heat of fuel [J kg-1 K-1]
delta_h = 2.3e6 # Heat of latent evaporation [J kg-1]
delta_H = 1.7433e07 # Heat of combustion [J kg-1]
## Model parameter
r00 = 2.5e-5 # Model parameter
chi0 = 0.3 # Radiant heat transfer fraction [-]
stoich_coeff = 8.3 # Stoichiometric coefficient [-]
base_LAI = 4.0 # Base leaf area index [-]
# fmc from percent to real
fmc_dead *= 0.01
fmc_live *= 0.01
# dead fuel load
sigma_d = fuel_load_dry_total * dead_fuel_ratio
sigma_l = fuel_load_dry_total - sigma_d
S_l = savr_live * sigma_l / fuel_dens_live
S_d = savr_dead * sigma_d / fuel_dens_dead
xi = (fmc_live - fmc_dead) * S_l * delta_h / (S_d * delta_H)
# nominal radiant temperature
T_n = temp_air + delta_H * (1 - chi0) * (1 - xi) / (Cpa * (1 + stoich_coeff))
# Flame tilt angle
nu = min(S_d / base_LAI, 1.0) # absorption coeff
v_vertical = (
2.0 * nu * base_LAI * (1 + stoich_coeff) * T_n * fuel_dens_dead / (dens_air * temp_air * tau0)
) # flame gas velocity
alpha_rad = np.deg2rad(slope)
tan_gamma = np.tan(alpha_rad) + wind / v_vertical # tan of tilt angle
gamma = np.arctan(tan_gamma)
# no wind no slope ros
a = delta_h / (Cp * (temp_ign - temp_air))
ros_00 = boltz * T_n**4 / (Cp * (temp_ign - temp_air))
ros_0 = fueldepth * ros_00 / (sigma_d * (1 + a * fmc_dead)) * (S_d / (S_d + S_l)) ** 2
if gamma > 0:
# positive tilt angle
A_0 = 0.25 * chi0 * delta_H / (Cp * (temp_ign - temp_air))
A = nu * A_0 * (1 - xi) / (1 + a * fmc_dead)
r_0 = savr_dead * r00
G = r_0 * (1 + np.sin(gamma) - np.cos(gamma)) / np.cos(gamma)
ros_t = ros_0 + A * G - r_0 / np.cos(gamma)
ros = 0.5 * (ros_t + np.sqrt(ros_t**2 + 4 * r_0 * ros_0 / np.cos(gamma)))
else:
ros = ros_0
return min(ros, 6.0)
[docs]
@staticmethod
def compute_ros(
input_dict: dict[str, float | int | list[float] | list[int]],
fuel_cat: int = 0,
**opt,
) -> float:
"""
Compute the rate of spread of fire using the ``Santoni's 2011`` model.
This function processes input fuel properties, optionally selects a specific fuel category,
and calculates the rate of spread (ROS) of fire using the ``santoni_2011`` method.
Input data must be provided in standard units without ``pint.Quantity`` objects.
For unit-aware calculations, use ``compute_ros_with_units``.
Parameters
----------
input_dict : dict
Dictionary containing the input data for various fuel properties.
The keys should be the standard variable names as defined in ``santoni_2011.metadata``.
Each value can be a single float/int or a list/array of floats/ints.
fuel_cat : int, optional
Fuel category index (one-based). If provided, fuel properties are expected to be lists or arrays,
and the function will extract the properties corresponding to the specified fuel category.
If not provided, fuel properties are expected to be scalar values.
**opt : dict
Additional optional parameters to be passed to the ``santoni_2011`` method.
Returns
-------
float
The computed rate of spread of fire.
Notes
-----
- ``fuel_cat`` uses one-based indexing to align with natural fuel category numbering.
When accessing lists or arrays in ``input_dict``, the index is adjusted accordingly (i.e., ``index = fuel_cat - 1``).
- This function assumes ``input_dict`` contains values in standard units (e.g., no ``pint.Quantity`` objects),
compliant with units specified in the metadata dictionary.
""" # pylint: disable=line-too-long
# Prepare fuel properties using the base class method
fuel_properties_dict = RateOfSpreadModel.prepare_fuel_properties(
input_dict=input_dict, metadata=Santoni_2011.metadata, fuel_cat=fuel_cat
)
return Santoni_2011.santoni_2011(
**fuel_properties_dict,
**opt,
)
[docs]
@staticmethod
def compute_ros_with_units(
input_dict: dict[str, float | int | list[float] | list[int] | Quantity],
fuel_cat: int = 0,
**opt,
) -> Quantity:
"""
Compute the rate of spread (ROS) of fire using Santoni's 2011 model with unit handling.
This function extracts magnitudes from input data (removing `pint.Quantity` wrappers),
computes the ROS using `compute_ros`, and attaches the appropriate unit to the result.
Parameters
----------
input_dict : dict
Dictionary containing input fuel properties as `pint.Quantity` objects or standard values.
Keys should match the variable names defined in `Santoni_2011.metadata`.
fuel_cat : int, optional
One-based index for selecting a specific fuel category from lists in `input_dict`.
Defaults to 0, indicating scalar inputs.
**opt : dict
Additional optional parameters passed to `compute_ros`.
Returns
-------
ureg.Quantity
Computed rate of spread (ROS) with units (e.g., meters per second).
Notes
-----
- Use this function when working with `pint.Quantity` objects in `input_dict`.
- Units for the ROS are defined in `Balbi_2022_fixed_SFIRE.metadata["rate_of_spread"]["units"]`.
""" # pylint: disable=line-too-long
input_dict_no_units = extract_magnitudes(input_dict)
return ureg.Quantity(
Santoni_2011.compute_ros(input_dict_no_units, fuel_cat, **opt),
Santoni_2011.metadata["rate_of_spread"]["units"],
)