Skip to content

Rothermel Spread Model

This module implements Rothermel's (1972) surface fire spread equations, computing rate of spread (ROS), fireline intensity, and fire ellipse geometry for each cell in the simulation grid. It is the core fire behavior engine called by BaseFireSim at every time step — users do not call these functions directly but can inspect the cell-level outputs they produce (e.g., cell.r_ss, cell.I_ss).

Rothermel fire spread model implementation.

Compute surface fire rate of spread (ROS), fireline intensity, and related quantities using Rothermel's (1972) equations. All internal calculations use imperial units (ft/min, BTU/ft²·min); public outputs are converted to metric (m/s) unless noted otherwise.

Functions:

Name Description
- surface_fire

Compute steady-state ROS and fireline intensity for a cell.

- accelerate

Apply fire acceleration from rest toward steady-state ROS.

- calc_r_h

Compute head-fire ROS combining wind and slope effects.

- calc_r_0

Compute no-wind, no-slope base ROS and reaction intensity.

- calc_eccentricity

Compute fire ellipse eccentricity from effective wind speed.

- calc_flame_len

Estimate flame length from fireline intensity.

References

Rothermel, R. C. (1972). A mathematical model for predicting fire spread in wildland fuels. USDA Forest Service Research Paper INT-115.

BTU_ft2_min_to_kW_m2(f_btu_ft2_min)

Convert heat flux from BTU/(ft^2*min) to kW/m^2.

Parameters:

Name Type Description Default
f_btu_ft2_min float

Heat flux in BTU/(ft^2*min).

required

Returns:

Name Type Description
float float

Heat flux in kW/m^2.

Source code in embrs/utilities/unit_conversions.py
220
221
222
223
224
225
226
227
228
229
def BTU_ft2_min_to_kW_m2(f_btu_ft2_min: float) -> float:
    """Convert heat flux from BTU/(ft^2*min) to kW/m^2.

    Args:
        f_btu_ft2_min (float): Heat flux in BTU/(ft^2*min).

    Returns:
        float: Heat flux in kW/m^2.
    """
    return f_btu_ft2_min * _BTU_FT2_MIN_TO_KW_M2

BTU_ft_min_to_kW_m(f_btu_ft_min)

Convert fireline intensity from BTU/(ft*min) to kW/m.

Parameters:

Name Type Description Default
f_btu_ft_min float

Fireline intensity in BTU/(ft*min).

required

Returns:

Name Type Description
float float

Fireline intensity in kW/m.

Source code in embrs/utilities/unit_conversions.py
232
233
234
235
236
237
238
239
240
241
def BTU_ft_min_to_kW_m(f_btu_ft_min: float) -> float:
    """Convert fireline intensity from BTU/(ft*min) to kW/m.

    Args:
        f_btu_ft_min (float): Fireline intensity in BTU/(ft*min).

    Returns:
        float: Fireline intensity in kW/m.
    """
    return f_btu_ft_min * _BTU_FT_MIN_TO_KW_M

BTU_ft_min_to_kcal_s_m(f_btu_ft_min)

Convert fireline intensity from BTU/(ftmin) to kcal/(sm).

Parameters:

Name Type Description Default
f_btu_ft_min float

Fireline intensity in BTU/(ft*min).

required

Returns:

Name Type Description
float float

Fireline intensity in kcal/(s*m).

Source code in embrs/utilities/unit_conversions.py
244
245
246
247
248
249
250
251
252
253
def BTU_ft_min_to_kcal_s_m(f_btu_ft_min: float) -> float:
    """Convert fireline intensity from BTU/(ft*min) to kcal/(s*m).

    Args:
        f_btu_ft_min (float): Fireline intensity in BTU/(ft*min).

    Returns:
        float: Fireline intensity in kcal/(s*m).
    """
    return f_btu_ft_min * _BTU_FT_MIN_TO_KCAL_S_M

BTU_lb_to_cal_g(f_btu_lb)

Convert heat content from BTU/lb to cal/g.

Parameters:

Name Type Description Default
f_btu_lb float

Heat content in BTU/lb.

required

Returns:

Name Type Description
float float

Heat content in cal/g.

Source code in embrs/utilities/unit_conversions.py
272
273
274
275
276
277
278
279
280
281
def BTU_lb_to_cal_g(f_btu_lb: float) -> float:
    """Convert heat content from BTU/lb to cal/g.

    Args:
        f_btu_lb (float): Heat content in BTU/lb.

    Returns:
        float: Heat content in cal/g.
    """
    return f_btu_lb * _BTU_LB_TO_CAL_G

F_to_C(f_f)

Convert temperature from Fahrenheit to Celsius.

Parameters:

Name Type Description Default
f_f float

Temperature in degrees Fahrenheit.

required

Returns:

Name Type Description
float float

Temperature in degrees Celsius.

Source code in embrs/utilities/unit_conversions.py
48
49
50
51
52
53
54
55
56
57
def F_to_C(f_f: float) -> float:
    """Convert temperature from Fahrenheit to Celsius.

    Args:
        f_f (float): Temperature in degrees Fahrenheit.

    Returns:
        float: Temperature in degrees Celsius.
    """
    return (5.0 / 9.0) * (f_f - 32.0)

KiSq_to_Lbsft2(f_kisq)

Convert fuel loading from kg/m^2 to lb/ft^2.

Parameters:

Name Type Description Default
f_kisq float

Fuel loading in kg/m^2.

required

Returns:

Name Type Description
float float

Fuel loading in lb/ft^2.

