R/apsimx.R

Defines functions apsimx_options read_apsimx_all read_apsimx apsimx_example auto_detect_apsimx_examples auto_detect_apsimx xargs_apsimx apsimx

Documented in apsimx apsimx_example apsimx_options auto_detect_apsimx_examples read_apsimx read_apsimx_all xargs_apsimx

#' 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
#'              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")
  
  .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)
    res <- system(command = run.strng, ignore.stdout = silent, intern = TRUE)
  }
  
  if(.Platform$OS.type == "windows"){
    ## Should probably delete the line below
    ## As of 2020-07-04, this seems to be working fine
    ## I will leave the line below until I'm sure it is not needed
    ## if(src.dir != ".") stop("In Windows you can only run a file from the current directory.")
    run.strng <- paste0(ada, " ", file.name.path)
    if(!missing(xargs)) run.strng <- paste(run.strng, xargs$xargs.string)
    shell(cmd = run.strng, translate = TRUE, intern = TRUE)
  }

  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], .favd))
        newest.version <- laf[find.apsim][len.fa]
        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
        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){
          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){
        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){
        newest.version <- laf[find.apsim][length(find.apsim)]
        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("Barley", "ControlledEnvironment", "Eucalyptus",
             "EucalyptusRotation",
             "Maize", "Oats", "Rotation", "Soybean", "Sugarcane", "Wheat")

  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
#' @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)
  ## 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)  
      }
    }
  }
    
  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)  
          }
        }
        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)
        }
        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)
    }
  }
    
  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)
}

#' 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.
#' @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){
  
  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)
}

#' 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('.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
#' @importFrom stats aggregate anova coef cor cov2cor deviance lm optim qt var sd setNames sigma 
NULL

utils::globalVariables(".data")

Try the apsimx package in your browser

Any scripts or data that you put into this service are public.

apsimx documentation built on March 18, 2022, 7:52 p.m.