1 Basics

1.1 Assign and Retrieve Values

Utilize assign() and get() in loops when dealing with a dynamic number of variables.

# assign the value of 2 to a newly created variable called var.1
assign(paste("var", 1, sep="."),2)

# retrive the value of the var.1 variable
get("var.1")
#> [1] 2
set.seed(as.numeric(as.Date("1777-04-30")))

# Create a data frame directly
my_data = data.frame(
  id = paste("rval", 1:5, sep = "_"),
  value = rnorm(5)
)

print(my_data)
#>       id      value
#> 1 rval_1  0.0475086
#> 2 rval_2  1.0277163
#> 3 rval_3 -1.4956767
#> 4 rval_4 -1.2280738
#> 5 rval_5 -0.1876937

1.2 Environment Scoping

Create custom environments to control where R looks for variables, leveraging parent-child links.

defaults      = new.env()
defaults$tax  = 0.15

order         = new.env(parent = defaults)
order$price   = 200

# 'tax' is not in 'order', but is found via its parent environment
order$total   = order$price * (1 + get("tax", envir = order))
order$total
#> [1] 230
# Create a parent environment
parent.env = new.env()

# Add some variables to the parent environment
parent.env$a = 10
parent.env$b = 5

# A simple calculation in the parent environment
parent.env$sum_parent = parent.env$a + parent.env$b   # 15

# Check what's in the global environment (you should NOT see a, b, or sum_parent)
ls()                # default is ls(globalenv())
#> [1] "defaults"   "my_data"    "order"      "parent.env" "var.1"
# Check what's inside parent.env
ls(parent.env)
#> [1] "a"          "b"          "sum_parent"
# [1] "a" "b" "sum_parent"

# Try to access 'a' from the global environment (this should give an error)
try(a, silent = FALSE)
#> Error in eval(expr, envir) : object 'a' not found
# Error: object 'a' not found

# Correct way: explicitly get 'a' from parent.env
get("a", envir = parent.env)        # 10
#> [1] 10

1.3 Evaluate Expressions

Dynamically construct and evaluate expressions from strings, or extract data from model calls.

# Define an expression
expr = expression(sqrt(81))

# Evaluate the expression
result = eval(expr)

# Print the result
print(result)
#> [1] 9
# Dynamically construct an expression
x = 12
operation = "x ** 2"
expr = parse(text = operation)

# Evaluate the dynamically constructed expression
result = eval(expr)

# Print the result (returns 144)
print(result)
#> [1] 144
set.seed(42)

lm.data  = data.frame(x = 1:10, y = 2.5 * (1:10) + rnorm(10))
lm.model = lm(y ~ x, data = lm.data)

# Extract the original data frame via the stored call
head(eval(lm.model$call$data), 3)

1.4 For Loops and Preallocation

# basic usage
for (i in 1:10){
  print(i)
}
#> [1] 1
#> [1] 2
#> [1] 3
#> [1] 4
#> [1] 5
#> [1] 6
#> [1] 7
#> [1] 8
#> [1] 9
#> [1] 10
library(tictoc)

set.seed(1905)

n_sims = 100000

sim_means_grow = numeric(0)               # Empty vector

tic("Without pre-allocation")
for (i in seq_len(n_sims)) {
  sim_means_grow = c(sim_means_grow, mean(rnorm(50)))
}
toc()
#> Without pre-allocation: 10.25 sec elapsed
sim_means_prealloc = numeric(n_sims)      # Pre-allocated vector

tic("With pre-allocation")
for (i in seq_len(n_sims)) {
  sim_means_prealloc[i] = mean(rnorm(50))
}

toc()
#> With pre-allocation: 1.22 sec elapsed

Always preallocate storage before calculating for-loops to drastically improve efficiency.

set.seed(1925)

library(microbenchmark)

n = 1e4 

bench = microbenchmark(
  prealloc = { 
    v = numeric(n)
    for (i in seq_len(n)) v[i] = i     # EFFICIENT 
  },
  grow = { 
    v = numeric(0)
    for (i in seq_len(n)) v = c(v, i)  # NOT EFFICIENT
  },
  times = 10
)

# 1. Save the filtered summary to a data frame
bench_summary = summary(bench)[, c("expr", "median", "min", "max")]

# 2. Round the numeric columns (columns 2, 3, and 4) to 0 decimal places
bench_summary[, 2:4] = round(bench_summary[, 2:4], digits = 0)

# 3. Print the clean results
print(bench_summary)
#>       expr median min max
#> 1 prealloc      2   1   4
#> 2     grow    243 203 339

1.5 Memory & Path Management

rm(list = ls())      # Clear current environment
gc()                 # Garbage collection
.rs.restartR()       # Restart R session (RStudio only)

getwd()              # Get current working directory
setwd("..")          # Go one level up in the directory tree
setwd("~")           # Go directly to the user's home directory

1.6 RDS format

# Create a sample 3x3 matrix
sample_matrix = matrix(1:9, nrow = 3, ncol = 3)
print(sample_matrix)
#>      [,1] [,2] [,3]
#> [1,]    1    4    7
#> [2,]    2    5    8
#> [3,]    3    6    9
# Save the matrix as an .rds file in the current directory
saveRDS(sample_matrix, file = "./sample_matrix.rds")

# Read the file back into a new variable to verify
restored_matrix = readRDS("./sample_matrix.rds")

# Check if the restored object is identical to the original
identical(sample_matrix, restored_matrix)
#> [1] TRUE

1.7 Advanced Indexing

Use which() with arr.ind = TRUE to get row and column indices in data frames.

x = c(3, 7, 2, 6, 7, 9, 1)

which(x == 7)
#> [1] 2 5
which(x > 5)
#> [1] 2 4 5 6
x[which(x > 5)]
#> [1] 7 6 7 9
# indexes of maximum and minimum
which.max(x) # [1] 6
#> [1] 6
which.min(x) # [1] 7
#> [1] 7
# Get the actual values 
x[which.max(x)] # [1] 9
#> [1] 9
x[which.min(x)] # [1] 1
#> [1] 1
df = data.frame(a = c(1, 3, 5), 
                 b = c(5, 3, 1)
                 )

print(df)
#>   a b
#> 1 1 5
#> 2 3 3
#> 3 5 1
which(df > 3, arr.ind = TRUE)
#>      row col
#> [1,]   3   1
#> [2,]   1   2

2 Functions

2.1 Returning Multiple Objects

Group multiple outputs (data, models, plots) into a single nested list.

# Define sample function with input parameters
my_function = function(x, y){
  
  # Perform some operations
  result1 = x^2
  result2 = y^3
  
  # Perform additional operations
  result3 = x^2+y^2
  
  # Combine results into a list
  result_list      = list(
    x_squared      = result1,
    y_cubed        = result2,
    sum_of_squares = result3
  )
  
  # Return the list of results
  return(result_list)
}

# Example usage of the function
output = my_function(3, 5)

# Access the third result (sum of 3^2 + 5^2)
output$sum_of_squares
#> [1] 34

2.2 Optional Arguments with Ellipsis (...)

Pass varied arguments to internal functions using ... .

# Define the main plotting function
plot_modern = function(x, y, ...) {
  
  # Internal function to create a stylized plot
  internal_plot = function(x, y, ...) {
    
    # 1. Set a soft background color for the plot area
    par(bg = "#f4f7f6", mar = c(5, 5, 4, 2))
    
    # 2. Initialize an empty plot window (type = "n") to lay down the grid first
    plot(x, y, type = "n", bty = "n", ...)
    
    # 3. Add a clean white grid 
    grid(nx = NULL, ny = NULL, col = "white", lty = 1, lwd = 1.5)
    
    # 4. Draw a smooth trendline underneath the points
    lines(spline(x, y), col = "#b0bec5", lwd = 2)
    
    # 5. Plot the actual points, passing the ellipsis (...) for custom styling
    points(x, y, ...)
  }
  
  # Execute the internal function
  internal_plot(x, y, ...)
}


# 1. Generate sample data
set.seed(42)
x = 1:15
y = x^2 + rnorm(15, mean = 0, sd = 15)

# 2. Draw the standard base R plot (Left side)
plot(x, y, 
     main = "Standard Base R Plot",
     xlab = "X", ylab = "Y")

# 3. Draw our custom modern plot (Right side)
plot_modern(x, y, 
            main = "Quadratic Growth Trajectory", 
            xlab = "Time (X-axis)", 
            ylab = "Value (Y-axis)", 
            col  = "#ffffff",   # Point border color
            bg   = "#ff6b6b",   # Point fill color (works with pch = 21)
            pch  = 21,          # Filled circle 
            cex  = 2,           # Point size
            lwd  = 1.5,         # Border thickness
            family = "sans")    # Modern font

2.3 Multiple optional arguments with do.call().

# Define the main wrapper function
process_and_plot = function(data, plot_args = list(), ...) {
  
  # Internal: Calculate statistics using arguments from the ellipsis (...)
  calculate_summary = function(data, ...) {
    summary(data, ...)
  }
  
  # Internal: Create a histogram with modern default styling
  create_histogram = function(data, main = "Histogram", xlab = "Values", 
                               col = "#90CAF9", border = "white", breaks = 15) {
    hist(data, main = main, xlab = xlab, col = col, border = border, breaks = breaks)
  }
  
  # 1. Execute summary via do.call, passing along the ellipsis arguments
  statistics = do.call(calculate_summary, list(data = data, ...))
  
  # 2. Inject data into plot_args and execute the histogram
  plot_args$data = data
  do.call(create_histogram, plot_args)
  
  # Implicitly return the statistics object
  statistics
}

# Example usage
set.seed(1812)
sample_data = rnorm(100)

# Define custom arguments to override the histogram defaults
custom_plot_args = list(
  main   = "Customized Distribution", 
  col    = "#5C6BC0", 
  border = "white", 
  breaks = 25
)

# Call the function: 
# 'digits' and 'quantile.type' flow into the ellipsis (...) for the summary
# 'custom_plot_args' flows into the histogram
example_stats = process_and_plot(
  sample_data, 
  plot_args     = custom_plot_args,
  digits        = 2, 
  quantile.type = 5
)

# Print the resulting summary table
print(example_stats)
#>    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
#> -3.3000 -0.5700 -0.0094  0.0400  0.8100  2.6000

3 Data Structures and Matrix Math

3.1 Modifying Data Frames

Recreate the structure of a data frame and populate it cleanly.

# Create a sample data frame with some initial data
sample_df = data.frame(
  ID     = c(1, 2, 3),
  Name   = c("Alice", "Bruce", "Candice"),
  Age    = c(25, 30, 35),
  Salary = c(50000, 55000, 60000)
)

# Print the sample data frame
sample_df
empty_df = sample_df[NULL, ]
empty_df
# Fill the empty data frame by direct assignment
empty_df[1, ] = list(ID = 4, Name = "David", Age = 28, Salary = 55000)
empty_df[2, ] = list(ID = 5, Name = "Eva", Age = 32, Salary = 62000)

empty_df

3.2 Matrix Multiplication & Logistic Regression

# set seed
set.seed(1918)

# Define matrix A(2x3)
A = matrix(c(1, 2, 3, 4, 5, 6), nrow = 2, ncol = 3)
print(A)
#>      [,1] [,2] [,3]
#> [1,]    1    3    5
#> [2,]    2    4    6
# Define matrix B(3x2)
B = matrix(c(7, 8, 9, 10, 11, 12), nrow = 3, ncol = 2)
print(B)
#>      [,1] [,2]
#> [1,]    7   10
#> [2,]    8   11
#> [3,]    9   12
# Perform regular matrix multiplication(2x2)
C = A %*% B
print(C)
#>      [,1] [,2]
#> [1,]   76  103
#> [2,]  100  136
# Define matrix D(2x2) and fill it by rows
D = matrix(c(2, 4, 6, 8), nrow = 2, ncol = 2, byrow=T)
print(D)
#>      [,1] [,2]
#> [1,]    2    4
#> [2,]    6    8
# Perform element-wise multiplication(2x2)
F = C * D
print(F)
#>      [,1] [,2]
#> [1,]  152  412
#> [2,]  600 1088
simple_logistic_NN=function(X,y, W0,b0, num_epochs,learning_rate, verbose=F){
  
# X  -> n × num_features
# y  -> n × 1
# W0 -> num_features × 1
# b0 -> 1 × 1

  # define the sigmoid function
  sigmoid = function(z) {
   1 / (1 + exp(-z))
  }

  # obtain the number of data points and features
  n=dim(X)[1]
  num_features=dim(X)[2]

  # merge weights and biases and add a column of 1s into X
  W_matrix=matrix(rbind(W0,b0), nrow=num_features+1,ncol=1)
  X_matrix=matrix(cbind(X, rep(1,n)),nrow=n,ncol=num_features+1)
  
  # preallocate a data frame to save losses and a matrix to save gradients
  col_names = c("Epoch", "Loss") 
  df_loss = data.frame(matrix(NA, nrow = num_epochs, ncol = length(col_names))) 
  colnames(df_loss) = col_names
  Gradients = matrix(NA,nrow= num_features+1, ncol=num_epochs)
  
# Training loop
for (epoch in 1:num_epochs)
  {
  
  # compute the forward pass
  z = X_matrix%*% W_matrix 
  y_hat = sigmoid(z)
  
  # calculate loss using binary cross-entropy
  loss = -mean(y * log(y_hat) + (1 - y) * log(1 - y_hat))
  
  # save epoch and loss
  df_loss[epoch,]=c(epoch, loss)
  
  # compute gradients
  dW = t(X_matrix) %*% (y_hat - y) / n
  
  # save gradient
  Gradients[,epoch]=dW
  
  # update weights and bias
  W_matrix = W_matrix - learning_rate * dW
  
  if(verbose!=FALSE){
  # Print loss every verbose epochs
    if (epoch %% verbose == 0) {
      cat("Epoch:", epoch, "Loss:", loss,"Weights:", W_matrix[1:num_features,],
          "Bias:", W_matrix[num_features+1,], "\n")
                               }
                 }
   }
  
  l=list("W_est"     = W_matrix[1:num_features,], 
         "b_est"     = W_matrix[num_features+1,],
         "Loss_DF"   = df_loss, 
         "Gradients" = Gradients)
  return(l)
}
set.seed(1986)

# Generate a synthetic dataset
n_examp_1 = 1000
X_examp_1 = matrix(rnorm(n_examp_1 * 2), ncol = 2)
y_examp_1 = as.numeric(X_examp_1[,1] + X_examp_1[,2] > 0)

# Initialize weights and bias
W0_examp_1 = matrix(runif(2), nrow = 2)
b0_examp_1 = runif(1)

logistic_NN_training_examp_1=simple_logistic_NN(X=X_examp_1, y=y_examp_1, 
                                                W0=W0_examp_1,b0=b0_examp_1, 
                                                num_epochs=1000,
                                                learning_rate=0.01, 
                                                verbose=100)
