Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 18 additions & 2 deletions models/sipnet/R/model2netcdf.SIPNET.R
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,15 @@ model2netcdf.SIPNET <- function(outdir, sitelat, sitelon, start_date, end_date,
output[["coarse_root_carbon_content"]] <- sub.sipnet.output$coarseRootC * 0.001 ## coarse_root_carbon_content kgC/m2
output[["GWBI"]] <- (sub.sipnet.output$woodCreation * 0.001) / 86400 ## kgC/m2/s - this is daily in SIPNET
output[["AGB"]] <- (sub.sipnet.output$plantWoodC + sub.sipnet.output$plantLeafC) * 0.001 # Total aboveground biomass kgC/m2
if ("n2oFlux" %in% names(sub.sipnet.output)) {
output[["N2O_flux"]] <- (sub.sipnet.output$n2oFlux * 0.001) / timestep.s
# convert g N m-2 per timestep -> kg N m-2 s-1
}
ch4_col <- intersect(c("ch4Flux", "ch4"), names(sub.sipnet.output))
if (length(ch4_col) > 0) {
output[["CH4_flux"]] <- (sub.sipnet.output[[ch4_col[1]]] * 0.001) / timestep.s
# convert g C m-2 per timestep -> kg C m-2 s-1
Comment thread
infotroph marked this conversation as resolved.
Outdated
}
output[["time_bounds"]] <- c(rbind(bounds[,1], bounds[,2]))

# ******************** Declare netCDF variables ********************#
Expand Down Expand Up @@ -236,6 +245,13 @@ model2netcdf.SIPNET <- function(outdir, sitelat, sitelon, start_date, end_date,
longname = "history time interval endpoints", dim=list(time_interval,time = t),
prec = "double")
)

if ("N2O_flux" %in% names(output)) {
nc_var[["N2O_flux"]] <- PEcAn.utils::to_ncvar("N2O_flux", dims)
}
if ("CH4_flux" %in% names(output)) {
nc_var[["CH4_flux"]] <- PEcAn.utils::to_ncvar("CH4_flux", dims)
}

