Change log


## install packages with:
# install.packages(c("glmnet", "MASS", "ggplot2", "gridExtra", "ggpubr"))
# if (!requireNamespace("remotes", quietly = TRUE)) {
#     install.packages("remotes")
# }
# remotes::install_github("statOmics/HDDAData")

library(glmnet)
library(MASS)
library(HDDAData)
library(ggplot2)
library(gridExtra)
library(ggpubr)

1 Introduction

In this lab session we will look at the following topics

  • Methods to set some of the loadings exactly to zero in a PCA
  • Use glmnet() to add penalties on principal component loadings
  • Use LDA to understand differences between groups in a high dimensional space

The dataset

In this practical session, we use the dataset by Alon et al. (1999) on gene expression levels in 40 tumour and 22 normal colon tissue samples. They checked a total of 6500 human genes using the Affymetrix oligonucleotide array.

You can load the data in as follows:

data("Alon1999")
str(Alon1999[, 1:10])
#> 'data.frame':    62 obs. of  10 variables:
#>  $ Y : chr  "t" "n" "t" "n" ...
#>  $ X1: num  8589 9164 3826 6246 3230 ...
#>  $ X2: num  5468 6720 6970 7824 3694 ...
#>  $ X3: num  4263 4883 5370 5956 3401 ...
#>  $ X4: num  4065 3718 4706 3976 3464 ...
#>  $ X5: num  1998 2015 1167 2003 2181 ...
#>  $ X6: num  5282 5570 1572 2131 2923 ...
#>  $ X7: num  2170 3849 1325 1531 2069 ...
#>  $ X8: num  2773 2793 1472 1715 2949 ...
#>  $ X9: num  7526 7018 3297 3870 3303 ...
dim(Alon1999)
#> [1]   62 2001
table(Alon1999$Y)
#> 
#>  n  t 
#> 22 40

The dataset contains one variable named Y with the values t and n. This variable indicates whether the sample came from tumourous (t) or normal (n) tissue. For more information on this dataset, see ?Alon1999.

The goal of this practical is to find the best subset / combination of genes to detect tumourous tissue. As in Alon et al. (1999), we use the 2000 genes with the highest minimal intensity across the samples.

2 Sparse PCA

Begin by constructing the data matrix X, which contains the centered and scaled predictors, and the response variable Y as a binary factor.

X <- scale(Alon1999[, -1], center = TRUE, scale = TRUE)
Y <- as.factor(Alon1999[, 1])

Use these objects to solve the following exercises.

Tasks

1. Perform a SVD on X and store the scores of the PCs.
Solution

Using svd:

svd_X <- svd(X)
Z <- svd_X$u %*% diag(svd_X$d) # Calculate the scores
V <- svd_X$v                 # Calculate the loadings

Using prcomp:

# X is already centered and scaled so no need to do again
pca_x <- prcomp(X, center = FALSE, scale. = FALSE)
# The scores are given by `pca_x$x`, the loadings are `pca_x$rotation`
2. Produce a scree plot and confirm that the first and second PCs can approximate the data to some extent.

Recall from Lab 2 that a scree plot displays the proportion of variance explained by each PC.

Solution

Calculate the proportion of variance explained by each PC.

var_explained <- pca_x$sdev^2 / sum(pca_x$sdev^2)
# alternative: svd_X$d^2 / sum(svd_X$d^2)

# Create dataframe for plotting
prop_var <- data.frame(PC = 1:ncol(pca_x$x), Var = var_explained)
# alternative: PC = ncol(Z)

Visualize with a scree plot using ggplot2:

ggplot(prop_var, aes(PC, Var)) +
  geom_point() +
  geom_line() +
  geom_vline(xintercept = 2.5, col = "firebrick") +
  scale_x_continuous(breaks = seq(0, 60, by = 5)) +
  labs(
    y = "Proportion of variance",
    title = "Proportion of variance explained by each PC"
  ) +
  theme_minimal()

Alternatively, using base R plotting:

plot(prop_var$PC, prop_var$Var,
  type = "b", ylab = "Proportion of variance", xlab = "PC",
)
title("Proportion of variance explained by each PC")
abline(v = 2.5, col = "firebrick")

About 55% of the variance in the data are explained by the first and second PCs.

cumsum(prop_var$Var)[1:10]
#>  [1] 0.4495565 0.5480190 0.6156694 0.6722213 0.7050558 0.7363469 0.7596658
#>  [8] 0.7818428 0.7985138 0.8140390
3. Plot the first two PCs and use different colours for tumor / normal tissue.

Due to the large number of features, the biplot would be difficult to interpret. A simple scatterplot, separated by tumor / normal tissue, is more informative in this setting.

Solution
scores_1_2 <- data.frame(PC1 = pca_x$x[, 1], PC2 = pca_x$x[, 2], Tissue = Y)
# Alternative: retrieve scores from Z[, 1], Z[, 2]

ggplot(scores_1_2, aes(PC1, PC2)) +
  geom_point(aes(col = Tissue), size = 2) +
  scale_color_manual(values = c("deepskyblue2", "coral1")) +
  theme_minimal() +
  theme(aspect.ratio = 0.8, legend.position = "top")

Alternatively, using base R plotting:

cols <- c("n" = "deepskyblue2", "t" = "coral1")
plot(scores_1_2$PC1, scores_1_2$PC2,
  col = cols[Y],
  xlab = "PC1", ylab = "PC2", pch = 19
)
legend("topleft", c("Normal", "Tumor"),
  col = cols,
  pch = 19, title = "Tissue"
)

Interpretation: using only the first 2 PCs does not seem to separate the tumor and normal cases clearly.

4. Plot histograms of the loadings of the first and second PCs. Interpret.
Solution
loadings_1_2 <- data.frame(
  loadings_1 = pca_x$rotation[, 1], loadings_2 = pca_x$rotation[, 2]
)
# alternative: loadings_1 = V[, 1], loadings_2 = V[, 2]

# First plot (PC1)
p1 <- ggplot(loadings_1_2, aes(x = loadings_1)) +
  geom_histogram(bins = 50, fill = "grey80", color = "black") +
  geom_vline(
    xintercept = quantile(loadings_1_2$loadings_1, 0.95),
    color = "firebrick",
    linewidth = 1.2
  ) +
  labs(x = "PC 1 loadings", y = "Count", title = NULL) +
  theme_minimal(base_size = 14)

# Second plot (PC2)
p2 <- ggplot(loadings_1_2, aes(x = loadings_2)) +
  geom_histogram(bins = 50, fill = "grey80", color = "black") +
  geom_vline(
    xintercept = quantile(loadings_1_2$loadings_2, c(0.05, 0.95)),
    color = "firebrick",
    linewidth = 1.2
  ) +
  labs(x = "PC 2 loadings", y = "Count", title = NULL) +
  theme_minimal(base_size = 14)

# Arrange plots vertically
grid.arrange(p1, p2, ncol = 1)

Alternatively, using base R plotting:

par(mfrow = c(2, 1))

# First plot (PC1)
hist(loadings_1_2$loadings_1, breaks = 50, xlab = "PC 1 loadings", main = "")
# Add vertical line at 95% quantile
abline(v = quantile(loadings_1_2$loadings_1, 0.95), col = "firebrick", lwd = 2)

# Second plot (PC2)
hist(loadings_1_2$loadings_2, breaks = 50, xlab = "PC 2 loadings", main = "")
abline(v = c(
  quantile(loadings_1_2$loadings_2, 0.05),
  quantile(loadings_1_2$loadings_2, 0.95)
), col = "firebrick", lwd = 2)

Vertical lines were added at the 95th percentile for PC1 and the 5th and 95th percentiles for PC2 to reflect where the largest (in absolute value) loadings are situated (no negative loadings for PC1, so only showing the 95th percentile).

Interpretation: remember that the PC loadings reflect the contributions of each feature (in this case: gene) to the PC. From these histograms it should be clear that only a minor fraction of the genes are really driving these first 2 PCs, especially for PC2 (where the bulk of genes has loadings close to 0).

We know that the first PC π™πŸ\mathbf{Z_1} is given by π™πŸ=π—π•πŸ, \mathbf{Z_1}=\mathbf{X} \mathbf{V_1} \, ,

where π•πŸ\mathbf{V_1} are the loadings of the first PC. By setting 𝛃=π•πŸ\boldsymbol{\beta} = \mathbf{V_1} and Y=Z1Y = Z_1, we can express this as the regression

𝐘=𝐗𝛃. \mathbf{Y}=\mathbf{X}\boldsymbol{\beta} \, .

Recall that the ridge regression solution for 𝛃\boldsymbol{\beta} is given by

𝛃ridge=(𝐗𝐓𝐗+λ𝐈)βˆ’1𝐗T𝐘. \boldsymbol{\beta}_{\text{ridge}} = (\mathbf{X^TX}+\lambda\mathbf{I})^{-1}\mathbf{X}^T\mathbf Y \, .

5. Replace 𝐘\mathbf{Y} with π™πŸ\mathbf{Z_1} and verify numerically in R that

𝐕1=𝛃ridgeβˆ₯𝛃ridgeβˆ₯2. \mathbf V_1 = \frac{\boldsymbol\beta_{\text{ridge}}}{\|\boldsymbol\beta_{\text{ridge}}\|_2} \, .

You may use any Ξ»>0\lambda > 0 of your choice. Remember that βˆ₯𝛃ridgeβˆ₯2=𝛃ridgeT𝛃ridge=βˆ‘j=1pΞ²j2\|\boldsymbol\beta_{\text{ridge}}\|_2 = \sqrt{\boldsymbol\beta_{\text{ridge}}^T \boldsymbol\beta_{\text{ridge}}} = \sqrt{\sum_{j=1}^p \beta_j^2}.

Solution
p <- dim(X)[2]
tXX_lambda_I <- t(X) %*% X + 2 * diag(p)
# This might take a while to calculate
beta_ridge <- solve(tXX_lambda_I) %*% t(X) %*% Z[, 1]
mag_beta_ridge <- sqrt(sum(beta_ridge^2))
max(abs(V[, 1] - beta_ridge / mag_beta_ridge))
#> [1] 2.15872e-10
# alternative for V[, 1]: pca_x$rotation[, 1]

For a visual comparison, we can also plot 𝛃ridge/βˆ₯𝛃ridgeβˆ₯2\boldsymbol\beta_{\text{ridge}} / \|\boldsymbol\beta_{\text{ridge}}\|_2 against the loadings 𝐕1\mathbf V_1.

par(mfrow = c(1, 1))

plot(V[, 1], beta_ridge / mag_beta_ridge,
  xlab = expression("V"[1]),
  ylab = expression(beta["ridge"] / paste("||", beta, "||")[2]),
  pch = 19
)

You have now seen that the loadings of the PCs can be computed from ridge regression coefficients.

If we introduce an additional L1L_1 penalty on the 𝛃\boldsymbol \beta coefficients, we move from ridge regression to elastic net regression. Recall that elastic net regression minimizes the criterion βˆ₯π˜βˆ’π—π›ƒβˆ₯22+Ξ±Ξ»βˆ₯𝛃βˆ₯1+(1βˆ’Ξ±)Ξ»βˆ₯𝛃βˆ₯22 \|\mathbf Y-\mathbf{X}\boldsymbol \beta\|^2_2 + \alpha \lambda\|\boldsymbol \beta\|_1 + (1 - \alpha) \lambda\|\boldsymbol \beta\|^2_2 for α∈[0,1]\alpha \in [0,1]. Whenever Ξ±>0\alpha > 0, a sufficiently large Ξ»\lambda forces some of the coefficients in 𝛃\boldsymbol\beta to become zero.

Regressing the principal component Z1Z_1 on XX therefore sets some of the loadings V1V_1 to zero. This means that elastic net can be used to implement sparse PCA.

6. Fit an elastic net model for Z1Z_1. Use alpha = 0.5 and lambda = c(11, 1.75, 0.3). Determine the number of non-zero loadings for each choice of lambda. Repeat the same steps for Z2Z_2, but use lambda = c(5, 1.5, 0.15).
Solution
# Helper function to determine number of non-zero loadings
num_loadings <- function(fit) {
  lambdas <- fit$lambda
  num_nonzero <- NULL
  for (lambda in lambdas) {
    num_nonzero <- c(
      num_nonzero, sum(as.vector(coef(fit, s = lambda))[-1] != 0)
    )
  }
  return(data.frame(lambdas, num_nonzero))
}

# Elastic net for Z_1
fit_loadings1 <- glmnet(X, Z[, 1], alpha = 0.5, lambda = c(11, 1.75, 0.3))
num_loadings(fit_loadings1)

# Elastic net for Z_2
fit_loadings2 <- glmnet(X, Z[, 2], alpha = 0.5, lambda = c(5, 1.5, 0.15))
num_loadings(fit_loadings2)
7. Plot the resulting sparse first and second PCs and use different colors for tumor / normal tissue. How well do these new PCs separate the response classes?

You may reuse the code from task 3 to produce 3 new plots for (Ξ»PC1=11,Ξ»PC2=5)(\lambda_{\text{PC1}} = 11, \lambda_{\text{PC2}} = 5), (Ξ»PC1=1.75,Ξ»PC2=1.5)(\lambda_{\text{PC1}} = 1.75, \lambda_{\text{PC2}} = 1.5) and (Ξ»PC1=0.3,Ξ»PC2=0.15)(\lambda_{\text{PC1}} = 0.3, \lambda_{\text{PC2}} = 0.15). Compare this to the plot for the original PCs from task 3 and interpret.

Solution

Using base R plotting:

