Skip to content

Commit b9b28d9

Browse files
authored
Merge pull request #3622 from DongchenZ/SDA_downscale
Bug fixes for the date calculations in the `model2netcdf.SIPNET` function
2 parents ef016c8 + b36bc4f commit b9b28d9

6 files changed

Lines changed: 55 additions & 109 deletions

File tree

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@ section for the next release.
1414
* New function `sat_vapor_pressure()` added for computing saturation vapor pressure from temperature using various methods.
1515
* Added `AmeriFlux_met_ensemble()` function with ERA5 fallback for AmeriFlux meteorological data processing and ensemble generation
1616

17+
### Fixed
18+
19+
* Fixed a bug within the `model2netcdf.SIPNET` function where we assumed the constant calculations of `pecan_start_doy` across years (the calculations should vary depending on the last date from the last loop and the start date of the current loop), which will lead to incorrect calculations of the start `sub_dates` and `sub_dates_cf` if we are jumping between years (e.g., from 2012-12-31 to 2013-01-01). The `sipnet2datetime` function is no longer used anywhere and therefore has been removed.
20+
1721
## [1.9.0] - 2025-05-25
1822

1923
### Added

book_source/03_topical_pages/02_pecan_standards.Rmd

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
## Time Standard
99
Internal PEcAn standard time follows ISO_8601 format for dates and time (https://en.wikipedia.org/wiki/ISO_8601). For example ordinal dates go from 1 365/366 (https://en.wikipedia.org/wiki/ISO_8601#Ordinal_dates). However, time used in met drivers or model outputs follows CF convention with julian dates following the 0 to 364/365 format
1010

11-
To aid in the conversion between PEcAn internal ISO_8601 standard and CF convention used in all met drivers and PEcAn standard output you can utilize the functions: "cf2datetime","datetime2doy","cf2doy", and for SIPNET "sipnet2datetime"
11+
To aid in the conversion between PEcAn internal ISO_8601 standard and CF convention used in all met drivers and PEcAn standard output you can utilize the functions: "cf2datetime","datetime2doy",and "cf2doy"
1212

1313
## Input Standards
1414

models/sipnet/NAMESPACE

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ export(model2netcdf.SIPNET)
66
export(read_restart.SIPNET)
77
export(remove.config.SIPNET)
88
export(sample.IC.SIPNET)
9-
export(sipnet2datetime)
109
export(split_inputs.SIPNET)
1110
export(veg2model.SIPNET)
1211
export(write.config.SIPNET)

models/sipnet/NEWS.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,13 @@
44

55
* Breaking: Renamed the setting used to pass soil and hydrology parameters. `write.config.SIPNET` previously read these from `settings$run$inputs$soilinitcond`, now `settings$run$inputs$soil_physics` to better reflect that these are state factors applicable to the whole run rather than initial conditions. (Quianyu Xuan, #3406)
66
* model2netcdf.SIPNET no longer writes separate `<year>.nc.var` files for every year of output. Use `PEcAn.utils::nc_write_varfiles()` to create these as needed.
7+
* The `sipnet2datetime` function is no longer used anywhere and therefore has been removed.
78

89
## Fixed
910

1011
* `write.config.SIPNET` now checks more carefully whether an optional variable exists in an initial condition file before trying to read it, therefore printing fewer messages about (expectedly) missing variables. (#3545)
1112
* When passed a vector of multiple input paths, `write.config.SIPNET` was choosing one at random; it now throws an error (Blesson Thomas, #3298). Note that a single input path per call has always been the intended usage; being passed many was a second bug in PEcAn.uncertainty that is also now fixed.
12-
13+
* Fixed a bug within the `model2netcdf.SIPNET` function where we assumed the constant calculations of `pecan_start_doy` across years (the calculations should vary depending on the last date from the last loop and the start date of the current loop), which will lead to incorrect calculations of the start `sub_dates` and `sub_dates_cf` if we are jumping between years (e.g., from 2012-12-31 to 2013-01-01).
1314

1415
# PEcAn.SIPNET 1.9.0
1516

models/sipnet/R/model2netcdf.SIPNET.R

Lines changed: 48 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -38,41 +38,6 @@ mergeNC <- function(
3838
invisible(outfile)
3939
}
4040

41-
#--------------------------------------------------------------------------------------------------#
42-
##'
43-
##' Convert SIPNET DOY to datetime
44-
##'
45-
##' @param sipnet_tval vector of SIPNET DOY values
46-
##' @param base_year base year to calculate datetime from DOY
47-
##' @param base_month reference month for converting from DOY to datetime
48-
##' @param force_cf force output to follow CF convention. Default FALSE
49-
##'
50-
##' @export
51-
##'
52-
##' @author Alexey Shiklomanov, Shawn Serbin
53-
##'
54-
sipnet2datetime <- function(sipnet_tval, base_year, base_month = 1,
55-
force_cf = FALSE) {
56-
base_date <- ISOdatetime(base_year, base_month, 1,
57-
0, 0, 0, "UTC")
58-
base_date_str <- strftime(base_date, "%F %T %z", tz = "UTC")
59-
if (force_cf) {
60-
is_cf <- TRUE
61-
} else {
62-
# HACK: Determine heuristically
63-
# Is CF if first time step is zero
64-
is_cf <- sipnet_tval[[1]] == 0
65-
}
66-
67-
if (is_cf) {
68-
cfval <- sipnet_tval
69-
} else {
70-
cfval <- sipnet_tval - 1
71-
}
72-
73-
PEcAn.utils::cf2datetime(cfval, paste("days since", base_date_str))
74-
}
75-
7641
#--------------------------------------------------------------------------------------------------#
7742
##' Convert SIPNET output to netCDF
7843
##'
@@ -93,35 +58,34 @@ sipnet2datetime <- function(sipnet_tval, base_year, base_month = 1,
9358
##' @author Shawn Serbin, Michael Dietze
9459
model2netcdf.SIPNET <- function(outdir, sitelat, sitelon, start_date, end_date, delete.raw = FALSE, revision, prefix = "sipnet.out",
9560
overwrite = FALSE, conflict = FALSE) {
96-
9761
### Read in model output in SIPNET format
9862
sipnet_out_file <- file.path(outdir, prefix)
9963
sipnet_output <- utils::read.table(sipnet_out_file, header = T, skip = 1, sep = "")
10064
#sipnet_output_dims <- dim(sipnet_output)
101-
65+
10266
### Determine number of years and output timestep
10367
#start.day <- sipnet_output$day[1]
10468
num_years <- length(unique(sipnet_output$year))
10569
simulation_years <- unique(sipnet_output$year)
10670

10771
# get all years that we want data from
10872
year_seq <- seq(lubridate::year(start_date), lubridate::year(end_date))
109-
73+
11074
# check that specified years and output years match
11175
if (!all(year_seq %in% simulation_years)) {
11276
PEcAn.logger::logger.severe("Years selected for model run and SIPNET output years do not match ")
11377
}
114-
78+
11579
# get number of model timesteps per day
11680
# outday is the number of time steps in a day - for example 6 hours would have out_day of 4
117-
118-
out_day <- sum(
119-
sipnet_output$year == simulation_years[1] &
120-
sipnet_output$day == unique(sipnet_output$day)[1],
121-
na.rm = TRUE
122-
) # switched to day 2 in case first day is partial
123-
124-
81+
82+
out_day <- sum(
83+
sipnet_output$year == simulation_years[1] &
84+
sipnet_output$day == unique(sipnet_output$day)[1],
85+
na.rm = TRUE
86+
) # switched to day 2 in case first day is partial
87+
88+
12589
timestep.s <- 86400 / out_day
12690

12791

@@ -138,29 +102,30 @@ model2netcdf.SIPNET <- function(outdir, sitelat, sitelon, start_date, end_date,
138102
file.rename(file.path(outdir, paste(y, "nc", sep = ".")), file.path(outdir, "previous.nc"))
139103
}
140104
print(paste("---- Processing year: ", y)) # turn on for debugging
141-
105+
142106
## Subset data for processing
143107
sub.sipnet.output <- subset(sipnet_output, sipnet_output$year == y)
108+
109+
raw_time <- sub.sipnet.output[["time"]] # decimal hours (eg 13.75 = 1:45 PM)
110+
doy <- sub.sipnet.output[["day"]] # day of year, not of month
111+
hr <- floor(raw_time)
112+
minsec <- PEcAn.utils::ud_convert(raw_time - hr, "hour", "min")
113+
min <- floor(minsec)
114+
sec <- PEcAn.utils::ud_convert(minsec - min, "minute", "second")
115+
sub_dates <- strptime(
116+
paste(y, doy, hr, min, sec),
117+
"%Y %j %H %M %S",
118+
tz = "UTC"
119+
)
120+
sub_dates_cf <- PEcAn.utils::datetime2cf(
121+
sub_dates,
122+
paste0("days since ", y, "-01-01"),
123+
tz = "UTC"
124+
)
125+
144126
sub.sipnet.output.dims <- dim(sub.sipnet.output)
145127
dayfrac <- 1 / out_day
146-
step <- utils::head(seq(0, 1, by = dayfrac), -1) ## probably dont want to use
147-
## hard-coded "step" because
148-
## leap years may not contain
149-
## all "steps", or
150-
## if model run doesnt start
151-
## at 00:00:00
152128

153-
# try to determine if DOY is CF compliant (i.e. 0 based index) or not (1 base index)
154-
pecan_start_doy <- PEcAn.utils::datetime2cf(start_date, paste0("days since ",lubridate::year(start_date),"-01-01"),
155-
tz = "UTC")
156-
tvals <- sub.sipnet.output[["day"]] + sub.sipnet.output[["time"]] / 24
157-
if (sub.sipnet.output[["day"]][1]-pecan_start_doy==1) {
158-
sub_dates <- sipnet2datetime(tvals, y, force_cf = FALSE)
159-
} else {
160-
sub_dates <- sipnet2datetime(tvals, y, force_cf = TRUE)
161-
}
162-
sub_dates_cf <- PEcAn.utils::datetime2cf(sub_dates, paste0("days since ",paste0(y,"-01-01")))
163-
164129
# create netCDF time.bounds variable
165130
bounds <- array(data=NA, dim=c(length(sub_dates_cf),2))
166131
bounds[,1] <- sub_dates_cf
@@ -172,17 +137,17 @@ model2netcdf.SIPNET <- function(outdir, sitelat, sitelon, start_date, end_date,
172137
output <- list(
173138
"GPP" = (sub.sipnet.output$gpp * 0.001) / timestep.s, # GPP in kgC/m2/s
174139
"NPP" = (sub.sipnet.output$gpp * 0.001) / timestep.s - ((sub.sipnet.output$rAboveground *
175-
0.001) / timestep.s + (sub.sipnet.output$rRoot * 0.001) / timestep.s), # NPP in kgC/m2/s. Post SIPNET calculation
140+
0.001) / timestep.s + (sub.sipnet.output$rRoot * 0.001) / timestep.s), # NPP in kgC/m2/s. Post SIPNET calculation
176141
"TotalResp" = (sub.sipnet.output$rtot * 0.001) / timestep.s, # Total Respiration in kgC/m2/s
177142
"AutoResp" = (sub.sipnet.output$rAboveground * 0.001) / timestep.s + (sub.sipnet.output$rRoot *
178-
0.001) / timestep.s, # Autotrophic Respiration in kgC/m2/s
143+
0.001) / timestep.s, # Autotrophic Respiration in kgC/m2/s
179144
"HeteroResp" = ((sub.sipnet.output$rSoil - sub.sipnet.output$rRoot) * 0.001) / timestep.s, # Heterotrophic Respiration in kgC/m2/s
180145
"SoilResp" = (sub.sipnet.output$rSoil * 0.001) / timestep.s, # Soil Respiration in kgC/m2/s
181146
"NEE" = (sub.sipnet.output$nee * 0.001) / timestep.s, # NEE in kgC/m2/s
182147
"AbvGrndWood" = (sub.sipnet.output$plantWoodC * 0.001), # Above ground wood kgC/m2
183148
"leaf_carbon_content" = (sub.sipnet.output$plantLeafC * 0.001), # Leaf C kgC/m2
184149
"TotLivBiom" = (sub.sipnet.output$plantWoodC * 0.001) + (sub.sipnet.output$plantLeafC * 0.001) +
185-
(sub.sipnet.output$coarseRootC + sub.sipnet.output$fineRootC) * 0.001, # Total living C kgC/m2
150+
(sub.sipnet.output$coarseRootC + sub.sipnet.output$fineRootC) * 0.001, # Total living C kgC/m2
186151
"TotSoilCarb" = (sub.sipnet.output$soil * 0.001) + (sub.sipnet.output$litter * 0.001) # Total soil C kgC/m2
187152
)
188153
if (revision == "unk") {
@@ -193,7 +158,7 @@ model2netcdf.SIPNET <- function(outdir, sitelat, sitelon, start_date, end_date,
193158
## latent heat of vaporization is not constant and it varies slightly with temperature, get.lv() returns 2.5e6 J kg-1 by default
194159
output[["Qle"]] <- (sub.sipnet.output$npp * 10 * PEcAn.data.atmosphere::get.lv()) / timestep.s # Qle W/m2
195160
} else {
196-
output[["Qle"]] <- (sub.sipnet.output$evapotranspiration * 10 * PEcAn.data.atmosphere::get.lv()) / timestep.s # Qle W/m2
161+
output[["Qle"]] <- (sub.sipnet.output$evapotranspiration * 10 * PEcAn.data.atmosphere::get.lv()) / timestep.s # Qle W/m2
197162
}
198163
output[["Transp"]] <- (sub.sipnet.output$fluxestranspiration * 10) / timestep.s # Transpiration kgW/m2/s
199164
output[["SoilMoist"]] <- (sub.sipnet.output$soilWater * 10) # Soil moisture kgW/m2
@@ -203,8 +168,8 @@ model2netcdf.SIPNET <- function(outdir, sitelat, sitelon, start_date, end_date,
203168
output[["litter_mass_content_of_water"]] <- (sub.sipnet.output$litterWater * 10) # Litter water kgW/m2
204169
#calculate LAI for standard output
205170
param <- utils::read.table(file.path(gsub(pattern = "/out/",
206-
replacement = "/run/", x = outdir),
207-
"sipnet.param"), stringsAsFactors = FALSE)
171+
replacement = "/run/", x = outdir),
172+
"sipnet.param"), stringsAsFactors = FALSE)
208173
id <- which(param[, 1] == "leafCSpWt")
209174
leafC <- 0.48
210175
SLA <- 1000 / param[id, 2] #SLA, m2/kgC
@@ -217,11 +182,11 @@ model2netcdf.SIPNET <- function(outdir, sitelat, sitelon, start_date, end_date,
217182

218183
# ******************** Declare netCDF variables ********************#
219184
t <- ncdf4::ncdim_def(name = "time",
220-
longname = "time",
221-
units = paste0("days since ", y, "-01-01 00:00:00"),
222-
vals = sub_dates_cf,
223-
calendar = "standard",
224-
unlim = TRUE)
185+
longname = "time",
186+
units = paste0("days since ", y, "-01-01 00:00:00"),
187+
vals = sub_dates_cf,
188+
calendar = "standard",
189+
unlim = TRUE)
225190
lat <- ncdf4::ncdim_def("lat", "degrees_north", vals = as.numeric(sitelat),
226191
longname = "station_latitude")
227192
lon <- ncdf4::ncdim_def("lon", "degrees_east", vals = as.numeric(sitelon),
@@ -232,12 +197,12 @@ model2netcdf.SIPNET <- function(outdir, sitelat, sitelon, start_date, end_date,
232197
vals = 1:2, units="")
233198

234199
## ***** Need to dynamically update the UTC offset here *****
235-
200+
236201
for (i in seq_along(output)) {
237202
if (length(output[[i]]) == 0)
238203
output[[i]] <- rep(-999, length(t$vals))
239204
}
240-
205+
241206
# ******************** Declare netCDF variables ********************#
242207
mstmipvar <- PEcAn.utils::mstmipvar
243208
nc_var <- list(
@@ -264,12 +229,12 @@ model2netcdf.SIPNET <- function(outdir, sitelat, sitelon, start_date, end_date,
264229
"fine_root_carbon_content" = PEcAn.utils::to_ncvar("fine_root_carbon_content", dims),
265230
"coarse_root_carbon_content" = PEcAn.utils::to_ncvar("coarse_root_carbon_content", dims),
266231
"GWBI" = ncdf4::ncvar_def("GWBI", units = "kg C m-2", dim = list(lon, lat, t), missval = -999,
267-
longname = "Gross Woody Biomass Increment"),
232+
longname = "Gross Woody Biomass Increment"),
268233
"AGB" = ncdf4::ncvar_def("AGB", units = "kg C m-2", dim = list(lon, lat, t), missval = -999,
269-
longname = "Total aboveground biomass"),
234+
longname = "Total aboveground biomass"),
270235
"time_bounds" = ncdf4::ncvar_def(name="time_bounds", units='',
271-
longname = "history time interval endpoints", dim=list(time_interval,time = t),
272-
prec = "double")
236+
longname = "history time interval endpoints", dim=list(time_interval,time = t),
237+
prec = "double")
273238
)
274239

275240
# ******************** Create netCDF and output variables ********************#
@@ -305,7 +270,7 @@ model2netcdf.SIPNET <- function(outdir, sitelat, sitelon, start_date, end_date,
305270
ncdf4::nc_close(nc)
306271
}
307272
} ### End of year loop
308-
273+
309274
## Delete raw output, if requested
310275
if (delete.raw) {
311276
file.remove(sipnet_out_file)

models/sipnet/man/sipnet2datetime.Rd

Lines changed: 0 additions & 23 deletions
This file was deleted.

0 commit comments

Comments
 (0)