Skip to content

Set up a QUIC-Fire simulation with quicfire-tools

You are viewing in-progress documentation for v2 (Beta). Switch to the stable version for the current production release.

The QUIC-Fire export hands you the fuel and terrain arrays — treesrhof.dat, treesmoist.dat, treesfueldepth.dat, treesss.dat, topo.dat. QUIC-Fire itself needs about fifteen more files before it will run: the .inp input deck that sets winds, ignition, timing, and outputs, plus a gridlist and rasterorigin.txt describing the fire grid. Writing that deck by hand is error-prone; quicfire-tools writes all of it from a few Python calls. This guide builds the deck around a FastFuels export, runs the fire, and reads the outputs back as numpy arrays.

  1. A completed QUIC-Fire export, unzipped. The Create QUIC-Fire simulation inputs tutorial produces exactly this bundle — the .dat files plus a metadata.json describing the fire grid. This guide assumes it’s unzipped into a quicfire_inputs/ directory.

  2. QUIC-Fire 6.1 or later. QUIC-Fire is developed at Los Alamos National Laboratory and distributed on request — it is not on PyPI or GitHub. The distribution ships source plus build scripts; on macOS or Linux, compile with gfortran following the distribution’s Running QUIC-Fire under Mac or Linux guide (the result lands in exe/, e.g. quicfire_MACI.exe).

  3. quicfire-tools: pip install quicfire-tools (Python ≥ 3.8), plus numpy for reading outputs.

Step 1 — Build the input deck around the export

Section titled “Step 1 — Build the input deck around the export”

The export’s metadata.json records the fire-grid cell counts (nx, ny, nz) and cell sizes (dx, dy, dz). The script copies the horizontal resolutions into the deck and verifies that dz is 1 m, as required by QUIC-Fire 6.1.1’s FastFuels reader. It then points QUIC-Fire at the custom .dat files, adds an ignition and outputs, and writes the deck:

build_deck.py
"""Build a QUIC-Fire input deck around a FastFuels export with quicfire-tools."""
import json
import shutil
from pathlib import Path
from quicfire_tools.inputs import SimulationInputs
bundle = Path("quicfire_inputs") # the unzipped FastFuels export
run_dir = Path("qf_run")
run_dir.mkdir(exist_ok=True)
# 1. The export's metadata.json records the fire grid the .dat files sit on.
fire_grid = json.loads((bundle / "metadata.json").read_text())["fire_grid"]
# QUIC-Fire 6.1.1's FastFuels reader requires a uniform 1 m vertical grid.
# Horizontal resolutions can vary, but they must be copied into the deck.
if fire_grid["dz"] != 1.0:
raise ValueError(
"QUIC-Fire 6.1.1 FastFuels inputs require fire_grid.dz == 1.0 m; "
f"this export uses {fire_grid['dz']} m"
)
sim = SimulationInputs.create_simulation(
nx=fire_grid["nx"],
ny=fire_grid["ny"],
fire_nz=fire_grid["nz"],
wind_speed=6.0, # m/s, measured at 6.1 m
wind_direction=270, # degrees the wind blows FROM (270 = out of the west)
simulation_time=1800, # s
)
sim.qu_simparams.dx = fire_grid["dx"]
sim.qu_simparams.dy = fire_grid["dy"]
sim.quic_fire.dz = fire_grid["dz"]
# 2. Point the deck at the FastFuels .dat files.
sim.set_custom_simulation(
fuel_density=True,
fuel_moisture=True,
fuel_height=True,
size_scale=True, # use treesss.dat too
topo=True, # use topo.dat
ignition=False, # keep a simple rectangle ignition (step 3)
interpolate=False, # the files are already on the fire grid
)
# FastFuels .dat files use QUIC-Fire's FastFuels convention (flag 5).
# quicfire-tools writes the Firetec matching-grid convention (flag 3), which
# is not interchangeable and fails for this FastFuels export, so override it.
sim.quic_fire.fuel_density_flag = 5
sim.quic_fire.fuel_moisture_flag = 5
sim.quic_fire.size_scale_flag = 5
# QUIC-Fire requires the wind-solver domain to be at least 3x the terrain
# relief. This export spans 957-1172 m (215 m of relief), so raise the
# default 300 m QUIC domain to 700 m.
sim.qu_simparams.quic_domain_height = 700
# 3. A north-south ignition line near the west (upwind) edge, spanning the
# middle 80% of the domain. Coordinates are meters from the SW corner.
sim.set_rectangle_ignition(
x_min=0.1 * fire_grid["nx"] * fire_grid["dx"],
y_min=0.1 * fire_grid["ny"] * fire_grid["dy"],
x_length=10,
y_length=0.8 * fire_grid["ny"] * fire_grid["dy"],
)
# 4. Outputs: 3-D fuel density and fire energy, and the 2-D burnt-mass map,
# every 300 s of fire time.
sim.set_output_files(fuel_dens=True, mass_burnt=True, eng_to_atm=True)
sim.set_output_interval(300)
# 5. Write the deck and put the FastFuels files next to it.
sim.write_inputs(run_dir)
for name in ("treesrhof.dat", "treesmoist.dat", "treesfueldepth.dat",
"treesss.dat", "topo.dat"):
shutil.copy(bundle / name, run_dir / name)