# Helper function for scatterplot between two sparse PCs
plot_sparse_PCA <- function(loadings1, loadings2) {
  nonzero1 <- sum(loadings1[-1] != 0)
  nonzero2 <- sum(loadings2[-1] != 0)

  SPC1 <- X %*% loadings1[-1]
  SPC2 <- X %*% loadings2[-1]

  plot(
    SPC1, SPC2, col = cols[Y], xlab = "SPC1", ylab = "SPC2", pch = 16,
    ylim = c(-40, 40), xlim = c(-60, 95),
    main = paste(nonzero1, "genes for SPC1,", nonzero2, "for SPC2")
  )
  legend(
    "topleft", legend = c("Normal tissue", "Tumor tissue"),
    bty = "n", col = cols, pch = c(16, 16), cex = 1
  )
}

par(mfrow = c(2, 2))
plot(Z[, 1], Z[, 2],
  col = cols[Y], xlab = "PC1", ylab = "PC2", pch = 16,
  ylim = c(-40, 40), xlim = c(-60, 95),
  main = "All 2000 genes \nfor PC1 and PC2"
)
legend(
  "topleft", legend = c("Normal tissue", "Tumor tissue"),
  bty = "n", col = cols, pch = c(16, 16), cex = 1
)
plot_sparse_PCA(
  as.vector(coef(fit_loadings1, s = 0.3)),
  as.vector(coef(fit_loadings2, s = 0.15))
)
plot_sparse_PCA(
  as.vector(coef(fit_loadings1, s = 1.75)),
  as.vector(coef(fit_loadings2, s = 1.5))
)
plot_sparse_PCA(
  as.vector(coef(fit_loadings1, s = 11)),
  as.vector(coef(fit_loadings2, s = 6))
)

Alternatively, using ggplot:

# Helper function for scatterplot between two sparse PCs
plot_sparse_PCA <- function(loadings1, loadings2) {
  nonzero1 <- sum(loadings1[-1] != 0)
  nonzero2 <- sum(loadings2[-1] != 0)

  SPC1 <- X %*% loadings1[-1]
  SPC2 <- X %*% loadings2[-1]

  ggplot(data.frame(SPC1, SPC2, Tissue = Y), aes(SPC1, SPC2)) +
    geom_point(aes(col = Tissue), size = 2) +
    scale_color_manual(values = c("deepskyblue2", "coral1")) +
    labs(title = paste(nonzero1, "genes for SPC1,", nonzero2, "for SPC2")) +
    xlim(-60, 95) +
    ylim(-40, 40) +
    theme_minimal() +
    theme(legend.position = "bottom", text = element_text(size = 8))
}

plot1 <- ggplot(scores_1_2, aes(PC1, PC2)) +
  geom_point(aes(col = Tissue), size = 2) +
  scale_color_manual(values = c("deepskyblue2", "coral1")) +
  labs(title = "All 2000 genes for PC1 and PC2") +
  xlim(-60, 95) +
  ylim(-40, 40) +
  theme_minimal() +
  theme(legend.position = "bottom", text = element_text(size = 8))
plot2 <- plot_sparse_PCA(
  as.vector(coef(fit_loadings1, s = 0.3)),
  as.vector(coef(fit_loadings2, s = 0.15))
)
plot3 <- plot_sparse_PCA(
  as.vector(coef(fit_loadings1, s = 1.75)),
  as.vector(coef(fit_loadings2, s = 1.5))
)
plot4 <- plot_sparse_PCA(
  as.vector(coef(fit_loadings1, s = 11)),
  as.vector(coef(fit_loadings2, s = 6))
)

ggarrange(plot1, plot2, plot3, plot4)

Conclusion: Sparse PCA has succeeded in setting the uninformative genes / loadings to zero. In separating normal and tumour tissues, SPCA performs vitually the same as PCA. By increasing Ξ»\lambda, more sparsity is induced in the loadings, while the overall visual impression remains comparable. The key point here is that SPCA uses only a minor proportion of the original features to achieve the same results, suggesting that the largest variability of the data is only driven by a minority of features.

Remark: Oftentimes, Ξ»\lambda is chosen in such a way that a particular number or range of nonzero loadings is obtained. This is how we proceeded above, where we considered a sequence of Ξ»\lambda’s resulting in different levels of sparsity. Alternatively, if you want to select Ξ»\lambda in a data-driven manner, you cannot rely on cross-validation with the predictive MSE as used in Lab 3. The reason for this is that the minimal cross-validated MSE will be achieved for Ξ»=0\lambda = 0 (or the smallest positive value that can be evaluated without numerical problems). Instead, you can take a look at the proportion of variance that is explained by the sparse PCs to select a sensible number of nonzero loadings or a sensible Ξ»\lambda.

3 LDA

In this section, we will perform LDA on the data by Alon et al. (1999) to get a clear understanding on the genes responsible for separating the tumor and normal tissue groups.

Remember that the LDA problem can be stated as

𝐯=ArgMaxaπšπ“ππšπšπ“π–πš subject to πšπ“π–πš=1, \mathbf{v} = \text{ArgMax}_a \frac{\mathbf{a^T B a}}{\mathbf{a^T W a}} \text{ subject to } \mathbf{a^T W a} = 1 \, ,

which is equivalent to the eigenvalue / eigenvector problem

π–βˆ’1𝐁𝐚=λ𝐚. \mathbf W^{-1} \mathbf B \mathbf a=\lambda \mathbf a \, .

In our case, where we only have two groups, only one solution exists. This is the eigenvector a=𝐯a = \mathbf v and its eigenvalue. 𝐯\mathbf v can can be interpreted in terms of which predictors are important to discriminate between the two classes. We can then write the scores as

𝐙=𝐗𝐯. \mathbf Z=\mathbf X \mathbf v \, .

Tasks

1. Use the lda() function from the MASS package to fit an LDA on X with grouping Y.

Note that the grouping has to be a factor variable.

Solution
alon_lda <- lda(x = X, grouping = Y)
#> Warning in lda.default(x, grouping, ...): variables are collinear

# alternative: Y ~ X

Note the warning regarding collinearity.

2. Extract 𝐯\mathbf v from the lda object.

Hint: Have a look at the β€œValue” section of ?lda.

Solution
V1 <- alon_lda$scaling
3. Compute 𝐙\mathbf Z.
Solution
Z1 <- X %*% V1
4. Plot the scores 𝐙\mathbf Z against the response to see how well the LDA separates the tumour and normal tissues groups. Interpret your plot.

Hint: A boxplot would be an appropriate visualization, but feel free to be creative.

Solution

Using ggplot:

ggplot(data.frame(Z1, Y), aes(x = Y, y = Z1, fill = as.factor(Y))) +
  geom_boxplot() +
  scale_fill_manual(values = cols) +
  labs(x = "Y", y = "Z",
    title = "Separation of normal and tumour samples by LDA"
  ) +
  theme_minimal() +
  theme(legend.position = "none", plot.title = element_text(hjust = 0.5))

Alternatively, using base R plotting:

par(mfrow = c(1, 1))
boxplot(
  Z1 ~ Y, col = cols, ylab = "Z",
  main = "Separation of normal and tumour samples by LDA"
)

Interpretation: The LDA can separate the tumour and normal tissue samples very well. In comparison, the first and second components of the (sparse) PCA (an unsupervised method) in the first part of this Lab did not discriminate the two groups nearly as well.

We can view 𝐙=𝐗𝐯 \mathbf Z = \mathbf X \mathbf v as a regression of 𝐙\mathbf Z on 𝐗\mathbf X with coefficient vector 𝐯\mathbf v. The entries of 𝐯\mathbf v are non-zero for all genes. To implement a sparse LDA, where some of the entries of 𝐯\mathbf v are set to zero, we can introduce an L1L_1 penalty on 𝐯\mathbf v and fit a Lasso regression. The resulting 𝐯\mathbf v will only be determined by a few interesting genes instead of all 2000.

5. Fit a Lasso model for 𝐙\mathbf Z with lambda = c(0.2, 0.02, 0.002). Determine the number of non-zero loadings for each choice of lambda.
Solution
lda_loadings <- glmnet(X, Z1, alpha = 1, lambda = c(0.2, 0.02, 0.002))
num_loadings(lda_loadings)
6. Plot the resulting sparse scores against the response. Are the smaller subsets of genes as effective in separating the tumour and normal tissue groups as the entire set of genes?

You may reuse the code from task 4 to produce 3 new plots, one for each choice of Ξ»\lambda. Compare this to the plot for the original LDA from task 4 and interpret.

Solution

Using base R plotting:

# Helper function for scatterplot between two sparse PCs
plot_sparse_LDA <- function(loadings) {
  n_nonzero <- sum(loadings[-1] != 0)
  SLDA <- X %*% loadings[-1]

  boxplot(
    SLDA ~ Y, col = cols, ylab = "Z",
    main = sprintf("Subset of %d genes", n_nonzero),
    ylim = c(-4, 4)
  )
}

par(mfrow = c(2, 2))
boxplot(
  Z1 ~ Y, col = cols, ylab = "Z",
  main = "Entire set of 2000 genes",
  ylim = c(-4, 4)
)
plot_sparse_LDA(
  as.vector(coef(lda_loadings, s = 0.002))
)
plot_sparse_LDA(
  as.vector(coef(lda_loadings, s = 0.02))
)
plot_sparse_LDA(
  as.vector(coef(lda_loadings, s = 0.2))
)

Alternatively, using ggplot:

# Helper function for scatterplot between two sparse PCs
plot_sparse_LDA <- function(loadings) {
  n_nonzero <- sum(loadings[-1] != 0)
  SLDA <- X %*% loadings[-1]

  ggplot(data.frame(SLDA, Y), aes(x = Y, y = SLDA, fill = as.factor(Y))) +
    geom_boxplot() +
    scale_fill_manual(values = cols) +
    labs(x = "Y", y = "Z",
      title = sprintf("Subset of %d genes", n_nonzero)
    ) +
    ylim(-4, 4) +
    theme_minimal() +
    theme(legend.position = "none", plot.title = element_text(hjust = 0.5))
}

plot1 <- ggplot(data.frame(Z1, Y), aes(x = Y, y = Z1, fill = as.factor(Y))) +
  geom_boxplot() +
  scale_fill_manual(values = cols) +
  labs(x = "Y", y = "Z",
    title = "Entire set of 2000 genes"
  ) +
  ylim(-4, 4) +
  theme_minimal() +
  theme(legend.position = "none", plot.title = element_text(hjust = 0.5))
plot2 <- plot_sparse_LDA(
  as.vector(coef(lda_loadings, s = 0.002))
)
plot3 <- plot_sparse_LDA(
  as.vector(coef(lda_loadings, s = 0.02))
)
plot4 <- plot_sparse_LDA(
  as.vector(coef(lda_loadings, s = 0.2))
)

ggarrange(plot1, plot2, plot3, plot4)

Conclusion: Sparse LDA performs almost identically to the original LDA in separating normal and tumour tissues. Even with only the 18 most important genes, the sparse LDA is mostly able to discriminate the two classes well. Therefore, the separation between normal and tumour tissues is mainly driven by only a small proportion of genes.

Bonus: Evaluation of LDA as a predictive classifier

We use 10-fold cross-validation to evaluate the predictive accuracy of classifications from the original LDA on all 2000 genes and the SLDA on the three smaller subsets of genes.

set.seed(914751)

# Helper function to compute accuracy given v, X, and Y
lda_accuracy <- function(v, X, Y) {
  preds <- (X %*% v) > 0
  obs <- Y == "t"
  return(1 - mean(abs(preds - obs)))
}

acc_full <- acc_small <- acc_smaller <- acc_smallest <- NULL

# Randomly split the data into k folds
folds <- sample(rep(1:10, length.out = length(Y)))

for (k in 1:10) {
  # Assign the train and validation set for this fold
  val_idx <- which(folds == k)
  train_idx <- setdiff(seq_len(length(Y)), val_idx)
  X_train <- X[train_idx, , drop = FALSE]
  Y_train <- Y[train_idx]
  X_val <- X[val_idx, , drop = FALSE]
  Y_val <- Y[val_idx]

  # Fit the LDA
  lda_fold <- lda(x = X_train, grouping = Y_train)
  # Fit the SLDA with three levels of sparsity
  Z_fold <- X_train %*% lda_fold$scaling
  lasso_fold <- glmnet(X_train, Z_fold, alpha = 1, lambda = c(0.2, 0.02, 0.002))
  # Evaluate the accuracy of all four models on the validation set
  acc_full <- c(acc_full, lda_accuracy(lda_fold$scaling, X_val, Y_val))
  acc_small <- c(
    acc_small,
    lda_accuracy(as.vector(coef(lasso_fold, s = 0.002))[-1], X_val, Y_val)
  )
  acc_smaller <- c(
    acc_smaller,
    lda_accuracy(as.vector(coef(lasso_fold, s = 0.02))[-1], X_val, Y_val)
  )
  acc_smallest <- c(
    acc_smallest,
    lda_accuracy(as.vector(coef(lasso_fold, s = 0.2))[-1], X_val, Y_val)
  )
}
#> Warning in lda.default(x, grouping, ...): variables are collinear
#> Warning in lda.default(x, grouping, ...): variables are collinear
#> Warning in lda.default(x, grouping, ...): variables are collinear
#> Warning in lda.default(x, grouping, ...): variables are collinear
#> Warning in lda.default(x, grouping, ...): variables are collinear
#> Warning in lda.default(x, grouping, ...): variables are collinear
#> Warning in lda.default(x, grouping, ...): variables are collinear
#> Warning in lda.default(x, grouping, ...): variables are collinear
#> Warning in lda.default(x, grouping, ...): variables are collinear
#> Warning in lda.default(x, grouping, ...): variables are collinear

data.frame(
  LDA = c("original", "sparse", "sparser", "sparsest"),
  accuracy = c(
    mean(acc_full), mean(acc_small), mean(acc_smaller), mean(acc_smallest)
  )
)

