Git Product home page Git Product logo

lwfbrook90r's Introduction

LWFBrook90R

Project Status: Active – The project has reached a stable, usable state and is being actively developed. R-CMD-check CRAN License: GPL v3 DOI

LWFBrook90R provides an implementation of the Soil Vegetation Atmosphere Transport (SVAT) model LWF-BROOK90 (Hammel & Kennel, 2001) written in Fortran. The model simulates daily transpiration, interception, soil and snow evaporation, streamflow and soil water fluxes through a soil profile covered with vegetation. A set of high-level functions for model set up, execution and parallelization provide easy access to plot-level SVAT simulations, as well as multi-run and large-scale applications.

Installation

You can install the released version of LWFBrook90R from CRAN with:

install.packages("LWFBrook90R")

and the development version can be installed from Github using the package remotes:

remotes::install_github(repo="pschmidtwalter/LWFBrook90R", build_vignettes = TRUE) 

Usage

Below is basic example. For more complex examples take a look at the packages vignettes with browseVignettes("LWFBrook90R").

The main function run_LWFB90() creates the model input from model control options, parameters, climate and soil data and returns the simulation results.

# load package and sample data
library(LWFBrook90R)
data(slb1_meteo, slb1_soil)

# set up default model control options and parameters
opts <- set_optionsLWFB90()
parms <- set_paramLWFB90()

# Derive soil hydraulic properties from soil physical properties 
# using a pedotransfer function: 
soil <- cbind(slb1_soil, hydpar_wessolek_tab(texture = slb1_soil$texture))

# run the model and capture results
lwfb90_res <- run_LWFB90(options_b90 = opts,
                         param_b90 = parms,
                         climate = slb1_meteo,
                         soil = soil)

Plot results

Citation

Schmidt-Walter, P., Trotsiuk, V., Meusburger, K., Zacios, M., Meesenburg, H. (2020): Advancing simulations of water fluxes, soil moisture and drought stress by using the LWF-Brook90 hydrological model in R. Agr. For. Met. 291, 108023. https://doi.org/10.1016/j.agrformet.2020.108023

Contributions

Implementations of further methods for creating model input (e.g. leaf area dynamics, root depth density distributions, pedotransfer functions) and other improvements are highly welcome.

Authors

Paul Schmidt-Walter, Volodymyr Trotsiuk, Klaus Hammel, Martin Kennel, Tony Federer.

Tony Federer’s original Brook90 Fortran 77 code (Brook90_v3.1F, License: CC0) was enhanced by Klaus Hammel and Martin Kennel at Bavarian State Institute of Forestry (LWF) around the year 2000. Since then, LWF-BROOK90 is distributed by LWF upon request as a pre-compiled Fortran command line program together with an MS Access User Interface. In 2019, Volodymyr Trotsiuk converted the Fortran 77 code to Fortran 95 and implemented the connection to R. Paul Schmidt-Walter’s brook90r (Schmidt-Walter, 2018) package for LWF-Brook90 input data generation, model execution and result processing was adapted and extended to control this interface function.

License

GPL-3 for all Fortran and R code. brook90r has GPL-3, while LWF-Brook90 was without license until recently. Lothar Zimmermann and Stephan Raspe (LWF), and all previous Fortran contributors agreed to assign GPL-3 to the Fortran code.

References

Federer C.A. (2002): BROOK 90: A simulation model for evaporation, soil water, and streamflow. http://www.ecoshift.net/brook/brook90.htm

Federer C.A., Vörösmarty, C., Fekete, B. (2003): Sensitivity of Annual Evaporation to Soil and Root Properties in Two Models of Contrasting Complexity. J. Hydrometeorol. 4, 1276–1290. https://doi.org/10.1175/1525-7541(2003)004%3C1276:SOAETS%3E2.0.CO;2

Hammel, K., Kennel, M. (2001): Charakterisierung und Analyse der Wasserverfügbarkeit und des Wasserhaushalts von Waldstandorten in Bayern mit dem Simulationsmodell BROOK90. Forstliche Forschungsberichte München 185. ISBN 978-3-933506-16-0

Schmidt-Walter, P. (2018). brook90r: Run the LWF-BROOK90 hydrological model from within R (Version v1.0.1). Zenodo. https://doi.org/10.5281/zenodo.1433677

lwfbrook90r's People

Contributors

fabern avatar pschmidtwalter avatar trotsiuk avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

lwfbrook90r's Issues

Species settings (Picea abies)

For function vegperiod the argument species comprises three different settings for Picea abies ("Picea abies (frueh)", "Picea abies (spaet)", "Picea abies (noerdl.)"). What do these specifications refer to and how to decide which one to use?

Error in precipitation correction

Hi Paul, since you asked me to report "findings" here,...

The precipitation correction throws an error:

Error in correct_prec(month, tmean, prec, station.exposure = param_b90$prec_corr_statexp) :
data vectors have to be of the same lengths

in Line 59 in run_LWFB90 column "month" in "climate" is needed, but not found.

if (options_b90$correct_prec == TRUE) {
climate$prec <- with(climate, correct_prec(month, tmean,
prec, station.exposure = param_b90$prec_corr_statexp))
}

I think inserting
climate$month <- as.integer(format(climate$dates, "%m"))
might solve the problem.

Probably also correcting for precipitation timesteps of less than a day needs to be swiched off.

Best regards!

Examples take too long

R CMD check Note:

Examples with CPU (user + system) or elapsed time > 5s
               user system elapsed
msiterunLWFB90 1.32  0.297   6.429

texture classification in PTF HYPRES

The equations in hydpar_hypres are identical (given the change in the order of magnitude due to different units) to the equations in Woesten et al (1999). However, according to the paper, HYPRES was developed with a 2-50 μm silt-range. The other two PTFs PTFPUH2 and WESSOLEK use a 2-63μm silt range. So far, there is no correction for that implemented, is that correct?

For example, the package "soiltexture" provides a correction for different texture classes:

test <- data.frame("CLAY" = c(5,60,15,5,25), "SILT" = c(5,8,15,25,65), "SAND" = c(90, 32, 70, 70, 10))
soiltexture::TT.text.transf(tri.data = test, base.css.ps.lim = c(0,2,50,2000), dat.css.ps.lim = c(0,2,63,2000))

For large silt-percentages this makes quite a difference. Wouldn't it be good to implement such a correction into hydpar_hypres?

make_standprop not using stand properties from table input

Move the construction of daily standproperties from runLWFB90 to make_standprop. Currently, the function only uses values from parameters, and not from an optional table (as specified in param.b90$standprop_table), although all information is available without the need to change arguments.

bug in MakeRelRoots

Hello,
I think I found a bug in the MakeRelRoots function. The current versions of the relroot-output produced by "linear" and "constant" do not take into account the thickness of the layers.
For reproducing, you'll have to load "dplyr", "tidyr", "ggplot2", and "LWFBrook90R".

# Loading necessary Packages
lapply(c("dplyr", "tidyr", "ggplot2", "LWFBrook90R"), library, character.only = T)
# creating unevenly distributed depths to make the effect more visible: 
depths <- c(seq(-0.01, -0.19, by = -0.01), 
            seq(-0.20, -1.4, by = -0.05))

Current Version

This is what the current MakeRelRootDens-function produces:

roots_constant <- MakeRelRootDens(soilnodes = depths,
                                  maxrootdepth = -1.4,
                                  method = 'const',
                                  relrootden = 0.2)

roots_linear <- MakeRelRootDens(soilnodes = depths,
                                maxrootdepth = -1.4,
                                method = 'linear',
                                relrootden = 0.2)

df <- data.frame("depths" = depths, 
                 "roots_constant" = roots_constant, 
                 "roots_linear"  = roots_linear) %>% 
  mutate(diff = c(depths[1]*-100, diff(depths)*-100),             # visualising relroots per cm         
         roots_linear_percm = roots_linear/diff, 
         roots_constant_percm = roots_constant/diff, 
         roots_constant_cumsum = cumsum(roots_constant),          # visualising cumulative sums: 
         roots_linear_cumsum = cumsum(roots_linear)) %>% 
  dplyr::select(-diff) %>% 
  pivot_longer(cols = roots_constant:roots_linear_cumsum)


ggplot(df, aes(x = depths, y = value))+
  coord_flip()+
  geom_line()+
  facet_wrap(facets = vars(name), 
             nrow = 2, 
             scales = "free_x")+
  ggtitle("current state: MakeRelRoots")

If you run this, you can see that the relroots-output results in a constant (top-left) and a linear (bottom-left) shaped line. However, I think that's not correct if you really want a constant or linearly decreasing root-density.
If you take into account the different thickness of the layers, and visualise the rootdensity per cm, you see that you'll get a sharp break when the layers' thickness changes (right column). Or, if you imagine digging up the soil profile and counting all the roots you'll find (cumulative sum - middle column), you'll neither get a constant increase with depth (which would be a straight line from 0 to max), or a linearly decreasing amount with depth (which cumulatively would be a steepening curve). Yet, both cumulative curves show a knee in the depth of the thickness change.

Alternative Version

I've created an alternative version of the function. I also fixed the small issue, that, if you give the function a maxrootdepth value that is larger than the lowest soilnode, the line

 soilnodes[which.max(soilnodes >= maxrootdepth)] <- maxrootdept

