The SpreadModel module provides composable, differentiable components for driving level set fire simulations. Instead of manually constructing a spread rate matrix F, you build a FireSpreadModel from pluggable wind, moisture, and terrain components — each a callable (t, x, y) struct.
M =FuelClasses(d1=0.06, d10=0.07, d100=0.08, herb=0.0, wood=0.0)model =FireSpreadModel( SHORT_GRASS,UniformWind(speed=8.0),UniformMoisture(M),FlatTerrain())# Evaluate spread rate at a single pointmodel(0.0, 100.0, 100.0)
By default, FireSpreadModel uses cosine blending to distribute the head-fire rate across angles. This produces fires that are wider than typically observed. The elliptical model (Anderson, 1983) produces more realistic elongated fire shapes by computing a length-to-breadth ratio from wind speed.
Pass a directional model as the 5th argument to FireSpreadModel:
Spatially varying fuel moisture that responds to fire-induced drying. As the fire front approaches, radiative heat dries unburned fuel ahead of the front. The 1-hr dead fuel moisture (d1) varies spatially while other size classes remain constant.
How fast fire dries nearby fuel. Higher values = stronger pre-heating effect, faster fire acceleration.
recovery_rate
0.001
0.0001 – 0.01
How fast fuel rewets toward ambient. Higher values = fuel recovers quickly when fire moves away.
min_d1
0.03
0.02 – 0.05
Minimum 1-hr dead fuel moisture (fraction). Below ~0.03, wildland fuels are essentially oven-dry.
The dry_rate is the most sensitive parameter — it controls the positive feedback loop where fire dries adjacent fuel, increasing spread rate, which dries more fuel. Start with the default (0.1) and adjust based on whether the simulated fire accelerates too quickly or too slowly.
M =FuelClasses(d1=0.06, d10=0.07, d100=0.08, herb=0.0, wood=0.0)fig =Figure(size=(800, 250))for (col, s) inenumerate([0.0, 0.3, 0.6]) g =LevelSetGrid(150, 150, dx=20.0)ignite!(g, 1500.0, 1500.0, 50.0) m =FireSpreadModel(SHORT_GRASS, UniformWind(speed=5.0), UniformMoisture(M), UniformSlope(slope=s))simulate!(g, m, steps=150, dt=0.5) ax =Axis(fig[1, col], title="slope = $s", aspect=DataAspect())fireplot!(ax, g)hidedecorations!(ax)endfig
Fuel Breaks and Unburnable Areas
The LevelSetGrid tracks per-cell ignition state via t_ignite. By default all cells are burnable (t_ignite = Inf). Cells marked as unburnable have t_ignite = NaN, which sets their spread rate to zero.
Use set_unburnable! to mark circular regions, or directly set grid.t_ignite[i, j] = NaN for arbitrary shapes. The convenience function burnable(grid) returns a BitMatrix of burnable cells.
This is useful for representing water bodies, roads, fuel breaks, and pre-existing fire scars.
To represent an existing fire scar that should not re-ignite, combine set_unburnable! with setting φ < 0:
grid =LevelSetGrid(200, 200, dx=30.0)# Pre-existing fire scar: set φ < 0 AND mark unburnablecx, cy, r =2500.0, 3000.0, 300.0xs =xcoords(grid)ys =ycoords(grid)for j ineachindex(xs), i ineachindex(ys)ifhypot(xs[j] - cx, ys[i] - cy) <= r grid.φ[i, j] =-1.0endendset_unburnable!(grid, cx, cy, r)# New ignition away from the scarignite!(grid, 4000.0, 3000.0, 50.0)M =FuelClasses(d1=0.06, d10=0.07, d100=0.08, herb=0.0, wood=0.0)model =FireSpreadModel(SHORT_GRASS, UniformWind(speed=8.0), UniformMoisture(M), FlatTerrain())simulate!(grid, model, steps=100, dt=0.5)fig =Figure()ax =Axis(fig[1, 1], title="Fire scar blocks spread", aspect=DataAspect(), xlabel="x (m)", ylabel="y (m)")fireplot!(ax, grid)fig
Without set_unburnable!, the fire scar boundary would be treated as an active fire front and expand outward.
Burnout
By default, once a cell ignites it continues to contribute to fire spread indefinitely. In reality, fuel is consumed and the flaming front passes. The burnout keyword in simulate! models this by scaling the spread rate based on how long a cell has been burning.
Three burnout models are available:
Type
Formula
Behavior
NoBurnout()
1.0
Fire spreads indefinitely (default)
ExponentialBurnout(τ)
exp(-t/τ)
Smooth exponential decay over residence time τ
LinearBurnout(τ)
max(0, 1 - t/τ)
Linear ramp to zero over residence time τ
The residence time τ is computed from the Rothermel fuel model using Anderson’s (1969) formula:
For fast-burning fuels like short grass, the residence time is very small (< 0.01 min), so burnout primarily affects the spread rate near the ignition point where the fire has been burning longest. For heavier fuels with longer residence times, the effect is more pronounced.
Custom Components
To create a custom component, define a callable struct that subtypes AbstractWind, AbstractMoisture, or AbstractTerrain.
For example, a wind field that varies in space:
struct GradientWind <: AbstractWind base_speed::Float64 gradient::Float64 # speed increase per meter in xendfunction (w::GradientWind)(t, x, y) speed = w.base_speed + w.gradient * x direction =0.0# wind from the west (speed, direction)endM =FuelClasses(d1=0.06, d10=0.07, d100=0.08, herb=0.0, wood=0.0)g =LevelSetGrid(150, 150, dx=20.0)ignite!(g, 1500.0, 1500.0, 50.0)m =FireSpreadModel(SHORT_GRASS, GradientWind(5.0, 0.003), UniformMoisture(M), FlatTerrain())simulate!(g, m, steps=150, dt=0.5)fig =Figure()ax =Axis(fig[1, 1], title="Gradient Wind", aspect=DataAspect(), xlabel="x (m)", ylabel="y (m)")fireplot!(ax, g)fig
For dynamic components that respond to fire state, also implement update!:
The simulate! function accepts several keyword arguments that control numerical behavior:
Parameter
Default
Description
steps
100
Number of time steps to run
dt
nothing
Time step in minutes. When nothing, computed automatically via the CFL condition each step.
cfl
0.5
Safety factor for the CFL condition (dt = cfl * min(dx,dy) / max(F)). Only used when dt=nothing.
reinit_every
10
Reinitialization frequency. Every N steps, the level set is restored to a signed distance function.
solver
Godunov()
Numerical solver for the level set equation. See Solvers.
curvature
0.0
Curvature regularization coefficient. When > 0, smooths the fire front via F_eff = max(F - b·κ, 0). See Curvature.
reinit
IterativeReinit()
Reinitialization method. IterativeReinit() (default) or NewtonReinit() for machine-precision signed distance. See Reinitialization.
solver: Controls the numerical scheme for advancing the level set. Godunov() (default) uses first-order upwind differencing with forward Euler. Superbee() uses a second-order Superbee flux limiter with RK2 (Heun’s method) for sharper fire fronts. WENO5() uses a 5th-order WENO scheme with SSP-RK3 for the highest accuracy. See the Solvers section for details.
cfl: The default 0.5 is conservative — it uses half the maximum stable time step. Values closer to 1.0 are faster but risk instability if the spread rate field changes rapidly. If you see oscillations or divergence, lower this value.
reinit_every: Over many steps, φ drifts from a true signed distance function, degrading the accuracy of the upwind scheme. More frequent reinitialization (e.g., 5) improves accuracy at modest cost; less frequent (e.g., 50) is faster but may introduce artifacts in long simulations. For most cases, the default of 10 works well.
dt: When set to nothing (default), simulate! calls spread_rate_field! first, then computes a CFL-limited time step automatically. If you pass a fixed dt, you are responsible for ensuring it satisfies the CFL condition — violating it causes numerical blowup.
Animation with Trace
The Trace type records snapshots of the level set field during simulation, making it easy to create animations of fire spread. Pass trace=Trace(grid, every) to simulate! to record φ every every steps.
The level set grid and all simulation operations support Float32 for reduced memory and potentially faster computation. To create a Float32 simulation, pass Float32 values to all constructors:
Note
The LevelSetGrid constructor determines its element type via promote_type on all keyword arguments (dx, dy, x0, y0). The defaults are Float64, so you must pass Float32 values for all of them — e.g. LevelSetGrid(200, 200, dx=30f0, x0=0f0, y0=0f0).
usingBenchmarkToolsfunctionrun_simulation(::Type{T}, n=500) where T grid =LevelSetGrid(n, n, dx=T(10), x0=zero(T), y0=zero(T))ignite!(grid, T(2500), T(2500), T(50)) F =fill(T(10), size(grid))for _ in1:200advance!(grid, F, T(0.25))endreinitialize!(grid) gridendb64 =@benchmarkrun_simulation(Float64)b32 =@benchmarkrun_simulation(Float32)nothing
Metric
Float64
Float32
Ratio
Median time
317.84 ms
241.78 ms
1.31x
Memory
397.1 MiB
198.8 MiB
2.00x
References
Rothermel, R.C. (1972). A Mathematical Model for Predicting Fire Spread in Wildland Fuels. Res. Paper INT-115, USDA Forest Service.
Osher, S. & Sethian, J.A. (1988). Fronts propagating with curvature-dependent speed. J. Computational Physics, 79(1), 12-49.