Additional resources

  • Section 4.3 (LDA) and 14.5.5 (SPCA) of Hastie, Tibshirani, and Friedman (2009)
  • For a simple explanation of the concept and interpretation of LDA (and other statistical methods), have a look at https://www.youtube.com/watch?v=azXCzI57Yfc

Session info

Session info
#> [1] "2025-11-06 16:49:01 CET"
#> ─ Session info ───────────────────────────────────────────────────────────────
#>  setting  value
#>  version  R version 4.5.2 (2025-10-31)
#>  os       macOS Sequoia 15.6
#>  system   aarch64, darwin20
#>  ui       X11
#>  language (EN)
#>  collate  en_US.UTF-8
#>  ctype    en_US.UTF-8
#>  tz       Europe/Brussels
#>  date     2025-11-06
#>  pandoc   3.6.3 @ /Applications/RStudio.app/Contents/Resources/app/quarto/bin/tools/aarch64/ (via rmarkdown)
#>  quarto   1.7.32 @ /Applications/RStudio.app/Contents/Resources/app/quarto/bin/quarto
#> 
#> ─ Packages ───────────────────────────────────────────────────────────────────
#>  package      * version date (UTC) lib source
#>  abind          1.4-8   2024-09-12 [1] CRAN (R 4.5.0)
#>  backports      1.5.0   2024-05-23 [1] CRAN (R 4.5.0)
#>  bookdown       0.45    2025-10-03 [1] CRAN (R 4.5.0)
#>  broom          1.0.10  2025-09-13 [1] CRAN (R 4.5.0)
#>  bslib          0.9.0   2025-01-30 [1] CRAN (R 4.5.0)
#>  cachem         1.1.0   2024-05-16 [1] CRAN (R 4.5.0)
#>  car            3.1-3   2024-09-27 [1] CRAN (R 4.5.0)
#>  carData        3.0-5   2022-01-06 [1] CRAN (R 4.5.0)
#>  cli            3.6.5   2025-04-23 [1] CRAN (R 4.5.0)
#>  codetools      0.2-20  2024-03-31 [1] CRAN (R 4.5.2)
#>  cowplot        1.2.0   2025-07-07 [1] CRAN (R 4.5.0)
#>  digest         0.6.37  2024-08-19 [1] CRAN (R 4.5.0)
#>  dplyr          1.1.4   2023-11-17 [1] CRAN (R 4.5.0)
#>  evaluate       1.0.5   2025-08-27 [1] CRAN (R 4.5.0)
#>  farver         2.1.2   2024-05-13 [1] CRAN (R 4.5.0)
#>  fastmap        1.2.0   2024-05-15 [1] CRAN (R 4.5.0)
#>  foreach        1.5.2   2022-02-02 [1] CRAN (R 4.5.0)
#>  Formula        1.2-5   2023-02-24 [1] CRAN (R 4.5.0)
#>  generics       0.1.4   2025-05-09 [1] CRAN (R 4.5.0)
#>  ggplot2      * 4.0.0   2025-09-11 [1] CRAN (R 4.5.0)
#>  ggpubr       * 0.6.2   2025-10-17 [1] CRAN (R 4.5.0)
#>  ggsignif       0.6.4   2022-10-13 [1] CRAN (R 4.5.0)
#>  glmnet       * 4.1-10  2025-07-17 [1] CRAN (R 4.5.0)
#>  glue           1.8.0   2024-09-30 [1] CRAN (R 4.5.0)
#>  gridExtra    * 2.3     2017-09-09 [1] CRAN (R 4.5.0)
#>  gtable         0.3.6   2024-10-25 [1] CRAN (R 4.5.0)
#>  HDDAData     * 1.0.1   2025-11-06 [1] Github (statOmics/HDDAData@b832c71)
#>  htmltools      0.5.8.1 2024-04-04 [1] CRAN (R 4.5.0)
#>  iterators      1.0.14  2022-02-05 [1] CRAN (R 4.5.0)
#>  jquerylib      0.1.4   2021-04-26 [1] CRAN (R 4.5.0)
#>  jsonlite       2.0.0   2025-03-27 [1] CRAN (R 4.5.0)
#>  knitr          1.50    2025-03-16 [1] CRAN (R 4.5.0)
#>  labeling       0.4.3   2023-08-29 [1] CRAN (R 4.5.0)
#>  lattice        0.22-7  2025-04-02 [1] CRAN (R 4.5.2)
#>  lifecycle      1.0.4   2023-11-07 [1] CRAN (R 4.5.0)
#>  magrittr       2.0.4   2025-09-12 [1] CRAN (R 4.5.0)
#>  MASS         * 7.3-65  2025-02-28 [1] CRAN (R 4.5.2)
#>  Matrix       * 1.7-4   2025-08-28 [1] CRAN (R 4.5.2)
#>  pillar         1.11.1  2025-09-17 [1] CRAN (R 4.5.0)
#>  pkgconfig      2.0.3   2019-09-22 [1] CRAN (R 4.5.0)
#>  purrr          1.2.0   2025-11-04 [1] CRAN (R 4.5.0)
#>  R6             2.6.1   2025-02-15 [1] CRAN (R 4.5.0)
#>  RColorBrewer   1.1-3   2022-04-03 [1] CRAN (R 4.5.0)
#>  Rcpp           1.1.0   2025-07-02 [1] CRAN (R 4.5.0)
#>  rlang          1.1.6   2025-04-11 [1] CRAN (R 4.5.0)
#>  rmarkdown      2.30    2025-09-28 [1] CRAN (R 4.5.0)
#>  rstatix        0.7.3   2025-10-18 [1] CRAN (R 4.5.0)
#>  rstudioapi     0.17.1  2024-10-22 [1] CRAN (R 4.5.0)
#>  S7             0.2.0   2024-11-07 [1] CRAN (R 4.5.0)
#>  sass           0.4.10  2025-04-11 [1] CRAN (R 4.5.0)
#>  scales         1.4.0   2025-04-24 [1] CRAN (R 4.5.0)
#>  sessioninfo    1.2.3   2025-02-05 [1] CRAN (R 4.5.0)
#>  shape          1.4.6.1 2024-02-23 [1] CRAN (R 4.5.0)
#>  survival       3.8-3   2024-12-17 [1] CRAN (R 4.5.2)
#>  tibble         3.3.0   2025-06-08 [1] CRAN (R 4.5.0)
#>  tidyr          1.3.1   2024-01-24 [1] CRAN (R 4.5.0)
#>  tidyselect     1.2.1   2024-03-11 [1] CRAN (R 4.5.0)
#>  vctrs          0.6.5   2023-12-01 [1] CRAN (R 4.5.0)
#>  withr          3.0.2   2024-10-28 [1] CRAN (R 4.5.0)
#>  xfun           0.54    2025-10-30 [1] CRAN (R 4.5.0)
#>  yaml           2.3.10  2024-07-26 [1] CRAN (R 4.5.0)
#> 
#>  [1] /Library/Frameworks/R.framework/Versions/4.5-arm64/Resources/library
#>  * ── Packages attached to the search path.
#> 
#> ──────────────────────────────────────────────────────────────────────────────

References