will assign the value of maxrootdepth to the first soilnode, which results in a wrong peak in the first value of rootden.

MakeRelRootDens.adj <-function(soilnodes,
                               maxrootdepth = min(soilnodes),
                               method = "betamodel",
                               beta = 0.97, #
                               relrootden = NULL,
                               rootdepths=NULL
                               #cum_RLenDmax= 0.95 # maximum cumulative rootlength, at which maximim
) {
  method <- match.arg(method, choices = c("betamodel", "table", "constant", "linear"))
  
  if (method == "betamodel") {
    # only positive d-values allowed in beta-model:
    # maxrootdepth <- soilnodes[which(abs(soilnodes - maxrootdepth) == min(abs(soilnodes-maxrootdepth)))]
    maxrootdepth <- maxrootdepth * (-100)
    soilnodes <- soilnodes * (-100)
    
    # replace first element greater maxrootdepth with maxrootdepth
    if(any(soilnodes >= maxrootdepth)){
      soilnodes[which.max(soilnodes >= maxrootdepth)] <- maxrootdepth
    }
    
    # cumulative density
    RLenDcum <- 1 - (beta ^ soilnodes)
    
    # density
    RLenD <- c(RLenDcum[1], diff(RLenDcum))
    RLenD[which(soilnodes>maxrootdepth)] <- 0
    
    rootden <- RLenD
  }
  
  if (method == "table") {
    RelDenFun <- stats::approxfun(x = rootdepths, y = relrootden,
                                  method = "linear", rule = 1:2,  yleft = 0)
    midpoints <- c(min(soilnodes) + 0.01, soilnodes[1:length(soilnodes)-1]) + (diff(c(min(soilnodes) +0.01, soilnodes))/2)
    rootden <- RelDenFun(midpoints)
  }
  
  if (method == "linear") {
    if (is.null(relrootden)) {
      relrootden <- 1
    }
    maxrootdepth <- maxrootdepth * (-100)
    soilnodes <- soilnodes * (-100)
    
    if(any(soilnodes >= maxrootdepth)){
      soilnodes[which.max(soilnodes >= maxrootdepth)] <- maxrootdepth
    }
    
    RLenDcum <- soilnodes
    RLenD <- c(RLenDcum[1], diff(RLenDcum))
    RLenD[which(soilnodes > maxrootdepth)] <- 0
    quotient <- ifelse(any(soilnodes > maxrootdepth), maxrootdepth/100, max(soilnodes)/100)
    rootden <- (quotient-cumsum(RLenD * 0.01))*RLenD
    
    rootden <- (rootden/quotient)*relrootden
  }

  
  if (method == "constant") {
    if (is.null(relrootden)) {
      relrootden <- 1
    }
    maxrootdepth <- maxrootdepth * (-100)
    soilnodes <- soilnodes * (-100)
    
    if(any(soilnodes >= maxrootdepth)){
      soilnodes[which.max(soilnodes >= maxrootdepth)] <- maxrootdepth
    }
    
    RLenDcum <- soilnodes
    RLenD <- c(RLenDcum[1], diff(RLenDcum))
    RLenD[which(soilnodes > maxrootdepth)] <- 0
    rootden <- RLenD*relrootden
  }
  return(rootden)
}

If you run this chunk, you'll see what it does:

roots_constant <- MakeRelRootDens.adj(soilnodes = depths,
                                      maxrootdepth = -1.4,
                                      method = 'const',
                                      relrootden = 0.2)

roots_linear <- MakeRelRootDens.adj(soilnodes = depths,
                                    maxrootdepth = -1.4,
                                    method = 'linear',
                                    relrootden = 0.2)

df <- data.frame("depths" = depths, 
                 "roots_constant" = roots_constant, 
                 "roots_linear"  = roots_linear) %>% 
  mutate(diff = c(depths[1]*-100, diff(depths)*-100), 
         roots_linear_percm = roots_linear/diff, 
         roots_constant_percm = roots_constant/diff, 
         roots_constant_cumsum = cumsum(roots_constant), 
         roots_linear_cumsum = cumsum(roots_linear)) %>% 
  dplyr::select(-diff) %>% 
  pivot_longer(cols = roots_constant:roots_linear_cumsum)


ggplot(df, aes(x = depths, y = value))+
  coord_flip()+
  geom_line()+
  facet_wrap(facets = vars(name), 
             nrow = 2, 
             scales = "free_x")+
  ggtitle("proposed change: MakeRelRoots")

With this version, for the constant root density, you'll get a curve that returns a constant rootdensity per centimetre, independent of the individual layer's thickness (top-right), and, bringing up the "digging and counting from the top"-example produces a constant increase with depth in the cumulative curve.
Plus, there's now a linear decrease with depth, when roots per cm is calculated (bottom-right), no break in the line of the cumulative root density (bottom-middle), and the interesting shape of the resulting rootden-output takes into account the different width of the layers.

It also works with more complex layer partitions. You may also try this:

depths <- c(seq(-0.01, -0.14, by = -0.01), 
            seq(-0.15, -0.23, by = -0.02),
            seq(-0.25, -0.52, by = -0.03),
            seq(-0.55, -1.4, by = -0.05))


roots_constant <- MakeRelRootDens.adj(soilnodes = depths,
                                  maxrootdepth = -1.4,
                                  method = 'const',
                                  relrootden = 0.2)

roots_linear <- MakeRelRootDens.adj(soilnodes = depths,
                                maxrootdepth = -1.4,
                                method = 'linear',
                                relrootden = 0.2)

df <- data.frame("depths" = depths, 
                 "roots_constant" = roots_constant, 
                 "roots_linear"  = roots_linear) %>% 
  mutate(diff = c(depths[1]*-100, diff(depths)*-100), 
         roots_linear_percm = roots_linear/diff, 
         roots_constant_percm = roots_constant/diff, 
         roots_constant_cumsum = cumsum(roots_constant), 
         roots_linear_cumsum = cumsum(roots_linear)) %>% 
  dplyr::select(-diff) %>% 
  pivot_longer(cols = roots_constant:roots_linear_cumsum)


ggplot(df, aes(x = depths, y = value))+
  coord_flip()+
  geom_line()+
  facet_wrap(facets = vars(name), 
             nrow = 2, 
             scales = "free_x")+
  ggtitle("proposed change: MakeRelRoots")

Problems with gobals

R CMD check Note:

MakeRelRootDens: no visible binding for global variable ‘rthick’
MakeRelRootDens: no visible binding for global variable ‘upper’
MakeRelRootDens: no visible binding for global variable ‘lower’
MakeRelRootDens: no visible binding for global variable ‘rootmass’
MakeRelRootDens: no visible binding for global variable ‘thick_ol’
MakeRelRootDens: no visible binding for global variable ‘i.upper’
MakeRelRootDens: no visible binding for global variable ‘i.lower’
MakeRelRootDens: no visible binding for global variable ‘i.rden’
MakeRelRootDens: no visible binding for global variable ‘i.rootmass’
msiterunLWFB90 : foreach_loop: no visible binding for global variable
  ‘i’
msiterunLWFB90 : foreach_loop: no visible binding for global variable
  ‘thisclim’
process_outputs: no visible binding for global variable ‘yr’
process_outputs: no visible binding for global variable ‘mo’
process_outputs: no visible binding for global variable ‘da’
process_outputs: no visible binding for global variable ‘doy’
process_outputs: no visible binding for global variable ‘nl’
process_outputs: no visible binding for global variable ‘rfal’
process_outputs: no visible binding for global variable ‘sfal’
process_outputs: no visible binding for global variable ‘flow’
process_outputs: no visible binding for global variable ‘evap’
process_outputs: no visible binding for global variable ‘seep’
process_outputs: no visible binding for global variable ‘snow’
process_outputs: no visible binding for global variable ‘swat’
process_outputs: no visible binding for global variable ‘gwat’
process_outputs: no visible binding for global variable ‘intr’
process_outputs: no visible binding for global variable ‘ints’
process_outputs: no visible binding for global variable ‘vrfln’
process_outputs: no visible binding for global variable ‘safrac’
process_outputs: no visible binding for global variable ‘stres’
process_outputs: no visible binding for global variable ‘adef’
process_outputs: no visible binding for global variable ‘awat’
process_outputs: no visible binding for global variable ‘relawat’
process_outputs: no visible binding for global variable ‘nits’
process_outputs: no visible binding for global variable ‘balerr’
Undefined global functions or variables:
  adef awat balerr da doy evap flow gwat i i.lower i.rden i.rootmass
  i.upper intr ints lower mo nits nl relawat rfal rootmass rthick
  safrac seep sfal snow stres swat thick_ol thisclim upper vrfln yr

Attention: switching off downslope flux

Hi Paul,

Im am adding a comment just to document a strange behavoir of underlying LWFBrook90.

Brook90 offers the possibility to turn off dowslope flow. The documentation states
“If either DSLOPE or LENGTH is 0, there is no DSFL, and the other parameter is ignored.”
http://www.ecoshift.net/brook/b90doc.html

I assumed this would also be the case with LWF-Brook90 (dslope and slopelen). However, on days where heavy rainfall occurs and slopelen is set to 0, flow, dsf, swati, theta, and wetnes become NaN, while the model continues to give (reasonable) results for other water balance parameters.

Setting slopelen to any value >0 while keeping dslope=0 avoids the problem.

