Computing Flame Length and Fireline Intensity in Python

Rate of spread tells you how fast a fire moves; fireline intensity tells you what it can do when it arrives, and flame length is the form that fire crews actually plan against. The conversion is two short equations, and almost every mistake in it is a unit error that produces a number three orders of magnitude wrong while still looking plausible. This guide gets it right, inside Fire Behavior and Rate of Spread Modeling, part of Fire Risk & Fuel Assessment.

When to use each quantity

Quantity Units Answers Audience
Rate of spread m/min How fast the head moves Modelling, evacuation timing
Fireline intensity kW/m Energy released per metre of fireline per second Physical comparison, crown-fire initiation
Flame length m What the flames look like Suppression tactics, safety
Heat per unit area kJ/m² Total energy released as the front passes Soil heating, ecological effect

Intensity and flame length carry the same information — one is a monotone function of the other — but they serve different audiences. Suppression doctrine is written in flame length; crown-fire initiation thresholds are written in intensity. Compute both and report the one your reader uses.

Byram’s equations and their units

Fireline intensity is the product of the fuel’s heat yield, the mass of fuel consumed per unit area, and the rate of spread:

with the low heat of combustion in kJ/kg (about 18,600 for most woody fuels), the available fuel load in kg/m², and the rate of spread in m/s. The result is kW/m.

Flame length follows from intensity by Byram’s empirical relation:

with in kW/m and in metres.

The unit traps are all in and . Fuel load is nearly always tabulated in tonnes per hectare and must be converted to kg/m² by dividing by 10; rate of spread is nearly always reported in metres per minute and must be divided by 60. Get either wrong and the intensity is out by a factor of 10 or 60 — enough to move a fire from “hand crews can hold it” to “nothing will stop it”, and not obviously wrong on a map.

The unit chain from field measurements to flame length A flow from left to right. Fuel load in tonnes per hectare is divided by ten to give kilograms per square metre. Rate of spread in metres per minute is divided by sixty to give metres per second. Both feed a multiplication with heat yield in kilojoules per kilogram to produce fireline intensity in kilowatts per metre, which is then raised to the power 0.46 and scaled to give flame length in metres. The two division steps are marked as the places where errors of a factor of ten or sixty occur. fuel load t/ha rate of spread m/min ÷ 10 → kg/m² ÷ 60 → m/s I = H · w · r kW/m L = 0.0775 · I^0.46 metres H ≈ 18 600 kJ/kg woody fuels Both shaded boxes are unit conversions, and both are routinely skipped. Skipping them scales intensity by 10 or 60 — a plausible-looking map of the wrong fire.

Minimal reproducible example

import numpy as np

HEAT_YIELD_KJ_KG = 18_600.0        # low heat of combustion, typical woody fuel


def fireline_intensity(fuel_load_t_ha, ros_m_min, heat_kj_kg=HEAT_YIELD_KJ_KG):
    """Byram's fireline intensity in kW/m from field-convention inputs."""
    w_kg_m2 = np.asarray(fuel_load_t_ha, dtype="float64") / 10.0     # t/ha -> kg/m²
    r_m_s = np.asarray(ros_m_min, dtype="float64") / 60.0            # m/min -> m/s
    return heat_kj_kg * w_kg_m2 * r_m_s                              # kJ/kg·kg/m²·m/s = kW/m


def flame_length(intensity_kw_m):
    """Byram's flame length in metres from fireline intensity in kW/m."""
    i = np.asarray(intensity_kw_m, dtype="float64")
    return np.where(i > 0, 0.0775 * np.power(np.maximum(i, 1e-9), 0.46), 0.0)


def heat_per_unit_area(fuel_load_t_ha, heat_kj_kg=HEAT_YIELD_KJ_KG):
    """Total energy released per square metre as the front passes, in kJ/m²."""
    return heat_kj_kg * (np.asarray(fuel_load_t_ha, dtype="float64") / 10.0)

The comment on the return line of fireline_intensity is not decoration — writing the unit algebra out is the cheapest defence against the error this function exists to avoid.

Only the fuel that actually burns in the flaming front counts as . Large woody debris and duff burn behind the front and contribute to heat per unit area and to soil heating, but not to the intensity that drives flame length. Using total fuel load rather than available fine fuel systematically over-predicts, often by a factor of two or more in a stand with heavy downed wood.

Interpretation thresholds

Flame length maps onto suppression doctrine through a set of widely used breakpoints. They are the reason the conversion is worth doing at all.

Flame length Intensity Interpretation
< 1.2 m < 350 kW/m Hand crews with hand tools can generally hold the line
1.2–2.4 m 350–1,700 kW/m Too intense for direct attack at the head; equipment or aircraft needed
2.4–3.4 m 1,700–3,500 kW/m Torching, crowning and spotting likely; control efforts at the head are unlikely to succeed
> 3.4 m > 3,500 kW/m Crown fire; suppression at the head is ineffective
Flame length against fireline intensity with suppression bands A concave curve rises from the origin, showing flame length in metres against fireline intensity in kilowatts per metre. Four horizontal bands mark the interpretation thresholds at 1.2, 2.4 and 3.4 metres of flame length, and vertical marks show that they correspond to roughly 350, 1700 and 3500 kilowatts per metre. Because the exponent is less than one, the curve flattens, so large increases in intensity produce small increases in flame length at the upper end. 0 1.2 2.4 3.4 0 1000 2000 3000 4000 fireline intensity (kW/m) flame length (m) 350 1700 3500 hand crews equipment / aircraft torching & spotting crown fire The exponent is below one, so doubling intensity adds far less than double the flame length.

Parameter reference