Alon, Uri, Naama Barkai, Daniel A Notterman, Kurt Gish, Suzanne Ybarra, Daniel Mack, and Arnold J Levine. 1999. β€œBroad Patterns of Gene Expression Revealed by Clustering Analysis of Tumor and Normal Colon Tissues Probed by Oligonucleotide Arrays.” Proceedings of the National Academy of Sciences 96 (12): 6745–50.
Hastie, Trevor, Robert Tibshirani, and Jerome Friedman. 2009. The Elements of Statistical Learning. 2nd ed. Springer.
LS0tCnRpdGxlOiAiTGFiIDQ6IFNwYXJzZSBQQ0EgYW5kIExEQSIKc3VidGl0bGU6ICJIaWdoIERpbWVuc2lvbmFsIERhdGEgQW5hbHlzaXMgcHJhY3RpY2FscyIKYXV0aG9yOiAiQWRhcHRlZCBieSBNaWxhbiBNYWxmYWl0IGFuZCBMZW8gRnVocmhvcCIKZGF0ZTogIjE4IE5vdiAyMDIxIDxici8+IChMYXN0IHVwZGF0ZWQ6IDIwMjUtMTEtMDYpIgpyZWZlcmVuY2VzOgotIGlkOiBhbG9uMTk5OWJyb2FkCiAgdHlwZTogYXJ0aWNsZS1qb3VybmFsCiAgYXV0aG9yOgogIC0gZmFtaWx5OiBBbG9uCiAgICBnaXZlbjogVXJpCiAgLSBmYW1pbHk6IEJhcmthaQogICAgZ2l2ZW46IE5hYW1hCiAgLSBmYW1pbHk6IE5vdHRlcm1hbgogICAgZ2l2ZW46IERhbmllbCBBCiAgLSBmYW1pbHk6IEdpc2gKICAgIGdpdmVuOiBLdXJ0CiAgLSBmYW1pbHk6IFliYXJyYQogICAgZ2l2ZW46IFN1emFubmUKICAtIGZhbWlseTogTWFjawogICAgZ2l2ZW46IERhbmllbAogIC0gZmFtaWx5OiBMZXZpbmUKICAgIGdpdmVuOiBBcm5vbGQgSgogIGlzc3VlZDoKICAtIHllYXI6IDE5OTkKICB0aXRsZTogQnJvYWQgcGF0dGVybnMgb2YgZ2VuZSBleHByZXNzaW9uIHJldmVhbGVkIGJ5IGNsdXN0ZXJpbmcgYW5hbHlzaXMgb2YgdHVtb3IKICAgIGFuZCBub3JtYWwgY29sb24gdGlzc3VlcyBwcm9iZWQgYnkgb2xpZ29udWNsZW90aWRlIGFycmF5cwogIGNvbnRhaW5lci10aXRsZTogUHJvY2VlZGluZ3Mgb2YgdGhlIE5hdGlvbmFsIEFjYWRlbXkgb2YgU2NpZW5jZXMKICBwdWJsaXNoZXI6IE5hdGlvbmFsIEFjYWQgU2NpZW5jZXMKICBwYWdlOiA2NzQ1LTY3NTAKICB2b2x1bWU6ICc5NicKICBpc3N1ZTogJzEyJwotIGlkOiBlc2wtYm9vawogIHR5cGU6IGJvb2sKICBhdXRob3I6CiAgLSBmYW1pbHk6IEhhc3RpZQogICAgZ2l2ZW46IFRyZXZvcgogIC0gZmFtaWx5OiBUaWJzaGlyYW5pCiAgICBnaXZlbjogUm9iZXJ0CiAgLSBmYW1pbHk6IEZyaWVkbWFuCiAgICBnaXZlbjogSmVyb21lCiAgaXNzdWVkOgogIC0geWVhcjogMjAwOQogIHRpdGxlOiBUaGUgRWxlbWVudHMgb2YgU3RhdGlzdGljYWwgTGVhcm5pbmcKICBwdWJsaXNoZXI6IFNwcmluZ2VyCiAgZWRpdGlvbjogMm5kCi0tLQoKYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0UsIGNhY2hlPUZBTFNFfQprbml0cjo6b3B0c19jaHVuayRzZXQoCiAgY29sbGFwc2UgPSBUUlVFLAogIGNvbW1lbnQgPSAiIz4iLAogIGZpZy5hbGlnbiA9ICJjZW50ZXIiLAogIGZpZy53aWR0aCA9IDgsCiAgZmlnLmFzcCA9IDAuNjE4LAogIG91dC53aWR0aCA9ICIxMDAlIgopCmBgYAoKIyMjIFtDaGFuZ2UgbG9nXShodHRwczovL2dpdGh1Yi5jb20vc3RhdE9taWNzL0hEREEvY29tbWl0cy9tYXN0ZXIvTGFiNC1TcGFyc2UtUENBLUxEQS5SbWQpIHstfQoKKioqCgpgYGB7ciBsaWJyYXJpZXMsIHdhcm5pbmc9RkFMU0UsIG1lc3NhZ2U9RkFMU0V9CiMjIGluc3RhbGwgcGFja2FnZXMgd2l0aDoKIyBpbnN0YWxsLnBhY2thZ2VzKGMoImdsbW5ldCIsICJNQVNTIiwgImdncGxvdDIiLCAiZ3JpZEV4dHJhIiwgImdncHViciIpKQojIGlmICghcmVxdWlyZU5hbWVzcGFjZSgicmVtb3RlcyIsIHF1aWV0bHkgPSBUUlVFKSkgewojICAgICBpbnN0YWxsLnBhY2thZ2VzKCJyZW1vdGVzIikKIyB9CiMgcmVtb3Rlczo6aW5zdGFsbF9naXRodWIoInN0YXRPbWljcy9IRERBRGF0YSIpCgpsaWJyYXJ5KGdsbW5ldCkKbGlicmFyeShNQVNTKQpsaWJyYXJ5KEhEREFEYXRhKQpsaWJyYXJ5KGdncGxvdDIpCmxpYnJhcnkoZ3JpZEV4dHJhKQpsaWJyYXJ5KGdncHVicikKYGBgCgoKIyBJbnRyb2R1Y3Rpb24KCioqSW4gdGhpcyBsYWIgc2Vzc2lvbiB3ZSB3aWxsIGxvb2sgYXQgdGhlIGZvbGxvd2luZyB0b3BpY3MqKgoKICAtIE1ldGhvZHMgdG8gc2V0IHNvbWUgb2YgdGhlIGxvYWRpbmdzIGV4YWN0bHkgdG8gemVybyBpbiBhIFBDQQogIC0gVXNlIGBnbG1uZXQoKWAgdG8gYWRkIHBlbmFsdGllcyBvbiBwcmluY2lwYWwgY29tcG9uZW50IGxvYWRpbmdzCiAgLSBVc2UgTERBIHRvIHVuZGVyc3RhbmQgZGlmZmVyZW5jZXMgYmV0d2VlbiBncm91cHMgaW4gYSBoaWdoIGRpbWVuc2lvbmFsIHNwYWNlCgojIyBUaGUgZGF0YXNldCB7LX0KCkluIHRoaXMgcHJhY3RpY2FsIHNlc3Npb24sIHdlIHVzZSB0aGUgZGF0YXNldCBieSBAYWxvbjE5OTlicm9hZCBvbiBnZW5lCmV4cHJlc3Npb24gbGV2ZWxzIGluIDQwIHR1bW91ciBhbmQgMjIgbm9ybWFsIGNvbG9uIHRpc3N1ZSBzYW1wbGVzLiAgVGhleSBjaGVja2VkCmEgdG90YWwgb2YgNjUwMCBodW1hbiBnZW5lcyB1c2luZyB0aGUgQWZmeW1ldHJpeCBvbGlnb251Y2xlb3RpZGUgYXJyYXkuCgpZb3UgY2FuIGxvYWQgdGhlIGRhdGEgaW4gYXMgZm9sbG93czoKCmBgYHtyIGxvYWQtZGF0YX0KZGF0YSgiQWxvbjE5OTkiKQpzdHIoQWxvbjE5OTlbLCAxOjEwXSkKZGltKEFsb24xOTk5KQp0YWJsZShBbG9uMTk5OSRZKQpgYGAKClRoZSBkYXRhc2V0IGNvbnRhaW5zIG9uZSB2YXJpYWJsZSBuYW1lZCBgWWAgd2l0aCB0aGUgdmFsdWVzIGB0YCBhbmQgYG5gLiAgVGhpcwp2YXJpYWJsZSBpbmRpY2F0ZXMgd2hldGhlciB0aGUgc2FtcGxlIGNhbWUgZnJvbSB0dW1vdXJvdXMgKGB0YCkgb3Igbm9ybWFsIChgbmApCnRpc3N1ZS4gIEZvciBtb3JlIGluZm9ybWF0aW9uIG9uIHRoaXMgZGF0YXNldCwgc2VlIGA/QWxvbjE5OTlgLgoKVGhlIGdvYWwgb2YgdGhpcyBwcmFjdGljYWwgaXMgdG8gZmluZCB0aGUgYmVzdCBzdWJzZXQgLyBjb21iaW5hdGlvbiBvZiBnZW5lcyB0byBkZXRlY3QgdHVtb3Vyb3VzIHRpc3N1ZS4KQXMgaW4gQGFsb24xOTk5YnJvYWQsIHdlIHVzZSB0aGUgMjAwMCBnZW5lcyB3aXRoIHRoZSBoaWdoZXN0IG1pbmltYWwgaW50ZW5zaXR5CmFjcm9zcyB0aGUgc2FtcGxlcy4KCiMgU3BhcnNlIFBDQQoKQmVnaW4gYnkgY29uc3RydWN0aW5nIHRoZSBkYXRhIG1hdHJpeCBgWGAsIHdoaWNoIGNvbnRhaW5zIHRoZSBjZW50ZXJlZCBhbmQgc2NhbGVkIHByZWRpY3RvcnMsCmFuZCB0aGUgcmVzcG9uc2UgdmFyaWFibGUgYFlgIGFzIGEgYmluYXJ5IGZhY3Rvci4KCmBgYHtyfQpYIDwtIHNjYWxlKEFsb24xOTk5WywgLTFdLCBjZW50ZXIgPSBUUlVFLCBzY2FsZSA9IFRSVUUpClkgPC0gYXMuZmFjdG9yKEFsb24xOTk5WywgMV0pCmBgYAoKVXNlIHRoZXNlIG9iamVjdHMgdG8gc29sdmUgdGhlIGZvbGxvd2luZyBleGVyY2lzZXMuCgojIyBUYXNrcyB7LX0KCiMjIyMjIDEuIFBlcmZvcm0gYSBTVkQgb24gYFhgIGFuZCBzdG9yZSB0aGUgc2NvcmVzIG9mIHRoZSBQQ3MuIHstfQoKPGRldGFpbHM+PHN1bW1hcnk+U29sdXRpb248L3N1bW1hcnk+CgpVc2luZyBgc3ZkYDoKCmBgYHtyfQpzdmRfWCA8LSBzdmQoWCkKWiA8LSBzdmRfWCR1ICUqJSBkaWFnKHN2ZF9YJGQpICMgQ2FsY3VsYXRlIHRoZSBzY29yZXMKViA8LSBzdmRfWCR2ICAgICAgICAgICAgICAgICAjIENhbGN1bGF0ZSB0aGUgbG9hZGluZ3MKYGBgCgpVc2luZyBgcHJjb21wYDoKCmBgYHtyfQojIFggaXMgYWxyZWFkeSBjZW50ZXJlZCBhbmQgc2NhbGVkIHNvIG5vIG5lZWQgdG8gZG8gYWdhaW4KcGNhX3ggPC0gcHJjb21wKFgsIGNlbnRlciA9IEZBTFNFLCBzY2FsZS4gPSBGQUxTRSkKIyBUaGUgc2NvcmVzIGFyZSBnaXZlbiBieSBgcGNhX3gkeGAsIHRoZSBsb2FkaW5ncyBhcmUgYHBjYV94JHJvdGF0aW9uYApgYGAKCjwvZGV0YWlscz4KCgojIyMjIyAyLiBQcm9kdWNlIGEgc2NyZWUgcGxvdCBhbmQgY29uZmlybSB0aGF0IHRoZSBmaXJzdCBhbmQgc2Vjb25kIFBDcyBjYW4gYXBwcm94aW1hdGUgdGhlIGRhdGEgdG8gc29tZSBleHRlbnQuIHstfQoKUmVjYWxsIGZyb20gW0xhYiAyXShodHRwczovL3N0YXRvbWljcy5naXRodWIuaW8vSEREQS9MYWIyLVBDQS5odG1sI1Rhc2tzKSB0aGF0IGEgc2NyZWUgcGxvdCBkaXNwbGF5cyB0aGUgcHJvcG9ydGlvbiBvZiB2YXJpYW5jZSBleHBsYWluZWQgYnkgZWFjaCBQQy4KCjxkZXRhaWxzPjxzdW1tYXJ5PlNvbHV0aW9uPC9zdW1tYXJ5PgoKQ2FsY3VsYXRlIHRoZSBwcm9wb3J0aW9uIG9mIHZhcmlhbmNlIGV4cGxhaW5lZCBieSBlYWNoIFBDLgoKYGBge3IgcGNhLXZhci1wcm9wfQp2YXJfZXhwbGFpbmVkIDwtIHBjYV94JHNkZXZeMiAvIHN1bShwY2FfeCRzZGV2XjIpCiMgYWx0ZXJuYXRpdmU6IHN2ZF9YJGReMiAvIHN1bShzdmRfWCRkXjIpCgojIENyZWF0ZSBkYXRhZnJhbWUgZm9yIHBsb3R0aW5nCnByb3BfdmFyIDwtIGRhdGEuZnJhbWUoUEMgPSAxOm5jb2wocGNhX3gkeCksIFZhciA9IHZhcl9leHBsYWluZWQpCiMgYWx0ZXJuYXRpdmU6IFBDID0gbmNvbChaKQpgYGAKClZpc3VhbGl6ZSB3aXRoIGEgc2NyZWUgcGxvdCB1c2luZyBgZ2dwbG90MmA6CgpgYGB7ciBwY2Etc2NyZWUtZ2dwbG90fQpnZ3Bsb3QocHJvcF92YXIsIGFlcyhQQywgVmFyKSkgKwogIGdlb21fcG9pbnQoKSArCiAgZ2VvbV9saW5lKCkgKwogIGdlb21fdmxpbmUoeGludGVyY2VwdCA9IDIuNSwgY29sID0gImZpcmVicmljayIpICsKICBzY2FsZV94X2NvbnRpbnVvdXMoYnJlYWtzID0gc2VxKDAsIDYwLCBieSA9IDUpKSArCiAgbGFicygKICAgIHkgPSAiUHJvcG9ydGlvbiBvZiB2YXJpYW5jZSIsCiAgICB0aXRsZSA9ICJQcm9wb3J0aW9uIG9mIHZhcmlhbmNlIGV4cGxhaW5lZCBieSBlYWNoIFBDIgogICkgKwogIHRoZW1lX21pbmltYWwoKQpgYGAKCkFsdGVybmF0aXZlbHksIHVzaW5nIGJhc2UgYFJgIHBsb3R0aW5nOgoKYGBge3IgcGNhLXNjcmVlLXBsb3R9CnBsb3QocHJvcF92YXIkUEMsIHByb3BfdmFyJFZhciwKICB0eXBlID0gImIiLCB5bGFiID0gIlByb3BvcnRpb24gb2YgdmFyaWFuY2UiLCB4bGFiID0gIlBDIiwKKQp0aXRsZSgiUHJvcG9ydGlvbiBvZiB2YXJpYW5jZSBleHBsYWluZWQgYnkgZWFjaCBQQyIpCmFibGluZSh2ID0gMi41LCBjb2wgPSAiZmlyZWJyaWNrIikKYGBgCgpBYm91dCA1NSUgb2YgdGhlIHZhcmlhbmNlIGluIHRoZSBkYXRhIGFyZSBleHBsYWluZWQgYnkgdGhlIGZpcnN0IGFuZCBzZWNvbmQgUENzLgoKYGBge3IgcGNhLWN1bXVsYXRpdmUtcHJvcC12YXJ9CmN1bXN1bShwcm9wX3ZhciRWYXIpWzE6MTBdCmBgYAoKPC9kZXRhaWxzPgoKCiMjIyMjIDMuIFBsb3QgdGhlIGZpcnN0IHR3byBQQ3MgYW5kIHVzZSBkaWZmZXJlbnQgY29sb3VycyBmb3IgdHVtb3IgLyBub3JtYWwgdGlzc3VlLiB7LX0KCkR1ZSB0byB0aGUgbGFyZ2UgbnVtYmVyIG9mIGZlYXR1cmVzLCB0aGUgYmlwbG90IHdvdWxkIGJlIGRpZmZpY3VsdCB0byBpbnRlcnByZXQuIEEgc2ltcGxlIHNjYXR0ZXJwbG90LCBzZXBhcmF0ZWQgYnkgdHVtb3IgLyBub3JtYWwgdGlzc3VlLCBpcyBtb3JlIGluZm9ybWF0aXZlIGluIHRoaXMgc2V0dGluZy4KCjxkZXRhaWxzPjxzdW1tYXJ5PlNvbHV0aW9uPC9zdW1tYXJ5PgoKYGBge3IgcGNhLXRpc3N1ZS1nZ3Bsb3R9CnNjb3Jlc18xXzIgPC0gZGF0YS5mcmFtZShQQzEgPSBwY2FfeCR4WywgMV0sIFBDMiA9IHBjYV94JHhbLCAyXSwgVGlzc3VlID0gWSkKIyBBbHRlcm5hdGl2ZTogcmV0cmlldmUgc2NvcmVzIGZyb20gWlssIDFdLCBaWywgMl0KCmdncGxvdChzY29yZXNfMV8yLCBhZXMoUEMxLCBQQzIpKSArCiAgZ2VvbV9wb2ludChhZXMoY29sID0gVGlzc3VlKSwgc2l6ZSA9IDIpICsKICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzID0gYygiZGVlcHNreWJsdWUyIiwgImNvcmFsMSIpKSArCiAgdGhlbWVfbWluaW1hbCgpICsKICB0aGVtZShhc3BlY3QucmF0aW8gPSAwLjgsIGxlZ2VuZC5wb3NpdGlvbiA9ICJ0b3AiKQpgYGAKCkFsdGVybmF0aXZlbHksIHVzaW5nIGJhc2UgYFJgIHBsb3R0aW5nOgoKYGBge3IgcGNhLXRpc3N1ZS1wbG90fQpjb2xzIDwtIGMoIm4iID0gImRlZXBza3libHVlMiIsICJ0IiA9ICJjb3JhbDEiKQpwbG90KHNjb3Jlc18xXzIkUEMxLCBzY29yZXNfMV8yJFBDMiwKICBjb2wgPSBjb2xzW1ldLAogIHhsYWIgPSAiUEMxIiwgeWxhYiA9ICJQQzIiLCBwY2ggPSAxOQopCmxlZ2VuZCgidG9wbGVmdCIsIGMoIk5vcm1hbCIsICJUdW1vciIpLAogIGNvbCA9IGNvbHMsCiAgcGNoID0gMTksIHRpdGxlID0gIlRpc3N1ZSIKKQpgYGAKCl9fSW50ZXJwcmV0YXRpb246X18gdXNpbmcgb25seSB0aGUgZmlyc3QgMiBQQ3MgZG9lcyBub3Qgc2VlbSB0byBzZXBhcmF0ZSB0aGUgdHVtb3IgYW5kIG5vcm1hbCBjYXNlcyBjbGVhcmx5LgoKPC9kZXRhaWxzPgoKCiMjIyMjIDQuIFBsb3QgaGlzdG9ncmFtcyBvZiB0aGUgbG9hZGluZ3Mgb2YgdGhlIGZpcnN0IGFuZCBzZWNvbmQgUENzLiBJbnRlcnByZXQuIHstfQoKPGRldGFpbHM+PHN1bW1hcnk+U29sdXRpb248L3N1bW1hcnk+CgpgYGB7ciBwY2EtbG9hZGluZy1nZ3Bsb3R9CmxvYWRpbmdzXzFfMiA8LSBkYXRhLmZyYW1lKAogIGxvYWRpbmdzXzEgPSBwY2FfeCRyb3RhdGlvblssIDFdLCBsb2FkaW5nc18yID0gcGNhX3gkcm90YXRpb25bLCAyXQopCiMgYWx0ZXJuYXRpdmU6IGxvYWRpbmdzXzEgPSBWWywgMV0sIGxvYWRpbmdzXzIgPSBWWywgMl0KCiMgRmlyc3QgcGxvdCAoUEMxKQpwMSA8LSBnZ3Bsb3QobG9hZGluZ3NfMV8yLCBhZXMoeCA9IGxvYWRpbmdzXzEpKSArCiAgZ2VvbV9oaXN0b2dyYW0oYmlucyA9IDUwLCBmaWxsID0gImdyZXk4MCIsIGNvbG9yID0gImJsYWNrIikgKwogIGdlb21fdmxpbmUoCiAgICB4aW50ZXJjZXB0ID0gcXVhbnRpbGUobG9hZGluZ3NfMV8yJGxvYWRpbmdzXzEsIDAuOTUpLAogICAgY29sb3IgPSAiZmlyZWJyaWNrIiwKICAgIGxpbmV3aWR0aCA9IDEuMgogICkgKwogIGxhYnMoeCA9ICJQQyAxIGxvYWRpbmdzIiwgeSA9ICJDb3VudCIsIHRpdGxlID0gTlVMTCkgKwogIHRoZW1lX21pbmltYWwoYmFzZV9zaXplID0gMTQpCgojIFNlY29uZCBwbG90IChQQzIpCnAyIDwtIGdncGxvdChsb2FkaW5nc18xXzIsIGFlcyh4ID0gbG9hZGluZ3NfMikpICsKICBnZW9tX2hpc3RvZ3JhbShiaW5zID0gNTAsIGZpbGwgPSAiZ3JleTgwIiwgY29sb3IgPSAiYmxhY2siKSArCiAgZ2VvbV92bGluZSgKICAgIHhpbnRlcmNlcHQgPSBxdWFudGlsZShsb2FkaW5nc18xXzIkbG9hZGluZ3NfMiwgYygwLjA1LCAwLjk1KSksCiAgICBjb2xvciA9ICJmaXJlYnJpY2siLAogICAgbGluZXdpZHRoID0gMS4yCiAgKSArCiAgbGFicyh4ID0gIlBDIDIgbG9hZGluZ3MiLCB5ID0gIkNvdW50IiwgdGl0bGUgPSBOVUxMKSArCiAgdGhlbWVfbWluaW1hbChiYXNlX3NpemUgPSAxNCkKCiMgQXJyYW5nZSBwbG90cyB2ZXJ0aWNhbGx5CmdyaWQuYXJyYW5nZShwMSwgcDIsIG5jb2wgPSAxKQpgYGAKCkFsdGVybmF0aXZlbHksIHVzaW5nIGJhc2UgYFJgIHBsb3R0aW5nOgoKYGBge3IgcGNhLWxvYWRpbmdzLXBsb3R9CnBhcihtZnJvdyA9IGMoMiwgMSkpCgojIEZpcnN0IHBsb3QgKFBDMSkKaGlzdChsb2FkaW5nc18xXzIkbG9hZGluZ3NfMSwgYnJlYWtzID0gNTAsIHhsYWIgPSAiUEMgMSBsb2FkaW5ncyIsIG1haW4gPSAiIikKIyBBZGQgdmVydGljYWwgbGluZSBhdCA5NSUgcXVhbnRpbGUKYWJsaW5lKHYgPSBxdWFudGlsZShsb2FkaW5nc18xXzIkbG9hZGluZ3NfMSwgMC45NSksIGNvbCA9ICJmaXJlYnJpY2siLCBsd2QgPSAyKQoKIyBTZWNvbmQgcGxvdCAoUEMyKQpoaXN0KGxvYWRpbmdzXzFfMiRsb2FkaW5nc18yLCBicmVha3MgPSA1MCwgeGxhYiA9ICJQQyAyIGxvYWRpbmdzIiwgbWFpbiA9ICIiKQphYmxpbmUodiA9IGMoCiAgcXVhbnRpbGUobG9hZGluZ3NfMV8yJGxvYWRpbmdzXzIsIDAuMDUpLAogIHF1YW50aWxlKGxvYWRpbmdzXzFfMiRsb2FkaW5nc18yLCAwLjk1KQopLCBjb2wgPSAiZmlyZWJyaWNrIiwgbHdkID0gMikKYGBgCgpWZXJ0aWNhbCBsaW5lcyB3ZXJlIGFkZGVkIGF0IHRoZSA5NXRoIHBlcmNlbnRpbGUgZm9yIFBDMSBhbmQgdGhlIDV0aCBhbmQgOTV0aCBwZXJjZW50aWxlcyBmb3IgUEMyIHRvIHJlZmxlY3Qgd2hlcmUgdGhlIGxhcmdlc3QgKGluIGFic29sdXRlIHZhbHVlKSBsb2FkaW5ncyBhcmUgc2l0dWF0ZWQgKG5vIG5lZ2F0aXZlIGxvYWRpbmdzIGZvciBQQzEsIHNvIG9ubHkgc2hvd2luZyB0aGUgOTV0aCBwZXJjZW50aWxlKS4KCl9fSW50ZXJwcmV0YXRpb246X18gcmVtZW1iZXIgdGhhdCB0aGUgUEMgbG9hZGluZ3MgcmVmbGVjdCB0aGUgKmNvbnRyaWJ1dGlvbnMqIG9mIGVhY2ggZmVhdHVyZSAoaW4gdGhpcyBjYXNlOiBnZW5lKSB0byB0aGUgUEMuCkZyb20gdGhlc2UgaGlzdG9ncmFtcyBpdCBzaG91bGQgYmUgY2xlYXIgdGhhdCBvbmx5IGEgbWlub3IgZnJhY3Rpb24gb2YgdGhlIGdlbmVzIGFyZSByZWFsbHkgZHJpdmluZyB0aGVzZSBmaXJzdCAyIFBDcywgZXNwZWNpYWxseSBmb3IgUEMyICh3aGVyZSB0aGUgYnVsayBvZiBnZW5lcyBoYXMgbG9hZGluZ3MgY2xvc2UgdG8gMCkuCgo8L2RldGFpbHM+CgpXZSBrbm93IHRoYXQgdGhlIGZpcnN0IFBDICRcbWF0aGJme1pfMX0kIGlzIGdpdmVuIGJ5CiQkClxtYXRoYmZ7Wl8xfT1cbWF0aGJme1h9IFxtYXRoYmZ7Vl8xfSBcLCAsCiQkCgp3aGVyZSAkXG1hdGhiZntWXzF9JCBhcmUgdGhlIGxvYWRpbmdzIG9mIHRoZSBmaXJzdCBQQy4gQnkgc2V0dGluZyAkXGJvbGRzeW1ib2x7XGJldGF9ID0gXG1hdGhiZntWXzF9JCBhbmQgJFkgPSBaXzEkLCB3ZSBjYW4gZXhwcmVzcyB0aGlzIGFzIHRoZSByZWdyZXNzaW9uCgokJApcbWF0aGJme1l9PVxtYXRoYmZ7WH1cYm9sZHN5bWJvbHtcYmV0YX0gXCwgLgokJAoKUmVjYWxsIHRoYXQgdGhlIHJpZGdlIHJlZ3Jlc3Npb24gc29sdXRpb24gZm9yICRcYm9sZHN5bWJvbHtcYmV0YX0kIGlzIGdpdmVuIGJ5CgokJApcYm9sZHN5bWJvbHtcYmV0YX1fe1x0ZXh0e3JpZGdlfX0gPSAoXG1hdGhiZntYXlRYfStcbGFtYmRhXG1hdGhiZntJfSleey0xfVxtYXRoYmZ7WH1eVFxtYXRoYmYgWSBcLCAuCiQkCgojIyMjIyA1LiBSZXBsYWNlICRcbWF0aGJme1l9JCB3aXRoICRcbWF0aGJme1pfMX0kIGFuZCB2ZXJpZnkgbnVtZXJpY2FsbHkgaW4gYFJgIHRoYXQgIHstfQogICQkCiAgXG1hdGhiZiBWXzEgPQogICAgXGZyYWN7XGJvbGRzeW1ib2xcYmV0YV97XHRleHR7cmlkZ2V9fX17XHxcYm9sZHN5bWJvbFxiZXRhX3tcdGV4dHtyaWRnZX19XHxfMn0gXCwgLgogICQkCgpZb3UgbWF5IHVzZSBhbnkgJFxsYW1iZGEgPiAwJCBvZiB5b3VyIGNob2ljZS4gUmVtZW1iZXIgdGhhdCAkXHxcYm9sZHN5bWJvbFxiZXRhX3tcdGV4dHtyaWRnZX19XHxfMiA9IFxzcXJ0e1xib2xkc3ltYm9sXGJldGFfe1x0ZXh0e3JpZGdlfX1eVCBcYm9sZHN5bWJvbFxiZXRhX3tcdGV4dHtyaWRnZX19fSA9IFxzcXJ0e1xzdW1fe2o9MX1ecCBcYmV0YV9qXjJ9JC4KCjxkZXRhaWxzPjxzdW1tYXJ5PlNvbHV0aW9uPC9zdW1tYXJ5PgoKYGBge3IsIGNhY2hlPVRSVUV9CnAgPC0gZGltKFgpWzJdCnRYWF9sYW1iZGFfSSA8LSB0KFgpICUqJSBYICsgMiAqIGRpYWcocCkKIyBUaGlzIG1pZ2h0IHRha2UgYSB3aGlsZSB0byBjYWxjdWxhdGUKYmV0YV9yaWRnZSA8LSBzb2x2ZSh0WFhfbGFtYmRhX0kpICUqJSB0KFgpICUqJSBaWywgMV0KbWFnX2JldGFfcmlkZ2UgPC0gc3FydChzdW0oYmV0YV9yaWRnZV4yKSkKbWF4KGFicyhWWywgMV0gLSBiZXRhX3JpZGdlIC8gbWFnX2JldGFfcmlkZ2UpKQojIGFsdGVybmF0aXZlIGZvciBWWywgMV06IHBjYV94JHJvdGF0aW9uWywgMV0KYGBgCgpGb3IgYSB2aXN1YWwgY29tcGFyaXNvbiwgd2UgY2FuIGFsc28gcGxvdAokXGJvbGRzeW1ib2xcYmV0YV97XHRleHR7cmlkZ2V9fSAvIFx8XGJvbGRzeW1ib2xcYmV0YV97XHRleHR7cmlkZ2V9fVx8XzIkCmFnYWluc3QgdGhlIGxvYWRpbmdzICRcbWF0aGJmIFZfMSQuCgpgYGB7ciBiZXRhX3JpZGdlLXZzLVYxLXBsb3R9CnBhcihtZnJvdyA9IGMoMSwgMSkpCgpwbG90KFZbLCAxXSwgYmV0YV9yaWRnZSAvIG1hZ19iZXRhX3JpZGdlLAogIHhsYWIgPSBleHByZXNzaW9uKCJWIlsxXSksCiAgeWxhYiA9IGV4cHJlc3Npb24oYmV0YVsicmlkZ2UiXSAvIHBhc3RlKCJ8fCIsIGJldGEsICJ8fCIpWzJdKSwKICBwY2ggPSAxOQopCmBgYAoKPC9kZXRhaWxzPgoKWW91IGhhdmUgbm93IHNlZW4gdGhhdCB0aGUgbG9hZGluZ3Mgb2YgdGhlIFBDcyBjYW4gYmUgY29tcHV0ZWQgZnJvbSByaWRnZSByZWdyZXNzaW9uIGNvZWZmaWNpZW50cy4KCklmIHdlIGludHJvZHVjZSBhbiBhZGRpdGlvbmFsICRMXzEkIHBlbmFsdHkgb24gdGhlICRcYm9sZHN5bWJvbCBcYmV0YSQgY29lZmZpY2llbnRzLCB3ZSBtb3ZlIGZyb20gcmlkZ2UgcmVncmVzc2lvbiB0byBlbGFzdGljIG5ldCByZWdyZXNzaW9uLiBSZWNhbGwgdGhhdCBlbGFzdGljIG5ldCByZWdyZXNzaW9uIG1pbmltaXplcyB0aGUgY3JpdGVyaW9uCiQkClx8XG1hdGhiZiBZLVxtYXRoYmZ7WH1cYm9sZHN5bWJvbCBcYmV0YVx8XjJfMiArICBcYWxwaGEgXGxhbWJkYVx8XGJvbGRzeW1ib2wgXGJldGFcfF8xICsgKDEgLSBcYWxwaGEpIFxsYW1iZGFcfFxib2xkc3ltYm9sIFxiZXRhXHxeMl8yCiQkCmZvciAkXGFscGhhIFxpbiBbMCwxXSQuIFdoZW5ldmVyICRcYWxwaGEgPiAwJCwgYSBzdWZmaWNpZW50bHkgbGFyZ2UgJFxsYW1iZGEkIGZvcmNlcyBzb21lIG9mIHRoZSBjb2VmZmljaWVudHMgaW4gJFxib2xkc3ltYm9sXGJldGEkIHRvIGJlY29tZSB6ZXJvLgoKUmVncmVzc2luZyB0aGUgcHJpbmNpcGFsIGNvbXBvbmVudCAkWl8xJCBvbiAkWCQgdGhlcmVmb3JlIHNldHMgc29tZSBvZiB0aGUgbG9hZGluZ3MgJFZfMSQgdG8gemVyby4gVGhpcyBtZWFucyB0aGF0IGVsYXN0aWMgbmV0IGNhbiBiZSB1c2VkIHRvIGltcGxlbWVudCBzcGFyc2UgUENBLiAKCiMjIyMjIDYuIEZpdCBhbiBlbGFzdGljIG5ldCBtb2RlbCBmb3IgJFpfMSQuIFVzZSBgYWxwaGEgPSAwLjVgIGFuZCBgbGFtYmRhID0gYygxMSwgMS43NSwgMC4zKWAuIERldGVybWluZSB0aGUgbnVtYmVyIG9mIG5vbi16ZXJvIGxvYWRpbmdzIGZvciBlYWNoIGNob2ljZSBvZiBgbGFtYmRhYC4gUmVwZWF0IHRoZSBzYW1lIHN0ZXBzIGZvciAkWl8yJCwgYnV0IHVzZSBgbGFtYmRhID0gYyg1LCAxLjUsIDAuMTUpYC4gey19Cgo8ZGV0YWlscz48c3VtbWFyeT5Tb2x1dGlvbjwvc3VtbWFyeT4KCmBgYHtyIFBDLWdsbW5ldH0KIyBIZWxwZXIgZnVuY3Rpb24gdG8gZGV0ZXJtaW5lIG51bWJlciBvZiBub24temVybyBsb2FkaW5ncwpudW1fbG9hZGluZ3MgPC0gZnVuY3Rpb24oZml0KSB7CiAgbGFtYmRhcyA8LSBmaXQkbGFtYmRhCiAgbnVtX25vbnplcm8gPC0gTlVMTAogIGZvciAobGFtYmRhIGluIGxhbWJkYXMpIHsKICAgIG51bV9ub256ZXJvIDwtIGMoCiAgICAgIG51bV9ub256ZXJvLCBzdW0oYXMudmVjdG9yKGNvZWYoZml0LCBzID0gbGFtYmRhKSlbLTFdICE9IDApCiAgICApCiAgfQogIHJldHVybihkYXRhLmZyYW1lKGxhbWJkYXMsIG51bV9ub256ZXJvKSkKfQoKIyBFbGFzdGljIG5ldCBmb3IgWl8xCmZpdF9sb2FkaW5nczEgPC0gZ2xtbmV0KFgsIFpbLCAxXSwgYWxwaGEgPSAwLjUsIGxhbWJkYSA9IGMoMTEsIDEuNzUsIDAuMykpCm51bV9sb2FkaW5ncyhmaXRfbG9hZGluZ3MxKQoKIyBFbGFzdGljIG5ldCBmb3IgWl8yCmZpdF9sb2FkaW5nczIgPC0gZ2xtbmV0KFgsIFpbLCAyXSwgYWxwaGEgPSAwLjUsIGxhbWJkYSA9IGMoNSwgMS41LCAwLjE1KSkKbnVtX2xvYWRpbmdzKGZpdF9sb2FkaW5nczIpCmBgYAoKPC9kZXRhaWxzPgoKIyMjIyMgNy4gUGxvdCB0aGUgcmVzdWx0aW5nIHNwYXJzZSBmaXJzdCBhbmQgc2Vjb25kIFBDcyBhbmQgdXNlIGRpZmZlcmVudCBjb2xvcnMgZm9yIHR1bW9yIC8gbm9ybWFsIHRpc3N1ZS4gSG93IHdlbGwgZG8gdGhlc2UgbmV3IFBDcyBzZXBhcmF0ZSB0aGUgcmVzcG9uc2UgY2xhc3Nlcz8gey19CgpZb3UgbWF5IHJldXNlIHRoZSBjb2RlIGZyb20gdGFzayAzIHRvIHByb2R1Y2UgMyBuZXcgcGxvdHMgZm9yICQoXGxhbWJkYV97XHRleHR7UEMxfX0gPSAxMSwgXGxhbWJkYV97XHRleHR7UEMyfX0gPSA1KSQsICQoXGxhbWJkYV97XHRleHR7UEMxfX0gPSAxLjc1LCBcbGFtYmRhX3tcdGV4dHtQQzJ9fSA9IDEuNSkkIGFuZCAkKFxsYW1iZGFfe1x0ZXh0e1BDMX19ID0gMC4zLCBcbGFtYmRhX3tcdGV4dHtQQzJ9fSA9IDAuMTUpJC4gQ29tcGFyZSB0aGlzIHRvIHRoZSBwbG90IGZvciB0aGUgb3JpZ2luYWwgUENzIGZyb20gdGFzayAzIGFuZCBpbnRlcnByZXQuCgo8ZGV0YWlscz48c3VtbWFyeT5Tb2x1dGlvbjwvc3VtbWFyeT4KClVzaW5nIGJhc2UgYFJgIHBsb3R0aW5nOgoKYGBge3Igc3BhcnNlLVBDQS1wbG90cywgZmlnLndpZHRoID0gOX0KIyBIZWxwZXIgZnVuY3Rpb24gZm9yIHNjYXR0ZXJwbG90IGJldHdlZW4gdHdvIHNwYXJzZSBQQ3MKcGxvdF9zcGFyc2VfUENBIDwtIGZ1bmN0aW9uKGxvYWRpbmdzMSwgbG9hZGluZ3MyKSB7CiAgbm9uemVybzEgPC0gc3VtKGxvYWRpbmdzMVstMV0gIT0gMCkKICBub256ZXJvMiA8LSBzdW0obG9hZGluZ3MyWy0xXSAhPSAwKQoKICBTUEMxIDwtIFggJSolIGxvYWRpbmdzMVstMV0KICBTUEMyIDwtIFggJSolIGxvYWRpbmdzMlstMV0KCiAgcGxvdCgKICAgIFNQQzEsIFNQQzIsIGNvbCA9IGNvbHNbWV0sIHhsYWIgPSAiU1BDMSIsIHlsYWIgPSAiU1BDMiIsIHBjaCA9IDE2LAogICAgeWxpbSA9IGMoLTQwLCA0MCksIHhsaW0gPSBjKC02MCwgOTUpLAogICAgbWFpbiA9IHBhc3RlKG5vbnplcm8xLCAiZ2VuZXMgZm9yIFNQQzEsIiwgbm9uemVybzIsICJmb3IgU1BDMiIpCiAgKQogIGxlZ2VuZCgKICAgICJ0b3BsZWZ0IiwgbGVnZW5kID0gYygiTm9ybWFsIHRpc3N1ZSIsICJUdW1vciB0aXNzdWUiKSwKICAgIGJ0eSA9ICJuIiwgY29sID0gY29scywgcGNoID0gYygxNiwgMTYpLCBjZXggPSAxCiAgKQp9CgpwYXIobWZyb3cgPSBjKDIsIDIpKQpwbG90KFpbLCAxXSwgWlssIDJdLAogIGNvbCA9IGNvbHNbWV0sIHhsYWIgPSAiUEMxIiwgeWxhYiA9ICJQQzIiLCBwY2ggPSAxNiwKICB5bGltID0gYygtNDAsIDQwKSwgeGxpbSA9IGMoLTYwLCA5NSksCiAgbWFpbiA9ICJBbGwgMjAwMCBnZW5lcyBcbmZvciBQQzEgYW5kIFBDMiIKKQpsZWdlbmQoCiAgInRvcGxlZnQiLCBsZWdlbmQgPSBjKCJOb3JtYWwgdGlzc3VlIiwgIlR1bW9yIHRpc3N1ZSIpLAogIGJ0eSA9ICJuIiwgY29sID0gY29scywgcGNoID0gYygxNiwgMTYpLCBjZXggPSAxCikKcGxvdF9zcGFyc2VfUENBKAogIGFzLnZlY3Rvcihjb2VmKGZpdF9sb2FkaW5nczEsIHMgPSAwLjMpKSwKICBhcy52ZWN0b3IoY29lZihmaXRfbG9hZGluZ3MyLCBzID0gMC4xNSkpCikKcGxvdF9zcGFyc2VfUENBKAogIGFzLnZlY3Rvcihjb2VmKGZpdF9sb2FkaW5nczEsIHMgPSAxLjc1KSksCiAgYXMudmVjdG9yKGNvZWYoZml0X2xvYWRpbmdzMiwgcyA9IDEuNSkpCikKcGxvdF9zcGFyc2VfUENBKAogIGFzLnZlY3Rvcihjb2VmKGZpdF9sb2FkaW5nczEsIHMgPSAxMSkpLAogIGFzLnZlY3Rvcihjb2VmKGZpdF9sb2FkaW5nczIsIHMgPSA2KSkKKQpgYGAKCkFsdGVybmF0aXZlbHksIHVzaW5nIGBnZ3Bsb3RgOgoKYGBge3IgZ2dwbG90LXNwY30KIyBIZWxwZXIgZnVuY3Rpb24gZm9yIHNjYXR0ZXJwbG90IGJldHdlZW4gdHdvIHNwYXJzZSBQQ3MKcGxvdF9zcGFyc2VfUENBIDwtIGZ1bmN0aW9uKGxvYWRpbmdzMSwgbG9hZGluZ3MyKSB7CiAgbm9uemVybzEgPC0gc3VtKGxvYWRpbmdzMVstMV0gIT0gMCkKICBub256ZXJvMiA8LSBzdW0obG9hZGluZ3MyWy0xXSAhPSAwKQoKICBTUEMxIDwtIFggJSolIGxvYWRpbmdzMVstMV0KICBTUEMyIDwtIFggJSolIGxvYWRpbmdzMlstMV0KCiAgZ2dwbG90KGRhdGEuZnJhbWUoU1BDMSwgU1BDMiwgVGlzc3VlID0gWSksIGFlcyhTUEMxLCBTUEMyKSkgKwogICAgZ2VvbV9wb2ludChhZXMoY29sID0gVGlzc3VlKSwgc2l6ZSA9IDIpICsKICAgIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXMgPSBjKCJkZWVwc2t5Ymx1ZTIiLCAiY29yYWwxIikpICsKICAgIGxhYnModGl0bGUgPSBwYXN0ZShub256ZXJvMSwgImdlbmVzIGZvciBTUEMxLCIsIG5vbnplcm8yLCAiZm9yIFNQQzIiKSkgKwogICAgeGxpbSgtNjAsIDk1KSArCiAgICB5bGltKC00MCwgNDApICsKICAgIHRoZW1lX21pbmltYWwoKSArCiAgICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIiwgdGV4dCA9IGVsZW1lbnRfdGV4dChzaXplID0gOCkpCn0KCnBsb3QxIDwtIGdncGxvdChzY29yZXNfMV8yLCBhZXMoUEMxLCBQQzIpKSArCiAgZ2VvbV9wb2ludChhZXMoY29sID0gVGlzc3VlKSwgc2l6ZSA9IDIpICsKICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzID0gYygiZGVlcHNreWJsdWUyIiwgImNvcmFsMSIpKSArCiAgbGFicyh0aXRsZSA9ICJBbGwgMjAwMCBnZW5lcyBmb3IgUEMxIGFuZCBQQzIiKSArCiAgeGxpbSgtNjAsIDk1KSArCiAgeWxpbSgtNDAsIDQwKSArCiAgdGhlbWVfbWluaW1hbCgpICsKICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIiwgdGV4dCA9IGVsZW1lbnRfdGV4dChzaXplID0gOCkpCnBsb3QyIDwtIHBsb3Rfc3BhcnNlX1BDQSgKICBhcy52ZWN0b3IoY29lZihmaXRfbG9hZGluZ3MxLCBzID0gMC4zKSksCiAgYXMudmVjdG9yKGNvZWYoZml0X2xvYWRpbmdzMiwgcyA9IDAuMTUpKQopCnBsb3QzIDwtIHBsb3Rfc3BhcnNlX1BDQSgKICBhcy52ZWN0b3IoY29lZihmaXRfbG9hZGluZ3MxLCBzID0gMS43NSkpLAogIGFzLnZlY3Rvcihjb2VmKGZpdF9sb2FkaW5nczIsIHMgPSAxLjUpKQopCnBsb3Q0IDwtIHBsb3Rfc3BhcnNlX1BDQSgKICBhcy52ZWN0b3IoY29lZihmaXRfbG9hZGluZ3MxLCBzID0gMTEpKSwKICBhcy52ZWN0b3IoY29lZihmaXRfbG9hZGluZ3MyLCBzID0gNikpCikKCmdnYXJyYW5nZShwbG90MSwgcGxvdDIsIHBsb3QzLCBwbG90NCkKYGBgCgpfX0NvbmNsdXNpb246X18gU3BhcnNlIFBDQSBoYXMgc3VjY2VlZGVkIGluIHNldHRpbmcgdGhlIHVuaW5mb3JtYXRpdmUgZ2VuZXMgLyBsb2FkaW5ncyB0byB6ZXJvLiAKSW4gc2VwYXJhdGluZyBub3JtYWwgYW5kIHR1bW91ciB0aXNzdWVzLCBTUENBIHBlcmZvcm1zIHZpdHVhbGx5IHRoZSBzYW1lIGFzIFBDQS4gQnkgaW5jcmVhc2luZyAkXGxhbWJkYSQsIG1vcmUgc3BhcnNpdHkgaXMgaW5kdWNlZCBpbiB0aGUgbG9hZGluZ3MsIHdoaWxlIHRoZSBvdmVyYWxsIHZpc3VhbCBpbXByZXNzaW9uIHJlbWFpbnMgY29tcGFyYWJsZS4gClRoZSBrZXkgcG9pbnQgaGVyZSBpcyB0aGF0IFNQQ0EgdXNlcyBvbmx5IGEgbWlub3IgcHJvcG9ydGlvbiBvZiB0aGUgb3JpZ2luYWwgZmVhdHVyZXMgdG8gYWNoaWV2ZSB0aGUgc2FtZSByZXN1bHRzLCBzdWdnZXN0aW5nIHRoYXQgdGhlIGxhcmdlc3QgdmFyaWFiaWxpdHkgb2YgdGhlIGRhdGEgaXMgb25seSBkcml2ZW4gYnkgYSBtaW5vcml0eSBvZiBmZWF0dXJlcy4KCjwvZGV0YWlscz4KCl9fUmVtYXJrOl9fIE9mdGVudGltZXMsICRcbGFtYmRhJCBpcyBjaG9zZW4gaW4gc3VjaCBhIHdheSB0aGF0IGEgcGFydGljdWxhciBudW1iZXIgb3IgcmFuZ2Ugb2Ygbm9uemVybyBsb2FkaW5ncyBpcyBvYnRhaW5lZC4gVGhpcyBpcyBob3cgd2UgcHJvY2VlZGVkIGFib3ZlLCB3aGVyZSB3ZSBjb25zaWRlcmVkIGEgc2VxdWVuY2Ugb2YgJFxsYW1iZGEkJ3MgcmVzdWx0aW5nIGluIGRpZmZlcmVudCBsZXZlbHMgb2Ygc3BhcnNpdHkuCkFsdGVybmF0aXZlbHksIGlmIHlvdSB3YW50IHRvIHNlbGVjdCAkXGxhbWJkYSQgaW4gYSBkYXRhLWRyaXZlbiBtYW5uZXIsIHlvdSAqY2Fubm90KiByZWx5IG9uIGNyb3NzLXZhbGlkYXRpb24gd2l0aCB0aGUgcHJlZGljdGl2ZSBNU0UgYXMgdXNlZCBpbiBbTGFiIDNdKGh0dHBzOi8vc3RhdG9taWNzLmdpdGh1Yi5pby9IRERBL0xhYjMtUGVuYWxpemVkLVJlZ3Jlc3Npb24uaHRtbCM5X0V4ZXJjaXNlOl9ldmFsdWF0ZV9hbmRfY29tcGFyZV9wcmVkaWN0aW9uX21vZGVscykuIFRoZSByZWFzb24gZm9yIHRoaXMgaXMgdGhhdCB0aGUgbWluaW1hbCBjcm9zcy12YWxpZGF0ZWQgTVNFIHdpbGwgYmUgYWNoaWV2ZWQgZm9yICRcbGFtYmRhID0gMCQgKG9yIHRoZSBzbWFsbGVzdCBwb3NpdGl2ZSB2YWx1ZSB0aGF0IGNhbiBiZSBldmFsdWF0ZWQgd2l0aG91dCBudW1lcmljYWwgcHJvYmxlbXMpLgpJbnN0ZWFkLCB5b3UgY2FuIHRha2UgYSBsb29rIGF0IHRoZSBwcm9wb3J0aW9uIG9mIHZhcmlhbmNlIHRoYXQgaXMgZXhwbGFpbmVkIGJ5IHRoZSBzcGFyc2UgUENzIHRvIHNlbGVjdCBhIHNlbnNpYmxlIG51bWJlciBvZiBub256ZXJvIGxvYWRpbmdzIG9yIGEgc2Vuc2libGUgJFxsYW1iZGEkLiAKCgojIExEQQoKSW4gdGhpcyBzZWN0aW9uLCB3ZSB3aWxsIHBlcmZvcm0gTERBIG9uIHRoZSBkYXRhIGJ5IEBhbG9uMTk5OWJyb2FkIHRvIGdldCBhIGNsZWFyIHVuZGVyc3RhbmRpbmcgb24gdGhlIGdlbmVzIHJlc3BvbnNpYmxlIGZvciBzZXBhcmF0aW5nIHRoZSB0dW1vciBhbmQgbm9ybWFsIHRpc3N1ZSBncm91cHMuCgpSZW1lbWJlciB0aGF0IHRoZSBMREEgcHJvYmxlbSBjYW4gYmUgc3RhdGVkIGFzCgokJApcbWF0aGJme3Z9CiAgPSBcdGV4dHtBcmdNYXh9X2EgXGZyYWN7XG1hdGhiZnthXlQgQiBhfX17XG1hdGhiZnthXlQgVyBhfX0KICAgIFx0ZXh0eyBzdWJqZWN0IHRvIH0KICAgIFxtYXRoYmZ7YV5UIFcgYX0gPSAxIFwsICwKJCQKCndoaWNoIGlzIGVxdWl2YWxlbnQgdG8gdGhlIGVpZ2VudmFsdWUgLyBlaWdlbnZlY3RvciBwcm9ibGVtCgokJApcbWF0aGJmIFdeey0xfSBcbWF0aGJmIEIgXG1hdGhiZiBhPVxsYW1iZGEgXG1hdGhiZiBhIFwsIC4KJCQKCkluIG91ciBjYXNlLCB3aGVyZSB3ZSBvbmx5IGhhdmUgdHdvIGdyb3Vwcywgb25seSBvbmUgc29sdXRpb24gZXhpc3RzLgpUaGlzIGlzIHRoZSBlaWdlbnZlY3RvciAkYSA9IFxtYXRoYmYgdiQgYW5kIGl0cyBlaWdlbnZhbHVlLgokXG1hdGhiZiB2JCBjYW4gY2FuIGJlIGludGVycHJldGVkIGluIHRlcm1zIG9mIHdoaWNoIHByZWRpY3RvcnMgYXJlIGltcG9ydGFudCB0byBkaXNjcmltaW5hdGUgYmV0d2VlbiB0aGUgdHdvIGNsYXNzZXMuCldlIGNhbiB0aGVuIHdyaXRlIHRoZSBzY29yZXMgYXMKCiQkClxtYXRoYmYgWj1cbWF0aGJmIFggXG1hdGhiZiB2IFwsIC4KJCQKCgojIyBUYXNrcyB7LX0KCiMjIyMjIDEuIFVzZSB0aGUgYGxkYSgpYCBmdW5jdGlvbiBmcm9tIHRoZSBgTUFTU2AgcGFja2FnZSB0byBmaXQgYW4gTERBIG9uIGBYYCB3aXRoIGdyb3VwaW5nIGBZYC4gey19CgpOb3RlIHRoYXQgdGhlIGBncm91cGluZ2AgaGFzIHRvIGJlIGEgZmFjdG9yIHZhcmlhYmxlLgoKPGRldGFpbHM+PHN1bW1hcnk+U29sdXRpb248L3N1bW1hcnk+CgpgYGB7ciBMREF9CmFsb25fbGRhIDwtIGxkYSh4ID0gWCwgZ3JvdXBpbmcgPSBZKQoKIyBhbHRlcm5hdGl2ZTogWSB+IFgKYGBgCgpOb3RlIHRoZSB3YXJuaW5nIHJlZ2FyZGluZyBjb2xsaW5lYXJpdHkuCgo8L2RldGFpbHM+CgojIyMjIyAyLiBFeHRyYWN0ICRcbWF0aGJmIHYkIGZyb20gdGhlIGBsZGFgIG9iamVjdC4gey19CgpIaW50OiBIYXZlIGEgbG9vayBhdCB0aGUgIlZhbHVlIiBzZWN0aW9uIG9mIGA/bGRhYC4KCjxkZXRhaWxzPjxzdW1tYXJ5PlNvbHV0aW9uPC9zdW1tYXJ5PgoKYGBge3IgTERBLWVpZ2VudmVjdG9yfQpWMSA8LSBhbG9uX2xkYSRzY2FsaW5nCmBgYAoKPC9kZXRhaWxzPgoKIyMjIyMgMy4gQ29tcHV0ZSAkXG1hdGhiZiBaJC4gey19Cgo8ZGV0YWlscz48c3VtbWFyeT5Tb2x1dGlvbjwvc3VtbWFyeT4KCmBgYHtyIExEQS1zY29yZXN9CloxIDwtIFggJSolIFYxCmBgYAoKPC9kZXRhaWxzPgoKIyMjIyMgNC4gUGxvdCB0aGUgc2NvcmVzICRcbWF0aGJmIFokIGFnYWluc3QgdGhlIHJlc3BvbnNlIHRvIHNlZSBob3cgd2VsbCB0aGUgTERBIHNlcGFyYXRlcyB0aGUgdHVtb3VyIGFuZCBub3JtYWwgdGlzc3VlcyBncm91cHMuIEludGVycHJldCB5b3VyIHBsb3Quey19CgpIaW50OiBBIGJveHBsb3Qgd291bGQgYmUgYW4gYXBwcm9wcmlhdGUgdmlzdWFsaXphdGlvbiwgYnV0IGZlZWwgZnJlZSB0byBiZSBjcmVhdGl2ZS4KCjxkZXRhaWxzPjxzdW1tYXJ5PlNvbHV0aW9uPC9zdW1tYXJ5PgoKVXNpbmcgYGdncGxvdGA6CgpgYGB7ciBMREEtZ2dib3hwbG90fQpnZ3Bsb3QoZGF0YS5mcmFtZShaMSwgWSksIGFlcyh4ID0gWSwgeSA9IFoxLCBmaWxsID0gYXMuZmFjdG9yKFkpKSkgKwogIGdlb21fYm94cGxvdCgpICsKICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBjb2xzKSArCiAgbGFicyh4ID0gIlkiLCB5ID0gIloiLAogICAgdGl0bGUgPSAiU2VwYXJhdGlvbiBvZiBub3JtYWwgYW5kIHR1bW91ciBzYW1wbGVzIGJ5IExEQSIKICApICsKICB0aGVtZV9taW5pbWFsKCkgKwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIiwgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChoanVzdCA9IDAuNSkpCmBgYAoKQWx0ZXJuYXRpdmVseSwgdXNpbmcgYmFzZSBgUmAgcGxvdHRpbmc6CgpgYGB7ciBMREEtYm94cGxvdH0KcGFyKG1mcm93ID0gYygxLCAxKSkKYm94cGxvdCgKICBaMSB+IFksIGNvbCA9IGNvbHMsIHlsYWIgPSAiWiIsCiAgbWFpbiA9ICJTZXBhcmF0aW9uIG9mIG5vcm1hbCBhbmQgdHVtb3VyIHNhbXBsZXMgYnkgTERBIgopCmBgYAoKX19JbnRlcnByZXRhdGlvbl9fOiBUaGUgTERBIGNhbiBzZXBhcmF0ZSB0aGUgdHVtb3VyIGFuZCBub3JtYWwgdGlzc3VlIHNhbXBsZXMgdmVyeSB3ZWxsLiBJbiBjb21wYXJpc29uLCB0aGUgZmlyc3QgYW5kIHNlY29uZCBjb21wb25lbnRzIG9mIHRoZSAoc3BhcnNlKSBQQ0EgKGFuIHVuc3VwZXJ2aXNlZCBtZXRob2QpIGluIHRoZSBmaXJzdCBwYXJ0IG9mIHRoaXMgTGFiIGRpZCBub3QgZGlzY3JpbWluYXRlIHRoZSB0d28gZ3JvdXBzIG5lYXJseSBhcyB3ZWxsLgoKPC9kZXRhaWxzPgoKV2UgY2FuIHZpZXcKJCQKXG1hdGhiZiBaID0gXG1hdGhiZiBYIFxtYXRoYmYgdgokJAphcyBhIHJlZ3Jlc3Npb24gb2YgJFxtYXRoYmYgWiQgb24gJFxtYXRoYmYgWCQgd2l0aCBjb2VmZmljaWVudCB2ZWN0b3IgJFxtYXRoYmYgdiQuIFRoZSBlbnRyaWVzIG9mICRcbWF0aGJmIHYkIGFyZSBub24temVybyBmb3IgYWxsIGdlbmVzLgpUbyBpbXBsZW1lbnQgYSBzcGFyc2UgTERBLCB3aGVyZSBzb21lIG9mIHRoZSBlbnRyaWVzIG9mICRcbWF0aGJmIHYkIGFyZSBzZXQgdG8gemVybywgd2UgY2FuIGludHJvZHVjZSBhbiAkTF8xJCBwZW5hbHR5IG9uICRcbWF0aGJmIHYkIGFuZCBmaXQgYSBMYXNzbyByZWdyZXNzaW9uLgpUaGUgcmVzdWx0aW5nICRcbWF0aGJmIHYkIHdpbGwgb25seSBiZSBkZXRlcm1pbmVkIGJ5IGEgZmV3IGludGVyZXN0aW5nIGdlbmVzIGluc3RlYWQgb2YgYWxsIDIwMDAuCgojIyMjIyA1LiBGaXQgYSBMYXNzbyBtb2RlbCBmb3IgJFxtYXRoYmYgWiQgd2l0aCBgbGFtYmRhID0gYygwLjIsIDAuMDIsIDAuMDAyKWAuIERldGVybWluZSB0aGUgbnVtYmVyIG9mIG5vbi16ZXJvIGxvYWRpbmdzIGZvciBlYWNoIGNob2ljZSBvZiBgbGFtYmRhYC4gey19Cgo8ZGV0YWlscz48c3VtbWFyeT5Tb2x1dGlvbjwvc3VtbWFyeT4KCmBgYHtyIHNwYXJzZS1MREF9CmxkYV9sb2FkaW5ncyA8LSBnbG1uZXQoWCwgWjEsIGFscGhhID0gMSwgbGFtYmRhID0gYygwLjIsIDAuMDIsIDAuMDAyKSkKbnVtX2xvYWRpbmdzKGxkYV9sb2FkaW5ncykKYGBgCgo8L2RldGFpbHM+CgojIyMjIyA2LiBQbG90IHRoZSByZXN1bHRpbmcgc3BhcnNlIHNjb3JlcyBhZ2FpbnN0IHRoZSByZXNwb25zZS4gQXJlIHRoZSBzbWFsbGVyIHN1YnNldHMgb2YgZ2VuZXMgYXMgZWZmZWN0aXZlIGluIHNlcGFyYXRpbmcgdGhlIHR1bW91ciBhbmQgbm9ybWFsIHRpc3N1ZSBncm91cHMgYXMgdGhlIGVudGlyZSBzZXQgb2YgZ2VuZXM/IHstfQoKWW91IG1heSByZXVzZSB0aGUgY29kZSBmcm9tIHRhc2sgNCB0byBwcm9kdWNlIDMgbmV3IHBsb3RzLCBvbmUgZm9yIGVhY2ggY2hvaWNlIG9mICRcbGFtYmRhJC4gQ29tcGFyZSB0aGlzIHRvIHRoZSBwbG90IGZvciB0aGUgb3JpZ2luYWwgTERBIGZyb20gdGFzayA0IGFuZCBpbnRlcnByZXQuCgo8ZGV0YWlscz48c3VtbWFyeT5Tb2x1dGlvbjwvc3VtbWFyeT4KClVzaW5nIGJhc2UgYFJgIHBsb3R0aW5nOgoKYGBge3Igc3BhcnNlLUxEQS1wbG90cywgZmlnLndpZHRoID0gOX0KIyBIZWxwZXIgZnVuY3Rpb24gZm9yIHNjYXR0ZXJwbG90IGJldHdlZW4gdHdvIHNwYXJzZSBQQ3MKcGxvdF9zcGFyc2VfTERBIDwtIGZ1bmN0aW9uKGxvYWRpbmdzKSB7CiAgbl9ub256ZXJvIDwtIHN1bShsb2FkaW5nc1stMV0gIT0gMCkKICBTTERBIDwtIFggJSolIGxvYWRpbmdzWy0xXQoKICBib3hwbG90KAogICAgU0xEQSB+IFksIGNvbCA9IGNvbHMsIHlsYWIgPSAiWiIsCiAgICBtYWluID0gc3ByaW50ZigiU3Vic2V0IG9mICVkIGdlbmVzIiwgbl9ub256ZXJvKSwKICAgIHlsaW0gPSBjKC00LCA0KQogICkKfQoKcGFyKG1mcm93ID0gYygyLCAyKSkKYm94cGxvdCgKICBaMSB+IFksIGNvbCA9IGNvbHMsIHlsYWIgPSAiWiIsCiAgbWFpbiA9ICJFbnRpcmUgc2V0IG9mIDIwMDAgZ2VuZXMiLAogIHlsaW0gPSBjKC00LCA0KQopCnBsb3Rfc3BhcnNlX0xEQSgKICBhcy52ZWN0b3IoY29lZihsZGFfbG9hZGluZ3MsIHMgPSAwLjAwMikpCikKcGxvdF9zcGFyc2VfTERBKAogIGFzLnZlY3Rvcihjb2VmKGxkYV9sb2FkaW5ncywgcyA9IDAuMDIpKQopCnBsb3Rfc3BhcnNlX0xEQSgKICBhcy52ZWN0b3IoY29lZihsZGFfbG9hZGluZ3MsIHMgPSAwLjIpKQopCmBgYAoKQWx0ZXJuYXRpdmVseSwgdXNpbmcgYGdncGxvdGA6CgpgYGB7ciBzcGFyc2UtTERBLWdncGxvdHN9CiMgSGVscGVyIGZ1bmN0aW9uIGZvciBzY2F0dGVycGxvdCBiZXR3ZWVuIHR3byBzcGFyc2UgUENzCnBsb3Rfc3BhcnNlX0xEQSA8LSBmdW5jdGlvbihsb2FkaW5ncykgewogIG5fbm9uemVybyA8LSBzdW0obG9hZGluZ3NbLTFdICE9IDApCiAgU0xEQSA8LSBYICUqJSBsb2FkaW5nc1stMV0KCiAgZ2dwbG90KGRhdGEuZnJhbWUoU0xEQSwgWSksIGFlcyh4ID0gWSwgeSA9IFNMREEsIGZpbGwgPSBhcy5mYWN0b3IoWSkpKSArCiAgICBnZW9tX2JveHBsb3QoKSArCiAgICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBjb2xzKSArCiAgICBsYWJzKHggPSAiWSIsIHkgPSAiWiIsCiAgICAgIHRpdGxlID0gc3ByaW50ZigiU3Vic2V0IG9mICVkIGdlbmVzIiwgbl9ub256ZXJvKQogICAgKSArCiAgICB5bGltKC00LCA0KSArCiAgICB0aGVtZV9taW5pbWFsKCkgKwogICAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiLCBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGhqdXN0ID0gMC41KSkKfQoKcGxvdDEgPC0gZ2dwbG90KGRhdGEuZnJhbWUoWjEsIFkpLCBhZXMoeCA9IFksIHkgPSBaMSwgZmlsbCA9IGFzLmZhY3RvcihZKSkpICsKICBnZW9tX2JveHBsb3QoKSArCiAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gY29scykgKwogIGxhYnMoeCA9ICJZIiwgeSA9ICJaIiwKICAgIHRpdGxlID0gIkVudGlyZSBzZXQgb2YgMjAwMCBnZW5lcyIKICApICsKICB5bGltKC00LCA0KSArCiAgdGhlbWVfbWluaW1hbCgpICsKICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIsIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLjUpKQpwbG90MiA8LSBwbG90X3NwYXJzZV9MREEoCiAgYXMudmVjdG9yKGNvZWYobGRhX2xvYWRpbmdzLCBzID0gMC4wMDIpKQopCnBsb3QzIDwtIHBsb3Rfc3BhcnNlX0xEQSgKICBhcy52ZWN0b3IoY29lZihsZGFfbG9hZGluZ3MsIHMgPSAwLjAyKSkKKQpwbG90NCA8LSBwbG90X3NwYXJzZV9MREEoCiAgYXMudmVjdG9yKGNvZWYobGRhX2xvYWRpbmdzLCBzID0gMC4yKSkKKQoKZ2dhcnJhbmdlKHBsb3QxLCBwbG90MiwgcGxvdDMsIHBsb3Q0KQpgYGAKCl9fQ29uY2x1c2lvbjpfXyBTcGFyc2UgTERBIHBlcmZvcm1zIGFsbW9zdCBpZGVudGljYWxseSB0byB0aGUgb3JpZ2luYWwgTERBIGluIHNlcGFyYXRpbmcgbm9ybWFsIGFuZCB0dW1vdXIgdGlzc3Vlcy4gRXZlbiB3aXRoIG9ubHkgdGhlIDE4IG1vc3QgaW1wb3J0YW50IGdlbmVzLCB0aGUgc3BhcnNlIExEQSBpcyBtb3N0bHkgYWJsZSB0byBkaXNjcmltaW5hdGUgdGhlIHR3byBjbGFzc2VzIHdlbGwuClRoZXJlZm9yZSwgdGhlIHNlcGFyYXRpb24gYmV0d2VlbiBub3JtYWwgYW5kIHR1bW91ciB0aXNzdWVzIGlzIG1haW5seSBkcml2ZW4gYnkgb25seSBhIHNtYWxsIHByb3BvcnRpb24gb2YgZ2VuZXMuCgo8L2RldGFpbHM+CgojIyBCb251czogRXZhbHVhdGlvbiBvZiBMREEgYXMgYSBwcmVkaWN0aXZlIGNsYXNzaWZpZXIgey19CgpXZSB1c2UgMTAtZm9sZCBjcm9zcy12YWxpZGF0aW9uIHRvIGV2YWx1YXRlIHRoZSBwcmVkaWN0aXZlIGFjY3VyYWN5IG9mIGNsYXNzaWZpY2F0aW9ucyBmcm9tIHRoZSBvcmlnaW5hbCBMREEgb24gYWxsIDIwMDAgZ2VuZXMgYW5kIHRoZSBTTERBIG9uIHRoZSB0aHJlZSBzbWFsbGVyIHN1YnNldHMgb2YgZ2VuZXMuCgpgYGB7ciBMREEtQ1Z9CnNldC5zZWVkKDkxNDc1MSkKCiMgSGVscGVyIGZ1bmN0aW9uIHRvIGNvbXB1dGUgYWNjdXJhY3kgZ2l2ZW4gdiwgWCwgYW5kIFkKbGRhX2FjY3VyYWN5IDwtIGZ1bmN0aW9uKHYsIFgsIFkpIHsKICBwcmVkcyA8LSAoWCAlKiUgdikgPiAwCiAgb2JzIDwtIFkgPT0gInQiCiAgcmV0dXJuKDEgLSBtZWFuKGFicyhwcmVkcyAtIG9icykpKQp9CgphY2NfZnVsbCA8LSBhY2Nfc21hbGwgPC0gYWNjX3NtYWxsZXIgPC0gYWNjX3NtYWxsZXN0IDwtIE5VTEwKCiMgUmFuZG9tbHkgc3BsaXQgdGhlIGRhdGEgaW50byBrIGZvbGRzCmZvbGRzIDwtIHNhbXBsZShyZXAoMToxMCwgbGVuZ3RoLm91dCA9IGxlbmd0aChZKSkpCgpmb3IgKGsgaW4gMToxMCkgewogICMgQXNzaWduIHRoZSB0cmFpbiBhbmQgdmFsaWRhdGlvbiBzZXQgZm9yIHRoaXMgZm9sZAogIHZhbF9pZHggPC0gd2hpY2goZm9sZHMgPT0gaykKICB0cmFpbl9pZHggPC0gc2V0ZGlmZihzZXFfbGVuKGxlbmd0aChZKSksIHZhbF9pZHgpCiAgWF90cmFpbiA8LSBYW3RyYWluX2lkeCwgLCBkcm9wID0gRkFMU0VdCiAgWV90cmFpbiA8LSBZW3RyYWluX2lkeF0KICBYX3ZhbCA8LSBYW3ZhbF9pZHgsICwgZHJvcCA9IEZBTFNFXQogIFlfdmFsIDwtIFlbdmFsX2lkeF0KCiAgIyBGaXQgdGhlIExEQQogIGxkYV9mb2xkIDwtIGxkYSh4ID0gWF90cmFpbiwgZ3JvdXBpbmcgPSBZX3RyYWluKQogICMgRml0IHRoZSBTTERBIHdpdGggdGhyZWUgbGV2ZWxzIG9mIHNwYXJzaXR5CiAgWl9mb2xkIDwtIFhfdHJhaW4gJSolIGxkYV9mb2xkJHNjYWxpbmcKICBsYXNzb19mb2xkIDwtIGdsbW5ldChYX3RyYWluLCBaX2ZvbGQsIGFscGhhID0gMSwgbGFtYmRhID0gYygwLjIsIDAuMDIsIDAuMDAyKSkKICAjIEV2YWx1YXRlIHRoZSBhY2N1cmFjeSBvZiBhbGwgZm91ciBtb2RlbHMgb24gdGhlIHZhbGlkYXRpb24gc2V0CiAgYWNjX2Z1bGwgPC0gYyhhY2NfZnVsbCwgbGRhX2FjY3VyYWN5KGxkYV9mb2xkJHNjYWxpbmcsIFhfdmFsLCBZX3ZhbCkpCiAgYWNjX3NtYWxsIDwtIGMoCiAgICBhY2Nfc21hbGwsCiAgICBsZGFfYWNjdXJhY3koYXMudmVjdG9yKGNvZWYobGFzc29fZm9sZCwgcyA9IDAuMDAyKSlbLTFdLCBYX3ZhbCwgWV92YWwpCiAgKQogIGFjY19zbWFsbGVyIDwtIGMoCiAgICBhY2Nfc21hbGxlciwKICAgIGxkYV9hY2N1cmFjeShhcy52ZWN0b3IoY29lZihsYXNzb19mb2xkLCBzID0gMC4wMikpWy0xXSwgWF92YWwsIFlfdmFsKQogICkKICBhY2Nfc21hbGxlc3QgPC0gYygKICAgIGFjY19zbWFsbGVzdCwKICAgIGxkYV9hY2N1cmFjeShhcy52ZWN0b3IoY29lZihsYXNzb19mb2xkLCBzID0gMC4yKSlbLTFdLCBYX3ZhbCwgWV92YWwpCiAgKQp9CgpkYXRhLmZyYW1lKAogIExEQSA9IGMoIm9yaWdpbmFsIiwgInNwYXJzZSIsICJzcGFyc2VyIiwgInNwYXJzZXN0IiksCiAgYWNjdXJhY3kgPSBjKAogICAgbWVhbihhY2NfZnVsbCksIG1lYW4oYWNjX3NtYWxsKSwgbWVhbihhY2Nfc21hbGxlciksIG1lYW4oYWNjX3NtYWxsZXN0KQogICkKKQpgYGAKCiMgQWRkaXRpb25hbCByZXNvdXJjZXMgey19Ci0gU2VjdGlvbiA0LjMgKExEQSkgYW5kIDE0LjUuNSAoU1BDQSkgb2YgQGVzbC1ib29rCi0gRm9yIGEgc2ltcGxlIGV4cGxhbmF0aW9uIG9mIHRoZSBjb25jZXB0IGFuZCBpbnRlcnByZXRhdGlvbiBvZiBMREEgKGFuZCBvdGhlciBzdGF0aXN0aWNhbCBtZXRob2RzKSwgaGF2ZSBhIGxvb2sgYXQgPGh0dHBzOi8vd3d3LnlvdXR1YmUuY29tL3dhdGNoP3Y9YXpYQ3pJNTdZZmM+CgpgYGB7ciwgY2hpbGQ9Il9zZXNzaW9uLWluZm8uUm1kIn0KYGBgCgojIFJlZmVyZW5jZXMgey19Cg==