Here is an example:

library(LWFBrook90R)
library(ggplot2)

data("slb1_meteo")


out<-lapply(c(0,0.001), function(xx){

  options_b90 <- set_optionsLWFB90()
  param_b90 <- set_paramLWFB90()
  param_b90$dslope<-0
  param_b90$slopelen<-xx

  soil <- cbind(slb1_soil, hydpar_wessolek_tab(texture = slb1_soil$texture))
  options_b90$startdate<-as.Date("2002-01-01")
  options_b90$enddate<-as.Date("2002-12-31")
  
  slb1_meteo<- slb1_meteo[slb1_meteo$dates>=  options_b90$startdate,]
  slb1_meteo<- slb1_meteo[slb1_meteo$dates<=  options_b90$enddate,]
  slb1_meteo[slb1_meteo$dates==as.Date("2002-07-15"),"prec" ]<-120

  b90res <- run_LWFB90(options_b90 = options_b90,
                       param_b90 = param_b90,
                       climate = slb1_meteo,
                       soil = soil)

  output <- set_outputLWFB90()
  output[,] <- 0 # reset output
  output["Swat"   ,"Mon"] <- 1 # set mon output

  results<-process_outputs_LWFB90(b90res,
                                  selection = output,
                                  prec_interval = options_b90$prec_interval)

    names(results) <- paste0("slopelen = ", xx)
    return(results)
  
})

This yiels:

[[1]]
[[1]]$`slopelen = 0`
       yr mo nl  swati theta wetnes  psimi
  1: 2002  1  1  3.300 0.344  0.851 -8.943
  2: 2002  1  2  6.605 0.344  0.851 -8.885
  3: 2002  1  3  6.613 0.344  0.853 -8.787
  4: 2002  1  4  9.933 0.345  0.854 -8.678
  5: 2002  1  5 13.270 0.346  0.855 -8.526
 ---                                      
248: 2002 12 17    NaN   NaN    NaN  0.000
249: 2002 12 18    NaN   NaN    NaN  0.000
250: 2002 12 19    NaN   NaN    NaN  0.000
251: 2002 12 20    NaN   NaN    NaN  0.000
252: 2002 12 21    NaN   NaN    NaN  0.000


[[2]]
[[2]]$`slopelen = 0.001`
       yr mo nl  swati theta wetnes  psimi
  1: 2002  1  1  3.300 0.344  0.851 -8.943
  2: 2002  1  2  6.605 0.344  0.851 -8.885
  3: 2002  1  3  6.613 0.344  0.853 -8.787
  4: 2002  1  4  9.933 0.345  0.854 -8.678
  5: 2002  1  5 13.270 0.346  0.855 -8.526
 ---                                      
248: 2002 12 17 45.483 0.367  0.916 -3.004
249: 2002 12 18 13.624 0.389  0.831 -2.100
250: 2002 12 19  7.397 0.370  0.883 -1.600
251: 2002 12 20  7.392 0.370  0.882 -1.610
252: 2002 12 21  7.388 0.369  0.882 -1.618

minor bug

