Skip to contents

Combines per-replicate parse_dynamic_fire_logs() outputs into the multi- component weighted loss against observed targets from save_observed_fire_targets().

Usage

loss_from_stats(
  reps,
  observed,
  weights = c(count = 1, size = 1, size_tail = 1, area_fuel = 0, severity = 0),
  severity_weighting = getOption("landisutils.calibration.severity_weighting", "event")
)

Arguments

reps

List. Each element is the return value of parse_dynamic_fire_logs() for one replicate.

observed

List. Output of save_observed_fire_targets(). Must contain $primary (or $fru59 back-compat alias) with $lambda_obs, $n_fires_by_year, $fire_sizes_ha. May contain $primary$area_by_fuel_ha, $primary$severity_dist, $fuel_code_to_base, and $pixel_area_ha to activate Tier 2 components.

weights

Named numeric vector. Components: count, size, size_tail, area_fuel, severity, mortality, area_burned. Missing components default to 0, so an existing caller's weights keep their meaning when a component is added.

severity_weighting

One of "event", "area" or "cell": what one unit of the simulated severity distribution is. "event" gives every fire one vote regardless of size and is the default, for backwards compatibility; the observed reference is a share of burned AREA, so it is the setting that compares unlike quantities. "area" weights each fire's mean severity by the area it burned. "cell" uses the severity of every burned cell and so also drops the per-fire averaging, but it requires replicates parsed with by_class = TRUE and errors without them. Defaults to getOption("landisutils.calibration.severity_weighting", "event").

Value

Named list with total (the scalar minimised by DEoptim), components (per-component contributions), weights (echoed weights), and severity_weighting (how the severity component was counted).

Details

Components:

  • L_count = |mean(n_fires_sim) - lambda_obs| / sd(n_fires_obs) – annual-rate match against the primary ecoregion target.

  • L_size = KS_D(empirical CDF of sim sizes, empirical CDF of obs sizes) – shape match for the fire-size distribution.

  • L_area_fuel: chi-squared distance between simulated and observed burn-area-by-base-fuel-type proportions. Simulated area comes from each replicate's area_by_fuel_ha, whose base column parse_dynamic_fire_logs() takes from the run's own FuelTypeTable. A replicate parsed before landisutils 0.0.168 has no base column; its fuel_code is decoded as fuel-type index fuel_code - 1 through defaultFuelTypeTable(), with a warning. When a replicate with fires has no area_by_fuel_ha at all (mock simulator, Dynamic Fuels off), each event's burned cells are attributed to its ignition fuel-type index instead, also through defaultFuelTypeTable(). Observed area is the base column of observed$primary$area_by_fuel_ha; observed$fuel_code_to_base describes the observed raster only and is never applied to simulated fuel types. Skipped (contributes 0) when either observed$primary$area_by_fuel_ha is NULL or observed$fuel_code_to_base is missing.

  • L_severity: chi-squared distance between simulated and observed severity-class proportions. Simulated severities come from each event's MeanSeverity binned into integer classes 1..5; observed comes from observed$primary$severity_dist (a 5-element named numeric vector summing to 1). Skipped when observed is NULL.

  • L_mortality = |mean(share_sim) - share_obs| / share_obs – the share of burned area that lost its dominant cohort, against the same share observed. Contributes 0 when observed$primary$mortality_share is NULL or NA, or when no replicate kept the severity maps it is measured from.

  • L_area_burned = |log10(area_sim / area_obs)| – annual area burned, simulated against observed. No other component scores how much area burns: L_area_fuel scores how burned area is distributed across base fuel types, not how much there is. A log10 ratio keeps it scale-free, so the same weight means the same thing on study areas whose burn rates differ by orders of magnitude. Simulated area is summed from each replicate's events over the years L_count scores, converted with observed$pixel_area_ha. Observed area is lambda_obs * mean(fire_sizes_ha) – the count target's own annual rate times the size sample's mean fire size, NOT sum(fire_sizes_ha) / n_years. The two agree only when the size sample is every fire in the area and years the counts cover; a payload that borrows per-fire sizes from a wider region than it counts ignitions in would otherwise be scored against that wider region's annual area. Contributes 0 when fire_sizes_ha is empty or lambda_obs is not positive, and .AREA_BURNED_NO_FIRE (3.0) when a replicate set burns nothing, since log10(0) would be infinite and DEoptim cannot rank an infinite objective.

        This term and `count` both move with the number of fires, so they
        compete for the same lever wherever a calibration scales ignition
        rates. `count` is normalised by the observed year-to-year standard
        deviation, making it steeper by about
        `lambda_obs / sd(n_fires_obs) * ln(10)`; keep
        `weights["area_burned"]` well below `weights["count"]` times that
        factor, or the fitted fire count is pulled off its own target to
        compensate for a fire-size distribution the search cannot change.
        **Compute that factor for your own record rather than assuming it is
        large.** It is roughly 3.4 on a record averaging 27.8 fires per year
        with a standard deviation of 18.9, but only 1.6 on a sparse record
        averaging 0.87 fires per year, because a record whose counts are
        nearly Poisson has a small standard deviation to divide by. The
        sparser the fire record, the less headroom this component has.

All component values are unitless and non-negative; chi-squared components use a small epsilon in the denominator to avoid division by zero on empty observed bins.