Skip to content

Commit 3a8c485

Browse files
authored
Merge pull request #3788 from infotroph/rothc-opts
Rothc: Soil file support + read options from settings file
2 parents 44cd5d7 + 2b56bf6 commit 3a8c485

23 files changed

Lines changed: 610 additions & 72 deletions

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ For more information about this file see also [Keep a Changelog](http://keepacha
99
## Unreleased
1010

1111
### Added
12+
- New function `PEcAn.utils::netcdf2df()` flattens all dims and vars of a netCDF into a dataframe,
13+
with units attached as an attribute.
14+
- New package `PEcAn.RothC` runs the RothC soil carbon model.
1215
- Added `inst/ilamb/` pipeline in PEcAn.benchmark to convert downscaled SDA reanalysis GeoTIFFs into ILAMB-compatible CF netCDF for carbon-cycle benchmarking (#4019).
1316
- Added PEcAn.PEPRMT model, including a demo run with example data
1417
- Add `format_try_for_ma()` and `try_trait_mapping()` to `PEcAn.data.remote` to convert trait data from the external TRY database into the tabular format required by the PEcAn meta-analysis module (#3717).

base/utils/NAMESPACE

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ export(n_leap_day)
3636
export(nc_merge_all_sites_by_year)
3737
export(nc_write_varfiles)
3838
export(need_packages)
39+
export(netcdf2df)
3940
export(paste.stats)
4041
export(r2bugs.distributions)
4142
export(read.output)

base/utils/NEWS.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
# PEcAn.utils 1.8.2.9000
2+
3+
## Added
4+
5+
* New function `netcdf2df()` reads a netCDF as a dataframe, with units attached as an attribute. It is intended so far for files that are already "basically a rectangle" (dense grids where all variables have the same dimensions and all dimensions are scalars or fully-crossed vectors), but if you try it on files with more complex dimensions please report how it goes.
6+
17
# PEcAn.utils 1.8.2
28

39
## Fixed

base/utils/R/netcdf2df.R

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
#' Read all variables from a netCDF into a single data frame
2+
#'
3+
#' Reads all dimensions and variables from a netCDF and returns them as
4+
#' a single data frame with one row per cell of the source file's
5+
#' dimension array.
6+
#' Units are also read and are attached to the result as attribute "units"
7+
#'
8+
#' Written mostly for files where all dimensions are 1d vectors,
9+
#' e.g. single-site soil or met files.
10+
#' Many files with more complex dimensions should work
11+
#' (at least for cases where it's clear how to rectangle them),
12+
#' but they are not yet well tested.
13+
#'
14+
#' @param path path to a netcdf file
15+
#'
16+
#' @return data frame with columns for each dim and var of the input file.
17+
#' Units for all of these are attached as an attribute.
18+
#'
19+
#' @author Chris Black
20+
#' @export
21+
#'
22+
netcdf2df <- function(path) {
23+
nc <- ncdf4::nc_open(path)
24+
on.exit(ncdf4::nc_close(nc), add = TRUE)
25+
26+
dim_vals <- nc$dim |>
27+
sapply(\(x) c(x[["vals"]]), simplify = FALSE) |>
28+
# Load-bearing assumption:
29+
# Dimension listed first in nc$dim is fastest-varying (as for expand.grid)
30+
# TODO verify whether this is reliably true.
31+
expand.grid()
32+
33+
var_vals <- nc$var |>
34+
names() |>
35+
sapply(\(v) c(ncdf4::ncvar_get(nc, v)), simplify = FALSE)
36+
37+
if (any(lengths(var_vals) != nrow(dim_vals))) {
38+
PEcAn.logger::logger.error("Not all variables have same length")
39+
}
40+
41+
res <- cbind(dim_vals, as.data.frame(var_vals))
42+
attr(res, "units") <- c(
43+
sapply(nc$dim, `[[`, "units"),
44+
sapply(nc$var, `[[`, "units")
45+
)
46+
47+
res
48+
}

base/utils/man/netcdf2df.Rd

Lines changed: 31 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
test_that("one dim", {
2+
local_edition(3)
3+
4+
res <- c("a", "b") |>
5+
example_netcdf(file_path = withr::local_tempfile()) |>
6+
netcdf2df()
7+
8+
expect_equal(dim(res), c(365, 3))
9+
expect_equal(colnames(res), c("time", "a", "b"))
10+
expect_equal(
11+
attr(res, "units"),
12+
c(time = "days since 2001-01-01", a = "kg", b = "kg")
13+
)
14+
expect_equal(res$time, (0L:364L))
15+
})
16+
17+
18+
19+
test_that("multiple dims, all but one scalar", {
20+
local_edition(3)
21+
22+
# TODO does this work if pkg not installed (eg check time)?
23+
# I think testthat may shim system.file
24+
res <- netcdf2df(
25+
system.file("test-data/CRUNCEP.2000.nc", package = "PEcAn.utils")
26+
)
27+
28+
expect_equal(dim(res), c(366 * 4, 11))
29+
expect_equal(
30+
colnames(res),
31+
c(
32+
"latitude", "longitude", "time",
33+
"air_temperature", "surface_downwelling_longwave_flux_in_air",
34+
"air_pressure", "surface_downwelling_shortwave_flux_in_air",
35+
"eastward_wind", "northward_wind",
36+
"specific_humidity", "precipitation_flux"
37+
)
38+
)
39+
40+
# Check units. NB some of these are nonstandard;
41+
# Key point is they should match what the file reports
42+
expect_equal(
43+
attr(res, "units"),
44+
c(
45+
latitude = "degree_north",
46+
longitude = "degree_east",
47+
time = "days since 2000-01-01T00:00:00Z",
48+
air_temperature = "Kelvin",
49+
surface_downwelling_longwave_flux_in_air = "W/m2",
50+
air_pressure = "Pascal",
51+
surface_downwelling_shortwave_flux_in_air = "W/m2",
52+
eastward_wind = "m/s",
53+
northward_wind = "m/s",
54+
specific_humidity = "g/g",
55+
precipitation_flux = "kg/m2/s"
56+
)
57+
)
58+
# dims
59+
expect_equal(res$time, seq(0, 365 + 3 / 4, 1 / 4) + 1 / 8, tolerance = 1e-12)
60+
expect_equal(unique(res$latitude), 45.25, tolerance = 1e-6)
61+
expect_equal(unique(res$longitude), -84.75, tolerance = 1e-6)
62+
expect_equal(mean(res$air_temperature), 278.798636, tolerance = 1e-6)
63+
})
64+
65+
# TODO test with more than one non-degenerate dimension

models/rothc/.Rbuildignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
Dockerfile
22
model_info.json
33
inst/workflow_example
4+
^docs$

models/rothc/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
docs/

models/rothc/Dockerfile

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,21 +7,12 @@ ARG IMAGE_VERSION="latest"
77
# ----------------------------------------------------------------------
88
FROM pecan/models:${IMAGE_VERSION}
99

10-
# ----------------------------------------------------------------------
11-
# INSTALL MODEL SPECIFIC PIECES
12-
# ----------------------------------------------------------------------
13-
14-
#RUN apt-get update \
15-
# && apt-get install -y --no-install-recommends \
16-
# python3.9 \
17-
# && rm -rf /var/lib/apt/lists/*
18-
1910
# ----------------------------------------------------------------------
2011
# SETUP FOR SPECIFIC MODEL
2112
# ----------------------------------------------------------------------
2213

2314
# Some variables that can be used to set control the docker build
24-
ARG MODEL_VERSION=2.1.0
15+
ARG MODEL_VERSION=2.1.1
2516

2617
RUN mkdir -p /tmp/rothc \
2718
&& curl -sSL https://github.com/Rothamsted-Models/RothC_Code/archive/refs/tags/v${MODEL_VERSION}.tar.gz \
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
#' PEcAn.RothC-package
2+
#'
3+
#' PEcAn support for the RothC soil carbon model
4+
#'
5+
#' @importFrom rlang .data .env
6+
#' @keywords internal
7+
"_PACKAGE"
8+
9+
10+
# Define null-coalescing operator unless already available in base (R >= 4.4).
11+
# (We could also import it from rlang, but why add a whole import for one
12+
# line of code?)
13+
if (!exists("%||%", envir=.BaseNamespaceEnv)) {
14+
`%||%` <- function(x, y) if (is.null(x)) y else x
15+
}

0 commit comments

Comments
 (0)