Source code in embrs/utilities/unit_conversions.py
156
157
158
159
160
161
162
163
164
165
def KiSq_to_Lbsft2(f_kisq: float) -> float:
    """Convert fuel loading from kg/m^2 to lb/ft^2.

    Args:
        f_kisq (float): Fuel loading in kg/m^2.

    Returns:
        float: Fuel loading in lb/ft^2.
    """
    return f_kisq * _KISQ_TO_LBSFT2

KiSq_to_TPA(f_kisq)

Convert fuel loading from kg/m^2 to tons per acre.

Parameters:

Name Type Description Default
f_kisq float

Fuel loading in kg/m^2.

required

Returns:

Name Type Description
float float

Fuel loading in tons per acre.

Source code in embrs/utilities/unit_conversions.py
204
205
206
207
208
209
210
211
212
213
def KiSq_to_TPA(f_kisq: float) -> float:
    """Convert fuel loading from kg/m^2 to tons per acre.

    Args:
        f_kisq (float): Fuel loading in kg/m^2.

    Returns:
        float: Fuel loading in tons per acre.
    """
    return f_kisq * _KISQ_TO_TPA

Lbsft2_to_KiSq(f_libsft2)

Convert fuel loading from lb/ft^2 to kg/m^2.

Parameters:

Name Type Description Default
f_libsft2 float

Fuel loading in lb/ft^2.

required

Returns:

Name Type Description
float float

Fuel loading in kg/m^2.

Source code in embrs/utilities/unit_conversions.py
144
145
146
147
148
149
150
151
152
153
def Lbsft2_to_KiSq(f_libsft2: float) -> float:
    """Convert fuel loading from lb/ft^2 to kg/m^2.

    Args:
        f_libsft2 (float): Fuel loading in lb/ft^2.

    Returns:
        float: Fuel loading in kg/m^2.
    """
    return f_libsft2 * _LBSFT2_TO_KISQ

Lbsft2_to_TPA(f_lbsft2)

Convert fuel loading from lb/ft^2 to tons per acre.

Parameters:

Name Type Description Default
f_lbsft2 float

Fuel loading in lb/ft^2.

required

Returns:

Name Type Description
float float

Fuel loading in tons per acre.

Source code in embrs/utilities/unit_conversions.py
192
193
194
195
196
197
198
199
200
201
def Lbsft2_to_TPA(f_lbsft2: float) -> float:
    """Convert fuel loading from lb/ft^2 to tons per acre.

    Args:
        f_lbsft2 (float): Fuel loading in lb/ft^2.

    Returns:
        float: Fuel loading in tons per acre.
    """
    return f_lbsft2 * _LBSFT2_TO_TPA

TPA_to_KiSq(f_tpa)

Convert fuel loading from tons per acre to kg/m^2.

Parameters:

Name Type Description Default
f_tpa float

Fuel loading in tons per acre.

required

Returns:

Name Type Description
float float

Fuel loading in kg/m^2.

Source code in embrs/utilities/unit_conversions.py
168
169
170
171
172
173
174
175
176
177
def TPA_to_KiSq(f_tpa: float) -> float:
    """Convert fuel loading from tons per acre to kg/m^2.

    Args:
        f_tpa (float): Fuel loading in tons per acre.

    Returns:
        float: Fuel loading in kg/m^2.
    """
    return f_tpa / _TPA_TO_KISQ_DIVISOR

TPA_to_Lbsft2(f_tpa)

Convert fuel loading from tons per acre to lb/ft^2.

Parameters:

Name Type Description Default
f_tpa float

Fuel loading in tons per acre.

required

Returns:

Name Type Description
float float

Fuel loading in lb/ft^2.

Source code in embrs/utilities/unit_conversions.py
180
181
182
183
184
185
186
187
188
189
def TPA_to_Lbsft2(f_tpa: float) -> float:
    """Convert fuel loading from tons per acre to lb/ft^2.

    Args:
        f_tpa (float): Fuel loading in tons per acre.

    Returns:
        float: Fuel loading in lb/ft^2.
    """
    return f_tpa * _TPA_TO_LBSFT2

accelerate(cell, time_step)

Apply fire acceleration toward steady-state ROS.

Update the transient rate of spread (cell.r_t) and average ROS (cell.avg_ros) for each spread direction using the exponential acceleration model (McAlpine 1989). Directions already at or above steady-state are clamped.

Uses a JIT-compiled inner loop to avoid numpy dispatch overhead on small (12-element) arrays.

Parameters:

Name Type Description Default
cell Cell

Burning cell with r_ss, r_t, a_a set.

required
time_step float

Simulation time step in seconds.

required
Side Effects

Updates cell.r_t, cell.avg_ros, and cell.I_t in-place.

Source code in embrs/models/rothermel.py
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
def accelerate(cell: Cell, time_step: float):
    """Apply fire acceleration toward steady-state ROS.

    Update the transient rate of spread (``cell.r_t``) and average ROS
    (``cell.avg_ros``) for each spread direction using the exponential
    acceleration model (McAlpine 1989). Directions already at or above
    steady-state are clamped.

    Uses a JIT-compiled inner loop to avoid numpy dispatch overhead on
    small (12-element) arrays.

    Args:
        cell (Cell): Burning cell with ``r_ss``, ``r_t``, ``a_a`` set.
        time_step (float): Simulation time step in seconds.

    Side Effects:
        Updates ``cell.r_t``, ``cell.avg_ros``, and ``cell.I_t`` in-place.
    """
    _accelerate_core(cell.r_t, cell.r_ss, cell.avg_ros, cell.I_t, cell.I_ss,
                     cell.a_a, float(time_step))

cal_g_to_BTU_lb(f_cal_g)

Convert heat content from cal/g to BTU/lb.

Parameters:

