--- title: "benchmark" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{benchmark} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r, include = FALSE} knitr::opts_chunk$set( collapse = TRUE, eval = FALSE ) ``` This vignette guides you through using the benchmark function of lpjmlstats in a typical LPJmL development workflow. Say you have been working on a change in the LPJmL setup, tweaking some settings in the config or making changes to the source code. A global run with the new setup has been performed and the output was sucessfully written to the output directory. At this point, the natural question arises: 'How have the modifications affected the results?' The benchmarking system helps find an answer by systematically comparing the outputs of the modified, currently "under test," LPJmL setup against the outputs of a stable "baseline" setup. Results from different baseline LPJmL versions can be found in the `p/projects/lpjml/benchmark_run_outputs/` directory on the cluster. This package is a component of the PIAM cluster module. It is recommended to load this module via `module load piam` before starting the R session, ensuring that all necessary packages are loaded. # Using the benchmark function In this vignette we will evaluate how the results of LPJmL changed, going from version 5.7.1 to version 5.8.6. Start by loading the package. ``` {r load_package} library(lpjmlstats) # nolint ``` The basic usage of the benchmarking function is simple. Enter the paths of the under test and baseline output directories, along with some meta-information. ``` {r perform-first-benchmark} benchmark_result <- benchmark( baseline_dir = "/p/projects/lpjml/benchmark_run_outputs/5.7.1/output", # nolint # it is recommended to use absolute paths under_test_dirs = "/p/projects/lpjml/benchmark_run_outputs/5.8.6/output", # nolint author = "David", description = "Evaluate the changes from version 5.7.1 to 5.8.6", output_file = "benchmark_vignette.pdf" ) ``` This will create a PDF report in the working directory and stores the numerical results in the `benchmark_result` object. The process usually takes several minutes. It will summarize and compare the typically most relevant LPJmL outputs. Note that the console output highlights the currently processed output variable in blue. After the core numerical benchmarking is completed, it will start to generate the PDF report, which visualizes the numerical results in different plots and tables. Use `?benchmark` to see how the output directory for the PDF report as well as other function arguments can be defined. In the details section of the function documentation you can also see the requirements for the function to work properly. One of the most important other arguments that can be passed to the benchmark function is the `settings` list. It defines the full protocol of the benchmarking process, including which outputs are considered, how they are processed, compared and visualized, and what the order of the processing and output PDF is. If no settings are passed, the default as given in the `R\default_settings.R` package file is used. Here is an excerpt of that file: ``` {r, eval=FALSE} default_settings <- list( vegc = c(GlobSumTimeAvgTable, GlobSumAnnAvgTimeseries, TimeAvgMap), soilc = c(GlobSumTimeAvgTable, GlobSumAnnAvgTimeseries, TimeAvgMap), litc = c(GlobSumTimeAvgTable, GlobSumAnnAvgTimeseries, TimeAvgMap), ... ) ``` The settings list reads as follows: Each line corresponds to an output variable. The left hand side of the equality sign defines *which output is processed* and the right hand side defines *how this output is processed*. More specifically the right hand side defines which metrics are used for the evaluation of the output. A metric is a generic pipeline to summarize, compare and visualize output data. It is generic because the same metric can be applied to different outputs. The metrics are the cornerstone of the benchmarking process. See `?Metric` for technical details. The metric `GlobSumTimeAvgTable` globally sums up all cellular values weighted by reference area, and then averages over time. The resulting scalar values are compared and displayed in a table. The metric `GlobSumAnnAvgTimeseries` also computes a weighted sum, but only averages over each year (if the output is not already given anually). The resulting time series are displayed in a plot. Finally, `TimeAvgMap` only averages over time, and plots the differences from under-test to baseline as maps. Type `?GlobSumTimeAvgTable` for details. Often it is needed to change these settings, for example when new outputs should be evaluated. Read the [benchmark-change-settings](benchmark-change-settings.html) vignette for a tutorial. Benchmarking multiple under test runs against a baseline is also possible. For example, it could be interesting to compare the 5.8.1 version with several older versions. ``` {r bechmark_multiple_under_test, eval = F} benchmark( "/p/projects/lpjml/benchmark_run_outputs/5.8.1/output", # nolint list( "/p/projects/lpjml/benchmark_run_outputs/5.7.7/output", # nolint # note that older versions than 5.7.1 can at the moment not be benchmarked due # to missing terr_area output "/p/projects/lpjml/benchmark_run_outputs/5.7.1/output" # nolint ), author = "David", description = "Check how newer lpjml versions compare to older versions", output_file = "benchmark_vignette_multiple_ut.pdf" ) ``` Comparing more than two under test to the baseline may result in display problems in the PDF report. You can also give short identifiers for the simulation that will be used throughout at this point: ``` {r bechmark_multiple_under_test_custom_names, eval = F} benchmark( list(my_baseline = "/p/projects/lpjml/benchmark_run_outputs/5.8.1/output"), # nolint list( my_test1 = "/p/projects/lpjml/benchmark_run_outputs/5.7.7/output", # nolint # note that older versions than 5.7.1 can at the moment not be benchmarked due # to missing terr_area output my_test2 = "/p/projects/lpjml/benchmark_run_outputs/5.7.1/output" # nolint ), ... ) ``` # Working with the benchmark results ## The PDF report The PDF report offers a visualization of the numerical benchmarking results. It is structured by the different metrics used in the settings. Each metric has its own section where the result from all outputs using this metric are displayed. The front page shows some meta information on the benchmarking. Of particular importance is the simulation table, that maps short simulation identifiers to the runs considered in the benchmarking. Usually these identifier are abbreviations of the simulation names, as defined in the lpjml config. If these names are not unique, abbreviations of the simulation paths are used. The identifiers are employed because the simulation names and paths are often quite long, making it more convenient to use short abbreviations to refer to the different runs in the report, especially when comparing the baseline with multiple under test runs. ## The benchmark result object The benchmarking function also outputs its numerical results as a benchmark result object. Accessing this data is a bit technical, so skip this section if you are only interested in the PDF report. The benchmark result behaves similarly to a nested R list. The top layer is again structured by the different metrics. The data that is generated by a metric is stored in its var_grp_list attribute. It contains the results for all the variables that the metric evaluated as a list of so called variable groups. Each variable group consists of the processed baseline and (possibly multiple) under test outputs of that variable, as well as the comparisons of all under test outputs against the baseline. See `?Metric` for details on how the metrics organize their processed data. Say we are interested in how the time series of global vegetation carbon has changed in the newer version. The data from the baseline run can be accessed via ```{r acess_baseline_vegc} vegc_old_ts <- benchmark_result$GlobSumAnnAvgTimeseries$var_grp_list$vegc$baseline vegc_old_ts ``` Note that this is an LPJmLData object of `lpjmlkit`. Evaluating an LPJmLData object in the R Console will provide you with a lot of useful meta data, about the origin and processing history of the object. Note that subset is `TRUE` and space aggregation is `weighted_sum`, which reflects the processing steps done by the benchmarking function. You will encounter several elements of the `lpjmlkit` package in `lpjmlstats` - in fact `lpjmlstats` completly builds upon `lpjmlkit`. This also means that we can employ the functionality of this latter package to work with the benchmark results. For example, to output the first 5 years of `vegc_old_ts` we use ```{r subset_vegc} subset(vegc_old_ts, time = 1:5)$data ``` For under test results the simulation identifiers are used to access the data. This is needed to differentiate the under test outputs if multiple runs are benchmarked against a baseline run. The simulation table that relates the identifiers to simulation names, as well as other meta data can be accessed via: ```{r get_metadata} get_benchmark_meta_data(benchmark_result) ``` Note that the simulation with the new version has the identifier `5.8.`. Thus, to access the new vegc time series of the new LPJmL version we use ```{r access_under_test_metadata} vegc_new_ts <- benchmark_result$GlobSumAnnAvgTimeseries$var_grp_list$vegc$under_test$`5.8.` ``` To check the difference between the two time series we use ```{r calculate_vegc_difference} subset(vegc_new_ts, time = 1:5)$data - subset(vegc_old_ts, time = 1:5)$data ``` Observe that the newer version produced less vegetation carbon in the first five years. The data from the benchmarking object in combination with the functionality of lpjmlkit is also useful to make comparisons to literature data. Say we have a reference dataset of global soil nitrogen given as a terra object. Again the tools from lpjmlkit come in handy to convert the benchmark data to terra: ```{r convert_to_terra} soiln_lpjmldata <- benchmark_result$TimeAvgMap$var_grp_list$soiln$under_test$`5.8.` soiln_terra <- lpjmlkit::as_terra(soiln_lpjmldata) soiln_terra # read in validation data terra # ... ``` Another convenient feature of the benchmark data object is that all plot functions used for the report generation can also be called individually from here. For example the table of the `GlobSumTimeAvgTable` metric can be generated via ```{r output_table} benchmark_result$GlobSumTimeAvgTable$plot() ``` This enables a terminal-only benchmarking without generating plots or reports. See `?benchmark` for how to skip the PDF report generation in the benchmarking. Sometimes individual plots of the benchmarking may be needed in another context. The following chunk generates the vegc plot. ```{r plot_vegc, fig.width=8, fig.height=6} benchmark_result$TimeAvgMap$plot()[[1]] # vegc is the first output defined in the settings ``` With the exception of the table, all plots generated in the benchmarking are ggplot objects, which enables modifications of all visual plot elements. For example a different title can be added to the litter carbon plot with the following code. ```{r plot_litc, fig.width=8, fig.height=6} vegc_map <- benchmark_result$TimeAvgMap$plot()[[3]] # litc is the third output of the settings vegc_map + ggplot2::ggtitle("Difference in litter carbon; lpjml 5.8.6 - lpjml 5.7.1") ``` Maybe the preceding discussion convinced you that working with the benchmark object is fun and useful. It provides a lot of interfaces to further processing steps and functionality. It may thus be valuable to save the object for later analysis. This last step of the tutorial can be done for example with ```{r save_benchmarkResult_obj, eval = FALSE} saveRDS(benchmark_result, file = "benchmark_result_vignette.rds") ``` This file can also be used to recreate the benchmark report. See `?create_pdf_report` for details.