Parameter Type Typical Range Rationale
heat_kj_kg float 18,600 16,000–22,000 Low heat of combustion; varies modestly with species and resin content
available fuel t/ha 2–20 0.5–40 Only fine fuel consumed in the flaming front, not total load
rate of spread m/min 1–60 0.2–150 Head-fire spread; flanking and backing rates are much lower
Byram coefficient float 0.0775 Empirical; leave it unless replacing the whole relation
Byram exponent float 0.46 Empirical; changing it invalidates the threshold table
moisture of extinction % 25–40 Applied upstream in the spread model, not here

Expected output and verification

The verification that matters is dimensional. Two anchors and a monotonicity check catch essentially every implementation error:

import numpy as np

# Anchor 1: a light grass fire — 3 t/ha at 10 m/min.
i1 = fireline_intensity(3.0, 10.0)
assert 800 < i1 < 1000, f"grass-fire intensity out of range: {i1:.0f} kW/m"
assert 1.5 < flame_length(i1) < 2.2, "grass-fire flame length out of range"

# Anchor 2: a heavy slash fire — 30 t/ha at 40 m/min.
i2 = fireline_intensity(30.0, 40.0)
assert i2 > 30_000, f"slash-fire intensity implausibly low: {i2:.0f} kW/m"
assert flame_length(i2) > 8.0, "slash-fire flame length implausibly short"

# Monotonicity: both inputs must increase intensity, and intensity must increase length.
loads = np.linspace(1, 30, 50)
assert np.all(np.diff(fireline_intensity(loads, 10.0)) > 0)
assert np.all(np.diff(flame_length(fireline_intensity(loads, 10.0))) > 0)

# The refusal test: a unit slip must be detectable, not silently absorbed.
wrong = HEAT_YIELD_KJ_KG * 3.0 * 10.0          # t/ha and m/min used raw
assert wrong / i1 > 100, "a raw-units calculation should be wildly off, and is"

The last assertion is the one worth keeping. It feeds the machinery the exact mistake the unit conversions exist to prevent and confirms the result is obviously wrong rather than plausibly wrong — which is the difference between an error someone catches and an error that ships.

Applying it across a raster is straightforward once the units are right:

import rasterio


def intensity_raster(fuel_path: str, ros_path: str, out_path: str,
                     heat_kj_kg: float = HEAT_YIELD_KJ_KG):
    """Write fireline intensity and flame length as a two-band GeoTIFF."""
    with rasterio.open(fuel_path) as f, rasterio.open(ros_path) as r:
        assert f.transform == r.transform and f.crs == r.crs, "grids must align"
        fuel = f.read(1, masked=True).astype("float64")
        ros = r.read(1, masked=True).astype("float64")
        profile = f.profile

    intensity = fireline_intensity(fuel.filled(np.nan), ros.filled(np.nan), heat_kj_kg)
    length = flame_length(intensity)

    profile.update(count=2, dtype="float32", nodata=np.nan,
                   compress="deflate", predictor=2, tiled=True)
    with rasterio.open(out_path, "w", **profile) as dst:
        dst.write(intensity.astype("float32"), 1)
        dst.write(length.astype("float32"), 2)
        dst.set_band_description(1, "fireline_intensity_kW_per_m")
        dst.set_band_description(2, "flame_length_m")
    return out_path

Setting band descriptions costs one line and prevents the downstream question that always follows a two-band raster.

Flame length at the four suppression thresholds, drawn to scale Four flame profiles are drawn to scale against a common ground line, with a person and a machine for reference. A 1.2 metre flame stands about shoulder height on the figure and hand crews can work it. A 2.4 metre flame is twice the figure's height. A 3.4 metre flame reaches into the lower canopy. A flame beyond that engulfs the crown. Each is annotated with the corresponding fireline intensity. 1.8 m 1.2 m < 350 kW/m 2.4 m 1700 kW/m 3.4 m 3500 kW/m > 5 m · crown fire > 8000 kW/m Doubling the flame length takes roughly five times the intensity.

Common pitfalls

  • Total fuel load instead of available fine fuel. Heavy downed wood burns behind the front and inflates intensity if included.
  • Rate of spread in the wrong units. m/min into an equation expecting m/s is a factor of 60, and the resulting map still looks like a fire.
  • Applying head-fire spread everywhere. Flanks and the backing edge spread far more slowly, so a single spread raster over-predicts intensity around most of the perimeter.
  • Reading flame length as flame height. They differ on slopes and in wind, where the flame leans; the suppression thresholds are written in flame length.
  • Assuming intensity keeps rising with flame length. The exponent is 0.46, so a fire at 20,000 kW/m has roughly twice the flame length of one at 3,500 — not six times.

Frequently Asked Questions

Should I use Byram’s flame length or a crown-fire specific relation?

Byram’s relation is for surface fire and is what the suppression thresholds are built on. Once a fire is crowning, flame length is governed by canopy fuel rather than surface fuel, and a surface-fire relation under-predicts. Use Byram for the surface case and switch to a crown-fire model past the initiation threshold rather than extrapolating.

Where does heat per unit area fit?

It is the ecological quantity. Intensity governs suppression and crown-fire initiation; heat per unit area governs soil heating, seed bank survival and duff consumption. A slow fire in heavy fuel can have low intensity and very high heat per unit area — the classic long-residence smouldering burn that kills roots without ever looking dramatic.

Can I invert flame length to get intensity?

Yes, and it is often useful when the only record is an observer’s flame-length estimate: . Treat the result as an order-of-magnitude figure, because the relation is empirical and observed flame lengths carry large uncertainty.

Up: Fire Behavior and Rate of Spread Modeling · Fire Risk & Fuel Assessment