Name Type Description Default
f_cal_g float

Heat content in cal/g.

required

Returns:

Name Type Description
float float

Heat content in BTU/lb.

Source code in embrs/utilities/unit_conversions.py
260
261
262
263
264
265
266
267
268
269
def cal_g_to_BTU_lb(f_cal_g: float) -> float:
    """Convert heat content from cal/g to BTU/lb.

    Args:
        f_cal_g (float): Heat content in cal/g.

    Returns:
        float: Heat content in BTU/lb.
    """
    return f_cal_g * _CAL_G_TO_BTU_LB

calc_I_r(fuel, dead_moist_damping, live_moist_damping)

Compute reaction intensity from fuel properties and moisture damping.

Reaction intensity is the rate of heat release per unit area of the flaming front (Rothermel 1972, Eq. 27).

Parameters:

Name Type Description Default
fuel Fuel

Fuel model with net fuel loadings, heat content, and optimum reaction velocity (gamma).

required
dead_moist_damping float

Dead fuel moisture damping coefficient in [0, 1].

required
live_moist_damping float

Live fuel moisture damping coefficient in [0, 1].

required

Returns:

Name Type Description
float float

Reaction intensity (BTU/ft²/min).

Source code in embrs/models/rothermel.py
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
def calc_I_r(fuel: Fuel, dead_moist_damping: float, live_moist_damping: float) -> float:
    """Compute reaction intensity from fuel properties and moisture damping.

    Reaction intensity is the rate of heat release per unit area of the
    flaming front (Rothermel 1972, Eq. 27).

    Args:
        fuel (Fuel): Fuel model with net fuel loadings, heat content, and
            optimum reaction velocity (``gamma``).
        dead_moist_damping (float): Dead fuel moisture damping coefficient
            in [0, 1].
        live_moist_damping (float): Live fuel moisture damping coefficient
            in [0, 1].

    Returns:
        float: Reaction intensity (BTU/ft²/min).
    """
    mineral_damping = calc_mineral_damping()

    dead_calc = fuel.w_n_dead * fuel.heat_content * dead_moist_damping * mineral_damping
    live_calc = fuel.w_n_live * fuel.heat_content * live_moist_damping * mineral_damping

    I_r = fuel.gamma * (dead_calc + live_calc)

    return I_r

calc_eccentricity(fuel, R_h, R_0)

Compute fire ellipse eccentricity from effective wind speed.

Convert the effective wind speed to m/s, then compute the length-to- breadth ratio z and derive eccentricity. Capped at z = 8.0 following Anderson (1983).

Parameters:

Name Type Description Default
fuel Fuel

Fuel model for effective wind speed calculation.

required
R_h float

Head-fire rate of spread (ft/min).

required
R_0 float

No-wind, no-slope base ROS (ft/min).

required

Returns:

Name Type Description
float float

Fire ellipse eccentricity in [0, 1).

Source code in embrs/models/rothermel.py
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
def calc_eccentricity(fuel: Fuel, R_h: float, R_0: float) -> float:
    """Compute fire ellipse eccentricity from effective wind speed.

    Convert the effective wind speed to m/s, then compute the length-to-
    breadth ratio ``z`` and derive eccentricity. Capped at ``z = 8.0``
    following Anderson (1983).

    Args:
        fuel (Fuel): Fuel model for effective wind speed calculation.
        R_h (float): Head-fire rate of spread (ft/min).
        R_0 (float): No-wind, no-slope base ROS (ft/min).

    Returns:
        float: Fire ellipse eccentricity in [0, 1).
    """
    u_e = calc_effective_wind_speed(fuel, R_h, R_0)
    u_e_ms = ft_min_to_m_s(u_e)
    z = 0.936 * math.exp(0.2566 * u_e_ms) + 0.461 * math.exp(-0.1548 * u_e_ms) - 0.397
    z = min(z, 8.0)
    e = math.sqrt(z**2 - 1) / z

    return e

calc_effective_wind_factor(R_h, R_0)

Compute the effective wind factor from head-fire and base ROS.

The effective wind factor (phi_e) represents the combined influence of wind and slope as if it were a single wind-only factor.

Parameters:

Name Type Description Default
R_h float

Head-fire rate of spread (ft/min).

required
R_0 float

No-wind, no-slope base ROS (ft/min).

required

Returns:

Name Type Description
float float

Effective wind factor (dimensionless).

Source code in embrs/models/rothermel.py
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
def calc_effective_wind_factor(R_h: float, R_0: float) -> float:
    """Compute the effective wind factor from head-fire and base ROS.

    The effective wind factor (phi_e) represents the combined influence of
    wind and slope as if it were a single wind-only factor.

    Args:
        R_h (float): Head-fire rate of spread (ft/min).
        R_0 (float): No-wind, no-slope base ROS (ft/min).

    Returns:
        float: Effective wind factor (dimensionless).
    """
    phi_e = (R_h / R_0) - 1

    return phi_e

calc_effective_wind_speed(fuel, R_h, R_0)

Compute the effective wind speed from the effective wind factor.

Invert the wind factor equation to recover the equivalent wind speed that produces the same effect as the combined wind and slope.

Parameters:

Name Type Description Default
fuel Fuel

Fuel model with wind coefficients B, C, E, and packing ratio rat.

required
R_h float

Head-fire rate of spread (ft/min).

required
R_0 float

No-wind, no-slope base ROS (ft/min).

required

Returns:

Name Type Description
float float

Effective wind speed (ft/min). Returns 0 when R_h <= R_0.

