Here we will perfom some basic Approximate Bayesian Computation (ABC) inference of the mean and the standard deviation of a Normal distribution. We will use a rejection sampling algorithm, and then we will use a simple MCMC algorithm.

First, let’s generate our data.

We are assuming that our data consists of 100 samples from a Normal distribution of mean 4.3 and sd 2.7.

number_of_data_points = 100
data =  rnorm(number_of_data_points, mean = 4.3, sd = 2.7)
summary(data)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
 -2.540   2.798   4.355   4.440   5.999  13.150 

Rejection sampling algorithm

In this algorithm, we will simulate a large number (n_iterations) of samples from Normal distributions of parameters mu and sigma themselves sampled from uniform distributions, and retain only those samples that “look like” the observed data. In practice, we will try three different ways of quantifying how much simulated samples look like the observed data. Two measures will be based on quantiles, and one measure will be based on the moments of the distribution (the mean and the variance).

The priors on mu and sigma, the parameters of the Normal distribution

We assume uniform priors from which we draw mu and sigma.

draw_mu <- function () {
  return (runif(1, min=0, max=10))
}
draw_sigma <- function () {
  return (runif(1, min=0, max=10))
}

The function to simulate a data set from the parameters mu and sigma

simulate_data <- function (number_of_data_points, mu, sigma) { 
  return(rnorm(number_of_data_points, mean = mu, sd = sigma))
}

A function to compare simulated and observed samples

# Function to compute the quantiles.
# We choose to use 3 quantiles.
compute_quantiles <- function(data) {
  return (quantile(data, probs=c(0.1, 0.5, 0.9)))
}
# First method to compare a simulated sample to the observed data
compare_quantiles_with_squared_distance <- function (true, simulated) {
  distance = sqrt(sum(mapply(function(x,y) (x-y)^2, true, simulated)))
  return(distance)
}

A function to accept or reject a sample based on its distance(s) to the observed data

# Accept or reject based on the first method to compare a simulated sample to the observed data
accept_or_reject_with_squared_distance <- function (true, simulated, acceptance_threshold) {
  distance = compare_quantiles_with_squared_distance(compute_quantiles(true), compute_quantiles(simulated))
  if((distance < acceptance_threshold) ) return(T) else return(F)
}

Combining all the above elements to get the full rejection sampler

We implement the function so that it can take any accept_or_reject function. This function will create a data frame with three columns: the first, named “accepted_or_rejected”, tells whether a given sample has been accepted or rejected by the algorithm. The second and third, “sampled_mus” and “sampled_sigmas”, contain the sampled values of the parameters.

sample_by_rejection <- function (true_data, n_iterations, acceptance_threshold, accept_or_reject_function) {
  number_of_data_points = length(true_data)
  accepted_or_rejected <- vector(length = n_iterations)
  sampled_mus <- vector(length = n_iterations, mode = "numeric")
  sampled_sigmas <- vector (length = n_iterations, mode = "numeric")
  for (i in 1:n_iterations){
    mu <- draw_mu()
    sigma <- draw_sigma()
    parameters = list("mu"=mu, "sigma"=sigma)
    simulated_data <- simulate_data(number_of_data_points, mu, sigma)
    accepted_or_rejected[i] = accept_or_reject_function(true_data, simulated_data, acceptance_threshold)
    sampled_mus[i] = mu
    sampled_sigmas[i] = sigma
  }
  return(data.frame(cbind("accepted_or_rejected" = accepted_or_rejected, "sampled_mus" = sampled_mus, "sampled_sigmas" = sampled_sigmas)))
}

Now we perform inference using our distance functions, and analyze the results

First, the squared distance

We run the analysis:

sampled_parameter_values_squared_distances = sample_by_rejection(data, 200000, 0.5, accept_or_reject_with_squared_distance)

Analysis of the output

How many samples have been accepted out of 200000?

sum(sampled_parameter_values_squared_distances$accepted_or_rejected)
[1] 332

Using Coda to summarize the samples and do some plots

# Useful library used here for plotting mostly.
library(coda)
rej_samples_squared_distances_as_mcmc = mcmc(sampled_parameter_values_squared_distances[which(sampled_parameter_values_squared_distances$accepted_or_rejected==1),c(2,3)])
summary(rej_samples_squared_distances_as_mcmc)

Iterations = 1:332
Thinning interval = 1 
Number of chains = 1 
Sample size per chain = 332 

1. Empirical mean and standard deviation for each variable,
   plus standard error of the mean:

                Mean     SD Naive SE Time-series SE
sampled_mus    4.249 0.3316  0.01820        0.01721
sampled_sigmas 2.834 0.2855  0.01567        0.01567

2. Quantiles for each variable:

                2.5%   25%   50%   75% 97.5%
sampled_mus    3.623 4.000 4.270 4.474 4.848
sampled_sigmas 2.339 2.629 2.802 2.998 3.442
plot(rej_samples_squared_distances_as_mcmc)

Is there some correlation between subsequent iterations?

autocorr.plot(rej_samples_mean_and_variance_as_mcmc)

Of course not! The results would be the same with the other rejection samplers.

Trying two other distance functions

# Second method to compare a simulated sample to the observed data
compare_quantiles_with_median_and_spread <- function (true_quantiles, simulated_quantiles) {
  distances=vector(length=2, mode="numeric")
  distances[1] <- abs(true_quantiles[2]-simulated_quantiles[2])
  true_spread <- true_quantiles[3]-true_quantiles[1]
  simulated_spread <- simulated_quantiles[3]-simulated_quantiles[1]
  distances[2] <- abs(true_spread - simulated_spread)
  return(distances)
}
# Third method to compare a simulated sample to the observed data
compare_distributions_with_mean_and_variance <-function (true, simulated) {
  # comparison with the observed summary statistics
  diffmean <- abs(mean(true) - mean(simulated))
  diffsd <- abs(sd(true) - sd(simulated))
  return(c(diffmean, diffsd))
}

Two other accept or reject functions based on these distances

# Accept or reject based on the second method to compare a simulated sample to the observed data
accept_or_reject_with_median_and_spread <- function (true, simulated, acceptance_threshold) {
  distances = compare_quantiles_with_median_and_spread(compute_quantiles(true), compute_quantiles(simulated))
  if((distances[1] < acceptance_threshold) & (distances[2] < 4*acceptance_threshold) ) return(T) else return(F)
}
# Accept or reject based on the third method to compare a simulated sample to the observed data
accept_or_reject_with_mean_variance <- function (true, simulated, acceptance_threshold) {
  differences = compare_distributions_with_mean_and_variance(true, simulated)
  if((differences[1] < acceptance_threshold) & (differences[2] < 2*acceptance_threshold)) return(T) else return(F)
}

