Skip to content

Farsite: downhill spread is boosted as much as uphill #231

Closed

Description

@HugoFara

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.adjustementWind is 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

  1. antonio-leblanc commented on Oct 30, 2026

    @antonio-leblanc
    Collaborator

    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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions