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)
gene <- read.table("https://raw.githubusercontent.com/statOmics/SGA2020/data/gse2990BreastcancerOneGene.txt",header=TRUE)
head(gene)
##    sample_name grade node size age     gene
## 28   OXFT_2221     3    1  5.5  76 367.8179
## 29    OXFT_209     3    1  2.5  66 590.3576
## 30   OXFT_1769     1    1  3.5  86 346.6583
## 31    OXFT_928     1    0  1.1  47 118.6996
## 32   OXFT_2093     1    1  2.2  74 519.4489
## 33   OXFT_1770     1    1  1.7  69 258.4455

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
## # A tibble: 2 x 5
##   grade  mean    sd     n    se
##   <fct> <dbl> <dbl> <int> <dbl>
## 1 1      264.  117.    19  26.7
## 2 3      606.  267.    17  64.9

2.3 Visualisation

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

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_point()

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

p3 <- gene %>%
  filter(grade==1) %>%
  ggplot(aes(sample=gene)) +
  geom_qq() +
  geom_qq_line()

p1

p2

p3

2.4 Research questions

Researchers want to assess the association of the histolical 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
## # A tibble: 1 x 2
##   delta seDelta
##   <dbl>   <dbl>
## 1  342.    70.2

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)
p1

grid.arrange(p2,p3,ncol=2)

t.test(gene~grade,data=gene)
## 
##  Welch Two Sample t-test
## 
## data:  gene by grade
## t = -4.8806, df = 21.352, p-value = 7.61e-05
## alternative hypothesis: true difference in means is not equal to 0
## 95 percent confidence interval:
##  -488.1734 -196.6587
## sample estimates:
## mean in group 1 mean in group 3 
##        263.5516        605.9677
effectSize <- effectSize %>%
  mutate(t.stat=delta/seDelta) %>%
  mutate(p.value= pt(-abs(t.stat),21.352)*2)

effectSize
## # A tibble: 1 x 4
##   delta seDelta t.stat   p.value
##   <dbl>   <dbl>  <dbl>     <dbl>
## 1  342.    70.2   4.88 0.0000761
  • 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_point()

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

p3 <- gene %>%
  filter(grade==1) %>%
  ggplot(aes(sample=lgene)) +
  geom_qq() +
  geom_qq_line()

p1

grid.arrange(p2,p3,ncol=2)

logtest <- t.test(lgene~grade,data=gene)
logtest
## 
##  Welch Two Sample t-test
## 
## data:  lgene by grade
## t = -6.0508, df = 33.962, p-value = 7.432e-07
## alternative hypothesis: true difference in means is not equal to 0
## 95 percent confidence interval:
##  -1.6236927 -0.8072052
## sample estimates:
## mean in group 1 mean in group 3 
##        7.912963        9.128412
log2FC <- logtest$estimate[2]-logtest$estimate[1]
log2FC
## mean in group 3 
##        1.215449
names(log2FC) <- "g3-g1"
2^log2FC
##   g3-g1 
## 2.32213

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.3221304 times higher than the gene expression in grade 1 patients (95% CI [1.75, 3.08], \(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 
## -356.85  -91.98  -31.47   53.00  612.73 
## 
## Coefficients:
##              Estimate Std. Error t value Pr(>|t|)    
## (Intercept)    207.60      60.00   3.460  0.00155 ** 
## grade3         434.21      84.85   5.117 1.41e-05 ***
## node1          132.88      92.46   1.437  0.16040    
## grade3:node1  -234.43     136.92  -1.712  0.09655 .  
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 199 on 32 degrees of freedom
## Multiple R-squared:  0.4809, Adjusted R-squared:  0.4322 
## F-statistic: 9.881 on 3 and 32 DF,  p-value: 9.181e-05

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)


# 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

Hypothesis 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 https://gtpb.github.io/PSLS20/pages/08-multipleRegression/08-multipleRegression_KPNA2.html

  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
## (Intercept)  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 0 0 1 0  1  0  1  0  1  1  0  1  0  0  0  0  1  0
## node1        1 1 1 0 1 1 0 0 0  1  1  0  0  0  0  0  0  0  0  0  0  1  1
## grade3:node1 1 1 0 0 0 0 0 0 0  1  0  0  0  0  0  0  0  0  0  0  0  1  0
##              24 25 26 27 28 29 30 31 32 33 34 35 36
## (Intercept)   1  1  1  1  1  1  1  1  1  1  1  1  1
## grade3        1  1  0  1  1  1  1  0  1  0  0  1  0
## node1         0  0  1  0  1  0  0  1  0  0  1  1  0
## grade3:node1  0  0  0  0  1  0  0  0  0  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)           36     17    14            6
## grade3                17     17     6            6
## node1                 14      6    14            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 
## -356.85  -91.98  -31.47   53.00  612.73 
## 
## Coefficients:
##              Estimate Std. Error t value Pr(>|t|)    
## (Intercept)    207.60      60.00   3.460  0.00155 ** 
## grade3         434.21      84.85   5.117 1.41e-05 ***
## node1          132.88      92.46   1.437  0.16040    
## grade3:node1  -234.43     136.92  -1.712  0.09655 .  
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 199 on 32 degrees of freedom
## Multiple R-squared:  0.4809, Adjusted R-squared:  0.4322 
## F-statistic: 9.881 on 3 and 32 DF,  p-value: 9.181e-05
dfRes <- (nrow(X)-ncol(X))
dfRes
## [1] 32
  • 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)           36     17    14            6