We run the analyses with these distance functions

Second, the median and spread

We run the analysis:

sampled_parameter_values_median_and_spread = sample_by_rejection(data, 200000, 0.1, accept_or_reject_with_median_and_spread)

Third, the mean and variance

We run the analysis:

sampled_parameter_values_mean_and_variance = sample_by_rejection(data, 200000, 0.1, accept_or_reject_with_mean_variance)

How many samples have been accepted?

sum(sampled_parameter_values_median_and_spread$accepted_or_rejected)
[1] 122
sum(sampled_parameter_values_mean_and_variance$accepted_or_rejected)
[1] 139

Summarizing the posterior distributions

rej_samples_median_and_spread_as_mcmc = mcmc(sampled_parameter_values_median_and_spread[which(sampled_parameter_values_median_and_spread$accepted_or_rejected==1),c(2,3)])
rej_samples_mean_and_variance_as_mcmc = mcmc(sampled_parameter_values_mean_and_variance[which(sampled_parameter_values_mean_and_variance$accepted_or_rejected==1),c(2,3)])
summary(rej_samples_median_and_spread_as_mcmc)

Iterations = 1:122
Thinning interval = 1 
Number of chains = 1 
Sample size per chain = 122 

1. Empirical mean and standard deviation for each variable,
   plus standard error of the mean:

                Mean     SD Naive SE Time-series SE
sampled_mus    4.359 0.3732  0.03378        0.03751
sampled_sigmas 2.824 0.3063  0.02773        0.03222

2. Quantiles for each variable:

                2.5%   25%   50%   75% 97.5%
sampled_mus    3.629 4.089 4.374 4.621 5.120
sampled_sigmas 2.279 2.622 2.791 3.063 3.381
summary(rej_samples_mean_and_variance_as_mcmc)

Iterations = 1:139
Thinning interval = 1 
Number of chains = 1 
Sample size per chain = 139 

1. Empirical mean and standard deviation for each variable,
   plus standard error of the mean:

                Mean     SD Naive SE Time-series SE
sampled_mus    4.442 0.2953  0.02505        0.02505
sampled_sigmas 2.840 0.2128  0.01805        0.01805

2. Quantiles for each variable:

                2.5%   25%   50%   75% 97.5%
sampled_mus    3.802 4.250 4.438 4.633 4.983
sampled_sigmas 2.500 2.679 2.829 2.953 3.313

Plotting the posterior distributions

plot(rej_samples_median_and_spread_as_mcmc)

plot(rej_samples_mean_and_variance_as_mcmc)