#> Epoch: 100 Loss: 0.4321067 Weights: 0.3626608 0.9717162 Bias: 0.2820893 
#> Epoch: 200 Loss: 0.3857622 Weights: 0.5567836 1.056564 Bias: 0.2469912 
#> Epoch: 300 Loss: 0.351951 Weights: 0.7207754 1.134028 Bias: 0.2190585 
#> Epoch: 400 Loss: 0.3262268 Weights: 0.8619835 1.206138 Bias: 0.1967264 
#> Epoch: 500 Loss: 0.3059498 Weights: 0.9856042 1.274081 Bias: 0.1787698 
#> Epoch: 600 Loss: 0.289491 Weights: 1.095368 1.338592 Bias: 0.1642525 
#> Epoch: 700 Loss: 0.2758048 Weights: 1.194004 1.40016 Bias: 0.1524602 
#> Epoch: 800 Loss: 0.2641951 Weights: 1.283555 1.459134 Bias: 0.1428455 
#> Epoch: 900 Loss: 0.2541826 Weights: 1.365575 1.515776 Bias: 0.1349851 
#> Epoch: 1000 Loss: 0.2454275 Weights: 1.441269 1.570297 Bias: 0.1285489
# Print final weights and bias
cat("Estimated Weights:", logistic_NN_training_examp_1$W_est, "\n")
#> Estimated Weights: 1.441269 1.570297
cat("Estimated Bias:", logistic_NN_training_examp_1$b_est, "\n")
#> Estimated Bias: 0.1285489

3.3 Singular Value Decomposition (SVD) from Scratch

svd_custom = function(A) {
  # Check if A is a matrix
  if (!is.matrix(A)) stop("Input must be a matrix")
  
  # Step 1: Compute A^T * A
  AtA = t(A) %*% A
  
  # Step 2: Eigen decomposition of A^T * A
  eigen_AtA = eigen(AtA)
  
  # Eigenvalues and eigenvectors
  eigenvalues = eigen_AtA$values
  V = eigen_AtA$vectors
  
  # Step 3: Compute singular values
  singular_values = sqrt(eigenvalues)
  
  # Step 4: Compute U matrix
  U = matrix(0, nrow = nrow(A), ncol = nrow(A))
  for (i in 1:nrow(A)) {
    if (singular_values[i] != 0) {
      U[, i] = (A %*% V[, i]) / singular_values[i]
    }
  }
  
  # Step 5: Construct Sigma matrix
  Sigma = diag(singular_values)
  
  # Return the SVD components
  list(U = U, Sigma = Sigma, V = V)
}
set.seed(1903)

A = matrix(rnorm(9), nrow = 3, ncol = 3)
svd_result =svd_custom(A)

# UVT^t == A (evaluates to TRUE)
all.equal(svd_result$U%*%svd_result$Sigma%*%t(svd_result$V), A)
#> [1] TRUE

3.4 Pre-allocate lists

# Preallocate the empty list with 3 elements
n    = 3
list = vector("list", length = n)

# Fill in the first element with another list
list[[1]] = list(a = 5, b = 8, c = 13)

# Fill in the second element with a data frame
list[[2]] = data.frame(Name = c("Zu", "Georges"), Age = c(3, 14))

# Fill in the third element with a vector
list[[3]] = c(21, 34, 55, 89, 144)

list[[1]]$a
#> [1] 5
list[[2]]$Age
#> [1]  3 14
list[[3]]
#> [1]  21  34  55  89 144

3.5 Python-Like Dictionaries in R

Use environments for reference semantics and O(1) lookup.

# First way using an environment 
dict_env   = new.env()

# Assign key-value pairs to the environment
dict_env$a = 2
dict_env$b = 7
dict_env$c = 1

# Add a new key-value pair
dict_env$d = 8

# Modify an existing value
dict_env$a = 2

# List all objects in the environment
ls(dict_env)
#> [1] "a" "b" "c" "d"
# Get a value by key
get("c", envir = dict_env)
#> [1] 1
# generates a set of 100 colors using the rainbow function.
cols = rainbow(100)

# create a sequence of the first 100 odd numbers starting from 1.
numb = seq(1,200,2)

# preallocate three lists to store the colors, the numbers, and the indices
col_vals = vector("list",100)
num_vals = vector("list",100)
ind_vals = vector("list",100)

# iterate
for(c in 1:length(numb)){
  col_vals[[c]] = cols[c]
  num_vals[[c]] = numb[c]
  ind_vals[[c]] = c
}

# retrieve the color associated with the number 157 ("#AD00FF")
col_vals[[which(num_vals==157)]]
#> [1] "#AD00FF"
# find the number that corresponds to the color "#AD00FF" (157)
num_vals[[which(col_vals=="#AD00FF")]]
#> [1] 157
#check if the color and the number are associated with the same index (TRUE)
ind_vals[[which(col_vals=="#AD00FF")]]==ind_vals[[which(num_vals==157)]]
#> [1] TRUE

3.6 Vectors

nums   = c(1, 2, 3)               # numeric
chars  = c("a", "b", "c")         # character
logic  = c(TRUE, FALSE, TRUE)     # logical
seq_10 = 1:10                     # integer sequence
rep_pi = rep(pi, 3)               # repeat a value
x = 1:5
y = seq(10, 50, 10)

x + y        # 11 22 33 44 55
#> [1] 11 22 33 44 55
x^2 + log(y) # combined numeric ops
#> [1]  3.302585  6.995732 12.401197 19.688879 28.912023
vals = c(4, 7, 11, 3, 9)

# Keep elements > 5
vals[vals > 5]                  # 7 11 9
#> [1]  7 11  9
# Replace outliers with NA
vals[vals > 10] = NA           # 4 7 NA 3 9
print(vals)
#> [1]  4  7 NA  3  9

4 Vectorized Iterations

R’s apply family avoids explicit loops with concise, vectorized C-level execution.

# Sample 4×3 matrix
mat = matrix(1:12, nrow = 4, byrow = TRUE)

# Column means
apply(mat, 2, mean)
#> [1] 5.5 6.5 7.5
# Row standard deviations
apply(mat, 1, sd)
#> [1] 1 1 1 1
set.seed(1942)

# 1. Create a list of score vectors -----------------------------
scores = lapply(1:5, \(i) rnorm(50, mean = 70 + i * 5, sd = 8))
names(scores) = paste0("Class_", 1:5)

# 2. Compute the mean score for every class ---------------------
class_means = lapply(scores, mean)

class_means
#> $Class_1
#> [1] 74.04006
#> 
#> $Class_2
#> [1] 78.34366
#> 
#> $Class_3
#> [1] 84.09832
#> 
#> $Class_4
#> [1] 90.77986
#> 
#> $Class_5
#> [1] 95.59702
# Create 3 normally distributed vectors with different means and SDs
set.seed(1914)
params = data.frame(mu = c(0, 5, 10), sd = c(1, 2, 3))

samples = mapply(
  FUN = rnorm,
  n   = 5,                  # recycled for each call
  mean = params$mu,
  sd   = params$sd,
  SIMPLIFY = FALSE          # keep as list of vectors
)

str(samples)
#> List of 3
#>  $ : num [1:5] -0.379 0.13 0.334 -1.887 2.051
#>  $ : num [1:5] 10.09 4.61 7.11 1.09 5.21
#>  $ : num [1:5] 13.17 11.02 8.87 9.4 9.94
nums = list(a = 1:4, b = 5:7, c = 8:9)

# Sums each element returning a numeric vector
sapply(nums, sum)
#>  a  b  c 
#> 10 18 17
vec = 1:6
sapply(vec, function(x) x^2)
#> [1]  1  4  9 16 25 36

5 Parallel Computing

library(parallel)
n_cores = max(1L, detectCores() - 1L)
print(n_cores)
#> [1] 31

Use parLapply for cross-platform parallel execution, or mclapply for Unix-based systems.

# Expensive function: Monte Carlo estimate of pi
estimate_pi = function(n = 1e6) {
  x = runif(n)
  y = runif(n)
  mean(x^2 + y^2 <= 1) * 4
}

n_rep = 30

# ---------------------------------------------------------
# 1. SEQUENTIAL RUN
# ---------------------------------------------------------
set.seed(1986)

t_seq = system.time({
  seq_results = lapply(1:n_rep, function(i) {
    t0 = proc.time()["elapsed"]
    pi_est = estimate_pi()
    
    # Return both the estimate and the time it took
    data.frame(run = i, pi_est = pi_est, elapsed = proc.time()["elapsed"] - t0)
  })
})

# Bind into a single data frame
seq_df = do.call(rbind, seq_results)

# ---------------------------------------------------------
# 2. PARALLEL RUN
# ---------------------------------------------------------
n_cores = max(1, detectCores() - 1)
cl = makeCluster(n_cores)
clusterExport(cl, varlist = "estimate_pi")

set.seed(1986)

t_par = system.time({
  par_results = parLapply(cl, 1:n_rep, function(i) {
    t0 = proc.time()["elapsed"]
    pi_est = estimate_pi()
    
    # Return both the estimate and the time it took
    data.frame(run = i, pi_est = pi_est, elapsed = proc.time()["elapsed"] - t0)
  })
})

stopCluster(cl)

# Bind into a single data frame
par_df = do.call(rbind, par_results)

# ---------------------------------------------------------
# 3. COMPARE RESULTS
# ---------------------------------------------------------
# Check if the estimates are mathematically equivalent
all.equal(seq_df$pi_est, par_df$pi_est)
#> [1] "Mean relative difference: 0.0005693522"
# Print the timing results
cat("Total Sequential Time:     ", round(t_seq["elapsed"], 3), "seconds\n")
#> Total Sequential Time:      1.54 seconds
cat("Total Parallel Time:       ", round(t_par["elapsed"], 3), "seconds\n\n")
#> Total Parallel Time:        0.11 seconds
cat("Avg Sequential per run:    ", round(mean(seq_df$elapsed), 3), "seconds\n")
#> Avg Sequential per run:     0.051 seconds
cat("Avg Parallel per run:      ", round(mean(par_df$elapsed), 3), "seconds\n")
#> Avg Parallel per run:       0.079 seconds
# Install if needed: install.packages(c("future", "future.apply"))
library(future)
library(future.apply)

# 1. Define the workhorse function
estimate_pi = function(n = 1e6) {
  mean(runif(n)^2 + runif(n)^2 <= 1) * 4
}

n_rep = 20

# 2. Tell R your "plan" (run asynchronously across multiple background sessions)
plan(multisession, workers = availableCores() - 1)

set.seed(1986)

# 3. Execute! future_lapply acts just like lapply, but routes traffic asynchronously
t_future = system.time({
  
  # future.seed = TRUE ensures random numbers are safely generated across cores
  future_results = future_lapply(1:n_rep, function(i) {
    t0 = proc.time()["elapsed"]
    pi_est = estimate_pi()
    data.frame(run = i, pi_est = pi_est, elapsed = proc.time()["elapsed"] - t0)
  }, future.seed = TRUE)
  
})

# 4. Bind the results
future_df = do.call(rbind, future_results)

# 5. Always explicitly close background workers when done
plan(sequential)

cat("Total Future Async Time: ", round(t_future["elapsed"], 3), "seconds\n")
#> Total Future Async Time:  0.23 seconds
cat("Average Future Async per run: ", round(mean(future_df$elapsed), 3), "seconds\n")
#> Average Future Async per run:  0.056 seconds

6 Running Python Code in R

Execute Python scripts and pass data frames back and forth using {reticulate}.

library(reticulate)

# Show which Python is being used
py_config()
#> python:         C:/Users/vadim/AppData/Local/R/cache/R/reticulate/uv/cache/archive-v0/hTrjH-QW50JlBG25MDlZa/Scripts/python.exe
#> libpython:      C:/Users/vadim/AppData/Local/R/cache/R/reticulate/uv/python/cpython-3.12.13-windows-x86_64-none/python312.dll
#> pythonhome:     C:/Users/vadim/AppData/Local/R/cache/R/reticulate/uv/cache/archive-v0/hTrjH-QW50JlBG25MDlZa
#> virtualenv:     C:/Users/vadim/AppData/Local/R/cache/R/reticulate/uv/cache/archive-v0/hTrjH-QW50JlBG25MDlZa/Scripts/activate_this.py
#> version:        3.12.13 (main, Apr  7 2026, 20:53:22) [MSC v.1944 64 bit (AMD64)]
#> Architecture:   64bit
#> numpy:          C:/Users/vadim/AppData/Local/R/cache/R/reticulate/uv/cache/archive-v0/hTrjH-QW50JlBG25MDlZa/Lib/site-packages/numpy
#> numpy_version:  2.4.4
#> 
#> NOTE: Python version was forced by VIRTUAL_ENV
#reticulate::py_install("pandas")
# Run Python inline
py_run_string("
x = [1, 2, 3]
y = [i**2 for i in x]
")

# Access Python variables natively in R
py$y
py$x
np = import("numpy")

# Create NumPy arrays and compute statistics
a = np$array(c(1, 2, 3, 4, 5))
mean_a = np$mean(a)
mean_a
#> [1] 3
# Create a standard R dataframe
r_df = data.frame(a = 1:3, b = c(4, 5, 6))

import pandas as pd

# Pull the dataframe from R into Python
py_df = r.r_df

# Perform our Python operations
py_df['sum'] = py_df['a'] + py_df['b']
# Pull the modified dataframe back from Python into R
final_r_df = py$py_df

print(final_r_df)
#>   a b sum
#> 1 1 4   5
#> 2 2 5   7
#> 3 3 6   9

7 GPU Computing in R

Note: The following code blocks are structured but intentionally unevaluated (eval=FALSE) in this notebook to prevent rendering failures on environments lacking CUDA/GPU configurations.

Note on Windows Compatibility: Enabling GPU acceleration for TensorFlow in a Windows environment is not a “plug-and-play” process. This requires a specific infrastructure stack: a WSL2 (Windows Subsystem for Linux) installation, an Ubuntu R Server and the dedicated GPU version of TensorFlow. For a step-by-step walkthrough on configuring this environment, please refer to Chapter 6 of my Medium article, where I cover the full backend setup required to get R and your GPU talking to each other.

7.1 GPU Initialization

library(tensorflow)
library(keras3)

# Check for the GPU
tf$config$list_physical_devices("GPU")

7.2 Neural Network for Non-Linear Classification

set.seed(1925)

n_per_class <- 2000

# Inner circle (class 0)
theta1 <- runif(n_per_class, 0, 2 * pi)
r1     <- rnorm(n_per_class, mean = 1, sd = 0.08)
x1     <- cbind(r1 * cos(theta1), r1 * sin(theta1))
y1     <- rep(0, n_per_class)

# Outer ring (class 1)
theta2 <- runif(n_per_class, 0, 2 * pi)
r2     <- rnorm(n_per_class, mean = 2, sd = 0.08)
x2     <- cbind(r2 * cos(theta2), r2 * sin(theta2))
y2     <- rep(1, n_per_class)

# Full dataset
x <- rbind(x1, x2)
y <- c(y1, y2)

# Shuffle
idx <- sample(seq_len(nrow(x)))
x   <- x[idx, ]
y   <- y[idx]

# Train / test split
n    <- nrow(x)
n_tr <- floor(0.8 * n)

x_train <- x[1:n_tr, ]
y_train <- y[1:n_tr]

x_test  <- x[(n_tr + 1):n, ]
y_test  <- y[(n_tr + 1):n]

model <- keras_model_sequential() %>%
  layer_dense(units = 32, activation = "relu", input_shape = 2) %>%
  layer_dense(units = 64, activation = "relu") %>%
  layer_dense(units = 32, activation = "relu") %>%
  layer_dense(units = 1, activation = "sigmoid")  # binary classification

model %>%
  compile(
    optimizer = "adam",
    loss      = "binary_crossentropy",
    metrics   = "accuracy"
  )

# summary(model)

batch_size <- 128
epochs     <- 20

t <- system.time({
  history <- model %>%
    fit(
      x = x_train, y = y_train,
      batch_size      = batch_size,
      epochs          = epochs,
      validation_split = 0.2,
      verbose         = 2
    )
})

cat("Training elapsed time (GPU-backed TF):", t["elapsed"], "seconds\n")

model %>%
  evaluate(x_test, y_test, verbose = 0)
library(ggplot2)
library(viridis)

# Define grid for visualization
x_min <- min(x[,1]) - 0.2
x_max <- max(x[,1]) + 0.2
y_min <- min(x[,2]) - 0.2
y_max <- max(x[,2]) + 0.2

grid <- expand.grid(
  x1 = seq(x_min, x_max, length.out = 400), # Higher resolution for crispness
  x2 = seq(y_min, y_max, length.out = 400)
)

# Predict probabilities
grid$prob <- as.numeric(model %>% predict(as.matrix(grid), verbose = 0))

ggplot() +
  # 1. Background Grid/Heatmap (Smooth Ukrainian Flag gradient)
  # Low prob (Inner) -> Blue. High prob (Outer) -> Yellow.
  geom_raster(data = grid, aes(x = x1, y = x2, fill = prob), interpolate = TRUE) +
  
  # 2. Add a strong decision boundary line
  geom_contour(data = grid, aes(x = x1, y = x2, z = prob), 
               breaks = 0.5, color = "white", linetype = "solid", linewidth = 1.2) +
  
  # 3. Add the points (Small and translucent so they don't hide the boundary)
  geom_point(aes(x = x[,1], y = x[,2], color = factor(y)), size = 0.6, alpha = 0.3) +
  
  # --- Color Palettes (Ukrainian Flag Theme) ---
  
  # Background Fill: Cividis provides a great smooth blue-to-yellow gradient
  scale_fill_viridis_c(option = "cividis", direction = -1,
                       name = "Predicted\nProbability", limits = c(0, 1),
                       breaks = c(0, 0.5, 1), labels = c("Inner", "Boundary", "Outer")) +
  
  # Points Color: Force them to pop against the blue/yellow background
  scale_color_manual(values = c("black", "red"), 
                     name = "True Class", labels = c("Inner (0)", "Outer (1)")) +
  
  # --- Theme and Labs ---
  labs(
    title = "Neural Network classification Boundary",
    subtitle = "Modern TensorFlow/Keras on GPU via WSL2",
    x = "Feature 1", 
    y = "Feature 2"
  ) +
  coord_fixed() + # Critical: keeps the circle circular
  theme_minimal(base_size = 14) +
  theme(
    legend.position = "right",
    # Center the title and make it bold
    plot.title = element_text(face = "bold", size = 18, hjust = 0.5),
    plot.subtitle = element_text(hjust = 0.5, size = 12),
    plot.caption = element_text(size = 9, color = "gray50"),
    panel.grid.major = element_line(color = "gray90"),
    plot.background = element_rect(fill = "white", color = NA)
  )

)

