--- title: "Using fz for parametric studies" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Using fz for parametric studies} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r, include = FALSE} # Chunks are eval = FALSE (they need the funz-fz Python package and a # configured calculator) and purl = FALSE (so `R CMD check`'s tangle-and-run # step does not try to execute them either). The code is still shown in the # rendered vignette. knitr::opts_chunk$set( collapse = TRUE, comment = "#>", eval = FALSE ) ``` ```{r setup, purl = FALSE} library(fz) ``` ## Introduction The `fz` package provides R bindings to the [funz-fz](https://pypi.org/project/funz-fz/) Python package. It lets you: - run **parameter sweeps** over any simulation code, - drive **design of experiments** with adaptive algorithms, - read and collect **output files** into data frames. fz works with any simulation code that reads text input files and writes text output files. You describe the model in a small dict (or install it as a named alias with `fz install`). ## Installation ```{r install, purl = FALSE} # Install the funz-fz Python package into the active reticulate environment fz_install() # Verify fz_available() ``` ## Concepts ### Template files A **template** is an ordinary input file for your simulator with variable placeholders, e.g.: ``` # Perfect Gas parameters pressure = ${P~1.013} # variable P, default 1.013 volume = ${V~22.4} # variable V, default 22.4 moles = ${n~1.0} ``` The placeholder syntax (`$`, `{}`) is defined by the **model** dict. ### Model dict The model dict tells fz how to: 1. find variable placeholders (`varprefix`, `delim`), 2. extract output values from the result files (`output`). ```{r model_dict, purl = FALSE} model <- list( varprefix = "$", delim = "{}", formulaprefix = "@", commentline = "#", output = list( pressure = "grep 'pressure =' output.txt | cut -d= -f2" ) ) ``` You can also use an installed model alias (a string) instead of an inline dict: ```{r model_alias, purl = FALSE} fzl()$models # lists installed aliases, e.g. "PerfectGas" ``` ## A complete runnable example (external simulator) The rest of this vignette uses a placeholder `run.sh`. This section instead runs a **real external program**: a tiny Python simulator of the ideal gas law `P = n R T / V`. Both files ship with the package under `inst/examples/perfectgas/`. ```{r pg_setup, purl = FALSE} ex <- system.file("examples", "perfectgas", package = "fz") file.copy(list.files(ex, full.names = TRUE), ".") # perfectgas.txt + perfectgas.py ``` `perfectgas.txt` is the input template: ``` temperature = ${T~300} # K volume = ${V~0.001} # m3 moles = ${n~1} # mol ``` `perfectgas.py` reads the compiled `perfectgas.txt` from its working directory (fz runs each case in its own directory), computes the pressure, and writes `pressure = ` to `out.txt`. ```{r pg_model, purl = FALSE} model <- list( varprefix = "$", delim = "{}", formulaprefix = "@", commentline = "#", # shell-free output extraction (funz-fz >= 1.2) output = list(pressure = 'python://grep(r"pressure = (\\S+)", "out.txt")') ) fzi("perfectgas.txt", model) #> $T [1] 300 #> $V [1] 0.001 #> $n [1] 1 ``` ```{r pg_run, purl = FALSE} results <- fzr( "perfectgas.txt", list(T = c(300, 350, 400), V = 1e-3, n = 1), # 3 cases model, calculators = "sh://python3 perfectgas.py", # the external simulator input_static = "perfectgas.py" # shipped into every case dir (funz-fz >= 1.2) ) results[, c("T", "V", "n", "pressure")] #> T V n pressure #> 1 300 0.001 1 2494339 #> 2 350 0.001 1 2910062 #> 3 400 0.001 1 3325785 ``` The same model drives `fzd()` — keep `calculators` and `input_static`, and pass `input_variables` as `"[min;max]"` ranges (a variable held fixed is just `"1"`): ```{r pg_fzd, purl = FALSE} fzd( "perfectgas.txt", list(T = "[280;320]", V = "[1e-3;2e-3]", n = "1"), model, output_expression = "pressure", algorithm = "algorithms/montecarlo_uniform.py", calculators = "sh://python3 perfectgas.py", input_static = "perfectgas.py" ) ``` ## Basic workflow The typical fz workflow has four steps. ### Step 1 — Inspect the template `fzi` parses the template and returns the variable names together with their default values: ```{r fzi, purl = FALSE} vars <- fzi("input.txt", model) # $P [1] 1.013 # $V [1] 22.4 # $n [1] 1.0 ``` ### Step 2 — Compile (substitute values) `fzc` writes one copy of the input file per parameter combination into `output_dir`. Each copy goes into a subdirectory named `var1=val1,var2=val2,...`: ```{r fzc_single, purl = FALSE} # Single case fzc("input.txt", list(P = 2.0, V = 11.2), model, output_dir = "compiled") # writes: compiled/P=2,V=11.2/input.txt (placeholder replaced with 2.0 / 11.2) ``` Supply vectors to generate a full-factorial grid: ```{r fzc_grid, purl = FALSE} # 2 x 3 = 6 cases fzc("input.txt", list(P = c(1.0, 2.0), V = c(10.0, 20.0, 30.0)), model, output_dir = "compiled") ``` ### Step 3 — Run the model and collect outputs `fzr` wraps steps 1–3 and output collection into a single call. It compiles the template, runs the calculator for every case, and returns a data frame: ```{r fzr, purl = FALSE} results <- fzr( "input.txt", list(P = c(1.0, 2.0, 3.0), V = 22.4), # 3 cases (V fixed) model, results_dir = "results", calculators = "sh://bash run.sh" # run.sh executes the simulator ) # results is a data frame: # P V pressure # 1 1.0 22.4 ... # 2 2.0 22.4 ... # 3 3.0 22.4 ... ``` The `calculators` argument accepts: - `"sh://bash run.sh"` — run a local shell command - `"sh://"` — execute the input file directly as a shell script - `"ssh://user@host"` — run over SSH ### Step 4 — Read outputs from existing directories If you already ran the simulator externally, `fzo` reads the output files: ```{r fzo, purl = FALSE} values <- fzo("results/P=2,V=22.4", model) # $pressure [1] "2.026" # Glob to read all cases at once: all_values <- fzo("results/*", model) ``` ## Algorithm-driven design of experiments `fzd` runs an adaptive experiment: the algorithm decides which parameter combinations to evaluate based on previous results. Input variable ranges use `"[min;max]"` strings: ```{r fzd, purl = FALSE} result <- fzd( "input.txt", list(P = "[1;5]", V = "[10;30]"), model, output_expression = "pressure", algorithm = "algorithms/montecarlo_uniform.py", algorithm_options = list(batch_sample_size = 10, max_iterations = 5, seed = 42) ) ``` Algorithms are Python files; `fz` ships several in `algorithms/` (Monte Carlo, surrogate-based optimization, …). You can also write your own. ## Listing installed models `fzl` shows which model aliases and calculators are installed in `~/.fz/`: ```{r fzl, purl = FALSE} info <- fzl() names(info$models) # e.g. c("PerfectGas", "Moret") names(info$calculators) # e.g. c("sh://") # Filter by pattern fzl(models = "Perfect*") # Probe calculators to verify they are reachable fzl(check = TRUE) ``` ## Best practices 1. **Test with `fzi` first** — verify the correct variable names are found before running anything. 2. **Use `fzc` for a dry run** — inspect compiled files to confirm placeholder substitution is correct. 3. **Start small** — run a handful of cases before launching a large sweep. 4. **Save results** — persist the data frame for reproducibility. ```{r save_results, purl = FALSE} saveRDS(results, "fz_results.rds") write.csv(results, "fz_results.csv", row.names = FALSE) ``` ## Troubleshooting **`fz` Python package not found:** ```{r troubleshoot_install, purl = FALSE} fz_install() # install funz-fz into the reticulate environment fz_available() # should return TRUE afterwards ``` **Variables not found in template:** Check that `varprefix` and `delim` in your model dict match the syntax used in your template file. Run `fzi` and inspect the returned list. **Calculator errors:** Run the simulator manually on one compiled directory to confirm it works before using `fzr`. ## Further reading - funz-fz documentation: - reticulate: ## Session info ```{r session_info, purl = FALSE} sessionInfo() ```