From 3a84b2b2cc4bc5519d5eeaae8cd505612a3eccef Mon Sep 17 00:00:00 2001 From: Eliot McIntire Date: Mon, 28 Sep 2026 11:41:20 -0700 Subject: [PATCH 1/4] Predict with the fit's dom/sec fuel classes, not the prediction area's prepare_SpreadPredict() (fireSense_dataPrepPredict.R ~459) called fireSenseUtils::fireSenseCovariatesCreate() without rstLCC, so treedWetland never appeared, and there was no way to predict with the new dom/sec/other AGB fuel representation from fireSenseUtils#98. New inputs fuelClassRoles (one ELF) / fuelClassRolesList (several ELFs), from fireSense_dataPrepFit::sim$fuelClassRoles: when an ELF's domClass is set, ELFfuelSets() passes it and rstLCC through so that ELF's dom_agb_/ sec_agb_/other_agb/treedWetland_agb columns use the SAME classes the fit chose, never re-derived from the prediction area. Unsupplied (every previously-fitted ELF), prediction is unchanged. The module's own sanity check (forest fuel must not land on non-forest pixels) is fixed to exclude treedWetland/treedWetland_agb, which are not on the fuel columns' logMinB() floor scale. Version 1.0.4.9004. Floors fireSenseUtils@development (>= 0.2.3.9050). Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01CwcjqqK59FmTJscyi7xUqv --- NEWS.md | 9 ++++ fireSense_dataPrepPredict.R | 68 +++++++++++++++++++++----- fireSense_dataPrepPredict.html | 36 ++++++++++++-- fireSense_dataPrepPredict.md | 14 +++++- tests/testthat/test-covariates.R | 2 +- tests/testthat/test-domSecOtherFuels.R | 47 ++++++++++++++++++ tests/testthat/test-metadata.R | 2 + 7 files changed, 160 insertions(+), 18 deletions(-) create mode 100644 tests/testthat/test-domSecOtherFuels.R diff --git a/NEWS.md b/NEWS.md index cf78c1f..3a53858 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,5 +1,14 @@ # fireSense_dataPrepPredict (development version) +- `prepare_SpreadPredict()` now passes `rstLCC` to `fireSenseCovariatesCreate()` (previously never + passed, so `treedWetland` never appeared). New inputs `fuelClassRoles`/`fuelClassRolesList` + (from `fireSense_dataPrepFit::sim$fuelClassRoles`, one ELF or one per ELF): when an ELF's + `domClass` is set, prediction builds that ELF's `dom_agb_`/`sec_agb_`/`other_agb`/ + `treedWetland_agb` columns using THOSE classes, not the classes that happen to dominate the + prediction area. Unsupplied (the default, and every previously-fitted ELF), prediction is + unchanged: the previous one-column-per-fuel-class covariates. Needs + `fireSenseUtils@development (>= 0.2.3.9050)`. Version 1.0.4.9004. + - `studyArea` is now a declared input. `.inputObjects` used it to mask the fire polygons, land cover and stand age it makes, but an undeclared object is not visible there, so it was always `NULL` and nothing was masked. It also made an unset `.studyAreaName` come from the extent of `rasterToMatch` in `.inputObjects` but from `studyArea` in `Init`. diff --git a/fireSense_dataPrepPredict.R b/fireSense_dataPrepPredict.R index 084cc50..0c62ff2 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.9003"), + version = list(fireSense_dataPrepPredict = "1.0.4.9004"), timeframe = as.POSIXlt(c(NA, NA)), timeunit = "year", citation = list("citation.bib"), @@ -19,7 +19,7 @@ defineModule(sim, list( "fireSense_IgnitionFit", "fireSense_SpreadFit")), reqdPkgs = list( "data.table", - "PredictiveEcology/fireSenseUtils@development (>= 0.1.0)", + "PredictiveEcology/fireSenseUtils@development (>= 0.2.3.9050)", "terra" ), parameters = rbind( @@ -129,8 +129,17 @@ defineModule(sim, list( desc = "Only with several fitted ELFs: one `nonForestedLCCGroups` per ELF, in the order of `sppEquivs`."), expectsInput("missingLCCgroupList", "list", sourceURL = NA, desc = "Only with several fitted ELFs: one `missingLCCgroup` per ELF, in the order of `sppEquivs`."), + expectsInput("fuelClassRolesList", "list", sourceURL = NA, + desc = paste("Only with several fitted ELFs: one `fuelClassRoles` (`list(domClass =, secClass =)`, from", + "`fireSense_dataPrepFit::sim$fuelClassRoles`) per ELF, in the order of `sppEquivs`. An ELF", + "missing from this list, or fitted with `fuelCovariates = \"species\"`, predicts with the", + "previous per-fuel-class columns.")), expectsInput("sppEquiv", "data.table", sourceURL = NA, desc = "Table of LandR species equivalencies; must have columns `sppEquivCol` and `fuelClassCol`."), + expectsInput("fuelClassRoles", "list", sourceURL = NA, + desc = paste("One fitted ELF only: `list(domClass =, secClass =)`, from", + "`fireSense_dataPrepFit::sim$fuelClassRoles`. Unsupplied, or `domClass = NA`, predicts with", + "the previous per-fuel-class columns.")), expectsInput("standAgeMap", "SpatRaster", sourceURL = NA, desc = "Stand age (years) at `start(sim)`."), expectsInput("studyArea", "SpatVector", sourceURL = NA, @@ -456,11 +465,17 @@ prepare_SpreadPredict <- function(sim) { ## this fits cohortData into fuel classes ## if pixels are missing/absent but are able to be forested as determined by landcoverDT, ## they receive 0 values - e.g. pixelGroup zero + ## fs$fuelClassRoles is what the fit chose (fireSense_dataPrepFit::sim$fuelClassRoles, via + ## fuelClassRoles/fuelClassRolesList) -- domClass/secClass are forced here, never re-derived + ## from this prediction area, so an ELF predicts with the same dom_agb_*/sec_agb_* columns + ## its fit used even where a different class dominates here. domClass = NA (unset, or a fit + ## made with fuelCovariates = "species") predicts with the previous per-fuel-class columns. covs <- fireSenseUtils:::fireSenseCovariatesCreate( cohortData = sim$cohortData, pixelGroupMap = sim$pixelGroupMap, flammableRTM = sim$flammableRTM, landcoverDT = fs$landcoverDT, + rstLCC = fs$rstLCC, sppEquiv = fs$sppEquiv, sppEquivCol = P(sim)$sppEquivCol, @@ -472,12 +487,20 @@ prepare_SpreadPredict <- function(sim) { nonForestCanBeYoungAge = P(sim)$nonForestCanBeYoungAge, nonForest_timeSinceDisturbance = sim$nonForest_timeSinceDisturbance, studyAreaName = P(sim)$.studyAreaName, - useCache = FALSE # predict is annual, no point in caching + useCache = FALSE, # predict is annual, no point in caching + fuelCovariates = if (is.na(fs$fuelClassRoles$domClass)) "species" else "domSecOther", + domClass = fs$fuelClassRoles$domClass, + secClass = fs$fuelClassRoles$secClass ) # Sanity check - make sure the nonForest pixels have no forest fuels nfCols <- setdiff(names(fs$landcoverDT), "pixelID") df <- copy(covs) - fcs <- setdiff(colnames(df), c("pixelID", "youngAge", nfCols)) + ## treedWetland (0/1) and treedWetland_agb are not on the logMinB() floor scale the other + ## fuel columns are, so they are not "fuel" for this check either -- otherwise treedWetland's + ## 0s (never NA below) keep every row's rowSums(!is.na(fcs)) > 0, flagging every non-forest + ## pixel as if it had forest fuel + fcs <- setdiff(colnames(df), c("pixelID", "youngAge", nfCols, + fireSenseUtils::treedWetlandTxt, fireSenseUtils::treedWetlandAgbTxt)) if (length(fcs)) { df[df - min(df[[fcs[[1]]]], na.rm = TRUE) == 0] <- NA fuelInSamePixelAsNonForest <- any(rowSums(!is.na(df[, ..fcs])) > 0 & @@ -510,8 +533,16 @@ prepare_SpreadPredict <- function(sim) { #' own `landcoverDT`, made once and kept in `mod`. With one ELF, the single set of objects, as before. #' #' @param sim A `simList`. -#' @return list of lists, each with `sppEquiv`, `nonForestedLCCGroups`, `missingLCCgroup`, `landcoverDT` and -#' `requiredFuelClasses`. +#' @return list of lists, each with `sppEquiv`, `nonForestedLCCGroups`, `missingLCCgroup`, `landcoverDT`, +#' `requiredFuelClasses`, `rstLCC` (aligned to `flammableRTM`, for `treedWetland`/`treedWetland_agb`) and +#' `fuelClassRoles` (`list(domClass =, secClass =)`; `domClass = NA` predicts with per-fuel-class columns). +## fuelClassRoles for a single ELF: NA/NA (predict with per-fuel-class columns) unless the fit +## supplied one and it names an actual domClass +noFuelClassRoles <- list(domClass = NA_character_, secClass = NA_character_) +withFuelClassRoles <- function(fcr) { + if (is.null(fcr) || is.null(fcr$domClass) || is.na(fcr$domClass)) noFuelClassRoles else fcr +} + ELFfuelSets <- function(sim) { fcc <- P(sim)$fuelClassCol if (length(sim$sppEquivs) > 1L) { @@ -519,26 +550,35 @@ ELFfuelSets <- function(sim) { if (length(sim$nonForestedLCCGroupsList) != n || length(sim$missingLCCgroupList) != n) stop("fireSense_dataPrepPredict: sppEquivs, nonForestedLCCGroupsList and missingLCCgroupList must have one ", "element per ELF") - if (is.null(mod$ELFlandcoverDTs)) { - rstLCC <- if (!LandR::.compareRas(sim$flammableRTM, sim$rstLCC_RTM, stopOnError = FALSE)) + if (is.null(mod$ELFrstLCC)) { + mod$ELFrstLCC <- if (!LandR::.compareRas(sim$flammableRTM, sim$rstLCC_RTM, stopOnError = FALSE)) reproducible::postProcess(sim$rstLCC_RTM, to = sim$flammableRTM, method = "near") else sim$rstLCC_RTM + } + if (is.null(mod$ELFlandcoverDTs)) { mod$ELFlandcoverDTs <- lapply(sim$nonForestedLCCGroupsList, function(g) - makeLandcoverDT(rstLCC = rstLCC, flammableRTM = sim$flammableRTM, + makeLandcoverDT(rstLCC = mod$ELFrstLCC, flammableRTM = sim$flammableRTM, forestedLCC = P(sim)$forestedLCC, nonForestedLCCGroups = g)) } return(lapply(seq_len(n), function(i) { se <- data.table::as.data.table(sim$sppEquivs[[i]]) + fcr <- if (length(sim$fuelClassRolesList) >= i) sim$fuelClassRolesList[[i]] else NULL list(sppEquiv = se, nonForestedLCCGroups = sim$nonForestedLCCGroupsList[[i]], missingLCCgroup = sim$missingLCCgroupList[[i]], landcoverDT = mod$ELFlandcoverDTs[[i]], - requiredFuelClasses = se[[fcc]]) + requiredFuelClasses = se[[fcc]], rstLCC = mod$ELFrstLCC, + fuelClassRoles = withFuelClassRoles(fcr)) })) } if (!data.table::is.data.table(sim$sppEquiv)) data.table::setDT(sim$sppEquiv) if (is.null(mod$requiredFuelClasses)) mod$requiredFuelClasses <- sim$sppEquiv[[fcc]] + if (is.null(mod$ELFrstLCC)) { + mod$ELFrstLCC <- if (!LandR::.compareRas(sim$flammableRTM, sim$rstLCC_RTM, stopOnError = FALSE)) + reproducible::postProcess(sim$rstLCC_RTM, to = sim$flammableRTM, method = "near") else sim$rstLCC_RTM + } list(list(sppEquiv = sim$sppEquiv, nonForestedLCCGroups = sim$nonForestedLCCGroups, missingLCCgroup = sim$missingLCCgroup, landcoverDT = sim$landcoverDT, - requiredFuelClasses = mod$requiredFuelClasses)) + requiredFuelClasses = mod$requiredFuelClasses, rstLCC = mod$ELFrstLCC, + fuelClassRoles = withFuelClassRoles(sim$fuelClassRoles))) } #' Merge the covariates of several ELFs @@ -646,7 +686,11 @@ unionLCCGroups <- function(fuelSets) { nonForestedLCCGroups = sim$nonForestedLCCGroups ) } - + + if (!suppliedElsewhere("fuelClassRoles", sim)) { + ## no fit metadata to say otherwise: predict with the previous per-fuel-class columns + sim$fuelClassRoles <- list(domClass = NA_character_, secClass = NA_character_) + } return(invisible(sim)) } diff --git a/fireSense_dataPrepPredict.html b/fireSense_dataPrepPredict.html index cd522a1..9ac439a 100644 --- a/fireSense_dataPrepPredict.html +++ b/fireSense_dataPrepPredict.html @@ -2994,7 +2994,7 @@