Source code in embrs/models/rothermel.py
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
def calc_effective_wind_speed(fuel: Fuel, R_h: float, R_0: float) -> float:
    """Compute the effective wind speed from the effective wind factor.

    Invert the wind factor equation to recover the equivalent wind speed
    that produces the same effect as the combined wind and slope.

    Args:
        fuel (Fuel): Fuel model with wind coefficients ``B``, ``C``, ``E``,
            and packing ratio ``rat``.
        R_h (float): Head-fire rate of spread (ft/min).
        R_0 (float): No-wind, no-slope base ROS (ft/min).

    Returns:
        float: Effective wind speed (ft/min). Returns 0 when ``R_h <= R_0``.
    """


    if R_h <= R_0:
        phi_e = 0

    else: 
        phi_e = calc_effective_wind_factor(R_h, R_0)

    u_e = ((phi_e * (fuel.rat**fuel.E))/fuel.C) ** (1/fuel.B)

    return u_e

calc_flame_len(cell)

Estimate flame length from maximum fireline intensity.

For surface fires, uses Brown and Davis (1973) correlation. For crown fires, uses Thomas (1963) correlation.

Parameters:

Name Type Description Default
cell Cell

Cell with I_ss (BTU/ft/min) and _crown_status.

required

Returns:

Name Type Description
float float

Flame length in feet.

Source code in embrs/models/rothermel.py
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
def calc_flame_len(cell: Cell) -> float:
    """Estimate flame length from maximum fireline intensity.

    For surface fires, uses Brown and Davis (1973) correlation. For crown
    fires, uses Thomas (1963) correlation.

    Args:
        cell (Cell): Cell with ``I_ss`` (BTU/ft/min) and ``_crown_status``.

    Returns:
        float: Flame length in feet.
    """
    # Fireline intensity in Btu/ft/min
    fli = float(np.max(cell.I_ss))
    fli /= 60 # convert to Btu/ft/s

    if cell._crown_status == CrownStatus.NONE:
        # Surface fire
        # Brown and Davis 1973 pg. 175
        flame_len_ft = 0.45 * fli ** (0.46)

    else:
        flame_len_ft = (0.2 * (fli ** (2/3))) # in feet

    return flame_len_ft

calc_heat_sink(fuel, m_f)

Compute heat sink term for the Rothermel spread equation.

The heat sink represents the energy required to raise the fuel ahead of the fire front to ignition temperature, weighted by fuel class properties and moisture contents.

Parameters:

Name Type Description Default
fuel Fuel

Fuel model with bulk density, weighting factors, and surface-area-to-volume ratios.

required
m_f ndarray

Fuel moisture content array of shape (6,) as fractions.

required

Returns:

Name Type Description
float float

Heat sink (BTU/ft³).

Source code in embrs/models/rothermel.py
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
def calc_heat_sink(fuel: Fuel, m_f: np.ndarray) -> float:
    """Compute heat sink term for the Rothermel spread equation.

    The heat sink represents the energy required to raise the fuel ahead
    of the fire front to ignition temperature, weighted by fuel class
    properties and moisture contents.

    Args:
        fuel (Fuel): Fuel model with bulk density, weighting factors, and
            surface-area-to-volume ratios.
        m_f (np.ndarray): Fuel moisture content array of shape (6,) as
            fractions.

    Returns:
        float: Heat sink (BTU/ft³).
    """
    Q_ig = 250 + 1116 * m_f


    # Compute the heat sink term as per the equation
    heat_sink = 0

    dead_sum = 0
    for j in range(4):
        if fuel.s[j] != 0:
            dead_sum += fuel.f_dead_arr[j] * math.exp(-138/fuel.s[j]) * Q_ig[j]

    heat_sink += fuel.f_i[0] * dead_sum

    live_sum = 0
    for j in range(2):
        if fuel.s[4+j] != 0:
            live_sum += fuel.f_live_arr[j] * math.exp(-138/fuel.s[4+j]) * Q_ig[4+j]

    heat_sink += fuel.f_i[1] * live_sum
    heat_sink *= fuel.rho_b

    return heat_sink

calc_live_mx(fuel, m_f)

Compute live fuel moisture of extinction.

Determine the threshold moisture content above which live fuels will not sustain combustion, based on the ratio of dead-to-live fuel loading (fuel.W) and the dead characteristic moisture.

Parameters:

Name Type Description Default
fuel Fuel

Fuel model with loading ratio W and dead_mx.

required
m_f float

Weighted characteristic dead fuel moisture (fraction).

required

Returns:

Name Type Description
float float

Live fuel moisture of extinction (fraction). Clamped to be at least fuel.dead_mx.

Source code in embrs/models/rothermel.py
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
def calc_live_mx(fuel: Fuel, m_f: float) -> float:
    """Compute live fuel moisture of extinction.

    Determine the threshold moisture content above which live fuels will
    not sustain combustion, based on the ratio of dead-to-live fuel loading
    (``fuel.W``) and the dead characteristic moisture.

    Args:
        fuel (Fuel): Fuel model with loading ratio ``W`` and ``dead_mx``.
        m_f (float): Weighted characteristic dead fuel moisture (fraction).

    Returns:
        float: Live fuel moisture of extinction (fraction). Clamped to be
            at least ``fuel.dead_mx``.
    """
    W = fuel.W

    if W == math.inf:
        return fuel.dead_mx

    num = 0
    den = 0
    for i in range(4):
        if fuel.s[i] != 0:
            num += m_f * fuel.load[i] * math.exp(-138/fuel.s[i])
            den += fuel.load[i] * math.exp(-138/fuel.s[i])

    mf_dead = num/den

    mx = 2.9 * W * (1 - mf_dead / fuel.dead_mx) - 0.226

    return max(mx, fuel.dead_mx)