# ******************** Create netCDF and output variables ********************#
### Output netCDF data
Expand Down Expand Up @@ -264,8 +280,8 @@ model2netcdf.SIPNET <- function(outdir, sitelat, sitelon, start_date, end_date,
}else{
nc <- ncdf4::nc_create(file.path(outdir, paste(y, "nc", sep = ".")), nc_var)
ncdf4::ncatt_put(nc, "time", "bounds", "time_bounds", prec=NA)
for (i in seq_along(nc_var)) {
ncdf4::ncvar_put(nc, nc_var[[i]], output[[i]])
for (key in names(nc_var)) {
ncdf4::ncvar_put(nc, nc_var[[key]], output[[key]])
}
ncdf4::nc_close(nc)
}
Expand Down
197 changes: 197 additions & 0 deletions models/sipnet/tests/testthat/test-model2netcdf.SIPNET.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
test_that("model2netcdf.SIPNET produces netCDF from v2 output with GHG fluxes", {
outdir <- withr::local_tempdir(pattern = "sipnet_out_")
rundir <- withr::local_tempdir(pattern = "sipnet_run_")
Comment thread
infotroph marked this conversation as resolved.
Outdated

# minimal sipnet.param — model2netcdf only reads leafCSpWt for LAI

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not for this PR, but: Given we go to the trouble of reading leafCSpWt from the param file, why does it get combined with a constant leafC <- 0.48 instead of reading cFracLeaf from the very next param line?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks! opened PR #3820

writeLines(
c("plantWoodInit\t30000\t0\t6600\t14000\t200",
"leafCSpWt\t32\t0\t13\t500\t0"),

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sipnet v2 drops the extra numeric columns and I would hope it's safe to do that here too:

Suggested change
c("plantWoodInit\t30000\t0\t6600\t14000\t200",
"leafCSpWt\t32\t0\t13\t500\t0"),
c("plantWoodInit\t30000",
"leafCSpWt\t32"),

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also: is the plantWoodInit line needed for this test to work?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no, plantWoodInit is not needed. model2netcdf.SIPNET only reads leafCSpWt from the param file. I will trim sipnet.param down to just that one line

file.path(rundir, "sipnet.param")
)

# synthesise a 4-timestep sipnet.out (2 days x 12-hourly)
n <- 4L
ts_s <- 43200 # 12h in sec
sipnet_dat <- data.frame(
year = 2002, day = c(1, 1, 2, 2), time = c(6, 18, 6, 18),
plantWoodC = 5000, plantLeafC = 200, woodCreation = 0.5,
soil = 10000, microbeC = 8, coarseRootC = 1200, fineRootC = 800,
litter = 400, soilWater = 14, soilWetnessFrac = 0.85, snow = 0,
npp = 0.05, nee = 0.10, cumNEE = cumsum(rep(0.1, n)),
gpp = 0.30, rAboveground = 0.04, rSoil = 0.09, rRoot = 0.01,
ra = 0.05, rh = 0.08, rtot = 0.13,
evapotranspiration = 0.005, fluxestranspiration = 0.003,
n2oFlux = 0.002,
ch4Flux = 0.001
)

out_path <- file.path(outdir, "sipnet.out")
writeLines(
"Notes: units in g/m2 per timestep; water in cm",
out_path
)
suppressWarnings(
Comment thread
infotroph marked this conversation as resolved.
Outdated
write.table(sipnet_dat, file = out_path, append = TRUE,
row.names = FALSE, quote = FALSE, sep = "\t")
)

# outdir must contain "/out/" so the gsub to find "/run/" works
# we've set up rundir separately, so patch the path convention:
# model2netcdf does gsub("/out/", "/run/", outdir) to find sipnet.param
# use the structure: <base>/out/run1 and <base>/run/run1
Comment thread
infotroph marked this conversation as resolved.
Outdated
base <- withr::local_tempdir(pattern = "sipnet_base_")
real_outdir <- file.path(base, "out", "run1")
real_rundir <- file.path(base, "run", "run1")
dir.create(real_outdir, recursive = TRUE)
dir.create(real_rundir, recursive = TRUE)
file.copy(out_path, file.path(real_outdir, "sipnet.out"))
Comment thread
infotroph marked this conversation as resolved.
Outdated
writeLines(
c("plantWoodInit\t30000\t0\t6600\t14000\t200",
"leafCSpWt\t32\t0\t13\t500\t0"),
file.path(real_rundir, "sipnet.param")
)
Comment thread
infotroph marked this conversation as resolved.
Outdated

suppressMessages(
Comment thread
infotroph marked this conversation as resolved.
Outdated
model2netcdf.SIPNET(
outdir = real_outdir,
Comment thread
infotroph marked this conversation as resolved.
Outdated
sitelat = 38.0,
sitelon = -121.0,
start_date = "2002-01-01",
end_date = "2002-12-31",
delete.raw = FALSE,
revision = "r136"
)
)

nc_file <- file.path(real_outdir, "2002.nc")
expect_true(file.exists(nc_file))

nc <- ncdf4::nc_open(nc_file)
on.exit(ncdf4::nc_close(nc), add = TRUE)
vars <- names(nc$var)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tests like this seem like a great use case for the netcdf2df() I proposed in #3788 (which isn't merged yet, so no foul for not using it here!) -- one line to slurp the test file into a df instead of needing to remember these incantations every time.


# -- GHG variables --
expect_true("N2O_flux" %in% vars)
expect_true("CH4_flux" %in% vars)

# -- standard C-cycle variables --
expect_true(all(c("GPP", "NEE", "TotalResp", "TotSoilCarb") %in% vars))

# -- g m-2 per timestep -> kg m-2 s-1 --
n2o <- as.numeric(ncdf4::ncvar_get(nc, "N2O_flux"))
ch4 <- as.numeric(ncdf4::ncvar_get(nc, "CH4_flux"))
gpp <- as.numeric(ncdf4::ncvar_get(nc, "GPP"))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All of these should be stored as numeric already, and if they aren't then we want to know (as the expect_equal below will do if we skip coercion)

Suggested change
n2o <- as.numeric(ncdf4::ncvar_get(nc, "N2O_flux"))
ch4 <- as.numeric(ncdf4::ncvar_get(nc, "CH4_flux"))
gpp <- as.numeric(ncdf4::ncvar_get(nc, "GPP"))
n2o <- ncdf4::ncvar_get(nc, "N2O_flux")
ch4 <- ncdf4::ncvar_get(nc, "CH4_flux")
gpp <- ncdf4::ncvar_get(nc, "GPP")

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

right! that these should already be numeric , as.numeric() was masking the type check. But ncvar_get() returns a 1-D array (carries dim attribute), so bare comparison with rep() fails on attributes. Switched to as.vector(), which strips the array dimension without coercing type


expect_equal(n2o, rep(0.002 * 1e-3 / ts_s, n), tolerance = 1e-12)
expect_equal(ch4, rep(0.001 * 1e-3 / ts_s, n), tolerance = 1e-12)
expect_equal(gpp, rep(0.30 * 1e-3 / ts_s, n), tolerance = 1e-12)

expect_equal(nc$var$N2O_flux$units, "kg N m-2 s-1")
expect_equal(nc$var$CH4_flux$units, "kg C m-2 s-1")
expect_equal(nc$var$GPP$units, "kg C m-2 s-1")

# -- dimensions --
expect_equal(nc$dim$time$len, n)
expect_true(grepl("days since 2002", nc$dim$time$units))
Comment thread
infotroph marked this conversation as resolved.
Outdated
})


test_that("model2netcdf.SIPNET omits N2O/CH4 when columns absent (backward compat)", {
Comment thread
infotroph marked this conversation as resolved.
Outdated
base <- withr::local_tempdir(pattern = "sipnet_v1_")
real_outdir <- file.path(base, "out", "run1")
real_rundir <- file.path(base, "run", "run1")
dir.create(real_outdir, recursive = TRUE)
dir.create(real_rundir, recursive = TRUE)

writeLines(
c("plantWoodInit\t30000\t0\t6600\t14000\t200",
"leafCSpWt\t32\t0\t13\t500\t0"),
file.path(real_rundir, "sipnet.param")
)

sipnet_dat <- data.frame(
year = 2002, day = c(1, 1), time = c(6, 18),
plantWoodC = 5000, plantLeafC = 200, woodCreation = 0.5,
soil = 10000, microbeC = 8, coarseRootC = 1200, fineRootC = 800,
litter = 400, soilWater = 14, soilWetnessFrac = 0.85, snow = 0,
npp = 0.05, nee = 0.10, cumNEE = c(0.1, 0.2),
gpp = 0.30, rAboveground = 0.04, rSoil = 0.09, rRoot = 0.01,
ra = 0.05, rh = 0.08, rtot = 0.13,
evapotranspiration = 0.005, fluxestranspiration = 0.003
)

out_path <- file.path(real_outdir, "sipnet.out")
writeLines("Notes: g/m2", out_path)
suppressWarnings(
write.table(sipnet_dat, file = out_path, append = TRUE,
row.names = FALSE, quote = FALSE, sep = "\t")
)

suppressMessages(
model2netcdf.SIPNET(
outdir = real_outdir,
sitelat = 38.0,
sitelon = -121.0,
start_date = "2002-01-01",
end_date = "2002-12-31",
delete.raw = FALSE,
revision = "r136"
)
)

nc <- ncdf4::nc_open(file.path(real_outdir, "2002.nc"))
on.exit(ncdf4::nc_close(nc), add = TRUE)
vars <- names(nc$var)

expect_false("N2O_flux" %in% vars)
expect_false("CH4_flux" %in% vars)
expect_true("GPP" %in% vars)
})


test_that("delete.raw removes sipnet.out after conversion", {
base <- withr::local_tempdir(pattern = "sipnet_del_")
real_outdir <- file.path(base, "out", "run1")
real_rundir <- file.path(base, "run", "run1")
dir.create(real_outdir, recursive = TRUE)
dir.create(real_rundir, recursive = TRUE)

writeLines(
c("plantWoodInit\t30000\t0\t6600\t14000\t200",
"leafCSpWt\t32\t0\t13\t500\t0"),
file.path(real_rundir, "sipnet.param")
)

sipnet_dat <- data.frame(
year = 2002, day = 1, time = 12,
plantWoodC = 5000, plantLeafC = 200, woodCreation = 0.5,
soil = 10000, microbeC = 8, coarseRootC = 1200, fineRootC = 800,
litter = 400, soilWater = 14, soilWetnessFrac = 0.85, snow = 0,
npp = 0.05, nee = 0.10, cumNEE = 0.1,
gpp = 0.30, rAboveground = 0.04, rSoil = 0.09, rRoot = 0.01,
ra = 0.05, rh = 0.08, rtot = 0.13,
evapotranspiration = 0.005, fluxestranspiration = 0.003
)

raw_path <- file.path(real_outdir, "sipnet.out")
writeLines("Notes: g/m2", raw_path)
suppressWarnings(
write.table(sipnet_dat, file = raw_path, append = TRUE,
row.names = FALSE, quote = FALSE, sep = "\t")
)

suppressMessages(
model2netcdf.SIPNET(
outdir = real_outdir,
sitelat = 38.0,
sitelon = -121.0,
start_date = "2002-01-01",
end_date = "2002-12-31",
delete.raw = TRUE,
revision = "r136"
)
)

expect_false(file.exists(raw_path))
expect_true(file.exists(file.path(real_outdir, "2002.nc")))
})
Loading