#===== generate_all_benchmarks ===== #'generate_all_benchmark_scripts #'@description Wrapper function to generate benchmark scripts from all .prj #' files in a directory #'@param path string: Path to a benchmark directory to generate scripts from #'@param sim_path string: Path where all simulation files will be saved #'@param scripts_path string: Path where benchmark scripts will be saved #'@param read_in_gmls flag: Optional: Should .gml files just be copied or read #' in too? #'@param read_in_vtus flag: Optional: Should .vtu files just be copied or read #' in too? #'@param starting_from_prj_path string: Optional: #'@param skip_prj_paths character: Optional: .prj paths to skip generate_all_benchmark_scripts <- function(path, sim_path, scripts_path, read_in_gmls, read_in_vtus = FALSE, starting_from_prj_path = "", skip_prj_paths = character()){ if(missing(path)){ path <- unlist(options("r2ogs6.default_benchmark_path")) } if(missing(sim_path)){ sim_path <- unlist(options("r2ogs6.default_sim_path")) } if(missing(scripts_path)){ scripts_path <- unlist(options("r2ogs6.default_script_path")) } missing_read_in_gmls <- missing(read_in_gmls) path <- as_dir_path(path) scripts_path <- as_dir_path(scripts_path) assertthat::assert_that(assertthat::is.string(starting_from_prj_path)) assertthat::assert_that(is.character(skip_prj_paths)) assertthat::assert_that(assertthat::is.flag(read_in_vtus)) prj_paths <- list.files(path = path, pattern = "\\.prj$", recursive = TRUE, full.names = TRUE) # If we know the benchmarks up to a specific file are working, skip them if(starting_from_prj_path != ""){ if(is.na(match(starting_from_prj_path, prj_paths))){ warning(paste("Couldn't find path to start from.", "Returning all paths."), call. = FALSE) }else{ start_index <- match(starting_from_prj_path, prj_paths) prj_paths <- prj_paths[start_index:length(prj_paths)] } } invalid_xml_paths <- character() invalid_prj_paths <- character() for(i in seq_len(length(prj_paths))){ if(prj_paths[[i]] %in% skip_prj_paths){ next } skip_generate <- FALSE generate_failed <- FALSE out<- tryCatch( { xml2::read_xml(prj_paths[[i]], encoding="ISO-8859-1") }, error = function(cond){ message(paste("\nxml2::read_xml() failed for", prj_paths[[i]], ". Original error message:")) message(cond) skip_generate <<- TRUE } ) if(skip_generate){ invalid_xml_paths <- c(invalid_xml_paths, prj_paths[[i]]) next } # cat("\nGenerating script from path", prj_paths[[i]]) # Put simulations in their own subfolders under sim_path sim_subdir <- paste0(sim_path, basename(dirname(prj_paths[[i]])), "_", tools::file_path_sans_ext(basename(prj_paths[[i]]))) out<- tryCatch( { if(missing_read_in_gmls){ generate_benchmark_script(prj_path = prj_paths[[i]], sim_path = sim_subdir, script_path = scripts_path, read_in_vtu = read_in_vtus) }else{ generate_benchmark_script(prj_path = prj_paths[[i]], sim_path = sim_subdir, script_path = scripts_path, read_in_gml = read_in_gmls, read_in_vtu = read_in_vtus) } }, error = function(cond){ message(paste("\ngenerate_benchmark_script() failed for", prj_paths[[i]], ". Original error message:")) message(cond) generate_failed <<- TRUE } ) if(generate_failed){ invalid_prj_paths <- c(invalid_prj_paths, prj_paths[[i]]) } } return(invisible(list(invalid_xml_paths, invalid_prj_paths))) } #===== generate_benchmark_script ===== #'generate_benchmark_script #'@description Generates a benchmark script from an existing .prj file. #'@param prj_path string: .prj file the script will be based on #'@param sim_path string: Path where all simulation files will be saved #'@param ogs_bin_path string: OpenGeoSys bin folder path #'@param script_path string: Path where benchmark script will be saved #'@param read_in_gml flag: Optional: Should .gml file just be copied or read #' in too? #'@param read_in_vtu flag: Optional: Should .vtu file(s) just be copied or read #' in too? #'@export generate_benchmark_script <- function(prj_path, sim_path, ogs_bin_path, script_path, read_in_gml, read_in_vtu = FALSE) { if(missing(ogs_bin_path)){ ogs_bin_path <- unlist(options("r2ogs6.default_ogs_bin_path")) } if(missing(script_path)){ script_path <- unlist(options("r2ogs6.default_script_path")) } assertthat::assert_that(assertthat::is.string(prj_path)) assertthat::assert_that(assertthat::is.string(sim_path)) assertthat::assert_that(assertthat::is.string(ogs_bin_path)) assertthat::assert_that(assertthat::is.string(script_path)) assertthat::assert_that(assertthat::is.flag(read_in_vtu)) #Construct an object from a benchmark and then reverse engineer the call ogs6_obj <- OGS6$new(sim_name = "", sim_id = 1, sim_path = "") read_in_prj(ogs6_obj, prj_path, read_in_vtu, read_in_gml = FALSE) prj_components = prj_top_level_classes() sim_name <- tools::file_path_sans_ext(basename(prj_path)) script_str <- paste0("library(r2ogs6)\n\n", "ogs6_obj <- OGS6$new(sim_name = \"", sim_name, "\",\n", "sim_id = 1,\n", "sim_path = \"", sim_path, "\")\n\n\n") # If there is a .gml but it shouldn't be read in, add reference if (!is.null(ogs6_obj$geometry)) { # If read_in_gml isn't supplied, check number of lines in .gml file # since string concatenation is slow if(missing(read_in_gml)){ read_in_gml <- (length(readLines(ogs6_obj$geometry)) <= unlist(options("r2ogs6.max_lines_gml"))) } assertthat::assert_that(assertthat::is.flag(read_in_gml)) if(!read_in_gml){ script_str <- paste0(script_str, "ogs6_obj$add_gml(", construct_add_call(ogs6_obj$geometry), ")\n\n" ) }else{ ogs6_obj$add_gml(OGS6_gml$new(ogs6_obj$geometry)) script_str <- paste0(script_str, construct_add_call(ogs6_obj$gml), "\n\n") } } # Add .vtu references and optionally, OGS6_vtu objects for(i in seq_len(length(ogs6_obj$meshes))){ script_str <- paste0(script_str, "ogs6_obj$add_vtu(", construct_add_call(ogs6_obj$meshes[[i]]), ",\n", read_in_vtu, ")\n\n") } # Add class objects (and such in wrapper lists) for(i in seq_len(length(prj_components))){ get_component_call <- paste0("ogs6_obj$", names(prj_components)[[i]]) ogs6_component <- eval(parse(text = get_component_call)) # If benchmark doesn't have components of specified name, skip if(is.null(ogs6_component) || length(ogs6_component) == 0){ next } #If objects are not in wrapper list, wrap them up for seq_along() if(any(grepl("r2ogs6_", class(ogs6_component), fixed = TRUE)) || any(grepl("OGS6_", class(ogs6_component), fixed = TRUE))){ ogs6_component <- list(ogs6_component) } for(j in seq_along(ogs6_component)){ script_str <- paste0(script_str, paste0(construct_add_call(ogs6_component[[j]]), "\n\n")) } } script_str <- paste0(script_str, "run_simulation(ogs6_obj,\n", "ogs_bin_path = \"", ogs_bin_path, "\")\n") #If no destination file was defined, print output to console if(script_path != ""){ if(!dir.exists(script_path)){ dir.create(script_path, showWarnings = FALSE) } filename <- paste0(script_path, sim_name, ".R") if(file.exists(filename)){ filename <- paste0(script_path, basename(dirname(prj_path)), "__", sim_name, ".R") if(file.exists(filename)){ warning("\nMultiple .prj files with same name in 'path'\n", call. = FALSE) } } cat(script_str, file = filename) }else{ cat(script_str) } return(invisible(script_str)) } #'construct_add_call #'@description Constructs a call based on an OGS6 component. This is a #' recursive function, handle with care. #'@param object An object (numeric, character, list, NULL, OGS6 or r2ogs6 class #' object) #'@param nested_call Optional: For recursion purposes, you should leave this as #' it is. #'@return A string representing the code with which the component would be added #' to an OGS6 object construct_add_call <- function(object, nested_call = FALSE) { #For values of type numeric or character, dput will give us usable output if(is.character(object) || is.numeric(object)){ ret_str <- paste(utils::capture.output(dput(object)), collapse="\n") return(invisible(ret_str)) } #For NULL values we return "NULL" as string if(is.null(object)){ return("NULL") } #For r2ogs6 objects we need to use recursion if(any(grepl("r2ogs6", class(object), fixed = TRUE)) || any(grepl("OGS6", class(object), fixed = TRUE))){ class_name <- "" formals_call <- "" init_prefix <- "" use_s3_syntax <- TRUE if("R6" %in% class(object)){ class_name <- grep("OGS6", class(object), fixed = TRUE, value = TRUE) use_s3_syntax <- FALSE init_prefix <- "$new" }else{ class_name <- grep("r2ogs6", class(object), fixed = TRUE, value = TRUE) } assertthat::assert_that(length(class_name) == 1) tag_name <- paste(utils::tail(unlist(strsplit(class_name, "_")), -1), collapse = "_") #Grab helper param_names <- get_class_args(class_name) #Handle Ellipsis if it exists by removing and substituting it if("..." %in% param_names){ param_names <- param_names[param_names != "..."] param_names <- c(param_names, object$unwrap_on_exp) } param_strs <- list() for(i in seq_len(length(param_names))){ get_param_call <- paste0("object$", param_names[[i]]) param <- eval(parse(text = get_param_call)) param_str <- construct_add_call(param, TRUE) param_strs <- c(param_strs, list(param_str)) } content_str <- paste(param_names, param_strs, sep = " = ", collapse = ",\n") ret_str <- paste0(class_name, init_prefix, "(", content_str, ")") #If call isn't nested, it can be added if(!nested_call){ ret_str <- paste0("ogs6_obj$add(", ret_str, ")\n") } ret_str <- delete_nulls_from_str(ret_str) ret_str <- delete_keywords_from_str(ret_str) ret_str <- delete_empty_from_str(ret_str) return(invisible(ret_str)) } #For tibbles we don't need recursion, but they still require extra handling if("tbl_df" %in% class(object)){ tib_str <- paste(names(object), object, sep = " = ", collapse = ",\n") ret_str <- paste0("tibble::tibble(", tib_str, ")") return(invisible(ret_str)) } #For lists we need to use recursion if(class(object) == "list"){ element_strs <- lapply(object, function(x){construct_add_call(x, TRUE)}) if(is.null(names(object)) || rlist::list.any(names(object) == "")){ content_str <- paste(element_strs, collapse = ",\n") }else{ content_str <- paste(names(object), element_strs, sep = " = ", collapse = ",\n") } ret_str <- paste0("list(", content_str, ")") return(invisible(ret_str)) } } #'delete_nulls_from_str #'@description Utility function to delete "param_name = NULL" from a string, #' this isn't necessary for functionality of generate_benchmark_script but will #' make generated scripts way more readable. #'@param string string delete_nulls_from_str <- function(string){ regexp_1 <- ",[\n|[:space:]]?[\\w_]* = NULL" regexp_2 <- "[\\w_]* = NULL,[\n|[:space:]]?" string <- stringr::str_remove_all(string, regexp_1) string <- stringr::str_remove_all(string, regexp_2) return(invisible(string)) } #'delete_empty_from_str #'@description Utility function to delete "param_name = list()" from a string, #' this isn't necessary for functionality of generate_benchmark_script but will #' make generated scripts way more readable. #'@param string string delete_empty_from_str <- function(string){ regexp_1 <- ",[\n|[:space:]]?[\\w_]* = list\\(\\)" regexp_2 <- "[\\w_]* = list\\(\\),[\n|[:space:]]?" string <- stringr::str_remove_all(string, regexp_1) string <- stringr::str_remove_all(string, regexp_2) return(invisible(string)) } #'delete_keywords_from_str #'@description Utility function to delete keywords from a string, #' this is important because there is a <repeat> tag in <time_loop> and #' "repeat" is a reserved word in R (extend this function if you find more #' reserved words) #'@param string string delete_keywords_from_str <- function(string){ string <- stringr::str_remove_all(string, "repeat = ") return(invisible(string)) }