calc_mineral_damping(s_e=0.01)

Compute mineral damping coefficient.

Parameters:

Name Type Description Default
s_e float

Effective mineral content (fraction). Defaults to 0.010 (standard value for wildland fuels).

0.01

Returns:

Name Type Description
float float

Mineral damping coefficient (dimensionless).

Source code in embrs/models/rothermel.py
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
def calc_mineral_damping(s_e: float = 0.010) -> float:
    """Compute mineral damping coefficient.

    Args:
        s_e (float): Effective mineral content (fraction). Defaults to
            0.010 (standard value for wildland fuels).

    Returns:
        float: Mineral damping coefficient (dimensionless).
    """
    if s_e == 0.010:
        return _MINERAL_DAMPING_DEFAULT

    mineral_damping = 0.174 * s_e ** (-0.19)

    return mineral_damping

calc_moisture_damping(m_f, m_x)

Compute moisture damping coefficient for dead or live fuel.

Evaluates a cubic polynomial in the moisture ratio m_f / m_x (Rothermel 1972, Eq. 29). Returns 0 when moisture of extinction is zero or when the polynomial evaluates to a negative value.

Parameters:

Name Type Description Default
m_f float

Characteristic fuel moisture content (fraction).

required
m_x float

Moisture of extinction (fraction).

required

Returns:

Name Type Description
float float

Moisture damping coefficient in [0, 1].

Source code in embrs/models/rothermel.py
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
def calc_moisture_damping(m_f: float, m_x: float) -> float:
    """Compute moisture damping coefficient for dead or live fuel.

    Evaluates a cubic polynomial in the moisture ratio ``m_f / m_x``
    (Rothermel 1972, Eq. 29). Returns 0 when moisture of extinction is
    zero or when the polynomial evaluates to a negative value.

    Args:
        m_f (float): Characteristic fuel moisture content (fraction).
        m_x (float): Moisture of extinction (fraction).

    Returns:
        float: Moisture damping coefficient in [0, 1].
    """
    if m_x == 0:
        return 0

    r_m = m_f / m_x

    # Horner's form: fewer multiplications than expanded polynomial
    moist_damping = 1 + r_m * (-2.59 + r_m * (5.11 - 3.52 * r_m))

    return max(0, moist_damping)

calc_r_0(fuel, m_f)

Compute no-wind, no-slope base rate of spread and reaction intensity.

Evaluate the Rothermel (1972) equations for base ROS using fuel properties and moisture content. This is the fundamental spread rate before wind and slope adjustments.

Parameters:

Name Type Description Default
fuel Fuel

Fuel model with precomputed constants.

required
m_f ndarray

Fuel moisture content array of shape (6,) with entries [1h, 10h, 100h, dead herb, live herb, live woody] as fractions (g water / g fuel).

required

Returns:

Type Description
Tuple[float, float]

Tuple[float, float]: (R_0, I_r) where R_0 is base ROS (ft/min) and I_r is reaction intensity (BTU/ft²/min).

Source code in embrs/models/rothermel.py
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
def calc_r_0(fuel: Fuel, m_f: np.ndarray) -> Tuple[float, float]:
    """Compute no-wind, no-slope base rate of spread and reaction intensity.

    Evaluate the Rothermel (1972) equations for base ROS using fuel
    properties and moisture content. This is the fundamental spread rate
    before wind and slope adjustments.

    Args:
        fuel (Fuel): Fuel model with precomputed constants.
        m_f (np.ndarray): Fuel moisture content array of shape (6,) with
            entries [1h, 10h, 100h, dead herb, live herb, live woody] as
            fractions (g water / g fuel).

    Returns:
        Tuple[float, float]: ``(R_0, I_r)`` where ``R_0`` is base ROS
            (ft/min) and ``I_r`` is reaction intensity (BTU/ft²/min).
    """
    # Calculate moisture damping constants
    dead_mf, live_mf = get_characteristic_moistures(fuel, m_f)
    live_mx = calc_live_mx(fuel, dead_mf)
    live_moisture_damping = calc_moisture_damping(live_mf, live_mx)
    dead_moisture_damping = calc_moisture_damping(dead_mf, fuel.dead_mx)

    I_r = calc_I_r(fuel, dead_moisture_damping, live_moisture_damping)
    heat_sink = calc_heat_sink(fuel, m_f)

    R_0 = (I_r * fuel.flux_ratio)/heat_sink

    return R_0, I_r # ft/min, BTU/ft^2-min

calc_r_h(cell, R_0=None, I_r=None)

Compute head-fire rate of spread combining wind and slope effects.

Resolve the wind and slope vectors to determine the maximum spread direction (alpha) and head-fire ROS (R_h). Wind speed is capped at 0.9 × reaction intensity per Rothermel's wind limit.

Parameters:

Name Type Description Default
cell Cell

Cell with wind, slope, fuel, and moisture data.

required
R_0 float

Pre-computed no-wind, no-slope ROS (ft/min). Computed internally if None.

None
I_r float

Pre-computed reaction intensity (BTU/ft²/min). Computed internally if None.

None

Returns:

Type Description
Tuple[float, float, float, float]

Tuple[float, float, float, float]: (R_h, R_0, I_r, alpha) where R_h is head-fire ROS (ft/min), R_0 is base ROS (ft/min), I_r is reaction intensity (BTU/ft²/min), and alpha is the combined wind/slope heading (radians).

Side Effects

May update cell.aspect when slope is zero (set to wind direction).

