Introduction

R is designed to only use one cpu (or core) when running tasks. However, you may have access to a computer cluster1 that allows you to access more RAM and cpus. The use of more than one cpu is known as parallel computing in R. The goal of this tutorial is to provide the basics of using the batchtools package and utilizing more cores in a cluster.

This tutorial is built off the information provided by UCR’s High Performance Computing Center tutorial of batchtools package. This tutorial uses a simulation study to show you the power of batchtools. For more information visit their help documentation for batchtools.

This tutorial is meant to be run on the HPCC cluster at UCR. You will need an account to the HPCC cluster. If you are a graduate student in UCR’s Statistics Department, contact the UCR Statistics Graduate Student Association to gain access.

This tutorial conducts different simulation scenarios to be submitted as jobs. It requires an amount of set up to be effective. There is an an R script that you can use that may provide better insight on using batchtools. You can access R script here

Files, Functions and Packages

Files

Before you begin, download these files to the directory you are working with. The slurm.tmpl file provides information to the slurm scheduler. The .batchtools.conf.R file tells R to create a cluster function for slurm. The period in front of .batchtools.conf.R will hide the file in your directory. It is there, it is just not displayed.

download.file(\https://bit.ly/3gZJBsy\, \slurm.tmpl\)
download.file(\https://bit.ly/3nvSNHA\, \.batchtools.conf.R\)

Packages

You will need to use 2 R packages: RenvModule and batchtools. The RenvModule2 package provides the tools to interact with modules in the cluster. The batchtools package provides the tools to parallelize your code. Both are needed. For this tutorial, you will also need to have the mvtrnorm package installed.

library(RenvModule)
library(batchtools)
library(mvtnorm)

Functions

module()

The module() function loads different modules into the R environment.

makeRegistry()

The makeRegistry() function creates a registry for batchtools. You can think of the registry as the communication center for R, your functions, and the cluster. It will store everything in a directory. The makeRegistry() function needs the argument file.dir argument, the location to store the registry, and conf.file=.batchtools.conf.R. Store the output from the function into an R object.

batchMap()

The batchMap() function creates jobs to be submitted from a cluster. The first argument, fun, is the user-generated function and the additional arguments are for the user-generated function. The batchMap() function will produce a data frame of ids for each job as an output. The job ids are the numeric index of each element in a list/vector.

submitJobs()

The submitJobs() function will submit jobs to the cluster. It will need output from the batchMap() function as the first argument. Additionally, it will need the location og the registry (reg argument), from a stored R object, and a list of resources (resources argument).

getStatus()

The getStatus() function checks the status of your jobs and provides information of each jobs state.

killJobs()

The killJobs() function will kill all your jobs. This is equivalent to scancel.

loadResult()

The loadResult() function will load the results of each job. You will need to specify the id to load the results.

clearRegistry()

The clearRegistry() function will delete files in the registry to re-submit jobs.

removeRegistry()

The removeRegistry() function will delete the entire registry.

Simulation Example

To demonstrate how to use the batchtools package in R, we conduct several simulation studies showing how the estimates from the ordinary least squares estimator leads to unbiased results. We will simulate data from the following models:

\[ Y \sim N(20+30X,\ 3) \] \[ Y \sim N(5-8X_1+2X_2,\ 3) \]

\[ Y \sim N(5+4X_1-5X_2-3X_3,\ 3) \]

\[ Y \sim N(5+4X_1+-5X_2-3X_3+6X_4,\ 3) \] Each simulation scenario with have 500 data sets with 200 observations. The values for the predictor variables will be simulated by multivariate normal distributions. The mean vector for the predictors simulation are \((-2, 0)^T\), \((-2, 0)^T\), \((-2, 0, 2)^T\), \((-2, 0, 2 8)^T\). Each covariance for the predictor simulation will be an identity matrix.

Simulation Parameters

The simulation parameters will be stored in a list. Each element in the list will contain information of the about the simulation and the formula for the lm() function.

sim_list <- list(list(N = 500, # Number of Data sets
                      nobs = 200, # Number of observations
                      beta = c(20, 30), # beta parameters
                      xmeans = c(0), # Means for predictors
                      xsigs = diag(rep(1, 1)), # Variance for predictor
                      sigma = 3, # Variance for error term
                      formula = y ~ x), #Formula
                 list(N = 500, # Number of Data sets
                      nobs = 200, # Number of observations
                      beta = c(5, -8, 2), # beta parameters
                      xmeans = c(-2, 0), # Means for predictors
                      xsigs = diag(rep(1, 2)), # Variance for predictor
                      sigma = 3, # Variance for error term
                      formula = y ~ x.1 + x.2), #Formula
                 list(N = 500, # Number of Data sets
                      nobs = 200, # Number of observations
                      beta = c(5, 4, -5, -3), # beta parameters
                      xmeans = c(-2, 0, 2), # Means for predictors
                      xsigs = diag(rep(1, 3)), # Variance for predictor
                      sigma = 3, # Variance for error term
                      formula = y ~ x.1 + x.2 + x.3),  #Formula
                 list(N = 500, # Number of Data sets
                      nobs = 200, # Number of observations
                      beta = c(5, 4, -5, -3, 6), # beta parameters
                      xmeans = c(-2, 0, 2, 8), # Means for predictors
                      xsigs = diag(rep(1, 4)), # Variance for predictor
                      sigma = 3, # Variance for error term
                      formula = y ~ x.1 + x.2 + x.3 + x.4) #Formula
)

Simulation Functions

The function below generates 1 data set from a simulation scenario above and returns a data frame.

data_set_sim <- function(seed, nobs, beta, sigma, xmeans, xsigs){ # Simulates the data set
  set.seed(seed) # Sets a seed
  xrn <- rmvnorm(nobs, mean = xmeans, sigma = xsigs) # Simulates Predictors
  xped <- cbind(rep(1,nobs),xrn) # Creating Design Matrix
  y <- xped %*% beta + rnorm(nobs ,0, sigma) # Simulating Y
  df <- data.frame(x=xrn, y=y) # Creating Data Frame
  return(df)
}

The function needs the following arguments:

  • seed: the value to set for the random number generator
  • nobs: number of observations
  • beta: a vector specifying the true values for the regression coefficients (\(\beta_0\), \(\beta_1\), \(\beta_2\), \(\beta_3\))
  • sigma: the variance for the model above
  • xmeans: a vector of means used to generate the values for \(X_1\), \(X_2\), and \(X_3\)
  • xsigs: a matrix for the covariance for \(X_1\), \(X_2\), and \(X_3\)

The function below generates the data for a simulation scenario and returns a list of data (in a list) and the formula to assess the data for the lm() function

data_sim <- function(data){ # Simulates the data set
  df_list <- lapply(1:data$N, data_set_sim,
                    nobs = data$nobs, beta = data$beta, sigma = data$sigma,
                    xmeans = data$xmeans, xsigs = data$xsigs)
  return(list(df_list = df_list, formula = data$formula))
}

The function below takes the data generated from the data_sim() and fits a linear regression model. The function wraps around the lm() function and returns estimated regression coefficients.

Parallelization

Function

The function below process the data and implements the lm() to the data. This is the function that will be used in batchtools. First, the function obtains the number of data sets it will process. Second, it creates the lm_coef() function which applies the lm() function to the data3. Third, a parallel processor is applied to go through the data 4. Lastly, the results are converted to a matrix and returned as the output.

parallel_lm<-function(data){
  ll<-length(data$df_list)
  
  lm_coef <- function(formula, data){ # Applying a Ordinary Least Squares 
    lm_res <- lm(formula, data = data) # Find OLS Estimates
    return(as.vector(coef(lm_res)))# Obtaining 
  }
  
  results <- parallel::mclapply(data$df_list, lm_coef, formula = data$formula, # Applies lm_coef 
                                mc.cores = 8) # Using the parallel package to parallelize 
  
  mat<-matrix(unlist(results), nrow = ll, byrow = T)
  return(mat)
}

Execution

Modules

Load the slurm module into R.

module('load','slurm')

Registry

Create a registry and store it in an R object.

reg <- makeRegistry(file.dir=\myregdir\, conf.file=\.batchtools.conf.R\)

Resources

Create a list with the following elements and store it in an R object:

  • partition: The cluster partition to use ("stastdept")

  • walltime: The time to run the job in seconds (120)

  • ntasks: The number of tasks to complete (1)

  • ncpus: The number of cpus to complete the task (8)

  • memory: How much memory is being used in MB (1024)

res <- list(partition=\statsdept\, walltime=120, ntasks=1, ncpus=8, memory=1024)

Batch Jobs Prep

Use to batchMap() function to create the jobs that need to be submitted into the cluster and store it an R object.

submission_id <- batchMap(parallel_lm, data = standard_data)

Submitting Jobs

Submit the jobs using the submitJobs() and store it in an R object.

done <- submitJobs(submission_id, reg=reg, resources=res)

Getting Job Status

Use the getStatus() function to check on the status of your jobs.

getStatus()

Results

Once your jobs are completed, you can check the results. First you will need to extract the results from the registry and store it in an R object.

parallel_results <- lapply(1:length(standard_data), loadResult) #obtains the results of each job adds them as an element in a list

Now use the colMeans() function to see if the simulation study worked.

lapply(parallel_results, colMeans)

Notes

Error: Reached Submission Limit or Resources Limit

If You receive an error about reaching limits, do not worry about it. The slurm system will take care of this. Type the following below to cancel all your jobs and try again.

squeue --user $USER --noheader --format '%i' | xargs scancel

  1. The cluster has a scheduling system implemented.↩︎

  2. Fun Fact: This package is from HPCC↩︎

  3. User created functions needs to be created within the function to be loaded in batchtools↩︎

  4. For more information about parallel processing with the parallel package, visit here↩︎

LS0tCnRpdGxlOiAiUGFyYWxsZWwgQ29tcHV0aW5nIGluIFIgd2l0aCBgYmF0Y2h0b29sc2AiCmF1dGhvcjogIklzYWFjIFF1aW50YW5pbGxhIFNhbGluYXMiCmRlc2NyaXB0aW9uOiBUdXRvcmlhbCBmb3IgcGFyYWxsZWwgcHJvY2Vzc2luZyB1c2luZyB0aGUgUiBwYWNrYWdlIGJhdGNodG9vbHMuCmRhdGU6ICIxMi0yMC0yMDIwIgpvdXRwdXQ6IAogIGh0bWxfbm90ZWJvb2s6CiAgICB0b2M6IHRydWUKICAgIHRvY19mbG9hdDogdHJ1ZQotLS0KCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFfQprbml0cjo6b3B0c19jaHVuayRzZXQoZXZhbCA9IEZBTFNFKQpgYGAKCiMgSW50cm9kdWN0aW9uCgpSIGlzIGRlc2lnbmVkIHRvIG9ubHkgdXNlIG9uZSBjcHUgKG9yIGNvcmUpIHdoZW4gcnVubmluZyB0YXNrcy4gSG93ZXZlciwgeW91IG1heSBoYXZlIGFjY2VzcyB0byBhIGNvbXB1dGVyIGNsdXN0ZXJbXjFdIHRoYXQgYWxsb3dzIHlvdSB0byBhY2Nlc3MgbW9yZSBSQU0gYW5kIGNwdXMuIFRoZSB1c2Ugb2YgbW9yZSB0aGFuIG9uZSBjcHUgaXMga25vd24gYXMgcGFyYWxsZWwgY29tcHV0aW5nIGluIFIuIFRoZSBnb2FsIG9mIHRoaXMgdHV0b3JpYWwgaXMgdG8gcHJvdmlkZSB0aGUgYmFzaWNzIG9mIHVzaW5nIHRoZSBgYmF0Y2h0b29sc2AgcGFja2FnZSBhbmQgdXRpbGl6aW5nIG1vcmUgY29yZXMgaW4gYSBjbHVzdGVyLgoKW14xXTogVGhlIGNsdXN0ZXIgaGFzIGEgc2NoZWR1bGluZyBzeXN0ZW0gaW1wbGVtZW50ZWQuCgpUaGlzIHR1dG9yaWFsIGlzIGJ1aWx0IG9mZiB0aGUgaW5mb3JtYXRpb24gcHJvdmlkZWQgYnkgVUNSJ3MgSGlnaCBQZXJmb3JtYW5jZSBDb21wdXRpbmcgQ2VudGVyIHR1dG9yaWFsIG9mIGBiYXRjaHRvb2xzYCBwYWNrYWdlLiBUaGlzIHR1dG9yaWFsIHVzZXMgYSBzaW11bGF0aW9uIHN0dWR5IHRvIHNob3cgeW91IHRoZSBwb3dlciBvZiBgYmF0Y2h0b29sc2AuIEZvciBtb3JlIGluZm9ybWF0aW9uIHZpc2l0IHRoZWlyIGhlbHAgZG9jdW1lbnRhdGlvbiBmb3IgW2JhdGNodG9vbHNdKGh0dHBzOi8vaHBjYy51Y3IuZWR1L21hbnVhbHNfbGludXgtY2x1c3Rlcl9wYXJhbGxlbFIuaHRtbCkuCgpUaGlzIHR1dG9yaWFsIGlzIG1lYW50IHRvIGJlIHJ1biBvbiB0aGUgW0hQQ0NdKGh0dHBzOi8vaHBjYy51Y3IuZWR1LykgY2x1c3RlciBhdCBVQ1IuIFlvdSB3aWxsIG5lZWQgYW4gYWNjb3VudCB0byB0aGUgSFBDQyBjbHVzdGVyLiBJZiB5b3UgYXJlIGEgZ3JhZHVhdGUgc3R1ZGVudCBpbiBVQ1IncyBbU3RhdGlzdGljcyBEZXBhcnRtZW50XShodHRwczovL3N0YXRpc3RpY3MudWNyLmVkdS8pLCBjb250YWN0IHRoZSBVQ1IgU3RhdGlzdGljcyBHcmFkdWF0ZSBTdHVkZW50IEFzc29jaWF0aW9uIHRvIGdhaW4gYWNjZXNzLgoKVGhpcyB0dXRvcmlhbCBjb25kdWN0cyBkaWZmZXJlbnQgc2ltdWxhdGlvbiBzY2VuYXJpb3MgdG8gYmUgc3VibWl0dGVkIGFzIGpvYnMuIEl0IHJlcXVpcmVzIGFuIGFtb3VudCBvZiBzZXQgdXAgdG8gYmUgZWZmZWN0aXZlLiBUaGVyZSBpcyBhbiBhbiBSIHNjcmlwdCB0aGF0IHlvdSBjYW4gdXNlIHRoYXQgbWF5IHByb3ZpZGUgYmV0dGVyIGluc2lnaHQgb24gdXNpbmcgYGJhdGNodG9vbHNgLiBZb3UgY2FuIGFjY2VzcyBSIHNjcmlwdCBbaGVyZV0oYmF0Y2h0b29scy5SKQoKIyBGaWxlcywgRnVuY3Rpb25zIGFuZCBQYWNrYWdlcwoKIyMgRmlsZXMKCkJlZm9yZSB5b3UgYmVnaW4sIGRvd25sb2FkIHRoZXNlIGZpbGVzIHRvIHRoZSBkaXJlY3RvcnkgeW91IGFyZSB3b3JraW5nIHdpdGguIFRoZSBgc2x1cm0udG1wbGAgZmlsZSBwcm92aWRlcyBpbmZvcm1hdGlvbiB0byB0aGUgc2x1cm0gc2NoZWR1bGVyLiBUaGUgYC5iYXRjaHRvb2xzLmNvbmYuUmAgZmlsZSB0ZWxscyBSIHRvIGNyZWF0ZSBhIGNsdXN0ZXIgZnVuY3Rpb24gZm9yIHNsdXJtLiBUaGUgcGVyaW9kIGluIGZyb250IG9mIGAuYmF0Y2h0b29scy5jb25mLlJgIHdpbGwgaGlkZSB0aGUgZmlsZSBpbiB5b3VyIGRpcmVjdG9yeS4gSXQgaXMgdGhlcmUsIGl0IGlzIGp1c3Qgbm90IGRpc3BsYXllZC4KCmBgYHtyfQpkb3dubG9hZC5maWxlKCJodHRwczovL2hwY2MudWNyLmVkdS9fc3VwcG9ydF9kb2NzL3R1dG9yaWFscy9zbHVybS50bXBsIiwgInNsdXJtLnRtcGwiKQpkb3dubG9hZC5maWxlKCJodHRwczovL2hwY2MudWNyLmVkdS9fc3VwcG9ydF9kb2NzL3R1dG9yaWFscy8uYmF0Y2h0b29scy5jb25mLlIiLCAiLmJhdGNodG9vbHMuY29uZi5SIikKYGBgCgojIyBQYWNrYWdlcwoKWW91IHdpbGwgbmVlZCB0byB1c2UgMiBSIHBhY2thZ2VzOiBgUmVudk1vZHVsZWAgYW5kIGBiYXRjaHRvb2xzYC4gVGhlIGBSZW52TW9kdWxlYFteMl0gcGFja2FnZSBwcm92aWRlcyB0aGUgdG9vbHMgdG8gaW50ZXJhY3Qgd2l0aCBtb2R1bGVzIGluIHRoZSBjbHVzdGVyLiBUaGUgYGJhdGNodG9vbHNgIHBhY2thZ2UgcHJvdmlkZXMgdGhlIHRvb2xzIHRvIHBhcmFsbGVsaXplIHlvdXIgY29kZS4gQm90aCBhcmUgbmVlZGVkLiBGb3IgdGhpcyB0dXRvcmlhbCwgeW91IHdpbGwgYWxzbyBuZWVkIHRvIGhhdmUgdGhlIGBtdnRybm9ybWAgcGFja2FnZSBpbnN0YWxsZWQuCgpbXjJdOiBGdW4gRmFjdDogVGhpcyBwYWNrYWdlIGlzIGZyb20gSFBDQwoKYGBge3J9CmxpYnJhcnkoUmVudk1vZHVsZSkKbGlicmFyeShiYXRjaHRvb2xzKQpsaWJyYXJ5KG12dG5vcm0pCmBgYAoKIyMgRnVuY3Rpb25zCgojIyMgYG1vZHVsZSgpYAoKVGhlIGBtb2R1bGUoKWAgZnVuY3Rpb24gbG9hZHMgZGlmZmVyZW50IG1vZHVsZXMgaW50byB0aGUgUiBlbnZpcm9ubWVudC4KCiMjIyBgbWFrZVJlZ2lzdHJ5KClgCgpUaGUgYG1ha2VSZWdpc3RyeSgpYCBmdW5jdGlvbiBjcmVhdGVzIGEgcmVnaXN0cnkgZm9yIGBiYXRjaHRvb2xzYC4gWW91IGNhbiB0aGluayBvZiB0aGUgcmVnaXN0cnkgYXMgdGhlIGNvbW11bmljYXRpb24gY2VudGVyIGZvciBSLCB5b3VyIGZ1bmN0aW9ucywgYW5kIHRoZSBjbHVzdGVyLiBJdCB3aWxsIHN0b3JlIGV2ZXJ5dGhpbmcgaW4gYSBkaXJlY3RvcnkuIFRoZSBgbWFrZVJlZ2lzdHJ5KClgIGZ1bmN0aW9uIG5lZWRzIHRoZSBhcmd1bWVudCBgZmlsZS5kaXJgIGFyZ3VtZW50LCB0aGUgbG9jYXRpb24gdG8gc3RvcmUgdGhlIHJlZ2lzdHJ5LCBhbmQgYGNvbmYuZmlsZT0uYmF0Y2h0b29scy5jb25mLlJgLiBTdG9yZSB0aGUgb3V0cHV0IGZyb20gdGhlIGZ1bmN0aW9uIGludG8gYW4gUiBvYmplY3QuCgojIyMgYGJhdGNoTWFwKClgCgpUaGUgYGJhdGNoTWFwKClgIGZ1bmN0aW9uIGNyZWF0ZXMgam9icyB0byBiZSBzdWJtaXR0ZWQgZnJvbSBhIGNsdXN0ZXIuIFRoZSBmaXJzdCBhcmd1bWVudCwgYGZ1bmAsIGlzIHRoZSB1c2VyLWdlbmVyYXRlZCBmdW5jdGlvbiBhbmQgdGhlIGFkZGl0aW9uYWwgYXJndW1lbnRzIGFyZSBmb3IgdGhlIHVzZXItZ2VuZXJhdGVkIGZ1bmN0aW9uLiBUaGUgYGJhdGNoTWFwKClgIGZ1bmN0aW9uIHdpbGwgcHJvZHVjZSBhIGRhdGEgZnJhbWUgb2YgaWRzIGZvciBlYWNoIGpvYiBhcyBhbiBvdXRwdXQuIFRoZSBqb2IgaWRzIGFyZSB0aGUgbnVtZXJpYyBpbmRleCBvZiBlYWNoIGVsZW1lbnQgaW4gYSBsaXN0L3ZlY3Rvci4KCiMjIyBgc3VibWl0Sm9icygpYAoKVGhlIGBzdWJtaXRKb2JzKClgIGZ1bmN0aW9uIHdpbGwgc3VibWl0IGpvYnMgdG8gdGhlIGNsdXN0ZXIuIEl0IHdpbGwgbmVlZCBvdXRwdXQgZnJvbSB0aGUgYGJhdGNoTWFwKClgIGZ1bmN0aW9uIGFzIHRoZSBmaXJzdCBhcmd1bWVudC4gQWRkaXRpb25hbGx5LCBpdCB3aWxsIG5lZWQgdGhlIGxvY2F0aW9uIG9nIHRoZSByZWdpc3RyeSAoYHJlZ2AgYXJndW1lbnQpLCBmcm9tIGEgc3RvcmVkIFIgb2JqZWN0LCBhbmQgYSBsaXN0IG9mIHJlc291cmNlcyAoYHJlc291cmNlc2AgYXJndW1lbnQpLgoKIyMjIGBnZXRTdGF0dXMoKWAKClRoZSBgZ2V0U3RhdHVzKClgIGZ1bmN0aW9uIGNoZWNrcyB0aGUgc3RhdHVzIG9mIHlvdXIgam9icyBhbmQgcHJvdmlkZXMgaW5mb3JtYXRpb24gb2YgZWFjaCBqb2JzIHN0YXRlLgoKIyMjIGBraWxsSm9icygpYAoKVGhlIGBraWxsSm9icygpYCBmdW5jdGlvbiB3aWxsIGtpbGwgYWxsIHlvdXIgam9icy4gVGhpcyBpcyBlcXVpdmFsZW50IHRvIGBzY2FuY2VsYC4KCiMjIyBgbG9hZFJlc3VsdCgpYAoKVGhlIGBsb2FkUmVzdWx0KClgIGZ1bmN0aW9uIHdpbGwgbG9hZCB0aGUgcmVzdWx0cyBvZiBlYWNoIGpvYi4gWW91IHdpbGwgbmVlZCB0byBzcGVjaWZ5IHRoZSBpZCB0byBsb2FkIHRoZSByZXN1bHRzLgoKIyMjIGBjbGVhclJlZ2lzdHJ5KClgCgpUaGUgYGNsZWFyUmVnaXN0cnkoKWAgZnVuY3Rpb24gd2lsbCBkZWxldGUgZmlsZXMgaW4gdGhlIHJlZ2lzdHJ5IHRvIHJlLXN1Ym1pdCBqb2JzLgoKIyMjIGByZW1vdmVSZWdpc3RyeSgpYAoKVGhlIGByZW1vdmVSZWdpc3RyeSgpYCBmdW5jdGlvbiB3aWxsIGRlbGV0ZSB0aGUgZW50aXJlIHJlZ2lzdHJ5LgoKIyBTaW11bGF0aW9uIEV4YW1wbGUKClRvIGRlbW9uc3RyYXRlIGhvdyB0byB1c2UgdGhlIGBiYXRjaHRvb2xzYCBwYWNrYWdlIGluIFIsIHdlIGNvbmR1Y3Qgc2V2ZXJhbCBzaW11bGF0aW9uIHN0dWRpZXMgc2hvd2luZyBob3cgdGhlIGVzdGltYXRlcyBmcm9tIHRoZSBvcmRpbmFyeSBsZWFzdCBzcXVhcmVzIGVzdGltYXRvciBsZWFkcyB0byB1bmJpYXNlZCByZXN1bHRzLiBXZSB3aWxsIHNpbXVsYXRlIGRhdGEgZnJvbSB0aGUgZm9sbG93aW5nIG1vZGVsczoKCiQkClkgXHNpbSBOKDIwKzMwWCxcIDMpCiQkICQkClkgXHNpbSBOKDUtOFhfMSsyWF8yLFwgMykKJCQKCiQkClkgXHNpbSBOKDUrNFhfMS01WF8yLTNYXzMsXCAzKQokJAoKJCQKWSBcc2ltIE4oNSs0WF8xKy01WF8yLTNYXzMrNlhfNCxcIDMpCiQkIEVhY2ggc2ltdWxhdGlvbiBzY2VuYXJpbyB3aXRoIGhhdmUgNTAwIGRhdGEgc2V0cyB3aXRoIDIwMCBvYnNlcnZhdGlvbnMuIFRoZSB2YWx1ZXMgZm9yIHRoZSBwcmVkaWN0b3IgdmFyaWFibGVzIHdpbGwgYmUgc2ltdWxhdGVkIGJ5IG11bHRpdmFyaWF0ZSBub3JtYWwgZGlzdHJpYnV0aW9ucy4gVGhlIG1lYW4gdmVjdG9yIGZvciB0aGUgcHJlZGljdG9ycyBzaW11bGF0aW9uIGFyZSAkKC0yLCAwKV5UJCwgJCgtMiwgMCleVCQsICQoLTIsIDAsIDIpXlQkLCAkKC0yLCAwLCAyIDgpXlQkLiBFYWNoIGNvdmFyaWFuY2UgZm9yIHRoZSBwcmVkaWN0b3Igc2ltdWxhdGlvbiB3aWxsIGJlIGFuIGlkZW50aXR5IG1hdHJpeC4KCiMjIFNpbXVsYXRpb24gUGFyYW1ldGVycwoKVGhlIHNpbXVsYXRpb24gcGFyYW1ldGVycyB3aWxsIGJlIHN0b3JlZCBpbiBhIGxpc3QuIEVhY2ggZWxlbWVudCBpbiB0aGUgbGlzdCB3aWxsIGNvbnRhaW4gaW5mb3JtYXRpb24gb2YgdGhlIGFib3V0IHRoZSBzaW11bGF0aW9uIGFuZCB0aGUgZm9ybXVsYSBmb3IgdGhlIGBsbSgpYCBmdW5jdGlvbi4KCmBgYHtyfQpzaW1fbGlzdCA8LSBsaXN0KGxpc3QoTiA9IDUwMCwgIyBOdW1iZXIgb2YgRGF0YSBzZXRzCiAgICAgICAgICAgICAgICAgICAgICBub2JzID0gMjAwLCAjIE51bWJlciBvZiBvYnNlcnZhdGlvbnMKICAgICAgICAgICAgICAgICAgICAgIGJldGEgPSBjKDIwLCAzMCksICMgYmV0YSBwYXJhbWV0ZXJzCiAgICAgICAgICAgICAgICAgICAgICB4bWVhbnMgPSBjKDApLCAjIE1lYW5zIGZvciBwcmVkaWN0b3JzCiAgICAgICAgICAgICAgICAgICAgICB4c2lncyA9IGRpYWcocmVwKDEsIDEpKSwgIyBWYXJpYW5jZSBmb3IgcHJlZGljdG9yCiAgICAgICAgICAgICAgICAgICAgICBzaWdtYSA9IDMsICMgVmFyaWFuY2UgZm9yIGVycm9yIHRlcm0KICAgICAgICAgICAgICAgICAgICAgIGZvcm11bGEgPSB5IH4geCksICNGb3JtdWxhCiAgICAgICAgICAgICAgICAgbGlzdChOID0gNTAwLCAjIE51bWJlciBvZiBEYXRhIHNldHMKICAgICAgICAgICAgICAgICAgICAgIG5vYnMgPSAyMDAsICMgTnVtYmVyIG9mIG9ic2VydmF0aW9ucwogICAgICAgICAgICAgICAgICAgICAgYmV0YSA9IGMoNSwgLTgsIDIpLCAjIGJldGEgcGFyYW1ldGVycwogICAgICAgICAgICAgICAgICAgICAgeG1lYW5zID0gYygtMiwgMCksICMgTWVhbnMgZm9yIHByZWRpY3RvcnMKICAgICAgICAgICAgICAgICAgICAgIHhzaWdzID0gZGlhZyhyZXAoMSwgMikpLCAjIFZhcmlhbmNlIGZvciBwcmVkaWN0b3IKICAgICAgICAgICAgICAgICAgICAgIHNpZ21hID0gMywgIyBWYXJpYW5jZSBmb3IgZXJyb3IgdGVybQogICAgICAgICAgICAgICAgICAgICAgZm9ybXVsYSA9IHkgfiB4LjEgKyB4LjIpLCAjRm9ybXVsYQogICAgICAgICAgICAgICAgIGxpc3QoTiA9IDUwMCwgIyBOdW1iZXIgb2YgRGF0YSBzZXRzCiAgICAgICAgICAgICAgICAgICAgICBub2JzID0gMjAwLCAjIE51bWJlciBvZiBvYnNlcnZhdGlvbnMKICAgICAgICAgICAgICAgICAgICAgIGJldGEgPSBjKDUsIDQsIC01LCAtMyksICMgYmV0YSBwYXJhbWV0ZXJzCiAgICAgICAgICAgICAgICAgICAgICB4bWVhbnMgPSBjKC0yLCAwLCAyKSwgIyBNZWFucyBmb3IgcHJlZGljdG9ycwogICAgICAgICAgICAgICAgICAgICAgeHNpZ3MgPSBkaWFnKHJlcCgxLCAzKSksICMgVmFyaWFuY2UgZm9yIHByZWRpY3RvcgogICAgICAgICAgICAgICAgICAgICAgc2lnbWEgPSAzLCAjIFZhcmlhbmNlIGZvciBlcnJvciB0ZXJtCiAgICAgICAgICAgICAgICAgICAgICBmb3JtdWxhID0geSB+IHguMSArIHguMiArIHguMyksICAjRm9ybXVsYQogICAgICAgICAgICAgICAgIGxpc3QoTiA9IDUwMCwgIyBOdW1iZXIgb2YgRGF0YSBzZXRzCiAgICAgICAgICAgICAgICAgICAgICBub2JzID0gMjAwLCAjIE51bWJlciBvZiBvYnNlcnZhdGlvbnMKICAgICAgICAgICAgICAgICAgICAgIGJldGEgPSBjKDUsIDQsIC01LCAtMywgNiksICMgYmV0YSBwYXJhbWV0ZXJzCiAgICAgICAgICAgICAgICAgICAgICB4bWVhbnMgPSBjKC0yLCAwLCAyLCA4KSwgIyBNZWFucyBmb3IgcHJlZGljdG9ycwogICAgICAgICAgICAgICAgICAgICAgeHNpZ3MgPSBkaWFnKHJlcCgxLCA0KSksICMgVmFyaWFuY2UgZm9yIHByZWRpY3RvcgogICAgICAgICAgICAgICAgICAgICAgc2lnbWEgPSAzLCAjIFZhcmlhbmNlIGZvciBlcnJvciB0ZXJtCiAgICAgICAgICAgICAgICAgICAgICBmb3JtdWxhID0geSB+IHguMSArIHguMiArIHguMyArIHguNCkgI0Zvcm11bGEKKQpgYGAKCiMjIFNpbXVsYXRpb24gRnVuY3Rpb25zCgpUaGUgZnVuY3Rpb24gYmVsb3cgZ2VuZXJhdGVzIDEgZGF0YSBzZXQgZnJvbSBhIHNpbXVsYXRpb24gc2NlbmFyaW8gYWJvdmUgYW5kIHJldHVybnMgYSBkYXRhIGZyYW1lLgoKYGBge3J9CmRhdGFfc2V0X3NpbSA8LSBmdW5jdGlvbihzZWVkLCBub2JzLCBiZXRhLCBzaWdtYSwgeG1lYW5zLCB4c2lncyl7ICMgU2ltdWxhdGVzIHRoZSBkYXRhIHNldAogIHNldC5zZWVkKHNlZWQpICMgU2V0cyBhIHNlZWQKICB4cm4gPC0gcm12bm9ybShub2JzLCBtZWFuID0geG1lYW5zLCBzaWdtYSA9IHhzaWdzKSAjIFNpbXVsYXRlcyBQcmVkaWN0b3JzCiAgeHBlZCA8LSBjYmluZChyZXAoMSxub2JzKSx4cm4pICMgQ3JlYXRpbmcgRGVzaWduIE1hdHJpeAogIHkgPC0geHBlZCAlKiUgYmV0YSArIHJub3JtKG5vYnMgLDAsIHNpZ21hKSAjIFNpbXVsYXRpbmcgWQogIGRmIDwtIGRhdGEuZnJhbWUoeD14cm4sIHk9eSkgIyBDcmVhdGluZyBEYXRhIEZyYW1lCiAgcmV0dXJuKGRmKQp9CmBgYAoKVGhlIGZ1bmN0aW9uIG5lZWRzIHRoZSBmb2xsb3dpbmcgYXJndW1lbnRzOgoKLSAgIGBzZWVkYDogdGhlIHZhbHVlIHRvIHNldCBmb3IgdGhlIHJhbmRvbSBudW1iZXIgZ2VuZXJhdG9yCi0gICBgbm9ic2A6IG51bWJlciBvZiBvYnNlcnZhdGlvbnMKLSAgIGBiZXRhYDogYSB2ZWN0b3Igc3BlY2lmeWluZyB0aGUgdHJ1ZSB2YWx1ZXMgZm9yIHRoZSByZWdyZXNzaW9uIGNvZWZmaWNpZW50cyAoJFxiZXRhXzAkLCAkXGJldGFfMSQsICRcYmV0YV8yJCwgJFxiZXRhXzMkKQotICAgYHNpZ21hYDogdGhlIHZhcmlhbmNlIGZvciB0aGUgbW9kZWwgYWJvdmUKLSAgIGB4bWVhbnNgOiBhIHZlY3RvciBvZiBtZWFucyB1c2VkIHRvIGdlbmVyYXRlIHRoZSB2YWx1ZXMgZm9yICRYXzEkLCAkWF8yJCwgYW5kICRYXzMkCi0gICBgeHNpZ3NgOiBhIG1hdHJpeCBmb3IgdGhlIGNvdmFyaWFuY2UgZm9yICRYXzEkLCAkWF8yJCwgYW5kICRYXzMkCgpUaGUgZnVuY3Rpb24gYmVsb3cgZ2VuZXJhdGVzIHRoZSBkYXRhIGZvciBhIHNpbXVsYXRpb24gc2NlbmFyaW8gYW5kIHJldHVybnMgYSBsaXN0IG9mIGRhdGEgKGluIGEgbGlzdCkgYW5kIHRoZSBmb3JtdWxhIHRvIGFzc2VzcyB0aGUgZGF0YSBmb3IgdGhlIGBsbSgpYCBmdW5jdGlvbgoKYGBge3J9CmRhdGFfc2ltIDwtIGZ1bmN0aW9uKGRhdGEpeyAjIFNpbXVsYXRlcyB0aGUgZGF0YSBzZXQKICBkZl9saXN0IDwtIGxhcHBseSgxOmRhdGEkTiwgZGF0YV9zZXRfc2ltLAogICAgICAgICAgICAgICAgICAgIG5vYnMgPSBkYXRhJG5vYnMsIGJldGEgPSBkYXRhJGJldGEsIHNpZ21hID0gZGF0YSRzaWdtYSwKICAgICAgICAgICAgICAgICAgICB4bWVhbnMgPSBkYXRhJHhtZWFucywgeHNpZ3MgPSBkYXRhJHhzaWdzKQogIHJldHVybihsaXN0KGRmX2xpc3QgPSBkZl9saXN0LCBmb3JtdWxhID0gZGF0YSRmb3JtdWxhKSkKfQoKYGBgCgpUaGUgZnVuY3Rpb24gYmVsb3cgdGFrZXMgdGhlIGRhdGEgZ2VuZXJhdGVkIGZyb20gdGhlIGBkYXRhX3NpbSgpYCBhbmQgZml0cyBhIGxpbmVhciByZWdyZXNzaW9uIG1vZGVsLiBUaGUgZnVuY3Rpb24gd3JhcHMgYXJvdW5kIHRoZSBgbG0oKWAgZnVuY3Rpb24gYW5kIHJldHVybnMgZXN0aW1hdGVkIHJlZ3Jlc3Npb24gY29lZmZpY2llbnRzLgoKIyBQYXJhbGxlbGl6YXRpb24KCiMjIEZ1bmN0aW9uCgpUaGUgZnVuY3Rpb24gYmVsb3cgcHJvY2VzcyB0aGUgZGF0YSBhbmQgaW1wbGVtZW50cyB0aGUgYGxtKClgIHRvIHRoZSBkYXRhLiBUaGlzIGlzIHRoZSBmdW5jdGlvbiB0aGF0IHdpbGwgYmUgdXNlZCBpbiBgYmF0Y2h0b29sc2AuIEZpcnN0LCB0aGUgZnVuY3Rpb24gb2J0YWlucyB0aGUgbnVtYmVyIG9mIGRhdGEgc2V0cyBpdCB3aWxsIHByb2Nlc3MuIFNlY29uZCwgaXQgY3JlYXRlcyB0aGUgYGxtX2NvZWYoKWAgZnVuY3Rpb24gd2hpY2ggYXBwbGllcyB0aGUgYGxtKClgIGZ1bmN0aW9uIHRvIHRoZSBkYXRhW14zXS4gVGhpcmQsIGEgcGFyYWxsZWwgcHJvY2Vzc29yIGlzIGFwcGxpZWQgdG8gZ28gdGhyb3VnaCB0aGUgZGF0YSBbXjRdLiBMYXN0bHksIHRoZSByZXN1bHRzIGFyZSBjb252ZXJ0ZWQgdG8gYSBtYXRyaXggYW5kIHJldHVybmVkIGFzIHRoZSBvdXRwdXQuCgpbXjNdOiBVc2VyIGNyZWF0ZWQgZnVuY3Rpb25zIG5lZWRzIHRvIGJlIGNyZWF0ZWQgd2l0aGluIHRoZSBmdW5jdGlvbiB0byBiZSBsb2FkZWQgaW4gYGJhdGNodG9vbHNgCgpbXjRdOiBGb3IgbW9yZSBpbmZvcm1hdGlvbiBhYm91dCBwYXJhbGxlbCBwcm9jZXNzaW5nIHdpdGggdGhlIGBwYXJhbGxlbGAgcGFja2FnZSwgdmlzaXQgW2hlcmVdKHBhcmFsbGVsX25vdGVib29rLm5iLmh0bWwpCgpgYGB7cn0KcGFyYWxsZWxfbG08LWZ1bmN0aW9uKGRhdGEpewogIGxsPC1sZW5ndGgoZGF0YSRkZl9saXN0KQogIAogIGxtX2NvZWYgPC0gZnVuY3Rpb24oZm9ybXVsYSwgZGF0YSl7ICMgQXBwbHlpbmcgYSBPcmRpbmFyeSBMZWFzdCBTcXVhcmVzIAogICAgbG1fcmVzIDwtIGxtKGZvcm11bGEsIGRhdGEgPSBkYXRhKSAjIEZpbmQgT0xTIEVzdGltYXRlcwogICAgcmV0dXJuKGFzLnZlY3Rvcihjb2VmKGxtX3JlcykpKSMgT2J0YWluaW5nIAogIH0KICAKICByZXN1bHRzIDwtIHBhcmFsbGVsOjptY2xhcHBseShkYXRhJGRmX2xpc3QsIGxtX2NvZWYsIGZvcm11bGEgPSBkYXRhJGZvcm11bGEsICMgQXBwbGllcyBsbV9jb2VmIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1jLmNvcmVzID0gOCkgIyBVc2luZyB0aGUgcGFyYWxsZWwgcGFja2FnZSB0byBwYXJhbGxlbGl6ZSAKICAKICBtYXQ8LW1hdHJpeCh1bmxpc3QocmVzdWx0cyksIG5yb3cgPSBsbCwgYnlyb3cgPSBUKQogIHJldHVybihtYXQpCn0KYGBgCgojIyBFeGVjdXRpb24KCiMjIyBNb2R1bGVzCgpMb2FkIHRoZSBzbHVybSBtb2R1bGUgaW50byBSLgoKYGBge3J9Cm1vZHVsZSgnbG9hZCcsJ3NsdXJtJykKYGBgCgojIyMgUmVnaXN0cnkKCkNyZWF0ZSBhIHJlZ2lzdHJ5IGFuZCBzdG9yZSBpdCBpbiBhbiBSIG9iamVjdC4KCmBgYHtyfQpyZWcgPC0gbWFrZVJlZ2lzdHJ5KGZpbGUuZGlyPSJteXJlZ2RpciIsIGNvbmYuZmlsZT0iLmJhdGNodG9vbHMuY29uZi5SIikKYGBgCgojIyMgUmVzb3VyY2VzCgpDcmVhdGUgYSBsaXN0IHdpdGggdGhlIGZvbGxvd2luZyBlbGVtZW50cyBhbmQgc3RvcmUgaXQgaW4gYW4gUiBvYmplY3Q6CgotICAgYHBhcnRpdGlvbmA6IFRoZSBjbHVzdGVyIHBhcnRpdGlvbiB0byB1c2UgKGAic3Rhc3RkZXB0ImApCgotICAgYHdhbGx0aW1lYDogVGhlIHRpbWUgdG8gcnVuIHRoZSBqb2IgaW4gc2Vjb25kcyAoYDEyMGApCgotICAgYG50YXNrc2A6IFRoZSBudW1iZXIgb2YgdGFza3MgdG8gY29tcGxldGUgKGAxYCkKCi0gICBgbmNwdXNgOiBUaGUgbnVtYmVyIG9mIGNwdXMgdG8gY29tcGxldGUgdGhlIHRhc2sgKGA4YCkKCi0gICBgbWVtb3J5YDogSG93IG11Y2ggbWVtb3J5IGlzIGJlaW5nIHVzZWQgaW4gTUIgKGAxMDI0YCkKCmBgYHtyfQpyZXMgPC0gbGlzdChwYXJ0aXRpb249InN0YXRzZGVwdCIsIHdhbGx0aW1lPTEyMCwgbnRhc2tzPTEsIG5jcHVzPTgsIG1lbW9yeT0xMDI0KQpgYGAKCiMjIyBCYXRjaCBKb2JzIFByZXAKClVzZSB0byBgYmF0Y2hNYXAoKWAgZnVuY3Rpb24gdG8gY3JlYXRlIHRoZSBqb2JzIHRoYXQgbmVlZCB0byBiZSBzdWJtaXR0ZWQgaW50byB0aGUgY2x1c3RlciBhbmQgc3RvcmUgaXQgYW4gUiBvYmplY3QuCgpgYGB7cn0Kc3VibWlzc2lvbl9pZCA8LSBiYXRjaE1hcChwYXJhbGxlbF9sbSwgZGF0YSA9IHN0YW5kYXJkX2RhdGEpCmBgYAoKIyMjIFN1Ym1pdHRpbmcgSm9icwoKU3VibWl0IHRoZSBqb2JzIHVzaW5nIHRoZSBgc3VibWl0Sm9icygpYCBhbmQgc3RvcmUgaXQgaW4gYW4gUiBvYmplY3QuCgpgYGB7cn0KZG9uZSA8LSBzdWJtaXRKb2JzKHN1Ym1pc3Npb25faWQsIHJlZz1yZWcsIHJlc291cmNlcz1yZXMpCmBgYAoKIyMjIEdldHRpbmcgSm9iIFN0YXR1cwoKVXNlIHRoZSBgZ2V0U3RhdHVzKClgIGZ1bmN0aW9uIHRvIGNoZWNrIG9uIHRoZSBzdGF0dXMgb2YgeW91ciBqb2JzLgoKYGBge3J9CmdldFN0YXR1cygpCmBgYAoKIyBSZXN1bHRzCgpPbmNlIHlvdXIgam9icyBhcmUgY29tcGxldGVkLCB5b3UgY2FuIGNoZWNrIHRoZSByZXN1bHRzLiBGaXJzdCB5b3Ugd2lsbCBuZWVkIHRvIGV4dHJhY3QgdGhlIHJlc3VsdHMgZnJvbSB0aGUgcmVnaXN0cnkgYW5kIHN0b3JlIGl0IGluIGFuIFIgb2JqZWN0LgoKYGBge3J9CnBhcmFsbGVsX3Jlc3VsdHMgPC0gbGFwcGx5KDE6bGVuZ3RoKHN0YW5kYXJkX2RhdGEpLCBsb2FkUmVzdWx0KSAjb2J0YWlucyB0aGUgcmVzdWx0cyBvZiBlYWNoIGpvYiBhZGRzIHRoZW0gYXMgYW4gZWxlbWVudCBpbiBhIGxpc3QKYGBgCgpOb3cgdXNlIHRoZSBgY29sTWVhbnMoKWAgZnVuY3Rpb24gdG8gc2VlIGlmIHRoZSBzaW11bGF0aW9uIHN0dWR5IHdvcmtlZC4KCmBgYHtyfQpsYXBwbHkocGFyYWxsZWxfcmVzdWx0cywgY29sTWVhbnMpCmBgYAoKIyBOb3RlcwoKIyMgRXJyb3I6IFJlYWNoZWQgU3VibWlzc2lvbiBMaW1pdCBvciBSZXNvdXJjZXMgTGltaXQKCklmIFlvdSByZWNlaXZlIGFuIGVycm9yIGFib3V0IHJlYWNoaW5nIGxpbWl0cywgZG8gbm90IHdvcnJ5IGFib3V0IGl0LiBUaGUgc2x1cm0gc3lzdGVtIHdpbGwgdGFrZSBjYXJlIG9mIHRoaXMuIFR5cGUgdGhlIGZvbGxvd2luZyBiZWxvdyB0byBjYW5jZWwgYWxsIHlvdXIgam9icyBhbmQgdHJ5IGFnYWluLgoKYGBge2Jhc2h9CnNxdWV1ZSAtLXVzZXIgJFVTRVIgLS1ub2hlYWRlciAtLWZvcm1hdCAnJWknIHwgeGFyZ3Mgc2NhbmNlbApgYGAK