Nothing
#' Run an APSIM-X Simulation
#'
#' A valid apsimx file can be run from within R. The main goal is to make running APSIM-X
#' simple, especially for large scale simulations or parameter optimization
#'
#' @title Run an APSIM-X simulation
#' @name apsimx
#' @description Run apsimx from R. It uses \sQuote{system} (unix) or \sQuote{shell} (windows) and it attempts to be platform independent.
#' @param file file name to be run (the extension .apsimx is optional)
#' @param src.dir directory containing the .apsimx file to be run (defaults to the current directory)
#' @param silent whether to print messages from apsim simulation
#' @param value how much output to return: \cr
#' option \sQuote{report} returns only the \sQuote{main} report component; \cr
#' option \sQuote{all} returns all components of the simulation; \cr
#' option \sQuote{none} does not create a data.frame but it generates the databases; \cr
#' option \sQuote{user-defined} should be the name of a specific table
#' @param cleanup logical. Whether to delete the .db file generated by APSIM-X. Default is FALSE
#' @param simplify whether to return a single data frame when multiple reports are present. If FALSE it will return a list.
#' @param xargs extra arguments to be passed to the APSIM-X run. Use function xargs_apsimx.
#' @return a data frame with the \sQuote{Report} from the APSIM-X simulation. The return value depends on the argument \sQuote{value} above.
#' @export
#' @examples
#' \donttest{
#' ## See function 'apsimx_example' and vignette 'apsimx'
#' }
#'
apsimx <- function(file = "", src.dir = ".",
silent = FALSE,
value = "report",
cleanup = FALSE,
simplify = TRUE,
xargs){
if(file == "") stop("need to specify file name")
if(isFALSE(apsimx::apsimx.options$allow.path.spaces)){
.check_apsim_name(file)
.check_apsim_name(normalizePath(src.dir))
}
## The might offer suggestions in case there is a typo in 'file'
file.names <- dir(path = src.dir, pattern=".apsimx$", ignore.case = TRUE)
if(length(file.names) == 0){
stop("There are no .apsimx files in the specified directory to run.")
}
file <- match.arg(file, file.names)
file.name.path <- file.path(src.dir, file)
ada <- auto_detect_apsimx()
## Grabs the global variables
dotnet.flag <- apsimx::apsimx.options$dotnet
mono.flag <- apsimx::apsimx.options$mono
if(!missing(xargs)){
dotnet.flag <- xargs$dotnet
mono.flag <- xargs$mono
if(!is.na(xargs$exe.path))
ada <- xargs$exe.path
}
if(.Platform$OS.type == "unix"){
if(dotnet.flag){
run.strng <- paste("dotnet", ada, file.name.path)
}
if(mono.flag){
mono <- system("which mono", intern = TRUE)
run.strng <- paste(mono, ada, file.name.path)
}
if(isFALSE(dotnet.flag) && isFALSE(mono.flag)){
run.strng <- paste(ada, file.name.path)
}
## Run APSIM-X on the command line
if(!missing(xargs)) run.strng <- paste(run.strng, xargs$xargs.string)
if(apsimx::apsimx.options$allow.path.spaces){
## As written here, this will not work with dotnet or mono
## Just so I do not forget. The problem with spaces can be
## overcome for file paths but I do not think it can be
## solved for the program command name
run.strng <- paste0(ada,
" ",
shQuote(normalizePath(file.name.path)))
}
res <- system(command = run.strng, ignore.stdout = silent, intern = FALSE)
}
if(.Platform$OS.type == "windows"){
if(isFALSE(apsimx::apsimx.options$allow.path.spaces)){
run.strng <- paste0(ada, " ", file.name.path)
}else{
run.strng <- paste0(ada,
" ",
shQuote(normalizePath(file.name.path)))
}
if(!missing(xargs)) run.strng <- paste(run.strng, xargs$xargs.string)
shell(cmd = run.strng, translate = TRUE, intern = FALSE)
}
if(value != "none"){
ans <- read_apsimx(file = sub("apsimx$", "db", file),
src.dir = src.dir, value = value, simplify = simplify)
}else{
if(value == "none" && !silent){
cat("APSIM created .db files, but nothing is returned \n")
}
}
if(cleanup){
## Default is not to cleanup
if(value == "none") stop("do not clean up if you choose value = 'none' ")
## Delete the apsim-generated sql database
file.remove(paste0(src.dir, "/", sub("apsimx", "db", file)))
}
if(value != "none")
return(ans)
}
#' Extra arguments for running APSIM-X
#' @title Provide extra arguments for APSIM-X
#' @name xargs_apsimx
#' @description This provides additinoal command line arguments when running the model
#' @param verbose Write detailed messages to stdout when a simulation starts/finishes.
#' @param csv Export all reports to .csv files.
#' @param merge.db.files Merge multiple .db files into a single .db file.
#' @param list.simulations List simulation names without running them.
#' @param list.referenced.filenames List all files that are referenced by an .apsimx file(s).
#' @param single.threaded Run all simulations sequentially on a single thread.
#' @param cpu.count (Default: -1) Maximum number of threads/processes to spawn for running simulations.
#' @param simulation.names Only run simulations if their names match this regular expression.
#' @param dotnet Logical. There is a global option for this argument, but this will override it. This
#' can be useful if the goal is to compare an old version of Next Gen (before Sept 2021) with a more
#' recent version in the same script. This might be needed if you have your own compiled version of APSIM Next Gen.
#' @param mono Logical. Should be set to TRUE if running a version of APSIM Next Gen from Aug 2021 or older on Mac or Linux.
#' @param exe.path executable path. This can be useful for having both a global option through \sQuote{apsimx.options} and
#' a local option that will override that. This option will take precedence.
#' @return it returns a character vector with the extra arguments.
#' @export
xargs_apsimx <- function(verbose = FALSE, csv = FALSE, merge.db.files = FALSE, list.simulations = FALSE,
list.referenced.filenames = FALSE, single.threaded = FALSE, cpu.count = -1L,
simulation.names = FALSE, dotnet = FALSE, mono = FALSE,
exe.path = NA){
if(dotnet && mono)
stop("either dotnet or mono should be TRUE, but not both", call. = TRUE)
verbose <- ifelse(verbose, " --verbose", "")
csv <- ifelse(csv, " --csv", "")
merge.db.files <- ifelse(merge.db.files, " --merge-db-files", "")
list.simulations <- ifelse(list.simulations, " --list-simulations", "")
list.referenced.filenames <- ifelse(list.referenced.filenames, " --list-referenced-filenames", "")
single.threaded <- ifelse(single.threaded, " --single-threaded", "")
if(cpu.count == 0L) stop("cpu.count cannot be zero")
if(!is.integer(cpu.count)) stop("cpu.count should be an integer. For example, cpu.count=2L for two cores")
cpu.count <- ifelse(cpu.count > 0, paste0(" --cpu-count=", cpu.count), "")
simulation.names <- ifelse(simulation.names, " --simulation-names", "")
## I'm guessing single threaded is not compatible with cpu.count
xargs.string <- paste0(verbose, csv, merge.db.files, list.simulations, list.referenced.filenames, single.threaded, cpu.count, simulation.names)
## Remove beggining or trailing spaces
xargs.string <- gsub("^\\s+|\\s+$", "", xargs.string)
xargs.string
ans <- list(xargs.string = xargs.string, dotnet = dotnet, mono = mono, exe.path = exe.path)
}
## This is an internal function so I won't export/document it
#' @noRd
auto_detect_apsimx <- function(){
if(.Platform$OS.type == "unix"){
if(grepl("Darwin", Sys.info()[["sysname"]])){
laf <- list.files("/Applications/")
find.apsim <- grep("APSIM", laf)
## This deals with the fact that APSIM-X might not be present but perhaps a
## custom version is available
if(length(find.apsim) == 0){
## I only throw a warning because maybe the user has a custom version of APSIM-X only
if(!is.na(apsimx::apsimx.options$exe.path) && apsimx::apsimx.options$warn.find.apsimx){
warning("APSIM-X not found, but a custom one is present")
}else{
if(is.na(apsimx::apsimx.options$exe.path))
stop("APSIM-X not found and no 'exe.path' exists.")
}
}
## If only one version of APSIM-X is present
## APSIM executable
st1 <- "/Applications/"
st3 <- "/Contents/Resources/bin/Models.exe"
if(apsimx.options$dotnet) st3 <- gsub("exe$", "dll", st3)
if(isFALSE(apsimx.options$mono) && isFALSE(apsimx.options$dotnet))
st3 <- "/Contents/Resources/bin/Models"
if(length(find.apsim) == 1){
apsimx.name <- laf[find.apsim]
apsimx_dir <- paste0(st1, apsimx.name, st3)
## I could use 'file.path' instead, but it this is not a 'file'
## so it could be confusing
}
## If there is more than one version of APSIM-X
if(length(find.apsim) > 1){
## Originally I was sorting by #issue number but this
## does not give you the latest version
len.fa <- length(find.apsim)
## This extracts the date from the APSIM name but I
## only need this for debugging in case there is a problem
fa.dt <- as.numeric(sapply(laf[find.apsim], .favn))
newest.version.number <- max(fa.dt)
newest.version <- grep(newest.version.number, laf[find.apsim], value = TRUE)
if(apsimx::apsimx.options$warn.versions &&
is.na(apsimx::apsimx.options$exe.path)){
options.warn.versions <- getOption("apsimx.warn.versions")
if(is.null(options.warn.versions)){
warning(paste("Multiple versions of APSIM-X installed. \n
Choosing the newest one:", newest.version))
}else{
if(isTRUE(options.warn.versions)){
warning(paste("Multiple versions of APSIM-X installed. \n
Choosing the newest one:", newest.version))
}
}
}
apsimx.name <- newest.version
apsimx_dir <- paste0(st1, apsimx.name, st3)
}
}
if(grepl("Linux", Sys.info()[["sysname"]])){
apsimx.versions <- NULL
find.apsim <- grep("apsim", list.files("/usr/local/lib"))
## What if length equals zero?
if(length(find.apsim) == 0){
## I only throw a warning because maybe the user has a custom version of APSIM-X only
if(!is.na(apsimx::apsimx.options$exe.path) && apsimx::apsimx.options$warn.find.apsimx){
warning("APSIM-X not found, but a custom one is present")
}else{
if(is.na(apsimx::apsimx.options$exe.path))
stop("APSIM-X not found and no 'exe.path' exists.")
}
}
## APSIM executable
st1 <- "/usr/local/lib/apsim/"
st3 <- "/bin/Models.exe"
if(apsimx.options$dotnet) st3 <- gsub("exe$", "dll", st3)
if(isFALSE(apsimx.options$mono) && isFALSE(apsimx.options$dotnet))
st3 <- "/bin/Models"
if(length(find.apsim) == 1){
apsimx.versions <- list.files("/usr/local/lib/apsim")
apsimx.name <- apsimx.versions
apsimx_dir <- paste0(st1, apsimx.name, st3)
}
## Note: Apparently Debian does not tolerate multiple
## versions of APSIM-X installed, date 2019-12-12
if(length(apsimx.versions) > 1){
len.fa <- length(find.apsim)
newest.version <- apsimx.versions[find.apsim]
if(apsimx::apsimx.options$warn.versions){
options.warn.versions <- getOption("apsimx.warn.versions")
if(is.null(options.warn.versions)){
warning(paste("Multiple versions of APSIM-X installed. \n
Choosing the newest one:", newest.version))
}else{
if(isTRUE(options.warn.versions)){
warning(paste("Multiple versions of APSIM-X installed. \n
Choosing the newest one:", newest.version))
}
}
}
apsimx.name <- newest.version
apsimx_dir <- paste0(st1, apsimx.name, st3)
}
}
}
if(.Platform$OS.type == "windows"){
st1 <- "C:/PROGRA~1/"
laf <- list.files(st1)
find.apsim <- grep("APSIM", laf)
## What if length equals zero?
if(length(find.apsim) == 0 && is.na(apsimx::apsimx.options$exe.path)){
## Try using the registry only if APSIM path has not been set manually
if(apsimx::apsimx.options$warn.find.apsimx) warning("Searching the Windows registry for APSIM-X")
## HCR hive is for HKEY_CLASSES_ROOT, HLM is for HKEY_LOCAL_MACHINE and HCU is for HKEY_CURRENT_USER
regcmd <- try(utils::readRegistry("APSIMXFile\\shell\\open\\command", "HCR")[[1]], silent = TRUE)
if(inherits(regcmd, "try-error")) regcmd <- try(utils::readRegistry("APSIMXFile\\shell\\open\\command", "HCU")[[1]], silent = TRUE)
if(inherits(regcmd, "try-error")) regcmd <- try(utils::readRegistry("APSIMXFile\\shell\\open\\command", "HLM")[[1]], silent = TRUE)
if(inherits(regcmd, "try-error")) stop("Could not find APSIM-X in the Windows Registry")
regcmd2 <- gsub("\\\\", "/", strsplit(regcmd, "\"")[[1]][2])
apsimx_dir <- gsub("ApsimNG", "Models", regcmd2)
if(length(apsimx_dir) == 0) stop("APSIM-X not found and no 'exe.path' exists.")
if(grepl("\\s", apsimx_dir)) stop("Found a space in the path. Please provide the path manually to APSIM-X using exe.path in apsimx_options.")
return(apsimx_dir)
}
st3 <- "/bin/Models.exe"
if(apsimx.options$dotnet) st3 <- gsub("exe$", "dll", st3)
if(length(find.apsim) == 1){
apsimx.versions <- laf[find.apsim]
apsimx.name <- apsimx.versions
apsimx_dir <- paste0(st1, apsimx.name, st3)
}
if(length(find.apsim) > 1){
apsimx.versions <- laf[find.apsim]
newest.version <- apsimx.versions[length(find.apsim)]
if(apsimx::apsimx.options$warn.versions){
options.warn.versions <- getOption("apsimx.warn.versions")
if(is.null(options.warn.versions)){
warning(paste("Multiple versions of APSIM-X installed. \n
Choosing the newest one:", newest.version))
}else{
if(isTRUE(options.warn.versions)){
warning(paste("Multiple versions of APSIM-X installed. \n
Choosing the newest one:", newest.version))
}
}
}
apsimx.name <- newest.version
apsimx_dir <- paste0(st1, apsimx.name, st3)
}
}
if(!is.na(apsimx::apsimx.options$exe.path)){
## Windows paths might contain white spaces which are
## problematic when running them at the command line
if(grepl("\\s", apsimx::apsimx.options$exe.path))
stop("White spaces are not allowed in APSIM-X executable path")
apsimx_dir <- apsimx::apsimx.options$exe.path
}
return(apsimx_dir)
}
#' Auto detect where apsimx examples are located
#'
#' @title Auto detect where apsimx examples are located
#' @name auto_detect_apsimx_examples
#' @description simple function to detect where APSIM-X examples are located
#' @return will create a directory (character string) pointing to APSIM-X distributed examples
#' @export
#' @examples
#' \dontrun{
#' ex.dir <- auto_detect_apsimx_examples()
#' }
#'
auto_detect_apsimx_examples <- function(){
if(.Platform$OS.type == "unix"){
apsim.name <- NULL # Why did I create this NULL variable?
if(grepl("Darwin", Sys.info()[["sysname"]])){
## If APSIM-X is installed it will be in /Applications/
## look into Applications folder
laf <- list.files("/Applications/")
find.apsim <- grep("APSIM",laf)
if(length(find.apsim) == 0) stop("APSIM-X examples not found")
if(length(find.apsim) > 1){
fa.dt <- as.numeric(sapply(laf[find.apsim], .favn))
newest.version.number <- max(fa.dt)
newest.version <- grep(newest.version.number, laf[find.apsim], value = TRUE)
if(apsimx::apsimx.options$warn.versions && is.na(apsimx::apsimx.options$exe.path)){
warning(paste("Multiple versions of APSIM-X installed. \n
Choosing the newest one:", newest.version))
}
apsimx.name <- newest.version
}else{
apsimx.name <- laf[find.apsim]
}
st1 <- "/Applications/"
st3 <- "/Contents/Resources/Examples"
apsimx_ex_dir <- paste0(st1, apsimx.name, st3)
}
if(grepl("Linux", Sys.info()[["sysname"]])){
st1 <- "/usr/local/lib/apsim/"
apsimx.versions <- list.files(st1)
## In Ubuntu it looks like you can only have one version
## of APSIM-X installed
if(length(apsimx.versions) > 1){
len.fa <- length(find.apsim)
newest.version <- apsimx.versions[find.apsim]
if(apsimx.options$warn.versions && is.na(apsimx::apsimx.options$exe.path)){
warning(paste("Multiple versions of APSIM-X installed. \n
Choosing the newest one:", newest.version))
}
apsimx.name <- newest.version
}else{
apsimx.name <- apsimx.versions
}
apsimx_ex_dir <- paste0(st1, apsimx.name, "/Examples")
}
}
if(.Platform$OS.type == "windows"){
## Need to test this change
adax <- auto_detect_apsimx()
## APSIM path to examples
# st3 <- "/Examples"
# apsimx_ex_dir <- paste0(st1, "/", apsimx.name, st3)
apsimx_ex_dir <- gsub("bin/Models.*", "Examples", adax)
}
if(!is.na(apsimx::apsimx.options$examples.path)){
## I dislike white spaces in paths!
## I'm looking at you Windows!
if(grepl("\\s", apsimx::apsimx.options$examples.path))
stop("White spaces are not allowed in examples.path")
apsimx_ex_dir <- apsimx::apsimx.options$examples.path
}
return(apsimx_ex_dir)
}
#'
#' @title Access Example APSIM-X Simulations
#' @name apsimx_example
#' @description simple function to run some of the built-in APSIM-X examples
#' @param example run an example from built-in APSIM-X. Options are all of the ones included with the APSIM-X distribution, except \sQuote{Graph}.
#' @param silent whether to print standard output from the APSIM-X execution
#' @note This function creates a new column \sQuote{Date} which is in the R \sQuote{Date} format which is convenient for graphics.
#' @details This function creates a temporary copy of the example file distributed with APSIM-X to avoid writing a .db file
#' to the directory where the \sQuote{Examples} are located. It is not a good practice and there is no guarantee that
#' the user has read/write permissions in that directory.
#' @return It returns a data frame
#' @export
#' @examples
#' \dontrun{
#' wheat <- apsimx_example("Wheat")
#' maize <- apsimx_example("Maize")
#' barley <- apsimx_example("Barley")
#' ## The 'Date' column is created by this function, based on apsim output.
#' require(ggplot2)
#' ggplot(data = wheat , aes(x = Date, y = Yield)) +
#' geom_point()
#' }
#'
apsimx_example <- function(example = "Wheat", silent = FALSE){
## Write to a temp directory only
tmp.dir <- tempdir()
## Run a limited set of examples
## Now the only one missing is Graph, which I assume is about
## graphics and not that relevant to apsimx
## Several examples are not supported because they do not use
## relative paths for the weather file
## Examples which do not run: Chicory
ex.ch <- c("AgPasture", "Barley", "Canola", "Chickpea",
"ControlledEnvironment", "Eucalyptus",
"EucalyptusRotation",
"Maize", "Mungbean",
"Oats", "OilPalm", "Peanut", "Pinus", "Potato",
"RedClover",
"Rotation", "Slurp", "Sorghum",
"Soybean", "Sugarcane", "Wheat", "WhiteClover")
example <- match.arg(example, choices = ex.ch)
ex.dir <- auto_detect_apsimx_examples()
ex <- file.path(ex.dir, paste0(example, ".apsimx"))
if(!file.exists(ex)) stop(paste0("cannot find example file ", example))
## Make a temporary copy of the file to the current directory
## Do not transfer permissions?
file.copy(from = ex, to = tmp.dir, copy.mode = FALSE)
sim <- apsimx(paste0(example, ".apsimx"), src.dir = tmp.dir,
value = "report", simplify = FALSE)
## OS independent cleanup (risky?)
file.remove(paste0(tmp.dir, "/", example, ".db"))
file.remove(paste0(tmp.dir, "/", example, ".apsimx"))
return(sim)
}
#' Read APSIM-X generated .db files
#'
#' @title Read APSIM-X generated .db files
#' @name read_apsimx
#' @description read SQLite databases created by APSIM-X runs. One file at a time.
#' @param file file name
#' @param src.dir source directory where file is located
#' @param value either \sQuote{report}, \sQuote{all} (list) or user-defined for a specific report
#' @param simplify if TRUE will attempt to simplify multiple reports into a single data.frame.
#' If FALSE it will return a list.
#' @note if there is one single report it will return a data.frame.
#' If there are multiple reports, it will attempt to merge them into a data frame.
#' If not possible it will return a list with names corresponding to the
#' table report names. It is also possible to select a specific report from several
#' available by selecting \sQuote{value = ReportName}, where \sQuote{ReportName} is the name
#' of the specific report that should be returned.
#' If you select \sQuote{all} it will return all the components in the data base also as a list.
#' @return normally it returns a data frame, but it depends on the argument \sQuote{value} above
#' @seealso \code{\link{read_apsimx_all}}
#' @export
#'
read_apsimx <- function(file = "", src.dir = ".", value = "report", simplify = TRUE){
if(file == "") stop("need to specify file name")
file.names <- dir(path = src.dir, pattern=".db$", ignore.case=TRUE)
if(length(file.names) == 0){
stop("There are no .db files in the specified directory to read.")
}
file.name.path <- file.path(src.dir, file)
con <- DBI::dbConnect(RSQLite::SQLite(), file.name.path, flags = RSQLite::SQLITE_RO)
## create data frame for each table
## Find table names first
table.names <- RSQLite::dbListTables(con)
other.tables <- grep("^_", table.names, value = TRUE)
report.names <- setdiff(table.names, other.tables)
### Are there other potential tables starting with "_"?
## I guess I always expect to find a table, but not always... better to catch it here
if(length(report.names) < 1)
stop("No report tables found")
if(length(report.names) == 1L){
tbl0 <- DBI::dbGetQuery(con, paste("SELECT * FROM ", report.names))
if(nrow(tbl0) == 0)
warning("Report table has no data")
if(any(grepl("Clock.Today", names(tbl0)))){
if(nrow(tbl0) > 0){
tbl0$Date <- try(as.Date(sapply(tbl0$Clock.Today, function(x) strsplit(x, " ")[[1]][1])), silent = TRUE)
}
}
## Maybe include SimulationNames column here?
if(all(!grepl("SimulationName", names(tbl0)))){
tbl0$SimulationName <- NA
stn <- grep("Simulation", other.tables, value = TRUE) ## stn stands for simulation table name
SimulationNamesTable <- DBI::dbGetQuery(con, paste("SELECT * FROM ", stn))
for(j in seq_along(SimulationNamesTable$ID)){
tbl0[tbl0$SimulationID == j, "SimulationName"] <- SimulationNamesTable$Name[j]
}
}
}
if(length(report.names) > 1L && value %in% c("report", "all")){
if(simplify){
lst0 <- NULL
for(i in seq_along(report.names)){
tbl0 <- DBI::dbGetQuery(con, paste("SELECT * FROM ", report.names[i]))
if(nrow(tbl0) == 0)
warning(paste("Report", report.names[i]), "has no data")
if(any(grepl("Clock.Today", names(tbl0)))){
if(nrow(tbl0) > 0){
tbl0$Date <- try(as.Date(sapply(tbl0$Clock.Today, function(x) strsplit(x, " ")[[1]][1])), silent = TRUE)
}
}
## Maybe include SimulationNames column here?
if(all(!grepl("SimulationName", names(tbl0)))){
tbl0$SimulationName <- NA
stn <- grep("Simulation", other.tables, value = TRUE) ## stn stands for simulation table name
SimulationNamesTable <- DBI::dbGetQuery(con, paste("SELECT * FROM ", stn))
for(j in seq_along(SimulationNamesTable$ID)){
tbl0[tbl0$SimulationID == j, "SimulationName"] <- SimulationNamesTable$Name[j]
}
}
dat0 <- data.frame(report = report.names[i], tbl0)
lst0 <- try(rbind(lst0, dat0), silent = TRUE)
if(inherits(lst0, "try-error")){
stop("Could not simplify reports into a single data.frame \n
Choose simplify = FALSE or modify your reports.")
}
}
}else{
lst0 <- vector("list", length = length(report.names))
for(i in seq_along(report.names)){
tbl0 <- DBI::dbGetQuery(con, paste("SELECT * FROM ", report.names[i]))
if(any(grepl("Clock.Today", names(tbl0)))){
tbl0$Date <- try(as.Date(sapply(tbl0$Clock.Today, function(x) strsplit(x, " ")[[1]][1])), silent = TRUE)
}
## Maybe include SimulationNames column here?
if(all(!grepl("SimulationName", names(tbl0)))){
tbl0$SimulationName <- NA
stn <- grep("Simulation", other.tables, value = TRUE) ## stn stands for simulation table name
SimulationNamesTable <- DBI::dbGetQuery(con, paste("SELECT * FROM ", stn))
for(j in seq_along(SimulationNamesTable$ID)){
tbl0[tbl0$SimulationID == j, "SimulationName"] <- SimulationNamesTable$Name[j]
}
}
lst0[[i]] <- tbl0
}
names(lst0) <- report.names ## Name the lists with report names
}
}
if(!value %in% c("report", "all") && length(report.names) > 1L){
if(!value %in% report.names){
cat("Available table names: ", report.names ,"\n")
stop("user defined report name is not in the list of available tables",
call. = FALSE)
}
tbl0 <- DBI::dbGetQuery(con, paste("SELECT * FROM ", value))
if(any(grepl("Clock.Today", names(tbl0)))){
tbl0$Date <- try(as.Date(sapply(tbl0$Clock.Today, function(x) strsplit(x, " ")[[1]][1])), silent = TRUE)
}
## Maybe include SimulationNames column here?
if(all(!grepl("SimulationName", names(tbl0)))){
tbl0$SimulationName <- NA
stn <- grep("Simulation", other.tables, value = TRUE) ## stn stands for simulation table name
SimulationNamesTable <- DBI::dbGetQuery(con, paste("SELECT * FROM ", stn))
for(j in seq_along(SimulationNamesTable$ID)){
tbl0[tbl0$SimulationID == j, "SimulationName"] <- SimulationNamesTable$Name[j]
}
}
}
if(value == "all"){
other.tables.list <- vector("list", length = length(other.tables))
for(i in seq_along(other.tables)){
other.tables.list[[i]] <- DBI::dbGetQuery(con, paste("SELECT * FROM ", other.tables[i]))
}
names(other.tables.list) <- gsub("_", "", other.tables, fixed = TRUE)
}
## Disconnect
DBI::dbDisconnect(con)
## Return a list if there is only one report, whatever the name and value == "all"
if(value == "all" && length(report.names) == 1L){
lst1 <- list(Report = tbl0)
ans <- do.call(c, list(lst1, other.tables.list))
}
if(value == "all" && length(report.names) > 1L){
ans <- do.call(c, list(lst0, other.tables.list))
}
if(value == "report" && length(report.names) > 1L){
ans <- lst0
}
## Return data.frame if report and length 1 or user defined
if((value == "report" && length(report.names) == 1L) || (!value %in% c("report", "all"))){
ans <- tbl0
}
return(ans)
}
#' Read all APSIM-X generated .db files in a directory
#'
#' @title Read all APSIM-X generated .db files in a directory
#' @name read_apsimx_all
#' @description Like \code{\link{read_apsimx}}, but it reads all .db files in a directory.
#' @param src.dir source directory where files are located
#' @param value either \sQuote{report} or \sQuote{all} (only \sQuote{report} implemented at the moment)
#' @note Warning: very simple function at the moment, not optimized for memory or speed.
#' @return it returns a data frame or a list if \sQuote{value} equals \sQuote{all}.
#' @export
#'
read_apsimx_all <- function(src.dir = ".", value = "report"){
## This is super memorey hungry and not efficient at all, but it might work
## for now
file.names <- dir(path = src.dir, pattern=".db$", ignore.case=TRUE)
ans <- NULL
for(i in file.names){
tmp <- read_apsimx(file.names[i], value = value)
tmp.d <- data.frame(file.name = file.names[i], tmp)
ans <- rbind(ans, tmp)
}
return(ans)
}
#### check_apsimx ----
#' @title Partial checking of an apsimx file for possible issues.
#' @name check_apsimx
#' @param file file ending in .apsimx to be edited (JSON)
#' @param src.dir directory containing the .apsimx file to be checked; defaults to the current working directory
#' @param node either \sQuote{all}, \sQuote{Clock}, \sQuote{Weather}, \sQuote{Soil}
#' @param soil.child specific soil component to be checked.
#' @param check.apsim.met whether to check the \sQuote{met} file. Default is FALSE.
#' @param root supply the node position in the case of multiple simulations such as factorials.
#' @param verbose whether to print information
#' @return It does not return an object, but it prints messages useful for diagnosing issues.
#' @export
#' @examples
#' \donttest{
#'
#' ## Check file distributed with the package
#' extd.dir <- system.file("extdata", package = "apsimx")
#'
#' check_apsimx("Wheat.apsimx", src.dir = extd.dir)
#' ## This throws warnings but it should not produce errors
#' }
check_apsimx <- function(file = "", src.dir = ".",
node = c("all", "Clock", "Weather", "Soil"),
soil.child = c("all", "Physical", "InitialWater", "SoilWater", "Solute", "Organic"),
check.apsim.met = FALSE,
root = NULL,
verbose = TRUE){
if(file == "") stop("need to specify file name")
if(isFALSE(apsimx::apsimx.options$allow.path.spaces)){
.check_apsim_name(file)
.check_apsim_name(normalizePath(src.dir))
}
## The might offer suggestions in case there is a typo in 'file'
file.names <- dir(path = src.dir, pattern = ".apsimx$", ignore.case = TRUE)
if(length(file.names) == 0){
stop("There are no .apsimx files in the specified directory to run.")
}
file <- match.arg(file, file.names)
node <- match.arg(node)
soil.child <- match.arg(soil.child)
### Checking the Clock
if(node %in% c("all", "Clock")){
if(verbose) cat("Checking the Clock \n")
dates <- try(extract_data_apsimx(file = file, src.dir = src.dir,
node = "Clock", root = root), silent = TRUE)
if(inherits(dates, 'try-error') || nrow(dates) == 0){
warning("Clock not found", immediate. = TRUE)
}else{
try.start <- try(as.Date(dates[[1]], tryFormats = c("%Y-%m-%dT%H:%M:%S")), silent = TRUE)
if(inherits(try.start, "try-error"))
warning(names(dates)[1], " : ", try.start, immediate. = TRUE)
try.end <- try(as.Date(dates[[2]], tryFormats = c("%Y-%m-%dT%H:%M:%S")), silent = TRUE)
if(inherits(try.end, "try-error"))
warning(names(dates)[2], " : ", try.end, immediate. = TRUE)
}
}
### Checking the Weather
if(node %in% c("all", "Weather")){
if(verbose) cat("Checking the Weather (met) \n")
met.name <- try(extract_data_apsimx(file = file, src.dir = src.dir,
node = "Weather",
root = root), silent = TRUE)
if(inherits(met.name, 'try-error')){
message("Weather (met) file path: ", met.name)
warning("Weather (met) not found", immediate. = TRUE)
}else{
if(grepl("%root%", met.name)){
## This means that the weather file is from an example
ex.dir <- auto_detect_apsimx_examples()
mfn0 <- strsplit(met.name[[1]], "[\\/]", fixed = FALSE)[[1]]
mfn0.length <- length(mfn0)
mfn <- mfn0[mfn0.length]
mwf <- mfn0[mfn0.length - 1]
mfe <- file.exists(file.path(ex.dir, mwf, mfn))
}else{
mfe <- file.exists(met.name[[1]])
}
if(!mfe){
message("Weather (met) file path: ", met.name)
warning("Could not find 'met' file", immediate. = TRUE)
}else{
if(check.apsim.met){
### Annoying, but I need to split the name
mfn <- basename(normalizePath(met.name[[1]])) ## met file name
mfsd <- dirname(normalizePath(met.name[[1]]))
met <- read_apsim_met(file = mfn, src.dir = mfsd)
check_apsim_met(met)
}
}
}
}
if(node %in% c("all", "Soil")){
if(soil.child %in% c("all", "Physical")){
if(verbose) cat("Checking the soil physical properties \n")
### First: extract soil physical
soil.physical <- extract_data_apsimx(file = file, src.dir = src.dir,
node = "Soil",
soil.child = "Physical",
root = root)
### Check the layers
soil.physical.layers <- soil.physical[[1]]
if(verbose) cat("Soil has: ", nrow(soil.physical.layers), "layers \n")
for(i in seq_len(nrow(soil.physical.layers))){
if(verbose) cat("Checking Physical layer:", i, "\n")
### Checking Thickness
thickness.value <- as.numeric(soil.physical.layers[["Thickness"]][i])
if(thickness.value <= 0)
message("Thickness for layer: ", i, " is less than or equal to zero. Value: ", thickness.value)
### Checking Bulk Density
bd.value <- as.numeric(soil.physical.layers[["BD"]][i])
if(bd.value <= 0)
message("Bulk Density for layer: ", i, " is less than or equal to zero. Value: ", bd.value)
if(bd.value > 3)
message("Bulk Density for layer: ", i, " is greater than 3. Value: ", bd.value)
### Checking AirDry
AirDry.value <- as.numeric(soil.physical.layers[["AirDry"]][i])
if(AirDry.value <= 0)
message("AirDry for layer: ", i, " is less than or equal to zero. Value: ", AirDry.value)
if(AirDry.value > 1)
message("AirDry for layer: ", i, " is greater than 1. Value: ", AirDry.value)
### Checking LL15
LL15.value <- as.numeric(soil.physical.layers[["LL15"]][i])
if(LL15.value <= 0)
message("LL15 for layer: ", i, " is less than or equal to zero. Value: ", LL15.value)
if(LL15.value > 1)
message("LL15 for layer: ", i, " is greater than 1. Value: ", LL15.value)
if(AirDry.value > LL15.value) ## AirDry value should be lower than the LL15 value, always right?
message("The AirDry value is greater than the LL15 value for layer: ", i, ". AirDry value: ", AirDry.value, ". LL15 value:", LL15.value)
### Checking DUL
DUL.value <- as.numeric(soil.physical.layers[["DUL"]][i])
if(DUL.value <= 0)
message("DUL for layer: ", i, " is less than or equal to zero. Value: ", DUL.value)
if(DUL.value > 1)
message("DUL for layer: ", i, " is greater than 1. Value: ", DUL.value)
if(DUL.value < LL15.value) ## DUL value should be greater than the LL15 value, always right?
message("The DUL value is lower than the LL15 value for layer: ", i, ". DUL value: ", DUL.value, ". LL15 value:", LL15.value)
### Checking SAT
SAT.value <- as.numeric(soil.physical.layers[["SAT"]][i])
if(SAT.value <= 0)
message("SAT for layer: ", i, " is less than or equal to zero. Value: ", SAT.value)
if(SAT.value > 1)
message("SAT for layer: ", i, " is greater than 1. Value: ", SAT.value)
if(SAT.value < DUL.value) ## SAT value should be greater than the DUL value, always right?
message("The SAT value is lower than the DUL value for layer: ", i, ". SAT value: ", SAT.value, ". DUL value:", DUL.value)
}
}
if(soil.child %in% c("all", "InitialWater")){
if(verbose) cat("Checking the soil initial water \n")
### First: extract soil InitialWater
soil.initialwater <- extract_data_apsimx(file = file, src.dir = src.dir,
node = "Soil",
soil.child = "InitialWater",
root = root)
soil.initialwater.initialvalues <- soil.initialwater$second
if(is.null(soil.initialwater.initialvalues)){
warning("'soil.initialwater.initialvalues' is null", immediate. = TRUE)
}else{
## Check number of layers
if(soil.child %in% "all"){
## Are the number of layers the same as for 'Physical'?
soil.physical.layers.length <- nrow(soil.physical.layers)
soil.initialwater.initialvalues.length <- nrow(soil.initialwater.initialvalues)
if(soil.physical.layers.length != soil.initialwater.initialvalues.length){
cat("Number of layers in soil physical:", soil.physical.layers.length, "\n")
cat("Number of layers in initial water values:", soil.initialwater.initialvalues.length, "\n")
message("Number of layers in physical does match the ones in InitialWater - InitialValues")
}
## Checking that InitialWater is between LL15 and SAT (It can be greater than DUL?)
if(soil.physical.layers.length == soil.initialwater.initialvalues.length){
for(i in seq_len(soil.initialwater.initialvalues.length)){
soil.initialwater.layer.thickness <- soil.initialwater.initialvalues$Thickness[i]
soil.physical.layer.thickness <- soil.physical.layers$Thickness[i]
if(soil.physical.layer.thickness != soil.initialwater.layer.thickness){
cat("Soil physical Thickness for layer:", i, " is ", soil.physical.layer.thickness, "\n")
cat("Soil InitialWater Thickness:", soil.initialwater.layer.thickness, "\n")
message("Soil Physical Thickness does not match InitialWater")
}
soil.initialwater.layer.values <- soil.initialwater.initialvalues$InitialValues[i]
soil.physical.layers.LL15 <- soil.physical.layers$LL15[i]
soil.physical.layers.SAT <- soil.physical.layers$SAT[i]
if(soil.initialwater.layer.values < soil.physical.layers.LL15){
message("Soil InitialWater InitialValues for layer ", i, " is lower than LL15")
}
if(soil.initialwater.layer.values > soil.physical.layers.SAT){
message("Soil InitialWater InitialValues for layer ", i, " is greater than SAT")
}
}
}
}
}
}
### Checking SoilWater
if(soil.child %in% c("all", "SoilWater")){
if(verbose) cat("Checking the soil water \n")
### First: extract soil InitialWater
soil.soilwater <- extract_data_apsimx(file = file, src.dir = src.dir,
node = "Soil",
soil.child = "SoilWater",
root = root)
soil.soilwater.second <- soil.soilwater$second
if(is.null(soil.soilwater.second))
warning("'soil.soilwater.second' is null", immediate. = TRUE)
## Check number of layers
if(soil.child %in% "all"){
## Are the number of layers the same as for 'Physical'?
soil.soilwater.second.length <- nrow(soil.soilwater.second)
for(i in seq_len(soil.soilwater.second.length)){
### Checking that Thickness is grater than zero
if(verbose) cat("Checking SoilWater layer:", i, "\n")
if(soil.soilwater.second$Thickness[i] < 0)
message("SoilWater Thickness for layer: ", i, " is less than zero")
if(soil.soilwater.second$SWCON[i] < 0)
message("SoilWater SWCON for layer: ", i, " is less than zero")
if(soil.soilwater.second$SWCON[i] > 1)
message("SoilWater SWCON for layer: ", i, " is greater than one")
}
}
}
if(soil.child %in% c("all", "Solute")){
if(verbose) cat("Checking the soil Solutes \n")
### First: extract soil Solutes
soil.solutes <- try(extract_data_apsimx(file = file, src.dir = src.dir,
node = "Soil",
soil.child = "Solute",
root = root), silent = TRUE)
if(inherits(soil.solutes, 'try-error')){
warning("Soil 'Solutes' not found", immediate. = TRUE)
}else{
if(verbose) cat("Solutes: ", names(soil.solutes), "\n")
for(i in seq_along(soil.solutes)){
soil.solute.initialvalues <- soil.solutes[[i]][[1]]
soil.solute.initialvalues.length <- nrow(soil.solute.initialvalues)
if(soil.physical.layers.length != soil.solute.initialvalues.length){
cat("Number of layers in soil physical:", soil.physical.layers.length, "\n")
cat("Number of layers in solute values:", soil.solute.initialvalues.length, "\n")
message("Number of layers in physical does match the ones in Solute - InitialValues")
}
for(j in seq_len(soil.solute.initialvalues.length)){
soil.solute.layer.thickness <- soil.solute.initialvalues$Thickness[i]
soil.physical.layer.thickness <- soil.physical.layers$Thickness[i]
if(soil.physical.layer.thickness != soil.solute.layer.thickness){
cat("Soil physical Thickness for layer:", i, " is ", soil.physical.layer.thickness, "\n")
cat("Soil solute Thickness:", soil.solute.layer.thickness, "\n")
message("Soil Physical Thickness does not match Solute")
}
soil.solute.layer.values <- soil.solute.initialvalues$InitialValues[i]
if(!is.numeric(soil.solute.layer.values)){
warning("Soil Solute InitialValues for layer ", i, " is not numeric", immediate. = TRUE)
}
if(soil.solute.layer.values < 0){
warning("Soil Solute InitialValues for layer ", i, " is less than zero", immediate. = TRUE)
}
}
}
}
}
if(soil.child %in% c("all", "Organic")){
if(verbose) cat("Checking the soil Organic \n")
### First: extract soil Organic
soil.organic <- extract_data_apsimx(file = file, src.dir = src.dir,
node = "Soil",
soil.child = "Organic",
root = root)
soil.organic.layers <- soil.organic[[2]]
### Only checking the second component for now
if(verbose) cat("Soil Organic has: ", nrow(soil.organic.layers), "layers \n")
for(i in seq_len(nrow(soil.organic.layers))){
if(verbose) cat("Checking Organic layer:", i, "\n")
thickness.value <- as.numeric(soil.organic.layers[["Thickness"]][i])
if(thickness.value <= 0)
message("Thickness for layer: ", i, " is less than or equal to zero. Value: ", thickness.value)
### Checking Carbon
carbon.value <- as.numeric(soil.organic.layers[["Carbon"]][i])
if(carbon.value <= 0)
message("Carbon for layer: ", i, " is less than or equal to zero. Value: ", carbon.value)
if(carbon.value > 100)
message("Carbon for layer: ", i, " is greater than 100. Value: ", carbon.value)
### Checking SoilCNRatio
soilcnratio.value <- as.numeric(soil.organic.layers[["SoilCNRatio"]][i])
if(soilcnratio.value <= 0)
message("SoilCNRatio for layer: ", i, " is less than or equal to zero. Value: ", soilcnratio.value)
if(soilcnratio.value > 100)
message("SoilCNRatio for layer: ", i, " is greater than 100. Value: ", soilcnratio.value)
### Checking FBiom
FBiom.value <- as.numeric(soil.organic.layers[["FBiom"]][i])
if(FBiom.value <= 0)
message("FBiom for layer: ", i, " is less than or equal to zero. Value: ", FBiom.value)
if(FBiom.value > 1)
message("FBiom for layer: ", i, " is greater than 1. Value: ", FBiom.value)
### Checking FInert
FInert.value <- as.numeric(soil.organic.layers[["FInert"]][i])
if(FInert.value <= 0)
message("FInert for layer: ", i, " is less than or equal to zero. Value: ", FInert.value)
if(FInert.value > 1)
message("FInert for layer: ", i, " is greater than 1. Value: ", FInert.value)
### Checking that FBiom + FInert are not greater than 1
if(FBiom.value + FInert.value > 1){
message("FBiom + FInert for layer: ", i, " is greater than 1. FBiom Value: ", FBiom.value, " FInert Value: ", FInert.value)
}
### Checking FOM
FOM.value <- as.numeric(soil.organic.layers[["FOM"]][i])
if(FOM.value < 0)
message("FOM for layer: ", i, " is less than zero. Value: ", FOM.value)
if(FOM.value > 1e5)
message("FOM for layer: ", i, " is greater than 100,000. Value: ", FOM.value)
}
}
}
}
#' Set apsimx options
#'
#' @title Setting some options for the package
#' @name apsimx_options
#' @description Set the path to the APSIM-X executable, examples and warning suppression.
#' @param exe.path path to apsim executable. White spaces are not allowed.
#' @param dotnet logical indicating if APSIM should be run through the dotnet command
#' @param mono logical indicating if the mono command should be used when running APSIM. This is for versions
#' for Mac/Linux older than Sept 2021.
#' @param examples.path path to apsim examples
#' @param warn.versions logical. warning if multiple versions of APSIM-X are detected.
#' @param warn.find.apsimx logical. By default a warning will be thrown if APSIM-X is not found.
#' If \sQuote{exe.path} is \sQuote{NA} an error will be thrown instead.
#' @param allow.path.spaces logical. By default spaces are not allowed in paths or in the run command.
#' @note It is possible that APSIM-X is installed in some alternative location other than the
#' defaults ones. Guessing this can be difficult and then the auto_detect functions might
#' fail. Also, if multiple versions of APSIM-X are installed apsimx will choose the newest
#' one but it will issue a warning. Suppress the warning by setting warn.versions = FLASE.
#' @return as a side effect it modifies the \sQuote{apsimx.options} environment.
#' @export
#' @examples
#'\donttest{
#' names(apsimx.options)
#' apsimx_options(exe.path = "some-new-path-to-executable")
#' apsimx.options$exe.path
#' }
apsimx_options <- function(exe.path = NA, dotnet = FALSE, mono = FALSE, examples.path = NA,
warn.versions = TRUE, warn.find.apsimx = TRUE, allow.path.spaces = FALSE){
if(dotnet && mono)
stop("either dotnet or mono should be TRUE, but not both", call. = TRUE)
assign('exe.path', exe.path, apsimx.options)
assign('dotnet', dotnet, apsimx.options)
assign('mono', mono, apsimx.options)
assign('examples.path', examples.path, apsimx.options)
assign('warn.versions', warn.versions, apsimx.options)
assign('warn.find.apsimx', warn.find.apsimx, apsimx.options)
assign('allow.path.spaces', allow.path.spaces, apsimx.options)
}
#' Environment which stores APSIM-X options
#'
#' @title Environment which stores APSIM-X options
#' @name apsimx.options
#' @description Environment which can store the path to the executable, warning settings and
#' where examples are located.
#' Creating an environment avoids the use of global variables or other similar practices
#' which would have possible undesriable consequences.
#' @return This is an environment, not a function, so nothing is returned.
#' @export
#' @examples
#' \donttest{
#' names(apsimx.options)
#' apsimx_options(exe.path = "some-new-path-to-executable")
#' apsimx.options$exe.path
#' }
#'
apsimx.options <- new.env(parent = emptyenv())
assign('exe.path', NA, apsimx.options)
assign('dotnet', FALSE, apsimx.options)
assign('mono', FALSE, apsimx.options)
assign('examples.path', NA, apsimx.options)
assign('warn.versions', TRUE, apsimx.options)
assign('warn.find.apsimx', TRUE, apsimx.options)
assign('allow.path.spaces', FALSE, apsimx.options)
assign('.run.local.tests', FALSE, apsimx.options)
## I'm planning to use '.run.local.tests' for running tests
## which do not require an APSIM install
## Import packages needed for apsimx to work correctly
#' @import DBI jsonlite knitr RSQLite xml2
#' @importFrom utils read.table write.table packageVersion
#' @importFrom tools file_path_sans_ext file_ext
#' @importFrom stats aggregate anova coef cor cov2cor deviance lm optim pnorm qt quantile var sd setNames sigma terms reformulate
NULL
utils::globalVariables(".data")
Any scripts or data that you put into this service are public.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.