Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# fireSense_spreadFit (development version)

- New parameter `censoredRunaways` (default `FALSE`, nothing changes; a `FALSE` is left out of the fit's and the threshold calibration's cache keys). `TRUE` passes `fireSenseUtils::.objfunSpreadFit(censoredRunaways = TRUE)` to the fit, its re-score and the calibration: a simulated fire that reaches its buffer's edge ring is right-censored at the size it burned (it keeps burning inside its buffer, so its size is buffer-bounded exactly as the observed fire is; the size likelihood fits the replicates with it as 'at least this big'; the Anderson-Darling, annual-area and area-distribution terms score the size it burned, and `runawayBufferMultiple` is not used). Why: scoring a runaway as 2 x its buffer made the objective prefer `yearSpreadSD = 0` whatever the true year effect (known-sigma test on ELF 14.3, 2026-10-17). Needs fireSenseUtils with `censoredRunaways`.
- New parameter `fitFileSuffix` (default `""`), the same in `fireSense_ignitionFit`, `fireSense_dataPrepFit` and `fireSense_ELFs` (checked with `paramCheckOtherMods()`): with `spreadFitFilename = "latest"` the fit is written to `fireSenseUtils::spreadFitFilenameFor(fireYears, fitFileSuffix)`, so a test run (`.globals = list(fitFileSuffix = "_boundfix_rep81")`) never writes to the production ledger file. `""` is unchanged. Needs fireSenseUtils (>= 0.2.3.9104).
- New parameter `convergence` (default `NULL`): the DEoptim early-stop rule, a list with any of `window`, `minGenerations`, `se`, `sdChange`. It is resolved once to the full list (the parameter, else the `clusters.deoptim*` options, else clusters' defaults) and passed to `runDEoptim()` inside the fit's `Cache()` call, so changing the parameter or the options changes the fit's cache key; clusters keeps it out of each generation's key, so a fit that stopped early continues from its cached generations under a stricter rule. Fits cached before this re-key once. Floors `fireSenseUtils` 0.2.3.9102 and `clusters` 0.0.80 (Depends-on: PredictiveEcology/fireSenseUtils#162, PredictiveEcology/clusters#55).
- The fit's and the threshold calibration's cache keys now include `spreadProbFromIntegerCovs()`, `spreadProbCovsOfYear()` and `dataYearBefore()`, so a change in which vegetation table a fire year reads is a cache miss and a threshold cached as `NA` under the old rule is not reused. A calibration whose every trial errored now stops with the first trial's error (`stopIfAllTrialsErrored()`) instead of returning `NA` (then `Inf`) to be cached. `fireSenseUtils` floor 0.2.3.9100, whose objective reads the table of the latest data year strictly before the fire year, as `fireSense_dataPrepFit` builds them (Depends-on: PredictiveEcology/fireSenseUtils#159). Spread-fit input tables must be named by data year.
Expand Down
12 changes: 9 additions & 3 deletions R/fitSpread.R
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ fitSpread <- function(sim, covs, thresh, runName, diagnostics = TRUE) {
runawayEdgeFrac = P(sim)$runawayEdgeFrac,
runawayEdgeMin = P(sim)$runawayEdgeMin,
runawayBufferMultiple = runawayMultiple,
censoredRunaways = censoredRunawaysOrNULL(P(sim)$censoredRunaways),
profileReps = if (diagnostics) P(sim)$profileReps else 0L,
simulateMembers = if (diagnostics) P(sim)$simulateMembers else 0L),
.functionName = fnName,
Expand All @@ -93,10 +94,11 @@ fitSpread <- function(sim, covs, thresh, runName, diagnostics = TRUE) {
## runawayEdgeFrac/Min only change how quickly DEoptim moves away from an unlucky draw, not what a
## fit means, so fits cached under the 1-cell rule stay valid and must not rerun.
omitArgs = c(".verbose", "cores", "paths", "logPath", "plotEvery", "progressFile", "runawayEdgeFrac", "runawayEdgeMin",
## NULL without an intercept (or with runawayBufferMultiple NA): omitted then, so fits
## cached before it existed are found
## NULL without an intercept (or with runawayBufferMultiple NA, censoredRunaways FALSE): omitted
## then, so fits cached before it existed are found
fireSenseUtils::omitNullArgs(covCentre = sim$covCentre_spread,
runawayBufferMultiple = runawayMultiple)),
runawayBufferMultiple = runawayMultiple,
censoredRunaways = censoredRunawaysOrNULL(P(sim)$censoredRunaways))),
useCache = P(sim)$useCache_DE
)
## 1e6 is the objective's fail value (fireSenseUtils::.objfunSpreadFit). A population that is all
Expand Down Expand Up @@ -126,6 +128,10 @@ escapeSizeHaOrNULL <- function(x) if (length(x) && !is.na(x)) x
## burned) for NULL or NA
runawayBufferMultipleOrNULL <- escapeSizeHaOrNULL