MCMC ABC from Florian Hartig (https://www.r-bloggers.com/a-simple-approximate-bayesian-computation-mcmc-abc-mcmc-in-r/)

 
 
# we want to use ABC to infer the parameters that were used. 
# we sample from the same model and use mean and variance
# as summary statistics. We return true for ABC acceptance when
# the difference to the data is smaller than a certain threshold
 
meandata <- mean(data)
standarddeviationdata <- sd(data)
 
ABC_acceptance <- function(par){
   
  # prior to avoid negative standard deviation
  if (par[2] <= 0) return(F) 
   
  # stochastic model generates a sample for given par
  samples <- rnorm(10, mean =par[1], sd = par[2])
 
  # comparison with the observed summary statistics
  diffmean <- abs(mean(samples) - meandata)
  diffsd <- abs(sd(samples) - standarddeviationdata)
  if((diffmean < 0.1) & (diffsd < 0.2)) return(T) else return(F)
}
 
 
# we plug this in a standard MCMC, 
# with the metropolis acceptance exchanged for the ABC acceptance
 
run_MCMC_ABC <- function(startvalue, iterations, thinning_interval=1, size_of_move=0.7){
 
    chain = array(dim = c(iterations+1,2))
    chain[1,] = startvalue
 
    for (i in 1:iterations){
         
        # proposalfunction
        proposal = rnorm(2,mean = chain[i,], sd= c(size_of_move,size_of_move))
         
        if(ABC_acceptance(proposal)){
            chain[i+1,] = proposal
        }else{
            chain[i+1,] = chain[i,]
        }
    }
    return(mcmc(chain, thin=thinning_interval))
}
 
posterior <- run_MCMC_ABC(c(4,2.3),300000, 1, 0.7)
plot(posterior)

summary(posterior)

Iterations = 1:300001
Thinning interval = 1 
Number of chains = 1 
Sample size per chain = 300001 

1. Empirical mean and standard deviation for each variable,
   plus standard error of the mean:

      Mean     SD Naive SE Time-series SE
[1,] 4.553 1.5178 0.002771        0.17851
[2,] 3.361 0.7498 0.001369        0.04763

2. Quantiles for each variable:

      2.5%   25%   50%   75% 97.5%
var1 1.395 3.652 4.428 5.283 7.972
var2 2.056 2.797 3.341 3.842 4.851

Is there some correlation between subsequent iterations?

autocorr.plot(posterior)

Yes, there is a huge amount of correlation!

What is the effective sample size of the sample obtained with this MCMC?

effectiveSize(posterior)
     var1      var2 
 72.29052 247.79597 

which is obviously much less than the 300 000 iterations that have been run…

Let’s see how thinning affects the results:

posterior <- run_MCMC_ABC(c(4,2.3),300000, 500, 0.7)
plot(posterior)

summary(posterior)

Iterations = 1:1.5e+08
Thinning interval = 500 
Number of chains = 1 
Sample size per chain = 300001 

1. Empirical mean and standard deviation for each variable,
   plus standard error of the mean:

      Mean    SD Naive SE Time-series SE
[1,] 4.258 1.015 0.001852         0.0780
[2,] 3.552 1.258 0.002297         0.1329

2. Quantiles for each variable:

      2.5%   25%   50%   75% 97.5%
var1 2.358 3.497 4.258 4.929 6.340
var2 2.008 2.635 3.205 4.001 6.916
autocorr.plot(posterior)

effectiveSize(posterior)
     var1      var2 
169.21251  89.59986 

Let’s see how changing the move width affects the results, first making it smaller:

posterior <- run_MCMC_ABC(c(4,2.3),300000, 1, 0.5)
plot(posterior)

summary(posterior)

Iterations = 1:300001
Thinning interval = 1 
Number of chains = 1 
Sample size per chain = 300001 

1. Empirical mean and standard deviation for each variable,
   plus standard error of the mean:

      Mean    SD Naive SE Time-series SE
[1,] 4.308 1.054 0.001924        0.10503
[2,] 3.390 0.905 0.001652        0.07798

2. Quantiles for each variable:

      2.5%   25%   50%   75% 97.5%
var1 1.903 3.630 4.384 5.072 6.059
var2 1.976 2.702 3.255 3.965 5.304
autocorr.plot(posterior)

effectiveSize(posterior)
    var1     var2 
100.6622 134.6898 

Let’s see how changing the move width affects the results, now making it larger:

posterior <- run_MCMC_ABC(c(4,2.3),300000, 1, 0.9)
plot(posterior)

summary(posterior)

Iterations = 1:300001
Thinning interval = 1 
Number of chains = 1 
Sample size per chain = 300001 

1. Empirical mean and standard deviation for each variable,
   plus standard error of the mean:

      Mean    SD Naive SE Time-series SE
[1,] 4.413 1.062 0.001939        0.07247
[2,] 3.394 1.016 0.001855        0.07581

2. Quantiles for each variable:

      2.5%   25%   50%   75% 97.5%
var1 2.299 3.727 4.380 5.059 6.791
var2 2.018 2.647 3.161 3.881 6.001
autocorr.plot(posterior)

effectiveSize(posterior)
    var1     var2 
214.7822 179.6936 
LS0tCnRpdGxlOiAiSW50cm9kdWN0aW9uIHRvIEFCQyIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQoKSGVyZSB3ZSB3aWxsIHBlcmZvbSBzb21lIGJhc2ljIEFwcHJveGltYXRlIEJheWVzaWFuIENvbXB1dGF0aW9uIChBQkMpIGluZmVyZW5jZSBvZiB0aGUgbWVhbiBhbmQgdGhlIHN0YW5kYXJkIGRldmlhdGlvbiBvZiBhIE5vcm1hbCBkaXN0cmlidXRpb24uCldlIHdpbGwgdXNlIGEgcmVqZWN0aW9uIHNhbXBsaW5nIGFsZ29yaXRobSwgYW5kIHRoZW4gd2Ugd2lsbCB1c2UgYSBzaW1wbGUgTUNNQyBhbGdvcml0aG0uCgojIyBGaXJzdCwgbGV0J3MgZ2VuZXJhdGUgb3VyIGRhdGEuCldlIGFyZSBhc3N1bWluZyB0aGF0IG91ciBkYXRhIGNvbnNpc3RzIG9mIDEwMCBzYW1wbGVzIGZyb20gYSBOb3JtYWwgZGlzdHJpYnV0aW9uIG9mIG1lYW4gNC4zIGFuZCBzZCAyLjcuCgoKCgpgYGB7cn0KbnVtYmVyX29mX2RhdGFfcG9pbnRzID0gMTAwCmRhdGEgPSAgcm5vcm0obnVtYmVyX29mX2RhdGFfcG9pbnRzLCBtZWFuID0gNC4zLCBzZCA9IDIuNykKYGBgCgpgYGB7cn0Kc3VtbWFyeShkYXRhKQpgYGAKCgojIyBSZWplY3Rpb24gc2FtcGxpbmcgYWxnb3JpdGhtCkluIHRoaXMgYWxnb3JpdGhtLCB3ZSB3aWxsIHNpbXVsYXRlIGEgbGFyZ2UgbnVtYmVyIChuX2l0ZXJhdGlvbnMpIG9mIHNhbXBsZXMgZnJvbSBOb3JtYWwgZGlzdHJpYnV0aW9ucyBvZiBwYXJhbWV0ZXJzIG11IGFuZCBzaWdtYSB0aGVtc2VsdmVzIHNhbXBsZWQgZnJvbSB1bmlmb3JtIGRpc3RyaWJ1dGlvbnMsIGFuZCByZXRhaW4gb25seSB0aG9zZSBzYW1wbGVzIHRoYXQgImxvb2sgbGlrZSIgdGhlIG9ic2VydmVkIGRhdGEuIEluIHByYWN0aWNlLCB3ZSB3aWxsIHRyeSB0aHJlZSBkaWZmZXJlbnQgd2F5cyBvZiBxdWFudGlmeWluZyBob3cgbXVjaCBzaW11bGF0ZWQgc2FtcGxlcyBsb29rIGxpa2UgdGhlIG9ic2VydmVkIGRhdGEuClR3byBtZWFzdXJlcyB3aWxsIGJlIGJhc2VkIG9uIHF1YW50aWxlcywgYW5kIG9uZSBtZWFzdXJlIHdpbGwgYmUgYmFzZWQgb24gdGhlIG1vbWVudHMgb2YgdGhlIGRpc3RyaWJ1dGlvbiAodGhlIG1lYW4gYW5kIHRoZSB2YXJpYW5jZSkuIAoKCiMjIyBUaGUgcHJpb3JzIG9uIG11IGFuZCBzaWdtYSwgdGhlIHBhcmFtZXRlcnMgb2YgdGhlIE5vcm1hbCBkaXN0cmlidXRpb24KV2UgYXNzdW1lIHVuaWZvcm0gcHJpb3JzIGZyb20gd2hpY2ggd2UgZHJhdyBtdSBhbmQgc2lnbWEuCmBgYHtyfQpkcmF3X211IDwtIGZ1bmN0aW9uICgpIHsKICByZXR1cm4gKHJ1bmlmKDEsIG1pbj0wLCBtYXg9MTApKQp9CgpkcmF3X3NpZ21hIDwtIGZ1bmN0aW9uICgpIHsKICByZXR1cm4gKHJ1bmlmKDEsIG1pbj0wLCBtYXg9MTApKQp9CgpgYGAKCgojIyMgVGhlIGZ1bmN0aW9uIHRvIHNpbXVsYXRlIGEgZGF0YSBzZXQgZnJvbSB0aGUgcGFyYW1ldGVycyBtdSBhbmQgc2lnbWEKYGBge3J9CnNpbXVsYXRlX2RhdGEgPC0gZnVuY3Rpb24gKG51bWJlcl9vZl9kYXRhX3BvaW50cywgbXUsIHNpZ21hKSB7IAogIHJldHVybihybm9ybShudW1iZXJfb2ZfZGF0YV9wb2ludHMsIG1lYW4gPSBtdSwgc2QgPSBzaWdtYSkpCn0KYGBgCgoKIyMjIEEgZnVuY3Rpb24gdG8gY29tcGFyZSBzaW11bGF0ZWQgYW5kIG9ic2VydmVkIHNhbXBsZXMKYGBge3J9CgojIEZ1bmN0aW9uIHRvIGNvbXB1dGUgdGhlIHF1YW50aWxlcy4KIyBXZSBjaG9vc2UgdG8gdXNlIDMgcXVhbnRpbGVzLgpjb21wdXRlX3F1YW50aWxlcyA8LSBmdW5jdGlvbihkYXRhKSB7CiAgcmV0dXJuIChxdWFudGlsZShkYXRhLCBwcm9icz1jKDAuMSwgMC41LCAwLjkpKSkKfQoKIyBGaXJzdCBtZXRob2QgdG8gY29tcGFyZSBhIHNpbXVsYXRlZCBzYW1wbGUgdG8gdGhlIG9ic2VydmVkIGRhdGEKY29tcGFyZV9xdWFudGlsZXNfd2l0aF9zcXVhcmVkX2Rpc3RhbmNlIDwtIGZ1bmN0aW9uICh0cnVlLCBzaW11bGF0ZWQpIHsKICBkaXN0YW5jZSA9IHNxcnQoc3VtKG1hcHBseShmdW5jdGlvbih4LHkpICh4LXkpXjIsIHRydWUsIHNpbXVsYXRlZCkpKQogIHJldHVybihkaXN0YW5jZSkKfQpgYGAKCgojIyMgQSBmdW5jdGlvbiB0byBhY2NlcHQgb3IgcmVqZWN0IGEgc2FtcGxlIGJhc2VkIG9uIGl0cyBkaXN0YW5jZShzKSB0byB0aGUgb2JzZXJ2ZWQgZGF0YQpgYGB7cn0KIyBBY2NlcHQgb3IgcmVqZWN0IGJhc2VkIG9uIHRoZSBmaXJzdCBtZXRob2QgdG8gY29tcGFyZSBhIHNpbXVsYXRlZCBzYW1wbGUgdG8gdGhlIG9ic2VydmVkIGRhdGEKYWNjZXB0X29yX3JlamVjdF93aXRoX3NxdWFyZWRfZGlzdGFuY2UgPC0gZnVuY3Rpb24gKHRydWUsIHNpbXVsYXRlZCwgYWNjZXB0YW5jZV90aHJlc2hvbGQpIHsKICBkaXN0YW5jZSA9IGNvbXBhcmVfcXVhbnRpbGVzX3dpdGhfc3F1YXJlZF9kaXN0YW5jZShjb21wdXRlX3F1YW50aWxlcyh0cnVlKSwgY29tcHV0ZV9xdWFudGlsZXMoc2ltdWxhdGVkKSkKICBpZigoZGlzdGFuY2UgPCBhY2NlcHRhbmNlX3RocmVzaG9sZCkgKSByZXR1cm4oVCkgZWxzZSByZXR1cm4oRikKfQoKYGBgCgoKCiMjIyBDb21iaW5pbmcgYWxsIHRoZSBhYm92ZSBlbGVtZW50cyB0byBnZXQgdGhlIGZ1bGwgcmVqZWN0aW9uIHNhbXBsZXIKCldlIGltcGxlbWVudCB0aGUgZnVuY3Rpb24gc28gdGhhdCBpdCBjYW4gdGFrZSBhbnkgYWNjZXB0X29yX3JlamVjdCBmdW5jdGlvbi4KVGhpcyBmdW5jdGlvbiB3aWxsIGNyZWF0ZSBhIGRhdGEgZnJhbWUgd2l0aCB0aHJlZSBjb2x1bW5zOiB0aGUgZmlyc3QsIG5hbWVkICJhY2NlcHRlZF9vcl9yZWplY3RlZCIsIHRlbGxzIHdoZXRoZXIgYSBnaXZlbiBzYW1wbGUgaGFzIGJlZW4gYWNjZXB0ZWQgb3IgcmVqZWN0ZWQgYnkgdGhlIGFsZ29yaXRobS4gVGhlIHNlY29uZCBhbmQgdGhpcmQsICJzYW1wbGVkX211cyIgYW5kICJzYW1wbGVkX3NpZ21hcyIsIGNvbnRhaW4gdGhlIHNhbXBsZWQgdmFsdWVzIG9mIHRoZSBwYXJhbWV0ZXJzLgpgYGB7cn0KCnNhbXBsZV9ieV9yZWplY3Rpb24gPC0gZnVuY3Rpb24gKHRydWVfZGF0YSwgbl9pdGVyYXRpb25zLCBhY2NlcHRhbmNlX3RocmVzaG9sZCwgYWNjZXB0X29yX3JlamVjdF9mdW5jdGlvbikgewogIG51bWJlcl9vZl9kYXRhX3BvaW50cyA9IGxlbmd0aCh0cnVlX2RhdGEpCiAgYWNjZXB0ZWRfb3JfcmVqZWN0ZWQgPC0gdmVjdG9yKGxlbmd0aCA9IG5faXRlcmF0aW9ucykKICBzYW1wbGVkX211cyA8LSB2ZWN0b3IobGVuZ3RoID0gbl9pdGVyYXRpb25zLCBtb2RlID0gIm51bWVyaWMiKQogIHNhbXBsZWRfc2lnbWFzIDwtIHZlY3RvciAobGVuZ3RoID0gbl9pdGVyYXRpb25zLCBtb2RlID0gIm51bWVyaWMiKQogIGZvciAoaSBpbiAxOm5faXRlcmF0aW9ucyl7CiAgICBtdSA8LSBkcmF3X211KCkKICAgIHNpZ21hIDwtIGRyYXdfc2lnbWEoKQogICAgcGFyYW1ldGVycyA9IGxpc3QoIm11Ij1tdSwgInNpZ21hIj1zaWdtYSkKICAgIHNpbXVsYXRlZF9kYXRhIDwtIHNpbXVsYXRlX2RhdGEobnVtYmVyX29mX2RhdGFfcG9pbnRzLCBtdSwgc2lnbWEpCiAgICBhY2NlcHRlZF9vcl9yZWplY3RlZFtpXSA9IGFjY2VwdF9vcl9yZWplY3RfZnVuY3Rpb24odHJ1ZV9kYXRhLCBzaW11bGF0ZWRfZGF0YSwgYWNjZXB0YW5jZV90aHJlc2hvbGQpCiAgICBzYW1wbGVkX211c1tpXSA9IG11CiAgICBzYW1wbGVkX3NpZ21hc1tpXSA9IHNpZ21hCiAgfQogIHJldHVybihkYXRhLmZyYW1lKGNiaW5kKCJhY2NlcHRlZF9vcl9yZWplY3RlZCIgPSBhY2NlcHRlZF9vcl9yZWplY3RlZCwgInNhbXBsZWRfbXVzIiA9IHNhbXBsZWRfbXVzLCAic2FtcGxlZF9zaWdtYXMiID0gc2FtcGxlZF9zaWdtYXMpKSkKfQoKCmBgYAoKCiMjIyBOb3cgd2UgcGVyZm9ybSBpbmZlcmVuY2UgdXNpbmcgb3VyIGRpc3RhbmNlIGZ1bmN0aW9ucywgYW5kIGFuYWx5emUgdGhlIHJlc3VsdHMKCiMjIyMgRmlyc3QsIHRoZSBzcXVhcmVkIGRpc3RhbmNlCldlIHJ1biB0aGUgYW5hbHlzaXM6CmBgYHtyfQpzYW1wbGVkX3BhcmFtZXRlcl92YWx1ZXNfc3F1YXJlZF9kaXN0YW5jZXMgPSBzYW1wbGVfYnlfcmVqZWN0aW9uKGRhdGEsIDIwMDAwMCwgMC41LCBhY2NlcHRfb3JfcmVqZWN0X3dpdGhfc3F1YXJlZF9kaXN0YW5jZSkKYGBgCgojIyMgQW5hbHlzaXMgb2YgdGhlIG91dHB1dAoKIyMjIyBIb3cgbWFueSBzYW1wbGVzIGhhdmUgYmVlbiBhY2NlcHRlZCBvdXQgb2YgMjAwMDAwPwpgYGB7cn0Kc3VtKHNhbXBsZWRfcGFyYW1ldGVyX3ZhbHVlc19zcXVhcmVkX2Rpc3RhbmNlcyRhY2NlcHRlZF9vcl9yZWplY3RlZCkKYGBgCgoKIyMjIyBVc2luZyBDb2RhIHRvIHN1bW1hcml6ZSB0aGUgc2FtcGxlcyBhbmQgZG8gc29tZSBwbG90cwpgYGB7cn0KIyBVc2VmdWwgbGlicmFyeSB1c2VkIGhlcmUgZm9yIHBsb3R0aW5nIG1vc3RseS4KbGlicmFyeShjb2RhKQoKcmVqX3NhbXBsZXNfc3F1YXJlZF9kaXN0YW5jZXNfYXNfbWNtYyA9IG1jbWMoc2FtcGxlZF9wYXJhbWV0ZXJfdmFsdWVzX3NxdWFyZWRfZGlzdGFuY2VzW3doaWNoKHNhbXBsZWRfcGFyYW1ldGVyX3ZhbHVlc19zcXVhcmVkX2Rpc3RhbmNlcyRhY2NlcHRlZF9vcl9yZWplY3RlZD09MSksYygyLDMpXSkKCmBgYAoKYGBge3J9CnN1bW1hcnkocmVqX3NhbXBsZXNfc3F1YXJlZF9kaXN0YW5jZXNfYXNfbWNtYykKYGBgCgpgYGB7cn0KcGxvdChyZWpfc2FtcGxlc19zcXVhcmVkX2Rpc3RhbmNlc19hc19tY21jKQpgYGAKCgpJcyB0aGVyZSBzb21lIGNvcnJlbGF0aW9uIGJldHdlZW4gc3Vic2VxdWVudCBpdGVyYXRpb25zPwpgYGB7cn0KYXV0b2NvcnIucGxvdChyZWpfc2FtcGxlc19tZWFuX2FuZF92YXJpYW5jZV9hc19tY21jKQpgYGAKT2YgY291cnNlIG5vdCEgVGhlIHJlc3VsdHMgd291bGQgYmUgdGhlIHNhbWUgd2l0aCB0aGUgb3RoZXIgcmVqZWN0aW9uIHNhbXBsZXJzLgoKIyMgVHJ5aW5nIHR3byBvdGhlciBkaXN0YW5jZSBmdW5jdGlvbnMKCgpgYGB7cn0KCiMgU2Vjb25kIG1ldGhvZCB0byBjb21wYXJlIGEgc2ltdWxhdGVkIHNhbXBsZSB0byB0aGUgb2JzZXJ2ZWQgZGF0YQpjb21wYXJlX3F1YW50aWxlc193aXRoX21lZGlhbl9hbmRfc3ByZWFkIDwtIGZ1bmN0aW9uICh0cnVlX3F1YW50aWxlcywgc2ltdWxhdGVkX3F1YW50aWxlcykgewogIGRpc3RhbmNlcz12ZWN0b3IobGVuZ3RoPTIsIG1vZGU9Im51bWVyaWMiKQogIGRpc3RhbmNlc1sxXSA8LSBhYnModHJ1ZV9xdWFudGlsZXNbMl0tc2ltdWxhdGVkX3F1YW50aWxlc1syXSkKICB0cnVlX3NwcmVhZCA8LSB0cnVlX3F1YW50aWxlc1szXS10cnVlX3F1YW50aWxlc1sxXQogIHNpbXVsYXRlZF9zcHJlYWQgPC0gc2ltdWxhdGVkX3F1YW50aWxlc1szXS1zaW11bGF0ZWRfcXVhbnRpbGVzWzFdCiAgZGlzdGFuY2VzWzJdIDwtIGFicyh0cnVlX3NwcmVhZCAtIHNpbXVsYXRlZF9zcHJlYWQpCiAgcmV0dXJuKGRpc3RhbmNlcykKfQoKIyBUaGlyZCBtZXRob2QgdG8gY29tcGFyZSBhIHNpbXVsYXRlZCBzYW1wbGUgdG8gdGhlIG9ic2VydmVkIGRhdGEKY29tcGFyZV9kaXN0cmlidXRpb25zX3dpdGhfbWVhbl9hbmRfdmFyaWFuY2UgPC1mdW5jdGlvbiAodHJ1ZSwgc2ltdWxhdGVkKSB7CiAgIyBjb21wYXJpc29uIHdpdGggdGhlIG9ic2VydmVkIHN1bW1hcnkgc3RhdGlzdGljcwogIGRpZmZtZWFuIDwtIGFicyhtZWFuKHRydWUpIC0gbWVhbihzaW11bGF0ZWQpKQogIGRpZmZzZCA8LSBhYnMoc2QodHJ1ZSkgLSBzZChzaW11bGF0ZWQpKQogIHJldHVybihjKGRpZmZtZWFuLCBkaWZmc2QpKQp9CgoKYGBgCgoKIyMjIFR3byBvdGhlciBhY2NlcHQgb3IgcmVqZWN0IGZ1bmN0aW9ucyBiYXNlZCBvbiB0aGVzZSBkaXN0YW5jZXMKYGBge3J9CgojIEFjY2VwdCBvciByZWplY3QgYmFzZWQgb24gdGhlIHNlY29uZCBtZXRob2QgdG8gY29tcGFyZSBhIHNpbXVsYXRlZCBzYW1wbGUgdG8gdGhlIG9ic2VydmVkIGRhdGEKYWNjZXB0X29yX3JlamVjdF93aXRoX21lZGlhbl9hbmRfc3ByZWFkIDwtIGZ1bmN0aW9uICh0cnVlLCBzaW11bGF0ZWQsIGFjY2VwdGFuY2VfdGhyZXNob2xkKSB7CiAgZGlzdGFuY2VzID0gY29tcGFyZV9xdWFudGlsZXNfd2l0aF9tZWRpYW5fYW5kX3NwcmVhZChjb21wdXRlX3F1YW50aWxlcyh0cnVlKSwgY29tcHV0ZV9xdWFudGlsZXMoc2ltdWxhdGVkKSkKICBpZigoZGlzdGFuY2VzWzFdIDwgYWNjZXB0YW5jZV90aHJlc2hvbGQpICYgKGRpc3RhbmNlc1syXSA8IDQqYWNjZXB0YW5jZV90aHJlc2hvbGQpICkgcmV0dXJuKFQpIGVsc2UgcmV0dXJuKEYpCn0KCiMgQWNjZXB0IG9yIHJlamVjdCBiYXNlZCBvbiB0aGUgdGhpcmQgbWV0aG9kIHRvIGNvbXBhcmUgYSBzaW11bGF0ZWQgc2FtcGxlIHRvIHRoZSBvYnNlcnZlZCBkYXRhCmFjY2VwdF9vcl9yZWplY3Rfd2l0aF9tZWFuX3ZhcmlhbmNlIDwtIGZ1bmN0aW9uICh0cnVlLCBzaW11bGF0ZWQsIGFjY2VwdGFuY2VfdGhyZXNob2xkKSB7CiAgZGlmZmVyZW5jZXMgPSBjb21wYXJlX2Rpc3RyaWJ1dGlvbnNfd2l0aF9tZWFuX2FuZF92YXJpYW5jZSh0cnVlLCBzaW11bGF0ZWQpCiAgaWYoKGRpZmZlcmVuY2VzWzFdIDwgYWNjZXB0YW5jZV90aHJlc2hvbGQpICYgKGRpZmZlcmVuY2VzWzJdIDwgMiphY2NlcHRhbmNlX3RocmVzaG9sZCkpIHJldHVybihUKSBlbHNlIHJldHVybihGKQp9CgpgYGAKCiMjIyBXZSBydW4gdGhlIGFuYWx5c2VzIHdpdGggdGhlc2UgZGlzdGFuY2UgZnVuY3Rpb25zCgojIyMjIFNlY29uZCwgdGhlIG1lZGlhbiBhbmQgc3ByZWFkCldlIHJ1biB0aGUgYW5hbHlzaXM6CmBgYHtyfQpzYW1wbGVkX3BhcmFtZXRlcl92YWx1ZXNfbWVkaWFuX2FuZF9zcHJlYWQgPSBzYW1wbGVfYnlfcmVqZWN0aW9uKGRhdGEsIDIwMDAwMCwgMC4xLCBhY2NlcHRfb3JfcmVqZWN0X3dpdGhfbWVkaWFuX2FuZF9zcHJlYWQpCmBgYAoKCiMjIyMgVGhpcmQsIHRoZSBtZWFuIGFuZCB2YXJpYW5jZQpXZSBydW4gdGhlIGFuYWx5c2lzOgpgYGB7cn0Kc2FtcGxlZF9wYXJhbWV0ZXJfdmFsdWVzX21lYW5fYW5kX3ZhcmlhbmNlID0gc2FtcGxlX2J5X3JlamVjdGlvbihkYXRhLCAyMDAwMDAsIDAuMSwgYWNjZXB0X29yX3JlamVjdF93aXRoX21lYW5fdmFyaWFuY2UpCmBgYAoKIyMjIEhvdyBtYW55IHNhbXBsZXMgaGF2ZSBiZWVuIGFjY2VwdGVkPwpgYGB7cn0Kc3VtKHNhbXBsZWRfcGFyYW1ldGVyX3ZhbHVlc19tZWRpYW5fYW5kX3NwcmVhZCRhY2NlcHRlZF9vcl9yZWplY3RlZCkKYGBgCgpgYGB7cn0Kc3VtKHNhbXBsZWRfcGFyYW1ldGVyX3ZhbHVlc19tZWFuX2FuZF92YXJpYW5jZSRhY2NlcHRlZF9vcl9yZWplY3RlZCkKYGBgCgojIyMgU3VtbWFyaXppbmcgdGhlIHBvc3RlcmlvciBkaXN0cmlidXRpb25zCmBgYHtyfQoKcmVqX3NhbXBsZXNfbWVkaWFuX2FuZF9zcHJlYWRfYXNfbWNtYyA9IG1jbWMoc2FtcGxlZF9wYXJhbWV0ZXJfdmFsdWVzX21lZGlhbl9hbmRfc3ByZWFkW3doaWNoKHNhbXBsZWRfcGFyYW1ldGVyX3ZhbHVlc19tZWRpYW5fYW5kX3NwcmVhZCRhY2NlcHRlZF9vcl9yZWplY3RlZD09MSksYygyLDMpXSkKcmVqX3NhbXBsZXNfbWVhbl9hbmRfdmFyaWFuY2VfYXNfbWNtYyA9IG1jbWMoc2FtcGxlZF9wYXJhbWV0ZXJfdmFsdWVzX21lYW5fYW5kX3ZhcmlhbmNlW3doaWNoKHNhbXBsZWRfcGFyYW1ldGVyX3ZhbHVlc19tZWFuX2FuZF92YXJpYW5jZSRhY2NlcHRlZF9vcl9yZWplY3RlZD09MSksYygyLDMpXSkKYGBgCgoKYGBge3J9CnN1bW1hcnkocmVqX3NhbXBsZXNfbWVkaWFuX2FuZF9zcHJlYWRfYXNfbWNtYykKYGBgCgpgYGB7cn0Kc3VtbWFyeShyZWpfc2FtcGxlc19tZWFuX2FuZF92YXJpYW5jZV9hc19tY21jKQpgYGAKCiMjIyBQbG90dGluZyB0aGUgcG9zdGVyaW9yIGRpc3RyaWJ1dGlvbnMKCmBgYHtyfQpwbG90KHJlal9zYW1wbGVzX21lZGlhbl9hbmRfc3ByZWFkX2FzX21jbWMpCmBgYAoKCmBgYHtyfQpwbG90KHJlal9zYW1wbGVzX21lYW5fYW5kX3ZhcmlhbmNlX2FzX21jbWMpCmBgYAoKCgojIE1DTUMgQUJDIGZyb20gRmxvcmlhbiBIYXJ0aWcgKGh0dHBzOi8vd3d3LnItYmxvZ2dlcnMuY29tL2Etc2ltcGxlLWFwcHJveGltYXRlLWJheWVzaWFuLWNvbXB1dGF0aW9uLW1jbWMtYWJjLW1jbWMtaW4tci8pCgoKYGBge3J9CiAKIAojIHdlIHdhbnQgdG8gdXNlIEFCQyB0byBpbmZlciB0aGUgcGFyYW1ldGVycyB0aGF0IHdlcmUgdXNlZC4gCiMgd2Ugc2FtcGxlIGZyb20gdGhlIHNhbWUgbW9kZWwgYW5kIHVzZSBtZWFuIGFuZCB2YXJpYW5jZQojIGFzIHN1bW1hcnkgc3RhdGlzdGljcy4gV2UgcmV0dXJuIHRydWUgZm9yIEFCQyBhY2NlcHRhbmNlIHdoZW4KIyB0aGUgZGlmZmVyZW5jZSB0byB0aGUgZGF0YSBpcyBzbWFsbGVyIHRoYW4gYSBjZXJ0YWluIHRocmVzaG9sZAogCm1lYW5kYXRhIDwtIG1lYW4oZGF0YSkKc3RhbmRhcmRkZXZpYXRpb25kYXRhIDwtIHNkKGRhdGEpCiAKQUJDX2FjY2VwdGFuY2UgPC0gZnVuY3Rpb24ocGFyKXsKICAgCiAgIyBwcmlvciB0byBhdm9pZCBuZWdhdGl2ZSBzdGFuZGFyZCBkZXZpYXRpb24KICBpZiAocGFyWzJdIDw9IDApIHJldHVybihGKSAKICAgCiAgIyBzdG9jaGFzdGljIG1vZGVsIGdlbmVyYXRlcyBhIHNhbXBsZSBmb3IgZ2l2ZW4gcGFyCiAgc2FtcGxlcyA8LSBybm9ybSgxMCwgbWVhbiA9cGFyWzFdLCBzZCA9IHBhclsyXSkKIAogICMgY29tcGFyaXNvbiB3aXRoIHRoZSBvYnNlcnZlZCBzdW1tYXJ5IHN0YXRpc3RpY3MKICBkaWZmbWVhbiA8LSBhYnMobWVhbihzYW1wbGVzKSAtIG1lYW5kYXRhKQogIGRpZmZzZCA8LSBhYnMoc2Qoc2FtcGxlcykgLSBzdGFuZGFyZGRldmlhdGlvbmRhdGEpCiAgaWYoKGRpZmZtZWFuIDwgMC4xKSAmIChkaWZmc2QgPCAwLjIpKSByZXR1cm4oVCkgZWxzZSByZXR1cm4oRikKfQogCiAKIyB3ZSBwbHVnIHRoaXMgaW4gYSBzdGFuZGFyZCBNQ01DLCAKIyB3aXRoIHRoZSBtZXRyb3BvbGlzIGFjY2VwdGFuY2UgZXhjaGFuZ2VkIGZvciB0aGUgQUJDIGFjY2VwdGFuY2UKIApydW5fTUNNQ19BQkMgPC0gZnVuY3Rpb24oc3RhcnR2YWx1ZSwgaXRlcmF0aW9ucywgdGhpbm5pbmdfaW50ZXJ2YWw9MSwgc2l6ZV9vZl9tb3ZlPTAuNyl7CiAKICAgIGNoYWluID0gYXJyYXkoZGltID0gYyhpdGVyYXRpb25zKzEsMikpCiAgICBjaGFpblsxLF0gPSBzdGFydHZhbHVlCiAKICAgIGZvciAoaSBpbiAxOml0ZXJhdGlvbnMpewogICAgICAgICAKICAgICAgICAjIHByb3Bvc2FsZnVuY3Rpb24KICAgICAgICBwcm9wb3NhbCA9IHJub3JtKDIsbWVhbiA9IGNoYWluW2ksXSwgc2Q9IGMoc2l6ZV9vZl9tb3ZlLHNpemVfb2ZfbW92ZSkpCiAgICAgICAgIAogICAgICAgIGlmKEFCQ19hY2NlcHRhbmNlKHByb3Bvc2FsKSl7CiAgICAgICAgICAgIGNoYWluW2krMSxdID0gcHJvcG9zYWwKICAgICAgICB9ZWxzZXsKICAgICAgICAgICAgY2hhaW5baSsxLF0gPSBjaGFpbltpLF0KICAgICAgICB9CiAgICB9CiAgICByZXR1cm4obWNtYyhjaGFpbiwgdGhpbj10aGlubmluZ19pbnRlcnZhbCkpCn0KIApwb3N0ZXJpb3IgPC0gcnVuX01DTUNfQUJDKGMoNCwyLjMpLDMwMDAwMCwgMSwgMC43KQpwbG90KHBvc3RlcmlvcikKCgpgYGAKCmBgYHtyfQpzdW1tYXJ5KHBvc3RlcmlvcikKYGBgCgoKSXMgdGhlcmUgc29tZSBjb3JyZWxhdGlvbiBiZXR3ZWVuIHN1YnNlcXVlbnQgaXRlcmF0aW9ucz8KYGBge3J9CmF1dG9jb3JyLnBsb3QocG9zdGVyaW9yKQpgYGAKClllcywgdGhlcmUgaXMgYSBodWdlIGFtb3VudCBvZiBjb3JyZWxhdGlvbiEKCldoYXQgaXMgdGhlIGVmZmVjdGl2ZSBzYW1wbGUgc2l6ZSBvZiB0aGUgc2FtcGxlIG9idGFpbmVkIHdpdGggdGhpcyBNQ01DPwoKYGBge3J9CmVmZmVjdGl2ZVNpemUocG9zdGVyaW9yKQpgYGAKCndoaWNoIGlzIG9idmlvdXNseSBtdWNoIGxlc3MgdGhhbiB0aGUgMzAwIDAwMCBpdGVyYXRpb25zIHRoYXQgaGF2ZSBiZWVuIHJ1bi4uLgoKIyMjIExldCdzIHNlZSBob3cgdGhpbm5pbmcgYWZmZWN0cyB0aGUgcmVzdWx0czoKCmBgYHtyfQpwb3N0ZXJpb3IgPC0gcnVuX01DTUNfQUJDKGMoNCwyLjMpLDMwMDAwMCwgNTAwLCAwLjcpCnBsb3QocG9zdGVyaW9yKQoKYGBgCgoKCmBgYHtyfQpzdW1tYXJ5KHBvc3RlcmlvcikKYGBgCgpgYGB7cn0KYXV0b2NvcnIucGxvdChwb3N0ZXJpb3IpCmBgYAoKYGBge3J9CmVmZmVjdGl2ZVNpemUocG9zdGVyaW9yKQpgYGAKCiMjIyBMZXQncyBzZWUgaG93IGNoYW5naW5nIHRoZSBtb3ZlIHdpZHRoIGFmZmVjdHMgdGhlIHJlc3VsdHMsIGZpcnN0IG1ha2luZyBpdCBzbWFsbGVyOgoKYGBge3J9CnBvc3RlcmlvciA8LSBydW5fTUNNQ19BQkMoYyg0LDIuMyksMzAwMDAwLCAxLCAwLjUpCnBsb3QocG9zdGVyaW9yKQoKYGBgCgpgYGB7cn0Kc3VtbWFyeShwb3N0ZXJpb3IpCmBgYAoKCmBgYHtyfQphdXRvY29yci5wbG90KHBvc3RlcmlvcikKYGBgCgpgYGB7cn0KZWZmZWN0aXZlU2l6ZShwb3N0ZXJpb3IpCmBgYAoKCiMjIyBMZXQncyBzZWUgaG93IGNoYW5naW5nIHRoZSBtb3ZlIHdpZHRoIGFmZmVjdHMgdGhlIHJlc3VsdHMsIG5vdyBtYWtpbmcgaXQgbGFyZ2VyOgoKYGBge3J9CnBvc3RlcmlvciA8LSBydW5fTUNNQ19BQkMoYyg0LDIuMyksMzAwMDAwLCAxLCAwLjkpCnBsb3QocG9zdGVyaW9yKQoKYGBgCgpgYGB7cn0Kc3VtbWFyeShwb3N0ZXJpb3IpCmBgYAoKCmBgYHtyfQphdXRvY29yci5wbG90KHBvc3RlcmlvcikKYGBgCgpgYGB7cn0KZWZmZWN0aXZlU2l6ZShwb3N0ZXJpb3IpCmBgYAoKCjwhLS0gYGBge3J9IC0tPgo8IS0tIHRlc3Q9bWF0cml4KG5yb3c9bGVuZ3RoKHNhbXBsZWRfcGFyYW1ldGVyX3ZhbHVlcyRzYW1wbGVkX211cyksIG5jb2w9MTAwKSAtLT4KPCEtLSBmb3IgKGkgaW4gMTpsZW5ndGgoc2FtcGxlZF9wYXJhbWV0ZXJfdmFsdWVzJHNhbXBsZWRfbXVzKSkgeyAtLT4KPCEtLSAgdGVzdFtpLF0gPSBybm9ybSgxMDAsIG1lYW4gPSBzYW1wbGVkX3BhcmFtZXRlcl92YWx1ZXMkc2FtcGxlZF9tdXMsIHNkPXNhbXBsZWRfcGFyYW1ldGVyX3ZhbHVlcyRzYW1wbGVkX3NpZ21hcykgLS0+Cgo8IS0tIH0gLS0+CjwhLS0gYGBgIC0tPgoKPCEtLSBgYGB7cn0gLS0+CjwhLS0gc2RzPXZlY3RvcihsZW5ndGgoc2FtcGxlZF9wYXJhbWV0ZXJfdmFsdWVzJHNhbXBsZWRfbXVzKSwgbW9kZT0ibnVtZXJpYyIpIC0tPgo8IS0tIHF1YW50cz12ZWN0b3IobGVuZ3RoKHNhbXBsZWRfcGFyYW1ldGVyX3ZhbHVlcyRzYW1wbGVkX211cyksIG1vZGU9Im51bWVyaWMiKSAtLT4KPCEtLSBmb3IgKGkgaW4gMTpsZW5ndGgoc2FtcGxlZF9wYXJhbWV0ZXJfdmFsdWVzJHNhbXBsZWRfbXVzKSkgeyAtLT4KPCEtLSBxdWFudCA9IHF1YW50aWxlKHRlc3RbaSxdLCBwcm9icyA9IGMoMC4xLCAwLjkpKSAgLS0+CjwhLS0gcXVhbnRzW2ldID0gcXVhbnRbMl0gLSBxdWFudFsxXSAtLT4KPCEtLSBzZHNbaV0gPSBzZCh0ZXN0W2ksXSkgLS0+CjwhLS0gfSAtLT4KCjwhLS0gYGBgIC0tPgoKPCEtLSBgYGB7cn0gLS0+CjwhLS0gcGxvdChzZHMsIHF1YW50cykgLS0+CjwhLS0gYWJsaW5lKGE9MCwgYj0xKSAtLT4KPCEtLSBgYGAgLS0+CgoK