Creative Commons License

This is part of the online course Statistical Genomics 2021 (SGA21)

1 Breast cancer example

  • part of study https://doi.org/10.1093/jnci/djj052)
  • Histologic grade in breast cancer clinically prognostic. Association of histologic grade on expression of KPNA2 gene that is known to be associated with poor BC prognosis.
  • Population: all current and future breast cancer patients







2 Data Exploration

2.1 Import

library(tidyverse)
## ── Attaching packages ─────────────────────────────────────── tidyverse 1.3.1 ──
## ✔ ggplot2 3.3.5     ✔ purrr   0.3.4
## ✔ tibble  3.1.6     ✔ dplyr   1.0.7
## ✔ tidyr   1.1.4     ✔ stringr 1.4.0
## ✔ readr   2.1.1     ✔ forcats 0.5.1
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ dplyr::filter() masks stats::filter()
## ✖ dplyr::lag()    masks stats::lag()
gene <- read.table("https://raw.githubusercontent.com/statOmics/SGA21/master/data/kpna2.txt",header=TRUE)
head(gene)

We will transform the variable grade and node to a factor

gene$grade <- as.factor(gene$grade)
gene$node <- as.factor(gene$node)

2.2 Summary statistics

geneSum <- gene %>%
  group_by(grade) %>%
  summarize(mean = mean(gene),
            sd = sd(gene),
            n=length(gene)
            ) %>%
  mutate(se = sd/sqrt(n))
geneSum

2.3 Visualisation

gene %>%
  ggplot(aes(x=grade,y=gene)) +
  geom_boxplot(outlier.shape=NA) +
  geom_jitter()

We can also save the plots as objects for later use!

p1 <- gene %>%
  ggplot(aes(x=grade,y=gene)) +
  geom_boxplot(outlier.shape=NA) +
  geom_jitter()

p2 <- gene %>%
  ggplot(aes(sample=gene)) +
  geom_qq() +
  geom_qq_line() +
  facet_wrap(~grade)

p1

p2

2.4 Research questions

Researchers want to assess the association of the histological grade on KPNA2 gene expression


2.5 Estimation of effect size and standard error

effectSize <- tibble(
  delta = geneSum$mean[2]- geneSum$mean[1],
  seDelta = geneSum %>%
    pull(se) %>%
    .^2 %>%
    sum %>%
    sqrt
  )
effectSize

3 Statistical Inference

  • Researchers want to assess the association of histological grade on KPNA2 gene expression
  • Inference?


  • Researchers want to assess the association of histological grade on KPNA2 gene expression
  • Inference?
  • testing + CI $ $ Assumptions

  • In general we start from alternative hypothese \(H_A\): we want to show an association

  • Gene expression of grade 1 and grade 3 patients is on average different

  • But, we will assess it by falsifying the opposite:

  • The average KPNA2 gene expression of grade 1 and grade 3 patients is equal


  • How likely is it to observe an equal or more extreme association than the one observed in the sample when the null hypothesis is true?

  • When we make assumptions about the distribution of our test statistic we can quantify this probability: p-value.

  • If the p-value is below a significance threshold \(\alpha\) we reject the null hypothesis

We control the probability on a false positive result at the \(\alpha\)-level (type I error)

  • The p-value will only be calculated correctly if the underlying assumptions hold!
library(gridExtra)
## 
## Attaching package: 'gridExtra'
## The following object is masked from 'package:dplyr':
## 
##     combine
p1

p2

t.test(gene~grade,data=gene)
## 
##  Welch Two Sample t-test
## 
## data:  gene by grade
## t = -7.2132, df = 15.384, p-value = 2.598e-06
## alternative hypothesis: true difference in means between group 1 and group 3 is not equal to 0
## 95 percent confidence interval:
##  -425.4218 -231.6751
## sample estimates:
## mean in group 1 mean in group 3 
##        232.5003        561.0487
effectSize <- effectSize %>%
  mutate(t.stat=delta/seDelta) %>%
  mutate(p.value= pt(-abs(t.stat),21.352)*2)

effectSize
  • Intensities are often not normally distributed and have a mean variance relation
  • Commonly log2-transformed
  • Differences on log scale:

\[ \log_2(B) - \log_2(A) = \log_2 \frac{B}{A} = \log_2 FC_{\frac{B}{A}} \]


3.1 Log transformation

gene <- gene %>%
  mutate(lgene = log2(gene))

p1 <- gene %>%
  ggplot(aes(x=grade,y=lgene)) +
  geom_boxplot(outlier.shape=NA) +
  geom_jitter()

p2 <- gene %>%
  ggplot(aes(sample=lgene)) +
  geom_qq() +
  geom_qq_line() +
  facet_wrap(~grade)

p1

p2

logtest <- t.test(lgene~grade,data=gene,var.equal=TRUE)
logtest
## 
##  Two Sample t-test
## 
## data:  lgene by grade
## t = -8.0455, df = 22, p-value = 5.372e-08
## alternative hypothesis: true difference in means between group 1 and group 3 is not equal to 0
## 95 percent confidence interval:
##  -1.610148 -0.950178
## sample estimates:
## mean in group 1 mean in group 3 
##        7.808478        9.088641
log2FC <- logtest$estimate[2]-logtest$estimate[1]
log2FC
## mean in group 3 
##        1.280163
names(log2FC) <- "g3-g1"
2^log2FC
##    g3-g1 
## 2.428664

3.2 Conclusion

There is a extremely significant association of the histological grade on the gene expression in tumor tissue. On average, the gene expression for the grade 3 patients is 2.43 times higher than the gene expression in grade 1 patients (95% CI [1.93, 3.05], \(p<<0.001\)).



The patients also differ in the their lymph node status. Hence, we have a two factorial design: grade x lymph node status!!!

Solution??


4 General Linear Model

How can we integrate multiple factors and continuous covariates in linear model.

\[ y_i= \beta_0 + \beta_1 x_{i,1} + \beta_2 x_{i,2} + \beta_{12}x_{i,1}x_{i,2}+\epsilon_i, \] with

  • \(x_{i,1}\) a dummy variable for histological grade: \(x_{i,1}=\begin{cases} 0& \text{grade 1}\\ 1& \text{grade 3} \end{cases}\)
  • \(x_{i,2}\) a dummy variable for : \(x_{i,2}=\begin{cases} 0& \text{lymph nodes were not removed}\\ 1& \text{lymph nodes were removed} \end{cases}\)
  • \(\epsilon_i\)?

4.1 Implementation in R

lm1 <- lm(gene~grade*node,data=gene)
summary(lm1)
## 
## Call:
## lm(formula = gene ~ grade * node, data = gene)
## 
## Residuals:
##      Min       1Q   Median       3Q      Max 
## -201.748  -53.294   -6.308   46.216  277.601 
## 
## Coefficients:
##              Estimate Std. Error t value Pr(>|t|)    
## (Intercept)    180.51      44.37   4.068   0.0006 ***
## grade3         401.33      62.75   6.396 3.07e-06 ***
## node1          103.98      62.75   1.657   0.1131    
## grade3:node1  -145.57      88.74  -1.640   0.1166    
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 108.7 on 20 degrees of freedom
## Multiple R-squared:  0.7437, Adjusted R-squared:  0.7052 
## F-statistic: 19.34 on 3 and 20 DF,  p-value: 3.971e-06

4.2 Assumptions

plot(lm1)


4.3 Breast cancer example

  • Paper: https://doi.org/10.1093/jnci/djj052
  • Histologic grade in breast cancer provides clinically important prognostic information. Two factors have to be concidered: Histologic grade (grade 1 and grade 3) and lymph node status (0 vs 1). The researchers assessed gene expression of the KPNA2 gene a protein-coding gene associated with breast cancer and are mainly interested in the association of histological grade. Note, that the gene variable consists of background corrected normalized intensities obtained with a microarray platform. Upon log-transformation, they are known to be a good proxy for the \(\log\) transformed concentration of gene expression product of the KPNA2 gene.
  • Research questions and translate them towards model parameters (contrasts)?
  • Make an R markdown file to answer the research questions
library(ExploreModelMatrix)
explMx <- VisualizeDesign(gene,designFormula = ~grade*node)
explMx$plotlist
## [[1]]

You can also explore the model matrix interactively:

ExploreModelMatrix(gene,designFormula = ~grade*node)

5 Linear regression in matrix form

5.1 Scalar form

  • Consider a vector of predictors \(\mathbf{x}=(x_1,\ldots,x_p)^T\) and
  • a real-valued response \(Y\)
  • then the linear regression model can be written as \[ Y=f(\mathbf{x}) +\epsilon=\beta_0+\sum\limits_{j=1}^p x_j\beta_j + \epsilon \] with i.i.d. \(\epsilon\sim N(0,\sigma^2)\)