## `censoredRunaways` as `runDEoptim()` and the calibration take it: TRUE, or NULL (not FALSE) so that it is left out
## of their cache keys and fits made before it existed are found
censoredRunawaysOrNULL <- function(x) if (isTRUE(x)) TRUE

#' Turn the fit's re-score, profile and simulations into `sim` objects
#'
#' @param sim a `simList`.
Expand Down
5 changes: 4 additions & 1 deletion R/runSpreadWithoutDEoptim.R
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
#' @param mode character; if it includes "debug", the debug branch runs.
#' @param seed integer or NULL; `set.seed()` value. NULL draws one at random.
#' @param escapeSizeHa passed to `.objfunSpreadFit()`: fit escaped fires only (NULL: any fire over 1 pixel).
#' @param sizeLik,sizeLikDf,adWeight,link,jumpTries,jumpMeanDist,yearAreaWeight,areaDistWeight,penaliseRunaways,runawayEdgeFrac,runawayEdgeMin,runawayBufferMultiple passed to
#' @param sizeLik,sizeLikDf,adWeight,link,jumpTries,jumpMeanDist,yearAreaWeight,areaDistWeight,penaliseRunaways,runawayEdgeFrac,runawayEdgeMin,runawayBufferMultiple,censoredRunaways passed to
#' `.objfunSpreadFit()`, so the threshold is calibrated on the objective the fit uses. The defaults are
#' that function's.
#' @return the calibrated threshold (numeric, or NA if no trial was usable); NULL in "debug" mode.
Expand All @@ -53,6 +53,7 @@ runSpreadWithoutDEoptim <- function(iterThresh, lower, upper, fireSense_spreadFo
runawayEdgeFrac = fireSenseUtils::fireSenseRunawayEdgeFrac,
runawayEdgeMin = fireSenseUtils::fireSenseRunawayEdgeMin,
runawayBufferMultiple = NULL,
censoredRunaways = NULL,
thresholdMargin = 2, maxDrawsPerTrial = 100) {
## The threshold this returns becomes `thresh` in runDEoptim(), so it is part of every cached
## DEoptim generation's key. With a seed drawn here, a single cache miss on the estimateThreshold
Expand Down Expand Up @@ -115,6 +116,7 @@ runSpreadWithoutDEoptim <- function(iterThresh, lower, upper, fireSense_spreadFo
penaliseRunaways = penaliseRunaways,
runawayEdgeFrac = runawayEdgeFrac, runawayEdgeMin = runawayEdgeMin,
runawayBufferMultiple = runawayBufferMultiple,
censoredRunaways = censoredRunaways,
plot.it = plot.it
)
}
Expand Down Expand Up @@ -189,6 +191,7 @@ runSpreadWithoutDEoptim <- function(iterThresh, lower, upper, fireSense_spreadFo
yearAreaWeight = yearAreaWeight, areaDistWeight = areaDistWeight,
penaliseRunaways = penaliseRunaways,
runawayBufferMultiple = runawayBufferMultiple,
censoredRunaways = censoredRunaways,
plot.it = FALSE)
)
})
Expand Down
19 changes: 16 additions & 3 deletions fireSense_spreadFit.R
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,17 @@ defineModule(sim, list(
"not capped at a size; spread is bounded by the buffers. FALSE scores the simulated",
"size in the likelihood too. Passed to",
"`fireSenseUtils::runDEoptim()`; the threshold calibration uses the same setting.")),
defineParameter("censoredRunaways", "logical", default = FALSE,
desc = paste("With `penaliseRunaways`, TRUE treats a runaway replicate as right-censored at the size",
"it burned (`fireSenseUtils::.objfunSpreadFit(censoredRunaways =)`): it keeps burning",
"inside its buffer, so its size is buffer-bounded exactly as the observed fire is; the",
"size likelihood fits the replicates with it as 'at least this big' (censored-normal",
"maximum likelihood, `sizeLik = 't'` only); the Anderson-Darling, annual-area and",
"area-distribution terms score the size it burned, and `runawayBufferMultiple` is not",
"used. FALSE (default) keeps the scoring above. Why: scoring a runaway as 2 x its buffer",
"made the objective prefer no year effect whatever the true one (known-sigma test, ELF",
"14.3, 2026-10-17). Passed to `fireSenseUtils::runDEoptim()` and the threshold",
"calibration; left out of their cache keys when FALSE.")),
defineParameter("runawayBufferMultiple", "numeric", default = 2,
desc = paste("With `penaliseRunaways`, a runaway counts in the size-based terms of the objective",
"(Anderson-Darling, annual area, area-weighted size distribution) as this multiple of",
Expand Down Expand Up @@ -981,6 +992,7 @@ estimateSNLLThresholdPostLargeFires <- function(sim, covs) {
penaliseRunaways = P(sim)$penaliseRunaways,
runawayEdgeFrac = P(sim)$runawayEdgeFrac, runawayEdgeMin = P(sim)$runawayEdgeMin,
runawayBufferMultiple = runawayMultiple,
censoredRunaways = censoredRunawaysOrNULL(P(sim)$censoredRunaways),
thresholdMargin = P(sim)$thresholdMargin,
maxFireSpread = P(sim)$maxFireSpread) |>
## Nothing is omitted from the key, because both of the arguments that used to
Expand Down Expand Up @@ -1011,11 +1023,12 @@ estimateSNLLThresholdPostLargeFires <- function(sim, covs) {
spreadProbGates = deparse(fireSenseUtils::spreadProbGates),
spreadProbGateTest = deparse(fireSenseUtils::spreadProbGateTest),
objective = objectiveBodies()),
## covCentre is NULL without an intercept, runawayBufferMultiple with NA: omitted then, so
## thresholds cached before they existed are found
## covCentre is NULL without an intercept, runawayBufferMultiple with NA, censoredRunaways with
## FALSE: omitted then, so thresholds cached before they existed are found
omitArgs = c("runawayEdgeFrac", "runawayEdgeMin",
fireSenseUtils::omitNullArgs(covCentre = sim$covCentre_spread,
runawayBufferMultiple = runawayMultiple)))
runawayBufferMultiple = runawayMultiple,
censoredRunaways = censoredRunawaysOrNULL(P(sim)$censoredRunaways))))
} else {
P(sim)$SNLL_FS_thresh
}
Expand Down
2 changes: 1 addition & 1 deletion tests/testthat/test-metadata.R
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ test_that("parameters are the expected names", {
"heldOutFold", "initialpop", "iterDEoptim", "iterThresh", "thresholdMargin", "libPathDEoptim",
"link", "lower", "maxFireSpread", "mode", "mutuallyExclusiveCols", "nCoresNeeded",
"objFunCoresInternal", "objfunFireReps", ".rep", "adWeight", "profileReps",
"simulateMembers", "sizeLik", "sizeLikDf", "escapeSizeHa", "yearAreaWeight", "areaDistWeight", "penaliseRunaways", "runawayEdgeFrac", "runawayEdgeMin", "runawayBufferMultiple",
"simulateMembers", "sizeLik", "sizeLikDf", "escapeSizeHa", "yearAreaWeight", "areaDistWeight", "penaliseRunaways", "runawayEdgeFrac", "runawayEdgeMin", "runawayBufferMultiple", "censoredRunaways",
"jumpTries", "jumpMeanDist", "upperTailBounds", "weighted", "yearSpreadSDBounds",
"refitExisting", "rescaleAll", "SNLL_FS_thresh", "fitFileSuffix", "spreadFitFilename",
"spreadFitGoogleDriveFolder", "stopIfNoPreRunFit", "strategy", "trace",
Expand Down
3 changes: 2 additions & 1 deletion tests/testthat/test-metadataContract.R
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ test_that("parameter classes", {
link = "character", profileReps = "integer", simulateMembers = "integer",
sizeLik = "character", sizeLikDf = "numeric", escapeSizeHa = "numeric", weighted = "logical|character",
adWeight = "character|numeric", yearAreaWeight = "numeric|character",
areaDistWeight = "numeric|character", penaliseRunaways = "logical", runawayEdgeFrac = "numeric", runawayEdgeMin = "integer", runawayBufferMultiple = "numeric", jumpTries = "numeric", jumpMeanDist = "numeric",
areaDistWeight = "numeric|character", penaliseRunaways = "logical", runawayEdgeFrac = "numeric", runawayEdgeMin = "integer", runawayBufferMultiple = "numeric", censoredRunaways = "logical", jumpTries = "numeric", jumpMeanDist = "numeric",
upperTailBounds = "numeric", yearSpreadSDBounds = "numeric",
verbose = "numeric", visualizeDEoptim = "Path"))
)
Expand Down Expand Up @@ -71,6 +71,7 @@ test_that("the runaway rule's parameters exist with their defaults", {
expect_identical(def$runawayEdgeFrac, 0.01)
expect_identical(def$runawayEdgeMin, 3L)
expect_identical(def$runawayBufferMultiple, 2)
expect_identical(def$censoredRunaways, FALSE)
})

test_that("numeric defaults", {
Expand Down
29 changes: 29 additions & 0 deletions tests/testthat/test-objectiveSettings.R
Original file line number Diff line number Diff line change
Expand Up @@ -117,3 +117,32 @@ test_that("penaliseRunaways = FALSE reaches the fit, the calibration and the obj
logical(1))))
expect_true(all(vapply(seen, function(a) identical(a$penaliseRunaways, FALSE), logical(1))))
})