Source code in embrs/models/rothermel.py
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
def calc_r_h(cell: Cell, R_0: float = None, I_r: float = None) -> Tuple[float, float, float, float]:
    """Compute head-fire rate of spread combining wind and slope effects.

    Resolve the wind and slope vectors to determine the maximum spread
    direction (``alpha``) and head-fire ROS (``R_h``). Wind speed is capped
    at 0.9 × reaction intensity per Rothermel's wind limit.

    Args:
        cell (Cell): Cell with wind, slope, fuel, and moisture data.
        R_0 (float, optional): Pre-computed no-wind, no-slope ROS (ft/min).
            Computed internally if None.
        I_r (float, optional): Pre-computed reaction intensity
            (BTU/ft²/min). Computed internally if None.

    Returns:
        Tuple[float, float, float, float]: ``(R_h, R_0, I_r, alpha)`` where
            ``R_h`` is head-fire ROS (ft/min), ``R_0`` is base ROS (ft/min),
            ``I_r`` is reaction intensity (BTU/ft²/min), and ``alpha`` is the
            combined wind/slope heading (radians).

    Side Effects:
        May update ``cell.aspect`` when slope is zero (set to wind direction).
    """
    wind_speed_m_s, wind_dir_deg = cell.curr_wind()

    wind_speed_ft_min = m_s_to_ft_min(wind_speed_m_s)

    wind_speed_ft_min *= cell.wind_adj_factor

    slope_angle_deg = cell.slope_deg
    slope_dir_deg = cell.aspect

    if slope_angle_deg == 0:
        rel_wind_dir_deg = 0
        cell.aspect = wind_dir_deg

    elif wind_speed_m_s == 0:
        rel_wind_dir_deg = 0
        wind_dir_deg = cell.aspect

    else:
        rel_wind_dir_deg = wind_dir_deg - slope_dir_deg
        if rel_wind_dir_deg < 0:
            rel_wind_dir_deg += 360

    rel_wind_dir = math.radians(rel_wind_dir_deg)
    slope_angle = math.radians(slope_angle_deg)

    fuel = cell.fuel
    m_f = cell.fmois

    if R_0 is None or I_r is None:
        R_0, I_r = calc_r_0(fuel, m_f)

    if R_0 <= 1e-12:
        # No spread in this cell
        return 0, 0, 0, 0

    # Enforce maximum wind speed
    U_max = 0.9 * I_r
    wind_speed_ft_min = min(U_max, wind_speed_ft_min)

    phi_w = calc_wind_factor(fuel, wind_speed_ft_min)
    phi_s = calc_slope_factor(fuel, slope_angle)

    vec_speed, alpha = calc_wind_slope_vec(R_0, phi_w, phi_s, rel_wind_dir)

    R_h = R_0 + vec_speed

    return R_h, R_0, I_r, alpha

calc_slope_factor(fuel, phi)

Compute the slope factor (phi_s) for the Rothermel spread equation.

Parameters:

Name Type Description Default
fuel Fuel

Fuel model with bulk density rho_b and particle density rho_p.

required
phi float

Slope angle (radians).

required

Returns:

Name Type Description
float float

Dimensionless slope factor (phi_s).

Source code in embrs/models/rothermel.py
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
def calc_slope_factor(fuel: Fuel, phi: float) -> float:
    """Compute the slope factor (phi_s) for the Rothermel spread equation.

    Args:
        fuel (Fuel): Fuel model with bulk density ``rho_b`` and particle
            density ``rho_p``.
        phi (float): Slope angle (radians).

    Returns:
        float: Dimensionless slope factor (phi_s).
    """
    packing_ratio = fuel.rho_b / fuel.rho_p

    phi_s = 5.275 * (packing_ratio ** (-0.3)) * math.tan(phi) ** 2

    return phi_s

calc_vals_for_all_directions(cell, R_h, I_r, alpha, e, I_h=None)

Compute ROS and fireline intensity along all spread directions.

Use the fire ellipse (eccentricity e) and the combined wind/slope heading alpha to resolve the head-fire ROS into each of the cell's spread directions.

Parameters:

Name Type Description Default
cell Cell

Cell providing directions and fuel properties.

required
R_h float

Head-fire rate of spread (ft/min for surface, m/min for crown fire).

required
I_r float

Reaction intensity (BTU/ft²/min). Ignored when I_h is provided.

required
alpha float

Combined wind/slope heading in radians, relative to the cell's aspect (upslope direction).

required
e float

Fire ellipse eccentricity in [0, 1).

required
I_h float

Head-fire fireline intensity (BTU/ft/min). When provided, directional intensities are scaled from this value instead of being computed from I_r.

None

Returns:

Type Description
Tuple[ndarray, ndarray]

Tuple[np.ndarray, np.ndarray]: (r_list, I_list) where r_list is ROS in m/s per direction and I_list is fireline intensity in BTU/ft/min per direction.

