R/apsimx.R

Defines functions apsimx_options check_apsimx 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 check_apsimx 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; \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")

Try the apsimx package in your browser

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

apsimx documentation built on Sept. 11, 2024, 5:42 p.m.