test_that("censoredRunaways reaches the fit, the calibration and the objective as TRUE; FALSE arrives as NULL (cache keys unchanged)", {
for (value in list(TRUE, FALSE)) {
rec <- new.env(); fitRec <- new.env()
sim <- toySim(list(SNLL_FS_thresh = NULL, censoredRunaways = value))
mockFitAndLedger(sim, fitRec)
mockInModule(sim, runSpreadWithoutDEoptim = recordingRSWD(rec, 777))
sim <- suppressMessages(SpaDES.core::spades(sim))
expected <- if (value) TRUE else NULL
expect_identical(rec$args$censoredRunaways, expected)
expect_identical(fitRec$deArgs$censoredRunaways, expected)
}
expect_null(formals(runSpreadWithoutDEoptim)$censoredRunaways)
seen <- list()
local_mocked_bindings(.objfunSpreadFit = function(par, thresh, ...) {
seen[[length(seen) + 1L]] <<- list(...)
1000 + thresh
})
land <- terra::rast(nrows = 10, ncols = 10, xmin = 0, xmax = 2400, ymin = 0, ymax = 2400)
fires <- list(year2001 = data.frame(size = c(5L, 12L), date = "year2001", ids = 11:12, cells = c(3L, 56L)))
suppressMessages(utils::capture.output(runSpreadWithoutDEoptim(
iterThresh = 3L, lower = c(a = 0), upper = c(a = 1), fireSense_spreadFormula = "~ 0 + a",
flammableRTM = land, annualDTx1000 = list(), nonAnnualDTx1000 = list(), fireBufferedListDT = list(),
historicalFires = fires, covMinMax = NULL, objfunFireReps = 2L, maxFireSpread = 0.28,
tests = "SNLL_FS", formulaToFit = "~ 0 + a", mode = "debug", seed = 1L, escapeSizeHa = 50, doObjFunAssertions = FALSE,
censoredRunaways = TRUE)))
expect_true(length(seen) > 0)
expect_true(all(vapply(seen, function(a) isTRUE(a$censoredRunaways), logical(1))))
})
Loading