Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
9c44631
declare sensitivity sample dependency
divine7022 Nov 8, 2026
0409089
regenerate sipnet dependency order
divine7022 Nov 8, 2026
3a93ddc
regenerate sipnet dependency list
divine7022 Nov 8, 2026
9c486d1
preserve baseline-only sensitivity manifests
divine7022 Nov 8, 2026
8c9320e
match pft constants and preserve null defaults
divine7022 Nov 8, 2026
41ca5a5
apply sensitivity samples across crop changes
divine7022 Nov 8, 2026
a480583
collect sensitivity output from saved run metadata
divine7022 Nov 8, 2026
6b5f967
retain later crops in sensitivity analysis
divine7022 Nov 8, 2026
07e07a5
test separate sensitivity and ensemble calls
divine7022 Nov 8, 2026
909904c
test later crops and baseline-only designs
divine7022 Nov 8, 2026
78ae9d2
test null defaults and pft constant selection
divine7022 Nov 8, 2026
fc4af05
test crop and soil sensitivity routing
divine7022 Nov 8, 2026
4bfd82d
test later crops and legacy sensitivity bundles
divine7022 Nov 8, 2026
85c83ae
document one analysis per writer call
divine7022 Nov 8, 2026
1325132
document pft constants and null defaults
divine7022 Nov 8, 2026
63f1850
document segmented sensitivity designs
divine7022 Nov 8, 2026
2b8f24d
document sensitivity pft selection
divine7022 Nov 8, 2026
fee7d05
note baseline manifest support for segmentation
divine7022 Nov 8, 2026
e2d4003
note segmented sensitivity support
divine7022 Nov 8, 2026
4bd51fd
note later-crop sensitivity results
divine7022 Nov 8, 2026
5624657
document sensitivity runs across crop changes
divine7022 Nov 8, 2026
3c4a3c2
keep environment samples out of pft constants
divine7022 Nov 9, 2026
528d6b0
cover environment samples in sipnet config writing
divine7022 Nov 9, 2026
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 CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ For more information about this file see also [Keep a Changelog](http://keepacha
- Fixed broken and outdated documentation links in web and demo workflows (#4071).
- Fixed dead MsTMIP variable-standard and PEcAn workflow diagram links in the PEcAn book, tutorial and package documentation (#4071).
- Sensitivity analysis evaluated the splines of traits in Celsius at samples converted to K, about 273 units from where the splines were fit, giving wrong sensitivity, elasticity and variance for those traits. Only CV and elasticity are now computed in K.
- Sensitivity configurations preserve the supplied design rather than restricting it to initial vegetation in `<site.pft>`. Sensitivity analysis selects PFTs from saved run IDs, including crops used later in a rotation.
- Fixed date-based planting and harvest resolution in `PEcAn.data.land::eto_to_etc_bism()` for cross-calendar-year overwinter crops (#4125).
- Fixed broken and outdated links across developer workflows in the PEcAn book and DEV-INTRO.md (#4071).
- Building package documentation sites via `scripts/build_pkgdown.R` no longer exits early when any package gives a build warning (#4034).
Expand Down
2 changes: 1 addition & 1 deletion Makefile.depends
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ $(call depends,models/maespa): | .install/base/logger .install/base/remote .inst
$(call depends,models/peprmt): | .install/base/logger .install/base/remote .install/base/utils
$(call depends,models/rothc): | .install/base/logger .install/base/utils
$(call depends,models/sibcasa): | .install/base/logger
$(call depends,models/sipnet): | .install/base/logger .install/base/remote .install/base/utils .install/modules/data.atmosphere .install/modules/data.land
$(call depends,models/sipnet): | .install/base/logger .install/base/remote .install/base/utils .install/modules/data.atmosphere .install/modules/data.land .install/modules/uncertainty
$(call depends,models/stics): | .install/base/logger .install/base/remote .install/base/settings .install/base/utils
$(call depends,models/template): | .install/base/db .install/base/logger .install/base/utils
$(call depends,modules/allometry): | .install/base/db
Expand Down
1 change: 1 addition & 0 deletions base/workflow/NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
was already required for `sa.samples`) and no longer re-draws it from
`trait.samples`. (#4185)
* `runModule.run.write.configs()` with `overwrite = TRUE` now replaces `runs_manifest.csv` for multisite settings, as it does `runs.txt`. Previously every rerun appended a second copy of each row.
* `run.write.configs()` preserves the supplied sensitivity design, including PFTs used after the initial vegetation in a crop rotation. Baseline-only designs retain a manifest record for segmentation.
* `start_model_runs()` gains argument `check_interval`, setting the time in seconds to wait between queries of job status on queued systems.
* Sensitivity analysis and ensemble runs now generate separate input design matrices with appropriate dimensions, fixing dimension mismatch errors in multisite workflows. (#3708)
* Generated runs are now stored in a `runs_manifest.csv` file in the output directory instead of modifying `samples.Rdata` (#3708)
Expand Down
35 changes: 23 additions & 12 deletions base/workflow/R/run.write.configs.R
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
#' Write model-specific run scripts and configuration files
#'
#' Generates run scripts and configuration files for all analyses (ensemble
#' and/or sensitivity analysis) specified in the provided settings. Delegates
#' the model-specific config writing to the appropriate `write.config.*`
#' function (e.g. `write.config.ED2`, `write.config.SIPNET`).
#' Generates run scripts and configuration files for one ensemble or sensitivity
#' analysis described by `input_design`. Use `runModule.run.write.configs()` to
#' write both analyses. Delegates model-specific configuration to the
#' appropriate `write.config.*` function (e.g. `write.config.ED2`,
#' `write.config.SIPNET`).
#'
#' @details
#' **Upstream contract (reads from `settings$outdir`):**
Expand All @@ -24,8 +25,8 @@
#' \item{`ensemble.samples.<ensemble_id>.Rdata`}{Contains `ens.run.ids`
#' (vector of run IDs), `ens.ensemble.id`, `ens.samples`, `pft.names`,
#' and `trait.names`. Saved when ensemble is configured.}
#' \item{`runs_manifest.csv`}{A CSV table tracking all runs created,
#' appended across ensemble and SA analyses.}
#' \item{`runs_manifest.csv`}{A CSV table tracking runs and their design row
#' indices. Sensitivity baseline rows are repeated for each varying trait.}
#' }
#'
#' **Downstream contract:** The `sensitivity.samples.*.Rdata` and
Expand All @@ -42,7 +43,8 @@
#'
#' @md
#'
#' @param settings a PEcAn settings list
#' @param settings a PEcAn settings list containing either `ensemble` or
#' `sensitivity.analysis`.
#' @param ensemble.size number of ensemble runs
#' @param input_design Input design data.frame coordinating input files across
#' runs. Contains columns for each sampled input (met, param, etc.) with row
Expand All @@ -58,9 +60,8 @@
#' (default), the function falls back to loading `samples.Rdata` from
#' `settings$outdir`.
#'
#' @return The `settings` list (invisibly), updated with ensemble IDs for SA
#' and ensemble analysis (e.g. `settings$sensitivity.analysis$ensemble.id`,
#' `settings$ensemble$ensemble.id`).
#' @return The `settings` list (invisibly), updated with the configured
#' analysis ID.
#' @export
#'
#' @author David LeBauer, Shawn Serbin, Ryan Kelly, Mike Dietze, Akash B V
Expand All @@ -69,6 +70,14 @@ run.write.configs <- function(settings, ensemble.size, input_design, write = TRU
posterior.files = rep(NA, length(settings$pfts)),
overwrite = TRUE, samples = NULL) {

if (all(c("ensemble", "sensitivity.analysis") %in% names(settings))) {
PEcAn.logger::logger.severe(
"run.write.configs() accepts one analysis per call.",
"Use runModule.run.write.configs() or call run.write.configs() separately",
"for ensemble and sensitivity designs."
)
}

# Validate that input_design matches ensemble.size for ensemble runs
# Note: for SA, ensemble.size is not meaningful; SA design size is determined by
# number of (pft, trait, quantile) combinations
Expand Down Expand Up @@ -270,8 +279,10 @@ run.write.configs <- function(settings, ensemble.size, input_design, write = TRU
median_rows <- median_row[rep(1, nrow(traits)), , drop = FALSE]
median_rows$pft_name <- traits$pft_name
median_rows$trait <- traits$trait
median_rows$quantile <- "50"
median_rows$type <- "Sensitivity"
if (nrow(traits) == 0) {
# segmentation still needs the physical baseline run when no traits vary
median_rows <- median_row
}

run_manifest_df <- rbind(
run_manifest_df,
Expand Down
21 changes: 11 additions & 10 deletions base/workflow/man/run.write.configs.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

22 changes: 21 additions & 1 deletion base/workflow/tests/testthat/test.run.write.configs.regression.R
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,26 @@ local_fake_model_writer <- function(env = parent.frame()) {
# input_design / ensemble.size guard
# ------------------------------------

test_that("run.write.configs rejects more than one analysis per call", {
tmp <- withr::local_tempdir()
settings <- make_rwc_settings(
tmp,
with_ensemble = TRUE,
with_sa = TRUE
)

expect_error(
PEcAn.workflow::run.write.configs(
settings,
ensemble.size = 1,
input_design = data.frame(param = 1),
write = FALSE
),
"one analysis per call"
)
expect_length(list.files(tmp, all.files = TRUE, no.. = TRUE), 0)
})

test_that("run.write.configs errors when input_design rows != ensemble.size", {
tmp <- withr::local_tempdir()
settings <- make_rwc_settings(tmp, with_ensemble = TRUE)
Expand Down Expand Up @@ -274,4 +294,4 @@ test_that("sensitivity.samples.<id>.Rdata keeps its 5-object save contract", {
expect_equal(e$sa.run.ids$temperate.deciduous["50", "Vcmax"], "r1")
expect_equal(e$sa.run.ids$temperate.deciduous["2.5", "Vcmax"], "r2")
expect_equal(e$sa.run.ids$temperate.deciduous["97.5", "Vcmax"], "r3")
})
})
139 changes: 138 additions & 1 deletion base/workflow/tests/testthat/test_run.write.configs_multisite.R
Original file line number Diff line number Diff line change
Expand Up @@ -173,4 +173,141 @@ test_that("run.write.configs appends to manifest when overwrite = FALSE", {

# no duplicate column headers from append
expect_false(any(merged_manifest$run_id == "run_id"))
})
})


test_that("run.write.configs preserves later PFTs and an explicit design subset", {
assign("write.config.FAKE", function(...) invisible(NULL), envir = .GlobalEnv)
withr::defer(rm("write.config.FAKE", envir = .GlobalEnv), priority = "first")

env <- make_test_env()
# pftA is initial vegetation; pftB may be planted in a later segment
trait.samples <- list(pftA = list(Vcmax = c(40, 45)), pftB = list(Vcmax = c(60, 65)))
sa.samples <- lapply(c(pftA = 42, pftB = 62), function(v) {
matrix(c(v, v + 6), nrow = 2, ncol = 1, dimnames = list(c("50", "95"), "Vcmax"))
})
runs.samples <- list()
pft.names <- names(trait.samples)
trait.names <- lapply(trait.samples, names)
save(trait.samples, sa.samples, runs.samples, pft.names, trait.names,
file = file.path(env$workflow_root, "samples.Rdata"))
settings <- make_settings(env, "1001")
settings$pfts <- list(list(name = "pftA"), list(name = "pftB"))

input_design <- data.frame(
param = c(1, 2, 3),
met = c(1, 1, 1),
sa_pft = c(NA, "pftA", "pftB"),
sa_trait = c(NA, "Vcmax", "Vcmax"),
sa_quantile = c("50", "95", "95"),
stringsAsFactors = FALSE
)

run_write_configs <- PEcAn.workflow::run.write.configs
mockery::stub(run_write_configs, "PEcAn.utils::load.modelpkg", function(...) invisible(NULL))

run_write_configs(
settings = settings,
ensemble.size = 1,
input_design = input_design,
write = FALSE,
overwrite = TRUE
)

expect_length(readLines(file.path(env$rundir, "runs.txt")), 3)
manifest <- utils::read.csv(env$manifest_file, stringsAsFactors = FALSE)
expect_setequal(manifest$pft_name, c("pftA", "pftB"))
expect_equal(manifest$ens_num, c(2, 3, 1, 1))

settings$run$site$id <- "1002"
run_write_configs(
settings = settings,
ensemble.size = 1,
input_design = input_design[1:2, ],
write = FALSE,
overwrite = TRUE
)
manifest <- utils::read.csv(env$manifest_file, stringsAsFactors = FALSE)
expect_setequal(manifest$pft_name, "pftA")
})


test_that("run.write.configs handles a site with no varying traits", {
assign("write.config.FAKE", function(...) invisible(NULL), envir = .GlobalEnv)
withr::defer(rm("write.config.FAKE", envir = .GlobalEnv), priority = "first")

env <- make_test_env()
settings <- make_settings(env, "1001")
settings$pfts <- list(
list(name = "pftA", constants = list(Vcmax = 42)),
list(name = "pftB", constants = list())
)

samples <- list(
trait.samples = list(
pftA = list(),
pftB = list(Vcmax = c(60, 65))
),
sa.samples = list(
pftA = matrix(
numeric(),
nrow = 2,
ncol = 0,
dimnames = list(c("50", "95"), character())
),
pftB = matrix(
c(62, 68),
nrow = 2,
ncol = 1,
dimnames = list(c("50", "95"), "Vcmax")
)
),
ensemble.samples = list(),
runs.samples = list(),
env.samples = list()
)
design <- PEcAn.uncertainty::generate_OAT_SA_design(
settings,
samples = samples
)$design_matrix
design <- design[is.na(design$sa_pft), , drop = FALSE]

run_write_configs <- PEcAn.workflow::run.write.configs
mockery::stub(
run_write_configs,
"PEcAn.utils::load.modelpkg",
function(...) invisible(NULL)
)

run_write_configs(
settings = settings,
ensemble.size = 1,
input_design = design,
write = FALSE,
overwrite = TRUE,
samples = samples
)

expect_length(readLines(file.path(env$rundir, "runs.txt")), 1)

manifest <- utils::read.csv(env$manifest_file, stringsAsFactors = FALSE)
expect_equal(nrow(manifest), 1)
expect_true(is.na(manifest$pft_name))
expect_equal(manifest$quantile, 50)
expect_equal(manifest$type, "Sensitivity")
expect_named(
manifest,
c("run_id", "site_id", "ens_num", "pft_name", "trait", "quantile", "type")
)

samples_file <- list.files(
env$workflow_root,
pattern = "^sensitivity\\.samples\\..*\\.Rdata$",
full.names = TRUE
)
expect_length(samples_file, 1)

saved <- new.env(parent = emptyenv())
load(samples_file, envir = saved)
expect_identical(saved$sa.run.ids, list())
})
1 change: 1 addition & 0 deletions docker/depends/pecan_package_dependencies.csv
Original file line number Diff line number Diff line change
Expand Up @@ -420,6 +420,7 @@
"PEcAn.SIPNET","*","base/qaqc","Suggests",TRUE
"PEcAn.uncertainty","*","base/all","Depends",TRUE
"PEcAn.uncertainty","*","base/workflow","Imports",TRUE
"PEcAn.uncertainty","*","models/sipnet","Imports",TRUE
"PEcAn.uncertainty","*","modules/assim.batch","Imports",TRUE
"PEcAn.uncertainty","*","modules/assim.sequential","Imports",TRUE
"PEcAn.utils","*","base/all","Depends",TRUE
Expand Down
1 change: 1 addition & 0 deletions models/sipnet/DESCRIPTION
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ Imports:
PEcAn.data.atmosphere,
PEcAn.data.land,
PEcAn.logger,
PEcAn.uncertainty,
rlang,
stats,
stringr,
Expand Down
6 changes: 6 additions & 0 deletions models/sipnet/NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,12 @@
CF convention, and the name `cdo` already produces when merging
sub-annual outputs) instead of `time_bounds`. `read_restart.SIPNET`
reads `time_bnds` and falls back to `time_bounds` for older outputs (#3303).
* `write_segmented_configs.SIPNET()` accepts OAT sensitivity designs and applies
each run's sampled parameters to the crops active in its segments. Shared
baseline runs are segmented once, and crop intervals are clipped to the run window.
* `write.config.SIPNET()` matches constants to PFT names instead of applying the
first PFT's constants to every crop and soil. A single unnamed defaults entry
retains its shared-constants behavior.
* Added regression coverage for SIPNET event-JSON segmentation and segmented restart chaining (#4021).
* `write.config.SIPNET` now maps the `leafNResorptionFrac` trait to its v2
parameter, so PFT supplied values reach the param file instead of
Expand Down
Loading
Loading