using Elmfire
using PlotsSuppression
Fire containment and resource management
This tutorial covers fire suppression modeling in Elmfire.jl.
Suppression Resources
Elmfire supports different resource types:
# Create different resource types
hand_crew = SuppressionResource{Float64}(1, :hand_crew; location_x = 10.0, location_y = 10.0)
engine = SuppressionResource{Float64}(2, :engine; location_x = 15.0, location_y = 10.0)
dozer = SuppressionResource{Float64}(3, :dozer; location_x = 20.0, location_y = 10.0)
aircraft = SuppressionResource{Float64}(4, :aircraft; location_x = 50.0, location_y = 50.0)
for r in [hand_crew, engine, dozer, aircraft]
println("$(r.resource_type): Production rate = $(r.line_production_rate) ft/min, Width = $(r.effective_width) ft")
endhand_crew: Production rate = 2.5 ft/min, Width = 6.0 ft
engine: Production rate = 5.0 ft/min, Width = 10.0 ft
dozer: Production rate = 20.0 ft/min, Width = 15.0 ft
aircraft: Production rate = 100.0 ft/min, Width = 100.0 ft
Creating Suppression State
ncols, nrows = 100, 100
supp_state = SuppressionState{Float64}(ncols, nrows)
# Add resources
add_resource!(supp_state, hand_crew)
add_resource!(supp_state, engine)
add_resource!(supp_state, dozer)
println("Total resources: $(length(supp_state.resources))")Total resources: 3
Resource Assignment
Assign resources to build containment lines:
# Assign dozer to build line from (30,30) to (30,70)
targets = [(30, y) for y in 30:5:70]
assign_resource!(supp_state, 3, targets)
# Check assignment
println("Dozer status: $(supp_state.resources[3].status)")
println("Assignment targets: $(length(supp_state.active_assignments[3])) cells")Dozer status: deployed
Assignment targets: 9 cells
Building Containment Lines
fire_state = FireState{Float64}(ncols, nrows, 30.0)
# Manually construct a line
cells_built, length_built = construct_containment_line!(
supp_state,
supp_state.resources[3], # Dozer
30, 30, # Start
30, 50, # Target
20.0, # Time (minutes)
30.0, # Cell size
0.0 # Current time
)
println("Built $(length(cells_built)) cells")
println("Length: $(round(length_built, digits=0)) ft")
# Visualize
heatmap(supp_state.contained_cells',
title = "Containment Line",
color = [:white, :blue],
aspect_ratio = 1
)Built 14 cells
Length: 420.0 ft
Containment Effectiveness
Containment lines reduce fire spread:
# Check effectiveness at contained cells
ix, iy = cells_built[1]
eff = supp_state.containment_effectiveness[ix, iy]
println("Effectiveness at ($ix, $iy): $(round((1-eff)*100, digits=0))% reduction")Effectiveness at (30, 30): 95.0% reduction
Simulation with Suppression
ncols, nrows = 100, 100
cellsize = 30.0
fire_state = FireState{Float64}(ncols, nrows, cellsize)
supp_state = SuppressionState{Float64}(ncols, nrows)
fuel_table = create_standard_fuel_table(Float64)
fuel_ids = fill(1, ncols, nrows)
slope = zeros(Float64, ncols, nrows)
aspect = zeros(Float64, ncols, nrows)
weather = ConstantWeather{Float64}(
wind_speed_mph = 10.0,
wind_direction = 270.0,
M1 = 0.06, M10 = 0.08, M100 = 0.10,
MLH = 0.60, MLW = 0.90
)
# Add a dozer
dozer = SuppressionResource{Float64}(1, :dozer; location_x = 70.0, location_y = 30.0)
add_resource!(supp_state, dozer)
# Pre-build containment line ahead of fire
for y in 30:70
supp_state.contained_cells[70, y] = true
supp_state.containment_effectiveness[70, y] = 0.1 # 90% reduction
end
# Ignite fire
ignite!(fire_state, 40, 50, 0.0)
# Run simulation with suppression
simulate_with_suppression!(
fire_state,
supp_state,
fuel_ids,
fuel_table,
weather,
slope,
aspect,
0.0, 45.0
)
# Visualize
p1 = heatmap(fire_state.burned',
title = "Fire with Containment Line",
color = :YlOrRd, aspect_ratio = 1)
# Overlay containment line
contained_x = [ix for ix in 1:ncols for iy in 1:nrows if supp_state.contained_cells[ix, iy]]
contained_y = [iy for ix in 1:ncols for iy in 1:nrows if supp_state.contained_cells[ix, iy]]
scatter!(p1, contained_x, contained_y, color = :blue, markersize = 2, label = "Containment")
p1Comparing With and Without Suppression
# Without suppression
state_no_supp = FireState{Float64}(ncols, nrows, cellsize)
ignite!(state_no_supp, 40, 50, 0.0)
simulate_uniform!(state_no_supp, 1, fuel_table, weather, 0.0, 0.0, 0.0, 45.0)
# With suppression (already run above as fire_state)
p1 = heatmap(state_no_supp.burned',
title = "No Suppression\n$(round(get_burned_area_acres(state_no_supp), digits=1)) acres",
color = :YlOrRd, aspect_ratio = 1)
p2 = heatmap(fire_state.burned',
title = "With Containment Line\n$(round(get_burned_area_acres(fire_state), digits=1)) acres",
color = :YlOrRd, aspect_ratio = 1)
plot(p1, p2, layout = (1, 2), size = (800, 400))Suppression Statistics
stats = get_suppression_statistics(supp_state)
println("Suppression Summary:")
println(" Total resources: $(stats.total_resources)")
println(" Available: $(stats.available)")
println(" Deployed: $(stats.deployed)")
println(" Resting: $(stats.resting)")
println(" Contained cells: $(stats.contained_cells)")
println(" Total line: $(round(stats.total_line_feet, digits=0)) ft ($(round(stats.total_line_miles, digits=2)) miles)")Suppression Summary:
Total resources: 1
Available: 1
Deployed: 0
Resting: 0
Contained cells: 41
Total line: 0.0 ft (0.0 miles)
Tactical Planning
Plan indirect attack ahead of fire:
# Get current fire state
state = FireState{Float64}(ncols, nrows, cellsize)
for ix in 35:45, iy in 45:55
state.burned[ix, iy] = true
end
# Plan indirect line 5 cells ahead
line_cells = plan_indirect_attack(state, weather, 5)
println("Planned $(length(line_cells)) cells for indirect attack")Planned 19 cells for indirect attack
Direct Attack
Target the perimeter directly:
perimeter = plan_direct_attack(state)
println("Perimeter has $(length(perimeter)) cells for direct attack")Perimeter has 40 cells for direct attack