5.2 Matrix form

  • \(n\) observations \((\mathbf{x}_1,y_1) \ldots (\mathbf{x}_n,y_n)\)
  • Regression in matrix notation \[\mathbf{Y}=\mathbf{X\beta} + \mathbf{\epsilon}\] with \(\mathbf{Y}=\left[\begin{array}{c}y_1\\ \vdots\\y_n\end{array}\right]\), \(\mathbf{X}=\left[\begin{array}{cccc} 1&x_{11}&\ldots&x_{1p}\\ \vdots&\vdots&&\vdots\\ 1&x_{n1}&\ldots&x_{np} \end{array}\right]\), \(\mathbf{\beta}=\left[\begin{array}{c}\beta_0\\ \vdots\\ \beta_p\end{array}\right]\) and \(\mathbf{\epsilon}=\left[\begin{array}{c} \epsilon_1 \\ \vdots \\ \epsilon_n\end{array}\right]\)

5.3 Least Squares (LS)

  • Minimize the residual sum of squares \[\begin{eqnarray*} RSS(\mathbf{\beta})&=&\sum\limits_{i=1}^n e^2_i\\ &=&\sum\limits_{i=1}^n \left(y_i-\beta_0-\sum\limits_{j=1}^p x_{ij}\beta_j\right)^2 \end{eqnarray*}\]
  • or in matrix notation \[\begin{eqnarray*} RSS(\mathbf{\beta})&=&(\mathbf{Y}-\mathbf{X\beta})^T(\mathbf{Y}-\mathbf{X\beta})\\ &=&\Vert \mathbf{Y}-\mathbf{X\beta}\Vert^2_2 \end{eqnarray*}\] with the \(L_2\)-norm of a \(p\)-dim. vector \(v\) \(\Vert \mathbf{v} \Vert=\sqrt{v_1^2+\ldots+v_p^2}\) \(\rightarrow\) \(\hat{\mathbf{\beta}}=\text{argmin}_\beta \Vert \mathbf{Y}-\mathbf{X\beta}\Vert^2_2\)

5.3.1 Minimize RSS

\[ \begin{array}{ccc} \frac{\partial RSS}{\partial \mathbf{\beta}}&=&\mathbf{0}\\\\ \frac{(\mathbf{Y}-\mathbf{X\beta})^T(\mathbf{Y}-\mathbf{X\beta})}{\partial \mathbf{\beta}}&=&\mathbf{0}\\\\ -2\mathbf{X}^T(\mathbf{Y}-\mathbf{X\beta})&=&\mathbf{0}\\\\ \mathbf{X}^T\mathbf{X\beta}&=&\mathbf{X}^T\mathbf{Y}\\\\ \hat{\mathbf{\beta}}&=&(\mathbf{X}^T\mathbf{X})^{-1}\mathbf{X}^T\mathbf{Y} \end{array} \]


5.3.2 Geometrical Interpretation

Toy Example: fit without intercept

  • n=3 and p=2 \[ \mathbf{X}=\left[\begin{array}{cc} 2&0\\ 0&2\\ 0&0 \end{array}\right] \]
set.seed(4)
x1 <- c(2,0,0)
x2 <- c(0,2,0)
y <- x1*0.5 + x2*0.5 + rnorm(3,2)
fit <- lm(y~-1+x1+x2)

5.3.2.1 Visualise fit

# predict values on regular xy grid
x1pred <- seq(-1, 3, length.out = 10)
x2pred <- seq(-1, 3, length.out = 10)
xy <- expand.grid(x1 = x1pred,
x2 = x2pred)
ypred <- matrix (nrow = 30, ncol = 30,
data = predict(fit, newdata = data.frame(xy),
interval = "prediction"))

library(plot3D)
## Warning in fun(libname, pkgname): no display name and no $DISPLAY environment
## variable
# fitted points for droplines to surface
th=20
ph=5
scatter3D(x1,
  x2,
  y,
  pch = 16,
  col="darkblue",
  cex = 1,
  theta = th,
  ticktype = "detailed",
  xlab = "x1",
  ylab = "x2",
  zlab = "y",  
  colvar=FALSE,
  bty = "g",
  xlim=c(-1,3),
  ylim=c(-1,3),
  zlim=c(-2,4))

for (i in 1:3)
  lines3D(
    x = rep(x1[i],2),
    y = rep(x2[i],2),
    z = c(y[i],fit$fitted[i]),
    col="darkblue",
    add=TRUE,
    lty=2)

z.pred3D <- outer(
  x1pred,
  x2pred,
  function(x1,x2)
  {
    fit$coef[1]*x1+fit$coef[2]*x2
  })

x.pred3D <- outer(
  x1pred,
  x2pred,
  function(x,y) x)

y.pred3D <- outer(
  x1pred,
  x2pred,
  function(x,y) y)

surf3D(
  x.pred3D,
  y.pred3D,
  z.pred3D,
  col="blue",
  facets=NA,
  add=TRUE)

5.3.3 Projection

  • We can also interpret the fit as the projection of the \(n\times 1\) vector \(\mathbf{Y}\) on the column space of the matrix \(\mathbf{X}\).

  • So each column in \(\mathbf{X}\) is also an \(n\times 1\) vector.

  • For the toy example n=3 and p=2. So the column space of X is a plane in the three dimensional space.

\[ \hat{\mathbf{Y}} = \mathbf{X} (\mathbf{X}^T\mathbf{X})^{-1} \mathbf{X}^T \mathbf{Y} \]

  1. Plane spanned by column space:
arrows3D(0,0,0,x1[1],x1[2],x1[3],xlim=c(0,5),ylim=c(0,5),zlim=c(0,5),bty = "g",theta=th,col=2,xlab="row 1",ylab="row 2",zlab="row 3")
text3D(x1[1],x1[2],x1[3],labels="X1",col=2,add=TRUE)
arrows3D(0,0,0,x2[1],x2[2],x2[3],add=TRUE,col=2)
text3D(x2[1],x2[2],x2[3],labels="X2",col=2,add=TRUE)

  1. Vector of Y:
arrows3D(0,0,0,x1[1],x1[2],x1[3],xlim=c(0,5),ylim=c(0,5),zlim=c(0,5),bty = "g",theta=th,col=2,xlab="row 1",ylab="row 2",zlab="row 3")
text3D(x1[1],x1[2],x1[3],labels="X1",col=2,add=TRUE)
arrows3D(0,0,0,x2[1],x2[2],x2[3],add=TRUE,col=2)
text3D(x2[1],x2[2],x2[3],labels="X2",col=2,add=TRUE)
arrows3D(0,0,0,y[1],y[2],y[3],add=TRUE,col="darkblue")
text3D(y[1],y[2],y[3],labels="Y",col="darkblue",add=TRUE)

  1. Projection of Y onto column space
arrows3D(0,0,0,x1[1],x1[2],x1[3],xlim=c(0,5),ylim=c(0,5),zlim=c(0,5),bty = "g",theta=th,col=2,xlab="row 1",ylab="row 2",zlab="row 3")
text3D(x1[1],x1[2],x1[3],labels="X1",col=2,add=TRUE)
arrows3D(0,0,0,x2[1],x2[2],x2[3],add=TRUE,col=2)
text3D(x2[1],x2[2],x2[3],labels="X2",col=2,add=TRUE)
arrows3D(0,0,0,y[1],y[2],y[3],add=TRUE,col="darkblue")
text3D(y[1],y[2],y[3],labels="Y",col="darkblue",add=TRUE)
arrows3D(0,0,0,fit$fitted[1],fit$fitted[2],fit$fitted[3],add=TRUE,col="darkblue")
segments3D(y[1],y[2],y[3],fit$fitted[1],fit$fitted[2],fit$fitted[3],add=TRUE,lty=2,col="darkblue")
text3D(fit$fitted[1],fit$fitted[2],fit$fitted[3],labels="fit",col="darkblue",add=TRUE)

  • Note, that it is also clear from the equation in the derivation of the LS that the residual is orthogonal on the column space: \[ -2 \mathbf{X}^T(\mathbf{Y}-\mathbf{X}\boldsymbol{\beta}) = 0 \]

5.4 Variance Estimator?