Five decisions in that script are worth pausing on:

  • fuel_*_flag = 5 (FastFuels convention). set_custom_simulation writes flag 3, the Firetec matching-grid convention. It is not interchangeable with the FastFuels convention: this export uses treesfueldepth.dat for surface fuel-bed depth in the ground layer and requires flag 5 with a uniform 1 m dz. On QUIC-Fire 6.1.1, this case fails at the first fire step under flag 3, so the override matters.
  • quic_domain_height. QUIC-Fire requires its wind-solver domain to be at least 3× the terrain relief. This export’s terrain spans 957 m to 1,172 m — 215 m of relief — so the default 300 m domain fails at startup; 700 m clears it.
  • interpolate=False. The export wrote the .dat files on the fire grid itself, so no re-gridding is needed. (For QUIC-Fire ≤ 6.0 the interpolation flag was also a workaround for a custom-fuels bug; on 6.1+ keep it off when the grids already match.)
  • Wind direction is meteorological — the direction the wind blows from, in degrees clockwise from north. 270 is a west wind pushing the fire east.
  • Ignition coordinates are meters, not cells, measured from the domain’s southwest corner.

write_inputs also writes the gridlist and rasterorigin.txt that QUIC-Fire’s custom-fuel reader requires — you don’t create those yourself.

Copy the compiled executable into the run directory and start it there. Two environment details matter for gfortran builds:

Run the simulation
# From the run directory, next to the .inp deck and .dat files
cd qf_run
cp /path/to/QUIC-Fire/exe/quicfire_MACI.exe .
# gfortran builds keep large automatic arrays on the stack — give the main
# thread and each OpenMP thread room, or the run dies at "Fire time: 1"
ulimit -s 65520
OMP_NUM_THREADS=8 OMP_STACKSIZE=1G ./quicfire_MACI.exe | tee qf_console.log

QUIC-Fire first solves the initial wind field over the terrain, then steps the fire one second at a time, writing outputs into Output/ on the interval you set:

qf_console.log — the run finishing
Allocating 1 threads.
Small cell physics turned on
WARNING: Trees files dz assumed equal to 1 m
Plume time step [s]: 1.0
Initializing fire variables
Percentage of cells with fuel (fire domain): 4.85 %
Percentage of cells with fuel (QU domain): 10.43 %
Writing fire-related output files
Total vegetation mass burnt: 0.00%
Calculating winds for 08/13/2026 15:13:21
Finished interpolating winds
Fire time [s]: 1
Update buoyant plumes
Plume time step 1.00; Plume tot time 1.00
...
Update buoyant plumes
Plume time step 1.00; Plume tot time 1.00
Fire time [s]: 1800
Writing fire-related output files
Total vegetation mass burnt: 33.09%
Update buoyant plumes
Plume time step 1.00; Plume tot time 1.00
Simulation time = 2444 s

