Repository navigation
Farsite: downhill spread is boosted as much as uphill #231
Description
Is the Farsite model meant to speed up downhill spread? Right now it does, exactly as much as uphill.
FarsitePropagationModel.cpp:431 squares the slope (phis = 5.275·β^-0.3·slopex²), so its sign is lost. In Rothermel the slope factor only applies upslope. Here is GR2, no wind, a uniform 30% grade, 20 min after a point ignition (repro_slope.py):
| Uphill is | North (m) | South (m) | East (m) |
|---|---|---|---|
| — (flat) | 94 | 93 | 94 |
| north | 122 | 184 | 102 |
| south | 122 | 184 | 102 |
Swapping uphill and downhill changes nothing. Separately, north and south differ (122 vs 184) whichever way the slope goes.
On a real case (Mineral Fire 2016, near Coalinga, CA), this sends the simulated fire down into the valley, while the real one climbed the hills.
Related, in the same file:
slopex = slope*2(line 289): the commented-out code takes the tangent. Is the ×2 intended? It multiplies the slope term by 4.windspd * 88.0(line 288) converts mph to ft/min, but ForeFire winds are m/s.Farsite.adjustementWindis read into a local (line 117) and never used.
Making the slope term zero when spreading downhill (slopex > 0 ? … : 0) gives 124 m uphill and 92 m downhill, the same as flat (uphill to the south still gives 184 m). I can open a PR once we agree on the intended behaviour.
repro_slope.py
"""Farsite on a uniform 30% slope, no wind: how far the front goes up, down and across in 20 min."""
import re, numpy as np, pyforefire as pyff
L, N = 2000.0, 100
def spread(grade):
with pyff.ForeFireRunner() as ff:
ff["propagationModel"] = "Farsite"; ff["fuelsTable"] = "STDfarsiteFuelsTable"; ff["minSpeed"] = 0
ff.execute(f"FireDomain[sw=(0,0,0);ne=({L},{L},0);t=0]")
ff.addLayer("propagation", "Farsite", "propagationModel")
ff.addIndexLayer("table", "fuel", 0, 0, 0, L, L, 0, np.full((1, 1, N, N), 102, np.int32))
y = (np.arange(N) + 0.5) * L / N
alt = np.broadcast_to((grade * y)[:, None], (N, N))[None, None].copy() # rises to the north
ff.addScalarLayer("data", "altitude", 0, 0, 0, L, L, 0, alt)
for name in ("windU", "windV"): # calm; without a wind layer goTo segfaults
ff.addScalarLayer("data", name, 0, 0, 0, L, L, 0, np.zeros((1, 1, 2, 2)))
ff.execute(f"startFire[loc=({L/2},{L/2},0);t=0]")
ff.execute("goTo[t=1200]")
xy = np.array([[float(c) for c in m.split(",")[:2]] for m in re.findall(r"loc=\(([^)]*)\)", ff.execute("print[]"))])
return dict(north=round(xy[:, 1].max() - L/2), south=round(L/2 - xy[:, 1].min()),
east=round(xy[:, 0].max() - L/2))
for g in (0.0, 0.3, -0.3):
print(f"grade {g:+.1f} (north is {'up' if g > 0 else 'down' if g < 0 else 'flat'}):", spread(g))Drafted with Claude Opus 5.5.
Activity
fix(propagation): Farsite reads ForeFire's m/s wind and slope tangent…
antonio-leblanc commentedon Oct 30, 2026
Merged in #242. @filippi Hugo aligned the Farsite emulation with
mbedward/farsite, as you suggested in #233: wind in m/s and slope only uphill, like the other Rothermel models.