\[ \begin{array}{ccl} \hat{\boldsymbol{\Sigma}}_{\hat{\mathbf{\beta}}} &=&\text{var}\left[(\mathbf{X}^T\mathbf{X})^{-1}\mathbf{X}^T\mathbf{Y}\right]\\\\ &=&(\mathbf{X}^T\mathbf{X})^{-1}\mathbf{X}^T\text{var}\left[\mathbf{Y}\right]\mathbf{X}(\mathbf{X}^T\mathbf{X})^{-1}\\\\ &=&(\mathbf{X}^T\mathbf{X})^{-1}\mathbf{X}^T(\mathbf{I}\sigma^2)\mathbf{X}(\mathbf{X}^T\mathbf{X})^{-1} \\\\ &=&(\mathbf{X}^T\mathbf{X})^{-1}\mathbf{X}^T\mathbf{I}\quad\mathbf{X}(\mathbf{X}^T\mathbf{X})^{-1}\sigma^2\\\\ %\hat{\boldmath{\Sigma}}_{\hat{\mathbf{\beta}}}&=&(\mathbf{X}^T\mathbf{X})^{-1}\mathbf{X}^T\var\left[\mathbf{Y}\right](\mathbf{X}^T\mathbf{X})^{-1}\mathbf{X}\\ &=&(\mathbf{X}^T\mathbf{X})^{-1}\mathbf{X}^T\mathbf{X}(\mathbf{X}^T\mathbf{X})^{-1}\sigma^2\\\\ &=&(\mathbf{X}^T\mathbf{X})^{-1}\sigma^2 \end{array} \]


5.5 Contrasts

Hypotheses often involve linear combinations of the model parameters!

e.g.

  • \(H_0: \log_2{FC}_{g3n1-g1n1}= \beta_{g3} + \hat\beta_{g3n1}=0\) \(\rightarrow\) “grade3+grade3:node1 = 0”

  • Let \[ \boldsymbol{\beta} = \left[ \begin{array}{c} \beta_{0}\\ \beta_{g3}\\ \beta_{n1}\\ \beta_{g3:n1} \end{array} \right]\]

  • we can write that contrast using a contrast matrix: \[ \mathbf{L}=\left[\begin{array}{c}0\\1\\0\\1\end{array}\right] \rightarrow \mathbf{L}^T\boldsymbol{beta} \]

  • Then the variance becomes: \[ \text{var}_{\mathbf{L}^T\boldsymbol{\hat\beta}}= \mathbf{L}^T \boldsymbol{\Sigma}_{\boldsymbol{\hat\beta}}\mathbf{L} \]


6 Homework: Adopt the gene analysis on log scale in matrix form!

  1. Study the solution of the exercise to understand the analysis in R

  2. Calculate

  • model parameters and contrasts of interest
  • standard errors, standard errors on contrasts
  • t-test statistics on the model parameters and contrasts of interest
  1. Compare your results with the output of the lm(.) function

6.1 Inspiration

Tip: details on the implementation can be found in the book of Faraway (chapter 2). https://people.bath.ac.uk/jjf23/book/

  • Design matrix
X <- model.matrix(~grade*node,data=gene)
  • Transpose of a matrix: use function t(.)
t(X)
##              1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
## (Intercept)  1 1 1 1 1 1 1 1 1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1
## grade3       1 1 0 0 0 1 1 0 1  1  0  1  0  0  0  1  0  1  0  1  0  1  1  0
## node1        1 1 1 1 0 0 1 1 0  0  0  0  0  0  0  1  1  0  1  1  1  0  1  0
## grade3:node1 1 1 0 0 0 0 1 0 0  0  0  0  0  0  0  1  0  0  0  1  0  0  1  0
## attr(,"assign")
## [1] 0 1 2 3
## attr(,"contrasts")
## attr(,"contrasts")$grade
## [1] "contr.treatment"
## 
## attr(,"contrasts")$node
## [1] "contr.treatment"
  • Matrix product %*% operator
t(X)%*%X
##              (Intercept) grade3 node1 grade3:node1
## (Intercept)           24     12    12            6
## grade3                12     12     6            6
## node1                 12      6    12            6
## grade3:node1           6      6     6            6
  • Degrees of freedom of a model?

\[ df = n-p\]

summary(lm1)
## 
## Call:
## lm(formula = gene ~ grade * node, data = gene)
## 
## Residuals:
##      Min       1Q   Median       3Q      Max 
## -201.748  -53.294   -6.308   46.216  277.601 
## 
## Coefficients:
##              Estimate Std. Error t value Pr(>|t|)    
## (Intercept)    180.51      44.37   4.068   0.0006 ***
## grade3         401.33      62.75   6.396 3.07e-06 ***
## node1          103.98      62.75   1.657   0.1131    
## grade3:node1  -145.57      88.74  -1.640   0.1166    
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 108.7 on 20 degrees of freedom
## Multiple R-squared:  0.7437, Adjusted R-squared:  0.7052 
## F-statistic: 19.34 on 3 and 20 DF,  p-value: 3.971e-06
dfRes <- (nrow(X)-ncol(X))
dfRes
## [1] 20
  • Variance estimator: MSE

\[ \hat \sigma^2 = \frac{\sum\limits_{i=1}^n\epsilon_i^2}{n-p} \]

  • Invert matrix: use function solve(.)

  • Diagonal elements of a matrix: use function diag(.)

