From 2c4cad9b03d41abd19c0e20f9fcab1f1867dbfc9 Mon Sep 17 00:00:00 2001 From: Eliot McIntire Date: Tue, 29 Sep 2026 11:05:33 -0700 Subject: [PATCH 1/4] Name the renamed sibling modules fireSense is now fireSense_burn; fireSense_IgnitionFit, _SpreadFit, _IgnitionPredict and _SpreadPredict are now lower camelCase (fireSense_ignitionFit etc.). Update loadOrder, whichModulesToPrepare and docs that name them. Co-Authored-By: Claude Sonnet 5.5 Claude-Session: https://claude.ai/code/session_01CwcjqqK59FmTJscyi7xUqv --- NEWS.md | 5 +++++ fireSense_dataPrepPredict.R | 26 +++++++++++++------------- fireSense_dataPrepPredict.Rmd | 10 +++++----- fireSense_dataPrepPredict.md | 14 +++++++------- tests/testthat/test-init.R | 10 +++++----- tests/testthat/test-metadata.R | 8 ++++++++ tests/testthat/test-multiELF.R | 2 +- 7 files changed, 44 insertions(+), 31 deletions(-) diff --git a/NEWS.md b/NEWS.md index e6db5b0..d6bf2ea 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,5 +1,10 @@ # fireSense_dataPrepPredict (development version) +- `loadOrder` and the `whichModulesToPrepare` default and comparisons use the renamed `fireSense_ignitionFit`, `fireSense_spreadFit`, + `fireSense_ignitionPredict` and `fireSense_spreadPredict` (formerly `fireSense_IgnitionFit`, `fireSense_SpreadFit`, + `fireSense_IgnitionPredict`, `fireSense_SpreadPredict`). A project setting `whichModulesToPrepare` must use the new names. + + - `forestedLCC`, `cutoffForYoungAge`, `nonForestCanBeYoungAge`, `flammabilityThreshold`, `fuelClassCol` and `igAggFactor` now default to `fireSenseUtils`'s shared constants (`fireSenseForestedLCC`, `fireSenseYoungAgeCutoff`, `fireSenseNonForestCanBeYoungAge`, diff --git a/fireSense_dataPrepPredict.R b/fireSense_dataPrepPredict.R index 3099c65..8b2f875 100644 --- a/fireSense_dataPrepPredict.R +++ b/fireSense_dataPrepPredict.R @@ -1,8 +1,8 @@ defineModule(sim, list( name = "fireSense_dataPrepPredict", description = paste( - "Prepares, each year, the covariate tables used by fireSense_IgnitionPredict,", - "fireSense_EscapePredict and fireSense_SpreadPredict."), + "Prepares, each year, the covariate tables used by fireSense_ignitionPredict,", + "fireSense_EscapePredict and fireSense_spreadPredict."), keywords = "", authors = c( person("Ian", "Eddy", role = c("aut", "cre"), email = "ian.eddy@nrcan-rncan.gc.ca"), @@ -10,13 +10,13 @@ defineModule(sim, list( person("Alex M", "Chubaty", role = "ctb", email = "achubaty@for-cast.ca") ), childModules = character(0), - version = list(fireSense_dataPrepPredict = "1.0.4.9006"), + version = list(fireSense_dataPrepPredict = "1.0.4.9007"), timeframe = as.POSIXlt(c(NA, NA)), timeunit = "year", citation = list("citation.bib"), documentation = deparse(list("README.txt", "fireSense_dataPrepPredict.Rmd")), loadOrder = list(after = c("Biomass_borealDataPrep", "fireSense_dataPrepFit", - "fireSense_IgnitionFit", "fireSense_SpreadFit")), + "fireSense_ignitionFit", "fireSense_spreadFit")), reqdPkgs = list( "data.table", "PredictiveEcology/fireSenseUtils@development (>= 0.2.3.9062)", @@ -60,11 +60,11 @@ defineModule(sim, list( defineParameter("sppEquivCol", "character", "LandR", NA, NA, desc = "Column of `sppEquiv` with the species names used in `cohortData`."), defineParameter("whichModulesToPrepare", "character", - default = c("fireSense_SpreadPredict", "fireSense_IgnitionPredict", "fireSense_EscapePredict"), + default = c("fireSense_spreadPredict", "fireSense_ignitionPredict", "fireSense_EscapePredict"), NA, NA, - desc = paste("Predict modules to prepare covariates for: `fireSense_IgnitionPredict` or", + desc = paste("Predict modules to prepare covariates for: `fireSense_ignitionPredict` or", "`fireSense_EscapePredict` for the ignition/escape table,", - "`fireSense_SpreadPredict` for the spread table. Defaults to all three.")), + "`fireSense_spreadPredict` for the spread table. Defaults to all three.")), defineParameter( ".runInitialTime", "numeric", start(sim), NA, NA, "Time of the first climate and covariate preparation events." ), @@ -139,7 +139,7 @@ defineModule(sim, list( desc = "Table of LandR species equivalencies; must have columns `sppEquivCol` and `fuelClassCol`."), expectsInput("studyAreaWithSpreadParams", "sf", sourceURL = NA, desc = paste("The fitted SpreadFit ledger rows (from `fireSense_ELFs`; also read, undeclared, by", - "`fireSense_SpreadPredict`), one row per fitted ELF, in the order of `sppEquivs`. Each", + "`fireSense_spreadPredict`), one row per fitted ELF, in the order of `sppEquivs`. Each", "row's `params[[1]]` column names are the fitted formula's terms: an ELF whose terms", "include `dom_agb_`/`sec_agb_` predicts with those classes' AGB columns,", "matching what that ELF was fitted with; otherwise (an older, per-species fit) with the", @@ -178,14 +178,14 @@ doEvent.fireSense_dataPrepPredict <- function(sim, eventTime, eventType) { sim <- scheduleEvent(sim, time(sim) + 1, "fireSense_dataPrepPredict", "ageNonForest") sim <- scheduleEvent(sim, P(sim)$.runInitialTime, "fireSense_dataPrepPredict", "getClimateRasters") - if ("fireSense_IgnitionPredict" %in% P(sim)$whichModulesToPrepare | + if ("fireSense_ignitionPredict" %in% P(sim)$whichModulesToPrepare | "fireSense_EscapePredict" %in% P(sim)$whichModulesToPrepare) { sim <- scheduleEvent(sim, P(sim)$.runInitialTime, "fireSense_dataPrepPredict", "prepIgAndEscPredictData" ) } - if ("fireSense_SpreadPredict" %in% P(sim)$whichModulesToPrepare) { + if ("fireSense_spreadPredict" %in% P(sim)$whichModulesToPrepare) { sim <- scheduleEvent(sim, P(sim)$.runInitialTime, "fireSense_dataPrepPredict", "prepSpreadPredictData" ) } @@ -406,7 +406,7 @@ prepare_IgnitionAndEscapePredict <- function(sim) { # Coming out of the CacheGeo, this is unreliably a data.frame instead of a data.table if (!data.table::is.data.table(sim$sppEquiv)) data.table::setDT(sim$sppEquiv) ## one fuel set per fitted ELF (one, as before, when there is one ELF); the covariate tables are merged, - ## each ELF's columns alongside the others', for fireSense_IgnitionPredict to pick its own + ## each ELF's columns alongside the others', for fireSense_ignitionPredict to pick its own fuelSets <- ELFfuelSets(sim) fuelCovsCoarse <- mergeCovariateTables(lapply(fuelSets, function(fs) prepare_FuelCovsCoarse( cohortData = sim$cohortData, @@ -462,7 +462,7 @@ prepare_SpreadPredict <- function(sim) { stop("spreadClimate is NULL; there is a problem to debug") ## one fuel set per fitted ELF (one, as before, when there is one ELF). Every ELF's covariates are made for - ## every pixel and the tables merged, so fireSense_SpreadPredict can apply each ELF's model wherever it + ## every pixel and the tables merged, so fireSense_spreadPredict can apply each ELF's model wherever it ## predicts, including the blend zone around its own pixels. Column names say what they hold (fuel class, ## non-forest LCC codes), so a column two ELFs share means the same thing in both. fuelSets <- ELFfuelSets(sim) @@ -565,7 +565,7 @@ fuelClassRolesFromTermNames <- function(termNames) { #' #' @param sim A `simList`. #' @param i integer, the row (ELF), in the order of `sppEquivs` -- the same order -#' `fireSense_SpreadPredict::spreadPredictRun()` indexes `sa$params[[i]]` by. +#' `fireSense_spreadPredict::spreadPredictRun()` indexes `sa$params[[i]]` by. #' @return `list(domClass =, secClass =)`, from [fuelClassRolesFromTermNames()]; both `NA` when #' `studyAreaWithSpreadParams` is absent, too short, or that ELF has no fitted parameters yet. fuelClassRolesForELF <- function(sim, i = 1L) { diff --git a/fireSense_dataPrepPredict.Rmd b/fireSense_dataPrepPredict.Rmd index c61e8af..b71ffee 100644 --- a/fireSense_dataPrepPredict.Rmd +++ b/fireSense_dataPrepPredict.Rmd @@ -47,8 +47,8 @@ download.file(url = "https://img.shields.io/badge/Made%20with-Markdown-1f425f.pn Prepares, each year, the covariate tables that the fireSense [@Marchal:2017a; @Marchal:2017b; @Marchal:2019] predict modules use: -- `fireSense_igAndEscapePred_Covariates` for *fireSense_IgnitionPredict* and *fireSense_EscapePredict*: fuel classes, non-forest landcover, `youngAge`, ignition climate and lightning days, aggregated by `igAggFactor`. -- `fireSense_SpreadCovariates` for *fireSense_SpreadPredict*: the same fuel, landcover and `youngAge` columns plus spread climate, at the resolution of `flammableRTM`. +- `fireSense_igAndEscapePred_Covariates` for *fireSense_ignitionPredict* and *fireSense_EscapePredict*: fuel classes, non-forest landcover, `youngAge`, ignition climate and lightning days, aggregated by `igAggFactor`. +- `fireSense_SpreadCovariates` for *fireSense_spreadPredict*: the same fuel, landcover and `youngAge` columns plus spread climate, at the resolution of `flammableRTM`. Fuel classes come from `cohortData` and `pixelGroupMap`, grouped by the `fuelClassCol` column of `sppEquiv`. The covariates are built by the same *fireSenseUtils* functions that *fireSense_dataPrepFit* uses, so they match the fitted models. @@ -84,8 +84,8 @@ All events after `init`, except `save`, repeat every `fireTimeStep` years. - `init`: aligns `standAgeMap` and `rstLCC_RTM` to `rasterToMatch`; builds `landcoverDT` if absent; builds `nonForest_timeSinceDisturbance` if absent, from the fire polygons of the `cutoffForYoungAge` years up to `dataYear`. - `getClimateRasters` (from `.runInitialTime`): a supplied `currentClimateRasters` (e.g. from the `climateYear` module) is left alone. If it is absent, or this module built it for another year, takes layer `year` of each element of `projectedClimateRasters`, where `Y` is `climateYear` if supplied, else `time(sim)`. Stops if it does not match `pixelGroupMap`. -- `prepIgAndEscPredictData` (from `.runInitialTime`): builds `fireSense_igAndEscapePred_Covariates`. Scheduled if `whichModulesToPrepare` has `fireSense_IgnitionPredict` or `fireSense_EscapePredict`. -- `prepSpreadPredictData` (from `.runInitialTime`): builds `fireSense_SpreadCovariates`. Scheduled if `whichModulesToPrepare` has `fireSense_SpreadPredict`. +- `prepIgAndEscPredictData` (from `.runInitialTime`): builds `fireSense_igAndEscapePred_Covariates`. Scheduled if `whichModulesToPrepare` has `fireSense_ignitionPredict` or `fireSense_EscapePredict`. +- `prepSpreadPredictData` (from `.runInitialTime`): builds `fireSense_SpreadCovariates`. Scheduled if `whichModulesToPrepare` has `fireSense_spreadPredict`. - `ageNonForest` (from `time(sim) + 1`): adds 1 to `nonForest_timeSinceDisturbance` and resets pixels burned in `rstCurrentBurn` to 0. - `save`: does nothing except emit a message. The module never schedules it. @@ -103,7 +103,7 @@ knitr::kable(df_outputs, caption = "List of (ref:fireSense-dataPrepPredict) outp ### Links to other modules -Runs after *Biomass_borealDataPrep*, *fireSense_dataPrepFit*, *fireSense_IgnitionFit* and *fireSense_SpreadFit*, and supplies *fireSense_IgnitionPredict*, *fireSense_EscapePredict* and *fireSense_SpreadPredict*. +Runs after *Biomass_borealDataPrep*, *fireSense_dataPrepFit*, *fireSense_ignitionFit* and *fireSense_spreadFit*, and supplies *fireSense_ignitionPredict*, *fireSense_EscapePredict* and *fireSense_spreadPredict*. It is normally run as part of the [fireSense](https://github.com/PredictiveEcology/fireSense) module group. ### Getting help diff --git a/fireSense_dataPrepPredict.md b/fireSense_dataPrepPredict.md index d6b2902..c4955ee 100644 --- a/fireSense_dataPrepPredict.md +++ b/fireSense_dataPrepPredict.md @@ -39,8 +39,8 @@ Ian Eddy [aut, cre], Eliot McIntire studyAreaWithSpreadParams sf - The fitted SpreadFit ledger rows (from `fireSense_ELFs`; also read, undeclared, by `fireSense_SpreadPredict`), one row per fitted ELF, in the order of `sppEquivs`. Each row's `params[[1]]` column names are the fitted formula's terms: an ELF whose terms include `dom_agb_<class>`/`sec_agb_<class>` predicts with those classes' AGB columns, matching what that ELF was fitted with; otherwise (an older, per-species fit) with the previous one-column-per-fuel-class covariates. Unsupplied: every ELF predicts per-fuel-class, as before this was read. + The fitted SpreadFit ledger rows (from `fireSense_ELFs`; also read, undeclared, by `fireSense_spreadPredict`), one row per fitted ELF, in the order of `sppEquivs`. Each row's `params[[1]]` column names are the fitted formula's terms: an ELF whose terms include `dom_agb_<class>`/`sec_agb_<class>` predicts with those classes' AGB columns, matching what that ELF was fitted with; otherwise (an older, per-species fit) with the previous one-column-per-fuel-class covariates. Unsupplied: every ELF predicts per-fuel-class, as before this was read. NA @@ -310,7 +310,7 @@ Summary of user-visible parameters (Table \@ref(tab:moduleParams-fireSense-dataP fireSens.... NA NA - Predict modules to prepare covariates for: `fireSense_IgnitionPredict` or `fireSense_EscapePredict` for the ignition/escape table, `fireSense_SpreadPredict` for the spread table. Defaults to all three. + Predict modules to prepare covariates for: `fireSense_ignitionPredict` or `fireSense_EscapePredict` for the ignition/escape table, `fireSense_spreadPredict` for the spread table. Defaults to all three. .runInitialTime @@ -345,8 +345,8 @@ All events after `init`, except `save`, repeat every `fireTimeStep` years. - `init`: aligns `standAgeMap` and `rstLCC_RTM` to `rasterToMatch`; builds `landcoverDT` if absent; builds `nonForest_timeSinceDisturbance` if absent, from the fire polygons of the `cutoffForYoungAge` years up to `dataYear`. - `getClimateRasters` (from `.runInitialTime`): a supplied `currentClimateRasters` (e.g. from the `climateYear` module) is left alone. If it is absent, or this module built it for another year, takes layer `year` of each element of `projectedClimateRasters`, where `Y` is `climateYear` if supplied, else `time(sim)`. Stops if it does not match `pixelGroupMap`. -- `prepIgAndEscPredictData` (from `.runInitialTime`): builds `fireSense_igAndEscapePred_Covariates`. Scheduled if `whichModulesToPrepare` has `fireSense_IgnitionPredict` or `fireSense_EscapePredict`. -- `prepSpreadPredictData` (from `.runInitialTime`): builds `fireSense_SpreadCovariates`. Scheduled if `whichModulesToPrepare` has `fireSense_SpreadPredict`. +- `prepIgAndEscPredictData` (from `.runInitialTime`): builds `fireSense_igAndEscapePred_Covariates`. Scheduled if `whichModulesToPrepare` has `fireSense_ignitionPredict` or `fireSense_EscapePredict`. +- `prepSpreadPredictData` (from `.runInitialTime`): builds `fireSense_SpreadCovariates`. Scheduled if `whichModulesToPrepare` has `fireSense_spreadPredict`. - `ageNonForest` (from `time(sim) + 1`): adds 1 to `nonForest_timeSinceDisturbance` and resets pixels burned in `rstCurrentBurn` to 0. - `save`: does nothing except emit a message. The module never schedules it. @@ -391,7 +391,7 @@ Description of the module outputs (Table \@ref(tab:moduleOutputs-fireSense-dataP ### Links to other modules -Runs after *Biomass_borealDataPrep*, *fireSense_dataPrepFit*, *fireSense_IgnitionFit* and *fireSense_SpreadFit*, and supplies *fireSense_IgnitionPredict*, *fireSense_EscapePredict* and *fireSense_SpreadPredict*. +Runs after *Biomass_borealDataPrep*, *fireSense_dataPrepFit*, *fireSense_ignitionFit* and *fireSense_spreadFit*, and supplies *fireSense_ignitionPredict*, *fireSense_EscapePredict* and *fireSense_spreadPredict*. It is normally run as part of the [fireSense](https://github.com/PredictiveEcology/fireSense) module group. ### Getting help diff --git a/tests/testthat/test-init.R b/tests/testthat/test-init.R index 1ebaaf8..b9ab15b 100644 --- a/tests/testthat/test-init.R +++ b/tests/testthat/test-init.R @@ -71,14 +71,14 @@ test_that("init schedules exactly the events implied by whichModulesToPrepare", sort(as.data.frame(SpaDES.core::events(sim))$eventType) } ## ageNonForest and getClimateRasters are unconditional; the two prep events are not - expect_identical(evs("fireSense_SpreadPredict"), + expect_identical(evs("fireSense_spreadPredict"), c("ageNonForest", "getClimateRasters", "prepSpreadPredictData")) - expect_identical(evs("fireSense_IgnitionPredict"), + expect_identical(evs("fireSense_ignitionPredict"), c("ageNonForest", "getClimateRasters", "prepIgAndEscPredictData")) ## the ignition/escape table is shared: EscapePredict alone schedules it too expect_identical(evs("fireSense_EscapePredict"), c("ageNonForest", "getClimateRasters", "prepIgAndEscPredictData")) - expect_identical(evs(c("fireSense_SpreadPredict", "fireSense_IgnitionPredict")), + expect_identical(evs(c("fireSense_spreadPredict", "fireSense_ignitionPredict")), c("ageNonForest", "getClimateRasters", "prepIgAndEscPredictData", "prepSpreadPredictData")) ## a Fit module name is not a Predict module name: no covariate table is prepared @@ -89,8 +89,8 @@ test_that("the default whichModulesToPrepare is the three Predict modules doEven ## The default used to include `fireSense_EscapeFit`, which `doEvent` never tests for. md <- SpaDES.core::moduleMetadata(module = moduleName, path = modulePath) default <- md$parameters$default[[which(md$parameters$paramName == "whichModulesToPrepare")]] - expect_setequal(default, c("fireSense_IgnitionPredict", "fireSense_EscapePredict", - "fireSense_SpreadPredict")) + expect_setequal(default, c("fireSense_ignitionPredict", "fireSense_EscapePredict", + "fireSense_spreadPredict")) ## with the default, init schedules both covariate events sim <- SpaDES.core::spades(toyPrepSim(), events = "init", debug = FALSE) diff --git a/tests/testthat/test-metadata.R b/tests/testthat/test-metadata.R index fb916d1..875f67d 100644 --- a/tests/testthat/test-metadata.R +++ b/tests/testthat/test-metadata.R @@ -64,3 +64,11 @@ test_that("parameters are the expected names", { "sppEquivCol", "whichModulesToPrepare")) ) }) + +test_that("loadOrder names the renamed fit modules", { + ## moduleMetadata() does not return `loadOrder`, so read it from the parsed defineModule() call + parsed <- parse(file.path(moduleRoot, paste0(moduleName, ".R")), keep.source = TRUE) + dm <- Filter(function(e) grepl("^defineModule", paste(deparse(e), collapse = "")), as.list(parsed)) + lo <- eval(dm[[1]][[3]]$loadOrder) + expect_true(all(c("fireSense_ignitionFit", "fireSense_spreadFit") %in% lo$after)) +}) diff --git a/tests/testthat/test-multiELF.R b/tests/testthat/test-multiELF.R index eed653c..9222176 100644 --- a/tests/testthat/test-multiELF.R +++ b/tests/testthat/test-multiELF.R @@ -1,5 +1,5 @@ ## Several fitted ELFs in one study area (the 2-ELF Mackenzie forecast, 2026-09). Each ELF's model predicts -## over its own pixels and a blend zone around them (fireSense_SpreadPredict), so every pixel needs every +## over its own pixels and a blend zone around them (fireSense_spreadPredict), so every pixel needs every ## ELF's covariates: each ELF's fuel classes and non-forest groups, made from its own sppEquiv. Columns two ## ELFs share have the same name and so the same content. ## From a47cff7bc287db4814d4ac25b3a5804b41608f7d Mon Sep 17 00:00:00 2001 From: Eliot McIntire Date: Tue, 29 Sep 2026 11:12:24 -0700 Subject: [PATCH 2/4] Use the per-ELF groups when there is one fitted ELF ELFfuelSets() used nonForestedLCCGroupsList and missingLCCgroupList only when length(sppEquivs) > 1, so a predict-only run with one ELF built covariates from the module default group (nf) while the fit had nfLCC_* terms. Use the lists whenever present; landcoverDT is built from their union. Co-Authored-By: Claude Sonnet 5.5 Claude-Session: https://claude.ai/code/session_01CwcjqqK59FmTJscyi7xUqv --- NEWS.md | 4 ++++ fireSense_dataPrepPredict.R | 20 +++++++++++++++----- tests/testthat/test-multiELF.R | 21 +++++++++++++++++++++ 3 files changed, 40 insertions(+), 5 deletions(-) diff --git a/NEWS.md b/NEWS.md index d6bf2ea..0775904 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,5 +1,9 @@ # fireSense_dataPrepPredict (development version) +- With one fitted ELF, the covariates and `landcoverDT` are built from that ELF's groups in `nonForestedLCCGroupsList` and + `missingLCCgroupList` whenever those are present, as with several ELFs. Before, a predict-only run with one ELF used the + module default `nonForestedLCCGroups` (`nf`) and the spread prediction failed on the fitted `nfLCC_*` terms. + - `loadOrder` and the `whichModulesToPrepare` default and comparisons use the renamed `fireSense_ignitionFit`, `fireSense_spreadFit`, `fireSense_ignitionPredict` and `fireSense_spreadPredict` (formerly `fireSense_IgnitionFit`, `fireSense_SpreadFit`, `fireSense_IgnitionPredict`, `fireSense_SpreadPredict`). A project setting `whichModulesToPrepare` must use the new names. diff --git a/fireSense_dataPrepPredict.R b/fireSense_dataPrepPredict.R index 8b2f875..91cf0bf 100644 --- a/fireSense_dataPrepPredict.R +++ b/fireSense_dataPrepPredict.R @@ -10,7 +10,7 @@ defineModule(sim, list( person("Alex M", "Chubaty", role = "ctb", email = "achubaty@for-cast.ca") ), childModules = character(0), - version = list(fireSense_dataPrepPredict = "1.0.4.9007"), + version = list(fireSense_dataPrepPredict = "1.0.4.9008"), timeframe = as.POSIXlt(c(NA, NA)), timeunit = "year", citation = list("citation.bib"), @@ -263,7 +263,7 @@ Init <- function(sim) { rstLCC = rstLCC, flammableRTM = sim$flammableRTM, forestedLCC = P(sim)$forestedLCC, - nonForestedLCCGroups = sim$nonForestedLCCGroups + nonForestedLCCGroups = landcoverGroups(sim) ) } @@ -536,7 +536,9 @@ prepare_SpreadPredict <- function(sim) { #' #' With several fitted ELFs, `fireSense_dataPrepFit` supplies one species table, non-forest grouping and #' missing-LCC group per ELF (`sppEquivs`, `nonForestedLCCGroupsList`, `missingLCCgroupList`); each gets its -#' own `landcoverDT`, made once and kept in `mod`. With one ELF, the single set of objects, as before. +#' own `landcoverDT`, made once and kept in `mod`. This holds for one ELF too whenever the per-ELF lists are +#' present (a predict-only run has only the module defaults in `nonForestedLCCGroups`); the single set of +#' objects is used only when they are absent. #' #' @param sim A `simList`. #' @return list of lists, each with `sppEquiv`, `nonForestedLCCGroups`, `missingLCCgroup`, `landcoverDT`, @@ -577,9 +579,17 @@ fuelClassRolesForELF <- function(sim, i = 1L) { fuelClassRolesFromTermNames(colnames(p)) } +## the non-forest groups to build `landcoverDT` with: the fitted ELFs' groups when known, else the single set +landcoverGroups <- function(sim) { + g <- sim$nonForestedLCCGroupsList + if (!length(g)) return(sim$nonForestedLCCGroups) + g <- do.call(c, unname(g)) + g[!duplicated(names(g))] +} + ELFfuelSets <- function(sim) { fcc <- P(sim)$fuelClassCol - if (length(sim$sppEquivs) > 1L) { + if (length(sim$sppEquivs) > 1L || length(sim$nonForestedLCCGroupsList)) { n <- length(sim$sppEquivs) if (length(sim$nonForestedLCCGroupsList) != n || length(sim$missingLCCgroupList) != n) stop("fireSense_dataPrepPredict: sppEquivs, nonForestedLCCGroupsList and missingLCCgroupList must have one ", @@ -717,7 +727,7 @@ unionLCCGroups <- function(fuelSets) { rstLCC = sim$rstLCC_RTM, flammableRTM = sim$flammableRTM, forestedLCC = P(sim)$forestedLCC, - nonForestedLCCGroups = sim$nonForestedLCCGroups + nonForestedLCCGroups = landcoverGroups(sim) ) } diff --git a/tests/testthat/test-multiELF.R b/tests/testthat/test-multiELF.R index 9222176..8803a2d 100644 --- a/tests/testthat/test-multiELF.R +++ b/tests/testthat/test-multiELF.R @@ -55,3 +55,24 @@ test_that("mismatched per-ELF lists stop with a message", { o$sppEquivs <- list(sppA(), sppB()); o$nonForestedLCCGroupsList <- list(nfA); o$missingLCCgroupList <- list("grass", "wetgrs") expect_error(toyPrepRun(o), "one element per ELF") }) + +## A predict-only run (no fireSense_dataPrepFit; parameters from the ledger) with ONE fitted ELF still +## gets that ELF's groups in `nonForestedLCCGroupsList`; `nonForestedLCCGroups` is then only the module +## default. The covariates must be built with the ELF's groups (fireCarbon run 4.2.2, 2026-09-29: the fit +## had nfLCC_* terms, the covariates had `nf`, and the spread prediction failed). +test_that("with one ELF and per-ELF lists, the list's groups build the covariates", { + o <- toyObjects() + o$landcoverDT <- NULL + o$nonForestedLCCGroups <- list(nf = c(16L, 19L)); o$missingLCCgroup <- "nf" # the module defaults + o$sppEquivs <- list(sppA()) + o$nonForestedLCCGroupsList <- list(nfA) + o$missingLCCgroupList <- list("grass") + one <- toyPrepRun(o) + ref <- oneELF(sppA, nfA, "grass") + + sp <- covDF(one$fireSense_SpreadCovariates); spRef <- covDF(ref$fireSense_SpreadCovariates) + expect_true(all(c("wetland", "grass") %in% names(sp))) + expect_false("nf" %in% names(sp)) + expect_setequal(names(sp), names(spRef)) + for (cn in setdiff(names(spRef), "pixelID")) expect_equal(sp[[cn]], spRef[[cn]], info = cn) +}) From 88e9e33473bc63852c7ca23b7f54d44d1117edc3 Mon Sep 17 00:00:00 2001 From: Eliot McIntire Date: Tue, 29 Sep 2026 11:14:56 -0700 Subject: [PATCH 3/4] Keep the shared landcoverDT on the single default groups Only makeTSD() reads the shared sim$landcoverDT (rowSums > 0 and pixelID, timeSinceDisturbance.R:88-97), so its grouping does not matter; each ELF's own landcoverDT comes from ELFfuelSets(). Revert the Init/.inputObjects use of the union of per-ELF groups. Co-Authored-By: Claude Sonnet 5.5 Claude-Session: https://claude.ai/code/session_01CwcjqqK59FmTJscyi7xUqv --- NEWS.md | 2 +- fireSense_dataPrepPredict.R | 12 ++---------- 2 files changed, 3 insertions(+), 11 deletions(-) diff --git a/NEWS.md b/NEWS.md index 0775904..96e0b53 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,6 +1,6 @@ # fireSense_dataPrepPredict (development version) -- With one fitted ELF, the covariates and `landcoverDT` are built from that ELF's groups in `nonForestedLCCGroupsList` and +- With one fitted ELF, the covariates (and that ELF's `landcoverDT`) are built from that ELF's groups in `nonForestedLCCGroupsList` and `missingLCCgroupList` whenever those are present, as with several ELFs. Before, a predict-only run with one ELF used the module default `nonForestedLCCGroups` (`nf`) and the spread prediction failed on the fitted `nfLCC_*` terms. diff --git a/fireSense_dataPrepPredict.R b/fireSense_dataPrepPredict.R index 91cf0bf..b78c8a1 100644 --- a/fireSense_dataPrepPredict.R +++ b/fireSense_dataPrepPredict.R @@ -263,7 +263,7 @@ Init <- function(sim) { rstLCC = rstLCC, flammableRTM = sim$flammableRTM, forestedLCC = P(sim)$forestedLCC, - nonForestedLCCGroups = landcoverGroups(sim) + nonForestedLCCGroups = sim$nonForestedLCCGroups ) } @@ -579,14 +579,6 @@ fuelClassRolesForELF <- function(sim, i = 1L) { fuelClassRolesFromTermNames(colnames(p)) } -## the non-forest groups to build `landcoverDT` with: the fitted ELFs' groups when known, else the single set -landcoverGroups <- function(sim) { - g <- sim$nonForestedLCCGroupsList - if (!length(g)) return(sim$nonForestedLCCGroups) - g <- do.call(c, unname(g)) - g[!duplicated(names(g))] -} - ELFfuelSets <- function(sim) { fcc <- P(sim)$fuelClassCol if (length(sim$sppEquivs) > 1L || length(sim$nonForestedLCCGroupsList)) { @@ -727,7 +719,7 @@ unionLCCGroups <- function(fuelSets) { rstLCC = sim$rstLCC_RTM, flammableRTM = sim$flammableRTM, forestedLCC = P(sim)$forestedLCC, - nonForestedLCCGroups = landcoverGroups(sim) + nonForestedLCCGroups = sim$nonForestedLCCGroups ) } From 677ef1ba4234d25fec073148a1716d82865aa1fa Mon Sep 17 00:00:00 2001 From: Eliot McIntire Date: Tue, 29 Sep 2026 11:32:31 -0700 Subject: [PATCH 4/4] Remove the retired fireSense_EscapePredict from whichModulesToPrepare fireSense_ignitionPredict predicts ignition and escape. Naming fireSense_EscapePredict stops with a message. Co-Authored-By: Claude Sonnet 5.5 Claude-Session: https://claude.ai/code/session_01CwcjqqK59FmTJscyi7xUqv --- NEWS.md | 3 +++ fireSense_dataPrepPredict.R | 20 +++++++++++--------- fireSense_dataPrepPredict.Rmd | 6 +++--- fireSense_dataPrepPredict.md | 8 ++++---- tests/testthat/test-init.R | 19 +++++++++++-------- 5 files changed, 32 insertions(+), 24 deletions(-) diff --git a/NEWS.md b/NEWS.md index 96e0b53..6ffba27 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,5 +1,8 @@ # fireSense_dataPrepPredict (development version) +- `fireSense_EscapePredict` no longer exists (`fireSense_ignitionPredict` predicts ignition and escape): it is removed from the + `whichModulesToPrepare` default (now `fireSense_ignitionPredict` and `fireSense_spreadPredict`) and naming it stops with a message. + - With one fitted ELF, the covariates (and that ELF's `landcoverDT`) are built from that ELF's groups in `nonForestedLCCGroupsList` and `missingLCCgroupList` whenever those are present, as with several ELFs. Before, a predict-only run with one ELF used the module default `nonForestedLCCGroups` (`nf`) and the spread prediction failed on the fitted `nfLCC_*` terms. diff --git a/fireSense_dataPrepPredict.R b/fireSense_dataPrepPredict.R index b78c8a1..f032469 100644 --- a/fireSense_dataPrepPredict.R +++ b/fireSense_dataPrepPredict.R @@ -1,8 +1,8 @@ defineModule(sim, list( name = "fireSense_dataPrepPredict", description = paste( - "Prepares, each year, the covariate tables used by fireSense_ignitionPredict,", - "fireSense_EscapePredict and fireSense_spreadPredict."), + "Prepares, each year, the covariate tables used by fireSense_ignitionPredict", + "(ignition and escape) and fireSense_spreadPredict."), keywords = "", authors = c( person("Ian", "Eddy", role = c("aut", "cre"), email = "ian.eddy@nrcan-rncan.gc.ca"), @@ -10,7 +10,7 @@ defineModule(sim, list( person("Alex M", "Chubaty", role = "ctb", email = "achubaty@for-cast.ca") ), childModules = character(0), - version = list(fireSense_dataPrepPredict = "1.0.4.9008"), + version = list(fireSense_dataPrepPredict = "1.0.4.9009"), timeframe = as.POSIXlt(c(NA, NA)), timeunit = "year", citation = list("citation.bib"), @@ -60,11 +60,11 @@ defineModule(sim, list( defineParameter("sppEquivCol", "character", "LandR", NA, NA, desc = "Column of `sppEquiv` with the species names used in `cohortData`."), defineParameter("whichModulesToPrepare", "character", - default = c("fireSense_spreadPredict", "fireSense_ignitionPredict", "fireSense_EscapePredict"), + default = c("fireSense_spreadPredict", "fireSense_ignitionPredict"), NA, NA, - desc = paste("Predict modules to prepare covariates for: `fireSense_ignitionPredict` or", - "`fireSense_EscapePredict` for the ignition/escape table,", - "`fireSense_spreadPredict` for the spread table. Defaults to all three.")), + desc = paste("Predict modules to prepare covariates for: `fireSense_ignitionPredict` for the", + "ignition/escape table, `fireSense_spreadPredict` for the spread table.", + "Defaults to both.")), defineParameter( ".runInitialTime", "numeric", start(sim), NA, NA, "Time of the first climate and covariate preparation events." ), @@ -174,12 +174,14 @@ defineModule(sim, list( doEvent.fireSense_dataPrepPredict <- function(sim, eventTime, eventType) { switch(eventType, init = { + if ("fireSense_EscapePredict" %in% P(sim)$whichModulesToPrepare) + stop("fireSense_EscapePredict no longer exists as a module; escape covariates are prepared with ", + "fireSense_ignitionPredict. Remove it from parameter whichModulesToPrepare.") sim <- Init(sim) sim <- scheduleEvent(sim, time(sim) + 1, "fireSense_dataPrepPredict", "ageNonForest") sim <- scheduleEvent(sim, P(sim)$.runInitialTime, "fireSense_dataPrepPredict", "getClimateRasters") - if ("fireSense_ignitionPredict" %in% P(sim)$whichModulesToPrepare | - "fireSense_EscapePredict" %in% P(sim)$whichModulesToPrepare) { + if ("fireSense_ignitionPredict" %in% P(sim)$whichModulesToPrepare) { sim <- scheduleEvent(sim, P(sim)$.runInitialTime, "fireSense_dataPrepPredict", "prepIgAndEscPredictData" ) diff --git a/fireSense_dataPrepPredict.Rmd b/fireSense_dataPrepPredict.Rmd index b71ffee..6c6f3c1 100644 --- a/fireSense_dataPrepPredict.Rmd +++ b/fireSense_dataPrepPredict.Rmd @@ -47,7 +47,7 @@ download.file(url = "https://img.shields.io/badge/Made%20with-Markdown-1f425f.pn Prepares, each year, the covariate tables that the fireSense [@Marchal:2017a; @Marchal:2017b; @Marchal:2019] predict modules use: -- `fireSense_igAndEscapePred_Covariates` for *fireSense_ignitionPredict* and *fireSense_EscapePredict*: fuel classes, non-forest landcover, `youngAge`, ignition climate and lightning days, aggregated by `igAggFactor`. +- `fireSense_igAndEscapePred_Covariates` for *fireSense_ignitionPredict* (ignition and escape): fuel classes, non-forest landcover, `youngAge`, ignition climate and lightning days, aggregated by `igAggFactor`. - `fireSense_SpreadCovariates` for *fireSense_spreadPredict*: the same fuel, landcover and `youngAge` columns plus spread climate, at the resolution of `flammableRTM`. Fuel classes come from `cohortData` and `pixelGroupMap`, grouped by the `fuelClassCol` column of `sppEquiv`. @@ -84,7 +84,7 @@ All events after `init`, except `save`, repeat every `fireTimeStep` years. - `init`: aligns `standAgeMap` and `rstLCC_RTM` to `rasterToMatch`; builds `landcoverDT` if absent; builds `nonForest_timeSinceDisturbance` if absent, from the fire polygons of the `cutoffForYoungAge` years up to `dataYear`. - `getClimateRasters` (from `.runInitialTime`): a supplied `currentClimateRasters` (e.g. from the `climateYear` module) is left alone. If it is absent, or this module built it for another year, takes layer `year` of each element of `projectedClimateRasters`, where `Y` is `climateYear` if supplied, else `time(sim)`. Stops if it does not match `pixelGroupMap`. -- `prepIgAndEscPredictData` (from `.runInitialTime`): builds `fireSense_igAndEscapePred_Covariates`. Scheduled if `whichModulesToPrepare` has `fireSense_ignitionPredict` or `fireSense_EscapePredict`. +- `prepIgAndEscPredictData` (from `.runInitialTime`): builds `fireSense_igAndEscapePred_Covariates`. Scheduled if `whichModulesToPrepare` has `fireSense_ignitionPredict`; the module `fireSense_EscapePredict` no longer exists and is rejected. - `prepSpreadPredictData` (from `.runInitialTime`): builds `fireSense_SpreadCovariates`. Scheduled if `whichModulesToPrepare` has `fireSense_spreadPredict`. - `ageNonForest` (from `time(sim) + 1`): adds 1 to `nonForest_timeSinceDisturbance` and resets pixels burned in `rstCurrentBurn` to 0. - `save`: does nothing except emit a message. The module never schedules it. @@ -103,7 +103,7 @@ knitr::kable(df_outputs, caption = "List of (ref:fireSense-dataPrepPredict) outp ### Links to other modules -Runs after *Biomass_borealDataPrep*, *fireSense_dataPrepFit*, *fireSense_ignitionFit* and *fireSense_spreadFit*, and supplies *fireSense_ignitionPredict*, *fireSense_EscapePredict* and *fireSense_spreadPredict*. +Runs after *Biomass_borealDataPrep*, *fireSense_dataPrepFit*, *fireSense_ignitionFit* and *fireSense_spreadFit*, and supplies *fireSense_ignitionPredict* (ignition and escape) and *fireSense_spreadPredict*. It is normally run as part of the [fireSense](https://github.com/PredictiveEcology/fireSense) module group. ### Getting help diff --git a/fireSense_dataPrepPredict.md b/fireSense_dataPrepPredict.md index c4955ee..caff9e9 100644 --- a/fireSense_dataPrepPredict.md +++ b/fireSense_dataPrepPredict.md @@ -39,7 +39,7 @@ Ian Eddy [aut, cre], Eliot McIntire fireSens.... NA NA - Predict modules to prepare covariates for: `fireSense_ignitionPredict` or `fireSense_EscapePredict` for the ignition/escape table, `fireSense_spreadPredict` for the spread table. Defaults to all three. + Predict modules to prepare covariates for: `fireSense_ignitionPredict` for the ignition/escape table, `fireSense_spreadPredict` for the spread table. Defaults to both. .runInitialTime @@ -345,7 +345,7 @@ All events after `init`, except `save`, repeat every `fireTimeStep` years. - `init`: aligns `standAgeMap` and `rstLCC_RTM` to `rasterToMatch`; builds `landcoverDT` if absent; builds `nonForest_timeSinceDisturbance` if absent, from the fire polygons of the `cutoffForYoungAge` years up to `dataYear`. - `getClimateRasters` (from `.runInitialTime`): a supplied `currentClimateRasters` (e.g. from the `climateYear` module) is left alone. If it is absent, or this module built it for another year, takes layer `year` of each element of `projectedClimateRasters`, where `Y` is `climateYear` if supplied, else `time(sim)`. Stops if it does not match `pixelGroupMap`. -- `prepIgAndEscPredictData` (from `.runInitialTime`): builds `fireSense_igAndEscapePred_Covariates`. Scheduled if `whichModulesToPrepare` has `fireSense_ignitionPredict` or `fireSense_EscapePredict`. +- `prepIgAndEscPredictData` (from `.runInitialTime`): builds `fireSense_igAndEscapePred_Covariates`. Scheduled if `whichModulesToPrepare` has `fireSense_ignitionPredict`; the module `fireSense_EscapePredict` no longer exists and is rejected. - `prepSpreadPredictData` (from `.runInitialTime`): builds `fireSense_SpreadCovariates`. Scheduled if `whichModulesToPrepare` has `fireSense_spreadPredict`. - `ageNonForest` (from `time(sim) + 1`): adds 1 to `nonForest_timeSinceDisturbance` and resets pixels burned in `rstCurrentBurn` to 0. - `save`: does nothing except emit a message. The module never schedules it. @@ -391,7 +391,7 @@ Description of the module outputs (Table \@ref(tab:moduleOutputs-fireSense-dataP ### Links to other modules -Runs after *Biomass_borealDataPrep*, *fireSense_dataPrepFit*, *fireSense_ignitionFit* and *fireSense_spreadFit*, and supplies *fireSense_ignitionPredict*, *fireSense_EscapePredict* and *fireSense_spreadPredict*. +Runs after *Biomass_borealDataPrep*, *fireSense_dataPrepFit*, *fireSense_ignitionFit* and *fireSense_spreadFit*, and supplies *fireSense_ignitionPredict* (ignition and escape) and *fireSense_spreadPredict*. It is normally run as part of the [fireSense](https://github.com/PredictiveEcology/fireSense) module group. ### Getting help diff --git a/tests/testthat/test-init.R b/tests/testthat/test-init.R index b9ab15b..9c94a51 100644 --- a/tests/testthat/test-init.R +++ b/tests/testthat/test-init.R @@ -75,22 +75,17 @@ test_that("init schedules exactly the events implied by whichModulesToPrepare", c("ageNonForest", "getClimateRasters", "prepSpreadPredictData")) expect_identical(evs("fireSense_ignitionPredict"), c("ageNonForest", "getClimateRasters", "prepIgAndEscPredictData")) - ## the ignition/escape table is shared: EscapePredict alone schedules it too - expect_identical(evs("fireSense_EscapePredict"), - c("ageNonForest", "getClimateRasters", "prepIgAndEscPredictData")) expect_identical(evs(c("fireSense_spreadPredict", "fireSense_ignitionPredict")), c("ageNonForest", "getClimateRasters", "prepIgAndEscPredictData", "prepSpreadPredictData")) ## a Fit module name is not a Predict module name: no covariate table is prepared - expect_identical(evs("fireSense_EscapeFit"), c("ageNonForest", "getClimateRasters")) + expect_identical(evs("fireSense_ignitionFit"), c("ageNonForest", "getClimateRasters")) }) -test_that("the default whichModulesToPrepare is the three Predict modules doEvent tests for", { - ## The default used to include `fireSense_EscapeFit`, which `doEvent` never tests for. +test_that("the default whichModulesToPrepare is the two Predict modules", { md <- SpaDES.core::moduleMetadata(module = moduleName, path = modulePath) default <- md$parameters$default[[which(md$parameters$paramName == "whichModulesToPrepare")]] - expect_setequal(default, c("fireSense_ignitionPredict", "fireSense_EscapePredict", - "fireSense_spreadPredict")) + expect_setequal(default, c("fireSense_ignitionPredict", "fireSense_spreadPredict")) ## with the default, init schedules both covariate events sim <- SpaDES.core::spades(toyPrepSim(), events = "init", debug = FALSE) @@ -99,6 +94,14 @@ test_that("the default whichModulesToPrepare is the three Predict modules doEven "prepSpreadPredictData")) }) +test_that("the retired fireSense_EscapePredict stops with a message naming fireSense_ignitionPredict", { + expect_error( + SpaDES.core::spades( + toyPrepSim(params = list(whichModulesToPrepare = c("fireSense_ignitionPredict", "fireSense_EscapePredict"))), + events = "init", debug = FALSE), + "no longer exists.*fireSense_ignitionPredict") +}) + test_that("the save event does nothing", { sim <- SpaDES.core::spades(toyPrepSim(), events = "init", debug = FALSE) before <- sapply(ls(sim), function(nm) reproducible::.robustDigest(sim[[nm]]))