
Calibrating the Dynamic Fire System Extension
Dynamic-Fire-Calibration.RmdScope
This vignette covers the calibration of the LANDIS-II Dynamic
Fire System extension – adjusting
SeverityCalibrationFactor, the per-season FMC
HiProp values, and per-base-fuel-type IgnProb
multipliers so simulated fires match observed regional fire
statistics.
It does not cover the empirical fits already done at
the data layer (Mu, Sigma, Max,
NumFires, seasonal PropFire) – those come from
NFDB lognormal MLE / count summaries and don’t need an optimisation
loop.
Function families
The calibration is built from three layers:
| Layer | Purpose | Functions |
|---|---|---|
| Observation | Pre-compute observed targets from NFDB |
save_observed_fire_targets() (+
bc_fuel_code_to_base() default; pass your own if fuel codes
differ) |
| Scenario | Build a static-landscape calibration scenario (post-spinup IC; succession frozen; dynamic-fire baseline) |
build_calibration_spinup_scenario(),
run_calibration_spinup(),
build_calibration_scenario_template()
|
| Optimiser | Run DEoptim against the calibration scenario, applying patches per trial |
patch_fire_config(), loss_from_stats(),
parse_dynamic_fire_logs(), sim_landis() (or
sim_mock() for testing),
calibrate_dynamic_fire()
|
Once calibrated, the best parameter vector flows back to a production
fire config via apply_calibrated_ignprob() +
apply_calibrated_hi_prop() and the
SeverityCalibrationFactor scalar.
Required project-side inputs
The library is project-agnostic but expects you to provide:
-
NFDB-style SpatVectors clipped to your primary fire
regime (and optionally a secondary, broader regime for context) – with
YEARandSIZE_HAcolumns on the points. -
A fuel-type raster covering your LANDIS simulation
extent, with a mapping from integer codes to base fuel types
(
Conifer,ConiferPlantation,Deciduous,Slash,Open). Usebc_fuel_code_to_base()if you’re working with BCFUEL_TYPE_CDfactor levels. - A production scenario directory with ForCS / Dynamic Fuels / Dynamic Fire configs already written. The calibration scenario template is built by copying this and patching.
-
A spun-up IC: produced by
build_calibration_spinup_scenario()+run_calibration_spinup()– one LANDIS-II run during pipeline build.
End-to-end function sketch
The functions are designed to be composed inside a project’s
targets pipeline (or any other workflow framework). Below
is the typical wiring; each numbered block is one or more
tar_target() calls in practice.
## 1. Observed fire targets (NFDB-derived; cheap; no LANDIS-II).
save_observed_fire_targets(
primary_points = nfdb_point_fru59,
primary_polys = nfdb_poly_fru59,
secondary_points = nfdb_point_frt12, ## optional regional context
secondary_polys = nfdb_poly_frt12,
fire_years = 1950L:2023L,
fuel_types_rast = fuel_types_rast,
path = "outputs/calibration/observed_fire_targets.rds",
primary_label = "FRU59",
secondary_label = "FRT12"
## fuel_code_to_base defaults to bc_fuel_code_to_base(); override if needed
)
## 2. Pre-calibration spinup: one LANDIS-II run with both ForCS spinup flags ON
## to populate biomass; snapshot via the Output Biomass Community extension.
## The snapshot's year-0 CSV+TIF become the calibration IC.
build_calibration_spinup_scenario(
out_dir = "LANDIS-II/_calibration_spinup",
template_dir = "LANDIS-II/phase_2_ICH", ## any non-fire scenario will do
duration = 1L,
cell_length = 100L
)
spinup_csv <- run_calibration_spinup(
scenario_dir = "LANDIS-II/_calibration_spinup",
base_seed = 12345L,
method = "docker"
)
spinup_tif <- file.path(dirname(spinup_csv), "output-community-0.tif")
## 3. Calibration scenario template: copy from a production fire scenario, swap
## in the spinup IC, patch ForCS to skip succession, write a fresh baseline
## dynamic-fire.txt from the supplied tables.
build_calibration_scenario_template(
out_dir = "LANDIS-II/_calibration",
template_dir = "LANDIS-II/phase_2_ICH_fire",
snapshot_ic_csv = spinup_csv,
snapshot_ic_tif = spinup_tif,
baseline_fire_size_table = fire_size_table,
baseline_fuel_type_table = defaultFuelTypeTable(),
baseline_fire_damage_table = defaultFireDamageTable(),
baseline_seasons_sim_table = seasons_sim_table,
sim_years = 10L,
cell_length = 100L
)
## 4. DEoptim driver: starts a warm Docker pool, runs the calibration, tears
## down on exit. Returns best_params (a named numeric of length 9) + the
## DEoptim result object + a trace CSV.
cfg <- list(
lower = c(
SeverityCalibrationFactor = 0.5,
SpHiProp = 0, SumHiProp = 0, FallHiProp = 0,
IgnProb_Conifer = 0, IgnProb_ConiferPlantation = 0,
IgnProb_Deciduous = 0, IgnProb_Slash = 0, IgnProb_Open = 0
),
upper = c(
SeverityCalibrationFactor = 2.5,
SpHiProp = 1, SumHiProp = 1, FallHiProp = 1,
IgnProb_Conifer = 1.5, IgnProb_ConiferPlantation = 1.5,
IgnProb_Deciduous = 1.5, IgnProb_Slash = 1.5, IgnProb_Open = 1.5
),
NP = 60L, itermax = 100L, n_reps = 5L, sim_years = 10L,
weights = c(count = 1, size = 1, area_fuel = 0, severity = 0),
simulator = "landis", method = "docker",
n_cores = max(1L, parallel::detectCores() - 2L), parallel = TRUE,
base_seed = 12345L
)
result <- calibrate_dynamic_fire(
observed_targets_path = "outputs/calibration/observed_fire_targets.rds",
scenario_template = "LANDIS-II/_calibration/scenario.txt",
cfg = cfg,
out_dir = "outputs/calibration"
)
print(result$best_params)
## 5. Apply calibrated params back to the production fire config (e.g., when
## writing dynamic-fire.txt for the production scenarios).
calibrated <- result$best_params
production_fuel_type_table <- apply_calibrated_ignprob(
defaultFuelTypeTable(), calibrated
)
production_fire_size_table <- apply_calibrated_hi_prop(
fire_size_table, calibrated
)
production_severity_factor <- calibrated[["SeverityCalibrationFactor"]]Wiring in targets
The library doesn’t ship a target factory (deliberate – target naming
conventions vary across consuming projects). Adopt the helpers as plain
function calls inside tar_target(). Three things to watch
for:
Cycle avoidance. If your production
tar_targetthat writesdynamic-fire.txtconsumescalibrated_fire_params(the result ofcalibrate_dynamic_fire()), then the calibration’s scenario template must NOT transitively depend on that target. Usebuild_calibration_scenario_template()’sbaseline_*arguments to write a fresh uncalibrated dynamic-fire.txt inline, and depend on the per-extension file targets that populate your production scenario directory directly, NOT on your scenario-assembly target.Deployment mode. The calibration target should run on the main process (
deployment = "main"intargets), not on acrewworker, because the function internally manages its own FORK cluster ofn_coresworkers + a warm Docker pool ofn_corescontainers. A crew worker trying to spawn its own cluster would compete for cores.DEoptim is a Suggest. Install via
renv::install("DEoptim")before running the calibration target.
Per-trial cost and lever knobs
sim_landis() per-trial cost is dominated by the
LANDIS-II Docker run (~1-3 minutes including dotnet startup). Total
wall-clock for production calibration: roughly
NP * itermax * n_reps * per-trial / n_cores. The warm
Docker pool (started internally by
calibrate_dynamic_fire()) saves container-creation overhead
but not dotnet startup, so the dominant cost remains the LANDIS-II
simulation itself. Practical levers if calibration takes too long:
- Cut
sim_yearsto 5 (instead of 10): roughly half the per-trial cost. - Aggregate
fuel_types_rastto a coarser resolution (e.g., 200 m cells) before passing tobuild_calibration_scenario_template(): ~4x speedup. - Reduce
NP×itermax; DEoptim’s evolutionary search compensates somewhat for fewer evaluations.
A pure-R reimplementation (sim_r_reimpl(), currently a
stub) would remove the dotnet bottleneck entirely but introduces its own
maintenance burden tracking LANDIS-II’s evolving algorithm. See the
design notes in _tmp_dynamic_fire_calibration.md of the
gitanyow-partial-harvest project.
Testing without Docker
sim_mock() returns plausibly-shaped per-trial output
without invoking LANDIS-II, suitable for end-to-end testing of the
DEoptim driver’s control flow:
cfg$simulator <- "mock"
cfg$NP <- 6L
cfg$itermax <- 2L
cfg$parallel <- FALSE
calibrate_dynamic_fire(
observed_targets_path = "outputs/calibration/observed_fire_targets.rds",
scenario_template = "LANDIS-II/_calibration/scenario.txt",
cfg = cfg,
out_dir = tempdir()
)The mock varies its output with par_vec so DEoptim sees
a non-trivial loss surface (good for catching driver-wiring
regressions). It is NOT a substitute for the real LANDIS-II simulator –
the parameter optima under sim_mock() are not
meaningful.
See also
-
vignette("Dynamic-Fire-Fuels", package = "landisutils")– preparing the Dynamic Fire and Dynamic Fuels extension inputs in the first place. -
?DynamicFire– the R6 class that backs the uncalibrated baseline config writer used insidebuild_calibration_scenario_template(). -
?landis_pool_start– the warm Docker pool thatcalibrate_dynamic_fire()starts internally; also usable standalone.