Source code in embrs/models/rothermel.py
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
def calc_vals_for_all_directions(cell: Cell, R_h: float, I_r: float, alpha: float,
                                 e: float, I_h: float = None) -> Tuple[np.ndarray, np.ndarray]:
    """Compute ROS and fireline intensity along all spread directions.

    Use the fire ellipse (eccentricity ``e``) and the combined wind/slope
    heading ``alpha`` to resolve the head-fire ROS into each of the cell's
    spread directions.

    Args:
        cell (Cell): Cell providing directions and fuel properties.
        R_h (float): Head-fire rate of spread (ft/min for surface, m/min
            for crown fire).
        I_r (float): Reaction intensity (BTU/ft²/min). Ignored when
            ``I_h`` is provided.
        alpha (float): Combined wind/slope heading in radians, relative to
            the cell's aspect (upslope direction).
        e (float): Fire ellipse eccentricity in [0, 1).
        I_h (float, optional): Head-fire fireline intensity (BTU/ft/min).
            When provided, directional intensities are scaled from this
            value instead of being computed from ``I_r``.

    Returns:
        Tuple[np.ndarray, np.ndarray]: ``(r_list, I_list)`` where
            ``r_list`` is ROS in m/s per direction and ``I_list`` is
            fireline intensity in BTU/ft/min per direction.
    """
    spread_directions = np.deg2rad(cell.directions)

    gamma = np.abs(((alpha + np.deg2rad(cell.aspect)) - spread_directions) % (2*np.pi))
    gamma = np.minimum(gamma, 2*np.pi - gamma)

    R_gamma = R_h * ((1 - e)/(1 - e * np.cos(gamma)))

    if I_h is None:
        t_r = 384 / cell.fuel.sav_ratio
        H_a = I_r * t_r
        I_gamma = H_a * R_gamma # BTU/ft/min

    else:
        I_gamma = I_h * (R_gamma / R_h) # BTU/ft/min

    r_list = ft_min_to_m_s(R_gamma)
    return r_list, I_gamma

calc_wind_factor(fuel, wind_speed)

Compute the wind factor (phi_w) for the Rothermel spread equation.

Parameters:

Name Type Description Default
fuel Fuel

Fuel model with precomputed wind coefficients B, C, E, and packing ratio rat.

required
wind_speed float

Midflame wind speed (ft/min).

required

Returns:

Name Type Description
float float

Dimensionless wind factor (phi_w).

Source code in embrs/models/rothermel.py
436
437
438
439
440
441
442
443
444
445
446
447
448
449
def calc_wind_factor(fuel: Fuel, wind_speed: float) -> float:
    """Compute the wind factor (phi_w) for the Rothermel spread equation.

    Args:
        fuel (Fuel): Fuel model with precomputed wind coefficients
            ``B``, ``C``, ``E``, and packing ratio ``rat``.
        wind_speed (float): Midflame wind speed (ft/min).

    Returns:
        float: Dimensionless wind factor (phi_w).
    """
    phi_w = fuel.C * (wind_speed ** fuel.B) * fuel.rat ** (-fuel.E)

    return phi_w

calc_wind_slope_vec(R_0, phi_w, phi_s, angle)

Compute the combined wind and slope vector magnitude and direction.

Resolve wind and slope spread factors into a single resultant vector using Rothermel's vector addition method.

Parameters:

Name Type Description Default
R_0 float

No-wind, no-slope ROS (ft/min).

required
phi_w float

Wind factor (dimensionless).

required
phi_s float

Slope factor (dimensionless).

required
angle float

Angle between wind and upslope directions (radians).

required

Returns:

Type Description
Tuple[float, float]

Tuple[float, float]: (vec_mag, vec_dir) where vec_mag is the combined wind/slope spread increment (ft/min) and vec_dir is the direction of the resultant vector (radians).

Source code in embrs/models/rothermel.py
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
def calc_wind_slope_vec(R_0: float, phi_w: float, phi_s: float, angle: float) -> Tuple[float, float]:
    """Compute the combined wind and slope vector magnitude and direction.

    Resolve wind and slope spread factors into a single resultant vector
    using Rothermel's vector addition method.

    Args:
        R_0 (float): No-wind, no-slope ROS (ft/min).
        phi_w (float): Wind factor (dimensionless).
        phi_s (float): Slope factor (dimensionless).
        angle (float): Angle between wind and upslope directions (radians).

    Returns:
        Tuple[float, float]: ``(vec_mag, vec_dir)`` where ``vec_mag`` is the
            combined wind/slope spread increment (ft/min) and ``vec_dir`` is
            the direction of the resultant vector (radians).
    """
    d_w = R_0 * phi_w
    d_s = R_0 * phi_s

    x = d_s + d_w * math.cos(angle)
    y = d_w * math.sin(angle)
    vec_mag = math.sqrt(x**2 + y**2)

    if vec_mag == 0:
        vec_dir = 0

    else:
        vec_dir = math.atan2(y, x)

    return vec_mag, vec_dir

ft_min_to_m_s(f_ft_min)

Convert speed from feet per minute to meters per second.

Parameters:

Name Type Description Default
f_ft_min float

Speed in ft/min.

required

Returns:

Name Type Description
float float

Speed in m/s.

Source code in embrs/utilities/unit_conversions.py
 92
 93
 94
 95
 96
 97
 98
 99
100
101
def ft_min_to_m_s(f_ft_min: float) -> float:
    """Convert speed from feet per minute to meters per second.

    Args:
        f_ft_min (float): Speed in ft/min.

    Returns:
        float: Speed in m/s.
    """
    return f_ft_min * _FT_MIN_TO_M_S

ft_min_to_mph(f_ft_min)

Convert speed from feet per minute to miles per hour.

Parameters:

Name Type Description Default
f_ft_min float

Speed in ft/min.

required

Returns:

Name Type Description
float float

Speed in mph.

Source code in embrs/utilities/unit_conversions.py
116
117
118
119
120
121
122
123
124
125
def ft_min_to_mph(f_ft_min: float) -> float:
    """Convert speed from feet per minute to miles per hour.

    Args:
        f_ft_min (float): Speed in ft/min.

    Returns:
        float: Speed in mph.
    """
    return f_ft_min * _FT_MIN_TO_MPH

ft_to_m(f_ft)

Convert length from feet to meters.

Parameters:

Name Type Description Default
f_ft float

Length in feet.

required

Returns:

Name Type Description
float float

Length in meters.

