Skip to content

Commit 8e76b85

Browse files
committed
RothC: sample multisite workflow, modeled after MAGiC scripts
1 parent 3635429 commit 8e76b85

File tree

8 files changed

+497
-0
lines changed

8 files changed

+497
-0
lines changed

models/rothc/.Rbuildignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
Dockerfile
22
model_info.json
3+
inst/workflow_example
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
#!/usr/bin/env Rscript
2+
3+
# Converts a directory full of gridded ERA5 meteorology data
4+
# from PEcAn's standard netCDF format to RothC weather statistics.
5+
6+
# This is basically a thin wrapper around `met2model.RothC()`,
7+
# and is specific to ERA5 only by its assumptions about the filenames:
8+
# It assumes inputs has a directory per location and ensemble member (1-10)
9+
# containing one file per year, e.g.
10+
# path/to/ERA5_<location>_<ens>/ERA5.<ens>.<year>.nc
11+
# and produces output with one directory per location containing one multiyear
12+
# file per ensemble member, e.g.
13+
# path/to/ERA5_<location>/ERA5.<ens>.<startdate>.<enddate>.nc
14+
15+
# For a related script that takes a list of locations to be converted rather
16+
# than operate on the entire input directory, see
17+
# https://github.com/ccmmf/workflows/blob/main/2a_grass/01_ERA5_nc_to_clim.R
18+
19+
20+
## --------- runtime values: change for your system and simulation ---------
21+
22+
options <- list(
23+
optparse::make_option("--site_era5_path",
24+
default = "data_raw/ERA5_CA_nc",
25+
help = paste(
26+
"Path to your existing ERA5 data in PEcAn CF format, organized as",
27+
"single-site, single-year netcdfs in subdirectories per ensemble member.",
28+
"Files should be named",
29+
"'<site_era5_path>/ERA5_<siteid>_<ensid>/ERA5.<ensid>.<year>.nc'"
30+
)
31+
),
32+
optparse::make_option("--site_rothc_met_path",
33+
default = "data/ERA5_CA_RothC",
34+
help = paste(
35+
"Output path:",
36+
"single-site, multi-year weather summaries, one per ensemble member.",
37+
"Files will be named",
38+
"<site_rothc_met_path>/<siteid>/ERA5.<ensid>.<start>.<end>.dat"
39+
)
40+
),
41+
optparse::make_option("--start_date",
42+
default = "2016-01-01",
43+
help = "Date to begin clim file",
44+
),
45+
optparse::make_option("--end_date",
46+
default = "2024-12-31",
47+
help = "Date to end clim file",
48+
),
49+
optparse::make_option("--n_cores",
50+
default = Sys.getenv("NCPUS", 1L),
51+
help = "number of CPUs to use in parallel",
52+
),
53+
optparse::make_option("--parallel_strategy",
54+
default = "multisession",
55+
help = "Strategy for parallel conversion, passed to future::plan()",
56+
)
57+
) |>
58+
# Show default values in help message
59+
purrr::modify(\(x) {
60+
x@help <- paste(x@help, "[default: %default]")
61+
x
62+
})
63+
64+
args <- optparse::OptionParser(option_list = options) |>
65+
optparse::parse_args()
66+
67+
68+
# ----------- end system-specific ---------------------------------
69+
70+
71+
future::plan(args$parallel_strategy, workers = as.numeric(args$n_cores))
72+
73+
74+
dirs <- list.dirs(args$site_era5_path, recursive = FALSE, full.names = FALSE) |>
75+
data.frame(indir = _) |>
76+
dplyr::mutate(
77+
in_path = file.path(args$site_era5_path, indir),
78+
location = sub(r"(ERA5_(.*)_(\d+)$)", "\\1", indir),
79+
ens_num = sub(r"(ERA5_(.*)_(\d+)$)", "\\2", indir),
80+
in_prefix = paste0("ERA5.", ens_num),
81+
out_path = file.path(args$site_rothc_met_path, paste0("ERA5_", location))
82+
)
83+
84+
if (!dir.exists(args$site_rothc_met_path)) {
85+
dir.create(args$site_rothc_met_path, recursive = TRUE)
86+
}
87+
88+
furrr::future_pwalk(
89+
dirs,
90+
function(in_path, in_prefix, out_path, ...) {
91+
PEcAn.RothC::met2model.RothC(
92+
in.path = in_path,
93+
start_date = args$start_date,
94+
end_date = args$end_date,
95+
in.prefix = in_prefix,
96+
outfolder = out_path
97+
)
98+
}
99+
)
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
#!/usr/bin/env bash
2+
3+
set -e
4+
start_time=$(date +'%Y%m%d%H%M%S')
5+
logfile=${1:-runlog_"$start_time".txt}
6+
7+
exec > >(tee -a "$logfile") 2>&1
8+
echo "start: $start_time"
9+
10+
export NCPUS=8
11+
12+
if [[ ! -d data/ERA5_CA_RothC ]]; then
13+
echo "================= Converting met data ================="
14+
./ERA5_nc_to_RothC.R --n_cores="$NCPUS"
15+
fi
16+
17+
echo "================= Building XML settings ================="
18+
./xml_build.R --output_dir_name=output_"$start_time"
19+
20+
echo "================= Setting up model files ================="
21+
./set_up_rothc_runs.R
22+
23+
echo "================= Running RothC ================="
24+
./run_model.R --n_cores="$NCPUS" \
25+
--settings=output_"$start_time"/pecan.CONFIGS.xml
26+
27+
echo "end: $(date +'%Y%m%d%H%M%S')"
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
#!/usr/bin/env Rscript
2+
3+
4+
options <- list(
5+
optparse::make_option(c("-s", "--settings"),
6+
default = "output/pecan.CONFIGS.xml",
7+
help = paste(
8+
"path to the XML settings file you want to use for this run.",
9+
"Be aware all paths inside the file are interpreted relative to the",
10+
"working directory of the process that invokes run_model.R,",
11+
"not relative to the settings file path"
12+
)
13+
),
14+
optparse::make_option(c("-n", "--n_cores"),
15+
default = Sys.getenv("NCPUS", 1),
16+
help = "number of CPUs to use in parallel"
17+
)
18+
) |>
19+
# Show default values in help message
20+
purrr::modify(\(x) {
21+
x@help <- paste(x@help, "[default: %default]")
22+
x
23+
})
24+
25+
args <- optparse::OptionParser(option_list = options) |>
26+
optparse::parse_args()
27+
28+
29+
options(warn = 1)
30+
options(error = quote({
31+
try(PEcAn.utils::status.end("ERROR"))
32+
try(PEcAn.remote::kill.tunnel(settings))
33+
if (!interactive()) {
34+
q(status = 1)
35+
}
36+
}))
37+
38+
39+
library("PEcAn.all")
40+
PEcAn.all::pecan_version()
41+
42+
# load settings
43+
settings <- PEcAn.settings::read.settings(args$settings)
44+
45+
# run RothC
46+
# NB assumes write.config step has already happened
47+
# (i.e. settings should look like `output/pecan.CONFIGS.xml`)
48+
PEcAn.utils::status.start("MODEL")
49+
PEcAn.workflow::runModule_start_model_runs(settings, stop.on.error = FALSE)
50+
PEcAn.utils::status.end()
51+
52+
# extract model results for ensemble analysis
53+
# this function is arguably too chatty, so we'll suppress
54+
# INFO-level log output for this step.
55+
loglevel <- PEcAn.logger::logger.setLevel("WARN")
56+
PEcAn.utils::status.start("OUTPUT")
57+
runModule.get.results(settings)
58+
PEcAn.utils::status.end()
59+
PEcAn.logger::logger.setLevel(loglevel)
60+
61+
# Summarize ensemble timeseries + end of run distributions
62+
PEcAn.utils::status.start("ENSEMBLE")
63+
runModule.run.ensemble.analysis(settings, TRUE)
64+
PEcAn.utils::status.end()
65+
66+
# Done
67+
PEcAn.utils::status.start("FINISHED")
68+
PEcAn.remote::kill.tunnel(settings)
69+
PEcAn.utils::status.end()
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
#!/usr/bin/env Rscript
2+
3+
# --------------------------------------------------
4+
# Run-time parameters
5+
6+
options <- list(
7+
optparse::make_option(c("-s", "--settings"),
8+
default = "settings.xml",
9+
help = paste(
10+
"path to the XML settings file you want to use for this run.",
11+
"Be aware all paths inside the file are interpreted relative to the",
12+
"working directory of the process that invokes run_model.R,",
13+
"not relative to the settings file path"
14+
)
15+
)
16+
# TODO: This would be a natural place to specify output directories,
17+
# but it was already done in the XML build step.
18+
# Consider moving that here with appropriate runtime option(s).
19+
# optparse::make_option(c("-d", "--output_dir"),
20+
# default = "output",
21+
# # ...
22+
# ),
23+
# optparse::make_option(c("-o", "--settings_out"),
24+
# default = "output/pecan.CONFIG.xml",
25+
# # ...
26+
# ),
27+
) |>
28+
# Show default values in help message
29+
purrr::modify(\(x) {
30+
x@help <- paste(x@help, "[default: %default]")
31+
x
32+
})
33+
34+
args <- optparse::OptionParser(option_list = options) |>
35+
optparse::parse_args()
36+
37+
38+
# Put global environment into "PEcAn mode"
39+
options(warn = 1)
40+
options(error = quote({
41+
try(PEcAn.utils::status.end("ERROR"))
42+
try(PEcAn.remote::kill.tunnel(settings))
43+
if (!interactive()) {
44+
q(status = 1)
45+
}
46+
}))
47+
48+
library("PEcAn.all")
49+
PEcAn.all::pecan_version()
50+
51+
52+
# Open and read in settings file for PEcAn run.
53+
settings <- PEcAn.settings::read.settings(args$settings)
54+
55+
if (dir.exists(settings$outdir)) {
56+
PEcAn.logger::logger.severe(
57+
"outdir", sQuote(settings$outdir), "already exists.",
58+
"If you want to replace it, please delete and rerun."
59+
)
60+
}
61+
dir.create(settings$outdir, recursive = TRUE)
62+
status_file <- file.path(settings$outdir, "STATUS")
63+
64+
# Write model specific configs
65+
PEcAn.utils::status.start("CONFIG")
66+
settings <- PEcAn.workflow::runModule.run.write.configs(settings)
67+
PEcAn.settings::write.settings(settings, outputfile = "pecan.CONFIGS.xml")
68+
PEcAn.utils::status.end()
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
id,lat,lon,site.pft
2+
Century,38.54,-121.8,soil
3+
Fivepoints,36.341677,-120.108692,soil
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
<?xml version="1.0"?>
2+
<pecan>
3+
<info>
4+
<notes>Template for demo runs of RothC using PEcAn. This file is not by itself
5+
a usable PEcAn settings object, will be further processed by the workflow to
6+
create one.</notes>
7+
<userid>-1</userid>
8+
<username></username>
9+
</info>
10+
<outdir><!-- altered at config time -->output</outdir>
11+
<modeloutdir><!-- altered at config time -->output/out</modeloutdir>
12+
<rundir><!-- altered at config time -->output/run</rundir>
13+
<pfts>
14+
<pft>
15+
<name>soil</name>
16+
<outdir>data_raw/pfts/soil/</outdir>
17+
</pft>
18+
</pfts>
19+
<ensemble>
20+
<size><!-- inserted at config time --></size>
21+
<variable>TotSoilCarb</variable>
22+
<samplingspace>
23+
<parameters>
24+
<method>uniform</method>
25+
</parameters>
26+
<met>
27+
<method>sampling</method>
28+
</met>
29+
<!-- TODO disabled until running right without them
30+
<poolinitcond>
31+
<method>sampling</method>
32+
</poolinitcond>
33+
<soilphysics>
34+
<method>sampling</method>
35+
</soilphysics> -->
36+
</samplingspace>
37+
<start.year><!-- inserted at config time --></start.year>
38+
<end.year><!-- inserted at config time --></end.year>
39+
</ensemble>
40+
<model>
41+
<id>rothc_2_1_0</id>
42+
<type>RothC</type>
43+
<revision>2.1.0</revision>
44+
<delete.raw>FALSE</delete.raw>
45+
<binary>/usr/local/bin/RothC_v2.1.0</binary>
46+
</model>
47+
<run>
48+
<site><!-- inserted at config time --></site>
49+
<inputs>
50+
<met>
51+
<id></id>
52+
<path><!-- inserted at config time --></path>
53+
</met>
54+
</inputs>
55+
<start.date><!-- inserted at config time --></start.date>
56+
<end.date><!-- inserted at config time --></end.date>
57+
</run>
58+
<host>
59+
<name>localhost</name>
60+
<outdir><!-- altered at config time -->output/out</outdir>
61+
<rundir><!-- altered at config time -->output/run</rundir>
62+
<modellauncher>
63+
<binary>parallel -j ${NCPUS:-1} --skip-first-line '{}/job.sh' ::::</binary>
64+
<Njobmax>1000</Njobmax>
65+
</modellauncher>
66+
</host>
67+
</pecan>

0 commit comments

Comments
 (0)