calc_globrad( as.integer(format(dates, "%j")),

according to the help site of calc_globrad, date needs to be supported as "Date"-object. Works if changed to

  climate$globrad <- with(climate,
                          calc_globrad( dates, 
                                        sunhours, param_b90$coords_y ))

Parallel computation incl. progress bar w/o `doSNOW`

As discussed in person, something needs to be done to get away from the superseded packages snow and doSNOW while keeping the reporting of progress via a progress bar.

To get an overview about the state-of-the-art of parallel computing in R I tried to cast a wide net and read a lot. There are two current preprint papers giving an overview about parallel computing in R in general and the new future-package/approach, a couple of well written vignettes, a precisely fitting question stackoverflow question and source code of some packages using doParallel, doFuture and progressr.

Absolutely readable stuff about current parallel computing approaches in R

Conclusions

  • There shall be no dependency on doSNOW in new CRAN packages since it is superseded.
  • The ease of use of doSNOW's progress bar is yet unseen in other packages.
  • There is no known way to get a progress bar while using doParallel. Tried and failed.
  • pbapply is out of question because it would require a major rewrite of the parallel computing code.
  • future and progressr seem to be the future of parallel computing and progress reporting in R (see Eddelbuettel 2020 and Bengtsson 2020)
  • future and progressr call for a strict divide between developer/package side and user side of things. The package should lay the foundation (set up parallel tasks, signal progress) but shall not define the parallelization backend (cores, HPC cluster etc.) or cause the progress bar to be displayed.
  • Not many packages adhere to this design idea (only mlr3 comes to mind), most opt for ease of use and handle both sides within the package (e.g. rangeMapper, trundler)

Suggestion

  • switch from snow, doSNOW to future, doFuture, progressr
  • the foreach-part remains unchanged
  • since the raison-d'etre of msiterunLWFB90() and mrunLWFB90() is to make parallel processing of runLWFB90() easy for the user (after all, this could all be done outside of LWFBrook90R), I would currently opt for including the parallelization backend and progress bar in the package.
  • This could/should be handled differently later when future and progressr are better known in the community (cf. mlr3 for a good example)

Estimation of fine root density from soil physical parameters

There is a method to calculate relative rootdepth density per soil horizon, based on horizon depth, bulk density, and available water capacity. This was used in the NFIWADS-Database. Consider an implementaion for input generation.

Reference: https://
doi.org/10.3220/REP1473930232000
(Section 10.2.2., pp I-358)

Equation:
FWD = 11.63 - 0.084ut_cm + 3.22humus_klasse - 3.42TRD_gcm3 + 0.108hangneigung_grad +
0.095*nFK_pct

Fortran writes to stdout

R CMD check note:

File ‘LWFBrook90R/libs/LWFBrook90R.so’:
  Found ‘_gfortran_st_write’, possibly from ‘write’ (Fortran), ‘print’
    (Fortran)
    Object: ‘md_brook90.o’

Compiled code should not call entry points which might terminate R nor
write to stdout/stderr instead of to the console, nor use Fortran I/O
nor system RNGs.

See ‘Writing portable packages’ in the ‘Writing R Extensions’ manual.

potential adjustment of run_LWFB90

Hi,

The chk_errors()-function in L297 (as well as L408ff) of run_LWFB90() stops the code while throwing an error-message

chk_errors()

when, say, we're running thousands of points automatically with run_multisite_LWFB90, there will inevitably be points that create errors of some kind. In this case, it would be useful if stopping the whole modelling process through an error would be an optional choice. In a local version of the run_LWFB90-function I wrote a workaround like this:

    simout <- LWFBrook90R:::r_lwfbrook90(
      siteparam = data.frame(simyears[1],
                             as.integer(format(options_b90$startdate, "%j")),
                             param_b90$coords_y, param_b90$snowini, param_b90$gwatini,
                             options_b90$prec_interval),
      climveg = cbind(climate[, c("yr", "mo", "da","globrad","tmax","tmin",
                                  "vappres","windspeed","prec","mesfl")],
                      standprop_daily[, c("densef", "height", "lai", "sai", "age")]),
      precdat = precip,
      param = LWFBrook90R:::param_to_rlwfbrook90(param_b90, options_b90$imodel),
      pdur = param_b90$pdur,
      soil_materials = param_b90$soil_materials,
      soil_nodes = param_b90$soil_nodes[,c("layer","midpoint", "thick", "mat", "psiini", "rootden")],
      output_log = verbose,
      timelimit = timelimit)

    if (simout$error_code != 0L) {
      if (simout$error_code == 1L){outline <- "Simulation terminated abnormally: 'initial matrix psi > 0'"}
      if (simout$error_code == 2L){outline <- "Simulation initialazation failed: 'FWETK failed to determine wetness at KF'"}
      if (simout$error_code == 3L){outline <- "Simulation terminated abnormally: 'inconsistent dates in climate!'"}
      if (simout$error_code == 4L){outline <- "Simulation terminated abnormally: 'inconsistent dates in precipitation input!'"}
      if (simout$error_code == 5L){outline <- "Simulation terminated abnormally: 'wrong precipitation interval input!'"}
      if (simout$error_code == 6L){outline <- "Simulation terminated abnormally: 'negative soil water storage!'"}
      if (simout$error_code == 7L){outline <- "Simulation terminated abnormally: 'water storage exceeds water capacity!'"}

      write(paste0("Error in ID ", unique(soil$id_custom), ": ", outline),
               file = paste0(errorpath, "errors.txt"), append=TRUE)
      
      simres <- "error"
      
    }else{
      finishing_time <- Sys.time()
      ...

This way, all points get modelled and one can check which ones caused errors in the error-file. I know, this code-snippet is quite specific for my application (having an \code{id_custom} in the soil-dataframe plus \code{errorpath} in the additional arguments) but potentially the general optional-stopping-functionality might be useful to implement.

Dependencies

Hi @pschmidtwalter

I wonder to which extend we can limit the usage of data.table for this package. In general, we would like to avoid dependencies as much as possible before submitting to CRAN.

This is an idea, and can be rejected. But I would maybe think again about it

Unneeded `:::`

R CMD check Note:

There are ::: calls to the package's namespace in its code. A package
  almost never needs to use ::: for its own objects:
  ‘hydpar_forestfloor’ ‘wessolek_mvg_tab10’

Calculation of AWC using PTF-functions

I calculated the AWC from retention curves that were derived from the PTFs HYPRES, Wessolek and PTFPUH2. While the results of Wessolek and PTFPUH2 were similar, HYPRES led to twice as high AWCs. As I ran the same code on the MvG-parameters and the effect is reproducible in other areas, I assume there might be an issue with the HYPRES PTF.

Bug in run_multi_LWFB90 when calibrating frinsai and fsintsai in cases where sai input is a vector, not a single number

Here is an example:

library(LWFBrook90R)


data("slb1_meteo")
data("slb1_soil")

# Set up lists containing model control options and model parameters:
parms <- set_paramLWFB90()
# choose the 'Coupmodel' shape option for the annual lai dynamic,
# with fixed budburst and leaf fall dates:
opts <- set_optionsLWFB90(startdate = as.Date("2002-06-01"),  # MODEL 2 YEARS
                          enddate = as.Date("2003-06-30"),
                          lai_method = 'Coupmodel',
                          budburst_method = 'fixed',
                          leaffall_method = 'fixed')

# Derive soil hydraulic properties from soil physical properties using pedotransfer functions
soil <- cbind(slb1_soil, hydpar_wessolek_tab(slb1_soil$texture))

#set up data.frame with variable parameters
n <- 10
set.seed(2021)
vary_parms <- data.frame(shp_optdoy = runif(n,180,240),
                         shp_budburst = runif(n, 0.1,1),
                         winlaifrac = runif(n, 0,0.5),
                         budburstdoy = runif(n,100,150),
                         fsintsai = runif(n, 0.1, 0.8),                                             #  VARY THIS
                         frintsai = runif(n, 0.1, 0.8),                                             #  VARY THIS
                         soil_materials.ths3 = runif(n, 0.3,0.5), # ths of material 3
                         maxlai = runif(n,2,7))

# add the soil as soil_nodes and soil materials to param_b90, so ths3 can be looked up
parms[c("soil_nodes", "soil_materials")] <- soil_to_param(soil)

# INPUT A SAI FOR THE 2 YEARS
parms$sai<- rep(.5, 2) # 2 years

# Make a Multirun-Simulation

b90.multi <- run_multi_LWFB90(paramvar = vary_parms,
                              param_b90 = parms,
                              options_b90 = opts,
                              climate = slb1_meteo)

# HARVEST AN ERROR
b90.multi[1]

$RunNo.1
<simpleError in replace_vecelements(param_b90[[list_ind]], varnms = paramvar_nms[param_ll[[l]]],     vals = unlist(paramvar[i, unlist(param_ll[[l]])])): !anyNA(as.integer(gsub("[^[:digit:].]", "", varnms))) ist nicht TRUE>

The error occurs as pattern matching finds the string sai in the paramvar_nms parameters fsintsai and frintsai from line 80 onwards in mrunLWFB90.R if param_b90 contains a vector as sai input:

  is_ll <- lapply(param_b90, function(x) is.list(x) | length(x) >  1)
  param_ll <- sapply(names(param_b90[names(is_ll)[which(is_ll == TRUE)]]),
                     simplify = FALSE, FUN = grep, x = paramvar_nms)

In such cases an error is produced by replace_vecelements in line 133:

   param_b90[[list_ind]] <- replace_vecelements(param_b90[[list_ind]],
                                                         varnms = paramvar_nms[param_ll[[l]]],
                                                         vals = unlist(paramvar[i, unlist(param_ll[[l]])]))

A quick and dirty fix would be to insert param_ll<-param_ll[-grep("sai",names(param_ll))] at line 89

 param_ll <- sapply(names(param_b90[names(is_ll)[which(is_ll == TRUE)]]),
                     simplify = FALSE, FUN = grep, x = paramvar_nms)
  # Michas dirty fix
  param_ll<-param_ll[-grep("sai",names(param_ll))]

Error in Log.txt: FWETK: no convergence, stopping programm!

I try to run multiple sites in a forloop using the standard runLWFB90-function. I am changing the soil input files and the climate file for each site as well as slope and aspect. For some points the function gets stuck and the Log.txt returns the line (multiple times) "FWETK: no convergence, stopping programm!". What could be the reason for this error?

outdated documentation causes a check error and a warning

The old man page of MakeRelRootDens() causes one error and one warning when checking the package with R CMD check.

* checking examples ... ERROR
Running examples in ‘LWFBrook90R-Ex.R’ failed
The error most likely occurred in:

> base::assign(".ptime", proc.time(), pos = "CheckExEnv")
> ### Name: MakeRelRootDens
> ### Title: Generates a root density depth function for the soil layers'
> ###   lower depth limits
> ### Aliases: MakeRelRootDens
> 
> ### ** Examples
> 
> 
> depths <- slb1_soil$lower
> roots_beta <- MakeRelRootDens(soilnodes = depths,
+                               maxrootdepth = -1,4,
+                               beta = 0.97,
+                               method = "betamodel")
> 
> rootden.table <- data.frame(
+   depth = c(-0.02, -0.15, -0.35, -0.5, -0.65,-0.9,-1.1,-1.3,-1.6),
+   rootden = c(15, 35, 15, 7.5, 4, 12, 2, 2, 0))
> 
> roots_table <- MakeRelRootDens(soilnodes = depths,
+                                method = "table",
+                                relrootden = rootden.table$rootden,
+                                rootdepths = rootden.table$depth)
Error in MakeRelRootDens(soilnodes = depths, method = "table", relrootden = rootden.table$rootden,  : 
  unused arguments (relrootden = rootden.table$rootden, rootdepths = rootden.table$depth)
Execution halted
* checking for code/documentation mismatches ... WARNING
Codoc mismatches from documentation object 'MakeRelRootDens':
MakeRelRootDens
  Code: function(soilnodes, maxrootdepth = min(soilnodes), method =
                 "betamodel", beta = 0.97, rootdat = NULL)
  Docs: function(soilnodes, maxrootdepth = min(soilnodes), method =
                 "betamodel", beta = 0.97, relrootden = NULL,
                 rootdepths = NULL)
  Argument names in code not in docs:
    rootdat
  Argument names in docs not in code:
    relrootden rootdepths
  Mismatches in argument names:
    Position: 5 Code: rootdat Docs: relrootden

examples in the inst/examples folder

This is just a suggestions, but it is useful for CRAN add examples to the inst/examples with names of the files likefunctionName-help.R. Then in the function file one can link to those examples via #' @example inst/examples/functionName-help.R

R cmd check warnings

I ran a 'check _for_cran' and 'check_on_macos' on r-hub, with the following results:

macOS 10.11 El Capitan, R-release (experimental):

  • error: checking whether package ‘LWFBrook90R’ can be installed ... ERROR. Installation failed.

For the other environments, it basicly comes down to 1 WARNING:

  • checking compiled code: LWFBrook90R.so: Compiled code should not call entry points which might terminate R nor write to stdout/stderr instead of to the console, nor use Fortran I/O
    nor system RNGs. -> this is probably the missing error catching routine that we should implement
  • (fbrook_mod.mod unlikely filename for src files)

On Fedora and Ubuntu Linux I get another warning, because the vignette is not build:

  • Warning: Quitting from lines 92-94 (intro_lwfbrook90r.Rmd) Error: processing vignette 'intro_lwfbrook90r.Rmd' failed with diagnostics:
    object 'YR' not found
    I have no idea why this happens. The only reason I could think of is that there is an error in the simulation results, and the 'SWATDAY.ASC' object which should hold 'YR' doesnt exist. However, the examples with runLWFB90 are running!

CRAN LTO issue

CRAN checks gave the following issues with link time optimization
(https://www.stats.ox.ac.uk/pub/bdr/LTO/LWFBrook90R.out)

gfortran -fno-optimize-sibling-calls -fpic -g -O2 -mtune=native -Wall -pedantic -flto=10 -c md_brook90.f95 -o md_brook90.o
VARDCL.h:174:28:

174 | real(kind=8) :: Par(MPar,ML) ! hydraulic parameters for layer
| 1
Warning: Array ‘par’ at (1) is larger than limit set by ‘-fmax-stack-var-size=’, moved from stack to static storage. This makes the procedure unsafe when called recursively, or concurrently from multiple threads. Consider using ‘-frecursive’, or increase the ‘-fmax-stack-var-size=’ limit, or change the code to use an ALLOCATABLE array. [-Wsurprising]

skeleton.c:7:6: warning: type of ‘s_brook90_f_’ does not match original declaration [-Wlto-type-mismatch]
7 | void F77_NAME(s_brook90_f)(double *siteparam, double *climveg, double *param, double *pdur,
| ^
In line 9, pr is defined as int, but in 's_brook90_f_' pr is logical

md_brook90.f95:33:22: note: ‘s_brook90_f’ was previously declared here
33 | subroutine s_brook90_f( siteparam, climveg, param, pdur, soil_materials, soil_nodes, precdat, &
| ^
md_brook90.f95:33:22: note: code may be misoptimized unless ‘-fno-strict-aliasing’ is used

Not sure, do we also have to fix the notes, or only warnings?
I dont know also how to check if attempts to fix were successful.

macOS 10.14.6 compilation error

Hi,
Thanks for providing this package.
I'm trying to install it on macOS 10.14.6 and am getting a compilation error (see R output at the bottom).

The compiler gfortran was installed using brew install gcc and is located at
(Terminal outputs):

$ which gfortran
/usr/local/bin/gfortran

Although which indicates above path, upon inspection this seems to be a link to /usr/local/Cellar/gcc/9.2.0_1/bin/gfortran-9.

It seems that upon installing the R package it tries to use a gfortran compiler located in /usr/local/gfortran/lib/gcc/x86_64-apple-darwin15/6.1.0 (see R output below). Is this hardcoded or could this be modified in LWFBrook90? The R output also shows the -L flag and the path as a single string without space in between. Might this somehow be related to the error?

Thanks for helping out.
Best regards,
Fabian

> devtools::install_github(repo = "pschmidtwalter/LWFBrook90R", 
+                          dependencies = T, build_vignettes = T)
Downloading GitHub repo pschmidtwalter/LWFBrook90R@master
✔  checking for file ‘/private/var/folders/qq/vp0w5lnd5xbc2p_y48b0zl200024c1/T/RtmpSdv2El/remotes2bf853c59178/pschmidtwalter-LWFBrook90R-a629491/DESCRIPTION’ ...
─  preparing ‘LWFBrook90R’:
✔  checking DESCRIPTION meta-information ...
─  cleaning src
─  installing the package to build vignettes
         -----------------------------------
─  installing *source* package ‘LWFBrook90R’ ...
   ** using staged installation
   ** libs
   gfortran  -fPIC  -Wall -g -O2  -c  brook90.f95 -o brook90.o
   brook90.f95:99:0:
   
      99 |         HeatCapOld(i) = TPar(7,mat(i)) * TPar(1,mat(i)) + TPar(8,mat(i))
         | 
   Warning: 'tpar[_335]' may be used uninitialized in this function [-Wmaybe-uninitialized]
   brook90.f95:99:0: Warning: 'tpar[_338]' may be used uninitialized in this function [-Wmaybe-uninitialized]
   brook90.f95:99:0: Warning: 'tpar[_341]' may be used uninitialized in this function [-Wmaybe-uninitialized]
   clang -I"/Library/Frameworks/R.framework/Resources/include" -DNDEBUG   -isysroot /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk -I/usr/local/include  -fPIC  -Wall -g -O2  -c init.c -o init.o
   clang -dynamiclib -Wl,-headerpad_max_install_names -undefined dynamic_lookup -single_module -multiply_defined suppress -L/Library/Frameworks/R.framework/Resources/lib -L/usr/local/lib -o LWFBrook90R.so brook90.o init.o -L/usr/local/gfortran/lib/gcc/x86_64-apple-darwin15/6.1.0 -L/usr/local/gfortran/lib -lgfortran -lquadmath -lm -F/Library/Frameworks/R.framework/.. -framework R -Wl,-framework -Wl,CoreFoundation
   ld: warning: directory not found for option '-L/usr/local/gfortran/lib/gcc/x86_64-apple-darwin15/6.1.0'
   ld: warning: directory not found for option '-L/usr/local/gfortran/lib'
   ld: library not found for -lgfortran
   clang: error: linker command failed with exit code 1 (use -v to see invocation)
   make: *** [LWFBrook90R.so] Error 1
   ERROR: compilation failed for package ‘LWFBrook90R’
─  removing ‘/private/var/folders/qq/vp0w5lnd5xbc2p_y48b0zl200024c1/T/RtmpHQCpfz/Rinst4dc962b6eebd/LWFBrook90R’
         -----------------------------------
   ERROR: package installation failed
Error: Failed to install 'LWFBrook90R' from GitHub:
  System command error, exit status: 1, stdout + stderr (last 10 lines):
E> clang -dynamiclib -Wl,-headerpad_max_install_names -undefined dynamic_lookup -single_module -multiply_defined suppress -L/Library/Frameworks/R.framework/Resources/lib -L/usr/local/lib -o LWFBrook90R.so brook90.o init.o -L/usr/local/gfortran/lib/gcc/x86_64-apple-darwin15/6.1.0 -L/usr/local/gfortran/lib -lgfortran -lquadmath -lm -F/Library/Frameworks/R.framework/.. -framework R -Wl,-framework -Wl,CoreFoundation
E> ld: warning: directory not found for option '-L/usr/local/gfortran/lib/gcc/x86_64-apple-darwin15/6.1.0'
E> ld: warning: directory not found for option '-L/usr/local/gfortran/lib'
E> ld: library not found for -lgfortran
E> clang: error: linker command failed with exit code 1 (use -v to see invocation)
E> make: *** [LWFBrook90R.so] Error 1
E> ERROR: compilation failed for package ‘LWFBrook90R’
E> * removing ‘/private/var/folders/qq/vp0w5lnd5x

Lots of warnings caused by `mrunLWFB90()`

The example of the function mrunLWFB90() causes 50 warnings due to different length of vectors in selections within function plant.coupmodel() but the output to stdout (in this case the warnings) is concealed by foreach & doSNOW.

The warnings can be made visible by changing

cl <- snow::makeSOCKcluster(cores)
to cl <- snow::makeSOCKcluster(cores, outfile="") and adding options(warn=1) within foreach eg at

Change link to zenodo to all versions doi

Before resubmission change the link to zenodo to all versions doi. Otherwise the link in readme on cran will point to old version.

The all versions doi will always resolve to newest version in zenodo.

The all versions doi can be found in this packages zenodo page in the Versions Box.

Issue with interception parameters in setparam_LWFB90

Hello,
there's a bug in the default settings of setparam_LWFB90():

The interception parameter's defaults are described as

frintlai | Intercepted fraction of rain per unit LAI. Default: 0.06
frintsai | Intercepted fraction of rain per unit SAI. Default: 0.04
fsintlai | Intercepted fraction of snow per unit LAI. Default: 0.06
fsintsai | Intercepted fraction of snow per unit SAI. Default: 0.04

However, according to the Brook90 documentation (http://www.ecoshift.net/brook/b90doc.html - Parameters and variables): FRINTL & FRINTS are 0.06 and FSINTL and FSINTS are 0.04

"FRINTL and FRINTS (Fixed parameters) - intercepted fraction of rain per unit LAI and per unit SAI respectively, dimensionless. See also FSINTL. FRINTL and FRINTS are both fixed at 0.06.
...
FSINTL and FSINTS (Fixed parameters) - intercepted fraction of snow per unit LAI and per unit SAI respectively, dimensionless. See also FRINTL. FSINTL and FSINTS are both fixed at 0.04.

Best regards,
Raphael

function for data preparation

Hi @pschmidtwalter

I wonder if we can have a wrapper function to prepare the data as they shall be sent to the fortran (r_brook90). I know you did it separately before for parameters, climate, etc, but thought if we can have it as one piece, as you have it in your runLWFB90.

Please find attached a first sketch, if you think this would be useful. Basically this is your runLWFB90, except the working directory part removed, and instead of running the model it provide the list output.

r_brook90_input <- function( 
  options.b90,
  param.b90,
  climate,
  soil = NULL,
  outputmat = setoutput_LWFB90(),
  out.dir = "out/",
  verbose = TRUE
){
  
  #name-checks ----------------------------------------------------------------------
  names(options.b90) <- tolower(names(options.b90))
  names(param.b90) <- tolower(names(param.b90))
  names(climate) <- tolower(names(climate))
  
  options.b90$fornetrad <- match.arg(options.b90$fornetrad, choices = c("globrad","sunhour"))
  
  options.b90$budburst.method <- match.arg(options.b90$budburst.method,
    choices = c("fixed", "constant","Menzel","StdMeteo", "ETCCDI", "Ribes uva-crispa"))
  
  options.b90$leaffall.method <- match.arg(options.b90$leaffall.method,
    choices = c("fixed", "constant","vonWilpert", "LWF-BROOK90", "NuskeAlbert", "StdMeteo","ETCCDI"))
  
  options.b90$standprop.input <- match.arg(options.b90$standprop.input, choices = c("parameters", "table"))
  
  options.b90$lai.method <- match.arg(options.b90$lai.method, choices = c("b90", "linear", "Coupmodel"))
  
  options.b90$root.method <- match.arg(options.b90$root.method, choices = c("betamodel", "table", "linear", "constant", "soilvar"))
  
  # Check suggested packages --------------------------------------------------------
  if (!options.b90$budburst.method %in% c("constant", "fixed") || !options.b90$leaffall.method %in% c("constant", "fixed")) {
    if (!requireNamespace("vegperiod", quietly = TRUE)) {
      stop("In 'options.b90' you chose dynamic budburst or leaf fall, for which the
           package \"vegperiod\" is required. Please install it:
           install.packages('vegperiod')")
    }
  }
  
  # ---- Input checks ---------------------------------------------------------------
  
  if (!inherits(options.b90$startdate, "Date")) {
    stop("Invalid argument: 'options.b90$startdate'")}
  if (!inherits(options.b90$enddate, "Date")) {
    stop("Invalid argument: 'options.b90$enddate'")}
  if (!(options.b90$startdate < options.b90$enddate)) {
    stop("Invalid arguments: 'startdate > enddate ")}
  
  if ( is.null(soil) & (is.null(param.b90$soil_nodes) || is.null(param.b90$soil_material))){
    stop("Please provide soil data, either via the argument 'soil' or as list items 'soil_nodes' and 'soil_materials' in param.b90 ")
  }
  
  if (options.b90$root.method == "soilvar") {
    if (is.null(soil)) {
      stop("Please provide the 'soil'-argument when using options.b90$root.method = 'soilvar'.")
    } else {
      if (is.null(soil$rootden)) {
        stop("Please provide the column 'rootden' in 'soil'-data.frame")
      }
    }
  }
  
  if (!any( names(climate) == "mesfl") ){
    climate$mesfl <- 0
  }
  
  # ---- clean file paths and set up directories -----------------------------------------
  
  # output-directory: variable! But, in Param.in needs to be shorter than 80 characters!
  options.b90$out.dir <- normalizePath(out.dir, mustWork = FALSE)
  
  # if normalized output-name too long, and not inside 'project.dir' make out.dir within 'project.dir':
  if (nchar(options.b90$out.dir) > 80 &
      options.b90$out.dir != normalizePath(file.path(getwd(), basename(options.b90$out.dir)), mustWork = F) ) {
    warning(paste0("The specified output directory (",options.b90$out.dir,") is too long
                   and could not be read by the model. Find the results in ",basename(options.b90$out.dir),
      " within the project directory instead!"))
    options.b90$out.dir <- basename(options.b90$out.dir)
  }
  
  
  # ---- Simulation period ----------------------------------------------------------------
  climyears <- unique(year(climate$dates))
  simyears <- seq(from = as.integer(format(options.b90$startdate,"%Y")),
    to = as.integer(format(options.b90$enddate,"%Y")),
    by = 1)
  
  if (length(simyears[which(simyears %in% climyears)]) < length(simyears)) {
    if (length(simyears[which(simyears %in% climyears)]) == 0) {
      stop("Your climate data does not include the simulation period. Change startdate and enddate!")
    } else {
      warning("climate not covering simulation period completely, period was cut!")
    }
  }
  
  # Number of Simulation days
  param.b90$ndays <-  as.integer(difftime(options.b90$enddate,options.b90$startdate)) + 1
  
  
  # ---- Vegetation-Period  ----------------------------------------------------------
  # check length of fixed leaffall
  if (options.b90$leaffall.method %in% c("constant", "fixed")) {
    if (length(param.b90$leaffalldoy) > 1 & length(param.b90$leaffalldoy) != length(simyears)) {
      stop("When options.b90$leaffall.method == 'fixed', either provide a single value
           for param.b90$leaffall or a sequence of values, one for each year of the simulation period.")
    }
  }
  #check length of fixed budburst
  if (options.b90$budburst.method %in% c("constant", "fixed") ) {
    if (length(param.b90$budburstdoy) > 1 & length(param.b90$budburstdoy) != length(simyears)) {
      stop("When options.b90$budburst.method == 'fixed', either provide a single value
           for param.b90$budburstdoy or a sequence of values, one for each year of the simulation period.")
    }
  }
  
  # Extend budburst
  if (length(param.b90$budburstdoy) == 1) {
    param.b90$budburstdoy <- rep(param.b90$budburstdoy,times = length(simyears))
  }
  if (length(param.b90$leaffalldoy) == 1) {
    param.b90$leaffalldoy <- rep(param.b90$leaffalldoy,times = length(simyears))
  }
  
  # start and end dynamic
  if (!options.b90$budburst.method %in% c("constant", "fixed") &
      !options.b90$leaffall.method %in% c("constant", "fixed")) {
    budburst_leaffall <- with(climate, vegperiod::vegperiod(dates = dates,
      Tavg = tmean,
      start.method = as.character(options.b90$budburst.method),
      species = as.character(param.b90$budburst.species),
      end.method = as.character(options.b90$leaffall.method),
      est.prev = ifelse(length(climyears) <= 5, length(climyears) - 1, 5))
    )
    budburst_leaffall <- budburst_leaffall[which(budburst_leaffall$year %in% simyears),]
    param.b90$budburstdoy <- budburst_leaffall$start
    param.b90$leaffalldoy <- budburst_leaffall$end
  } else {
    # only budburst is dynamic:
    if (!options.b90$budburst.method %in% c("constant", "fixed") & options.b90$leaffall.method %in% c("constant", "fixed"))   {
      budburst_leaffall <- with(climate,
        vegperiod::vegperiod(dates = dates,
          Tavg = tmean,
          start.method = as.character(options.b90$budburst.method),
          species = as.character(param.b90$budburst.species),
          end.method = "StdMeteo",
          est.prev = ifelse(length(climyears) <= 5, length(climyears) - 1, 5))
      )
      param.b90$budburstdoy <- budburst_leaffall$start[which(budburst_leaffall$year %in% simyears)]
    } else {
      # only end dynamic
      if (options.b90$budburst.method %in% c("constant", "fixed") & !options.b90$leaffall.method %in% c("constant", "fixed"))   {
        budburst_leaffall <- with(climate,
          vegperiod::vegperiod(dates = dates,
            Tavg = tmean,
            start.method = "StdMeteo",
            end.method = options.b90$leaffall.method))
        options.b90$leaffalldoy <- budburst_leaffall$end[which(budburst_leaffall$year %in% simyears)]
      }
    }
  }
  
  
  # ---- Make Stand --------------------------------------------------------------------
  if (tolower(options.b90$standprop.input) == "table") {
    if (verbose == T) {message("Creating long term stand dynamics from table 'standprop.table'...")}
    
    if (is.null(param.b90$standprop.table) & tolower(options.b90$standprop.input) == "table") {
      stop("param.b90$standprop.table is missing. Required if options.b90$standprop.input = 'table'!")
    }
    
    if (!any(simyears %in% param.b90$standprop.table$year)) {
      stop("Simulation does not cover any of the years in param.b90$standprop.table")
    }
    
    # transfer table to parameters
    param.b90$height <- approx(x = as.Date(paste0(param.b90$standprop.table$year,"-12-31")),
      y = param.b90$standprop.table$height,
      xout = as.Date(c(paste0(simyears[1],"-01-01"),paste0(simyears,"-12-31"))),
      method = 'constant', rule = 2)$y
    param.b90$height.ini <- param.b90$height[1]
    param.b90$height <- param.b90$height[-1]
    
    param.b90$sai <- approx(x = as.Date(paste0(param.b90$standprop.table$year,"-12-31")),
      y = param.b90$standprop.table$sai,
      xout = as.Date(c(paste0(simyears[1],"-01-01"),paste0(simyears,"-12-31"))),
      method = 'constant', rule = 2)$y
    param.b90$sai.ini <- param.b90$sai[1]
    param.b90$sai <- param.b90$sai[-1]
    
    param.b90$densef <- approx(x = as.Date(paste0(param.b90$standprop.table$year,"-12-31")),
      y = param.b90$standprop.table$densef,
      xout = as.Date(c(paste0(simyears[1],"-01-01"),paste0(simyears,"-12-31"))),
      method = 'constant', rule = 2)$y
    
    param.b90$densef.ini <- param.b90$densef[1]
    param.b90$densef <- param.b90$densef[-1]
    
    param.b90$maxlai <- approx(x = as.Date(paste0(param.b90$standprop.table$year,"-01-01")),
      y = param.b90$standprop.table$maxlai,
      xout = as.Date(paste0(simyears,"-01-01")),
      method = 'constant', rule = 2)$y
    
    #extend or constrain age from table for simyears
    param.b90$age <- seq(param.b90$standprop.table$age[1] - (param.b90$standprop.table$year[1] - min(simyears)),
      by = 1, length.out = length(simyears))
    #recalculate age.ini
    param.b90$age.ini <- param.b90$age[1] - 1
    
  } else { if (verbose == T) {message("Creating constant stand properties from parameters...")}
    
    # derive age from age.ini for simyears
    param.b90$age <- seq(from = param.b90$age.ini+1,
      by = 1, length.out = length(simyears))
    
  }
  
  # interpolate yearly values to daily values
  standprop_daily <- data.table(
    dates = seq.Date(from = as.Date(paste0(min(simyears),"-01-01")),
      to = as.Date(paste0(max(simyears),"-12-31")),
      by = "day"),
    age = approx_standprop(x.years = simyears,
      y = param.b90$age,
      y.ini = param.b90$age.ini,
      use_growthperiod = options.b90$standprop.use_growthperiod,
      startdoy = param.b90$budburstdoy,
      enddoy = param.b90$leaffalldoy,
      approx.method = "linear"),
    height = approx_standprop(x.years = simyears,
      y = param.b90$height,
      y.ini = param.b90$height.ini,
      use_growthperiod = options.b90$standprop.use_growthperiod,
      startdoy = param.b90$budburstdoy,
      enddoy = param.b90$leaffalldoy,
      approx.method = options.b90$standprop.interp),
    sai = approx_standprop(x.years = simyears,
      y = param.b90$sai,
      y.ini = param.b90$sai.ini,
      use_growthperiod = options.b90$standprop.use_growthperiod,
      startdoy = param.b90$budburstdoy,
      enddoy = param.b90$leaffalldoy,
      approx.method = options.b90$standprop.interp),
    densef = approx_standprop(x.years = simyears,
      y = param.b90$densef,
      y.ini = param.b90$densef.ini,
      use_growthperiod = options.b90$standprop.use_growthperiod,
      startdoy = param.b90$budburstdoy,
      enddoy = param.b90$leaffalldoy,
      approx.method = options.b90$standprop.interp)
  )
  
  # daily leaf area index from parameters
  standprop_daily[, lai := MakeSeasLAI(simyears,
    method = options.b90$lai.method,
    maxlai = param.b90$maxlai,
    winlaifrac = param.b90$winlaifrac,
    budburst.doy = param.b90$budburstdoy,
    leaffall.doy = param.b90$leaffalldoy,
    emerge.dur = param.b90$emergedur,
    leaffall.dur = param.b90$leaffalldur,
    shape.budburst = param.b90$shape.budburst,
    shape.leaffall = param.b90$shape.leaffall,
    shape.optdoy = param.b90$shape.optdoy,
    lai.doy = param.b90$lai.doy,
    lai.frac = param.b90$lai.frac)]
  
  # constrain to simulation period
  standprop_daily <- standprop_daily[which(dates >= options.b90$startdate
    & dates <= options.b90$enddate),]
  
  if (verbose == T) {
    message("Standproperties created succesfully")
  }
  
  # ---- Prepare climate for input----------------------------------------------------
  
  # Precipitation corrrection (Richter)
  if (options.b90$richter.prec.corr==TRUE){
    warning("Richter correction of precipitation is not impemented yet. No correction is done!")
    #clim$Precip <- with(clim, RichterKorrPrec(dates=datum,tavg=tmean,precip=prec,exp=b90ini$richterexposition) )
  }
  
  if (options.b90$fornetrad == "sunhour") {
    climate[,globrad := CalcGlobRad( yday(dates), sunhours, param.b90$coords_y )]
  }
  
  #constrain to simulation period
  climate <- climate[which(dates >= options.b90$startdate & dates <= options.b90$enddate),]
  
  
  
  # ---- Make soilnodes & soil materials --------------------------------------------
  # soil provided as argument -> create soil nodes and materials and add them to param.b90
  if (!is.null(soil)) {
    soil_nodes_mat <- soil_to_param(soil)
    param.b90$soil_nodes <- soil_nodes_mat$soil_nodes
    param.b90$soil_materials <- soil_nodes_mat$soil_materials
  }
  
  param.b90$soil_nodes$thick <- param.b90$soil_nodes$upper - param.b90$soil_nodes$lower
  param.b90$soil_nodes$midpoint <- param.b90$soil_nodes$lower + param.b90$soil_nodes$thick/2
  param.b90$soil_nodes$thick <- param.b90$soil_nodes$thick * 1000 # mm
  param.b90$soil_nodes$layer <- 1:nrow(param.b90$soil_nodes)
  param.b90$soil_nodes$psiini <- param.b90$psiini
  
  # Make Roots ----------------------------------------------------------------------
  if (options.b90$root.method != "soilvar") {
    param.b90$soil_nodes$rootden <- MakeRelRootDens(soilnodes = param.b90$soil_nodes$lower,
      maxrootdepth = param.b90$maxrootdepth,
      method = options.b90$root.method,
      beta = param.b90$betaroot,
      relrootden = param.b90$rootden.tab$rootden,
      rootdepths = param.b90$rootden.tab$depth)
  } else {
    param.b90$soil_nodes$rootden <- soil$rootden
  }
  
  # ---- Input data ----------------------------------------------------------
  out_list <- list(
    site = data.frame(simyears[1],
      as.integer(format(options.b90$startdate, "%j")),
      param.b90$coords_y, param.b90$snowini, param.b90$gwatini,
      options.b90$prec.interval),
    
    climate = climate[, list(year(dates), month(dates), mday(dates),
      globrad,
      tmax,
      tmin,
      vappres,
      wind,
      prec,
      mesfl,
      standprop_daily$densef,
      standprop_daily$height,
      standprop_daily$lai,
      standprop_daily$sai,
      standprop_daily$age)],
    
    param = param_to_rbrook90(param.b90, options.b90$imodel),
    paramYear = param.b90$pdur,
    materials = param.b90$soil_materials,
    soil = param.b90$soil_nodes[,list(layer,midpoint, thick, mat, psiini, rootden)],
    output = outputmat
  )
  
  
  return(out_list)
}

R CMD check fails on single-core machines

Hi, while running revdep checks on parallelly and future emulating single-core machines, I noticed that your package fails its own package tests;

══ Failed tests ════════════════════════════════════════════════════════════════
── Error (test-msiterunLWFB90.R:10:3): basic runs using run_multisite_LWFB90 works ──
Error in `run_multisite_LWFB90(options_b90 = opts, param_b90 = parms, climate = slb1_meteo, 
    soil = list(soil1 = soil, soil2 = soil), output = -1, rtrn_output = FALSE, 
    rtrn_input = FALSE)`: Can not run on 2 cores! Only 1 available.
Backtrace:
    ▆
 1. ├─testthat::expect_type(...) at test-msiterunLWFB90.R:10:2
 2. │ └─testthat::quasi_label(enquo(object), arg = "object")
 3. │   └─rlang::eval_bare(expr, quo_get_env(quo))
 4. └─LWFBrook90R::run_multisite_LWFB90(...)
── Error (test-msiterunLWFB90.R:40:3): msiterun with climate_fun and multiple climate_args works ──
Error in `run_multisite_LWFB90(options_b90 = opts, param_b90 = parms, climate = climfun, 
    soil = soil, climate_args = varargs, rtrn_output = F, rtrn_input = F)`: Can not run on 2 cores! Only 1 available.
Backtrace:
    ▆
 1. ├─testthat::expect_type(...) at test-msiterunLWFB90.R:40:2
 2. │ └─testthat::quasi_label(enquo(object), arg = "object")
 3. │   └─rlang::eval_bare(expr, quo_get_env(quo))
 4. └─LWFBrook90R::run_multisite_LWFB90(...)

You can reproduce this yourself using:

$ R_PARALLELLY_AVAILABLECORES_SYSTEM=1 R CMD check LWFBrook90R_0.4.5.tar.gz

It looks like you should skip/run those tests conditioned on parallelly::availableCores() > 1.

Keep README.md and NEWS.md in the source bundle

README.md and NEWS.md will be displayed under the bullet point Materials: on the package's info page on CRAN if it is included in the source bundle. NEWS.md will even be available in the installed package on the users computer.

Thus, I would recommend to not put these files in .Rbuildignore and to rename NEWS to NEWS.md.

An README.Rmd should not be in the source bundle. But an README.Rmd is not needed anyway in this case since the README does not contain R-chunks.

Error in example

R CMD check Error:

Running examples in ‘LWFBrook90R-Ex.R’ failed
The error most likely occurred in:

> base::assign(".ptime", proc.time(), pos = "CheckExEnv")
> ### Name: soil_to_param
> ### Title: Split up soil into materials and soil nodes.
> ### Aliases: soil_to_param
> 
> ### ** Examples
> 
> soil <- slb1_soil
> soil <- cbind(soil, hydpar_wessolek_mvg(soil$texture))
Error in hydpar_wessolek_mvg(soil$texture) : 
  could not find function "hydpar_wessolek_mvg"
Calls: cbind -> cbind -> data.frame
Execution halted

Issue with MakeRoots.R

Hello,
I have the impression that there's something wrong with the MakeRoots.R script, or rather the MakeRelRootDens-function.

library(LWFBrook90R)
depth <-  slb1_soil$lower

roots_beta is the standard root density with the default values. (By the way, the first example on the MakeRelRootDens.R - help page has -1,4 instead of -1.4 as maxdepth.)

roots_beta <- MakeRelRootDens(soilnodes = depth,
                              maxrootdepth = -1.4,
                              beta = 0.97,
                              method = "betamodel")

Let's say I'm more interested in the upper layers where I have more measured data for example. So, I create more nodes in the top 15 cm.

depth.2 <- c(seq(-0.01, -0.15, -0.005), depth[6:22])
roots_beta.2 <- MakeRelRootDens(soilnodes = depth.2,
                                maxrootdepth = -1.4,
                                beta = 0.97,
                                method = "betamodel")

plot(roots_beta, depth, type = 's',
     col = "brown", lwd = 1.5,
     ylab = "soil depth [m]",xlab = "relative root density")
lines(roots_beta.2, depth.2, type = 's',
      col = "red", lwd = 1.5)

While they look different, the sum of the relative cumulative function has to be one and it is for both roots_beta and roots_beta.2 (sum(roots_beta); sum(roots_beta.2) are both 1), but there are some logical issues...

For example the cumulative sum in 16 cm depth is

cumsum(roots_beta)[which(depth == -0.16)] = 0.6660798, or 66%
cumsum(roots_beta.2)[which(depth.2 == -0.16)] = 0.9042575, over 90%

Or the depth at which 80% of roots are found would be
depth[which(cumsum(roots_beta) >0.80)[1]] = -0.28
depth.2[which(cumsum(roots_beta.2) >0.80)[1]] = -0.135, so 28 cm vs. 13.5 cm

Despite having a different amount of steps, the cumulative sums should lie on the same graph, right? The number of observational nodes shouldn't change the shape of the curves in relation to depth but they do.

plot(cumsum(roots_beta), depth, type = 's',
     col = "brown", lwd = 1.5,
     ylab = "soil depth [m]",xlab = "cumsum(relative root density)")
lines(cumsum(roots_beta.2), depth.2, type = 's',
      col = "red", lwd = 1.5)

According to the code, the theoretical background is Gale and Grigal (first entry here: https://tinyurl.com/yd3ggoss ), where the cumulative function Y is given by Y = 1-beta^d with d as depth in cm. But if I add this, it doesn't match either.

plot(cumsum(roots_beta), 100*depth, type = 's',
     col = "brown", lwd = 1.5,
     ylab = "soil depth [m]",xlab = "cumsum(relative root density)")
lines(cumsum(roots_beta.2), 100*depth.2, type = 's',
      col = "red", lwd = 1.5)
lines((1-0.97^(seq(0,210,1))),-seq(0,210,1), col = "blue") 

Of course, in the function's code creates values for the midpoints in between nodes, but this shape-differences are way too large to be explained by that. Am I missing something here or is there something wrong with the function?
Thanks a lot for your help and for the package in general!
Best regards,
Raphael

Severe bug when using sunshine duration as input

There is a bad issue with the function calc_globrad() that was discovered when running a longer simulation. In run_LWFB90(), calc_globrad() derives global radiation from sunshine duration hours when it is called with options_b90$fornetrad = 'sunhours'.

The bug was discovered from suspicious annual sums of potential transpiration (output$ptran):

image

Global radiation, as calculated from sunshine duration hour using calc_globrad() caused this weird course of potential transpiration, it seems an annual course of global radiation is somehow projected to entire timeseries:

image

I will fix this as soon as possible, and try to find out in what cases simulations are concerned.

Parameter documentation in mrunLWFB90

R CMD check Warning:

Documented arguments not in \usage in documentation object 'mrunLWFB90':
  ‘multirun.dir’ ‘keep.subdirs’

Functions with \usage entries need to have the appropriate \alias
entries, and all their arguments documented.
The \usage entries must correspond to syntactically valid R code.
See chapter ‘Writing R documentation files’ in the ‘Writing R
Extensions’ manual.

aggregation auf model outputs with "process_outputs_LWFB90"

Hi Paul and happy new year,

I was checking the results of the “process_outputs_LWFB90” function today and I found a problem when aggregating "budg" for “Ann” and "Mon” periods.

Here the aggregated tables still have 365 rows per year instead of 1 for annual or 12 for montly aggregation. I think the reason is that you aggregate values by groups (yr, mo ... ) using “sum” ( prec, flow, evap, seep) while using “rowid” to extract the last entry per group for other variables (e.g. snow, swat, gwat) in the same data.table call, where you intend to store the last value of the given period.

I suppose e.g. line 217 of your code (and all the similar lines) should read:

swat = round(last(swat),1),
or
swat = round(swat[.N],1),

instead of

swat = round(swat [rowid(yr, doy) == prec_interval],1),

to always get the last value per group.

Best regards,

Micha

some dataseta in data-raw are extremely large

Might be a good idea to save all datasets in data-raw as zipped csv or something else which is not as heavy.

Additionally it might also possible to use a subset of this datasets to save some MBs

`runLWFB90()` is very chatty

There is lots of text output to stdout / R console by runLWFB90() when called by msiterunLWFB90() or mrunLWFB90().

It might be a good idea to supress the messages or advise the user to shut up runLWFB90() with verbose=FALSE. Another option might be to have multiple verbosity levels: one with many messages if run on its own, one with few messages if run by msiterunLWFB90() or mrunLWFB90() and one without any messages.

vignettes DT complains

When trying to build a source package in R I'm getting an issue

Updating LWFBrook90R documentation
Warning: @examples [/Users/trotsiuv/Documents/work/papers/2019_Walter_brook90/analysis/LWFBrook90R/R/approx_standprop.R#32]: requires a value
Warning: @format [/Users/trotsiuv/Documents/work/papers/2019_Walter_brook90/analysis/LWFBrook90R/R/data.R#6]: mismatched braces or quotes
Warning: @examples [/Users/trotsiuv/Documents/work/papers/2019_Walter_brook90/analysis/LWFBrook90R/R/mrun_helper_combinations.R#15]: requires a value
Warning: @examples [/Users/trotsiuv/Documents/work/papers/2019_Walter_brook90/analysis/LWFBrook90R/R/soil_to_param.R#10]: requires a value
Writing NAMESPACE
Loading LWFBrook90R
Writing NAMESPACE
Updating vignettes
Documentation completed

==> devtools::build()

✔  checking for file ‘/Users/trotsiuv/Documents/work/papers/2019_Walter_brook90/analysis/LWFBrook90R/DESCRIPTION’ ...
─  preparing ‘LWFBrook90R’:
✔  checking DESCRIPTION meta-information ...
─  cleaning src
─  installing the package to build vignettes
E  creating vignettes (9s)
   Quitting from lines 111-112 (intro_lwfbrook90r.Rmd) 
   Error: processing vignette 'intro_lwfbrook90r.Rmd' failed with diagnostics:
   Check that is.data.table(DT) == TRUE. Otherwise, := and `:=`(...) are defined for use in j, once only and in particular ways. See help(":=").
   Execution halted
Error in (function (command = NULL, args = character(), error_on_status = TRUE,  : 
  System command error
Calls: <Anonymous> ... eval -> with_envvar -> force -> do.call -> <Anonymous>
Execution halted

Exited with status 1.


Typo in wessolek_mvg_tab10

Hi Paul ...

Typo in table "wessolek_mvg_tab10" used by "hydpar_wessolek_tab":

The texture class abbreviations "mSfS" (with capital "S" at the end) should be changed to"mSfs" in order to correspond to the nomenclature of the German soil mapping instructions. Otherwise NA is created when using soil data with standard texture class abbreviations and a coulple of below ground processes cannot be calculated.

Greetz!

date inheritance problem

I am facing this problem and do not know how to solve it.
Error in runLWFB90(options.b90 = options.b90, param.b90 = param.b90, climate = slb1_meteo, :
inherits(climate$dates, "Date") is not TRUE

Time return issue

@pschmidtwalter can you have a look at the lines 533-534.

simres$finishing_time <- Sys.time()

The issue is if I run the model with those parameters settings

 data.df <- runLWFB90(
    project.dir = ".",
    options.b90 = options.b90,
    param.b90 = param.b90,
    climate = slb1_meteo,
    soil = soil,
    outputmat = output,
    output.param.options = FALSE,
    run = TRUE,
    read.output = FALSE,
    output.log = FALSE, 
    verbose = FALSE)

I'm getting:

Warning message:
In simres$finishing_time <- Sys.time() : Coercing LHS to a list

This is anoying a bit

Issue with parallelisation in msiterunLWFB90

Hello,
I have the following question/request:

When running a large number of points parallelly with msiterunLWFB90, it sometimes happens that the soil parameters of certain points are set in a way that makes the individual LWFB90 process run for ages, in which case, the core is blocked. If this happens let's say 4 times on a 4-core cpu, the whole process basically stops.
In my case, the soil data is generated automatically for thousands of points so it is very hard to check, which points cause the issue, and that makes it tricky to change the soil data these points beforehand or to exclude them. The process bar just stops at some point without telling which points are blocking the cores.

Is it possible to include a maximum simulation duration (i.e. sim.max = 1000s), so that if that if sim.max is exceeded, the respective list element in "res" just becomes an error/warning message or NULL-dataframes, but the process continues?
This would immensely help to run a number of points that is too large to check the soil data of each point manually.

Define LAI as Input Value

I would like to be able to initially define LAI for each of the days I am going to analyze, in the same way that I defined SAI, densef, Age, height. However, the code I have found only gives me the option to calculate LAI with the available methods using make_seasLAI.
What function or variable input could I use to define specific LAI values for each of the days I will analyze?
Thanks in advance for your help.

CRAN doctype error

Dear maintainer,

You have file 'LWFBrook90R/man/LWFBrook90R.Rd' with \docType{package},
likely intended as a package overview help file, but without the
appropriate PKGNAME-package \alias as per "Documenting packages" in
R-exts.

This seems to be the consequence of the breaking change

Using @doctype package no longer automatically adds a -package alias.
Instead document _PACKAGE to get all the defaults for package
documentation.

in roxygen2 7.0.0 (2019-11-12) having gone unnoticed, see
r-lib/roxygen2#1491.

As explained in the issue, to get the desired PKGNAME-package \alias
back, you should either change to the new approach and document the new
special sentinel

"_PACKAGE"

or manually add

@Aliases LWFBrook90R-package

if remaining with the old approach.

Please fix in your master sources as appropriate, and submit a fixed
version of your package within the next few months.

Best,
-k

Faulty pattern of water absorption with deeper roots

I am modelling the water budget in 2021-2022 of a forest stand in southern Hesse. 2021 was rather "wet" while 2022 was a very dry year.

I have a 4m deep bottom profile and set the parameters:

maxrootdepth <- -4
betaroot<-0.99
root_method<- "linear"
psiini <- (-15) 

The rooting patterns extracted from model input looks like this:

rooting pattern

The resulting water budget and matric potentials:

bater_budget

It looks like the model allocates the roots mainly below 3m depth and practically hardly absorbs any water from the topsoil. Probably there is a sorting error in the transfer of the rooting patterns from preprocessing to the model.

Edit: it does not change the picture if I am setting bypar=0
Edit2: I am working with 15 min precipitation interval

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.