Skip to content

Commit 3097520

Browse files
authored
Merge branch 'develop' into automated-reporting-and-visualization
2 parents 73319c7 + 8b4e720 commit 3097520

12 files changed

Lines changed: 535 additions & 198 deletions

base/workflow/R/runModule.run.write.configs.R

Lines changed: 40 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,12 @@
1414
#' @param overwrite logical: Replace config files if they already exist?
1515
#' @param input_design Optional. The parameter/input design for the runs,
1616
#' normally the full result of \code{generate_joint_ensemble_design()}: a list
17-
#' with \code{X} (a data.frame whose \code{param} column selects rows of
17+
#' with \code{design_matrix} (a data.frame whose \code{param} column selects rows of
1818
#' \code{trait.samples}/\code{ensemble.samples}, plus optional columns named
1919
#' for \code{settings$run$inputs} tags such as \code{met} or \code{soil}) and
2020
#' \code{samples} (the parameter bundle those indices point into). Can be:
2121
#' \itemize{
22-
#' \item The \code{list(X, samples)} returned by
22+
#' \item The \code{list(design_matrix, samples)} returned by
2323
#' \code{generate_joint_ensemble_design()}
2424
#' \item \code{NULL} to generate the design and samples internally from
2525
#' \code{settings}
@@ -161,9 +161,10 @@ runModule.run.write.configs <- function(settings,
161161
#' \item If \code{input_design} is already a list with
162162
#' \code{ensemble}/\code{sensitivity} keys (e.g. threaded from a
163163
#' MultiSettings parent), return as-is.
164-
#' \item If \code{input_design} is the \code{list(X, samples)} from
165-
#' \code{generate_joint_ensemble_design()}, use \code{X} as the ensemble
166-
#' design and \code{samples} as the bundle (no resampling).
164+
#' \item If \code{input_design} is the \code{list(design_matrix, samples)}
165+
#' from \code{generate_joint_ensemble_design()}, use the design matrix
166+
#' as the ensemble design and \code{samples} as the bundle (no
167+
#' resampling). \code{X} is accepted as the older name for it.
167168
#' \item If \code{input_design} is a bare data.frame (a design without its
168169
#' samples), raise an error: the design's \code{param} indices only match
169170
#' the samples they were drawn with.
@@ -191,24 +192,32 @@ runModule.run.write.configs <- function(settings,
191192
# into the samples it was drawn with, so a design must arrive together with
192193
# those samples; otherwise it would be silently paired with a fresh, mismatched
193194
# resample. We therefore accept the full generate_joint_ensemble_design()
194-
# result (a list with X and samples) and reject a bare design.
195+
# result (a list with design_matrix and samples) and reject a bare design.
195196
if (!is.null(input_design)) {
196-
if (is.list(input_design) && !is.data.frame(input_design) &&
197-
all(c("X", "samples") %in% names(input_design)) &&
198-
!is.null(input_design$samples)) {
199-
designs$ensemble <- input_design$X
197+
# Generators return the design as `design_matrix`. `X` is the older name for
198+
# the same matrix, kept so existing callers keep working, and is what
199+
# sensitivity sets on a sobol object.
200+
supplied_design <- if (is.list(input_design) && !is.data.frame(input_design)) {
201+
input_design[["design_matrix"]] %||% input_design[["X"]]
202+
} else {
203+
NULL
204+
}
205+
206+
if (!is.null(supplied_design) && !is.null(input_design$samples)) {
207+
designs$ensemble <- supplied_design
200208
supplied_samples <- input_design$samples
201209
} else if (is.data.frame(input_design)) {
202210
PEcAn.logger::logger.severe(
203211
"input_design was supplied without its parameter samples.",
204212
"Pass the full generate_joint_ensemble_design() result",
205-
"(a list with `X` and `samples`) so the design's `param` indices match",
206-
"the samples, or leave input_design = NULL to generate both together."
213+
"(a list with `design_matrix` and `samples`) so the design's `param`",
214+
"indices match the samples, or leave input_design = NULL to generate",
215+
"both together."
207216
)
208217
} else {
209218
PEcAn.logger::logger.severe(
210-
"Unrecognized input_design format. Expected NULL or the list(X, samples)",
211-
"returned by generate_joint_ensemble_design()."
219+
"Unrecognized input_design format. Expected NULL or the",
220+
"list(design_matrix, samples) returned by generate_joint_ensemble_design()."
212221
)
213222
}
214223
}
@@ -252,6 +261,20 @@ runModule.run.write.configs <- function(settings,
252261
designs$samples <- samples
253262
}
254263

264+
# Deprecation: internal design generation is going away. Passing input_design
265+
# explicitly (the generate_joint_ensemble_design() result) will become the
266+
# required path. Warn only when we are actually about to auto-generate.
267+
auto_generating <-
268+
(is.null(designs$ensemble) && need_ensemble) ||
269+
(is.null(designs$sensitivity) && need_sa)
270+
if (auto_generating) {
271+
PEcAn.logger::logger.warn(
272+
"Internal input design generation is deprecated and will be removed.",
273+
"Pass input_design explicitly as the list(design_matrix, samples) returned",
274+
"by generate_joint_ensemble_design(); this will become required."
275+
)
276+
}
277+
255278
# Generate the ensemble design only when the caller did not supply one,
256279
# handing over the resolved samples so the generator does not resample.
257280
if (is.null(designs$ensemble) && need_ensemble) {
@@ -260,17 +283,17 @@ runModule.run.write.configs <- function(settings,
260283
ensemble_size = ensemble_size,
261284
samples = designs$samples
262285
)
263-
designs$ensemble <- design_result$X
286+
designs$ensemble <- design_result$design_matrix %||% design_result$X
264287
}
265288

266289
# Generate the SA design if needed, threading the SA samples so the generator
267290
# uses them directly instead of re-reading samples.Rdata via the deprecated path.
268291
if (is.null(designs$sensitivity) && need_sa) {
269292
design_result <- PEcAn.uncertainty::generate_OAT_SA_design(
270293
settings,
271-
sa_samples = designs$samples$sa.samples
294+
samples = designs$samples
272295
)
273-
designs$sensitivity <- design_result$X
296+
designs$sensitivity <- design_result$design_matrix %||% design_result$X
274297
}
275298

276299
return(designs)

base/workflow/man/dot-prepare_input_designs.Rd

Lines changed: 4 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

base/workflow/man/runModule.run.write.configs.Rd

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

base/workflow/tests/testthat/test-runModule.run.write.configs.R

Lines changed: 75 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -182,9 +182,13 @@ test_that(".prepare_input_designs threads SA samples into the OAT generator", {
182182
gps_args <- mockery::mock_args(gps)[[1]]
183183
expect_false(gps_args$do_ensemble)
184184

185-
# the OAT generator receives the SA samples directly (no disk re-read).
185+
# the OAT generator receives the full sample bundle directly (no disk
186+
# re-read), and uses sa.samples from it
186187
oat_args <- mockery::mock_args(oat)[[1]]
187-
expect_identical(oat_args$sa_samples, sa_bundle$sa.samples)
188+
# generate_OAT_SA_design(settings, samples = designs$samples): settings is
189+
# positional (first), the bundle comes through named `samples`
190+
# OAT is handed the resolved bundle itself (so it never re-reads samples.Rdata)
191+
expect_equal(oat_args$samples, designs$samples)
188192

189193
expect_equal(designs$sensitivity, data.frame(param = 1:4))
190194
expect_true(file.exists(file.path(tmp, "samples.Rdata")))
@@ -213,4 +217,73 @@ test_that(".prepare_input_designs rejects a design whose samples are NULL", {
213217
mockery::expect_called(loader, 0)
214218
mockery::expect_called(gps, 0)
215219
mockery::expect_called(gen, 0)
220+
})
221+
222+
test_that(".prepare_input_designs warns when it auto-generates a design", {
223+
tmp <- withr::local_tempdir()
224+
settings <- make_prep_settings(tmp)
225+
226+
mockery::stub(.prepare_input_designs,
227+
"PEcAn.uncertainty::load_pft_posteriors",
228+
function(...) fake_loaded())
229+
mockery::stub(.prepare_input_designs,
230+
"PEcAn.uncertainty::get_parameter_samples",
231+
function(...) fake_bundle())
232+
mockery::stub(.prepare_input_designs,
233+
"PEcAn.uncertainty::generate_joint_ensemble_design",
234+
function(...) list(X = data.frame(param = 1:3)))
235+
236+
msgs <- capture.output(
237+
invisible(.prepare_input_designs(settings, input_design = NULL)),
238+
type = "message"
239+
)
240+
expect_match(paste(msgs, collapse = "\n"), "deprecated", all = FALSE)
241+
})
242+
243+
test_that(".prepare_input_designs does not warn when a design is supplied", {
244+
tmp <- withr::local_tempdir()
245+
settings <- make_prep_settings(tmp)
246+
247+
supplied <- list(X = data.frame(param = 1:3), samples = fake_bundle())
248+
249+
msgs <- capture.output(
250+
invisible(.prepare_input_designs(settings, input_design = supplied)),
251+
type = "message"
252+
)
253+
expect_false(any(grepl("deprecated", msgs)))
254+
})
255+
256+
test_that(".prepare_input_designs accepts a design supplied as design_matrix", {
257+
tmp <- withr::local_tempdir()
258+
settings <- make_prep_settings(tmp)
259+
260+
bundle <- fake_bundle()
261+
supplied <- list(design_matrix = data.frame(param = 1:3), samples = bundle)
262+
263+
gen <- mockery::mock()
264+
mockery::stub(.prepare_input_designs,
265+
"PEcAn.uncertainty::generate_joint_ensemble_design", gen)
266+
267+
designs <- .prepare_input_designs(settings, input_design = supplied)
268+
269+
mockery::expect_called(gen, 0)
270+
expect_identical(designs$ensemble, supplied$design_matrix)
271+
expect_identical(designs$samples, bundle)
272+
})
273+
274+
test_that(".prepare_input_designs still accepts a design supplied as X", {
275+
tmp <- withr::local_tempdir()
276+
settings <- make_prep_settings(tmp)
277+
278+
bundle <- fake_bundle()
279+
supplied <- list(X = data.frame(param = 1:3), samples = bundle)
280+
281+
gen <- mockery::mock()
282+
mockery::stub(.prepare_input_designs,
283+
"PEcAn.uncertainty::generate_joint_ensemble_design", gen)
284+
285+
designs <- .prepare_input_designs(settings, input_design = supplied)
286+
287+
mockery::expect_called(gen, 0)
288+
expect_identical(designs$ensemble, supplied$X)
216289
})

0 commit comments

Comments
 (0)