Source code in embrs/utilities/unit_conversions.py
76
77
78
79
80
81
82
83
84
85
def ft_to_m(f_ft: float) -> float:
    """Convert length from feet to meters.

    Args:
        f_ft (float): Length in feet.

    Returns:
        float: Length in meters.
    """
    return f_ft * _FT_TO_M

get_characteristic_moistures(fuel, m_f)

Compute weighted characteristic dead and live fuel moisture contents.

Use fuel weighting factors (f_dead_arr, f_live_arr) to collapse the per-class moisture array into single dead and live values.

Parameters:

Name Type Description Default
fuel Fuel

Fuel model providing weighting arrays.

required
m_f ndarray

Moisture content array of shape (6,) as fractions.

required

Returns:

Type Description
Tuple[float, float]

Tuple[float, float]: (dead_mf, live_mf) weighted characteristic moisture contents for dead and live fuel categories.

Source code in embrs/models/rothermel.py
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
def get_characteristic_moistures(fuel: Fuel, m_f: np.ndarray) -> Tuple[float, float]:
    """Compute weighted characteristic dead and live fuel moisture contents.

    Use fuel weighting factors (``f_dead_arr``, ``f_live_arr``) to collapse
    the per-class moisture array into single dead and live values.

    Args:
        fuel (Fuel): Fuel model providing weighting arrays.
        m_f (np.ndarray): Moisture content array of shape (6,) as fractions.

    Returns:
        Tuple[float, float]: ``(dead_mf, live_mf)`` weighted characteristic
            moisture contents for dead and live fuel categories.
    """
    dead_mf = np.dot(fuel.f_dead_arr, m_f[0:4])
    live_mf = np.dot(fuel.f_live_arr, m_f[4:])

    return dead_mf, live_mf

m_s_to_ft_min(m_s)

Convert speed from meters per second to feet per minute.

Parameters:

Name Type Description Default
m_s float

Speed in m/s.

required

Returns:

Name Type Description
float float

Speed in ft/min.

Source code in embrs/utilities/unit_conversions.py
104
105
106
107
108
109
110
111
112
113
def m_s_to_ft_min(m_s: float) -> float:
    """Convert speed from meters per second to feet per minute.

    Args:
        m_s (float): Speed in m/s.

    Returns:
        float: Speed in ft/min.
    """
    return m_s * _M_S_TO_FT_MIN

m_to_ft(f_m)

Convert length from meters to feet.

Parameters:

Name Type Description Default
f_m float

Length in meters.

required

Returns:

Name Type Description
float float

Length in feet.

Source code in embrs/utilities/unit_conversions.py
64
65
66
67
68
69
70
71
72
73
def m_to_ft(f_m: float) -> float:
    """Convert length from meters to feet.

    Args:
        f_m (float): Length in meters.

    Returns:
        float: Length in feet.
    """
    return f_m * _M_TO_FT

mph_to_ft_min(f_mph)

Convert speed from miles per hour to feet per minute.

Parameters:

Name Type Description Default
f_mph float

Speed in mph.

required

Returns:

Name Type Description
float float

Speed in ft/min.

Source code in embrs/utilities/unit_conversions.py
128
129
130
131
132
133
134
135
136
137
def mph_to_ft_min(f_mph: float) -> float:
    """Convert speed from miles per hour to feet per minute.

    Args:
        f_mph (float): Speed in mph.

    Returns:
        float: Speed in ft/min.
    """
    return f_mph * _MPH_TO_FT_MIN

surface_fire(cell)

Compute steady-state surface fire ROS and fireline intensity for a cell.

Calculate the head-fire rate of spread (R_h), then resolve spread rates and fireline intensities along all 12 spread directions using fire ellipse geometry. Results are stored directly on the cell object.

Parameters:

Name Type Description Default
cell Cell

Cell to evaluate. Must have fuel, moisture, wind, slope, and direction attributes populated.

required
Side Effects

Sets cell.r_ss (m/s), cell.I_ss (BTU/ft/min), cell.r_h_ss (m/s), cell.reaction_intensity (BTU/ft²/min), cell.alpha (radians), and cell.e (eccentricity) on the cell.

Source code in embrs/models/rothermel.py
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
def surface_fire(cell: Cell):
    """Compute steady-state surface fire ROS and fireline intensity for a cell.

    Calculate the head-fire rate of spread (R_h), then resolve spread rates and
    fireline intensities along all 12 spread directions using fire ellipse
    geometry. Results are stored directly on the cell object.

    Args:
        cell (Cell): Cell to evaluate. Must have fuel, moisture, wind, slope,
            and direction attributes populated.

    Side Effects:
        Sets ``cell.r_ss`` (m/s), ``cell.I_ss`` (BTU/ft/min),
        ``cell.r_h_ss`` (m/s), ``cell.reaction_intensity`` (BTU/ft²/min),
        ``cell.alpha`` (radians), and ``cell.e`` (eccentricity) on the cell.
    """
    R_h, R_0, I_r, alpha = calc_r_h(cell)

    cell.alpha = alpha
    spread_directions = np.deg2rad(cell.directions)

    if R_h < R_0 or R_0 == 0:
        cell.r_ss = np.zeros_like(spread_directions)
        cell.I_ss = np.zeros_like(spread_directions)
        cell.r_h_ss = 0.0
        cell.reaction_intensity = 0
        cell.e = 0
        return

    cell.reaction_intensity = I_r

    e = calc_eccentricity(cell.fuel, R_h, R_0)
    cell.e = e

    r_list, I_list = calc_vals_for_all_directions(cell, R_h, I_r, alpha, e)

    # r in m/s, I in btu/ft/min
    cell.r_ss = r_list
    cell.I_ss = I_list
    cell.r_h_ss = np.max(r_list)