Session Info

sessionInfo()
#> R version 4.5.2 (2025-10-31 ucrt)
#> Platform: x86_64-w64-mingw32/x64
#> Running under: Windows 11 x64 (build 26200)
#> 
#> Matrix products: default
#>   LAPACK version 3.12.1
#> 
#> locale:
#> [1] LC_COLLATE=English_Canada.utf8  LC_CTYPE=English_Canada.utf8   
#> [3] LC_MONETARY=English_Canada.utf8 LC_NUMERIC=C                   
#> [5] LC_TIME=English_Canada.utf8    
#> 
#> time zone: America/Toronto
#> tzcode source: internal
#> 
#> attached base packages:
#> [1] parallel  stats     graphics  grDevices utils     datasets  methods  
#> [8] base     
#> 
#> other attached packages:
#> [1] reticulate_1.45.0.9000 future.apply_1.20.2    future_1.69.0         
#> [4] microbenchmark_1.5.0   tictoc_1.2.1          
#> 
#> loaded via a namespace (and not attached):
#>  [1] vctrs_0.7.1       cli_3.6.5         knitr_1.51        rlang_1.1.7      
#>  [5] xfun_0.56         otel_0.2.0        png_0.1-9         jsonlite_2.0.0   
#>  [9] listenv_0.10.0    htmltools_0.5.9   sass_0.4.10       rmarkdown_2.30   
#> [13] grid_4.5.2        evaluate_1.0.5    jquerylib_0.1.4   fastmap_1.2.0    
#> [17] yaml_2.3.12       lifecycle_1.0.5   compiler_4.5.2    codetools_0.2-20 
#> [21] Rcpp_1.1.1        rstudioapi_0.18.0 lattice_0.22-9    digest_0.6.39    
#> [25] R6_2.6.1          parallelly_1.46.1 Matrix_1.7-4      bslib_0.10.0     
#> [29] withr_3.0.2       tools_4.5.2       globals_0.19.0    cachem_1.1.0
LS0tDQp0aXRsZTogIkNvZGUgQ29tcGFuaW9uOiBNYXN0ZXJpbmcgUiBmb3IgRGF0YSBTY2llbmNlIDIwMjYiDQphdXRob3I6ICJWYWRpbSBUeXVyeWFldiINCmRhdGU6ICJgciBmb3JtYXQoU3lzLkRhdGUoKSwgJyVCICVkLCAlWScpYCINCm91dHB1dDoNCiAgaHRtbF9kb2N1bWVudDoNCiAgICB0b2M6IHRydWUNCiAgICB0b2NfZGVwdGg6IDMNCiAgICB0b2NfZmxvYXQ6DQogICAgICBjb2xsYXBzZWQ6IGZhbHNlDQogICAgICBzbW9vdGhfc2Nyb2xsOiB0cnVlDQogICAgbnVtYmVyX3NlY3Rpb25zOiB0cnVlDQogICAgdGhlbWU6IHBhcGVyDQogICAgaGlnaGxpZ2h0OiB0YW5nbw0KICAgIGNvZGVfZm9sZGluZzogc2hvdw0KICAgIGNvZGVfZG93bmxvYWQ6IHRydWUNCiAgICBkZl9wcmludDogcGFnZWQNCiAgICBjc3M6IHN0eWxlcy5jc3MNCi0tLQ0KDQpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0NCiMgR2xvYmFsIGNodW5rIG9wdGlvbnM6IGV2YWx1YXRlIGNvZGUgdG8gc2hvdyBvdXRwdXRzLCBzdXBwcmVzcyBtZXNzYWdlcy93YXJuaW5ncw0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KA0KICBldmFsICAgICAgPSBUUlVFLCAgIA0KICBlY2hvICAgICAgPSBUUlVFLA0KICBjb21tZW50ICAgPSAiIz4iLA0KICBtZXNzYWdlICAgPSBGQUxTRSwNCiAgd2FybmluZyAgID0gRkFMU0UsDQogIGZpZy5hbGlnbiA9ICJjZW50ZXIiLA0KICBmaWcud2lkdGggPSA5LA0KICBmaWcuaGVpZ2h0ID0gNQ0KKQ0KYGBgDQoNCi0tLQ0KDQojIEJhc2ljcw0KDQojIyBBc3NpZ24gYW5kIFJldHJpZXZlIFZhbHVlcw0KDQpVdGlsaXplIGBhc3NpZ24oKWAgYW5kIGBnZXQoKWAgaW4gbG9vcHMgd2hlbiBkZWFsaW5nIHdpdGggYSBkeW5hbWljIG51bWJlciBvZiANCnZhcmlhYmxlcy4NCg0KYGBge3IgYmFzaWNzLWFzc2lnbn0NCg0KIyBhc3NpZ24gdGhlIHZhbHVlIG9mIDIgdG8gYSBuZXdseSBjcmVhdGVkIHZhcmlhYmxlIGNhbGxlZCB2YXIuMQ0KYXNzaWduKHBhc3RlKCJ2YXIiLCAxLCBzZXA9Ii4iKSwyKQ0KDQojIHJldHJpdmUgdGhlIHZhbHVlIG9mIHRoZSB2YXIuMSB2YXJpYWJsZQ0KZ2V0KCJ2YXIuMSIpDQoNCmBgYA0KYGBge3IgYmFzaWNzLWFzc2lnbi1sb29wfQ0KDQpzZXQuc2VlZChhcy5udW1lcmljKGFzLkRhdGUoIjE3NzctMDQtMzAiKSkpDQoNCiMgQ3JlYXRlIGEgZGF0YSBmcmFtZSBkaXJlY3RseQ0KbXlfZGF0YSA9IGRhdGEuZnJhbWUoDQogIGlkID0gcGFzdGUoInJ2YWwiLCAxOjUsIHNlcCA9ICJfIiksDQogIHZhbHVlID0gcm5vcm0oNSkNCikNCg0KcHJpbnQobXlfZGF0YSkNCg0KYGBgDQoNCiMjIEVudmlyb25tZW50IFNjb3BpbmcNCg0KQ3JlYXRlIGN1c3RvbSBlbnZpcm9ubWVudHMgdG8gY29udHJvbCB3aGVyZSBSIGxvb2tzIGZvciB2YXJpYWJsZXMsIGxldmVyYWdpbmcNCnBhcmVudC1jaGlsZCBsaW5rcy4NCg0KDQpgYGB7ciBiYXNpY3MtZW52LXBhcmVudC1jaGlsZH0NCg0KZGVmYXVsdHMgICAgICA9IG5ldy5lbnYoKQ0KZGVmYXVsdHMkdGF4ICA9IDAuMTUNCg0Kb3JkZXIgICAgICAgICA9IG5ldy5lbnYocGFyZW50ID0gZGVmYXVsdHMpDQpvcmRlciRwcmljZSAgID0gMjAwDQoNCiMgJ3RheCcgaXMgbm90IGluICdvcmRlcicsIGJ1dCBpcyBmb3VuZCB2aWEgaXRzIHBhcmVudCBlbnZpcm9ubWVudA0Kb3JkZXIkdG90YWwgICA9IG9yZGVyJHByaWNlICogKDEgKyBnZXQoInRheCIsIGVudmlyID0gb3JkZXIpKQ0Kb3JkZXIkdG90YWwNCg0KYGBgDQpgYGB7ciBlbnYtcGFyZW50LWNoaWxkfQ0KDQojIENyZWF0ZSBhIHBhcmVudCBlbnZpcm9ubWVudA0KcGFyZW50LmVudiA9IG5ldy5lbnYoKQ0KDQojIEFkZCBzb21lIHZhcmlhYmxlcyB0byB0aGUgcGFyZW50IGVudmlyb25tZW50DQpwYXJlbnQuZW52JGEgPSAxMA0KcGFyZW50LmVudiRiID0gNQ0KDQojIEEgc2ltcGxlIGNhbGN1bGF0aW9uIGluIHRoZSBwYXJlbnQgZW52aXJvbm1lbnQNCnBhcmVudC5lbnYkc3VtX3BhcmVudCA9IHBhcmVudC5lbnYkYSArIHBhcmVudC5lbnYkYiAgICMgMTUNCg0KIyBDaGVjayB3aGF0J3MgaW4gdGhlIGdsb2JhbCBlbnZpcm9ubWVudCAoeW91IHNob3VsZCBOT1Qgc2VlIGEsIGIsIG9yIHN1bV9wYXJlbnQpDQpscygpICAgICAgICAgICAgICAgICMgZGVmYXVsdCBpcyBscyhnbG9iYWxlbnYoKSkNCg0KIyBDaGVjayB3aGF0J3MgaW5zaWRlIHBhcmVudC5lbnYNCmxzKHBhcmVudC5lbnYpDQojIFsxXSAiYSIgImIiICJzdW1fcGFyZW50Ig0KDQojIFRyeSB0byBhY2Nlc3MgJ2EnIGZyb20gdGhlIGdsb2JhbCBlbnZpcm9ubWVudCAodGhpcyBzaG91bGQgZ2l2ZSBhbiBlcnJvcikNCnRyeShhLCBzaWxlbnQgPSBGQUxTRSkNCiMgRXJyb3I6IG9iamVjdCAnYScgbm90IGZvdW5kDQoNCiMgQ29ycmVjdCB3YXk6IGV4cGxpY2l0bHkgZ2V0ICdhJyBmcm9tIHBhcmVudC5lbnYNCmdldCgiYSIsIGVudmlyID0gcGFyZW50LmVudikgICAgICAgICMgMTANCg0KYGBgDQoNCiMjIEV2YWx1YXRlIEV4cHJlc3Npb25zDQoNCkR5bmFtaWNhbGx5IGNvbnN0cnVjdCBhbmQgZXZhbHVhdGUgZXhwcmVzc2lvbnMgZnJvbSBzdHJpbmdzLCBvciBleHRyYWN0IGRhdGEgZnJvbSBtb2RlbCBjYWxscy4NCg0KYGBge3IgYmFzaWNzLWV2YWx9DQoNCiMgRGVmaW5lIGFuIGV4cHJlc3Npb24NCmV4cHIgPSBleHByZXNzaW9uKHNxcnQoODEpKQ0KDQojIEV2YWx1YXRlIHRoZSBleHByZXNzaW9uDQpyZXN1bHQgPSBldmFsKGV4cHIpDQoNCiMgUHJpbnQgdGhlIHJlc3VsdA0KcHJpbnQocmVzdWx0KQ0KDQpgYGANCg0KYGBge3IgYmFzaWNzLWV2YWwtcGFyc2V9DQoNCiMgRHluYW1pY2FsbHkgY29uc3RydWN0IGFuIGV4cHJlc3Npb24NCnggPSAxMg0Kb3BlcmF0aW9uID0gInggKiogMiINCmV4cHIgPSBwYXJzZSh0ZXh0ID0gb3BlcmF0aW9uKQ0KDQojIEV2YWx1YXRlIHRoZSBkeW5hbWljYWxseSBjb25zdHJ1Y3RlZCBleHByZXNzaW9uDQpyZXN1bHQgPSBldmFsKGV4cHIpDQoNCiMgUHJpbnQgdGhlIHJlc3VsdCAocmV0dXJucyAxNDQpDQpwcmludChyZXN1bHQpDQoNCmBgYA0KDQpgYGB7ciBiYXNpY3MtZXZhbC1sbX0NCg0Kc2V0LnNlZWQoNDIpDQoNCmxtLmRhdGEgID0gZGF0YS5mcmFtZSh4ID0gMToxMCwgeSA9IDIuNSAqICgxOjEwKSArIHJub3JtKDEwKSkNCmxtLm1vZGVsID0gbG0oeSB+IHgsIGRhdGEgPSBsbS5kYXRhKQ0KDQojIEV4dHJhY3QgdGhlIG9yaWdpbmFsIGRhdGEgZnJhbWUgdmlhIHRoZSBzdG9yZWQgY2FsbA0KaGVhZChldmFsKGxtLm1vZGVsJGNhbGwkZGF0YSksIDMpDQoNCmBgYA0KDQojIyBGb3IgTG9vcHMgYW5kIFByZWFsbG9jYXRpb24NCg0KYGBge3IgYmFzaWNzLWZvci1sb29wc30NCg0KIyBiYXNpYyB1c2FnZQ0KZm9yIChpIGluIDE6MTApew0KICBwcmludChpKQ0KfQ0KDQpgYGANCmBgYHtyfQ0KDQpsaWJyYXJ5KHRpY3RvYykNCg0Kc2V0LnNlZWQoMTkwNSkNCg0Kbl9zaW1zID0gMTAwMDAwDQoNCnNpbV9tZWFuc19ncm93ID0gbnVtZXJpYygwKSAgICAgICAgICAgICAgICMgRW1wdHkgdmVjdG9yDQoNCnRpYygiV2l0aG91dCBwcmUtYWxsb2NhdGlvbiIpDQpmb3IgKGkgaW4gc2VxX2xlbihuX3NpbXMpKSB7DQogIHNpbV9tZWFuc19ncm93ID0gYyhzaW1fbWVhbnNfZ3JvdywgbWVhbihybm9ybSg1MCkpKQ0KfQ0KdG9jKCkNCg0KDQoNCnNpbV9tZWFuc19wcmVhbGxvYyA9IG51bWVyaWMobl9zaW1zKSAgICAgICMgUHJlLWFsbG9jYXRlZCB2ZWN0b3INCg0KdGljKCJXaXRoIHByZS1hbGxvY2F0aW9uIikNCmZvciAoaSBpbiBzZXFfbGVuKG5fc2ltcykpIHsNCiAgc2ltX21lYW5zX3ByZWFsbG9jW2ldID0gbWVhbihybm9ybSg1MCkpDQp9DQoNCnRvYygpDQoNCmBgYA0KDQo+ICpBbHdheXMgcHJlYWxsb2NhdGUgc3RvcmFnZSBiZWZvcmUgY2FsY3VsYXRpbmcgZm9yLWxvb3BzIHRvIGRyYXN0aWNhbGx5IGltcHJvdmUgZWZmaWNpZW5jeSouDQoNCmBgYHtyIGJhc2ljcy1mb3ItYmVuY2h9DQoNCnNldC5zZWVkKDE5MjUpDQoNCmxpYnJhcnkobWljcm9iZW5jaG1hcmspDQoNCm4gPSAxZTQgDQoNCmJlbmNoID0gbWljcm9iZW5jaG1hcmsoDQogIHByZWFsbG9jID0geyANCiAgICB2ID0gbnVtZXJpYyhuKQ0KICAgIGZvciAoaSBpbiBzZXFfbGVuKG4pKSB2W2ldID0gaSAgICAgIyBFRkZJQ0lFTlQgDQogIH0sDQogIGdyb3cgPSB7IA0KICAgIHYgPSBudW1lcmljKDApDQogICAgZm9yIChpIGluIHNlcV9sZW4obikpIHYgPSBjKHYsIGkpICAjIE5PVCBFRkZJQ0lFTlQNCiAgfSwNCiAgdGltZXMgPSAxMA0KKQ0KDQojIDEuIFNhdmUgdGhlIGZpbHRlcmVkIHN1bW1hcnkgdG8gYSBkYXRhIGZyYW1lDQpiZW5jaF9zdW1tYXJ5ID0gc3VtbWFyeShiZW5jaClbLCBjKCJleHByIiwgIm1lZGlhbiIsICJtaW4iLCAibWF4IildDQoNCiMgMi4gUm91bmQgdGhlIG51bWVyaWMgY29sdW1ucyAoY29sdW1ucyAyLCAzLCBhbmQgNCkgdG8gMCBkZWNpbWFsIHBsYWNlcw0KYmVuY2hfc3VtbWFyeVssIDI6NF0gPSByb3VuZChiZW5jaF9zdW1tYXJ5WywgMjo0XSwgZGlnaXRzID0gMCkNCg0KIyAzLiBQcmludCB0aGUgY2xlYW4gcmVzdWx0cw0KcHJpbnQoYmVuY2hfc3VtbWFyeSkNCg0KYGBgDQoNCiMjIE1lbW9yeSAmIFBhdGggTWFuYWdlbWVudA0KDQpgYGB7ciBiYXNpY3MtbWVtb3J5LCBldmFsPUZBTFNFfQ0KDQpybShsaXN0ID0gbHMoKSkgICAgICAjIENsZWFyIGN1cnJlbnQgZW52aXJvbm1lbnQNCmdjKCkgICAgICAgICAgICAgICAgICMgR2FyYmFnZSBjb2xsZWN0aW9uDQoucnMucmVzdGFydFIoKSAgICAgICAjIFJlc3RhcnQgUiBzZXNzaW9uIChSU3R1ZGlvIG9ubHkpDQoNCmdldHdkKCkgICAgICAgICAgICAgICMgR2V0IGN1cnJlbnQgd29ya2luZyBkaXJlY3RvcnkNCnNldHdkKCIuLiIpICAgICAgICAgICMgR28gb25lIGxldmVsIHVwIGluIHRoZSBkaXJlY3RvcnkgdHJlZQ0Kc2V0d2QoIn4iKSAgICAgICAgICAgIyBHbyBkaXJlY3RseSB0byB0aGUgdXNlcidzIGhvbWUgZGlyZWN0b3J5DQoNCmBgYA0KDQojIyBSRFMgZm9ybWF0DQoNCmBgYHtyIG1hdHJpeC1yZHN9DQoNCiMgQ3JlYXRlIGEgc2FtcGxlIDN4MyBtYXRyaXgNCnNhbXBsZV9tYXRyaXggPSBtYXRyaXgoMTo5LCBucm93ID0gMywgbmNvbCA9IDMpDQpwcmludChzYW1wbGVfbWF0cml4KQ0KDQojIFNhdmUgdGhlIG1hdHJpeCBhcyBhbiAucmRzIGZpbGUgaW4gdGhlIGN1cnJlbnQgZGlyZWN0b3J5DQpzYXZlUkRTKHNhbXBsZV9tYXRyaXgsIGZpbGUgPSAiLi9zYW1wbGVfbWF0cml4LnJkcyIpDQoNCiMgUmVhZCB0aGUgZmlsZSBiYWNrIGludG8gYSBuZXcgdmFyaWFibGUgdG8gdmVyaWZ5DQpyZXN0b3JlZF9tYXRyaXggPSByZWFkUkRTKCIuL3NhbXBsZV9tYXRyaXgucmRzIikNCg0KIyBDaGVjayBpZiB0aGUgcmVzdG9yZWQgb2JqZWN0IGlzIGlkZW50aWNhbCB0byB0aGUgb3JpZ2luYWwNCmlkZW50aWNhbChzYW1wbGVfbWF0cml4LCByZXN0b3JlZF9tYXRyaXgpDQoNCmBgYA0KDQojIyBBZHZhbmNlZCBJbmRleGluZw0KDQpVc2UgYHdoaWNoKClgIHdpdGggYGFyci5pbmQgPSBUUlVFYCB0byBnZXQgcm93IGFuZCBjb2x1bW4gaW5kaWNlcyBpbiBkYXRhIGZyYW1lcy4NCg0KYGBge3IgYmFzaWNzLXdoaWNofQ0KeCA9IGMoMywgNywgMiwgNiwgNywgOSwgMSkNCg0Kd2hpY2goeCA9PSA3KQ0Kd2hpY2goeCA+IDUpDQp4W3doaWNoKHggPiA1KV0NCg0KYGBgDQpgYGB7ciBiYXNpY3Mtd2hpY2gtbWluLW1heH0NCg0KIyBpbmRleGVzIG9mIG1heGltdW0gYW5kIG1pbmltdW0NCndoaWNoLm1heCh4KSAjIFsxXSA2DQp3aGljaC5taW4oeCkgIyBbMV0gNw0KDQojIEdldCB0aGUgYWN0dWFsIHZhbHVlcyANCnhbd2hpY2gubWF4KHgpXSAjIFsxXSA5DQp4W3doaWNoLm1pbih4KV0gIyBbMV0gMQ0KDQpgYGANCmBgYHtyIGJhc2ljcy13aGljaC1hcnJpbmR9DQoNCmRmID0gZGF0YS5mcmFtZShhID0gYygxLCAzLCA1KSwgDQogICAgICAgICAgICAgICAgIGIgPSBjKDUsIDMsIDEpDQogICAgICAgICAgICAgICAgICkNCg0KcHJpbnQoZGYpDQoNCndoaWNoKGRmID4gMywgYXJyLmluZCA9IFRSVUUpDQoNCmBgYA0KDQotLS0NCg0KIyBGdW5jdGlvbnMNCg0KIyMgUmV0dXJuaW5nIE11bHRpcGxlIE9iamVjdHMNCg0KR3JvdXAgbXVsdGlwbGUgb3V0cHV0cyAoZGF0YSwgbW9kZWxzLCBwbG90cykgaW50byBhIHNpbmdsZSBuZXN0ZWQgbGlzdC4NCg0KYGBge3IgZnVuY3Rpb25zLW11bHRpLXJldHVybn0NCg0KIyBEZWZpbmUgc2FtcGxlIGZ1bmN0aW9uIHdpdGggaW5wdXQgcGFyYW1ldGVycw0KbXlfZnVuY3Rpb24gPSBmdW5jdGlvbih4LCB5KXsNCiAgDQogICMgUGVyZm9ybSBzb21lIG9wZXJhdGlvbnMNCiAgcmVzdWx0MSA9IHheMg0KICByZXN1bHQyID0geV4zDQogIA0KICAjIFBlcmZvcm0gYWRkaXRpb25hbCBvcGVyYXRpb25zDQogIHJlc3VsdDMgPSB4XjIreV4yDQogIA0KICAjIENvbWJpbmUgcmVzdWx0cyBpbnRvIGEgbGlzdA0KICByZXN1bHRfbGlzdCAgICAgID0gbGlzdCgNCiAgICB4X3NxdWFyZWQgICAgICA9IHJlc3VsdDEsDQogICAgeV9jdWJlZCAgICAgICAgPSByZXN1bHQyLA0KICAgIHN1bV9vZl9zcXVhcmVzID0gcmVzdWx0Mw0KICApDQogIA0KICAjIFJldHVybiB0aGUgbGlzdCBvZiByZXN1bHRzDQogIHJldHVybihyZXN1bHRfbGlzdCkNCn0NCg0KIyBFeGFtcGxlIHVzYWdlIG9mIHRoZSBmdW5jdGlvbg0Kb3V0cHV0ID0gbXlfZnVuY3Rpb24oMywgNSkNCg0KIyBBY2Nlc3MgdGhlIHRoaXJkIHJlc3VsdCAoc3VtIG9mIDNeMiArIDVeMikNCm91dHB1dCRzdW1fb2Zfc3F1YXJlcw0KDQpgYGANCg0KIyMgT3B0aW9uYWwgQXJndW1lbnRzIHdpdGggRWxsaXBzaXMgKGAuLi5gKQ0KDQpQYXNzIHZhcmllZCBhcmd1bWVudHMgdG8gaW50ZXJuYWwgZnVuY3Rpb25zIHVzaW5nIGAuLi5gIC4NCg0KYGBge3IgZnVuY3Rpb25zLWVsbGlwc2lzfQ0KDQojIERlZmluZSB0aGUgbWFpbiBwbG90dGluZyBmdW5jdGlvbg0KcGxvdF9tb2Rlcm4gPSBmdW5jdGlvbih4LCB5LCAuLi4pIHsNCiAgDQogICMgSW50ZXJuYWwgZnVuY3Rpb24gdG8gY3JlYXRlIGEgc3R5bGl6ZWQgcGxvdA0KICBpbnRlcm5hbF9wbG90ID0gZnVuY3Rpb24oeCwgeSwgLi4uKSB7DQogICAgDQogICAgIyAxLiBTZXQgYSBzb2Z0IGJhY2tncm91bmQgY29sb3IgZm9yIHRoZSBwbG90IGFyZWENCiAgICBwYXIoYmcgPSAiI2Y0ZjdmNiIsIG1hciA9IGMoNSwgNSwgNCwgMikpDQogICAgDQogICAgIyAyLiBJbml0aWFsaXplIGFuIGVtcHR5IHBsb3Qgd2luZG93ICh0eXBlID0gIm4iKSB0byBsYXkgZG93biB0aGUgZ3JpZCBmaXJzdA0KICAgIHBsb3QoeCwgeSwgdHlwZSA9ICJuIiwgYnR5ID0gIm4iLCAuLi4pDQogICAgDQogICAgIyAzLiBBZGQgYSBjbGVhbiB3aGl0ZSBncmlkIA0KICAgIGdyaWQobnggPSBOVUxMLCBueSA9IE5VTEwsIGNvbCA9ICJ3aGl0ZSIsIGx0eSA9IDEsIGx3ZCA9IDEuNSkNCiAgICANCiAgICAjIDQuIERyYXcgYSBzbW9vdGggdHJlbmRsaW5lIHVuZGVybmVhdGggdGhlIHBvaW50cw0KICAgIGxpbmVzKHNwbGluZSh4LCB5KSwgY29sID0gIiNiMGJlYzUiLCBsd2QgPSAyKQ0KICAgIA0KICAgICMgNS4gUGxvdCB0aGUgYWN0dWFsIHBvaW50cywgcGFzc2luZyB0aGUgZWxsaXBzaXMgKC4uLikgZm9yIGN1c3RvbSBzdHlsaW5nDQogICAgcG9pbnRzKHgsIHksIC4uLikNCiAgfQ0KICANCiAgIyBFeGVjdXRlIHRoZSBpbnRlcm5hbCBmdW5jdGlvbg0KICBpbnRlcm5hbF9wbG90KHgsIHksIC4uLikNCn0NCg0KDQojIDEuIEdlbmVyYXRlIHNhbXBsZSBkYXRhDQpzZXQuc2VlZCg0MikNCnggPSAxOjE1DQp5ID0geF4yICsgcm5vcm0oMTUsIG1lYW4gPSAwLCBzZCA9IDE1KQ0KDQojIDIuIERyYXcgdGhlIHN0YW5kYXJkIGJhc2UgUiBwbG90IChMZWZ0IHNpZGUpDQpwbG90KHgsIHksIA0KICAgICBtYWluID0gIlN0YW5kYXJkIEJhc2UgUiBQbG90IiwNCiAgICAgeGxhYiA9ICJYIiwgeWxhYiA9ICJZIikNCg0KIyAzLiBEcmF3IG91ciBjdXN0b20gbW9kZXJuIHBsb3QgKFJpZ2h0IHNpZGUpDQpwbG90X21vZGVybih4LCB5LCANCiAgICAgICAgICAgIG1haW4gPSAiUXVhZHJhdGljIEdyb3d0aCBUcmFqZWN0b3J5IiwgDQogICAgICAgICAgICB4bGFiID0gIlRpbWUgKFgtYXhpcykiLCANCiAgICAgICAgICAgIHlsYWIgPSAiVmFsdWUgKFktYXhpcykiLCANCiAgICAgICAgICAgIGNvbCAgPSAiI2ZmZmZmZiIsICAgIyBQb2ludCBib3JkZXIgY29sb3INCiAgICAgICAgICAgIGJnICAgPSAiI2ZmNmI2YiIsICAgIyBQb2ludCBmaWxsIGNvbG9yICh3b3JrcyB3aXRoIHBjaCA9IDIxKQ0KICAgICAgICAgICAgcGNoICA9IDIxLCAgICAgICAgICAjIEZpbGxlZCBjaXJjbGUgDQogICAgICAgICAgICBjZXggID0gMiwgICAgICAgICAgICMgUG9pbnQgc2l6ZQ0KICAgICAgICAgICAgbHdkICA9IDEuNSwgICAgICAgICAjIEJvcmRlciB0aGlja25lc3MNCiAgICAgICAgICAgIGZhbWlseSA9ICJzYW5zIikgICAgIyBNb2Rlcm4gZm9udA0KDQpgYGANCg0KIyMgTXVsdGlwbGUgb3B0aW9uYWwgYXJndW1lbnRzIHdpdGggYGRvLmNhbGwoKWAuDQoNCmBgYHtyIGZ1bmN0aW9ucy1kby1jYWxsfQ0KDQojIERlZmluZSB0aGUgbWFpbiB3cmFwcGVyIGZ1bmN0aW9uDQpwcm9jZXNzX2FuZF9wbG90ID0gZnVuY3Rpb24oZGF0YSwgcGxvdF9hcmdzID0gbGlzdCgpLCAuLi4pIHsNCiAgDQogICMgSW50ZXJuYWw6IENhbGN1bGF0ZSBzdGF0aXN0aWNzIHVzaW5nIGFyZ3VtZW50cyBmcm9tIHRoZSBlbGxpcHNpcyAoLi4uKQ0KICBjYWxjdWxhdGVfc3VtbWFyeSA9IGZ1bmN0aW9uKGRhdGEsIC4uLikgew0KICAgIHN1bW1hcnkoZGF0YSwgLi4uKQ0KICB9DQogIA0KICAjIEludGVybmFsOiBDcmVhdGUgYSBoaXN0b2dyYW0gd2l0aCBtb2Rlcm4gZGVmYXVsdCBzdHlsaW5nDQogIGNyZWF0ZV9oaXN0b2dyYW0gPSBmdW5jdGlvbihkYXRhLCBtYWluID0gIkhpc3RvZ3JhbSIsIHhsYWIgPSAiVmFsdWVzIiwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY29sID0gIiM5MENBRjkiLCBib3JkZXIgPSAid2hpdGUiLCBicmVha3MgPSAxNSkgew0KICAgIGhpc3QoZGF0YSwgbWFpbiA9IG1haW4sIHhsYWIgPSB4bGFiLCBjb2wgPSBjb2wsIGJvcmRlciA9IGJvcmRlciwgYnJlYWtzID0gYnJlYWtzKQ0KICB9DQogIA0KICAjIDEuIEV4ZWN1dGUgc3VtbWFyeSB2aWEgZG8uY2FsbCwgcGFzc2luZyBhbG9uZyB0aGUgZWxsaXBzaXMgYXJndW1lbnRzDQogIHN0YXRpc3RpY3MgPSBkby5jYWxsKGNhbGN1bGF0ZV9zdW1tYXJ5LCBsaXN0KGRhdGEgPSBkYXRhLCAuLi4pKQ0KICANCiAgIyAyLiBJbmplY3QgZGF0YSBpbnRvIHBsb3RfYXJncyBhbmQgZXhlY3V0ZSB0aGUgaGlzdG9ncmFtDQogIHBsb3RfYXJncyRkYXRhID0gZGF0YQ0KICBkby5jYWxsKGNyZWF0ZV9oaXN0b2dyYW0sIHBsb3RfYXJncykNCiAgDQogICMgSW1wbGljaXRseSByZXR1cm4gdGhlIHN0YXRpc3RpY3Mgb2JqZWN0DQogIHN0YXRpc3RpY3MNCn0NCg0KIyBFeGFtcGxlIHVzYWdlDQpzZXQuc2VlZCgxODEyKQ0Kc2FtcGxlX2RhdGEgPSBybm9ybSgxMDApDQoNCiMgRGVmaW5lIGN1c3RvbSBhcmd1bWVudHMgdG8gb3ZlcnJpZGUgdGhlIGhpc3RvZ3JhbSBkZWZhdWx0cw0KY3VzdG9tX3Bsb3RfYXJncyA9IGxpc3QoDQogIG1haW4gICA9ICJDdXN0b21pemVkIERpc3RyaWJ1dGlvbiIsIA0KICBjb2wgICAgPSAiIzVDNkJDMCIsIA0KICBib3JkZXIgPSAid2hpdGUiLCANCiAgYnJlYWtzID0gMjUNCikNCg0KIyBDYWxsIHRoZSBmdW5jdGlvbjogDQojICdkaWdpdHMnIGFuZCAncXVhbnRpbGUudHlwZScgZmxvdyBpbnRvIHRoZSBlbGxpcHNpcyAoLi4uKSBmb3IgdGhlIHN1bW1hcnkNCiMgJ2N1c3RvbV9wbG90X2FyZ3MnIGZsb3dzIGludG8gdGhlIGhpc3RvZ3JhbQ0KZXhhbXBsZV9zdGF0cyA9IHByb2Nlc3NfYW5kX3Bsb3QoDQogIHNhbXBsZV9kYXRhLCANCiAgcGxvdF9hcmdzICAgICA9IGN1c3RvbV9wbG90X2FyZ3MsDQogIGRpZ2l0cyAgICAgICAgPSAyLCANCiAgcXVhbnRpbGUudHlwZSA9IDUNCikNCg0KIyBQcmludCB0aGUgcmVzdWx0aW5nIHN1bW1hcnkgdGFibGUNCnByaW50KGV4YW1wbGVfc3RhdHMpDQoNCmBgYA0KDQoNCi0tLQ0KDQojIERhdGEgU3RydWN0dXJlcyBhbmQgTWF0cml4IE1hdGgNCg0KIyMgTW9kaWZ5aW5nIERhdGEgRnJhbWVzDQoNClJlY3JlYXRlIHRoZSBzdHJ1Y3R1cmUgb2YgYSBkYXRhIGZyYW1lIGFuZCBwb3B1bGF0ZSBpdCBjbGVhbmx5Lg0KDQpgYGB7ciBkZi1tYW5pcHVsYXRpb259DQoNCiMgQ3JlYXRlIGEgc2FtcGxlIGRhdGEgZnJhbWUgd2l0aCBzb21lIGluaXRpYWwgZGF0YQ0Kc2FtcGxlX2RmID0gZGF0YS5mcmFtZSgNCiAgSUQgICAgID0gYygxLCAyLCAzKSwNCiAgTmFtZSAgID0gYygiQWxpY2UiLCAiQnJ1Y2UiLCAiQ2FuZGljZSIpLA0KICBBZ2UgICAgPSBjKDI1LCAzMCwgMzUpLA0KICBTYWxhcnkgPSBjKDUwMDAwLCA1NTAwMCwgNjAwMDApDQopDQoNCiMgUHJpbnQgdGhlIHNhbXBsZSBkYXRhIGZyYW1lDQpzYW1wbGVfZGYNCg0KYGBgDQoNCmBgYHtyIGRmLW51bGx9DQoNCmVtcHR5X2RmID0gc2FtcGxlX2RmW05VTEwsIF0NCmVtcHR5X2RmDQoNCmBgYA0KDQpgYGB7ciBkZi1kaXJlY3QtYXNzaWdubWVudH0NCg0KIyBGaWxsIHRoZSBlbXB0eSBkYXRhIGZyYW1lIGJ5IGRpcmVjdCBhc3NpZ25tZW50DQplbXB0eV9kZlsxLCBdID0gbGlzdChJRCA9IDQsIE5hbWUgPSAiRGF2aWQiLCBBZ2UgPSAyOCwgU2FsYXJ5ID0gNTUwMDApDQplbXB0eV9kZlsyLCBdID0gbGlzdChJRCA9IDUsIE5hbWUgPSAiRXZhIiwgQWdlID0gMzIsIFNhbGFyeSA9IDYyMDAwKQ0KDQplbXB0eV9kZg0KDQpgYGANCg0KIyMgTWF0cml4IE11bHRpcGxpY2F0aW9uICYgTG9naXN0aWMgUmVncmVzc2lvbg0KDQpgYGB7ciBtYXh0cml4LW11bHRpcGxpY2F0aW9uLWJhc2ljc30NCg0KIyBzZXQgc2VlZA0Kc2V0LnNlZWQoMTkxOCkNCg0KIyBEZWZpbmUgbWF0cml4IEEoMngzKQ0KQSA9IG1hdHJpeChjKDEsIDIsIDMsIDQsIDUsIDYpLCBucm93ID0gMiwgbmNvbCA9IDMpDQpwcmludChBKQ0KDQojIERlZmluZSBtYXRyaXggQigzeDIpDQpCID0gbWF0cml4KGMoNywgOCwgOSwgMTAsIDExLCAxMiksIG5yb3cgPSAzLCBuY29sID0gMikNCnByaW50KEIpDQoNCiMgUGVyZm9ybSByZWd1bGFyIG1hdHJpeCBtdWx0aXBsaWNhdGlvbigyeDIpDQpDID0gQSAlKiUgQg0KcHJpbnQoQykNCg0KIyBEZWZpbmUgbWF0cml4IEQoMngyKSBhbmQgZmlsbCBpdCBieSByb3dzDQpEID0gbWF0cml4KGMoMiwgNCwgNiwgOCksIG5yb3cgPSAyLCBuY29sID0gMiwgYnlyb3c9VCkNCnByaW50KEQpDQoNCiMgUGVyZm9ybSBlbGVtZW50LXdpc2UgbXVsdGlwbGljYXRpb24oMngyKQ0KRiA9IEMgKiBEDQpwcmludChGKQ0KDQpgYGANCg0KYGBge3IgbWF0cml4LW5uLXRyYWlufQ0KDQpzaW1wbGVfbG9naXN0aWNfTk49ZnVuY3Rpb24oWCx5LCBXMCxiMCwgbnVtX2Vwb2NocyxsZWFybmluZ19yYXRlLCB2ZXJib3NlPUYpew0KICANCiMgWCAgLT4gbiDDlyBudW1fZmVhdHVyZXMNCiMgeSAgLT4gbiDDlyAxDQojIFcwIC0+IG51bV9mZWF0dXJlcyDDlyAxDQojIGIwIC0+IDEgw5cgMQ0KDQogICMgZGVmaW5lIHRoZSBzaWdtb2lkIGZ1bmN0aW9uDQogIHNpZ21vaWQgPSBmdW5jdGlvbih6KSB7DQogICAxIC8gKDEgKyBleHAoLXopKQ0KICB9DQoNCiAgIyBvYnRhaW4gdGhlIG51bWJlciBvZiBkYXRhIHBvaW50cyBhbmQgZmVhdHVyZXMNCiAgbj1kaW0oWClbMV0NCiAgbnVtX2ZlYXR1cmVzPWRpbShYKVsyXQ0KDQogICMgbWVyZ2Ugd2VpZ2h0cyBhbmQgYmlhc2VzIGFuZCBhZGQgYSBjb2x1bW4gb2YgMXMgaW50byBYDQogIFdfbWF0cml4PW1hdHJpeChyYmluZChXMCxiMCksIG5yb3c9bnVtX2ZlYXR1cmVzKzEsbmNvbD0xKQ0KICBYX21hdHJpeD1tYXRyaXgoY2JpbmQoWCwgcmVwKDEsbikpLG5yb3c9bixuY29sPW51bV9mZWF0dXJlcysxKQ0KICANCiAgIyBwcmVhbGxvY2F0ZSBhIGRhdGEgZnJhbWUgdG8gc2F2ZSBsb3NzZXMgYW5kIGEgbWF0cml4IHRvIHNhdmUgZ3JhZGllbnRzDQogIGNvbF9uYW1lcyA9IGMoIkVwb2NoIiwgIkxvc3MiKSANCiAgZGZfbG9zcyA9IGRhdGEuZnJhbWUobWF0cml4KE5BLCBucm93ID0gbnVtX2Vwb2NocywgbmNvbCA9IGxlbmd0aChjb2xfbmFtZXMpKSkgDQogIGNvbG5hbWVzKGRmX2xvc3MpID0gY29sX25hbWVzDQogIEdyYWRpZW50cyA9IG1hdHJpeChOQSxucm93PSBudW1fZmVhdHVyZXMrMSwgbmNvbD1udW1fZXBvY2hzKQ0KICANCiMgVHJhaW5pbmcgbG9vcA0KZm9yIChlcG9jaCBpbiAxOm51bV9lcG9jaHMpDQogIHsNCiAgDQogICMgY29tcHV0ZSB0aGUgZm9yd2FyZCBwYXNzDQogIHogPSBYX21hdHJpeCUqJSBXX21hdHJpeCANCiAgeV9oYXQgPSBzaWdtb2lkKHopDQogIA0KICAjIGNhbGN1bGF0ZSBsb3NzIHVzaW5nIGJpbmFyeSBjcm9zcy1lbnRyb3B5DQogIGxvc3MgPSAtbWVhbih5ICogbG9nKHlfaGF0KSArICgxIC0geSkgKiBsb2coMSAtIHlfaGF0KSkNCiAgDQogICMgc2F2ZSBlcG9jaCBhbmQgbG9zcw0KICBkZl9sb3NzW2Vwb2NoLF09YyhlcG9jaCwgbG9zcykNCiAgDQogICMgY29tcHV0ZSBncmFkaWVudHMNCiAgZFcgPSB0KFhfbWF0cml4KSAlKiUgKHlfaGF0IC0geSkgLyBuDQogIA0KICAjIHNhdmUgZ3JhZGllbnQNCiAgR3JhZGllbnRzWyxlcG9jaF09ZFcNCiAgDQogICMgdXBkYXRlIHdlaWdodHMgYW5kIGJpYXMNCiAgV19tYXRyaXggPSBXX21hdHJpeCAtIGxlYXJuaW5nX3JhdGUgKiBkVw0KICANCiAgaWYodmVyYm9zZSE9RkFMU0Upew0KICAjIFByaW50IGxvc3MgZXZlcnkgdmVyYm9zZSBlcG9jaHMNCiAgICBpZiAoZXBvY2ggJSUgdmVyYm9zZSA9PSAwKSB7DQogICAgICBjYXQoIkVwb2NoOiIsIGVwb2NoLCAiTG9zczoiLCBsb3NzLCJXZWlnaHRzOiIsIFdfbWF0cml4WzE6bnVtX2ZlYXR1cmVzLF0sDQogICAgICAgICAgIkJpYXM6IiwgV19tYXRyaXhbbnVtX2ZlYXR1cmVzKzEsXSwgIlxuIikNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB9DQogICAgICAgICAgICAgICAgIH0NCiAgIH0NCiAgDQogIGw9bGlzdCgiV19lc3QiICAgICA9IFdfbWF0cml4WzE6bnVtX2ZlYXR1cmVzLF0sIA0KICAgICAgICAgImJfZXN0IiAgICAgPSBXX21hdHJpeFtudW1fZmVhdHVyZXMrMSxdLA0KICAgICAgICAgIkxvc3NfREYiICAgPSBkZl9sb3NzLCANCiAgICAgICAgICJHcmFkaWVudHMiID0gR3JhZGllbnRzKQ0KICByZXR1cm4obCkNCn0NCg0KYGBgDQoNCmBgYHtyIG1hdHJpeC1ubi1leGFtcGxlfQ0KDQpzZXQuc2VlZCgxOTg2KQ0KDQojIEdlbmVyYXRlIGEgc3ludGhldGljIGRhdGFzZXQNCm5fZXhhbXBfMSA9IDEwMDANClhfZXhhbXBfMSA9IG1hdHJpeChybm9ybShuX2V4YW1wXzEgKiAyKSwgbmNvbCA9IDIpDQp5X2V4YW1wXzEgPSBhcy5udW1lcmljKFhfZXhhbXBfMVssMV0gKyBYX2V4YW1wXzFbLDJdID4gMCkNCg0KIyBJbml0aWFsaXplIHdlaWdodHMgYW5kIGJpYXMNClcwX2V4YW1wXzEgPSBtYXRyaXgocnVuaWYoMiksIG5yb3cgPSAyKQ0KYjBfZXhhbXBfMSA9IHJ1bmlmKDEpDQoNCmxvZ2lzdGljX05OX3RyYWluaW5nX2V4YW1wXzE9c2ltcGxlX2xvZ2lzdGljX05OKFg9WF9leGFtcF8xLCB5PXlfZXhhbXBfMSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBXMD1XMF9leGFtcF8xLGIwPWIwX2V4YW1wXzEsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbnVtX2Vwb2Nocz0xMDAwLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGVhcm5pbmdfcmF0ZT0wLjAxLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHZlcmJvc2U9MTAwKQ0KDQojIFByaW50IGZpbmFsIHdlaWdodHMgYW5kIGJpYXMNCmNhdCgiRXN0aW1hdGVkIFdlaWdodHM6IiwgbG9naXN0aWNfTk5fdHJhaW5pbmdfZXhhbXBfMSRXX2VzdCwgIlxuIikNCmNhdCgiRXN0aW1hdGVkIEJpYXM6IiwgbG9naXN0aWNfTk5fdHJhaW5pbmdfZXhhbXBfMSRiX2VzdCwgIlxuIikNCg0KYGBgDQoNCiMjIFNpbmd1bGFyIFZhbHVlIERlY29tcG9zaXRpb24gKFNWRCkgZnJvbSBTY3JhdGNoDQoNCmBgYHtyIG1hdHJpeC1zdmR9DQoNCnN2ZF9jdXN0b20gPSBmdW5jdGlvbihBKSB7DQogICMgQ2hlY2sgaWYgQSBpcyBhIG1hdHJpeA0KICBpZiAoIWlzLm1hdHJpeChBKSkgc3RvcCgiSW5wdXQgbXVzdCBiZSBhIG1hdHJpeCIpDQogIA0KICAjIFN0ZXAgMTogQ29tcHV0ZSBBXlQgKiBBDQogIEF0QSA9IHQoQSkgJSolIEENCiAgDQogICMgU3RlcCAyOiBFaWdlbiBkZWNvbXBvc2l0aW9uIG9mIEFeVCAqIEENCiAgZWlnZW5fQXRBID0gZWlnZW4oQXRBKQ0KICANCiAgIyBFaWdlbnZhbHVlcyBhbmQgZWlnZW52ZWN0b3JzDQogIGVpZ2VudmFsdWVzID0gZWlnZW5fQXRBJHZhbHVlcw0KICBWID0gZWlnZW5fQXRBJHZlY3RvcnMNCiAgDQogICMgU3RlcCAzOiBDb21wdXRlIHNpbmd1bGFyIHZhbHVlcw0KICBzaW5ndWxhcl92YWx1ZXMgPSBzcXJ0KGVpZ2VudmFsdWVzKQ0KICANCiAgIyBTdGVwIDQ6IENvbXB1dGUgVSBtYXRyaXgNCiAgVSA9IG1hdHJpeCgwLCBucm93ID0gbnJvdyhBKSwgbmNvbCA9IG5yb3coQSkpDQogIGZvciAoaSBpbiAxOm5yb3coQSkpIHsNCiAgICBpZiAoc2luZ3VsYXJfdmFsdWVzW2ldICE9IDApIHsNCiAgICAgIFVbLCBpXSA9IChBICUqJSBWWywgaV0pIC8gc2luZ3VsYXJfdmFsdWVzW2ldDQogICAgfQ0KICB9DQogIA0KICAjIFN0ZXAgNTogQ29uc3RydWN0IFNpZ21hIG1hdHJpeA0KICBTaWdtYSA9IGRpYWcoc2luZ3VsYXJfdmFsdWVzKQ0KICANCiAgIyBSZXR1cm4gdGhlIFNWRCBjb21wb25lbnRzDQogIGxpc3QoVSA9IFUsIFNpZ21hID0gU2lnbWEsIFYgPSBWKQ0KfQ0KDQpgYGANCg0KDQpgYGB7ciBtYXRyaXgtc3ZkLWV4YW1wbGV9DQoNCnNldC5zZWVkKDE5MDMpDQoNCkEgPSBtYXRyaXgocm5vcm0oOSksIG5yb3cgPSAzLCBuY29sID0gMykNCnN2ZF9yZXN1bHQgPXN2ZF9jdXN0b20oQSkNCg0KIyBVVlRedCA9PSBBIChldmFsdWF0ZXMgdG8gVFJVRSkNCmFsbC5lcXVhbChzdmRfcmVzdWx0JFUlKiVzdmRfcmVzdWx0JFNpZ21hJSoldChzdmRfcmVzdWx0JFYpLCBBKQ0KDQpgYGANCg0KIyMgUHJlLWFsbG9jYXRlIGxpc3RzDQoNCmBgYHtyIHByZWFsbG9jYXRlLWxpc3RzfQ0KDQojIFByZWFsbG9jYXRlIHRoZSBlbXB0eSBsaXN0IHdpdGggMyBlbGVtZW50cw0KbiAgICA9IDMNCmxpc3QgPSB2ZWN0b3IoImxpc3QiLCBsZW5ndGggPSBuKQ0KDQojIEZpbGwgaW4gdGhlIGZpcnN0IGVsZW1lbnQgd2l0aCBhbm90aGVyIGxpc3QNCmxpc3RbWzFdXSA9IGxpc3QoYSA9IDUsIGIgPSA4LCBjID0gMTMpDQoNCiMgRmlsbCBpbiB0aGUgc2Vjb25kIGVsZW1lbnQgd2l0aCBhIGRhdGEgZnJhbWUNCmxpc3RbWzJdXSA9IGRhdGEuZnJhbWUoTmFtZSA9IGMoIlp1IiwgIkdlb3JnZXMiKSwgQWdlID0gYygzLCAxNCkpDQoNCiMgRmlsbCBpbiB0aGUgdGhpcmQgZWxlbWVudCB3aXRoIGEgdmVjdG9yDQpsaXN0W1szXV0gPSBjKDIxLCAzNCwgNTUsIDg5LCAxNDQpDQoNCmxpc3RbWzFdXSRhDQpsaXN0W1syXV0kQWdlDQpsaXN0W1szXV0NCg0KYGBgDQoNCiMjIFB5dGhvbi1MaWtlIERpY3Rpb25hcmllcyBpbiBSDQoNClVzZSBlbnZpcm9ubWVudHMgZm9yIHJlZmVyZW5jZSBzZW1hbnRpY3MgYW5kIE8oMSkgbG9va3VwLg0KDQpgYGB7ciBkaWN0LWVudn0NCg0KIyBGaXJzdCB3YXkgdXNpbmcgYW4gZW52aXJvbm1lbnQgDQpkaWN0X2VudiAgID0gbmV3LmVudigpDQoNCiMgQXNzaWduIGtleS12YWx1ZSBwYWlycyB0byB0aGUgZW52aXJvbm1lbnQNCmRpY3RfZW52JGEgPSAyDQpkaWN0X2VudiRiID0gNw0KZGljdF9lbnYkYyA9IDENCg0KIyBBZGQgYSBuZXcga2V5LXZhbHVlIHBhaXINCmRpY3RfZW52JGQgPSA4DQoNCiMgTW9kaWZ5IGFuIGV4aXN0aW5nIHZhbHVlDQpkaWN0X2VudiRhID0gMg0KDQojIExpc3QgYWxsIG9iamVjdHMgaW4gdGhlIGVudmlyb25tZW50DQpscyhkaWN0X2VudikNCg0KIyBHZXQgYSB2YWx1ZSBieSBrZXkNCmdldCgiYyIsIGVudmlyID0gZGljdF9lbnYpDQoNCmBgYA0KYGBge3IgZGljdC1saXN0c30NCg0KIyBnZW5lcmF0ZXMgYSBzZXQgb2YgMTAwIGNvbG9ycyB1c2luZyB0aGUgcmFpbmJvdyBmdW5jdGlvbi4NCmNvbHMgPSByYWluYm93KDEwMCkNCg0KIyBjcmVhdGUgYSBzZXF1ZW5jZSBvZiB0aGUgZmlyc3QgMTAwIG9kZCBudW1iZXJzIHN0YXJ0aW5nIGZyb20gMS4NCm51bWIgPSBzZXEoMSwyMDAsMikNCg0KIyBwcmVhbGxvY2F0ZSB0aHJlZSBsaXN0cyB0byBzdG9yZSB0aGUgY29sb3JzLCB0aGUgbnVtYmVycywgYW5kIHRoZSBpbmRpY2VzDQpjb2xfdmFscyA9IHZlY3RvcigibGlzdCIsMTAwKQ0KbnVtX3ZhbHMgPSB2ZWN0b3IoImxpc3QiLDEwMCkNCmluZF92YWxzID0gdmVjdG9yKCJsaXN0IiwxMDApDQoNCiMgaXRlcmF0ZQ0KZm9yKGMgaW4gMTpsZW5ndGgobnVtYikpew0KICBjb2xfdmFsc1tbY11dID0gY29sc1tjXQ0KICBudW1fdmFsc1tbY11dID0gbnVtYltjXQ0KICBpbmRfdmFsc1tbY11dID0gYw0KfQ0KDQojIHJldHJpZXZlIHRoZSBjb2xvciBhc3NvY2lhdGVkIHdpdGggdGhlIG51bWJlciAxNTcgKCIjQUQwMEZGIikNCmNvbF92YWxzW1t3aGljaChudW1fdmFscz09MTU3KV1dDQoNCiMgZmluZCB0aGUgbnVtYmVyIHRoYXQgY29ycmVzcG9uZHMgdG8gdGhlIGNvbG9yICIjQUQwMEZGIiAoMTU3KQ0KbnVtX3ZhbHNbW3doaWNoKGNvbF92YWxzPT0iI0FEMDBGRiIpXV0NCg0KI2NoZWNrIGlmIHRoZSBjb2xvciBhbmQgdGhlIG51bWJlciBhcmUgYXNzb2NpYXRlZCB3aXRoIHRoZSBzYW1lIGluZGV4IChUUlVFKQ0KaW5kX3ZhbHNbW3doaWNoKGNvbF92YWxzPT0iI0FEMDBGRiIpXV09PWluZF92YWxzW1t3aGljaChudW1fdmFscz09MTU3KV1dDQoNCmBgYA0KDQojIyBWZWN0b3JzDQoNCmBgYHtyIHZlY3RvcnMtdHlwZXMsIGV2YWw9RkFMU0V9DQoNCm51bXMgICA9IGMoMSwgMiwgMykgICAgICAgICAgICAgICAjIG51bWVyaWMNCmNoYXJzICA9IGMoImEiLCAiYiIsICJjIikgICAgICAgICAjIGNoYXJhY3Rlcg0KbG9naWMgID0gYyhUUlVFLCBGQUxTRSwgVFJVRSkgICAgICMgbG9naWNhbA0Kc2VxXzEwID0gMToxMCAgICAgICAgICAgICAgICAgICAgICMgaW50ZWdlciBzZXF1ZW5jZQ0KcmVwX3BpID0gcmVwKHBpLCAzKSAgICAgICAgICAgICAgICMgcmVwZWF0IGEgdmFsdWUNCg0KYGBgDQoNCmBgYHtyIHZlY3RvcnMtZXhhbXBsZXN9DQoNCnggPSAxOjUNCnkgPSBzZXEoMTAsIDUwLCAxMCkNCg0KeCArIHkgICAgICAgICMgMTEgMjIgMzMgNDQgNTUNCnheMiArIGxvZyh5KSAjIGNvbWJpbmVkIG51bWVyaWMgb3BzDQoNCnZhbHMgPSBjKDQsIDcsIDExLCAzLCA5KQ0KDQojIEtlZXAgZWxlbWVudHMgPiA1DQp2YWxzW3ZhbHMgPiA1XSAgICAgICAgICAgICAgICAgICMgNyAxMSA5DQoNCiMgUmVwbGFjZSBvdXRsaWVycyB3aXRoIE5BDQp2YWxzW3ZhbHMgPiAxMF0gPSBOQSAgICAgICAgICAgIyA0IDcgTkEgMyA5DQpwcmludCh2YWxzKQ0KDQpgYGANCi0tLQ0KDQojIFZlY3Rvcml6ZWQgSXRlcmF0aW9ucw0KDQpSJ3MgYGFwcGx5YCBmYW1pbHkgYXZvaWRzIGV4cGxpY2l0IGxvb3BzIHdpdGggY29uY2lzZSwgdmVjdG9yaXplZCBDLWxldmVsIGV4ZWN1dGlvbi4NCg0KYGBge3IgYXBwbHl9DQoNCiMgU2FtcGxlIDTDlzMgbWF0cml4DQptYXQgPSBtYXRyaXgoMToxMiwgbnJvdyA9IDQsIGJ5cm93ID0gVFJVRSkNCg0KIyBDb2x1bW4gbWVhbnMNCmFwcGx5KG1hdCwgMiwgbWVhbikNCg0KIyBSb3cgc3RhbmRhcmQgZGV2aWF0aW9ucw0KYXBwbHkobWF0LCAxLCBzZCkNCg0KYGBgDQoNCmBgYHtyIGxhcHBseX0NCg0Kc2V0LnNlZWQoMTk0MikNCg0KIyAxLiBDcmVhdGUgYSBsaXN0IG9mIHNjb3JlIHZlY3RvcnMgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCnNjb3JlcyA9IGxhcHBseSgxOjUsIFwoaSkgcm5vcm0oNTAsIG1lYW4gPSA3MCArIGkgKiA1LCBzZCA9IDgpKQ0KbmFtZXMoc2NvcmVzKSA9IHBhc3RlMCgiQ2xhc3NfIiwgMTo1KQ0KDQojIDIuIENvbXB1dGUgdGhlIG1lYW4gc2NvcmUgZm9yIGV2ZXJ5IGNsYXNzIC0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KY2xhc3NfbWVhbnMgPSBsYXBwbHkoc2NvcmVzLCBtZWFuKQ0KDQpjbGFzc19tZWFucw0KDQpgYGANCg0KYGBge3IgbWFwcGx5fQ0KDQojIENyZWF0ZSAzIG5vcm1hbGx5IGRpc3RyaWJ1dGVkIHZlY3RvcnMgd2l0aCBkaWZmZXJlbnQgbWVhbnMgYW5kIFNEcw0Kc2V0LnNlZWQoMTkxNCkNCnBhcmFtcyA9IGRhdGEuZnJhbWUobXUgPSBjKDAsIDUsIDEwKSwgc2QgPSBjKDEsIDIsIDMpKQ0KDQpzYW1wbGVzID0gbWFwcGx5KA0KICBGVU4gPSBybm9ybSwNCiAgbiAgID0gNSwgICAgICAgICAgICAgICAgICAjIHJlY3ljbGVkIGZvciBlYWNoIGNhbGwNCiAgbWVhbiA9IHBhcmFtcyRtdSwNCiAgc2QgICA9IHBhcmFtcyRzZCwNCiAgU0lNUExJRlkgPSBGQUxTRSAgICAgICAgICAjIGtlZXAgYXMgbGlzdCBvZiB2ZWN0b3JzDQopDQoNCnN0cihzYW1wbGVzKQ0KDQpgYGANCg0KYGBge3Igc2FwcGx5fQ0KDQpudW1zID0gbGlzdChhID0gMTo0LCBiID0gNTo3LCBjID0gODo5KQ0KDQojIFN1bXMgZWFjaCBlbGVtZW50IHJldHVybmluZyBhIG51bWVyaWMgdmVjdG9yDQpzYXBwbHkobnVtcywgc3VtKQ0KDQp2ZWMgPSAxOjYNCnNhcHBseSh2ZWMsIGZ1bmN0aW9uKHgpIHheMikNCg0KYGBgDQoNCi0tLQ0KDQojIFBhcmFsbGVsIENvbXB1dGluZw0KDQpgYGB7ciBwYXJhbGxlbC1zZXR1cH0NCmxpYnJhcnkocGFyYWxsZWwpDQpuX2NvcmVzID0gbWF4KDFMLCBkZXRlY3RDb3JlcygpIC0gMUwpDQpwcmludChuX2NvcmVzKQ0KYGBgDQoNClVzZSBgcGFyTGFwcGx5YCBmb3IgY3Jvc3MtcGxhdGZvcm0gcGFyYWxsZWwgZXhlY3V0aW9uLCBvciBgbWNsYXBwbHlgIGZvciBVbml4LWJhc2VkIHN5c3RlbXMuDQoNCmBgYHtyIHBhcmFsbGVsLXBhcmxhcHBseX0NCg0KIyBFeHBlbnNpdmUgZnVuY3Rpb246IE1vbnRlIENhcmxvIGVzdGltYXRlIG9mIHBpDQplc3RpbWF0ZV9waSA9IGZ1bmN0aW9uKG4gPSAxZTYpIHsNCiAgeCA9IHJ1bmlmKG4pDQogIHkgPSBydW5pZihuKQ0KICBtZWFuKHheMiArIHleMiA8PSAxKSAqIDQNCn0NCg0Kbl9yZXAgPSAzMA0KDQojIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KIyAxLiBTRVFVRU5USUFMIFJVTg0KIyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCnNldC5zZWVkKDE5ODYpDQoNCnRfc2VxID0gc3lzdGVtLnRpbWUoew0KICBzZXFfcmVzdWx0cyA9IGxhcHBseSgxOm5fcmVwLCBmdW5jdGlvbihpKSB7DQogICAgdDAgPSBwcm9jLnRpbWUoKVsiZWxhcHNlZCJdDQogICAgcGlfZXN0ID0gZXN0aW1hdGVfcGkoKQ0KICAgIA0KICAgICMgUmV0dXJuIGJvdGggdGhlIGVzdGltYXRlIGFuZCB0aGUgdGltZSBpdCB0b29rDQogICAgZGF0YS5mcmFtZShydW4gPSBpLCBwaV9lc3QgPSBwaV9lc3QsIGVsYXBzZWQgPSBwcm9jLnRpbWUoKVsiZWxhcHNlZCJdIC0gdDApDQogIH0pDQp9KQ0KDQojIEJpbmQgaW50byBhIHNpbmdsZSBkYXRhIGZyYW1lDQpzZXFfZGYgPSBkby5jYWxsKHJiaW5kLCBzZXFfcmVzdWx0cykNCg0KIyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCiMgMi4gUEFSQUxMRUwgUlVODQojIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0Kbl9jb3JlcyA9IG1heCgxLCBkZXRlY3RDb3JlcygpIC0gMSkNCmNsID0gbWFrZUNsdXN0ZXIobl9jb3JlcykNCmNsdXN0ZXJFeHBvcnQoY2wsIHZhcmxpc3QgPSAiZXN0aW1hdGVfcGkiKQ0KDQpzZXQuc2VlZCgxOTg2KQ0KDQp0X3BhciA9IHN5c3RlbS50aW1lKHsNCiAgcGFyX3Jlc3VsdHMgPSBwYXJMYXBwbHkoY2wsIDE6bl9yZXAsIGZ1bmN0aW9uKGkpIHsNCiAgICB0MCA9IHByb2MudGltZSgpWyJlbGFwc2VkIl0NCiAgICBwaV9lc3QgPSBlc3RpbWF0ZV9waSgpDQogICAgDQogICAgIyBSZXR1cm4gYm90aCB0aGUgZXN0aW1hdGUgYW5kIHRoZSB0aW1lIGl0IHRvb2sNCiAgICBkYXRhLmZyYW1lKHJ1biA9IGksIHBpX2VzdCA9IHBpX2VzdCwgZWxhcHNlZCA9IHByb2MudGltZSgpWyJlbGFwc2VkIl0gLSB0MCkNCiAgfSkNCn0pDQoNCnN0b3BDbHVzdGVyKGNsKQ0KDQojIEJpbmQgaW50byBhIHNpbmdsZSBkYXRhIGZyYW1lDQpwYXJfZGYgPSBkby5jYWxsKHJiaW5kLCBwYXJfcmVzdWx0cykNCg0KIyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCiMgMy4gQ09NUEFSRSBSRVNVTFRTDQojIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KIyBDaGVjayBpZiB0aGUgZXN0aW1hdGVzIGFyZSBtYXRoZW1hdGljYWxseSBlcXVpdmFsZW50DQphbGwuZXF1YWwoc2VxX2RmJHBpX2VzdCwgcGFyX2RmJHBpX2VzdCkNCg0KIyBQcmludCB0aGUgdGltaW5nIHJlc3VsdHMNCmNhdCgiVG90YWwgU2VxdWVudGlhbCBUaW1lOiAgICAgIiwgcm91bmQodF9zZXFbImVsYXBzZWQiXSwgMyksICJzZWNvbmRzXG4iKQ0KY2F0KCJUb3RhbCBQYXJhbGxlbCBUaW1lOiAgICAgICAiLCByb3VuZCh0X3BhclsiZWxhcHNlZCJdLCAzKSwgInNlY29uZHNcblxuIikNCg0KY2F0KCJBdmcgU2VxdWVudGlhbCBwZXIgcnVuOiAgICAiLCByb3VuZChtZWFuKHNlcV9kZiRlbGFwc2VkKSwgMyksICJzZWNvbmRzXG4iKQ0KY2F0KCJBdmcgUGFyYWxsZWwgcGVyIHJ1bjogICAgICAiLCByb3VuZChtZWFuKHBhcl9kZiRlbGFwc2VkKSwgMyksICJzZWNvbmRzXG4iKQ0KDQpgYGANCg0KYGBge3J9DQoNCiMgSW5zdGFsbCBpZiBuZWVkZWQ6IGluc3RhbGwucGFja2FnZXMoYygiZnV0dXJlIiwgImZ1dHVyZS5hcHBseSIpKQ0KbGlicmFyeShmdXR1cmUpDQpsaWJyYXJ5KGZ1dHVyZS5hcHBseSkNCg0KIyAxLiBEZWZpbmUgdGhlIHdvcmtob3JzZSBmdW5jdGlvbg0KZXN0aW1hdGVfcGkgPSBmdW5jdGlvbihuID0gMWU2KSB7DQogIG1lYW4ocnVuaWYobileMiArIHJ1bmlmKG4pXjIgPD0gMSkgKiA0DQp9DQoNCm5fcmVwID0gMjANCg0KIyAyLiBUZWxsIFIgeW91ciAicGxhbiIgKHJ1biBhc3luY2hyb25vdXNseSBhY3Jvc3MgbXVsdGlwbGUgYmFja2dyb3VuZCBzZXNzaW9ucykNCnBsYW4obXVsdGlzZXNzaW9uLCB3b3JrZXJzID0gYXZhaWxhYmxlQ29yZXMoKSAtIDEpDQoNCnNldC5zZWVkKDE5ODYpDQoNCiMgMy4gRXhlY3V0ZSEgZnV0dXJlX2xhcHBseSBhY3RzIGp1c3QgbGlrZSBsYXBwbHksIGJ1dCByb3V0ZXMgdHJhZmZpYyBhc3luY2hyb25vdXNseQ0KdF9mdXR1cmUgPSBzeXN0ZW0udGltZSh7DQogIA0KICAjIGZ1dHVyZS5zZWVkID0gVFJVRSBlbnN1cmVzIHJhbmRvbSBudW1iZXJzIGFyZSBzYWZlbHkgZ2VuZXJhdGVkIGFjcm9zcyBjb3Jlcw0KICBmdXR1cmVfcmVzdWx0cyA9IGZ1dHVyZV9sYXBwbHkoMTpuX3JlcCwgZnVuY3Rpb24oaSkgew0KICAgIHQwID0gcHJvYy50aW1lKClbImVsYXBzZWQiXQ0KICAgIHBpX2VzdCA9IGVzdGltYXRlX3BpKCkNCiAgICBkYXRhLmZyYW1lKHJ1biA9IGksIHBpX2VzdCA9IHBpX2VzdCwgZWxhcHNlZCA9IHByb2MudGltZSgpWyJlbGFwc2VkIl0gLSB0MCkNCiAgfSwgZnV0dXJlLnNlZWQgPSBUUlVFKQ0KICANCn0pDQoNCiMgNC4gQmluZCB0aGUgcmVzdWx0cw0KZnV0dXJlX2RmID0gZG8uY2FsbChyYmluZCwgZnV0dXJlX3Jlc3VsdHMpDQoNCiMgNS4gQWx3YXlzIGV4cGxpY2l0bHkgY2xvc2UgYmFja2dyb3VuZCB3b3JrZXJzIHdoZW4gZG9uZQ0KcGxhbihzZXF1ZW50aWFsKQ0KDQpjYXQoIlRvdGFsIEZ1dHVyZSBBc3luYyBUaW1lOiAiLCByb3VuZCh0X2Z1dHVyZVsiZWxhcHNlZCJdLCAzKSwgInNlY29uZHNcbiIpDQpjYXQoIkF2ZXJhZ2UgRnV0dXJlIEFzeW5jIHBlciBydW46ICIsIHJvdW5kKG1lYW4oZnV0dXJlX2RmJGVsYXBzZWQpLCAzKSwgInNlY29uZHNcbiIpDQoNCmBgYA0KLS0tDQoNCiMgUnVubmluZyBQeXRob24gQ29kZSBpbiBSDQoNCkV4ZWN1dGUgUHl0aG9uIHNjcmlwdHMgYW5kIHBhc3MgZGF0YSBmcmFtZXMgYmFjayBhbmQgZm9ydGggdXNpbmcgYHtyZXRpY3VsYXRlfWAuDQoNCmBgYHtyfQ0KDQpsaWJyYXJ5KHJldGljdWxhdGUpDQoNCiMgU2hvdyB3aGljaCBQeXRob24gaXMgYmVpbmcgdXNlZA0KcHlfY29uZmlnKCkNCg0KI3JldGljdWxhdGU6OnB5X2luc3RhbGwoInBhbmRhcyIpDQoNCmBgYA0KDQoNCmBgYHtyIHJldGljdWxhdGUtaW5saW5lLCBldmFsPUZBTFNFfQ0KDQojIFJ1biBQeXRob24gaW5saW5lDQpweV9ydW5fc3RyaW5nKCINCnggPSBbMSwgMiwgM10NCnkgPSBbaSoqMiBmb3IgaSBpbiB4XQ0KIikNCg0KIyBBY2Nlc3MgUHl0aG9uIHZhcmlhYmxlcyBuYXRpdmVseSBpbiBSDQpweSR5DQpweSR4DQoNCmBgYA0KYGBge3J9DQoNCm5wID0gaW1wb3J0KCJudW1weSIpDQoNCiMgQ3JlYXRlIE51bVB5IGFycmF5cyBhbmQgY29tcHV0ZSBzdGF0aXN0aWNzDQphID0gbnAkYXJyYXkoYygxLCAyLCAzLCA0LCA1KSkNCm1lYW5fYSA9IG5wJG1lYW4oYSkNCm1lYW5fYQ0KDQpgYGANCg0KYGBge3J9DQoNCiMgQ3JlYXRlIGEgc3RhbmRhcmQgUiBkYXRhZnJhbWUNCnJfZGYgPSBkYXRhLmZyYW1lKGEgPSAxOjMsIGIgPSBjKDQsIDUsIDYpKQ0KDQpgYGANCg0KYGBge3B5dGhvbn0NCg0KaW1wb3J0IHBhbmRhcyBhcyBwZA0KDQojIFB1bGwgdGhlIGRhdGFmcmFtZSBmcm9tIFIgaW50byBQeXRob24NCnB5X2RmID0gci5yX2RmDQoNCiMgUGVyZm9ybSBvdXIgUHl0aG9uIG9wZXJhdGlvbnMNCnB5X2RmWydzdW0nXSA9IHB5X2RmWydhJ10gKyBweV9kZlsnYiddDQoNCmBgYA0KDQpgYGB7cn0NCg0KIyBQdWxsIHRoZSBtb2RpZmllZCBkYXRhZnJhbWUgYmFjayBmcm9tIFB5dGhvbiBpbnRvIFINCmZpbmFsX3JfZGYgPSBweSRweV9kZg0KDQpwcmludChmaW5hbF9yX2RmKQ0KDQpgYGANCi0tLQ0KDQojIEdQVSBDb21wdXRpbmcgaW4gUg0KDQo+ICpOb3RlOiBUaGUgZm9sbG93aW5nIGNvZGUgYmxvY2tzIGFyZSBzdHJ1Y3R1cmVkIGJ1dCBpbnRlbnRpb25hbGx5IHVuZXZhbHVhdGVkIChgZXZhbD1GQUxTRWApIGluIHRoaXMgbm90ZWJvb2sgdG8gcHJldmVudCByZW5kZXJpbmcgZmFpbHVyZXMgb24gZW52aXJvbm1lbnRzIGxhY2tpbmcgQ1VEQS9HUFUgY29uZmlndXJhdGlvbnMuKg0KDQo+ICpOb3RlIG9uIFdpbmRvd3MgQ29tcGF0aWJpbGl0eTogRW5hYmxpbmcgR1BVIGFjY2VsZXJhdGlvbiBmb3IgVGVuc29yRmxvdyBpbiBhIFdpbmRvd3MgZW52aXJvbm1lbnQgaXMgbm90IGEgInBsdWctYW5kLXBsYXkiIHByb2Nlc3MuIFRoaXMgcmVxdWlyZXMgYSBzcGVjaWZpYyBpbmZyYXN0cnVjdHVyZSBzdGFjazogYSBXU0wyIChXaW5kb3dzIFN1YnN5c3RlbSBmb3IgTGludXgpIGluc3RhbGxhdGlvbiwgYW4gVWJ1bnR1IFIgU2VydmVyIGFuZCB0aGUgZGVkaWNhdGVkIEdQVSB2ZXJzaW9uIG9mIFRlbnNvckZsb3cuIEZvciBhIHN0ZXAtYnktc3RlcCB3YWxrdGhyb3VnaCBvbiBjb25maWd1cmluZyB0aGlzIGVudmlyb25tZW50LCBwbGVhc2UgcmVmZXIgdG8gQ2hhcHRlciA2IG9mIG15IE1lZGl1bSBhcnRpY2xlLCB3aGVyZSBJIGNvdmVyIHRoZSBmdWxsIGJhY2tlbmQgc2V0dXAgcmVxdWlyZWQgdG8gZ2V0IFIgYW5kIHlvdXIgR1BVIHRhbGtpbmcgdG8gZWFjaCBvdGhlci4qDQoNCiMjIEdQVSBJbml0aWFsaXphdGlvbg0KDQpgYGB7ciBncHUtdmVyaWZ5LCBldmFsPUZBTFNFfQ0KDQpsaWJyYXJ5KHRlbnNvcmZsb3cpDQpsaWJyYXJ5KGtlcmFzMykNCg0KIyBDaGVjayBmb3IgdGhlIEdQVQ0KdGYkY29uZmlnJGxpc3RfcGh5c2ljYWxfZGV2aWNlcygiR1BVIikNCg0KYGBgDQoNCiMjIE5ldXJhbCBOZXR3b3JrIGZvciBOb24tTGluZWFyIENsYXNzaWZpY2F0aW9uDQoNCmBgYHtyIGdwdS1ubi1jb25maWcsIGV2YWw9RkFMU0V9DQoNCnNldC5zZWVkKDE5MjUpDQoNCm5fcGVyX2NsYXNzIDwtIDIwMDANCg0KIyBJbm5lciBjaXJjbGUgKGNsYXNzIDApDQp0aGV0YTEgPC0gcnVuaWYobl9wZXJfY2xhc3MsIDAsIDIgKiBwaSkNCnIxICAgICA8LSBybm9ybShuX3Blcl9jbGFzcywgbWVhbiA9IDEsIHNkID0gMC4wOCkNCngxICAgICA8LSBjYmluZChyMSAqIGNvcyh0aGV0YTEpLCByMSAqIHNpbih0aGV0YTEpKQ0KeTEgICAgIDwtIHJlcCgwLCBuX3Blcl9jbGFzcykNCg0KIyBPdXRlciByaW5nIChjbGFzcyAxKQ0KdGhldGEyIDwtIHJ1bmlmKG5fcGVyX2NsYXNzLCAwLCAyICogcGkpDQpyMiAgICAgPC0gcm5vcm0obl9wZXJfY2xhc3MsIG1lYW4gPSAyLCBzZCA9IDAuMDgpDQp4MiAgICAgPC0gY2JpbmQocjIgKiBjb3ModGhldGEyKSwgcjIgKiBzaW4odGhldGEyKSkNCnkyICAgICA8LSByZXAoMSwgbl9wZXJfY2xhc3MpDQoNCiMgRnVsbCBkYXRhc2V0DQp4IDwtIHJiaW5kKHgxLCB4MikNCnkgPC0gYyh5MSwgeTIpDQoNCiMgU2h1ZmZsZQ0KaWR4IDwtIHNhbXBsZShzZXFfbGVuKG5yb3coeCkpKQ0KeCAgIDwtIHhbaWR4LCBdDQp5ICAgPC0geVtpZHhdDQoNCiMgVHJhaW4gLyB0ZXN0IHNwbGl0DQpuICAgIDwtIG5yb3coeCkNCm5fdHIgPC0gZmxvb3IoMC44ICogbikNCg0KeF90cmFpbiA8LSB4WzE6bl90ciwgXQ0KeV90cmFpbiA8LSB5WzE6bl90cl0NCg0KeF90ZXN0ICA8LSB4WyhuX3RyICsgMSk6biwgXQ0KeV90ZXN0ICA8LSB5WyhuX3RyICsgMSk6bl0NCg0KbW9kZWwgPC0ga2VyYXNfbW9kZWxfc2VxdWVudGlhbCgpICU+JQ0KICBsYXllcl9kZW5zZSh1bml0cyA9IDMyLCBhY3RpdmF0aW9uID0gInJlbHUiLCBpbnB1dF9zaGFwZSA9IDIpICU+JQ0KICBsYXllcl9kZW5zZSh1bml0cyA9IDY0LCBhY3RpdmF0aW9uID0gInJlbHUiKSAlPiUNCiAgbGF5ZXJfZGVuc2UodW5pdHMgPSAzMiwgYWN0aXZhdGlvbiA9ICJyZWx1IikgJT4lDQogIGxheWVyX2RlbnNlKHVuaXRzID0gMSwgYWN0aXZhdGlvbiA9ICJzaWdtb2lkIikgICMgYmluYXJ5IGNsYXNzaWZpY2F0aW9uDQoNCm1vZGVsICU+JQ0KICBjb21waWxlKA0KICAgIG9wdGltaXplciA9ICJhZGFtIiwNCiAgICBsb3NzICAgICAgPSAiYmluYXJ5X2Nyb3NzZW50cm9weSIsDQogICAgbWV0cmljcyAgID0gImFjY3VyYWN5Ig0KICApDQoNCiMgc3VtbWFyeShtb2RlbCkNCg0KYmF0Y2hfc2l6ZSA8LSAxMjgNCmVwb2NocyAgICAgPC0gMjANCg0KdCA8LSBzeXN0ZW0udGltZSh7DQogIGhpc3RvcnkgPC0gbW9kZWwgJT4lDQogICAgZml0KA0KICAgICAgeCA9IHhfdHJhaW4sIHkgPSB5X3RyYWluLA0KICAgICAgYmF0Y2hfc2l6ZSAgICAgID0gYmF0Y2hfc2l6ZSwNCiAgICAgIGVwb2NocyAgICAgICAgICA9IGVwb2NocywNCiAgICAgIHZhbGlkYXRpb25fc3BsaXQgPSAwLjIsDQogICAgICB2ZXJib3NlICAgICAgICAgPSAyDQogICAgKQ0KfSkNCg0KY2F0KCJUcmFpbmluZyBlbGFwc2VkIHRpbWUgKEdQVS1iYWNrZWQgVEYpOiIsIHRbImVsYXBzZWQiXSwgInNlY29uZHNcbiIpDQoNCm1vZGVsICU+JQ0KICBldmFsdWF0ZSh4X3Rlc3QsIHlfdGVzdCwgdmVyYm9zZSA9IDApDQoNCmBgYA0KDQpgYGB7ciBncHUtbm4tcGxvdCwgZXZhbD1GQUxTRX0NCg0KbGlicmFyeShnZ3Bsb3QyKQ0KbGlicmFyeSh2aXJpZGlzKQ0KDQojIERlZmluZSBncmlkIGZvciB2aXN1YWxpemF0aW9uDQp4X21pbiA8LSBtaW4oeFssMV0pIC0gMC4yDQp4X21heCA8LSBtYXgoeFssMV0pICsgMC4yDQp5X21pbiA8LSBtaW4oeFssMl0pIC0gMC4yDQp5X21heCA8LSBtYXgoeFssMl0pICsgMC4yDQoNCmdyaWQgPC0gZXhwYW5kLmdyaWQoDQogIHgxID0gc2VxKHhfbWluLCB4X21heCwgbGVuZ3RoLm91dCA9IDQwMCksICMgSGlnaGVyIHJlc29sdXRpb24gZm9yIGNyaXNwbmVzcw0KICB4MiA9IHNlcSh5X21pbiwgeV9tYXgsIGxlbmd0aC5vdXQgPSA0MDApDQopDQoNCiMgUHJlZGljdCBwcm9iYWJpbGl0aWVzDQpncmlkJHByb2IgPC0gYXMubnVtZXJpYyhtb2RlbCAlPiUgcHJlZGljdChhcy5tYXRyaXgoZ3JpZCksIHZlcmJvc2UgPSAwKSkNCg0KZ2dwbG90KCkgKw0KICAjIDEuIEJhY2tncm91bmQgR3JpZC9IZWF0bWFwIChTbW9vdGggVWtyYWluaWFuIEZsYWcgZ3JhZGllbnQpDQogICMgTG93IHByb2IgKElubmVyKSAtPiBCbHVlLiBIaWdoIHByb2IgKE91dGVyKSAtPiBZZWxsb3cuDQogIGdlb21fcmFzdGVyKGRhdGEgPSBncmlkLCBhZXMoeCA9IHgxLCB5ID0geDIsIGZpbGwgPSBwcm9iKSwgaW50ZXJwb2xhdGUgPSBUUlVFKSArDQogIA0KICAjIDIuIEFkZCBhIHN0cm9uZyBkZWNpc2lvbiBib3VuZGFyeSBsaW5lDQogIGdlb21fY29udG91cihkYXRhID0gZ3JpZCwgYWVzKHggPSB4MSwgeSA9IHgyLCB6ID0gcHJvYiksIA0KICAgICAgICAgICAgICAgYnJlYWtzID0gMC41LCBjb2xvciA9ICJ3aGl0ZSIsIGxpbmV0eXBlID0gInNvbGlkIiwgbGluZXdpZHRoID0gMS4yKSArDQogIA0KICAjIDMuIEFkZCB0aGUgcG9pbnRzIChTbWFsbCBhbmQgdHJhbnNsdWNlbnQgc28gdGhleSBkb24ndCBoaWRlIHRoZSBib3VuZGFyeSkNCiAgZ2VvbV9wb2ludChhZXMoeCA9IHhbLDFdLCB5ID0geFssMl0sIGNvbG9yID0gZmFjdG9yKHkpKSwgc2l6ZSA9IDAuNiwgYWxwaGEgPSAwLjMpICsNCiAgDQogICMgLS0tIENvbG9yIFBhbGV0dGVzIChVa3JhaW5pYW4gRmxhZyBUaGVtZSkgLS0tDQogIA0KICAjIEJhY2tncm91bmQgRmlsbDogQ2l2aWRpcyBwcm92aWRlcyBhIGdyZWF0IHNtb290aCBibHVlLXRvLXllbGxvdyBncmFkaWVudA0KICBzY2FsZV9maWxsX3ZpcmlkaXNfYyhvcHRpb24gPSAiY2l2aWRpcyIsIGRpcmVjdGlvbiA9IC0xLA0KICAgICAgICAgICAgICAgICAgICAgICBuYW1lID0gIlByZWRpY3RlZFxuUHJvYmFiaWxpdHkiLCBsaW1pdHMgPSBjKDAsIDEpLA0KICAgICAgICAgICAgICAgICAgICAgICBicmVha3MgPSBjKDAsIDAuNSwgMSksIGxhYmVscyA9IGMoIklubmVyIiwgIkJvdW5kYXJ5IiwgIk91dGVyIikpICsNCiAgDQogICMgUG9pbnRzIENvbG9yOiBGb3JjZSB0aGVtIHRvIHBvcCBhZ2FpbnN0IHRoZSBibHVlL3llbGxvdyBiYWNrZ3JvdW5kDQogIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXMgPSBjKCJibGFjayIsICJyZWQiKSwgDQogICAgICAgICAgICAgICAgICAgICBuYW1lID0gIlRydWUgQ2xhc3MiLCBsYWJlbHMgPSBjKCJJbm5lciAoMCkiLCAiT3V0ZXIgKDEpIikpICsNCiAgDQogICMgLS0tIFRoZW1lIGFuZCBMYWJzIC0tLQ0KICBsYWJzKA0KICAgIHRpdGxlID0gIk5ldXJhbCBOZXR3b3JrIGNsYXNzaWZpY2F0aW9uIEJvdW5kYXJ5IiwNCiAgICBzdWJ0aXRsZSA9ICJNb2Rlcm4gVGVuc29yRmxvdy9LZXJhcyBvbiBHUFUgdmlhIFdTTDIiLA0KICAgIHggPSAiRmVhdHVyZSAxIiwgDQogICAgeSA9ICJGZWF0dXJlIDIiDQogICkgKw0KICBjb29yZF9maXhlZCgpICsgIyBDcml0aWNhbDoga2VlcHMgdGhlIGNpcmNsZSBjaXJjdWxhcg0KICB0aGVtZV9taW5pbWFsKGJhc2Vfc2l6ZSA9IDE0KSArDQogIHRoZW1lKA0KICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICJyaWdodCIsDQogICAgIyBDZW50ZXIgdGhlIHRpdGxlIGFuZCBtYWtlIGl0IGJvbGQNCiAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGZhY2UgPSAiYm9sZCIsIHNpemUgPSAxOCwgaGp1c3QgPSAwLjUpLA0KICAgIHBsb3Quc3VidGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLjUsIHNpemUgPSAxMiksDQogICAgcGxvdC5jYXB0aW9uID0gZWxlbWVudF90ZXh0KHNpemUgPSA5LCBjb2xvciA9ICJncmF5NTAiKSwNCiAgICBwYW5lbC5ncmlkLm1ham9yID0gZWxlbWVudF9saW5lKGNvbG9yID0gImdyYXk5MCIpLA0KICAgIHBsb3QuYmFja2dyb3VuZCA9IGVsZW1lbnRfcmVjdChmaWxsID0gIndoaXRlIiwgY29sb3IgPSBOQSkNCiAgKQ0KDQopDQpgYGANCg0KLS0tDQoNCiMgU2Vzc2lvbiBJbmZvIHsudW5udW1iZXJlZH0NCg0KYGBge3Igc2Vzc2lvbi1pbmZvfQ0Kc2Vzc2lvbkluZm8oKQ0KYGBg