fireSense_dataPrepPredict Manual

-

v.1.0.4.9003

+

v.1.0.4.9004

Last updated: 2026-09-28

@@ -3004,7 +3004,7 @@

Last updated: 2026-09-28

fireSense_dataPrepPredict Module

-

made-with-Markdown

+

made-with-Markdown

Authors:

@@ -3015,7 +3015,7 @@

Authors:

Module Overview

Module summary

-

Prepares, each year, the covariate tables that the fireSense (Marchal, Steve G. Cumming, et al. 2017b; Marchal, Steve G. Cumming, et al. 2017a; Marchal et al. 2019) predict modules use:

+

Prepares, each year, the covariate tables that the fireSense (Marchal, Cumming, and McIntire 2017b; Marchal, Cumming, and McIntire 2017a; Marchal, Cumming, and McIntire 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.
  • @@ -3291,6 +3291,20 @@

    Module inputs and parameters

    +fuelClassRolesList + + +list + + +Only with several fitted ELFs: one fuelClassRoles (list(domClass =, secClass =), from fireSense_dataPrepFit::sim$fuelClassRoles) per ELF, in the order of sppEquivs. An ELF missing from this list, or fitted with fuelCovariates = "species", predicts with the previous per-fuel-class columns. + + +NA + + + + sppEquiv @@ -3305,6 +3319,20 @@

    Module inputs and parameters

    +fuelClassRoles + + +list + + +One fitted ELF only: list(domClass =, secClass =), from fireSense_dataPrepFit::sim$fuelClassRoles. Unsupplied, or domClass = NA, predicts with the previous per-fuel-class columns. + + +NA + + + + standAgeMap @@ -3762,7 +3790,7 @@

    References

    Marchal, Jean, Steve G Cumming, and Eliot J B McIntire. 2017b. “Land Cover, More Than Monthly Fire Weather, Drives Fire-Size Distribution in Southern Québec Forests: Implications for Fire Risk Management.” PLoS ONE 12 (6): 1–17. https://doi.org/10.1371/journal.pone.0179294.
-Marchal, Jean, Steven G. Cumming, and Eliot J. B. McIntire. 2019. “Turning Down the Heat: Vegetation Feedbacks Limit Fire Regime Responses to Global Warming.” Ecosystems, ahead of print, May. https://doi.org/10.1007/s10021-019-00398-2. +Marchal, Jean, Steven G. Cumming, and Eliot J. B. McIntire. 2019. “Turning Down the Heat: Vegetation Feedbacks Limit Fire Regime Responses to Global Warming.” Ecosystems, May. https://doi.org/10.1007/s10021-019-00398-2.
diff --git a/fireSense_dataPrepPredict.md b/fireSense_dataPrepPredict.md index 802bcbb..4f2c2a2 100644 --- a/fireSense_dataPrepPredict.md +++ b/fireSense_dataPrepPredict.md @@ -1,6 +1,6 @@ --- title: "fireSense_dataPrepPredict Manual" -subtitle: "v.1.0.4.9003" +subtitle: "v.1.0.4.9004" date: "Last updated: 2026-09-28" output: bookdown::html_document2: @@ -167,12 +167,24 @@ Table \@ref(tab:moduleInputs-fireSense-dataPrepPredict) shows the full list of m Only with several fitted ELFs: one `missingLCCgroup` per ELF, in the order of `sppEquivs`. NA + + fuelClassRolesList + list + Only with several fitted ELFs: one `fuelClassRoles` (`list(domClass =, secClass =)`, from `fireSense_dataPrepFit::sim$fuelClassRoles`) per ELF, in the order of `sppEquivs`. An ELF missing from this list, or fitted with `fuelCovariates = "species"`, predicts with the previous per-fuel-class columns. + NA + sppEquiv data.table Table of LandR species equivalencies; must have columns `sppEquivCol` and `fuelClassCol`. NA + + fuelClassRoles + list + One fitted ELF only: `list(domClass =, secClass =)`, from `fireSense_dataPrepFit::sim$fuelClassRoles`. Unsupplied, or `domClass = NA`, predicts with the previous per-fuel-class columns. + NA + standAgeMap SpatRaster diff --git a/tests/testthat/test-covariates.R b/tests/testthat/test-covariates.R index f2c3d1d..8d878c1 100644 --- a/tests/testthat/test-covariates.R +++ b/tests/testthat/test-covariates.R @@ -14,7 +14,7 @@ test_that("prepSpreadPredictData builds one row per flammable pixel with this ye ## the declared column order: pixelID, the climate layers, youngAge, then the fuels expect_identical(names(df)[1:3], c("pixelID", "MDC", "youngAge")) expect_setequal(names(df), c("pixelID", "MDC", "youngAge", "class1", "class2", - "wetland", "grass")) + "wetland", "grass", "treedWetland")) ## hand-computed: spread climate is MDC, and toy MDC for 2001 is 100 + cellNumber, so a ## value of 100 + pixelID proves both the right variable and the right year were taken diff --git a/tests/testthat/test-domSecOtherFuels.R b/tests/testthat/test-domSecOtherFuels.R new file mode 100644 index 0000000..90e0689 --- /dev/null +++ b/tests/testthat/test-domSecOtherFuels.R @@ -0,0 +1,47 @@ +## fireSense_dataPrepPredict.R ~459 (prepare_SpreadPredict()): fireSenseCovariatesCreate() was +## never given `rstLCC`, and there was no way to tell it which dom/sec fuel classes a fit chose. +## Prediction must build the SAME dom_agb_*/sec_agb_* columns the fit used, never re-derive them +## from the prediction area (PG1+PG4 make class1 the naturally dominant class in this toy +## landscape); an ELF with no fuelClassRoles (an old, per-species fit) must still get the previous +## per-fuel-class columns. + +test_that("with no fuelClassRoles, prediction gets the previous per-fuel-class columns", { + out <- toyPrepRun(toyObjects()) + sp <- covDF(out$fireSense_SpreadCovariates) + expect_true(all(c("class1", "class2") %in% names(sp))) + expect_false(any(grepl("^dom_agb_|^sec_agb_|^other_agb$", names(sp)))) + ## rstLCC is now passed regardless of fuelCovariates: treedWetland appears (all 0: no LCC 81 here) + expect_true("treedWetland" %in% names(sp)) + expect_true(all(sp$treedWetland == 0)) +}) + +test_that("a forced fuelClassRoles builds dom_agb_*/sec_agb_* using THAT class, not the locally dominant one", { + ## class1 (Pice_mar + Pinu_ban) totals 3100 here, class2 (Popu_tre) only 550: class1 naturally + ## dominates. Force the opposite, as if the fit had chosen class2 as dominant. + o <- toyObjects() + o$fuelClassRoles <- list(domClass = "class2", secClass = "class1") + out <- toyPrepRun(o) + sp <- covDF(out$fireSense_SpreadCovariates) + + expect_true(all(c("dom_agb_class2", "sec_agb_class1", "other_agb") %in% names(sp))) + expect_false(any(c("class1", "class2") %in% names(sp))) + + ## pixel 1 (PG1): class1 (Pice_mar 2000 + Pinu_ban 1000) = 3000, class2 (Popu_tre) = 0 + px1 <- sp[sp$pixelID == 1, ] + expect_equal(px1$sec_agb_class1, fireSenseUtils::logMinB(3000)) + expect_equal(px1$dom_agb_class2, fireSenseUtils::logMinB(0)) + + ## pixel 3 (PG2): class2 (Popu_tre) = 500, class1 = 0 + px3 <- sp[sp$pixelID == 3, ] + expect_equal(px3$dom_agb_class2, fireSenseUtils::logMinB(500)) + expect_equal(px3$sec_agb_class1, fireSenseUtils::logMinB(0)) +}) + +test_that("fuelClassRoles with domClass = NA (an unforced or species-fit ELF) predicts as before", { + o <- toyObjects() + o$fuelClassRoles <- list(domClass = NA_character_, secClass = NA_character_) + out <- toyPrepRun(o) + sp <- covDF(out$fireSense_SpreadCovariates) + expect_true(all(c("class1", "class2") %in% names(sp))) + expect_false(any(grepl("^dom_agb_|^sec_agb_", names(sp)))) +}) diff --git a/tests/testthat/test-metadata.R b/tests/testthat/test-metadata.R index 1ef1f1a..96ab02f 100644 --- a/tests/testthat/test-metadata.R +++ b/tests/testthat/test-metadata.R @@ -22,6 +22,8 @@ test_that("inputs are the expected names and classes", { cohortData = "data.table", currentClimateRasters = "SpatRaster", flammableRTM = "SpatRaster", + fuelClassRoles = "list", + fuelClassRolesList = "list", landcoverDT = "data.table", lightningMaps = "SpatRaster", missingLCCgroup = "character", From d3a215a3ebe151d8eeae36bd160f7f6999e561d5 Mon Sep 17 00:00:00 2001 From: Eliot McIntire Date: Mon, 28 Sep 2026 11:46:36 -0700 Subject: [PATCH 2/4] Floor fireSenseUtils >= 0.2.3.9055 (the fuel PR #98 was renumbered after development moved) Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01CwcjqqK59FmTJscyi7xUqv --- NEWS.md | 2 +- fireSense_dataPrepPredict.R | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/NEWS.md b/NEWS.md index 3a53858..54adc49 100644 --- a/NEWS.md +++ b/NEWS.md @@ -7,7 +7,7 @@ `treedWetland_agb` columns using THOSE classes, not the classes that happen to dominate the prediction area. Unsupplied (the default, and every previously-fitted ELF), prediction is unchanged: the previous one-column-per-fuel-class covariates. Needs - `fireSenseUtils@development (>= 0.2.3.9050)`. Version 1.0.4.9004. + `fireSenseUtils@development (>= 0.2.3.9055)`. Version 1.0.4.9004. - `studyArea` is now a declared input. `.inputObjects` used it to mask the fire polygons, land cover and stand age it makes, but an undeclared object is not visible there, so it was always `NULL` and nothing was masked. It also made diff --git a/fireSense_dataPrepPredict.R b/fireSense_dataPrepPredict.R index 0c62ff2..e5528e2 100644 --- a/fireSense_dataPrepPredict.R +++ b/fireSense_dataPrepPredict.R @@ -19,7 +19,7 @@ defineModule(sim, list( "fireSense_IgnitionFit", "fireSense_SpreadFit")), reqdPkgs = list( "data.table", - "PredictiveEcology/fireSenseUtils@development (>= 0.2.3.9050)", + "PredictiveEcology/fireSenseUtils@development (>= 0.2.3.9055)", "terra" ), parameters = rbind( From 7e4d1cd0a8c569ef447b9628de0192a7474effd2 Mon Sep 17 00:00:00 2001 From: Eliot McIntire Date: Mon, 28 Sep 2026 12:00:27 -0700 Subject: [PATCH 3/4] Read the fit's dom/sec classes from fitted parameter names, not a new input Maintainer decision: read domClass/secClass from the fitted parameters' column names in sim$studyAreaWithSpreadParams (the SpreadFit ledger rows fireSense_ELFs already supplies), not from a new fuelClassRoles/ fuelClassRolesList input. Removes those two expectsInput and their .inputObjects default; declares studyAreaWithSpreadParams (already read, undeclared, by fireSense_SpreadPredict). New fuelClassRolesFromTermNames() strips the "dom_agb_"/"sec_agb_" prefix off a fitted term name to recover the fuel-class name unchanged (fireSenseUtils never mangles it further); fuelClassRolesForELF() reads ELF i's row of sim$studyAreaWithSpreadParams the same way fireSense_SpreadPredict's spreadPredictRun() indexes sa$params[[i]]. A fit with no dom_agb_*/sec_agb_* term (old, per-species fits) or no fitted parameters yet predicts with the previous per-fuel-class columns, unchanged. Floor raised to fireSenseUtils@development (>= 0.2.3.9057), matching the renumbered fireSenseUtils#98 and dataPrepFit#48. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01CwcjqqK59FmTJscyi7xUqv --- NEWS.md | 15 ++--- fireSense_dataPrepPredict.R | 76 +++++++++++++++++--------- fireSense_dataPrepPredict.html | 20 +------ fireSense_dataPrepPredict.md | 12 +--- tests/testthat/test-domSecOtherFuels.R | 71 ++++++++++++++++++++---- tests/testthat/test-metadata.R | 5 +- 6 files changed, 124 insertions(+), 75 deletions(-) diff --git a/NEWS.md b/NEWS.md index 54adc49..558e002 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,13 +1,14 @@ # fireSense_dataPrepPredict (development version) - `prepare_SpreadPredict()` now passes `rstLCC` to `fireSenseCovariatesCreate()` (previously never - passed, so `treedWetland` never appeared). New inputs `fuelClassRoles`/`fuelClassRolesList` - (from `fireSense_dataPrepFit::sim$fuelClassRoles`, one ELF or one per ELF): when an ELF's - `domClass` is set, prediction builds that ELF's `dom_agb_`/`sec_agb_`/`other_agb`/ - `treedWetland_agb` columns using THOSE classes, not the classes that happen to dominate the - prediction area. Unsupplied (the default, and every previously-fitted ELF), prediction is - unchanged: the previous one-column-per-fuel-class covariates. Needs - `fireSenseUtils@development (>= 0.2.3.9055)`. Version 1.0.4.9004. + passed, so `treedWetland` never appeared). It now also reads `sim$studyAreaWithSpreadParams` + (the fitted SpreadFit ledger rows `fireSense_ELFs` supplies, also read undeclared by + `fireSense_SpreadPredict`): when an ELF's fitted parameter names include `dom_agb_`/ + `sec_agb_` (new `fuelClassRolesFromTermNames()`), prediction builds that ELF's + `dom_agb_`/`sec_agb_`/`other_agb`/`treedWetland_agb` columns using THOSE classes, + not the classes that happen to dominate the prediction area. An older, per-species fit (no such + terms), or no fitted parameters yet, predicts as before: the previous one-column-per-fuel-class + covariates. Needs `fireSenseUtils@development (>= 0.2.3.9057)`. Version 1.0.4.9004. - `studyArea` is now a declared input. `.inputObjects` used it to mask the fire polygons, land cover and stand age it makes, but an undeclared object is not visible there, so it was always `NULL` and nothing was masked. It also made diff --git a/fireSense_dataPrepPredict.R b/fireSense_dataPrepPredict.R index e5528e2..f2d1de8 100644 --- a/fireSense_dataPrepPredict.R +++ b/fireSense_dataPrepPredict.R @@ -19,7 +19,7 @@ defineModule(sim, list( "fireSense_IgnitionFit", "fireSense_SpreadFit")), reqdPkgs = list( "data.table", - "PredictiveEcology/fireSenseUtils@development (>= 0.2.3.9055)", + "PredictiveEcology/fireSenseUtils@development (>= 0.2.3.9057)", "terra" ), parameters = rbind( @@ -129,17 +129,16 @@ defineModule(sim, list( desc = "Only with several fitted ELFs: one `nonForestedLCCGroups` per ELF, in the order of `sppEquivs`."), expectsInput("missingLCCgroupList", "list", sourceURL = NA, desc = "Only with several fitted ELFs: one `missingLCCgroup` per ELF, in the order of `sppEquivs`."), - expectsInput("fuelClassRolesList", "list", sourceURL = NA, - desc = paste("Only with several fitted ELFs: one `fuelClassRoles` (`list(domClass =, secClass =)`, from", - "`fireSense_dataPrepFit::sim$fuelClassRoles`) per ELF, in the order of `sppEquivs`. An ELF", - "missing from this list, or fitted with `fuelCovariates = \"species\"`, predicts with the", - "previous per-fuel-class columns.")), expectsInput("sppEquiv", "data.table", sourceURL = NA, desc = "Table of LandR species equivalencies; must have columns `sppEquivCol` and `fuelClassCol`."), - expectsInput("fuelClassRoles", "list", sourceURL = NA, - desc = paste("One fitted ELF only: `list(domClass =, secClass =)`, from", - "`fireSense_dataPrepFit::sim$fuelClassRoles`. Unsupplied, or `domClass = NA`, predicts with", - "the previous per-fuel-class columns.")), + 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", + "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", + "previous one-column-per-fuel-class covariates. Unsupplied: every ELF predicts", + "per-fuel-class, as before this was read.")), expectsInput("standAgeMap", "SpatRaster", sourceURL = NA, desc = "Stand age (years) at `start(sim)`."), expectsInput("studyArea", "SpatVector", sourceURL = NA, @@ -465,11 +464,12 @@ prepare_SpreadPredict <- function(sim) { ## this fits cohortData into fuel classes ## if pixels are missing/absent but are able to be forested as determined by landcoverDT, ## they receive 0 values - e.g. pixelGroup zero - ## fs$fuelClassRoles is what the fit chose (fireSense_dataPrepFit::sim$fuelClassRoles, via - ## fuelClassRoles/fuelClassRolesList) -- domClass/secClass are forced here, never re-derived + ## fs$fuelClassRoles is read from this ELF's fitted parameter names (fuelClassRolesForELF(), + ## from sim$studyAreaWithSpreadParams) -- domClass/secClass are forced here, never re-derived ## from this prediction area, so an ELF predicts with the same dom_agb_*/sec_agb_* columns - ## its fit used even where a different class dominates here. domClass = NA (unset, or a fit - ## made with fuelCovariates = "species") predicts with the previous per-fuel-class columns. + ## its fit used even where a different class dominates here. domClass = NA (a fit made with + ## fuelCovariates = "species", or with no fitted parameters yet) predicts with the previous + ## per-fuel-class columns. covs <- fireSenseUtils:::fireSenseCovariatesCreate( cohortData = sim$cohortData, pixelGroupMap = sim$pixelGroupMap, @@ -536,11 +536,39 @@ prepare_SpreadPredict <- function(sim) { #' @return list of lists, each with `sppEquiv`, `nonForestedLCCGroups`, `missingLCCgroup`, `landcoverDT`, #' `requiredFuelClasses`, `rstLCC` (aligned to `flammableRTM`, for `treedWetland`/`treedWetland_agb`) and #' `fuelClassRoles` (`list(domClass =, secClass =)`; `domClass = NA` predicts with per-fuel-class columns). -## fuelClassRoles for a single ELF: NA/NA (predict with per-fuel-class columns) unless the fit -## supplied one and it names an actual domClass -noFuelClassRoles <- list(domClass = NA_character_, secClass = NA_character_) -withFuelClassRoles <- function(fcr) { - if (is.null(fcr) || is.null(fcr$domClass) || is.na(fcr$domClass)) noFuelClassRoles else fcr + +#' The fuel-class name after `dom_agb_`/`sec_agb_` in a fitted covariate name +#' +#' `fireSenseUtils::fireSenseCovariatesCreate(fuelCovariates = "domSecOther")` names those columns +#' `dom_agb_`/`sec_agb_` with the fuel class's own name unchanged (no further +#' mangling), so recovering `domClass`/`secClass` from a fitted term name is stripping the prefix. +#' +#' @param termNames character vector, e.g. `colnames(sim$studyAreaWithSpreadParams$params[[1]])`. +#' @return `list(domClass =, secClass =)`; both `NA` when `termNames` has no `dom_agb_*` term (an +#' older, per-species fit -- predicts with the previous one-column-per-fuel-class covariates). +fuelClassRolesFromTermNames <- function(termNames) { + domTerm <- grep("^dom_agb_", termNames, value = TRUE) + secTerm <- grep("^sec_agb_", termNames, value = TRUE) + if (!length(domTerm)) + return(list(domClass = NA_character_, secClass = NA_character_)) + list(domClass = sub("^dom_agb_", "", domTerm[1]), + secClass = if (length(secTerm)) sub("^sec_agb_", "", secTerm[1]) else NA_character_) +} + +#' `fuelClassRoles` for one row of `sim$studyAreaWithSpreadParams` +#' +#' @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. +#' @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) { + sa <- sim$studyAreaWithSpreadParams + noRoles <- list(domClass = NA_character_, secClass = NA_character_) + if (is.null(sa) || NROW(sa) < i) return(noRoles) + p <- sa$params[[i]] + if (is.null(p) || !NROW(p)) return(noRoles) + fuelClassRolesFromTermNames(colnames(p)) } ELFfuelSets <- function(sim) { @@ -561,11 +589,10 @@ ELFfuelSets <- function(sim) { } return(lapply(seq_len(n), function(i) { se <- data.table::as.data.table(sim$sppEquivs[[i]]) - fcr <- if (length(sim$fuelClassRolesList) >= i) sim$fuelClassRolesList[[i]] else NULL list(sppEquiv = se, nonForestedLCCGroups = sim$nonForestedLCCGroupsList[[i]], missingLCCgroup = sim$missingLCCgroupList[[i]], landcoverDT = mod$ELFlandcoverDTs[[i]], requiredFuelClasses = se[[fcc]], rstLCC = mod$ELFrstLCC, - fuelClassRoles = withFuelClassRoles(fcr)) + fuelClassRoles = fuelClassRolesForELF(sim, i)) })) } if (!data.table::is.data.table(sim$sppEquiv)) data.table::setDT(sim$sppEquiv) @@ -578,7 +605,7 @@ ELFfuelSets <- function(sim) { list(list(sppEquiv = sim$sppEquiv, nonForestedLCCGroups = sim$nonForestedLCCGroups, missingLCCgroup = sim$missingLCCgroup, landcoverDT = sim$landcoverDT, requiredFuelClasses = mod$requiredFuelClasses, rstLCC = mod$ELFrstLCC, - fuelClassRoles = withFuelClassRoles(sim$fuelClassRoles))) + fuelClassRoles = fuelClassRolesForELF(sim, 1L))) } #' Merge the covariates of several ELFs @@ -687,11 +714,6 @@ unionLCCGroups <- function(fuelSets) { ) } - if (!suppliedElsewhere("fuelClassRoles", sim)) { - ## no fit metadata to say otherwise: predict with the previous per-fuel-class columns - sim$fuelClassRoles <- list(domClass = NA_character_, secClass = NA_character_) - } - return(invisible(sim)) } diff --git a/fireSense_dataPrepPredict.html b/fireSense_dataPrepPredict.html index 9ac439a..34ba500 100644 --- a/fireSense_dataPrepPredict.html +++ b/fireSense_dataPrepPredict.html @@ -3291,20 +3291,6 @@

Module inputs and parameters

-fuelClassRolesList - - -list - - -Only with several fitted ELFs: one fuelClassRoles (list(domClass =, secClass =), from fireSense_dataPrepFit::sim$fuelClassRoles) per ELF, in the order of sppEquivs. An ELF missing from this list, or fitted with fuelCovariates = "species", predicts with the previous per-fuel-class columns. - - -NA - - - - sppEquiv @@ -3319,13 +3305,13 @@

Module inputs and parameters

-fuelClassRoles +studyAreaWithSpreadParams -list +sf -One fitted ELF only: list(domClass =, secClass =), from fireSense_dataPrepFit::sim$fuelClassRoles. Unsupplied, or domClass = NA, predicts with the previous per-fuel-class columns. +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_&lt;class&gt;/sec_agb_&lt;class&gt; 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 diff --git a/fireSense_dataPrepPredict.md b/fireSense_dataPrepPredict.md index 4f2c2a2..f1f29b1 100644 --- a/fireSense_dataPrepPredict.md +++ b/fireSense_dataPrepPredict.md @@ -167,12 +167,6 @@ Table \@ref(tab:moduleInputs-fireSense-dataPrepPredict) shows the full list of m Only with several fitted ELFs: one `missingLCCgroup` per ELF, in the order of `sppEquivs`. NA - - fuelClassRolesList - list - Only with several fitted ELFs: one `fuelClassRoles` (`list(domClass =, secClass =)`, from `fireSense_dataPrepFit::sim$fuelClassRoles`) per ELF, in the order of `sppEquivs`. An ELF missing from this list, or fitted with `fuelCovariates = "species"`, predicts with the previous per-fuel-class columns. - NA - sppEquiv data.table @@ -180,9 +174,9 @@ Table \@ref(tab:moduleInputs-fireSense-dataPrepPredict) shows the full list of m NA - fuelClassRoles - list - One fitted ELF only: `list(domClass =, secClass =)`, from `fireSense_dataPrepFit::sim$fuelClassRoles`. Unsupplied, or `domClass = NA`, predicts with the previous per-fuel-class columns. + 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. NA diff --git a/tests/testthat/test-domSecOtherFuels.R b/tests/testthat/test-domSecOtherFuels.R index 90e0689..6330248 100644 --- a/tests/testthat/test-domSecOtherFuels.R +++ b/tests/testthat/test-domSecOtherFuels.R @@ -1,11 +1,29 @@ ## fireSense_dataPrepPredict.R ~459 (prepare_SpreadPredict()): fireSenseCovariatesCreate() was ## never given `rstLCC`, and there was no way to tell it which dom/sec fuel classes a fit chose. -## Prediction must build the SAME dom_agb_*/sec_agb_* columns the fit used, never re-derive them -## from the prediction area (PG1+PG4 make class1 the naturally dominant class in this toy -## landscape); an ELF with no fuelClassRoles (an old, per-species fit) must still get the previous -## per-fuel-class columns. +## Prediction reads that from sim$studyAreaWithSpreadParams (the SpreadFit ledger rows +## fireSense_ELFs supplies): each row's params[[1]] column names are the fitted formula's terms, +## and fuelClassRolesFromTermNames() strips "dom_agb_"/"sec_agb_" off any that have them. Prediction +## must build the SAME dom_agb_*/sec_agb_* columns the fit used, never re-derive them from the +## prediction area (PG1+PG4 make class1 the naturally dominant class in this toy landscape); an ELF +## whose fitted terms name no dom_agb_* (an old, per-species fit, or none yet) must still get the +## previous per-fuel-class columns. -test_that("with no fuelClassRoles, prediction gets the previous per-fuel-class columns", { +## a toy studyAreaWithSpreadParams row: one best-parameter-set row, column names = fitted terms +toyLedgerRow <- function(termNames) { + p <- matrix(1, nrow = 1, ncol = length(termNames), dimnames = list(NULL, termNames)) + data.frame(polygonID = "toyELF", params = I(list(p))) +} + +test_that("fuelClassRolesFromTermNames() strips the prefix, and is NA/NA with no dom_agb_ term", { + expect_identical(fuelClassRolesFromTermNames(c("b0", "MDC", "dom_agb_Pice_mar", "sec_agb_Pinu_ban")), + list(domClass = "Pice_mar", secClass = "Pinu_ban")) + expect_identical(fuelClassRolesFromTermNames(c("b0", "MDC", "dom_agb_Pice_mar")), + list(domClass = "Pice_mar", secClass = NA_character_)) + expect_identical(fuelClassRolesFromTermNames(c("b0", "MDC", "class1", "class2")), + list(domClass = NA_character_, secClass = NA_character_)) +}) + +test_that("with no studyAreaWithSpreadParams, prediction gets the previous per-fuel-class columns", { out <- toyPrepRun(toyObjects()) sp <- covDF(out$fireSense_SpreadCovariates) expect_true(all(c("class1", "class2") %in% names(sp))) @@ -15,11 +33,20 @@ test_that("with no fuelClassRoles, prediction gets the previous per-fuel-class c expect_true(all(sp$treedWetland == 0)) }) -test_that("a forced fuelClassRoles builds dom_agb_*/sec_agb_* using THAT class, not the locally dominant one", { +test_that("a per-species fitted row (no dom_agb_ term) predicts with the previous per-fuel-class columns", { + o <- toyObjects() + o$studyAreaWithSpreadParams <- toyLedgerRow(c("b0", "MDC", "youngAge", "class1", "class2")) + out <- toyPrepRun(o) + sp <- covDF(out$fireSense_SpreadCovariates) + expect_true(all(c("class1", "class2") %in% names(sp))) + expect_false(any(grepl("^dom_agb_|^sec_agb_", names(sp)))) +}) + +test_that("a fitted dom_agb_class2/sec_agb_class1 row builds THOSE columns, not the locally dominant one", { ## class1 (Pice_mar + Pinu_ban) totals 3100 here, class2 (Popu_tre) only 550: class1 naturally - ## dominates. Force the opposite, as if the fit had chosen class2 as dominant. + ## dominates. The fitted terms name the opposite, as if the fit had chosen class2 as dominant. o <- toyObjects() - o$fuelClassRoles <- list(domClass = "class2", secClass = "class1") + o$studyAreaWithSpreadParams <- toyLedgerRow(c("b0", "MDC", "youngAge", "dom_agb_class2", "sec_agb_class1")) out <- toyPrepRun(o) sp <- covDF(out$fireSense_SpreadCovariates) @@ -37,11 +64,31 @@ test_that("a forced fuelClassRoles builds dom_agb_*/sec_agb_* using THAT class, expect_equal(px3$sec_agb_class1, fireSenseUtils::logMinB(0)) }) -test_that("fuelClassRoles with domClass = NA (an unforced or species-fit ELF) predicts as before", { +test_that("two ELFs with different fitted roles each build their own dom/sec columns", { + ## ELF A (sppA, class1 = conifer, class2 -> decid): fitted dominant = conifer. + ## ELF B (sppB, per-species Pice_mar/Pinu_ban, class2 -> decid): an old per-species fit. + sppA <- function() data.table::data.table(LandR = c("Pice_mar", "Pinu_ban", "Popu_tre"), + FuelClass = c("conifer", "conifer", "decid")) + sppB <- function() data.table::data.table(LandR = c("Pice_mar", "Pinu_ban", "Popu_tre"), + FuelClass = c("Pice_mar", "Pinu_ban", "decid")) + nfA <- list(wetland = 19L, grass = 16L); nfB <- list(wetgrs = c(16L, 19L)) + o <- toyObjects() - o$fuelClassRoles <- list(domClass = NA_character_, secClass = NA_character_) + o$landcoverDT <- NULL + o$sppEquivs <- list(sppA(), sppB()) + o$nonForestedLCCGroupsList <- list(nfA, nfB) + o$missingLCCgroupList <- list("grass", "wetgrs") + o$studyAreaWithSpreadParams <- rbind( + toyLedgerRow(c("b0", "MDC", "youngAge", "dom_agb_conifer", "sec_agb_decid")), + toyLedgerRow(c("b0", "MDC", "youngAge", "Pice_mar", "Pinu_ban", "decid")) + ) out <- toyPrepRun(o) sp <- covDF(out$fireSense_SpreadCovariates) - expect_true(all(c("class1", "class2") %in% names(sp))) - expect_false(any(grepl("^dom_agb_|^sec_agb_", names(sp)))) + + ## ELF A's dom/sec columns + expect_true(all(c("dom_agb_conifer", "sec_agb_decid") %in% names(sp))) + ## ELF B's per-species columns + expect_true(all(c("Pice_mar", "Pinu_ban", "decid") %in% names(sp))) + ## neither ELF ever names a plain "conifer" column (that was ELF A's pre-collapse class) + expect_false("conifer" %in% names(sp)) }) diff --git a/tests/testthat/test-metadata.R b/tests/testthat/test-metadata.R index 96ab02f..7b37e15 100644 --- a/tests/testthat/test-metadata.R +++ b/tests/testthat/test-metadata.R @@ -22,8 +22,6 @@ test_that("inputs are the expected names and classes", { cohortData = "data.table", currentClimateRasters = "SpatRaster", flammableRTM = "SpatRaster", - fuelClassRoles = "list", - fuelClassRolesList = "list", landcoverDT = "data.table", lightningMaps = "SpatRaster", missingLCCgroup = "character", @@ -39,7 +37,8 @@ test_that("inputs are the expected names and classes", { sppEquiv = "data.table", sppEquivs = "list", standAgeMap = "SpatRaster", - studyArea = "SpatVector") + studyArea = "SpatVector", + studyAreaWithSpreadParams = "sf") ) }) From 5e341672b1f0ee390e31ceb6f5fc6cf723f96d0f Mon Sep 17 00:00:00 2001 From: Eliot McIntire Date: Mon, 28 Sep 2026 12:55:33 -0700 Subject: [PATCH 4/4] test: a young wetland pixel is not wetland (young is nothing else, fireSenseUtils >= 0.2.3.9048) Pixel 15 of the toy map is both wetland and young; since fireSenseUtils #92 the youngAge exclusivity zeroes its wetland value, so the expected wetland pixels are 9 and 10. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01CwcjqqK59FmTJscyi7xUqv --- tests/testthat/test-covariates.R | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/tests/testthat/test-covariates.R b/tests/testthat/test-covariates.R index 8d878c1..15c0837 100644 --- a/tests/testthat/test-covariates.R +++ b/tests/testthat/test-covariates.R @@ -20,8 +20,9 @@ test_that("prepSpreadPredictData builds one row per flammable pixel with this ye ## value of 100 + pixelID proves both the right variable and the right year were taken expect_identical(df$MDC, 100 + 1:15) - ## hand-computed from the toy map: cells 9, 10, 15 are wetland. - expect_identical(which(df$wetland == 1L), c(9L, 10L, 15L)) + ## hand-computed from the toy map: cells 9, 10, 15 are wetland, but 15 is also young (below), and a + ## young pixel is nothing else (fireSenseUtils >= 0.2.3.9048), so its wetland value is 0. + expect_identical(which(df$wetland == 1L), c(9L, 10L)) ## grass is 11 and 12 from the map, PLUS 13 and 14: those two are forested landcover (210) ## with no cohort in `pixelGroupMap`, and `missingLCCgroup` is "grass", so the module ## assigns them there. That is the documented behaviour of `missingLCCgroup` @@ -97,7 +98,8 @@ test_that("igAggFactor 1 leaves the ignition covariates at flammableRTM resoluti ## no aggregation: the same 15 flammable pixels as the spread table, with the same climate expect_identical(df$pixelID, 1:15) expect_equal(df$MDC, 100 + 1:15) - expect_identical(which(df$wetland == 1), c(9L, 10L, 15L)) + ## 15 is wetland but also young, and a young pixel is nothing else + expect_identical(which(df$wetland == 1), c(9L, 10L)) }) test_that("a different ignition climate variable selects a different layer", {