## grade3                17     17     6            6
## node1                 14      6    14            6
## grade3:node1           6      6     6            6
diag(t(X)%*%X)
##  (Intercept)       grade3        node1 grade3:node1 
##           36           17           14            6
LS0tCnRpdGxlOiAiUmVjYXAgZ2VuZXJhbCBsaW5lYXIgbW9kZWwiCmF1dGhvcjogIkxpZXZlbiBDbGVtZW50IgpkYXRlOiAic3RhdE9taWNzLCBHaGVudCBVbml2ZXJzaXR5IChodHRwczovL3N0YXRvbWljcy5naXRodWIuaW8pIgpvdXRwdXQ6CiAgICBodG1sX2RvY3VtZW50OgogICAgICBjb2RlX2Rvd25sb2FkOiB0cnVlCiAgICAgIHRoZW1lOiBjb3NtbwogICAgICB0b2M6IHRydWUKICAgICAgdG9jX2Zsb2F0OiB0cnVlCiAgICAgIGhpZ2hsaWdodDogdGFuZ28KICAgICAgbnVtYmVyX3NlY3Rpb25zOiB0cnVlCi0tLQoKCiMgQnJlYXN0IGNhbmNlciBleGFtcGxlCgotIHBhcnQgb2Ygc3R1ZHkgaHR0cHM6Ly9kb2kub3JnLzEwLjEwOTMvam5jaS9kamowNTIpCi0gSGlzdG9sb2dpYyBncmFkZSBpbiBicmVhc3QgY2FuY2VyIGNsaW5pY2FsbHkgcHJvZ25vc3RpYy4KQXNzb2NpYXRpb24gb2YgaGlzdG9sb2dpYyBncmFkZSBvbiBleHByZXNzaW9uIG9mIEtQTkEyIGdlbmUgdGhhdCBpcyBrbm93biB0byBiZSBhc3NvY2lhdGVkIHdpdGggcG9vciBCQyBwcm9nbm9zaXMuCi0gUG9wdWxhdGlvbjogYWxsIGN1cnJlbnQgYW5kIGZ1dHVyZSBicmVhc3QgY2FuY2VyIHBhdGllbnRzCgohW10oaHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL3N0YXRPbWljcy9TR0EyMDIwL2RhdGEvZmlncy9zdGF0R2Vub21pY3NHZW50MjAxNzE4LTEuanBlZykKCi0tLQoKIVtdKGh0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9zdGF0T21pY3MvU0dBMjAyMC9kYXRhL2ZpZ3Mvc3RhdEdlbm9taWNzR2VudDIwMTcxOC0yLmpwZWcpCgotLS0KCiFbXShodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vc3RhdE9taWNzL1NHQTIwMjAvZGF0YS9maWdzL3N0YXRHZW5vbWljc0dlbnQyMDE3MTgtMy5qcGVnKQoKLS0tCgoKIVtdKGh0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9zdGF0T21pY3MvU0dBMjAyMC9kYXRhL2ZpZ3Mvc3RhdEdlbm9taWNzR2VudDIwMTcxOC00LmpwZWcpCgotLS0KCgohW10oaHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL3N0YXRPbWljcy9TR0EyMDIwL2RhdGEvZmlncy9zdGF0R2Vub21pY3NHZW50MjAxNzE4LTUuanBlZykKCi0tLQoKCiFbXShodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vc3RhdE9taWNzL1NHQTIwMjAvZGF0YS9maWdzL3N0YXRHZW5vbWljc0dlbnQyMDE3MTgtNi5qcGVnKQoKLS0tCgojIERhdGEgRXhwbG9yYXRpb24KCiMjIEltcG9ydAoKYGBge3J9CmxpYnJhcnkodGlkeXZlcnNlKQpnZW5lIDwtIHJlYWQudGFibGUoImh0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9zdGF0T21pY3MvU0dBMjAyMC9kYXRhL2dzZTI5OTBCcmVhc3RjYW5jZXJPbmVHZW5lLnR4dCIsaGVhZGVyPVRSVUUpCmhlYWQoZ2VuZSkKYGBgCgpXZSB3aWxsIHRyYW5zZm9ybSB0aGUgdmFyaWFibGUgZ3JhZGUgYW5kIG5vZGUgdG8gYSBmYWN0b3IKCmBgYHtyfQpnZW5lJGdyYWRlIDwtIGFzLmZhY3RvcihnZW5lJGdyYWRlKQpnZW5lJG5vZGUgPC0gYXMuZmFjdG9yKGdlbmUkbm9kZSkKYGBgCgojIyBTdW1tYXJ5IHN0YXRpc3RpY3MKCmBgYHtyfQpnZW5lU3VtIDwtIGdlbmUgJT4lCiAgZ3JvdXBfYnkoZ3JhZGUpICU+JQogIHN1bW1hcml6ZShtZWFuID0gbWVhbihnZW5lKSwKICAgICAgICAgICAgc2QgPSBzZChnZW5lKSwKICAgICAgICAgICAgbj1sZW5ndGgoZ2VuZSkKICAgICAgICAgICAgKSAlPiUKICBtdXRhdGUoc2UgPSBzZC9zcXJ0KG4pKQpnZW5lU3VtCmBgYAoKIyMgVmlzdWFsaXNhdGlvbgoKYGBge3J9CmdlbmUgJT4lCiAgZ2dwbG90KGFlcyh4PWdyYWRlLHk9Z2VuZSkpICsKICBnZW9tX2JveHBsb3Qob3V0bGllci5zaGFwZT1OQSkgKwogIGdlb21fcG9pbnQoKQpgYGAKCldlIGNhbiBhbHNvIHNhdmUgdGhlIHBsb3RzIGFzIG9iamVjdHMgZm9yIGxhdGVyIHVzZSEKCmBgYHtyfQpwMSA8LSBnZW5lICU+JQogIGdncGxvdChhZXMoeD1ncmFkZSx5PWdlbmUpKSArCiAgZ2VvbV9ib3hwbG90KG91dGxpZXIuc2hhcGU9TkEpICsKICBnZW9tX3BvaW50KCkKCnAyIDwtIGdlbmUgJT4lCiAgZmlsdGVyKGdyYWRlPT0xKSAlPiUKICBnZ3Bsb3QoYWVzKHNhbXBsZT1nZW5lKSkgKwogIGdlb21fcXEoKSArCiAgZ2VvbV9xcV9saW5lKCkKCnAzIDwtIGdlbmUgJT4lCiAgZmlsdGVyKGdyYWRlPT0xKSAlPiUKICBnZ3Bsb3QoYWVzKHNhbXBsZT1nZW5lKSkgKwogIGdlb21fcXEoKSArCiAgZ2VvbV9xcV9saW5lKCkKCnAxCnAyCnAzCmBgYAoKCiMjIFJlc2VhcmNoIHF1ZXN0aW9ucwoKUmVzZWFyY2hlcnMgd2FudCB0byBhc3Nlc3MgdGhlIGFzc29jaWF0aW9uIG9mIHRoZSBoaXN0b2xpY2FsIGdyYWRlIG9uIEtQTkEyIGdlbmUgZXhwcmVzc2lvbgoKCgoKIVtdKGh0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9zdGF0T21pY3MvU0dBMjAyMC9kYXRhL2ZpZ3Mvc3RhdEdlbm9taWNzR2VudDIwMTcxOC02LmpwZWcpCgotLS0KCiMjIEVzdGltYXRpb24gb2YgZWZmZWN0IHNpemUgYW5kIHN0YW5kYXJkIGVycm9yCgpgYGB7cn0KZWZmZWN0U2l6ZSA8LSB0aWJibGUoCiAgZGVsdGEgPSBnZW5lU3VtJG1lYW5bMl0tIGdlbmVTdW0kbWVhblsxXSwKICBzZURlbHRhID0gZ2VuZVN1bSAlPiUKICAgIHB1bGwoc2UpICU+JQogICAgLl4yICU+JQogICAgc3VtICU+JQogICAgc3FydAogICkKZWZmZWN0U2l6ZQpgYGAKCiMgU3RhdGlzdGljYWwgSW5mZXJlbmNlCgotIFJlc2VhcmNoZXJzIHdhbnQgdG8gYXNzZXNzIHRoZSBhc3NvY2lhdGlvbiBvZiBoaXN0b2xvZ2ljYWwgZ3JhZGUgb24gS1BOQTIgZ2VuZSBleHByZXNzaW9uCi0gSW5mZXJlbmNlPwoKLS0tCgohW10oaHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL3N0YXRPbWljcy9TR0EyMDIwL2RhdGEvZmlncy9zdGF0R2Vub21pY3NHZW50MjAxNzE4LTcuanBlZykKCi0tLQoKCi0gUmVzZWFyY2hlcnMgd2FudCB0byBhc3Nlc3MgdGhlIGFzc29jaWF0aW9uIG9mIGhpc3RvbG9naWNhbCBncmFkZSBvbiBLUE5BMiBnZW5lIGV4cHJlc3Npb24KLSBJbmZlcmVuY2U/Ci0gdGVzdGluZyArIENJICQgXHJpZ2h0YXJyb3cgJCBBc3N1bXB0aW9ucwoKLS0tCgotIEluIGdlbmVyYWwgd2Ugc3RhcnQgZnJvbSAqKmFsdGVybmF0aXZlIGh5cG90aGVzZSoqICRIX0EkOiB3ZSB3YW50IHRvIHNob3cgYW4gYXNzb2NpYXRpb24KLSBHZW5lIGV4cHJlc3Npb24gb2YgZ3JhZGUgMSBhbmQgZ3JhZGUgMyBwYXRpZW50cyBpcyBvbiBhdmVyYWdlIGRpZmZlcmVudAoKLSBCdXQsIHdlIHdpbGwgYXNzZXNzIGl0IGJ5IGZhbHNpZnlpbmcgdGhlIG9wcG9zaXRlOgoKLSBUaGUgYXZlcmFnZSBLUE5BMiBnZW5lIGV4cHJlc3Npb24gb2YgIGdyYWRlIDEgYW5kIGdyYWRlIDMgcGF0aWVudHMgaXMgZXF1YWwKCi0tLQoKLSBIb3cgbGlrZWx5IGlzIGl0IHRvIG9ic2VydmUgYW4gZXF1YWwgb3IgbW9yZSBleHRyZW1lIGFzc29jaWF0aW9uIHRoYW4gdGhlIG9uZSBvYnNlcnZlZCBpbiB0aGUgc2FtcGxlIHdoZW4gdGhlIG51bGwgaHlwb3RoZXNpcyBpcyB0cnVlPwoKLSBXaGVuIHdlIG1ha2UgYXNzdW1wdGlvbnMgYWJvdXQgdGhlIGRpc3RyaWJ1dGlvbiBvZiBvdXIgdGVzdCBzdGF0aXN0aWMgd2UgY2FuIHF1YW50aWZ5IHRoaXMgcHJvYmFiaWxpdHk6ICoqcC12YWx1ZSoqLgotIElmIHRoZSBwLXZhbHVlIGlzIGJlbG93IGEgc2lnbmlmaWNhbmNlIHRocmVzaG9sZCAkXGFscGhhJCB3ZSByZWplY3QgdGhlIG51bGwgaHlwb3RoZXNpcwoKKldlIGNvbnRyb2wgdGhlIHByb2JhYmlsaXR5IG9uIGEgZmFsc2UgcG9zaXRpdmUgcmVzdWx0IGF0IHRoZSAkXGFscGhhJC1sZXZlbCAodHlwZSBJIGVycm9yKSoKCi0gVGhlIHAtdmFsdWUgd2lsbCBvbmx5IGJlIGNhbGN1bGF0ZWQgY29ycmVjdGx5IGlmIHRoZSB1bmRlcmx5aW5nIGFzc3VtcHRpb25zIGhvbGQhCgpgYGB7cn0KbGlicmFyeShncmlkRXh0cmEpCnAxCmdyaWQuYXJyYW5nZShwMixwMyxuY29sPTIpCmBgYAoKYGBge3J9CnQudGVzdChnZW5lfmdyYWRlLGRhdGE9Z2VuZSkKCmVmZmVjdFNpemUgPC0gZWZmZWN0U2l6ZSAlPiUKICBtdXRhdGUodC5zdGF0PWRlbHRhL3NlRGVsdGEpICU+JQogIG11dGF0ZShwLnZhbHVlPSBwdCgtYWJzKHQuc3RhdCksMjEuMzUyKSoyKQoKZWZmZWN0U2l6ZQpgYGAKCi0gSW50ZW5zaXRpZXMgYXJlIG9mdGVuIG5vdCBub3JtYWxseSBkaXN0cmlidXRlZCBhbmQgaGF2ZSBhIG1lYW4gdmFyaWFuY2UgcmVsYXRpb24KLSBDb21tb25seSBsb2cyLXRyYW5zZm9ybWVkCi0gRGlmZmVyZW5jZXMgb24gbG9nIHNjYWxlOgoKJCQKXGxvZ18yKEIpIC0gXGxvZ18yKEEpID0gXGxvZ18yIFxmcmFje0J9e0F9ID0gXGxvZ18yIEZDX3tcZnJhY3tCfXtBfX0KJCQKCgoKIVtdKGh0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9zdGF0T21pY3MvU0dBMjAyMC9kYXRhL2ZpZ3Mvc3RhdEdlbm9taWNzR2VudDIwMTcxOC04LmpwZWcpCgotLS0KCiMjIExvZyB0cmFuc2Zvcm1hdGlvbgoKYGBge3J9CmdlbmUgPC0gZ2VuZSAlPiUKICBtdXRhdGUobGdlbmUgPSBsb2cyKGdlbmUpKQoKcDEgPC0gZ2VuZSAlPiUKICBnZ3Bsb3QoYWVzKHg9Z3JhZGUseT1sZ2VuZSkpICsKICBnZW9tX2JveHBsb3Qob3V0bGllci5zaGFwZT1OQSkgKwogIGdlb21fcG9pbnQoKQoKcDIgPC0gZ2VuZSAlPiUKICBmaWx0ZXIoZ3JhZGU9PTEpICU+JQogIGdncGxvdChhZXMoc2FtcGxlPWxnZW5lKSkgKwogIGdlb21fcXEoKSArCiAgZ2VvbV9xcV9saW5lKCkKCnAzIDwtIGdlbmUgJT4lCiAgZmlsdGVyKGdyYWRlPT0xKSAlPiUKICBnZ3Bsb3QoYWVzKHNhbXBsZT1sZ2VuZSkpICsKICBnZW9tX3FxKCkgKwogIGdlb21fcXFfbGluZSgpCgpwMQpncmlkLmFycmFuZ2UocDIscDMsbmNvbD0yKQoKbG9ndGVzdCA8LSB0LnRlc3QobGdlbmV+Z3JhZGUsZGF0YT1nZW5lKQpsb2d0ZXN0Cgpsb2cyRkMgPC0gbG9ndGVzdCRlc3RpbWF0ZVsyXS1sb2d0ZXN0JGVzdGltYXRlWzFdCmxvZzJGQwpuYW1lcyhsb2cyRkMpIDwtICJnMy1nMSIKMl5sb2cyRkMKYGBgCgojIyBDb25jbHVzaW9uCgpUaGVyZSBpcyBhIGV4dHJlbWVseSBzaWduaWZpY2FudCBhc3NvY2lhdGlvbiBvZiB0aGUgaGlzdG9sb2dpY2FsIGdyYWRlIG9uIHRoZSBnZW5lIGV4cHJlc3Npb24gaW4gdHVtb3IgdGlzc3VlLiAgT24gYXZlcmFnZSwgdGhlIGdlbmUgZXhwcmVzc2lvbiBmb3IgdGhlIGdyYWRlIDMgcGF0aWVudHMgaXMgYHIgMl5sb2cyRkNgIHRpbWVzIGhpZ2hlciB0aGFuIHRoZSBnZW5lIGV4cHJlc3Npb24gaW4gZ3JhZGUgMSBwYXRpZW50cyAoOTVcJSBDSSAgW2ByIHBhc3RlKHJvdW5kKDJeLWxvZ3Rlc3QkY29uZi5pbnRbMjoxXSwyKSxjb2xsYXBzZT0iLCAiKWBdLCAkcDw8MC4wMDEkKS4KCgoKCiFbXShodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vc3RhdE9taWNzL1NHQTIwMjAvZGF0YS9maWdzL3N0YXRHZW5vbWljc0dlbnQyMDE3MTgtMTAuanBlZykKCi0tLQoKCiFbXShodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vc3RhdE9taWNzL1NHQTIwMjAvZGF0YS9maWdzL3N0YXRHZW5vbWljc0dlbnQyMDE3MTgtMTEuanBlZykKCi0tLQoKVGhlIHBhdGllbnRzIGFsc28gZGlmZmVyIGluIHRoZSB0aGVpciBseW1waCBub2RlIHN0YXR1cy4gSGVuY2UsIHdlIGhhdmUgYSB0d28gZmFjdG9yaWFsIGRlc2lnbjogZ3JhZGUgeCBseW1waCBub2RlIHN0YXR1cyEhIQoKU29sdXRpb24/PwoKIVtdKGh0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9zdGF0T21pY3MvU0dBMjAyMC9kYXRhL2ZpZ3Mvc3RhdEdlbm9taWNzR2VudDIwMTcxOC0xMi5qcGVnKQoKLS0tCgojIEdlbmVyYWwgTGluZWFyIE1vZGVsCgpIb3cgY2FuIHdlIGludGVncmF0ZSBtdWx0aXBsZSBmYWN0b3JzIGFuZCBjb250aW51b3VzIGNvdmFyaWF0ZXMgaW4gbGluZWFyIG1vZGVsLgoKXFsKeV9pPSBcYmV0YV8wICsgXGJldGFfMSB4X3tpLDF9ICsgXGJldGFfMiB4X3tpLDJ9ICsgXGJldGFfezEyfXhfe2ksMX14X3tpLDJ9K1xlcHNpbG9uX2ksClxdCndpdGgKCi0gJHhfe2ksMX0kIGEgZHVtbXkgdmFyaWFibGUgZm9yIGhpc3RvbG9naWNhbCBncmFkZTogJHhfe2ksMX09XGJlZ2lue2Nhc2VzfQowJiBcdGV4dHtncmFkZSAxfVxcCjEmIFx0ZXh0e2dyYWRlIDN9ClxlbmR7Y2FzZXN9JAotICR4X3tpLDJ9JCBhIGR1bW15IHZhcmlhYmxlIGZvciA6ICR4X3tpLDJ9PVxiZWdpbntjYXNlc30KMCYgXHRleHR7bHltcGggbm9kZXMgd2VyZSBub3QgcmVtb3ZlZH1cXAoxJiBcdGV4dHtseW1waCBub2RlcyB3ZXJlIHJlbW92ZWR9ClxlbmR7Y2FzZXN9JAotICRcZXBzaWxvbl9pJD8KCi0tLQoKIyMgSW1wbGVtZW50YXRpb24gaW4gUgoKYGBge3J9CmxtMSA8LSBsbShnZW5lfmdyYWRlKm5vZGUsZGF0YT1nZW5lKQpzdW1tYXJ5KGxtMSkKYGBgCgotLS0KCiMjIEFzc3VtcHRpb25zCgpgYGB7cn0KcGxvdChsbTEpCmBgYAoKLS0tCgojIyBCcmVhc3QgY2FuY2VyIGV4YW1wbGUKCi0gIFBhcGVyOiBodHRwczovL2RvaS5vcmcvMTAuMTA5My9qbmNpL2RqajA1MgotIEhpc3RvbG9naWMgZ3JhZGUgaW4gYnJlYXN0IGNhbmNlciBwcm92aWRlcyBjbGluaWNhbGx5IGltcG9ydGFudCBwcm9nbm9zdGljIGluZm9ybWF0aW9uLiBUd28gZmFjdG9ycyBoYXZlIHRvIGJlIGNvbmNpZGVyZWQ6IEhpc3RvbG9naWMgZ3JhZGUgKGdyYWRlIDEgYW5kIGdyYWRlIDMpIGFuZCBseW1waCBub2RlIHN0YXR1cyAoMCB2cyAxKS4gVGhlIHJlc2VhcmNoZXJzIGFzc2Vzc2VkIGdlbmUgZXhwcmVzc2lvbiBvZiB0aGUgS1BOQTIgZ2VuZSBhIHByb3RlaW4tY29kaW5nIGdlbmUgYXNzb2NpYXRlZCB3aXRoIGJyZWFzdCBjYW5jZXIgYW5kIGFyZSBtYWlubHkgaW50ZXJlc3RlZCBpbiB0aGUgYXNzb2NpYXRpb24gb2YgaGlzdG9sb2dpY2FsIGdyYWRlLiBOb3RlLCB0aGF0IHRoZSBnZW5lIHZhcmlhYmxlIGNvbnNpc3RzIG9mIGJhY2tncm91bmQgY29ycmVjdGVkIG5vcm1hbGl6ZWQgaW50ZW5zaXRpZXMgb2J0YWluZWQgd2l0aCBhIG1pY3JvYXJyYXkgcGxhdGZvcm0uIFVwb24gbG9nLXRyYW5zZm9ybWF0aW9uLCB0aGV5IGFyZSBrbm93biB0byBiZSBhIGdvb2QgcHJveHkgZm9yIHRoZSAkXGxvZyQgdHJhbnNmb3JtZWQgY29uY2VudHJhdGlvbiBvZiBnZW5lIGV4cHJlc3Npb24gcHJvZHVjdCBvZiB0aGUgS1BOQTIgZ2VuZS4KLSBSZXNlYXJjaCBxdWVzdGlvbnMgYW5kIHRyYW5zbGF0ZSB0aGVtIHRvd2FyZHMgbW9kZWwgcGFyYW1ldGVycyAoY29udHJhc3RzKT8KLSBNYWtlIGFuIFIgbWFya2Rvd24gZmlsZSB0byBhbnN3ZXIgdGhlIHJlc2VhcmNoIHF1ZXN0aW9ucwoKCmBgYHtyfQpsaWJyYXJ5KEV4cGxvcmVNb2RlbE1hdHJpeCkKZXhwbE14IDwtIFZpc3VhbGl6ZURlc2lnbihnZW5lLGRlc2lnbkZvcm11bGEgPSB+Z3JhZGUqbm9kZSkKZXhwbE14JHBsb3RsaXN0CmBgYAoKWW91IGNhbiBhbHNvIGV4cGxvcmUgdGhlIG1vZGVsIG1hdHJpeCBpbnRlcmFjdGl2ZWx5OgoKYGBge3IgZXZhbD1GQUxTRX0KRXhwbG9yZU1vZGVsTWF0cml4KGdlbmUsZGVzaWduRm9ybXVsYSA9IH5ncmFkZSpub2RlKQpgYGAKLS0tCgojIExpbmVhciByZWdyZXNzaW9uIGluIG1hdHJpeCBmb3JtCgojIyBTY2FsYXIgZm9ybQoKLSBDb25zaWRlciBhIHZlY3RvciBvZiBwcmVkaWN0b3JzICRcbWF0aGJme3h9PSh4XzEsXGxkb3RzLHhfcCleVCQgYW5kCi0gYSByZWFsLXZhbHVlZCByZXNwb25zZSAkWSQKLSB0aGVuIHRoZSBsaW5lYXIgcmVncmVzc2lvbiBtb2RlbCBjYW4gYmUgd3JpdHRlbiBhcwpcWwpZPWYoXG1hdGhiZnt4fSkgK1xlcHNpbG9uPVxiZXRhXzArXHN1bVxsaW1pdHNfe2o9MX1ecCB4X2pcYmV0YV9qICsgXGVwc2lsb24KXF0Kd2l0aCBpLmkuZC4gJFxlcHNpbG9uXHNpbSBOKDAsXHNpZ21hXjIpJAoKIyMgTWF0cml4IGZvcm0KCi0gJG4kIG9ic2VydmF0aW9ucyAkKFxtYXRoYmZ7eH1fMSx5XzEpIFxsZG90cyAoXG1hdGhiZnt4fV9uLHlfbikkCi0gUmVncmVzc2lvbiBpbiBtYXRyaXggbm90YXRpb24KXFtcbWF0aGJme1l9PVxtYXRoYmZ7WFxiZXRhfSArIFxtYXRoYmZ7XGVwc2lsb259XF0Kd2l0aCAkXG1hdGhiZntZfT1cbGVmdFtcYmVnaW57YXJyYXl9e2N9eV8xXFwgXHZkb3RzXFx5X25cZW5ke2FycmF5fVxyaWdodF0kLAokXG1hdGhiZntYfT1cbGVmdFtcYmVnaW57YXJyYXl9e2NjY2N9IDEmeF97MTF9JlxsZG90cyZ4X3sxcH1cXApcdmRvdHMmXHZkb3RzJiZcdmRvdHNcXAoxJnhfe24xfSZcbGRvdHMmeF97bnB9ClxlbmR7YXJyYXl9XHJpZ2h0XSQsCiRcbWF0aGJme1xiZXRhfT1cbGVmdFtcYmVnaW57YXJyYXl9e2N9XGJldGFfMFxcIFx2ZG90c1xcIFxiZXRhX3BcZW5ke2FycmF5fVxyaWdodF0kIGFuZAokXG1hdGhiZntcZXBzaWxvbn09XGxlZnRbXGJlZ2lue2FycmF5fXtjfSBcZXBzaWxvbl8xIFxcIFx2ZG90cyBcXCBcZXBzaWxvbl9uXGVuZHthcnJheX1ccmlnaHRdJAoKIyMgTGVhc3QgU3F1YXJlcyAoTFMpCi0gTWluaW1pemUgdGhlIHJlc2lkdWFsIHN1bSBvZiBzcXVhcmVzClxiZWdpbntlcW5hcnJheSp9ClJTUyhcbWF0aGJme1xiZXRhfSkmPSZcc3VtXGxpbWl0c197aT0xfV5uIGVeMl9pXFwKJj0mXHN1bVxsaW1pdHNfe2k9MX1ebiBcbGVmdCh5X2ktXGJldGFfMC1cc3VtXGxpbWl0c197aj0xfV5wIHhfe2lqfVxiZXRhX2pccmlnaHQpXjIKXGVuZHtlcW5hcnJheSp9Ci0gb3IgaW4gbWF0cml4IG5vdGF0aW9uClxiZWdpbntlcW5hcnJheSp9ClJTUyhcbWF0aGJme1xiZXRhfSkmPSYoXG1hdGhiZntZfS1cbWF0aGJme1hcYmV0YX0pXlQoXG1hdGhiZntZfS1cbWF0aGJme1hcYmV0YX0pXFwKJj0mXFZlcnQgXG1hdGhiZntZfS1cbWF0aGJme1hcYmV0YX1cVmVydF4yXzIKXGVuZHtlcW5hcnJheSp9CndpdGggdGhlICRMXzIkLW5vcm0gb2YgYSAkcCQtZGltLiB2ZWN0b3IgJHYkICRcVmVydCBcbWF0aGJme3Z9IFxWZXJ0PVxzcXJ0e3ZfMV4yK1xsZG90cyt2X3BeMn0kCiRccmlnaHRhcnJvdyQgJFxoYXR7XG1hdGhiZntcYmV0YX19PVx0ZXh0e2FyZ21pbn1fXGJldGEgXFZlcnQgXG1hdGhiZntZfS1cbWF0aGJme1hcYmV0YX1cVmVydF4yXzIkCgotLS0KCiMjIyBNaW5pbWl6ZSBSU1MKXFsKXGJlZ2lue2FycmF5fXtjY2N9ClxmcmFje1xwYXJ0aWFsIFJTU317XHBhcnRpYWwgXG1hdGhiZntcYmV0YX19Jj0mXG1hdGhiZnswfVxcXFwKXGZyYWN7KFxtYXRoYmZ7WX0tXG1hdGhiZntYXGJldGF9KV5UKFxtYXRoYmZ7WX0tXG1hdGhiZntYXGJldGF9KX17XHBhcnRpYWwgXG1hdGhiZntcYmV0YX19Jj0mXG1hdGhiZnswfVxcXFwKLTJcbWF0aGJme1h9XlQoXG1hdGhiZntZfS1cbWF0aGJme1hcYmV0YX0pJj0mXG1hdGhiZnswfVxcXFwKXG1hdGhiZntYfV5UXG1hdGhiZntYXGJldGF9Jj0mXG1hdGhiZntYfV5UXG1hdGhiZntZfVxcXFwKXGhhdHtcbWF0aGJme1xiZXRhfX0mPSYoXG1hdGhiZntYfV5UXG1hdGhiZntYfSleey0xfVxtYXRoYmZ7WH1eVFxtYXRoYmZ7WX0KXGVuZHthcnJheX0KXF0KCi0tLQoKIyMjIEdlb21ldHJpY2FsIEludGVycHJldGF0aW9uCgpUb3kgRXhhbXBsZTogZml0IHdpdGhvdXQgaW50ZXJjZXB0CgotIG49MyBhbmQgcD0yClxbClxtYXRoYmZ7WH09XGxlZnRbXGJlZ2lue2FycmF5fXtjY30KMiYwXFwKMCYyXFwKMCYwClxlbmR7YXJyYXl9XHJpZ2h0XQpcXQoKYGBge3J9CnNldC5zZWVkKDQpCngxIDwtIGMoMiwwLDApCngyIDwtIGMoMCwyLDApCnkgPC0geDEqMC41ICsgeDIqMC41ICsgcm5vcm0oMywyKQpmaXQgPC0gbG0oeX4tMSt4MSt4MikKYGBgCgojIyMjIFZpc3VhbGlzZSBmaXQKCmBgYHtyfQojIHByZWRpY3QgdmFsdWVzIG9uIHJlZ3VsYXIgeHkgZ3JpZAp4MXByZWQgPC0gc2VxKC0xLCAzLCBsZW5ndGgub3V0ID0gMTApCngycHJlZCA8LSBzZXEoLTEsIDMsIGxlbmd0aC5vdXQgPSAxMCkKeHkgPC0gZXhwYW5kLmdyaWQoeDEgPSB4MXByZWQsCngyID0geDJwcmVkKQp5cHJlZCA8LSBtYXRyaXggKG5yb3cgPSAzMCwgbmNvbCA9IDMwLApkYXRhID0gcHJlZGljdChmaXQsIG5ld2RhdGEgPSBkYXRhLmZyYW1lKHh5KSwKaW50ZXJ2YWwgPSAicHJlZGljdGlvbiIpKQoKbGlicmFyeShwbG90M0QpCgoKIyBmaXR0ZWQgcG9pbnRzIGZvciBkcm9wbGluZXMgdG8gc3VyZmFjZQp0aD0yMApwaD01CnNjYXR0ZXIzRCh4MSwKICB4MiwKICB5LAogIHBjaCA9IDE2LAogIGNvbD0iZGFya2JsdWUiLAogIGNleCA9IDEsCiAgdGhldGEgPSB0aCwKICB0aWNrdHlwZSA9ICJkZXRhaWxlZCIsCiAgeGxhYiA9ICJ4MSIsCiAgeWxhYiA9ICJ4MiIsCiAgemxhYiA9ICJ5IiwgIAogIGNvbHZhcj1GQUxTRSwKICBidHkgPSAiZyIsCiAgeGxpbT1jKC0xLDMpLAogIHlsaW09YygtMSwzKSwKICB6bGltPWMoLTIsNCkpCgpmb3IgKGkgaW4gMTozKQogIGxpbmVzM0QoCiAgICB4ID0gcmVwKHgxW2ldLDIpLAogICAgeSA9IHJlcCh4MltpXSwyKSwKICAgIHogPSBjKHlbaV0sZml0JGZpdHRlZFtpXSksCiAgICBjb2w9ImRhcmtibHVlIiwKICAgIGFkZD1UUlVFLAogICAgbHR5PTIpCgp6LnByZWQzRCA8LSBvdXRlcigKICB4MXByZWQsCiAgeDJwcmVkLAogIGZ1bmN0aW9uKHgxLHgyKQogIHsKICAgIGZpdCRjb2VmWzFdKngxK2ZpdCRjb2VmWzJdKngyCiAgfSkKCngucHJlZDNEIDwtIG91dGVyKAogIHgxcHJlZCwKICB4MnByZWQsCiAgZnVuY3Rpb24oeCx5KSB4KQoKeS5wcmVkM0QgPC0gb3V0ZXIoCiAgeDFwcmVkLAogIHgycHJlZCwKICBmdW5jdGlvbih4LHkpIHkpCgpzdXJmM0QoCiAgeC5wcmVkM0QsCiAgeS5wcmVkM0QsCiAgei5wcmVkM0QsCiAgY29sPSJibHVlIiwKICBmYWNldHM9TkEsCiAgYWRkPVRSVUUpCmBgYAoKIyMjIFByb2plY3Rpb24KCi0gV2UgY2FuIGFsc28gaW50ZXJwcmV0IHRoZSBmaXQgYXMgdGhlIHByb2plY3Rpb24gb2YgdGhlICRuXHRpbWVzIDEkIHZlY3RvciAkXG1hdGhiZntZfSQgb24gdGhlIGNvbHVtbiBzcGFjZSBvZiB0aGUgbWF0cml4ICRcbWF0aGJme1h9JC4KCi0gU28gZWFjaCBjb2x1bW4gaW4gJFxtYXRoYmZ7WH0kIGlzIGFsc28gYW4gJG5cdGltZXMgMSQgdmVjdG9yLgoKLSBGb3IgdGhlIHRveSBleGFtcGxlIG49MyBhbmQgcD0yLgpTbyB0aGUgY29sdW1uIHNwYWNlIG9mIFggaXMgYSBwbGFuZSBpbiB0aGUgdGhyZWUgZGltZW5zaW9uYWwgc3BhY2UuCgpcWwpcaGF0e1xtYXRoYmZ7WX19ID0gXG1hdGhiZntYfSAoXG1hdGhiZntYfV5UXG1hdGhiZntYfSleey0xfSBcbWF0aGJme1h9XlQgXG1hdGhiZntZfQpcXQoKMS4gUGxhbmUgc3Bhbm5lZCBieSBjb2x1bW4gc3BhY2U6CmBgYHtyfQphcnJvd3MzRCgwLDAsMCx4MVsxXSx4MVsyXSx4MVszXSx4bGltPWMoMCw1KSx5bGltPWMoMCw1KSx6bGltPWMoMCw1KSxidHkgPSAiZyIsdGhldGE9dGgsY29sPTIseGxhYj0icm93IDEiLHlsYWI9InJvdyAyIix6bGFiPSJyb3cgMyIpCnRleHQzRCh4MVsxXSx4MVsyXSx4MVszXSxsYWJlbHM9IlgxIixjb2w9MixhZGQ9VFJVRSkKYXJyb3dzM0QoMCwwLDAseDJbMV0seDJbMl0seDJbM10sYWRkPVRSVUUsY29sPTIpCnRleHQzRCh4MlsxXSx4MlsyXSx4MlszXSxsYWJlbHM9IlgyIixjb2w9MixhZGQ9VFJVRSkKYGBgCgoyLiBWZWN0b3Igb2YgWToKYGBge3J9CmFycm93czNEKDAsMCwwLHgxWzFdLHgxWzJdLHgxWzNdLHhsaW09YygwLDUpLHlsaW09YygwLDUpLHpsaW09YygwLDUpLGJ0eSA9ICJnIix0aGV0YT10aCxjb2w9Mix4bGFiPSJyb3cgMSIseWxhYj0icm93IDIiLHpsYWI9InJvdyAzIikKdGV4dDNEKHgxWzFdLHgxWzJdLHgxWzNdLGxhYmVscz0iWDEiLGNvbD0yLGFkZD1UUlVFKQphcnJvd3MzRCgwLDAsMCx4MlsxXSx4MlsyXSx4MlszXSxhZGQ9VFJVRSxjb2w9MikKdGV4dDNEKHgyWzFdLHgyWzJdLHgyWzNdLGxhYmVscz0iWDIiLGNvbD0yLGFkZD1UUlVFKQphcnJvd3MzRCgwLDAsMCx5WzFdLHlbMl0seVszXSxhZGQ9VFJVRSxjb2w9ImRhcmtibHVlIikKdGV4dDNEKHlbMV0seVsyXSx5WzNdLGxhYmVscz0iWSIsY29sPSJkYXJrYmx1ZSIsYWRkPVRSVUUpCmBgYAoKMy4gUHJvamVjdGlvbiBvZiBZIG9udG8gY29sdW1uIHNwYWNlCmBgYHtyfQphcnJvd3MzRCgwLDAsMCx4MVsxXSx4MVsyXSx4MVszXSx4bGltPWMoMCw1KSx5bGltPWMoMCw1KSx6bGltPWMoMCw1KSxidHkgPSAiZyIsdGhldGE9dGgsY29sPTIseGxhYj0icm93IDEiLHlsYWI9InJvdyAyIix6bGFiPSJyb3cgMyIpCnRleHQzRCh4MVsxXSx4MVsyXSx4MVszXSxsYWJlbHM9IlgxIixjb2w9MixhZGQ9VFJVRSkKYXJyb3dzM0QoMCwwLDAseDJbMV0seDJbMl0seDJbM10sYWRkPVRSVUUsY29sPTIpCnRleHQzRCh4MlsxXSx4MlsyXSx4MlszXSxsYWJlbHM9IlgyIixjb2w9MixhZGQ9VFJVRSkKYXJyb3dzM0QoMCwwLDAseVsxXSx5WzJdLHlbM10sYWRkPVRSVUUsY29sPSJkYXJrYmx1ZSIpCnRleHQzRCh5WzFdLHlbMl0seVszXSxsYWJlbHM9IlkiLGNvbD0iZGFya2JsdWUiLGFkZD1UUlVFKQphcnJvd3MzRCgwLDAsMCxmaXQkZml0dGVkWzFdLGZpdCRmaXR0ZWRbMl0sZml0JGZpdHRlZFszXSxhZGQ9VFJVRSxjb2w9ImRhcmtibHVlIikKc2VnbWVudHMzRCh5WzFdLHlbMl0seVszXSxmaXQkZml0dGVkWzFdLGZpdCRmaXR0ZWRbMl0sZml0JGZpdHRlZFszXSxhZGQ9VFJVRSxsdHk9Mixjb2w9ImRhcmtibHVlIikKdGV4dDNEKGZpdCRmaXR0ZWRbMV0sZml0JGZpdHRlZFsyXSxmaXQkZml0dGVkWzNdLGxhYmVscz0iZml0Iixjb2w9ImRhcmtibHVlIixhZGQ9VFJVRSkKCmBgYAoKLSBOb3RlLCB0aGF0IGl0IGlzIGFsc28gY2xlYXIgZnJvbSB0aGUgZXF1YXRpb24gaW4gdGhlIGRlcml2YXRpb24gb2YgdGhlIExTIHRoYXQgdGhlIHJlc2lkdWFsIGlzIG9ydGhvZ29uYWwgb24gdGhlIGNvbHVtbiBzcGFjZToKXFsKIC0yIFxtYXRoYmZ7WH1eVChcbWF0aGJme1l9LVxtYXRoYmZ7WH1cYm9sZHN5bWJvbHtcYmV0YX0pID0gMApcXQoKCi0tLQoKIyMgVmFyaWFuY2UgRXN0aW1hdG9yPwpcWwpcYmVnaW57YXJyYXl9e2NjbH0KXGhhdHtcYm9sZHN5bWJvbHtcU2lnbWF9fV97XGhhdHtcbWF0aGJme1xiZXRhfX19CiY9Jlx0ZXh0e3Zhcn1cbGVmdFsoXG1hdGhiZntYfV5UXG1hdGhiZntYfSleey0xfVxtYXRoYmZ7WH1eVFxtYXRoYmZ7WX1ccmlnaHRdXFxcXAomPSYoXG1hdGhiZntYfV5UXG1hdGhiZntYfSleey0xfVxtYXRoYmZ7WH1eVFx0ZXh0e3Zhcn1cbGVmdFtcbWF0aGJme1l9XHJpZ2h0XVxtYXRoYmZ7WH0oXG1hdGhiZntYfV5UXG1hdGhiZntYfSleey0xfVxcXFwKJj0mKFxtYXRoYmZ7WH1eVFxtYXRoYmZ7WH0pXnstMX1cbWF0aGJme1h9XlQoXG1hdGhiZntJfVxzaWdtYV4yKVxtYXRoYmZ7WH0oXG1hdGhiZntYfV5UXG1hdGhiZntYfSleey0xfQpcXFxcCiY9JihcbWF0aGJme1h9XlRcbWF0aGJme1h9KV57LTF9XG1hdGhiZntYfV5UXG1hdGhiZntJfVxxdWFkXG1hdGhiZntYfShcbWF0aGJme1h9XlRcbWF0aGJme1h9KV57LTF9XHNpZ21hXjJcXFxcCiVcaGF0e1xib2xkbWF0aHtcU2lnbWF9fV97XGhhdHtcbWF0aGJme1xiZXRhfX19Jj0mKFxtYXRoYmZ7WH1eVFxtYXRoYmZ7WH0pXnstMX1cbWF0aGJme1h9XlRcdmFyXGxlZnRbXG1hdGhiZntZfVxyaWdodF0oXG1hdGhiZntYfV5UXG1hdGhiZntYfSleey0xfVxtYXRoYmZ7WH1cXAomPSYoXG1hdGhiZntYfV5UXG1hdGhiZntYfSleey0xfVxtYXRoYmZ7WH1eVFxtYXRoYmZ7WH0oXG1hdGhiZntYfV5UXG1hdGhiZntYfSleey0xfVxzaWdtYV4yXFxcXAomPSYoXG1hdGhiZntYfV5UXG1hdGhiZntYfSleey0xfVxzaWdtYV4yClxlbmR7YXJyYXl9ClxdCgotLS0KCiMjIENvbnRyYXN0cwoKSHlwb3RoZXNpcyBvZnRlbiBpbnZvbHZlIGxpbmVhciBjb21iaW5hdGlvbnMgb2YgdGhlIG1vZGVsIHBhcmFtZXRlcnMhCgplLmcuCgotICRIXzA6IFxsb2dfMntGQ31fe2czbjEtZzFuMX09IFxiZXRhX3tnM30gKyBcaGF0XGJldGFfe2czbjF9PTAkICRccmlnaHRhcnJvdyQgImdyYWRlMytncmFkZTM6bm9kZTEgPSAwIgoKLSBMZXQgXFsKXGJvbGRzeW1ib2x7XGJldGF9ID0gXGxlZnRbClxiZWdpbnthcnJheX17Y30KXGJldGFfezB9XFwKXGJldGFfe2czfVxcClxiZXRhX3tuMX1cXApcYmV0YV97ZzM6bjF9ClxlbmR7YXJyYXl9ClxyaWdodF1cXQotIHdlIGNhbiB3cml0ZSB0aGF0IGNvbnRyYXN0IHVzaW5nIGEgY29udHJhc3QgbWF0cml4OgpcWwpcbWF0aGJme0x9PVxsZWZ0W1xiZWdpbnthcnJheX17Y30wXFwxXFwwXFwxXGVuZHthcnJheX1ccmlnaHRdIFxyaWdodGFycm93IFxtYXRoYmZ7TH1eVFxib2xkc3ltYm9se2JldGF9IFxdCgotIFRoZW4gdGhlIHZhcmlhbmNlIGJlY29tZXM6ClxbClx0ZXh0e3Zhcn1fe1xtYXRoYmZ7TH1eVFxib2xkc3ltYm9se1xoYXRcYmV0YX19PSBcbWF0aGJme0x9XlQgXGJvbGRzeW1ib2x7XFNpZ21hfV97XGJvbGRzeW1ib2x7XGhhdFxiZXRhfX1cbWF0aGJme0x9ClxdCgoKLS0tCgojIEhvbWV3b3JrOiBBZG9wdCB0aGUgZ2VuZSBhbmFseXNpcyBvbiBsb2cgc2NhbGUgaW4gbWF0cml4IGZvcm0hCgoxLiBTdHVkeSB0aGUgc29sdXRpb24gb2YgdGhlIGV4ZXJjaXNlIHRvIHVuZGVyc3RhbmQgdGhlIGFuYWx5c2lzIGluIFIKaHR0cHM6Ly9ndHBiLmdpdGh1Yi5pby9QU0xTMjAvcGFnZXMvMDgtbXVsdGlwbGVSZWdyZXNzaW9uLzA4LW11bHRpcGxlUmVncmVzc2lvbl9LUE5BMi5odG1sCgoyLiBDYWxjdWxhdGUKLSBtb2RlbCBwYXJhbWV0ZXJzIGFuZCBjb250cmFzdHMgb2YgaW50ZXJlc3QKLSBzdGFuZGFyZCBlcnJvcnMsIHN0YW5kYXJkIGVycm9ycyBvbiBjb250cmFzdHMKLSB0LXRlc3Qgc3RhdGlzdGljcyBvbiB0aGUgbW9kZWwgcGFyYW1ldGVycyBhbmQgY29udHJhc3RzIG9mIGludGVyZXN0CgozLiBDb21wYXJlIHlvdXIgcmVzdWx0cyB3aXRoIHRoZSBvdXRwdXQgb2YgdGhlIGxtKC4pIGZ1bmN0aW9uCgoKLS0tCgojIyBJbnNwaXJhdGlvbgoKVGlwOiBkZXRhaWxzIG9uIHRoZSBpbXBsZW1lbnRhdGlvbiBjYW4gYmUgZm91bmQgaW4gdGhlIGJvb2sgb2YgRmFyYXdheSAoY2hhcHRlciAyKS4gaHR0cHM6Ly9wZW9wbGUuYmF0aC5hYy51ay9qamYyMy9ib29rLwoKLSBEZXNpZ24gbWF0cml4CgpgYGB7cn0KWCA8LSBtb2RlbC5tYXRyaXgofmdyYWRlKm5vZGUsZGF0YT1nZW5lKQpgYGAKCi0gVHJhbnNwb3NlIG9mIGEgbWF0cml4OiB1c2UgZnVuY3Rpb24gdCguKQoKYGBge3J9CnQoWCkKYGBgCgotIE1hdHJpeCBwcm9kdWN0ICVcKiUgb3BlcmF0b3IKCmBgYHtyfQp0KFgpJSolWApgYGAKCi0gRGVncmVlcyBvZiBmcmVlZG9tIG9mIGEgbW9kZWw/CgokJCBkZiA9ICBuLXAkJAoKYGBge3J9CnN1bW1hcnkobG0xKQpkZlJlcyA8LSAobnJvdyhYKS1uY29sKFgpKQpkZlJlcwpgYGAKCi0gVmFyaWFuY2UgZXN0aW1hdG9yOiBNU0UKCiQkClxoYXQgXHNpZ21hXjIgPSBcZnJhY3tcc3VtXGxpbWl0c197aT0xfV5uXGVwc2lsb25faV4yfXtuLXB9CiQkCgoKLSBJbnZlcnQgbWF0cml4OiB1c2UgZnVuY3Rpb24gc29sdmUoLikKCi0gRGlhZ29uYWwgZWxlbWVudHMgb2YgYSBtYXRyaXg6IHVzZSBmdW5jdGlvbiBkaWFnKC4pCgpgYGB7cn0KdChYKSUqJVgKZGlhZyh0KFgpJSolWCkKYGBgCg==