By t = 1,800 s this burn has consumed 33.09% of the simulation’s initial modeled fuel mass. This percentage describes the fuel represented in the input arrays, not all vegetation biomass or ecological effects in the real domain. Wall-clock cost scales with the grid and how much is burning — this 1,800 s burn on the 654 × 442 × 33 grid took about 40 minutes on a laptop; scale expectations (and simulation_time) to your case.

The Output/ directory holds compressed binary arrays keyed to the fire grid. SimulationOutputs reads any of them back as numpy arrays:

read_outputs.py
"""Read QUIC-Fire outputs back as numpy arrays with quicfire-tools."""
import json
from pathlib import Path
import numpy as np
from quicfire_tools.outputs import SimulationOutputs
bundle = Path("quicfire_inputs")
fire_grid = json.loads((bundle / "metadata.json").read_text())["fire_grid"]
nz, ny, nx = fire_grid["nz"], fire_grid["ny"], fire_grid["nx"]
dx, dy = fire_grid["dx"], fire_grid["dy"]
outputs = SimulationOutputs("qf_run/Output", nz, ny, nx, dy=dy, dx=dx)
print(outputs.list_outputs())
# 3-D fuel density at each output time
dens = outputs.get_output("fuels-dens")
print(dens.times) # [0, 300, 600, ..., 1800] seconds of fire time
first = dens.to_numpy(timestep=0)[0] # shape (nz, ny, nx)
last = dens.to_numpy(timestep=len(dens.times) - 1)[0]
# Total modeled fuel-mass fraction. Cell volume is uniform, so it cancels.
modeled_mass_consumed = (first.sum() - last.sum()) / first.sum()
print(f"modeled fuel mass consumed: {modeled_mass_consumed:.1%}")
# Column totals -> fraction of modeled fuel consumed in each map cell
initial_by_column = first.sum(axis=0)
final_by_column = last.sum(axis=0)
consumed_by_column = np.where(
initial_by_column > 0.01,
1 - final_by_column / initial_by_column,
np.nan,
)
# 2-D vertically-integrated % of mass burnt, for perimeter maps
mburnt = outputs.get_output("mburnt_integ")
burned_area_m2 = (
(mburnt.to_numpy(timestep=len(mburnt.times) - 1)[0] > 1).sum() * dx * dy
)
print(f"burned area: {burned_area_m2 / 10_000:.1f} ha")

For an explanation of what the consumption and fire-energy arrays show — and what conclusions a single run cannot support — see Interpreting a FastFuels export in QUIC-Fire.

  • Domain is not tall enough. Must be 3. times the maximum terrain elevation. Raise sim.qu_simparams.quic_domain_height to at least three times the relief reported in the message.
  • Segfault right at Fire time [s]: 1 (gfortran builds). The fire step keeps large automatic arrays on the stack. Raise the limits shown in Step 2 (ulimit -s 65520, OMP_STACKSIZE=1G) before blaming the deck.
  • Crash at the first fire step with canopy fuels. You’re running FastFuels .dat files under fuel flag 3. Set the density, moisture, and size-scale flags to 5 as in Step 1.
  • Output directory already exists. QUIC-Fire refuses to overwrite a previous run — remove Output/ before re-running.
  • The fire never grows. Check the ignition rectangle actually overlaps fuel (roads and other masked cells hold zero), and remember the coordinates are meters from the southwest corner, not cell indices.
  • Tune the weather. wind_speed, wind_direction, and the moisture grids in the export are the main levers on fire behavior — rebuild the export with a drier uniform moisture grid or add wind sensors with sim.add_wind_sensor(...) for time-varying winds.
  • Visualize in 3-D. The QUIC-Fire distribution ships post-processing scripts (drawfire.py, PyVistaQF.py) that render the same outputs as images and VTK files for ParaView. The Blue Mountain case study explains the figures produced for this run.
  • Automate the whole loop. The export’s signed_url download, the deck build, and the run are all scriptable — see the tutorial’s single-script pipeline for the FastFuels half.