Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add functions that can extract and update phenological status based on MODIS data and enable monthly SDA #3249

Merged
merged 43 commits into from
Jun 5, 2024
Merged
Show file tree
Hide file tree
Changes from 25 commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
c297b8d
Adding qsub_parallel module into pecan.
Apr 13, 2023
ff0a52a
Making the function more robust.
Apr 24, 2023
b8f97d3
Adding the gamma parameters' names.
Apr 24, 2023
4333f11
Correct the way of updating Q.
Apr 24, 2023
4f4630b
Avoid extremely high SoilC.
Apr 24, 2023
b88db33
Update.
Apr 24, 2023
657339f
Use Alexis's script for reading adjusted soil respiration parameter
Jun 24, 2023
a49452b
Solve the conflict
Jun 24, 2023
f267550
Enable monthly SDA
Jul 17, 2023
b8dc5db
Resolve the confilct
Jul 17, 2023
5c9a987
Add update_phenology function
Aug 22, 2023
316ba1b
Resolve conflict when merging Dongchen's PR
Aug 23, 2023
8a4ea3e
Merge remote-tracking branch 'Dongchen_branch/develop' into develop
Aug 25, 2023
b80deff
Adjust update_phenology function based on MODIS phenology data
Sep 1, 2023
8ff4f4d
Fix some bugs
Sep 1, 2023
fb96d6f
Add leaf_phenology_extract script and merge from upstream
Jan 11, 2024
8d5ba2f
Fix some bugs of block-based SDA run and update phenology_MODIS_extra…
Jan 18, 2024
8f8623f
Merge branch 'develop' of https://github.com/Qianyuxuan/pecan into de…
Jan 19, 2024
84c9d6b
Add documention and adjust some spaces
Jan 19, 2024
59947a3
Update Create_Multi_settings.R to include the path for setting up lea…
Jan 20, 2024
b326828
Pull from upstream and resolve conflicts
Mar 8, 2024
9136c43
Revert to multisite mapping approach
Mar 8, 2024
bb33db1
Fix bugs in preparing initial LAI
Mar 22, 2024
5f46ddf
Merge branch 'develop' of https://github.com/PecanProject/pecan into …
Mar 22, 2024
1e1fde6
Delete files that were placed by mistake
Mar 25, 2024
2c77fd9
Update per Mike and Chris' comments
Mar 26, 2024
e6c683a
load the files into a specific envir
Mar 26, 2024
f56eed2
Update default arguments in functions
Mar 27, 2024
45007d2
remove timepoint arguement in write_config
May 30, 2024
a15b0fc
Resolve conflict after merging
May 30, 2024
68e4404
Update per Mike's comments
Jun 3, 2024
7b4839d
Small tweak
Jun 3, 2024
c3abe88
Apply suggestions
Jun 4, 2024
ac67c02
Merge branch 'develop' of https://github.com/PecanProject/pecan into …
Jun 4, 2024
bb5d40c
Update base/workflow/R/do_conversions.R
mdietze Jun 4, 2024
5ce785c
Update models/sipnet/R/write.configs.SIPNET.R
mdietze Jun 4, 2024
82a1713
Update models/sipnet/man/write.config.SIPNET.Rd
mdietze Jun 4, 2024
47ef5c4
Update document
Jun 4, 2024
212cdfa
Apply suggestions from code review
mdietze Jun 4, 2024
d6a4a3d
Update models/sipnet/R/write.configs.SIPNET.R
mdietze Jun 4, 2024
a6f3834
Update document
Jun 4, 2024
5280bfd
Revert back to filtering
Jun 5, 2024
d033c72
Merge branch 'develop' into sda_monthly_modex
mdietze Jun 5, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
13 changes: 10 additions & 3 deletions models/sipnet/R/model2netcdf.SIPNET.R
Original file line number Diff line number Diff line change
Expand Up @@ -97,13 +97,14 @@ sipnet2datetime <- function(sipnet_tval, base_year, base_month = 1,
##' @param revision model revision
##' @param overwrite Flag for overwriting nc files or not
##' @param conflict Flag for dealing with conflicted nc files, if T we then will merge those, if F we will jump to the next.
##' conflict is set to TRUE to enable the assimilation of monthly data.
##' @param prefix prefix to read the output files
##' @param delete.raw Flag to remove sipnet.out files, FALSE = do not remove files TRUE = remove files
##'
##' @export
##' @author Shawn Serbin, Michael Dietze
model2netcdf.SIPNET <- function(outdir, sitelat, sitelon, start_date, end_date, delete.raw = FALSE, revision, prefix = "sipnet.out",
overwrite = FALSE, conflict = FALSE) {
overwrite = FALSE, conflict = TRUE) {
Qianyuxuan marked this conversation as resolved.
Show resolved Hide resolved

### Read in model output in SIPNET format
sipnet_out_file <- file.path(outdir, prefix)
Expand Down Expand Up @@ -296,13 +297,19 @@ model2netcdf.SIPNET <- function(outdir, sitelat, sitelon, start_date, end_date,
close(varfile)
ncdf4::nc_close(nc)

#merge nc files
#merge nc files of the same year together to enable the assimilation of subannual data
if(file.exists(file.path(outdir, "previous.nc"))){
files <- c(file.path(outdir, "previous.nc"), file.path(outdir, "current.nc"))
}else{
files <- file.path(outdir, "current.nc")
}
mergeNC(files = files, outfile = file.path(outdir, paste(y, "nc", sep = ".")))
#The command "cdo" in mergeNC will automatically rename "time_bounds" to "time_bnds". However, "time_bounds" is used
#in read_restart codes later. So we need to read the new NetCDF file and convert the variable name back.
infotroph marked this conversation as resolved.
Show resolved Hide resolved
nc<- ncdf4::nc_open(file.path(outdir, paste(y, "nc", sep = ".")),write=TRUE)
nc<-ncdf4::ncvar_rename(nc,"time_bnds","time_bounds")
ncdf4::ncatt_put(nc, "time", "bounds","time_bounds", prec=NA)
ncdf4::nc_close(nc)
unlink(files, recursive = T)
}else{
nc <- ncdf4::nc_create(file.path(outdir, paste(y, "nc", sep = ".")), nc_var)
Expand All @@ -323,4 +330,4 @@ model2netcdf.SIPNET <- function(outdir, sitelat, sitelon, start_date, end_date,
}
} # model2netcdf.SIPNET
#--------------------------------------------------------------------------------------------------#
### EOF
### EOF
29 changes: 24 additions & 5 deletions models/sipnet/R/write.configs.SIPNET.R
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
##' @export
##' @author Michael Dietze
write.config.SIPNET <- function(defaults, trait.values, settings, run.id, inputs = NULL, IC = NULL,
restart = NULL, spinup = NULL) {
restart = NULL, spinup = NULL,obs_time=NULL,update_phenology=NULL) {
mdietze marked this conversation as resolved.
Show resolved Hide resolved
### WRITE sipnet.in
template.in <- system.file("sipnet.in", package = "PEcAn.SIPNET")
config.text <- readLines(con = template.in, n = -1)
Expand Down Expand Up @@ -437,7 +437,26 @@ write.config.SIPNET <- function(defaults, trait.values, settings, run.id, inputs
if ("leafGrowth" %in% pft.names) {
param[which(param[, 1] == "leafGrowth"), 2] <- pft.traits[which(pft.names == "leafGrowth")]
}
} ## end loop over PFTS

#update LeafOnday and LeafOffDay
if (update_phenology){
Qianyuxuan marked this conversation as resolved.
Show resolved Hide resolved
leaf_pheno_outdir <- settings$model$leaf_phenology$outdir ## read from settings
Qianyuxuan marked this conversation as resolved.
Show resolved Hide resolved
if (!is.null(leaf_pheno_outdir)) {
##read data
leafphdata <- utils::read.csv(paste0(leaf_pheno_outdir,"/leaf_phenology.csv"))
leafOnDay <- leafphdata$leafonday[leafphdata$year==obs_time & leafphdata$site_id==settings$run$site$id]
leafOffDay<- leafphdata$leafoffday[leafphdata$year==obs_time & leafphdata$site_id==settings$run$site$id]
Copy link
Member

Choose a reason for hiding this comment

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

  1. why does this have to be obs_time instead of start.date?
  2. I would expect obs_time to be in a datetime format, and yet you're matching it against year? Seems like they're a missing extraction of year
  3. how does SDA update the phenological status if the phenology is prescribed?
  4. If you're adding a whole new class of inputs to the PEcAn system (prescribed phenology) there needs to be updates to the general documentation explaining any new tags, new sources, new file formats, etc. Indeed, there needs to be updates explaining how to run the sub-annual SDA more generally as well.

if (!is.na(leafOnDay)){
param[which(param[, 1] == "leafOnDay"), 2] <- leafOnDay
}
if (!is.na(leafOffDay)){
param[which(param[, 1] == "leafOffDay"), 2] <- leafOffDay
}
}else {
PEcAn.logger::logger.info("No phenology data were found. Please consider running modules/data.remote/R/phenology_MODIS_extract.R to get the parameter file.")
}
}
} ## end loop over PFTS
####### end parameter update
#working on reading soil file (only working for 1 soil file)
if(length(settings$run$inputs$soilinitcond$path)==1){
Expand Down Expand Up @@ -466,7 +485,7 @@ write.config.SIPNET <- function(defaults, trait.values, settings, run.id, inputs
}else{
wood_total_C <- IC$AbvGrndWood / IC$abvGrndWoodFrac
}

#Sanity check
if (is.infinite(wood_total_C) | is.nan(wood_total_C) | wood_total_C < 0) {
wood_total_C <- 0
Expand Down Expand Up @@ -522,7 +541,7 @@ write.config.SIPNET <- function(defaults, trait.values, settings, run.id, inputs
ICs_num <- length(settings$run$inputs$poolinitcond$path)
IC.path <- settings$run$inputs$poolinitcond$path[[sample(1:ICs_num, 1)]]

IC.pools <- PEcAn.data.land::prepare_pools(IC.path, constants = list(sla = SLA))
IC.pools <- PEcAn.data.land::prepare_pools(IC.path, settings, constants = list(sla = SLA))

if(!is.null(IC.pools)){
IC.nc <- ncdf4::nc_open(IC.path) #for additional variables specific to SIPNET
Expand Down Expand Up @@ -630,4 +649,4 @@ remove.config.SIPNET <- function(main.outdir, settings) {
} else {
print("*** WARNING: Removal of files on remote host not yet implemented ***")
}
} # remove.config.SIPNET
} # remove.config.SIPNET
18 changes: 11 additions & 7 deletions models/sipnet/R/write_restart.SIPNET.R
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,17 @@
##' @param RENAME flag to either rename output file or not
##' @param new.params list of parameters to convert between different states
##' @param inputs list of model inputs to use in write.configs.SIPNET
##' @param obs_time obervation timepoints
##' @param update_phenology TRUE if we want to update the phenological data (i.e. leaf-on and leaf-off dates) for each restart run during SDA
##' @param verbose decide if we want to print the outputs.
##'
##' @description Write restart files for SIPNET. WARNING: Some variables produce illegal values < 0 and have been hardcoded to correct these values!!
##'
##' @return NONE
##' @export
write_restart.SIPNET <- function(outdir, runid, start.time, stop.time, settings, new.state,
RENAME = TRUE, new.params = FALSE, inputs, verbose = FALSE) {
RENAME = TRUE, new.params = FALSE, inputs, obs_time=NULL, update_phenology=NULL, verbose = FALSE) {
mdietze marked this conversation as resolved.
Show resolved Hide resolved

rundir <- settings$host$rundir
variables <- colnames(new.state)
# values that will be used for updating other states deterministically depending on the SDA states
Expand Down Expand Up @@ -59,15 +61,15 @@ write_restart.SIPNET <- function(outdir, runid, start.time, stop.time, settings,
analysis.save[[length(analysis.save) + 1]] <- PEcAn.utils::ud_convert(new.state$NPP, "kg/m^2/s", "Mg/ha/yr") #*unit.conv -> Mg/ha/yr
names(analysis.save[[length(analysis.save)]]) <- c("NPP")
}

if ("NEE" %in% variables) {
analysis.save[[length(analysis.save) + 1]] <- new.state$NEE
names(analysis.save[[length(analysis.save)]]) <- c("NEE")
}

if ("AbvGrndWood" %in% variables) {
AbvGrndWood <- PEcAn.utils::ud_convert(new.state$AbvGrndWood, "Mg/ha", "g/m^2")
analysis.save[[length(analysis.save) + 1]] <- AbvGrndWood
analysis.save[[length(analysis.save) + 1]] <- AbvGrndWood
names(analysis.save[[length(analysis.save)]]) <- c("AbvGrndWood")
}

Expand Down Expand Up @@ -106,7 +108,7 @@ write_restart.SIPNET <- function(outdir, runid, start.time, stop.time, settings,
if (analysis.save[[length(analysis.save)]] < 0) analysis.save[[length(analysis.save)]] <- 0
names(analysis.save[[length(analysis.save)]]) <- c("SWE")
}

if ("LAI" %in% variables) {
analysis.save[[length(analysis.save) + 1]] <- new.state$LAI
if (new.state$LAI < 0) analysis.save[[length(analysis.save)]] <- 0
Expand All @@ -120,7 +122,7 @@ write_restart.SIPNET <- function(outdir, runid, start.time, stop.time, settings,
}else{
analysis.save.mat <- NULL
}

if (verbose) {
print(runid %>% as.character())
print(analysis.save.mat)
Expand All @@ -130,6 +132,8 @@ write_restart.SIPNET <- function(outdir, runid, start.time, stop.time, settings,
settings = settings,
run.id = runid,
inputs = inputs,
IC = analysis.save.mat))
IC = analysis.save.mat,
obs_time=obs_time,
update_phenology=update_phenology))
mdietze marked this conversation as resolved.
Show resolved Hide resolved
print(runid)
} # write_restart.SIPNET
5 changes: 3 additions & 2 deletions models/sipnet/man/model2netcdf.SIPNET.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion models/sipnet/man/write.config.SIPNET.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions models/sipnet/man/write_restart.SIPNET.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

21 changes: 10 additions & 11 deletions modules/assim.sequential/R/Analysis_sda_block.R
Original file line number Diff line number Diff line change
Expand Up @@ -112,9 +112,9 @@ build.block.xy <- function(settings, block.list.all, X, obs.mean, obs.cov, t) {
PEcAn.logger::logger.warn("The zero variances in Pf is being replaced by one fifth of the minimum variance in those matrices respectively.")
}
#distance calculations and localization
site.locs <- settings$run %>%
purrr::map('site') %>%
purrr::map_dfr(~c(.x[['lon']],.x[['lat']]) %>% as.numeric)%>%
site.locs <- settings$run %>%
purrr::map('site') %>%
purrr::map_dfr(~c(.x[['lon']],.x[['lat']]) %>% as.numeric)%>%
t %>%
`colnames<-`(c("Lon","Lat")) %>%
`rownames<-`(site.ids)
Expand Down Expand Up @@ -172,9 +172,9 @@ build.block.xy <- function(settings, block.list.all, X, obs.mean, obs.cov, t) {
# if there is any site that has zero observation.
if (any(obs_per_site == 0)) {
#name matching between observation names and state variable names.
f.2.y.ind <- obs.mean[[t]] %>%
purrr::map(\(x)which(var.names %in% names(x))) %>%
unlist %>%
f.2.y.ind <- obs.mean[[t]] %>%
purrr::map(\(x)which(var.names %in% names(x))) %>%
unlist %>%
unique
H <- list(ind = f.2.y.ind %>% purrr::map(function(start){
seq(start, length(site.ids) * length(var.names), length(var.names))
Expand Down Expand Up @@ -283,9 +283,9 @@ build.block.xy <- function(settings, block.list.all, X, obs.mean, obs.cov, t) {
if (is.null(obs.mean[[t]])) {
f.2.y.ind <- seq_along(var.names)
} else {
f.2.y.ind <- obs.mean[[t]] %>%
purrr::map(\(x)which(var.names %in% names(x))) %>%
unlist %>%
f.2.y.ind <- obs.mean[[t]] %>%
purrr::map(\(x)which(var.names %in% names(x))) %>%
unlist %>%
unique
}
seq.ind <- f.2.y.ind %>% purrr::map(function(start){
Expand Down Expand Up @@ -430,7 +430,7 @@ MCMC_block_function <- function(block) {
conf$addSampler(target = samplerLists[[X.mod.ind]]$target, type = "ess",
control = list(propCov= block$data$pf, adaptScaleOnly = TRUE,
latents = "X", pfOptimizeNparticles = TRUE))

#add toggle Y sampler.
for (i in 1:block$constant$YN) {
conf$addSampler(paste0("y.censored[", i, "]"), 'toggle', control=list(type='RW'))
Expand All @@ -451,7 +451,6 @@ MCMC_block_function <- function(block) {

#run MCMC
dat <- runMCMC(Cmcmc, niter = block$MCMC$niter, nburnin = block$MCMC$nburnin, thin = block$MCMC$nthin, nchains = block$MCMC$nchain)

#update aq, bq, mua, and pa
M <- colMeans(dat)
block$update$aq <- block$Inits$q
Expand Down
4 changes: 2 additions & 2 deletions modules/assim.sequential/R/Nimble_codes.R
Original file line number Diff line number Diff line change
Expand Up @@ -187,14 +187,14 @@ GEF.MultiSite.Nimble <- nimbleCode({
# Sorting out qs
q[1:YN, 1:YN] ~ dwish(R = aq[1:YN, 1:YN], df = bq) ## aq and bq are estimated over time
}

for (i in 1:nH) {
tmpX[i] <- X.mod[H[i]]
Xs[i] <- tmpX[i]
}
## add process error to x model but just for the state variables that we have data and H knows who
X[1:YN] ~ dmnorm(Xs[1:YN], prec = q[1:YN, 1:YN])

## Likelihood
y.censored[1:YN] ~ dmnorm(X[1:YN], prec = r[1:YN, 1:YN])

Expand Down
9 changes: 5 additions & 4 deletions modules/assim.sequential/R/SDA_OBS_Assembler.R
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ SDA_OBS_Assembler <- function(settings){
}

#prepare site_info offline, because we need to submit this to server remotely, which might not support the Bety connection.
site_info <- settings$run %>%
site_info <- settings$run %>%
purrr::map('site')%>%
purrr::map(function(site.list){
#conversion from string to number
Expand Down Expand Up @@ -189,12 +189,13 @@ SDA_OBS_Assembler <- function(settings){
for (j in seq_along(obs.mean[[i]])) {
if (sum(is.na(obs.mean[[i]][[j]]))){
na_ind <- which(is.na(obs.mean[[i]][[j]]))
obs.mean[[i]][[j]] <- obs.mean[[i]][[j]][-na_ind]
if(length(new_diag(obs.cov[[i]][[j]])) == 1){
#obs.mean[[i]][[j]] <- obs.mean[[i]][[j]][-na_ind]
if(length(obs.mean[[i]][[j]]) == 1){
obs.cov[[i]][[j]] <- obs.cov[[i]][[j]][-na_ind]
}else{
obs.cov[[i]][[j]] <- obs.cov[[i]][[j]][-na_ind, -na_ind]
}
obs.mean[[i]][[j]] <- obs.mean[[i]][[j]][-na_ind]
}
SoilC_ind <- which(names(obs.mean[[i]][[j]]) == "TotSoilCarb")
if (length(SoilC_ind) > 0){
Expand All @@ -217,7 +218,7 @@ SDA_OBS_Assembler <- function(settings){
Obs_Prep[var_ind] %>% purrr::map(~.x$end.date)),
function(var_timestep, var_start_date, var_end_date){
obs_timestep2timepoint(var_start_date, var_end_date, var_timestep)
}) %>%
}) %>%
purrr::map(function(all_timepoints){
all_timepoints[which(!all_timepoints %in% time_points)]
}) %>%
Expand Down
12 changes: 8 additions & 4 deletions modules/assim.sequential/R/sda.enkf_MultiSite.R
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,8 @@ sda.enkf.multisite <- function(settings,
keepNC = TRUE,
forceRun = TRUE,
run_parallel = TRUE,
MCMC.args = NULL),
MCMC.args = NULL,
update_phenology = TRUE),
...) {
#add if/else for when restart points to folder instead if T/F set restart as T
if(is.list(restart)){
Expand Down Expand Up @@ -296,7 +297,8 @@ sda.enkf.multisite <- function(settings,
settings = settings,
model = settings$model$type,
write.to.db = settings$database$bety$write,
restart = restart.arg
restart = restart.arg,
update_phenology=control$update_phenology
)
}) %>%
stats::setNames(site.ids)
Expand Down Expand Up @@ -420,7 +422,9 @@ sda.enkf.multisite <- function(settings,
model = settings$model$type,
write.to.db = settings$database$bety$write,
restart = restart.arg,
rename = TRUE
rename = TRUE,
time = obs.year,
update_phenology=control$update_phenology
)
}) %>%
stats::setNames(site.ids)
Expand Down Expand Up @@ -530,7 +534,7 @@ sda.enkf.multisite <- function(settings,
if (is.null(control$MCMC.args)) {
MCMC.args <- list(niter = 1e5,
nthin = 10,
nchain = 3,
nchain = 1,
mdietze marked this conversation as resolved.
Show resolved Hide resolved
nburnin = 5e4)
} else {
MCMC.args <- control$MCMC.args
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -240,8 +240,9 @@ template <- PEcAn.settings::Settings(list(
type = "SIPNET",
revision = "ssr",
delete.raw = FALSE,
binary = model_binary,
jobtemplate = "~/sipnet_geo.job"
binary = "/usr2/postdoc/istfer/SIPNET/trunk//sipnet_if",
Qianyuxuan marked this conversation as resolved.
Show resolved Hide resolved
jobtemplate = "~/sipnet_geo.job",
leaf_phenology=structure(list(outdir="USER_DEFINED"))
)),

###########################################################################
Expand Down