Skip to content

Commit 4ff94ed

Browse files
committed
Merge branch 'evi_helpers' of https://github.com/abbylewis/pecan into evi_helpers
2 parents 88eda28 + 4c1dd47 commit 4ff94ed

16 files changed

Lines changed: 313 additions & 83 deletions

File tree

book_source/03_topical_pages/06_data/01_meteorology.Rmd

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -153,8 +153,8 @@ Availability: 1950-present
153153

154154
Notes:
155155

156-
It's important to know that the raw ERA5 tiles needs to be downloaded and registered in the database first. Inside the `inst` folder in the data.atmosphere package there are R files for downloading and registering files in the BETY. However, it assumes that you have registered and setup your API requirements. Check out how to setup your API [here] (https://confluence.ecmwf.int/display/CKB/How+to+download+ERA5).
157-
In the `inst` folder you can find two files (`ERA5_db_register.R` and `ERA5_USA_download.R`). If you setup your `ecmwf` account as it's explained in the link above, `ERA5_USA_download.R` will help you to download all the tiles with all the variables required for pecan `extract.nc.ERA5` function to generate pecan standard met files. Besides installing the required packages for this file, it should work from top to bottom with no problem. After downloading the tiles, there is simple script in `ERA5_db_register.R` which helps you register your tiles in the bety. `met.process` later on uses that entery to find the required tiles for extracting met data for your sites. There are important points about this file. 1- Make sure you don't change the site id in the script (which is the same the `ParentSite` in ERA5 registeration xml file). 2- Make sure the start and end date in that script matches the downloaded tiles. Set your `ERA5.files.path` to where you downloaded the tiles and then the rest of the script should be working fine.
156+
It's important to know that the raw ERA5 tiles needs to be downloaded and registered in the database first. Inside the `inst` folder in the data.atmosphere package there are R files for downloading and registering files in the BETY. However, it assumes that you have registered and setup your API requirements. The download uses the `ecmwfr` package, so you need the UID and API key from your [CDS profile page](https://cds.climate.copernicus.eu/profile), which you pass as the `user` and `key` arguments. Note that `ecmwfr` stores the key using `keyring`; on a headless machine there is no OS keyring and the default backend will prompt for a password, so set `Sys.setenv(R_KEYRING_BACKEND = "env")` first.
157+
In the `inst` folder you can find two files (`ERA5_db_register.R` and `ERA5_NA_download.R`). Once your CDS credentials are set up, `ERA5_NA_download.R` will help you to download all the tiles with all the variables required for pecan `extract.nc.ERA5` function to generate pecan standard met files. Besides installing the required packages for this file, it should work from top to bottom with no problem. After downloading the tiles, there is simple script in `ERA5_db_register.R` which helps you register your tiles in the bety. `met.process` later on uses that entery to find the required tiles for extracting met data for your sites. There are important points about this file. 1- Make sure you don't change the site id in the script (which is the same the `ParentSite` in ERA5 registeration xml file). 2- Make sure the start and end date in that script matches the downloaded tiles. Set your `ERA5.files.path` to where you downloaded the tiles and then the rest of the script should be working fine.
158158

159159
## ICOS Drought 2018
160160

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+
})

modules/data.atmosphere/R/ERA5_download.R

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,12 @@
99
#' To get a Copernicus CDS API key, register at \url{https://cds.climate.copernicus.eu/profile}.
1010
#' You must provide both \code{user} (UID) and \code{key} parameters from your CDS profile.
1111
#'
12+
#' Credentials are stored by \code{ecmwfr::wf_set_key}, which uses the \code{keyring} package.
13+
#' On a headless machine (cluster node, server, container) there is no OS keyring available and
14+
#' the default backend will prompt for a keyring password. To avoid this, set
15+
#' \code{Sys.setenv(R_KEYRING_BACKEND = "env")} before calling this function, which keeps the
16+
#' key in an environment variable for the session instead.
17+
#'
1218
#' You can check the "CC-BY" license under the \href{https://cds.climate.copernicus.eu/profile?tab=licences}{'licences' tab of your profile page}.
1319
#' @param outfolder Character. Directory where downloaded NetCDF files will be saved.
1420
#' @param start_date character: the start date of the data to be downloaded. Format is YYYY-MM-DD (will only use the year part of the date)

modules/data.atmosphere/inst/ERA5/ERA5_NA_download.R

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,12 @@ outfolder <- "/projectnb/dietzelab/dongchen/anchorSites/ERA5/"
22
start_date <- "2012-01-01"
33
end_date <- "2021-12-31"
44
extent <- c(-179, -20, 7, 85)
5+
# cds credentials, from https://cds.climate.copernicus.eu/profile
6+
user <- "your_cds_uid"
7+
key <- "your_cds_api_key"
8+
# ecmwfr keeps the key in the keyring. a headless machine has no os keyring and
9+
# the default backend prompts for a password, so keep the key in the environment
10+
Sys.setenv(R_KEYRING_BACKEND = "env")
511
variables <- c("2m_temperature",
612
"surface_pressure",
713
"2m_dewpoint_temperature",
@@ -15,4 +21,5 @@ results <- PEcAn.data.atmosphere::download.ERA5_cds(outfolder = outfolder,
1521
end_date = end_date,
1622
extent = extent,
1723
variables = variables,
18-
auto.create.key = T)
24+
user = user,
25+
key = key)

modules/data.atmosphere/man/download.ERA5_cds.Rd

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

0 commit comments

Comments
 (0)