Skip to content

Commit f357f4e

Browse files
authored
Merge branch 'develop' into phase4-benchmarking-integration
2 parents f4d049c + 52f9183 commit f357f4e

6 files changed

Lines changed: 168 additions & 29 deletions

File tree

models/sipnet/NEWS.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,16 @@
11
# PEcAn.SIPNET 1.10.0.9000
22

3+
* Improvements to the job.sh written by `write_segmented_configs`:
4+
- Now places README.txt, segments.csv, and the full log files from each
5+
segment, into the outdir (as was already done for one-segment runs).
6+
- Now respects `settings$model$delete.raw` by deleting sipnet.out from segment
7+
dirs as well as the from the job outdir
8+
- No longer calls model2netcdf.SIPNET() inside each segment dir (it was
9+
redundant with the whole-job netcdf output).
10+
- Fixed incorrect run paths in the settings$host block passed to single segments
11+
* `model2netcdf.SIPNET` takes `LAI` from sipnet.out if present (which requires
12+
Sipnet > v2.2). If it is not present, LAI is calculated as
13+
`plantLeafC / leafCSpWt` as previously.
314
* `split_inputs.SIPNET` now avoids internal time format conversions, giving a
415
substantial speedup and reduced memory use when processing multi-year files.
516
* `model2netcdf.SIPNET` now detects the number of timesteps per day by taking the maximum count across all days in the first simulation year, rather than reading only from day 1. This prevents a factor-of-N error in flux unit conversions when the first day of output is partial (fewer timesteps than a complete day) (#3624, #3989).

models/sipnet/R/model2netcdf.SIPNET.R

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -150,16 +150,21 @@ model2netcdf.SIPNET <- function(outdir, sitelat, sitelon, start_date, end_date,
150150
)
151151

152152

153-
# calculate LAI for standard output
154-
# LAI = plantLeafC / leafCSpWt
155-
# both operands are in carbon units (gC/m2 and gC/m2_leaf),
156-
# so no carbon fraction conversion (e.g. cFracLeaf) is needed.
157-
param <- utils::read.table(file.path(gsub(pattern = "/out/",
158-
replacement = "/run/", x = outdir),
159-
"sipnet.param"), stringsAsFactors = FALSE)
160-
leafCSpWt <- param[param[, 1] == "leafCSpWt", 2]
161-
SLA <- 1000 / leafCSpWt # m2 leaf / kg C
162-
153+
if (!("LAI" %in% colnames(sipnet_output))) {
154+
# When LAI not reported, calculate it from leaf C and leaf specific weight,
155+
# with the latter read from the parameter file.
156+
# Note the hardcoded + undocumented assumption that ../run/sipnet.param
157+
# exists and contains the values that were used to generate this output.
158+
# LAI = plantLeafC [gC/m2] / leafCSpWt [gC/m2 leaf] = m2 leaf / m2
159+
# both operands are in carbon units (gC/m2 and gC/m2_leaf),
160+
# so no carbon fraction conversion (e.g. cFracLeaf) is needed.
161+
param <- utils::read.table(file.path(gsub(pattern = "/out/",
162+
replacement = "/run/", x = outdir),
163+
"sipnet.param"), stringsAsFactors = FALSE)
164+
leafCSpWt <- param[param[, 1] == "leafCSpWt", 2]
165+
SLA <- 1000 / leafCSpWt # m2 leaf / kg C
166+
sipnet_output$LAI = sipnet_output$plantLeafC * SLA
167+
}
163168

164169
### Loop over years in SIPNET output to create separate netCDF outputs
165170
for (y in year_seq) {
@@ -208,7 +213,7 @@ model2netcdf.SIPNET <- function(outdir, sitelat, sitelon, start_date, end_date,
208213
"litter_carbon_content" = sub.sipnet.output$litter,
209214
"fine_root_carbon_content" = sub.sipnet.output$fineRootC,
210215
"coarse_root_carbon_content" = sub.sipnet.output$coarseRootC,
211-
"LAI" = sub.sipnet.output$plantLeafC * SLA,
216+
"LAI" = sub.sipnet.output$LAI,
212217
"TotLivBiom" = sub.sipnet.output$plantWoodC + sub.sipnet.output$plantLeafC +
213218
sub.sipnet.output$coarseRootC + sub.sipnet.output$fineRootC,
214219
"TotSoilCarb" = sub.sipnet.output$soil + sub.sipnet.output$litter,

models/sipnet/R/write.configs.SIPNET.R

Lines changed: 18 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,16 @@ write.config.SIPNET <- function(defaults, trait.values, settings, run.id, inputs
121121
has_microbeInit = rev_str == "v1"
122122
)
123123

124+
125+
# find out where to write run/ouput
126+
rundir <- file.path(settings$host$rundir, as.character(run.id))
127+
outdir <- file.path(settings$host$outdir, as.character(run.id))
128+
if (is.null(settings$host$qsub) && (settings$host$name == "localhost")) {
129+
rundir <- file.path(settings$rundir, as.character(run.id))
130+
outdir <- file.path(settings$modeloutdir, as.character(run.id))
131+
}
132+
133+
124134
### WRITE sipnet.in
125135
template.in <- system.file(
126136
paste0("sipnet.in_", rev_str),
@@ -140,7 +150,7 @@ write.config.SIPNET <- function(defaults, trait.values, settings, run.id, inputs
140150
}
141151
config.text <- update_flag_lines(config.text, user_flags)
142152

143-
writeLines(config.text, con = file.path(settings$rundir, run.id, "sipnet.in"))
153+
writeLines(config.text, con = file.path(rundir, "sipnet.in"))
144154

145155
### WRITE *.clim
146156
template.clim <- settings$run$inputs$met$path ## read from settings
@@ -152,14 +162,6 @@ write.config.SIPNET <- function(defaults, trait.values, settings, run.id, inputs
152162
}
153163
PEcAn.logger::logger.info(paste0("Writing SIPNET configs with input ", template.clim))
154164

155-
# find out where to write run/ouput
156-
rundir <- file.path(settings$host$rundir, as.character(run.id))
157-
outdir <- file.path(settings$host$outdir, as.character(run.id))
158-
if (is.null(settings$host$qsub) && (settings$host$name == "localhost")) {
159-
rundir <- file.path(settings$rundir, as.character(run.id))
160-
outdir <- file.path(settings$modeloutdir, as.character(run.id))
161-
}
162-
163165
# create launch script (which will create symlink)
164166
if (!is.null(settings$model$jobtemplate) && file.exists(settings$model$jobtemplate)) {
165167
jobsh <- readLines(con = settings$model$jobtemplate, n = -1)
@@ -251,8 +253,8 @@ write.config.SIPNET <- function(defaults, trait.values, settings, run.id, inputs
251253
}
252254
jobsh <- gsub("@DELETE.RAW@", settings$model$delete.raw, jobsh)
253255

254-
writeLines(jobsh, con = file.path(settings$rundir, run.id, "job.sh"))
255-
Sys.chmod(file.path(settings$rundir, run.id, "job.sh"))
256+
writeLines(jobsh, con = file.path(rundir, "job.sh"))
257+
Sys.chmod(file.path(rundir, "job.sh"))
256258

257259

258260
### Copy event file
@@ -268,7 +270,7 @@ write.config.SIPNET <- function(defaults, trait.values, settings, run.id, inputs
268270
### WRITE *.param-spatial
269271
if (caps$has_param_spatial) {
270272
template.paramSpatial <- system.file("template.param-spatial", package = "PEcAn.SIPNET")
271-
file.copy(template.paramSpatial, file.path(settings$rundir, run.id, "sipnet.param-spatial"))
273+
file.copy(template.paramSpatial, file.path(rundir, "sipnet.param-spatial"))
272274
}
273275

274276
### WRITE *.param
@@ -1028,12 +1030,11 @@ write.config.SIPNET <- function(defaults, trait.values, settings, run.id, inputs
10281030
}
10291031

10301032
}
1031-
if (file.exists(file.path(settings$rundir, run.id, "sipnet.param"))) {
1033+
if (file.exists(file.path(rundir, "sipnet.param"))) {
10321034
file.rename(
1033-
file.path(settings$rundir, run.id, "sipnet.param"),
1035+
file.path(rundir, "sipnet.param"),
10341036
file.path(
1035-
settings$rundir,
1036-
run.id,
1037+
rundir,
10371038
paste0("sipnet_", lubridate::year(settings$run$start.date), "_", lubridate::year(settings$run$end.date), ".param")
10381039
)
10391040
)
@@ -1042,7 +1043,7 @@ write.config.SIPNET <- function(defaults, trait.values, settings, run.id, inputs
10421043

10431044
utils::write.table(
10441045
param,
1045-
file.path(settings$rundir, run.id, "sipnet.param"),
1046+
file.path(rundir, "sipnet.param"),
10461047
row.names = FALSE,
10471048
col.names = FALSE,
10481049
quote = FALSE

models/sipnet/R/write_segmented_configs.R

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,15 @@ write_segmented_configs.SIPNET <- function(settings, input_design = NULL, ...) {
9797
by = "ens_num",
9898
relationship = "many-to-one")
9999
}
100+
# Use a custom job.sh template with no model2netcdf step;
101+
# we convert all segments together at the end of the timeseries instead.
102+
# ...unless user provided their own template, in which case use it as-is
103+
if (is.null(settings$model$jobtemplate) || !file.exists(settings$model$jobtemplate)) {
104+
settings$model$jobtemplate <- system.file(
105+
"template_singlesegment.job",
106+
package = "PEcAn.SIPNET"
107+
)
108+
}
100109

101110
new_jobfiles <- character()
102111

@@ -218,6 +227,7 @@ write_segment_configs <- function(
218227
)
219228

220229
jobsh_files <- character()
230+
job_logfiles <- character()
221231

222232
for (isegment in seq_len(nrow(segments))) {
223233
segment <- segments[isegment, ]
@@ -259,6 +269,18 @@ write_segment_configs <- function(
259269
segment_settings[["outdir"]] <- segment_outdir
260270
segment_settings[["modeloutdir"]] <- segment_outdir
261271
segment_settings[["rundir"]] <- segment_rundir
272+
segment_settings[[c("host", "rundir")]] <- sub(
273+
pattern = settings$rundir,
274+
replacement = settings$host$rundir,
275+
x = segment_rundir,
276+
fixed = TRUE
277+
)
278+
segment_settings[[c("host", "outdir")]] <- sub(
279+
pattern = settings$modeloutdir,
280+
replacement = settings$host$outdir,
281+
x = segment_outdir,
282+
fixed = TRUE
283+
)
262284
segment_settings[[c("run", "start.date")]] <- dstart
263285
segment_settings[[c("run", "end.date")]] <- dend
264286
segment_settings[[c("run", "inputs")]] <- segment_inputs
@@ -291,12 +313,19 @@ write_segment_configs <- function(
291313
run.id = runid_dummy
292314
)
293315

316+
segment_log <- file.path(segment_settings$modeloutdir,
317+
runid_dummy,
318+
"logfile.txt")
294319
segment_jobsh <- file.path(segment_settings$rundir, runid_dummy, "job.sh")
295320
stopifnot(file.exists(segment_jobsh))
321+
job_logfiles <- c(job_logfiles, segment_log)
296322
jobsh_files <- c(jobsh_files, segment_jobsh)
297323
}
298324

299325
# Now, get the run's jobsh file
326+
# NB this doesn't do any path expansion or @TEMPLATE@ string replacement --
327+
# those are done in the per-segment job.sh.
328+
# Note especially that means no @HOST_SETUP@ / @HOST_TEARDOWN@ in this script
300329
run_jobsh <- file.path(run_dir, "job.sh")
301330
target_sipnet_out <- file.path(run_modeloutdir, "sipnet.out")
302331
segmented_jobsh_file <- file.path(run_dir, "job_segmented.sh")
@@ -326,10 +355,31 @@ write_segment_configs <- function(
326355
sprintf("sitelon = %s", as.character(settings$run$site$lon)),
327356
sprintf("start_date = %s", shQuote(settings$run$start.date)),
328357
sprintf("end_date = %s", shQuote(settings$run$end.date)),
358+
sprintf("delete.raw = %s", shQuote(settings$model$delete.raw)),
329359
sprintf("revision = %s", shQuote(settings$model$revision)),
330360
sep = ", "
331361
)
332-
)
362+
),
363+
"",
364+
"# copy readme with specs to output",
365+
paste("cp", file.path(run_dir, "README.txt"), file.path(run_modeloutdir, "README.txt")),
366+
paste("cp", file.path(run_dir, "segments.csv"), file.path(run_modeloutdir, "segments.csv")),
367+
"",
368+
"# Concatenate segment log files",
369+
paste("echo \"\n--> contents of", job_logfiles, ":\" && cat", job_logfiles),
370+
"",
371+
if (isTRUE(as.logical(settings$model$delete.raw))) {
372+
# Removing all outputs that have been safely copied to the outdir
373+
c(
374+
"# Remove per-segment outputs & logs after concatenating to job outdir",
375+
sprintf("find %s -name sipnet.out -delete", segment_rootdir),
376+
sprintf("find %s -name logfile.txt -delete", segment_rootdir),
377+
sprintf("rm %s/README.txt", run_dir),
378+
sprintf("rm %s/segments.csv", run_dir)
379+
)
380+
},
381+
"",
382+
"echo -e \"MODEL FINISHED\nLogfile is located at '${OUTDIR}/logfile.txt'\" >&3"
333383
)
334384
writeLines(segmented_jobsh_lines, segmented_jobsh_file)
335385
if (replace_and_link) {
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
#!/bin/bash
2+
3+
# redirect output
4+
exec 3>&1
5+
exec &> "@OUTDIR@/logfile.txt"
6+
7+
# host specific setup
8+
@HOST_SETUP@
9+
10+
# cdo setup
11+
@CDO_SETUP@
12+
13+
# create output folder
14+
mkdir -p "@OUTDIR@"
15+
16+
# Convert any relative paths to absolute
17+
# (otherwise we'd lose track of them when cd'ing into rundir)
18+
OUTDIR=$(cd "@OUTDIR@" && pwd -P)
19+
RUNDIR=$(cd "@RUNDIR@" && pwd -P)
20+
SITE_MET=$(cd $(dirname "@SITE_MET@") && pwd -P)/$(basename "@SITE_MET@")
21+
BINARY=$(cd $(dirname "@BINARY@") && pwd -P)/$(basename "@BINARY@")
22+
23+
# see if application needs running
24+
if [ ! -e "${OUTDIR}/sipnet.out" ]; then
25+
cd "$RUNDIR"
26+
ln -s "${SITE_MET}" sipnet.clim
27+
28+
"${BINARY}"
29+
STATUS=$?
30+
31+
# copy output
32+
mv "${RUNDIR}/sipnet.out" "$OUTDIR"
33+
34+
# check the status
35+
if [ $STATUS -ne 0 ]; then
36+
echo -e "ERROR IN MODEL RUN\nLogfile is located at '${OUTDIR}/logfile.txt'" >&3
37+
exit $STATUS
38+
fi
39+
40+
# conversion to MsTMIP not done in this single-segment step.
41+
# The whole-job script will handle conversion after all segments have run.
42+
fi
43+
44+
# copy readme with specs to output
45+
cp "${RUNDIR}/README.txt" "${OUTDIR}/README.txt"
46+
47+
# run getdata to extract right variables
48+
49+
# host specific teardown
50+
@HOST_TEARDOWN@
51+
52+
# all done
53+
echo -e "MODEL FINISHED\nLogfile is located at '${OUTDIR}/logfile.txt'" >&3

models/sipnet/tests/testthat/test-model2netcdf.SIPNET.R

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -291,3 +291,22 @@ test_that("out_day is correct when the first day of output is partial", {
291291
ts_correct <- 86400 / 2
292292
expect_equal(gpp, rep(base_row$gpp * 1e-3 / ts_correct, 5), tolerance = 1e-10)
293293
})
294+
295+
test_that("LAI taken from leafCSpWt if not present in output", {
296+
dat <- make_v2_sipnet(2)
297+
# setup_sipnet_test sets leafCSpWt = 32 => expect LAI of 1 and 10
298+
dat$plantLeafC = c(32, 320)
299+
300+
# First without LAI in dat => looks for sipnet.param in run dir
301+
paths <- setup_sipnet_test(dat, notes_line = NULL)
302+
nc1 <- ncdf4::nc_open(file.path(paths$outdir, "2002.nc"))
303+
on.exit(ncdf4::nc_close(nc1), add = TRUE)
304+
expect_equal(as.vector(ncdf4::ncvar_get(nc1, "LAI")), c(1, 10))
305+
306+
# Now with LAI specified => uses it as-is
307+
dat$LAI = c(2, 3)
308+
paths <- setup_sipnet_test(dat, notes_line = NULL)
309+
nc2 <- ncdf4::nc_open(file.path(paths$outdir, "2002.nc"))
310+
on.exit(ncdf4::nc_close(nc2), add = TRUE)
311+
expect_equal(as.vector(ncdf4::ncvar_get(nc2, "LAI")), c(2, 3))
312+
})

0 commit comments

Comments
 (0)