t(X)%*%X
##              (Intercept) grade3 node1 grade3:node1
## (Intercept)           24     12    12            6
## grade3                12     12     6            6
## node1                 12      6    12            6
## grade3:node1           6      6     6            6
diag(t(X)%*%X)
##  (Intercept)       grade3        node1 grade3:node1 
##           24           12           12            6
LS0tCnRpdGxlOiAiUmVjYXAgZ2VuZXJhbCBsaW5lYXIgbW9kZWwiCmF1dGhvcjogIkxpZXZlbiBDbGVtZW50IgpkYXRlOiAic3RhdE9taWNzLCBHaGVudCBVbml2ZXJzaXR5IChodHRwczovL3N0YXRvbWljcy5naXRodWIuaW8pIgpvdXRwdXQ6CiAgICBodG1sX2RvY3VtZW50OgogICAgICBjb2RlX2Rvd25sb2FkOiB0cnVlCiAgICAgIHRoZW1lOiBjb3NtbwogICAgICB0b2M6IHRydWUKICAgICAgdG9jX2Zsb2F0OiB0cnVlCiAgICAgIGhpZ2hsaWdodDogdGFuZ28KICAgICAgbnVtYmVyX3NlY3Rpb25zOiB0cnVlCi0tLQoKCjxhIHJlbD0ibGljZW5zZSIgaHJlZj0iaHR0cHM6Ly9jcmVhdGl2ZWNvbW1vbnMub3JnL2xpY2Vuc2VzL2J5LW5jLXNhLzQuMCI+PGltZyBhbHQ9IkNyZWF0aXZlIENvbW1vbnMgTGljZW5zZSIgc3R5bGU9ImJvcmRlci13aWR0aDowIiBzcmM9Imh0dHBzOi8vaS5jcmVhdGl2ZWNvbW1vbnMub3JnL2wvYnktbmMtc2EvNC4wLzg4eDMxLnBuZyIgLz48L2E+CgpUaGlzIGlzIHBhcnQgb2YgdGhlIG9ubGluZSBjb3Vyc2UgW1N0YXRpc3RpY2FsIEdlbm9taWNzIDIwMjEgKFNHQTIxKV0oaHR0cHM6Ly9zdGF0b21pY3MuZ2l0aHViLmlvL1NHQTIxLykKCiMgQnJlYXN0IGNhbmNlciBleGFtcGxlCgotIHBhcnQgb2Ygc3R1ZHkgaHR0cHM6Ly9kb2kub3JnLzEwLjEwOTMvam5jaS9kamowNTIpCi0gSGlzdG9sb2dpYyBncmFkZSBpbiBicmVhc3QgY2FuY2VyIGNsaW5pY2FsbHkgcHJvZ25vc3RpYy4KQXNzb2NpYXRpb24gb2YgaGlzdG9sb2dpYyBncmFkZSBvbiBleHByZXNzaW9uIG9mIEtQTkEyIGdlbmUgdGhhdCBpcyBrbm93biB0byBiZSBhc3NvY2lhdGVkIHdpdGggcG9vciBCQyBwcm9nbm9zaXMuCi0gUG9wdWxhdGlvbjogYWxsIGN1cnJlbnQgYW5kIGZ1dHVyZSBicmVhc3QgY2FuY2VyIHBhdGllbnRzCgohW10oaHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL3N0YXRPbWljcy9TR0EyMS9tYXN0ZXIvZmlndXJlcy9zdGF0R2Vub21pY3NHZW50MjAxNzE4LTEuanBlZykKCi0tLQoKIVtdKGh0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9zdGF0T21pY3MvU0dBMjEvbWFzdGVyL2ZpZ3VyZXMvc3RhdEdlbm9taWNzR2VudDIwMTcxOC0yLmpwZWcpCgotLS0KCiFbXShodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vc3RhdE9taWNzL1NHQTIxL21hc3Rlci9maWd1cmVzL3N0YXRHZW5vbWljc0dlbnQyMDE3MTgtMy5qcGVnKQoKLS0tCgoKIVtdKGh0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9zdGF0T21pY3MvU0dBMjEvbWFzdGVyL2ZpZ3VyZXMvc3RhdEdlbm9taWNzR2VudDIwMTcxOC00LmpwZWcpCgotLS0KCgohW10oaHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL3N0YXRPbWljcy9TR0EyMS9tYXN0ZXIvZmlndXJlcy9zdGF0R2Vub21pY3NHZW50MjAxNzE4LTUuanBlZykKCi0tLQoKCiFbXShodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vc3RhdE9taWNzL1NHQTIxL21hc3Rlci9maWd1cmVzL3N0YXRHZW5vbWljc0dlbnQyMDE3MTgtNi5qcGVnKQoKLS0tCgojIERhdGEgRXhwbG9yYXRpb24KCiMjIEltcG9ydAoKYGBge3J9CmxpYnJhcnkodGlkeXZlcnNlKQpnZW5lIDwtIHJlYWQudGFibGUoImh0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9zdGF0T21pY3MvU0dBMjEvbWFzdGVyL2RhdGEva3BuYTIudHh0IixoZWFkZXI9VFJVRSkKaGVhZChnZW5lKQpgYGAKCldlIHdpbGwgdHJhbnNmb3JtIHRoZSB2YXJpYWJsZSBncmFkZSBhbmQgbm9kZSB0byBhIGZhY3RvcgoKYGBge3J9CmdlbmUkZ3JhZGUgPC0gYXMuZmFjdG9yKGdlbmUkZ3JhZGUpCmdlbmUkbm9kZSA8LSBhcy5mYWN0b3IoZ2VuZSRub2RlKQpgYGAKCiMjIFN1bW1hcnkgc3RhdGlzdGljcwoKYGBge3J9CmdlbmVTdW0gPC0gZ2VuZSAlPiUKICBncm91cF9ieShncmFkZSkgJT4lCiAgc3VtbWFyaXplKG1lYW4gPSBtZWFuKGdlbmUpLAogICAgICAgICAgICBzZCA9IHNkKGdlbmUpLAogICAgICAgICAgICBuPWxlbmd0aChnZW5lKQogICAgICAgICAgICApICU+JQogIG11dGF0ZShzZSA9IHNkL3NxcnQobikpCmdlbmVTdW0KYGBgCgojIyBWaXN1YWxpc2F0aW9uCgpgYGB7cn0KZ2VuZSAlPiUKICBnZ3Bsb3QoYWVzKHg9Z3JhZGUseT1nZW5lKSkgKwogIGdlb21fYm94cGxvdChvdXRsaWVyLnNoYXBlPU5BKSArCiAgZ2VvbV9qaXR0ZXIoKQpgYGAKCldlIGNhbiBhbHNvIHNhdmUgdGhlIHBsb3RzIGFzIG9iamVjdHMgZm9yIGxhdGVyIHVzZSEKCmBgYHtyfQpwMSA8LSBnZW5lICU+JQogIGdncGxvdChhZXMoeD1ncmFkZSx5PWdlbmUpKSArCiAgZ2VvbV9ib3hwbG90KG91dGxpZXIuc2hhcGU9TkEpICsKICBnZW9tX2ppdHRlcigpCgpwMiA8LSBnZW5lICU+JQogIGdncGxvdChhZXMoc2FtcGxlPWdlbmUpKSArCiAgZ2VvbV9xcSgpICsKICBnZW9tX3FxX2xpbmUoKSArCiAgZmFjZXRfd3JhcCh+Z3JhZGUpCgpwMQpwMgpgYGAKCgojIyBSZXNlYXJjaCBxdWVzdGlvbnMKClJlc2VhcmNoZXJzIHdhbnQgdG8gYXNzZXNzIHRoZSBhc3NvY2lhdGlvbiBvZiB0aGUgaGlzdG9sb2dpY2FsIGdyYWRlIG9uIEtQTkEyIGdlbmUgZXhwcmVzc2lvbgoKCgoKIVtdKGh0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9zdGF0T21pY3MvU0dBMjEvbWFzdGVyL2ZpZ3VyZXMvc3RhdEdlbm9taWNzR2VudDIwMTcxOC02LmpwZWcpCgotLS0KCiMjIEVzdGltYXRpb24gb2YgZWZmZWN0IHNpemUgYW5kIHN0YW5kYXJkIGVycm9yCgpgYGB7cn0KZWZmZWN0U2l6ZSA8LSB0aWJibGUoCiAgZGVsdGEgPSBnZW5lU3VtJG1lYW5bMl0tIGdlbmVTdW0kbWVhblsxXSwKICBzZURlbHRhID0gZ2VuZVN1bSAlPiUKICAgIHB1bGwoc2UpICU+JQogICAgLl4yICU+JQogICAgc3VtICU+JQogICAgc3FydAogICkKZWZmZWN0U2l6ZQpgYGAKCiMgU3RhdGlzdGljYWwgSW5mZXJlbmNlCgotIFJlc2VhcmNoZXJzIHdhbnQgdG8gYXNzZXNzIHRoZSBhc3NvY2lhdGlvbiBvZiBoaXN0b2xvZ2ljYWwgZ3JhZGUgb24gS1BOQTIgZ2VuZSBleHByZXNzaW9uCi0gSW5mZXJlbmNlPwoKLS0tCgohW10oaHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL3N0YXRPbWljcy9TR0EyMS9tYXN0ZXIvZmlndXJlcy9zdGF0R2Vub21pY3NHZW50MjAxNzE4LTcuanBlZykKCgotLS0KCgotIFJlc2VhcmNoZXJzIHdhbnQgdG8gYXNzZXNzIHRoZSBhc3NvY2lhdGlvbiBvZiBoaXN0b2xvZ2ljYWwgZ3JhZGUgb24gS1BOQTIgZ2VuZSBleHByZXNzaW9uCi0gSW5mZXJlbmNlPwotIHRlc3RpbmcgKyBDSSAkIFxyaWdodGFycm93ICQgQXNzdW1wdGlvbnMKCi0tLQoKLSBJbiBnZW5lcmFsIHdlIHN0YXJ0IGZyb20gKiphbHRlcm5hdGl2ZSBoeXBvdGhlc2UqKiAkSF9BJDogd2Ugd2FudCB0byBzaG93IGFuIGFzc29jaWF0aW9uCi0gR2VuZSBleHByZXNzaW9uIG9mIGdyYWRlIDEgYW5kIGdyYWRlIDMgcGF0aWVudHMgaXMgb24gYXZlcmFnZSBkaWZmZXJlbnQKCi0gQnV0LCB3ZSB3aWxsIGFzc2VzcyBpdCBieSBmYWxzaWZ5aW5nIHRoZSBvcHBvc2l0ZToKCi0gVGhlIGF2ZXJhZ2UgS1BOQTIgZ2VuZSBleHByZXNzaW9uIG9mICBncmFkZSAxIGFuZCBncmFkZSAzIHBhdGllbnRzIGlzIGVxdWFsCgotLS0KCi0gSG93IGxpa2VseSBpcyBpdCB0byBvYnNlcnZlIGFuIGVxdWFsIG9yIG1vcmUgZXh0cmVtZSBhc3NvY2lhdGlvbiB0aGFuIHRoZSBvbmUgb2JzZXJ2ZWQgaW4gdGhlIHNhbXBsZSB3aGVuIHRoZSBudWxsIGh5cG90aGVzaXMgaXMgdHJ1ZT8KCi0gV2hlbiB3ZSBtYWtlIGFzc3VtcHRpb25zIGFib3V0IHRoZSBkaXN0cmlidXRpb24gb2Ygb3VyIHRlc3Qgc3RhdGlzdGljIHdlIGNhbiBxdWFudGlmeSB0aGlzIHByb2JhYmlsaXR5OiAqKnAtdmFsdWUqKi4KLSBJZiB0aGUgcC12YWx1ZSBpcyBiZWxvdyBhIHNpZ25pZmljYW5jZSB0aHJlc2hvbGQgJFxhbHBoYSQgd2UgcmVqZWN0IHRoZSBudWxsIGh5cG90aGVzaXMKCipXZSBjb250cm9sIHRoZSBwcm9iYWJpbGl0eSBvbiBhIGZhbHNlIHBvc2l0aXZlIHJlc3VsdCBhdCB0aGUgJFxhbHBoYSQtbGV2ZWwgKHR5cGUgSSBlcnJvcikqCgotIFRoZSBwLXZhbHVlIHdpbGwgb25seSBiZSBjYWxjdWxhdGVkIGNvcnJlY3RseSBpZiB0aGUgdW5kZXJseWluZyBhc3N1bXB0aW9ucyBob2xkIQoKYGBge3J9CmxpYnJhcnkoZ3JpZEV4dHJhKQpwMQpwMgpgYGAKCmBgYHtyfQp0LnRlc3QoZ2VuZX5ncmFkZSxkYXRhPWdlbmUpCgplZmZlY3RTaXplIDwtIGVmZmVjdFNpemUgJT4lCiAgbXV0YXRlKHQuc3RhdD1kZWx0YS9zZURlbHRhKSAlPiUKICBtdXRhdGUocC52YWx1ZT0gcHQoLWFicyh0LnN0YXQpLDIxLjM1MikqMikKCmVmZmVjdFNpemUKYGBgCgotIEludGVuc2l0aWVzIGFyZSBvZnRlbiBub3Qgbm9ybWFsbHkgZGlzdHJpYnV0ZWQgYW5kIGhhdmUgYSBtZWFuIHZhcmlhbmNlIHJlbGF0aW9uCi0gQ29tbW9ubHkgbG9nMi10cmFuc2Zvcm1lZAotIERpZmZlcmVuY2VzIG9uIGxvZyBzY2FsZToKCiQkClxsb2dfMihCKSAtIFxsb2dfMihBKSA9IFxsb2dfMiBcZnJhY3tCfXtBfSA9IFxsb2dfMiBGQ197XGZyYWN7Qn17QX19CiQkCgoKCiFbXShodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vc3RhdE9taWNzL1NHQTIxL21hc3Rlci9maWd1cmVzL3N0YXRHZW5vbWljc0dlbnQyMDE3MTgtOC5qcGVnKQoKLS0tCgojIyBMb2cgdHJhbnNmb3JtYXRpb24KCmBgYHtyfQpnZW5lIDwtIGdlbmUgJT4lCiAgbXV0YXRlKGxnZW5lID0gbG9nMihnZW5lKSkKCnAxIDwtIGdlbmUgJT4lCiAgZ2dwbG90KGFlcyh4PWdyYWRlLHk9bGdlbmUpKSArCiAgZ2VvbV9ib3hwbG90KG91dGxpZXIuc2hhcGU9TkEpICsKICBnZW9tX2ppdHRlcigpCgpwMiA8LSBnZW5lICU+JQogIGdncGxvdChhZXMoc2FtcGxlPWxnZW5lKSkgKwogIGdlb21fcXEoKSArCiAgZ2VvbV9xcV9saW5lKCkgKwogIGZhY2V0X3dyYXAofmdyYWRlKQoKcDEKcDIKCmxvZ3Rlc3QgPC0gdC50ZXN0KGxnZW5lfmdyYWRlLGRhdGE9Z2VuZSx2YXIuZXF1YWw9VFJVRSkKbG9ndGVzdAoKbG9nMkZDIDwtIGxvZ3Rlc3QkZXN0aW1hdGVbMl0tbG9ndGVzdCRlc3RpbWF0ZVsxXQpsb2cyRkMKbmFtZXMobG9nMkZDKSA8LSAiZzMtZzEiCjJebG9nMkZDCmBgYAoKIyMgQ29uY2x1c2lvbgoKVGhlcmUgaXMgYSBleHRyZW1lbHkgc2lnbmlmaWNhbnQgYXNzb2NpYXRpb24gb2YgdGhlIGhpc3RvbG9naWNhbCBncmFkZSBvbiB0aGUgZ2VuZSBleHByZXNzaW9uIGluIHR1bW9yIHRpc3N1ZS4gIE9uIGF2ZXJhZ2UsIHRoZSBnZW5lIGV4cHJlc3Npb24gZm9yIHRoZSBncmFkZSAzIHBhdGllbnRzIGlzIGByIHJvdW5kKDJebG9nMkZDLDIpYCB0aW1lcyBoaWdoZXIgdGhhbiB0aGUgZ2VuZSBleHByZXNzaW9uIGluIGdyYWRlIDEgcGF0aWVudHMgKDk1XCUgQ0kgIFtgciBwYXN0ZShyb3VuZCgyXi1sb2d0ZXN0JGNvbmYuaW50WzI6MV0sMiksY29sbGFwc2U9IiwgIilgXSwgJHA8PDAuMDAxJCkuCgoKCgohW10oaHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL3N0YXRPbWljcy9TR0EyMS9tYXN0ZXIvZmlndXJlcy9zdGF0R2Vub21pY3NHZW50MjAxNzE4LTEwLmpwZWcpIAoKLS0tCgoKIVtdKGh0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9zdGF0T21pY3MvU0dBMjEvbWFzdGVyL2ZpZ3VyZXMvc3RhdEdlbm9taWNzR2VudDIwMTcxOC0xMS5qcGVnKQoKLS0tCgpUaGUgcGF0aWVudHMgYWxzbyBkaWZmZXIgaW4gdGhlIHRoZWlyIGx5bXBoIG5vZGUgc3RhdHVzLiBIZW5jZSwgd2UgaGF2ZSBhIHR3byBmYWN0b3JpYWwgZGVzaWduOiBncmFkZSB4IGx5bXBoIG5vZGUgc3RhdHVzISEhCgpTb2x1dGlvbj8/CgohW10oaHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL3N0YXRPbWljcy9TR0EyMS9tYXN0ZXIvZmlndXJlcy9zdGF0R2Vub21pY3NHZW50MjAxNzE4LTEyLmpwZWcpCgotLS0KCiMgR2VuZXJhbCBMaW5lYXIgTW9kZWwKCkhvdyBjYW4gd2UgaW50ZWdyYXRlIG11bHRpcGxlIGZhY3RvcnMgYW5kIGNvbnRpbnVvdXMgY292YXJpYXRlcyBpbiBsaW5lYXIgbW9kZWwuCgpcWwp5X2k9IFxiZXRhXzAgKyBcYmV0YV8xIHhfe2ksMX0gKyBcYmV0YV8yIHhfe2ksMn0gKyBcYmV0YV97MTJ9eF97aSwxfXhfe2ksMn0rXGVwc2lsb25faSwKXF0Kd2l0aAoKLSAkeF97aSwxfSQgYSBkdW1teSB2YXJpYWJsZSBmb3IgaGlzdG9sb2dpY2FsIGdyYWRlOiAkeF97aSwxfT1cYmVnaW57Y2FzZXN9CjAmIFx0ZXh0e2dyYWRlIDF9XFwKMSYgXHRleHR7Z3JhZGUgM30KXGVuZHtjYXNlc30kCi0gJHhfe2ksMn0kIGEgZHVtbXkgdmFyaWFibGUgZm9yIDogJHhfe2ksMn09XGJlZ2lue2Nhc2VzfQowJiBcdGV4dHtseW1waCBub2RlcyB3ZXJlIG5vdCByZW1vdmVkfVxcCjEmIFx0ZXh0e2x5bXBoIG5vZGVzIHdlcmUgcmVtb3ZlZH0KXGVuZHtjYXNlc30kCi0gJFxlcHNpbG9uX2kkPwoKLS0tCgojIyBJbXBsZW1lbnRhdGlvbiBpbiBSCgpgYGB7cn0KbG0xIDwtIGxtKGdlbmV+Z3JhZGUqbm9kZSxkYXRhPWdlbmUpCnN1bW1hcnkobG0xKQpgYGAKCi0tLQoKIyMgQXNzdW1wdGlvbnMKCmBgYHtyfQpwbG90KGxtMSkKYGBgCgotLS0KCiMjIEJyZWFzdCBjYW5jZXIgZXhhbXBsZQoKLSAgUGFwZXI6IGh0dHBzOi8vZG9pLm9yZy8xMC4xMDkzL2puY2kvZGpqMDUyCi0gSGlzdG9sb2dpYyBncmFkZSBpbiBicmVhc3QgY2FuY2VyIHByb3ZpZGVzIGNsaW5pY2FsbHkgaW1wb3J0YW50IHByb2dub3N0aWMgaW5mb3JtYXRpb24uIFR3byBmYWN0b3JzIGhhdmUgdG8gYmUgY29uY2lkZXJlZDogSGlzdG9sb2dpYyBncmFkZSAoZ3JhZGUgMSBhbmQgZ3JhZGUgMykgYW5kIGx5bXBoIG5vZGUgc3RhdHVzICgwIHZzIDEpLiBUaGUgcmVzZWFyY2hlcnMgYXNzZXNzZWQgZ2VuZSBleHByZXNzaW9uIG9mIHRoZSBLUE5BMiBnZW5lIGEgcHJvdGVpbi1jb2RpbmcgZ2VuZSBhc3NvY2lhdGVkIHdpdGggYnJlYXN0IGNhbmNlciBhbmQgYXJlIG1haW5seSBpbnRlcmVzdGVkIGluIHRoZSBhc3NvY2lhdGlvbiBvZiBoaXN0b2xvZ2ljYWwgZ3JhZGUuIE5vdGUsIHRoYXQgdGhlIGdlbmUgdmFyaWFibGUgY29uc2lzdHMgb2YgYmFja2dyb3VuZCBjb3JyZWN0ZWQgbm9ybWFsaXplZCBpbnRlbnNpdGllcyBvYnRhaW5lZCB3aXRoIGEgbWljcm9hcnJheSBwbGF0Zm9ybS4gVXBvbiBsb2ctdHJhbnNmb3JtYXRpb24sIHRoZXkgYXJlIGtub3duIHRvIGJlIGEgZ29vZCBwcm94eSBmb3IgdGhlICRcbG9nJCB0cmFuc2Zvcm1lZCBjb25jZW50cmF0aW9uIG9mIGdlbmUgZXhwcmVzc2lvbiBwcm9kdWN0IG9mIHRoZSBLUE5BMiBnZW5lLgotIFJlc2VhcmNoIHF1ZXN0aW9ucyBhbmQgdHJhbnNsYXRlIHRoZW0gdG93YXJkcyBtb2RlbCBwYXJhbWV0ZXJzIChjb250cmFzdHMpPwotIE1ha2UgYW4gUiBtYXJrZG93biBmaWxlIHRvIGFuc3dlciB0aGUgcmVzZWFyY2ggcXVlc3Rpb25zCgoKYGBge3J9CmxpYnJhcnkoRXhwbG9yZU1vZGVsTWF0cml4KQpleHBsTXggPC0gVmlzdWFsaXplRGVzaWduKGdlbmUsZGVzaWduRm9ybXVsYSA9IH5ncmFkZSpub2RlKQpleHBsTXgkcGxvdGxpc3QKYGBgCgpZb3UgY2FuIGFsc28gZXhwbG9yZSB0aGUgbW9kZWwgbWF0cml4IGludGVyYWN0aXZlbHk6CgpgYGB7ciBldmFsPUZBTFNFfQpFeHBsb3JlTW9kZWxNYXRyaXgoZ2VuZSxkZXNpZ25Gb3JtdWxhID0gfmdyYWRlKm5vZGUpCmBgYAotLS0KCiMgTGluZWFyIHJlZ3Jlc3Npb24gaW4gbWF0cml4IGZvcm0KCiMjIFNjYWxhciBmb3JtCgotIENvbnNpZGVyIGEgdmVjdG9yIG9mIHByZWRpY3RvcnMgJFxtYXRoYmZ7eH09KHhfMSxcbGRvdHMseF9wKV5UJCBhbmQKLSBhIHJlYWwtdmFsdWVkIHJlc3BvbnNlICRZJAotIHRoZW4gdGhlIGxpbmVhciByZWdyZXNzaW9uIG1vZGVsIGNhbiBiZSB3cml0dGVuIGFzClxbClk9ZihcbWF0aGJme3h9KSArXGVwc2lsb249XGJldGFfMCtcc3VtXGxpbWl0c197aj0xfV5wIHhfalxiZXRhX2ogKyBcZXBzaWxvbgpcXQp3aXRoIGkuaS5kLiAkXGVwc2lsb25cc2ltIE4oMCxcc2lnbWFeMikkCgojIyBNYXRyaXggZm9ybQoKLSAkbiQgb2JzZXJ2YXRpb25zICQoXG1hdGhiZnt4fV8xLHlfMSkgXGxkb3RzIChcbWF0aGJme3h9X24seV9uKSQKLSBSZWdyZXNzaW9uIGluIG1hdHJpeCBub3RhdGlvbgpcW1xtYXRoYmZ7WX09XG1hdGhiZntYXGJldGF9ICsgXG1hdGhiZntcZXBzaWxvbn1cXQp3aXRoICRcbWF0aGJme1l9PVxsZWZ0W1xiZWdpbnthcnJheX17Y315XzFcXCBcdmRvdHNcXHlfblxlbmR7YXJyYXl9XHJpZ2h0XSQsCiRcbWF0aGJme1h9PVxsZWZ0W1xiZWdpbnthcnJheX17Y2NjY30gMSZ4X3sxMX0mXGxkb3RzJnhfezFwfVxcClx2ZG90cyZcdmRvdHMmJlx2ZG90c1xcCjEmeF97bjF9JlxsZG90cyZ4X3tucH0KXGVuZHthcnJheX1ccmlnaHRdJCwKJFxtYXRoYmZ7XGJldGF9PVxsZWZ0W1xiZWdpbnthcnJheX17Y31cYmV0YV8wXFwgXHZkb3RzXFwgXGJldGFfcFxlbmR7YXJyYXl9XHJpZ2h0XSQgYW5kCiRcbWF0aGJme1xlcHNpbG9ufT1cbGVmdFtcYmVnaW57YXJyYXl9e2N9IFxlcHNpbG9uXzEgXFwgXHZkb3RzIFxcIFxlcHNpbG9uX25cZW5ke2FycmF5fVxyaWdodF0kCgojIyBMZWFzdCBTcXVhcmVzIChMUykKLSBNaW5pbWl6ZSB0aGUgcmVzaWR1YWwgc3VtIG9mIHNxdWFyZXMKXGJlZ2lue2VxbmFycmF5Kn0KUlNTKFxtYXRoYmZ7XGJldGF9KSY9JlxzdW1cbGltaXRzX3tpPTF9Xm4gZV4yX2lcXAomPSZcc3VtXGxpbWl0c197aT0xfV5uIFxsZWZ0KHlfaS1cYmV0YV8wLVxzdW1cbGltaXRzX3tqPTF9XnAgeF97aWp9XGJldGFfalxyaWdodCleMgpcZW5ke2VxbmFycmF5Kn0KLSBvciBpbiBtYXRyaXggbm90YXRpb24KXGJlZ2lue2VxbmFycmF5Kn0KUlNTKFxtYXRoYmZ7XGJldGF9KSY9JihcbWF0aGJme1l9LVxtYXRoYmZ7WFxiZXRhfSleVChcbWF0aGJme1l9LVxtYXRoYmZ7WFxiZXRhfSlcXAomPSZcVmVydCBcbWF0aGJme1l9LVxtYXRoYmZ7WFxiZXRhfVxWZXJ0XjJfMgpcZW5ke2VxbmFycmF5Kn0Kd2l0aCB0aGUgJExfMiQtbm9ybSBvZiBhICRwJC1kaW0uIHZlY3RvciAkdiQgJFxWZXJ0IFxtYXRoYmZ7dn0gXFZlcnQ9XHNxcnR7dl8xXjIrXGxkb3RzK3ZfcF4yfSQKJFxyaWdodGFycm93JCAkXGhhdHtcbWF0aGJme1xiZXRhfX09XHRleHR7YXJnbWlufV9cYmV0YSBcVmVydCBcbWF0aGJme1l9LVxtYXRoYmZ7WFxiZXRhfVxWZXJ0XjJfMiQKCi0tLQoKIyMjIE1pbmltaXplIFJTUwpcWwpcYmVnaW57YXJyYXl9e2NjY30KXGZyYWN7XHBhcnRpYWwgUlNTfXtccGFydGlhbCBcbWF0aGJme1xiZXRhfX0mPSZcbWF0aGJmezB9XFxcXApcZnJhY3soXG1hdGhiZntZfS1cbWF0aGJme1hcYmV0YX0pXlQoXG1hdGhiZntZfS1cbWF0aGJme1hcYmV0YX0pfXtccGFydGlhbCBcbWF0aGJme1xiZXRhfX0mPSZcbWF0aGJmezB9XFxcXAotMlxtYXRoYmZ7WH1eVChcbWF0aGJme1l9LVxtYXRoYmZ7WFxiZXRhfSkmPSZcbWF0aGJmezB9XFxcXApcbWF0aGJme1h9XlRcbWF0aGJme1hcYmV0YX0mPSZcbWF0aGJme1h9XlRcbWF0aGJme1l9XFxcXApcaGF0e1xtYXRoYmZ7XGJldGF9fSY9JihcbWF0aGJme1h9XlRcbWF0aGJme1h9KV57LTF9XG1hdGhiZntYfV5UXG1hdGhiZntZfQpcZW5ke2FycmF5fQpcXQoKLS0tCgojIyMgR2VvbWV0cmljYWwgSW50ZXJwcmV0YXRpb24KClRveSBFeGFtcGxlOiBmaXQgd2l0aG91dCBpbnRlcmNlcHQKCi0gbj0zIGFuZCBwPTIKXFsKXG1hdGhiZntYfT1cbGVmdFtcYmVnaW57YXJyYXl9e2NjfQoyJjBcXAowJjJcXAowJjAKXGVuZHthcnJheX1ccmlnaHRdClxdCgpgYGB7cn0Kc2V0LnNlZWQoNCkKeDEgPC0gYygyLDAsMCkKeDIgPC0gYygwLDIsMCkKeSA8LSB4MSowLjUgKyB4MiowLjUgKyBybm9ybSgzLDIpCmZpdCA8LSBsbSh5fi0xK3gxK3gyKQpgYGAKCiMjIyMgVmlzdWFsaXNlIGZpdAoKYGBge3J9CiMgcHJlZGljdCB2YWx1ZXMgb24gcmVndWxhciB4eSBncmlkCngxcHJlZCA8LSBzZXEoLTEsIDMsIGxlbmd0aC5vdXQgPSAxMCkKeDJwcmVkIDwtIHNlcSgtMSwgMywgbGVuZ3RoLm91dCA9IDEwKQp4eSA8LSBleHBhbmQuZ3JpZCh4MSA9IHgxcHJlZCwKeDIgPSB4MnByZWQpCnlwcmVkIDwtIG1hdHJpeCAobnJvdyA9IDMwLCBuY29sID0gMzAsCmRhdGEgPSBwcmVkaWN0KGZpdCwgbmV3ZGF0YSA9IGRhdGEuZnJhbWUoeHkpLAppbnRlcnZhbCA9ICJwcmVkaWN0aW9uIikpCgpsaWJyYXJ5KHBsb3QzRCkKCgojIGZpdHRlZCBwb2ludHMgZm9yIGRyb3BsaW5lcyB0byBzdXJmYWNlCnRoPTIwCnBoPTUKc2NhdHRlcjNEKHgxLAogIHgyLAogIHksCiAgcGNoID0gMTYsCiAgY29sPSJkYXJrYmx1ZSIsCiAgY2V4ID0gMSwKICB0aGV0YSA9IHRoLAogIHRpY2t0eXBlID0gImRldGFpbGVkIiwKICB4bGFiID0gIngxIiwKICB5bGFiID0gIngyIiwKICB6bGFiID0gInkiLCAgCiAgY29sdmFyPUZBTFNFLAogIGJ0eSA9ICJnIiwKICB4bGltPWMoLTEsMyksCiAgeWxpbT1jKC0xLDMpLAogIHpsaW09YygtMiw0KSkKCmZvciAoaSBpbiAxOjMpCiAgbGluZXMzRCgKICAgIHggPSByZXAoeDFbaV0sMiksCiAgICB5ID0gcmVwKHgyW2ldLDIpLAogICAgeiA9IGMoeVtpXSxmaXQkZml0dGVkW2ldKSwKICAgIGNvbD0iZGFya2JsdWUiLAogICAgYWRkPVRSVUUsCiAgICBsdHk9MikKCnoucHJlZDNEIDwtIG91dGVyKAogIHgxcHJlZCwKICB4MnByZWQsCiAgZnVuY3Rpb24oeDEseDIpCiAgewogICAgZml0JGNvZWZbMV0qeDErZml0JGNvZWZbMl0qeDIKICB9KQoKeC5wcmVkM0QgPC0gb3V0ZXIoCiAgeDFwcmVkLAogIHgycHJlZCwKICBmdW5jdGlvbih4LHkpIHgpCgp5LnByZWQzRCA8LSBvdXRlcigKICB4MXByZWQsCiAgeDJwcmVkLAogIGZ1bmN0aW9uKHgseSkgeSkKCnN1cmYzRCgKICB4LnByZWQzRCwKICB5LnByZWQzRCwKICB6LnByZWQzRCwKICBjb2w9ImJsdWUiLAogIGZhY2V0cz1OQSwKICBhZGQ9VFJVRSkKYGBgCgojIyMgUHJvamVjdGlvbgoKLSBXZSBjYW4gYWxzbyBpbnRlcnByZXQgdGhlIGZpdCBhcyB0aGUgcHJvamVjdGlvbiBvZiB0aGUgJG5cdGltZXMgMSQgdmVjdG9yICRcbWF0aGJme1l9JCBvbiB0aGUgY29sdW1uIHNwYWNlIG9mIHRoZSBtYXRyaXggJFxtYXRoYmZ7WH0kLgoKLSBTbyBlYWNoIGNvbHVtbiBpbiAkXG1hdGhiZntYfSQgaXMgYWxzbyBhbiAkblx0aW1lcyAxJCB2ZWN0b3IuCgotIEZvciB0aGUgdG95IGV4YW1wbGUgbj0zIGFuZCBwPTIuClNvIHRoZSBjb2x1bW4gc3BhY2Ugb2YgWCBpcyBhIHBsYW5lIGluIHRoZSB0aHJlZSBkaW1lbnNpb25hbCBzcGFjZS4KClxbClxoYXR7XG1hdGhiZntZfX0gPSBcbWF0aGJme1h9IChcbWF0aGJme1h9XlRcbWF0aGJme1h9KV57LTF9IFxtYXRoYmZ7WH1eVCBcbWF0aGJme1l9ClxdCgoxLiBQbGFuZSBzcGFubmVkIGJ5IGNvbHVtbiBzcGFjZToKYGBge3J9CmFycm93czNEKDAsMCwwLHgxWzFdLHgxWzJdLHgxWzNdLHhsaW09YygwLDUpLHlsaW09YygwLDUpLHpsaW09YygwLDUpLGJ0eSA9ICJnIix0aGV0YT10aCxjb2w9Mix4bGFiPSJyb3cgMSIseWxhYj0icm93IDIiLHpsYWI9InJvdyAzIikKdGV4dDNEKHgxWzFdLHgxWzJdLHgxWzNdLGxhYmVscz0iWDEiLGNvbD0yLGFkZD1UUlVFKQphcnJvd3MzRCgwLDAsMCx4MlsxXSx4MlsyXSx4MlszXSxhZGQ9VFJVRSxjb2w9MikKdGV4dDNEKHgyWzFdLHgyWzJdLHgyWzNdLGxhYmVscz0iWDIiLGNvbD0yLGFkZD1UUlVFKQpgYGAKCjIuIFZlY3RvciBvZiBZOgpgYGB7cn0KYXJyb3dzM0QoMCwwLDAseDFbMV0seDFbMl0seDFbM10seGxpbT1jKDAsNSkseWxpbT1jKDAsNSksemxpbT1jKDAsNSksYnR5ID0gImciLHRoZXRhPXRoLGNvbD0yLHhsYWI9InJvdyAxIix5bGFiPSJyb3cgMiIsemxhYj0icm93IDMiKQp0ZXh0M0QoeDFbMV0seDFbMl0seDFbM10sbGFiZWxzPSJYMSIsY29sPTIsYWRkPVRSVUUpCmFycm93czNEKDAsMCwwLHgyWzFdLHgyWzJdLHgyWzNdLGFkZD1UUlVFLGNvbD0yKQp0ZXh0M0QoeDJbMV0seDJbMl0seDJbM10sbGFiZWxzPSJYMiIsY29sPTIsYWRkPVRSVUUpCmFycm93czNEKDAsMCwwLHlbMV0seVsyXSx5WzNdLGFkZD1UUlVFLGNvbD0iZGFya2JsdWUiKQp0ZXh0M0QoeVsxXSx5WzJdLHlbM10sbGFiZWxzPSJZIixjb2w9ImRhcmtibHVlIixhZGQ9VFJVRSkKYGBgCgozLiBQcm9qZWN0aW9uIG9mIFkgb250byBjb2x1bW4gc3BhY2UKYGBge3J9CmFycm93czNEKDAsMCwwLHgxWzFdLHgxWzJdLHgxWzNdLHhsaW09YygwLDUpLHlsaW09YygwLDUpLHpsaW09YygwLDUpLGJ0eSA9ICJnIix0aGV0YT10aCxjb2w9Mix4bGFiPSJyb3cgMSIseWxhYj0icm93IDIiLHpsYWI9InJvdyAzIikKdGV4dDNEKHgxWzFdLHgxWzJdLHgxWzNdLGxhYmVscz0iWDEiLGNvbD0yLGFkZD1UUlVFKQphcnJvd3MzRCgwLDAsMCx4MlsxXSx4MlsyXSx4MlszXSxhZGQ9VFJVRSxjb2w9MikKdGV4dDNEKHgyWzFdLHgyWzJdLHgyWzNdLGxhYmVscz0iWDIiLGNvbD0yLGFkZD1UUlVFKQphcnJvd3MzRCgwLDAsMCx5WzFdLHlbMl0seVszXSxhZGQ9VFJVRSxjb2w9ImRhcmtibHVlIikKdGV4dDNEKHlbMV0seVsyXSx5WzNdLGxhYmVscz0iWSIsY29sPSJkYXJrYmx1ZSIsYWRkPVRSVUUpCmFycm93czNEKDAsMCwwLGZpdCRmaXR0ZWRbMV0sZml0JGZpdHRlZFsyXSxmaXQkZml0dGVkWzNdLGFkZD1UUlVFLGNvbD0iZGFya2JsdWUiKQpzZWdtZW50czNEKHlbMV0seVsyXSx5WzNdLGZpdCRmaXR0ZWRbMV0sZml0JGZpdHRlZFsyXSxmaXQkZml0dGVkWzNdLGFkZD1UUlVFLGx0eT0yLGNvbD0iZGFya2JsdWUiKQp0ZXh0M0QoZml0JGZpdHRlZFsxXSxmaXQkZml0dGVkWzJdLGZpdCRmaXR0ZWRbM10sbGFiZWxzPSJmaXQiLGNvbD0iZGFya2JsdWUiLGFkZD1UUlVFKQoKYGBgCgotIE5vdGUsIHRoYXQgaXQgaXMgYWxzbyBjbGVhciBmcm9tIHRoZSBlcXVhdGlvbiBpbiB0aGUgZGVyaXZhdGlvbiBvZiB0aGUgTFMgdGhhdCB0aGUgcmVzaWR1YWwgaXMgb3J0aG9nb25hbCBvbiB0aGUgY29sdW1uIHNwYWNlOgpcWwogLTIgXG1hdGhiZntYfV5UKFxtYXRoYmZ7WX0tXG1hdGhiZntYfVxib2xkc3ltYm9se1xiZXRhfSkgPSAwClxdCgoKLS0tCgojIyBWYXJpYW5jZSBFc3RpbWF0b3I/ClxbClxiZWdpbnthcnJheX17Y2NsfQpcaGF0e1xib2xkc3ltYm9se1xTaWdtYX19X3tcaGF0e1xtYXRoYmZ7XGJldGF9fX0KJj0mXHRleHR7dmFyfVxsZWZ0WyhcbWF0aGJme1h9XlRcbWF0aGJme1h9KV57LTF9XG1hdGhiZntYfV5UXG1hdGhiZntZfVxyaWdodF1cXFxcCiY9JihcbWF0aGJme1h9XlRcbWF0aGJme1h9KV57LTF9XG1hdGhiZntYfV5UXHRleHR7dmFyfVxsZWZ0W1xtYXRoYmZ7WX1ccmlnaHRdXG1hdGhiZntYfShcbWF0aGJme1h9XlRcbWF0aGJme1h9KV57LTF9XFxcXAomPSYoXG1hdGhiZntYfV5UXG1hdGhiZntYfSleey0xfVxtYXRoYmZ7WH1eVChcbWF0aGJme0l9XHNpZ21hXjIpXG1hdGhiZntYfShcbWF0aGJme1h9XlRcbWF0aGJme1h9KV57LTF9ClxcXFwKJj0mKFxtYXRoYmZ7WH1eVFxtYXRoYmZ7WH0pXnstMX1cbWF0aGJme1h9XlRcbWF0aGJme0l9XHF1YWRcbWF0aGJme1h9KFxtYXRoYmZ7WH1eVFxtYXRoYmZ7WH0pXnstMX1cc2lnbWFeMlxcXFwKJVxoYXR7XGJvbGRtYXRoe1xTaWdtYX19X3tcaGF0e1xtYXRoYmZ7XGJldGF9fX0mPSYoXG1hdGhiZntYfV5UXG1hdGhiZntYfSleey0xfVxtYXRoYmZ7WH1eVFx2YXJcbGVmdFtcbWF0aGJme1l9XHJpZ2h0XShcbWF0aGJme1h9XlRcbWF0aGJme1h9KV57LTF9XG1hdGhiZntYfVxcCiY9JihcbWF0aGJme1h9XlRcbWF0aGJme1h9KV57LTF9XG1hdGhiZntYfV5UXG1hdGhiZntYfShcbWF0aGJme1h9XlRcbWF0aGJme1h9KV57LTF9XHNpZ21hXjJcXFxcCiY9JihcbWF0aGJme1h9XlRcbWF0aGJme1h9KV57LTF9XHNpZ21hXjIKXGVuZHthcnJheX0KXF0KCi0tLQoKIyMgQ29udHJhc3RzCgpIeXBvdGhlc2VzIG9mdGVuIGludm9sdmUgbGluZWFyIGNvbWJpbmF0aW9ucyBvZiB0aGUgbW9kZWwgcGFyYW1ldGVycyEKCmUuZy4KCi0gJEhfMDogXGxvZ18ye0ZDfV97ZzNuMS1nMW4xfT0gXGJldGFfe2czfSArIFxoYXRcYmV0YV97ZzNuMX09MCQgJFxyaWdodGFycm93JCAiZ3JhZGUzK2dyYWRlMzpub2RlMSA9IDAiCgotIExldCBcWwpcYm9sZHN5bWJvbHtcYmV0YX0gPSBcbGVmdFsKXGJlZ2lue2FycmF5fXtjfQpcYmV0YV97MH1cXApcYmV0YV97ZzN9XFwKXGJldGFfe24xfVxcClxiZXRhX3tnMzpuMX0KXGVuZHthcnJheX0KXHJpZ2h0XVxdCi0gd2UgY2FuIHdyaXRlIHRoYXQgY29udHJhc3QgdXNpbmcgYSBjb250cmFzdCBtYXRyaXg6ClxbClxtYXRoYmZ7TH09XGxlZnRbXGJlZ2lue2FycmF5fXtjfTBcXDFcXDBcXDFcZW5ke2FycmF5fVxyaWdodF0gXHJpZ2h0YXJyb3cgXG1hdGhiZntMfV5UXGJvbGRzeW1ib2x7YmV0YX0gXF0KCi0gVGhlbiB0aGUgdmFyaWFuY2UgYmVjb21lczoKXFsKXHRleHR7dmFyfV97XG1hdGhiZntMfV5UXGJvbGRzeW1ib2x7XGhhdFxiZXRhfX09IFxtYXRoYmZ7TH1eVCBcYm9sZHN5bWJvbHtcU2lnbWF9X3tcYm9sZHN5bWJvbHtcaGF0XGJldGF9fVxtYXRoYmZ7TH0KXF0KCgotLS0KCiMgSG9tZXdvcms6IEFkb3B0IHRoZSBnZW5lIGFuYWx5c2lzIG9uIGxvZyBzY2FsZSBpbiBtYXRyaXggZm9ybSEKCjEuIFN0dWR5IHRoZSBzb2x1dGlvbiBvZiB0aGUgZXhlcmNpc2UgdG8gdW5kZXJzdGFuZCB0aGUgYW5hbHlzaXMgaW4gUgoKCjIuIENhbGN1bGF0ZQotIG1vZGVsIHBhcmFtZXRlcnMgYW5kIGNvbnRyYXN0cyBvZiBpbnRlcmVzdAotIHN0YW5kYXJkIGVycm9ycywgc3RhbmRhcmQgZXJyb3JzIG9uIGNvbnRyYXN0cwotIHQtdGVzdCBzdGF0aXN0aWNzIG9uIHRoZSBtb2RlbCBwYXJhbWV0ZXJzIGFuZCBjb250cmFzdHMgb2YgaW50ZXJlc3QKCjMuIENvbXBhcmUgeW91ciByZXN1bHRzIHdpdGggdGhlIG91dHB1dCBvZiB0aGUgbG0oLikgZnVuY3Rpb24KCgotLS0KCiMjIEluc3BpcmF0aW9uCgpUaXA6IGRldGFpbHMgb24gdGhlIGltcGxlbWVudGF0aW9uIGNhbiBiZSBmb3VuZCBpbiB0aGUgYm9vayBvZiBGYXJhd2F5IChjaGFwdGVyIDIpLiBodHRwczovL3Blb3BsZS5iYXRoLmFjLnVrL2pqZjIzL2Jvb2svCgotIERlc2lnbiBtYXRyaXgKCmBgYHtyfQpYIDwtIG1vZGVsLm1hdHJpeCh+Z3JhZGUqbm9kZSxkYXRhPWdlbmUpCmBgYAoKLSBUcmFuc3Bvc2Ugb2YgYSBtYXRyaXg6IHVzZSBmdW5jdGlvbiB0KC4pCgpgYGB7cn0KdChYKQpgYGAKCi0gTWF0cml4IHByb2R1Y3QgJVwqJSBvcGVyYXRvcgoKYGBge3J9CnQoWCklKiVYCmBgYAoKLSBEZWdyZWVzIG9mIGZyZWVkb20gb2YgYSBtb2RlbD8KCiQkIGRmID0gIG4tcCQkCgpgYGB7cn0Kc3VtbWFyeShsbTEpCmRmUmVzIDwtIChucm93KFgpLW5jb2woWCkpCmRmUmVzCmBgYAoKLSBWYXJpYW5jZSBlc3RpbWF0b3I6IE1TRQoKJCQKXGhhdCBcc2lnbWFeMiA9IFxmcmFje1xzdW1cbGltaXRzX3tpPTF9Xm5cZXBzaWxvbl9pXjJ9e24tcH0KJCQKCgotIEludmVydCBtYXRyaXg6IHVzZSBmdW5jdGlvbiBzb2x2ZSguKQoKLSBEaWFnb25hbCBlbGVtZW50cyBvZiBhIG1hdHJpeDogdXNlIGZ1bmN0aW9uIGRpYWcoLikKCmBgYHtyfQp0KFgpJSolWApkaWFnKHQoWCklKiVYKQpgYGAK