Creative Commons License

This is part of the online course Statistical Genomics 2022 (SGA)

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 core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
## ✔ dplyr     1.1.4     ✔ readr     2.1.5
## ✔ forcats   1.0.0     ✔ stringr   1.5.1
## ✔ ggplot2   3.5.1     ✔ tibble    3.2.1
## ✔ lubridate 1.9.3     ✔ tidyr     1.3.1
## ✔ purrr     1.0.2     
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ dplyr::filter() masks stats::filter()
## ✖ dplyr::lag()    masks stats::lag()
## ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
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 Geometric Interpretation

5.3.2.1 Toy dataset

We will illustrate this on a toy dataset

library(tidyverse)
data <- data.frame(x=1:3,y=c(1,2,2))
data

5.3.2.2 Matrix form for toy dataset

We can also write this in matrix form

\[ \mathbf{Y} = \mathbf{X}\boldsymbol{\beta}+\boldsymbol{\epsilon} \]

with

\[ \mathbf{Y}=\left[ \begin{array}{c} 1\\ 2\\ 2\\ \end{array}\right], \quad \mathbf{X}= \left[ \begin{array}{cc} 1&1\\ 1&2\\ 1&3\\ \end{array} \right], \quad \boldsymbol{\beta} = \left[ \begin{array}{c} \beta_0\\ \beta_1\\ \end{array} \right] \quad \text{and} \quad \boldsymbol{\epsilon}= \left[ \begin{array}{c} \epsilon_1\\ \epsilon_2\\ \epsilon_3 \end{array} \right] \]

5.3.2.3 Classical interpretation

Model fit and predictions based on the toy dataset

lm1 <- lm(y~x,data)
data$yhat <- lm1$fitted

data %>%
  ggplot(aes(x,y)) +
  geom_point() +
  ylim(0,4) +
  xlim(0,4) +
  stat_smooth(method = "lm", color = "red", fullrange = TRUE) +
  geom_point(aes(x=x, y =yhat), pch = 2, size = 3, color = "red") +
  geom_segment(data = data, aes(x = x, xend = x, y = y, yend = yhat), lty = 2 )
## `geom_smooth()` using formula = 'y ~ x'
## Warning in max(ids, na.rm = TRUE): no non-missing arguments to max; returning
## -Inf

5.3.3 Projection

There is also another picture to regression:

  • Instead of plotting each observation \(i= 1 \ldots n\) as a data-point in \(\mathbb{R}^p\) with dimensions \(1 \ldots p\) for every variable/feature that is recorded for each observation

  • We can also plot \(\mathbf{Y}\), \(\hat{\mathbf{Y}}\) and each column of \(\mathbf{X}\): \(\mathbf{X}_j\) with \(j=1 \ldots p\) as a vector in \(\mathbb{R}^n\) with dimensions \(1 \ldots n\) for every observation.

  • In this representation linear regression can be interpreted as a projection of the vector \(\mathbf{Y}\) onto the subspace of \(\mathbb{R}^n\) that is spanned by the vectors for the predictors \(\mathbf{X}_1 \ldots \mathbf{X}_p\).

  • The space \(\mathbf{X}_1 \ldots \mathbf{X}_p\) is also referred to as the column space of \(\mathbf{X}\), the space that consists of all linear combinations of the vectors of the predictors or columns \(\mathbf{X}_1 \ldots \mathbf{X}_p\).

5.3.3.1 Intermezzo: Projection of vector on X and Y axis

\[ \mathbf{e}=\left[\begin{array}{c} e_1\\e_2\end{array}\right], \mathbf{u}_1 = \left[\begin{array}{c} 1\\0\end{array}\right], \mathbf{u}_2 = \left[\begin{array}{c} 0\\1\end{array}\right] \]

## Warning: Using `size` aesthetic for lines was deprecated in ggplot2 3.4.0.
## ℹ Please use `linewidth` instead.
## This warning is displayed once every 8 hours.
## Call `lifecycle::last_lifecycle_warnings()` to see where this warning was
## generated.

  1. Projection of error on x-axis

\[\begin{eqnarray*} \mathbf{u}_1^T \mathbf{e} &=& \Vert \mathbf{u}_1\Vert_2 \Vert \mathbf{e}_1\Vert_2 \cos <\mathbf{u}_1,\mathbf{e}_1>\\ &=&\left[\begin{array}{cc} 1&0\end{array}\right] \left[\begin{array}{c} e_1\\e_2\end{array}\right]\\ &=& 1\times e_1 + 0 \times e_2 \\ &=& e_1\\ \end{eqnarray*}\]

  1. Projection of error on y-axis

\[\begin{eqnarray*} \mathbf{u}_2^T \mathbf{e} &=& \left[\begin{array}{cc} 0&1\end{array}\right] \left[\begin{array}{c} e_1\\e_2\end{array}\right]\\ &=& 0\times e_1 + 1 \times e_2 \\ &=& e_2 \end{eqnarray*}\]

  1. Projection of error on itself

\[\begin{eqnarray*} \mathbf{e}^T \mathbf{e} &=&\left[\begin{array}{cc} e_1&e_2\end{array}\right] \left[\begin{array}{c} e_1\\e_2\end{array}\right]\\ &=&e_1^2+e_2^2\\ &=&\Vert e \Vert^2_2 \rightarrow \text{ Pythagorean theorem} \end{eqnarray*}\]


5.3.3.2 Interpretation of least squares as a projection

Fitted values:

\[ \begin{array}{lcl} \hat{\mathbf{Y}} &=& \mathbf{X}\hat{\boldsymbol{\beta}}\\ &=& \mathbf{X} (\mathbf{X}^T\mathbf{X})^{-1}\mathbf{X}^T\mathbf{Y}\\ &=& \mathbf{HY} \end{array} \] with \(\mathbf{H}\) the projection matrix also referred to as the hat matrix.

X <- model.matrix(~x,data)
X
##   (Intercept) x
## 1           1 1
## 2           1 2
## 3           1 3
## attr(,"assign")
## [1] 0 1
XtX <- t(X)%*%X
XtX
##             (Intercept)  x
## (Intercept)           3  6
## x                     6 14
XtXinv <- solve(t(X)%*%X)
XtXinv
##             (Intercept)    x
## (Intercept)    2.333333 -1.0
## x             -1.000000  0.5
H <- X %*% XtXinv %*% t(X)
H
##            1         2          3
## 1  0.8333333 0.3333333 -0.1666667
## 2  0.3333333 0.3333333  0.3333333
## 3 -0.1666667 0.3333333  0.8333333
Y <- data$y
Yhat <- H%*%Y
Yhat
##       [,1]
## 1 1.166667
## 2 1.666667
## 3 2.166667
  • 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. The other picture to linear regression is to consider \(X_0\), \(X_1\) and \(Y\) as vectors in the space of the data \(\mathbb{R}^n\), here \(\mathbb{R}^3\) because we have three data points. 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:
originRn <- data.frame(X1=0,X2=0,X3=0)
data$x0 <- 1
dataRn <- data.frame(t(data))

library(plotly)
## 
## Attaching package: 'plotly'
## The following object is masked from 'package:ggplot2':
## 
##     last_plot
## The following object is masked from 'package:stats':
## 
##     filter
## The following object is masked from 'package:graphics':
## 
##     layout
p1 <- plot_ly(
    originRn,
    x = ~ X1,
    y = ~ X2,
    z= ~ X3, name="origin") %>%
  add_markers(type="scatter3d") %>%
  layout(
    scene = list(
      aspectmode="cube",
      xaxis = list(range=c(-4,4)), yaxis = list(range=c(-4,4)), zaxis = list(range=c(-4,4))
      )
    )
p1 <- p1 %>%
  add_trace(
    x = c(0,1),
    y = c(0,0),
    z = c(0,0),
    mode = "lines",
    line = list(width = 5, color = "grey"),
    type="scatter3d",
    name = "obs1") %>%
  add_trace(
    x = c(0,0),
    y = c(0,1),
    z = c(0,0),
    mode = "lines",
    line = list(width = 5, color = "grey"),
    type="scatter3d",
    name = "obs2") %>%
  add_trace(
    x = c(0,0),
    y = c(0,0),
    z = c(0,1),
    mode = "lines",
    line = list(width = 5, color = "grey"),
    type="scatter3d",
    name = "obs3") %>%
  add_trace(
    x = c(0,1),
    y = c(0,1),
    z = c(0,1),
    mode = "lines",
    line = list(width = 5, color = "black"),
    type="scatter3d",
    name = "X1") %>%
    add_trace(
    x = c(0,1),
    y = c(0,2),
    z = c(0,3),
    mode = "lines",
    line = list(width = 5, color = "black"),
    type="scatter3d",
    name = "X2")
p1
  1. Vector of Y:

Actual values of \(\mathbf{Y}\):

data$y
## [1] 1 2 2

\[ \mathbf{Y}=\left[\begin{array}{c} 1 \\ 2 \\ 2 \end{array}\right] \]

p2 <- p1 %>%
  add_trace(
    x = c(0,Y[1]),
    y = c(0,Y[2]),
    z = c(0,Y[3]),
    mode = "lines",
    line = list(width = 5, color = "red"),
    type="scatter3d",
    name = "Y") 
p2
  1. Projection of Y onto column space

Actual values of fitted values \(\mathbf{\hat{Y}}\):

data$yhat
## [1] 1.166667 1.666667 2.166667

\[ \mathbf{Y}=\left[\begin{array}{c} 1.1666667 \\ 1.6666667 \\ 2.1666667 \end{array}\right] \]

p2 <- p2 %>%
  add_trace(
    x = c(0,Yhat[1]),
    y = c(0,Yhat[2]),
    z = c(0,Yhat[3]),
    mode = "lines",
    line = list(width = 5, color = "orange"),
    type="scatter3d",
    name="Yhat") %>% 
    add_trace(
    x = c(Y[1],Yhat[1]),
    y = c(Y[2],Yhat[2]),
    z = c(Y[3],Yhat[3]),
    mode = "lines",
    line = list(width = 5, color = "red", dash="dash"),
    type="scatter3d",
    name="Y -> Yhat"
    )
p2

\(\mathbf{Y}\) is projected in the column space of \(\mathbf{X}\)! spanned by the columns.

5.3.3.3 How does this projection works?

\[ \begin{array}{lcl} \hat{\mathbf{Y}} &=& \mathbf{X} (\mathbf{X}^T\mathbf{X})^{-1}\mathbf{X}^T\mathbf{Y}\\ &=& \mathbf{X}(\mathbf{X}^T\mathbf{X})^{-1/2}(\mathbf{X}^T\mathbf{X})^{-1/2}\mathbf{X}^T\mathbf{Y}\\ &=& \mathbf{U}\mathbf{U}^T\mathbf{Y} \end{array} \]

  • \(\mathbf{U}\) is a new orthonormal basis in \(\mathbb{R}^2\), a subspace of \(\mathbb{R}^3\)

  • The space spanned by U and X is the column space of X, e.g. it contains all possible linear combinantions of X. \(\mathbf{U}^t\mathbf{Y}\) is the projection of Y on this new orthonormal basis

eigenXtX <- eigen(XtX)
XtXinvSqrt <- eigenXtX$vectors %*%diag(1/eigenXtX$values^.5)%*%t(eigenXtX$vectors)
U <- X %*% XtXinvSqrt
  • \(\mathbf{U}\) orthonormal basis
U
##         [,1]        [,2]
## 1  0.9116067 -0.04802616
## 2  0.3881706  0.42738380
## 3 -0.1352655  0.90279376
t(U)%*%U
##              [,1]         [,2]
## [1,] 1.000000e+00 2.915205e-16
## [2,] 2.915205e-16 1.000000e+00
  • \(\mathbf{UU}^T\) equals projection matrix
U%*%t(U)
##            1         2          3
## 1  0.8333333 0.3333333 -0.1666667
## 2  0.3333333 0.3333333  0.3333333
## 3 -0.1666667 0.3333333  0.8333333
H
##            1         2          3
## 1  0.8333333 0.3333333 -0.1666667
## 2  0.3333333 0.3333333  0.3333333
## 3 -0.1666667 0.3333333  0.8333333
p3 <- p1 %>%
  add_trace(
    x = c(0,U[1,1]),
    y = c(0,U[2,1]),
    z = c(0,U[3,1]),
    mode = "lines",
    line = list(width = 5, color = "blue"),
    type="scatter3d",
    name = "U1") %>%
  add_trace(
    x = c(0,U[1,2]),
    y = c(0,U[2,2]),
    z = c(0,U[3,2]),
    mode = "lines",
    line = list(width = 5, color = "blue"),
    type="scatter3d",
    name = "U2")

p3
  • \(\mathbf{U}^T\mathbf{Y}\) is the projection of \(\mathbf{Y}\) in the space spanned by \(\mathbf{U}\).
  • Indeed \(\mathbf{U}_1^T\mathbf{Y}\)
p4 <- p3 %>%
  add_trace(
    x = c(0,Y[1]),
    y = c(0,Y[2]),
    z = c(0,Y[3]),
    mode = "lines",
    line = list(width = 5, color = "red"),
    type="scatter3d",
    name = "Y") %>%
  add_trace(
    x = c(0,U[1,1]*(U[,1]%*%Y)),
    y = c(0,U[2,1]*(U[,1]%*%Y)),
    z = c(0,U[3,1]*(U[,1]%*%Y)),
    mode = "lines",
    line = list(width = 5, color = "red",dash="dash"),
    type="scatter3d",
    name="Y -> U1") %>% add_trace(
    x = c(Y[1],U[1,1]*(U[,1]%*%Y)),
    y = c(Y[2],U[2,1]*(U[,1]%*%Y)),
    z = c(Y[3],U[3,1]*(U[,1]%*%Y)),
    mode = "lines",
    line = list(width = 5, color = "red", dash="dash"),
    type="scatter3d",
    name="Y -> U1")
p4
  • and \(\mathbf{U}_2^T\mathbf{Y}\)
p5 <- p4 %>%
  add_trace(
    x = c(0,U[1,2]*(U[,2]%*%Y)),
    y = c(0,U[2,2]*(U[,2]%*%Y)),
    z = c(0,U[3,2]*(U[,2]%*%Y)),
    mode = "lines",
    line = list(width = 5, color = "red",dash="dash"),
    type="scatter3d",
    name="Y -> U2") %>% add_trace(
    x = c(Y[1],U[1,2]*(U[,2]%*%Y)),
    y = c(Y[2],U[2,2]*(U[,2]%*%Y)),
    z = c(Y[3],U[3,2]*(U[,2]%*%Y)),
    mode = "lines",
    line = list(width = 5, color = "red", dash="dash"),
    type="scatter3d",
    name="Y -> U2")
p5
  • \(\hat{\mathbf{Y}}\) is the resulting vector that lies in the plane spanned by \(\mathbf{U}_1\) and \(\mathbf{U}_2\) and thus also in the column space of \(\mathbf{X}\).
p6 <- p5 %>%
  add_trace(
    x = c(0,Yhat[1]),
    y = c(0,Yhat[2]),
    z = c(0,Yhat[3]),
    mode = "lines",
    line = list(width = 5, color = "orange"),
    type="scatter3d",
    name = "Yhat") %>%
  add_trace(
    x = c(Y[1],Yhat[1]),
    y = c(Y[2],Yhat[2]),
    z = c(Y[3],Yhat[3]),
    mode = "lines",
    line = list(width = 5, color = "maroon2"),
    type="scatter3d",
    name = "e") %>%
  add_trace(
    x = c(U[1,1]*(U[,1]%*%Y),Yhat[1]),
    y = c(U[2,1]*(U[,1]%*%Y),Yhat[2]),
    z = c(U[3,1]*(U[,1]%*%Y),Yhat[3]),
    mode = "lines",
    line = list(width = 5, color = "orange", dash="dash"),
    type="scatter3d",
    name = "Y -> U")  %>%
  add_trace(
    x = c(U[1,2]*(U[,2]%*%Y),Yhat[1]),
    y = c(U[2,2]*(U[,2]%*%Y),Yhat[2]),
    z = c(U[3,2]*(U[,2]%*%Y),Yhat[3]),
    mode = "lines",
    line = list(width = 5, color = "orange", dash="dash"),
    type="scatter3d",
    name = "Y -> U")
p6

5.3.4 Error

Note, that it is also clear from the equation in the derivation of the least squares solution 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 = y ~ x, data = data)
## 
## Residuals:
##       1       2       3 
## -0.1667  0.3333 -0.1667 
## 
## Coefficients:
##             Estimate Std. Error t value Pr(>|t|)
## (Intercept)   0.6667     0.6236   1.069    0.479
## x             0.5000     0.2887   1.732    0.333
## 
## Residual standard error: 0.4082 on 1 degrees of freedom
## Multiple R-squared:   0.75,  Adjusted R-squared:    0.5 
## F-statistic:     3 on 1 and 1 DF,  p-value: 0.3333
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
LS0tCnRpdGxlOiAiUmVjYXAgZ2VuZXJhbCBsaW5lYXIgbW9kZWwiCmF1dGhvcjogIkxpZXZlbiBDbGVtZW50IgpkYXRlOiAic3RhdE9taWNzLCBHaGVudCBVbml2ZXJzaXR5IChodHRwczovL3N0YXRvbWljcy5naXRodWIuaW8pIgpvdXRwdXQ6CiAgICBodG1sX2RvY3VtZW50OgogICAgICBjb2RlX2Rvd25sb2FkOiB0cnVlCiAgICAgIHRoZW1lOiBjb3NtbwogICAgICB0b2M6IHRydWUKICAgICAgdG9jX2Zsb2F0OiB0cnVlCiAgICAgIGhpZ2hsaWdodDogdGFuZ28KICAgICAgbnVtYmVyX3NlY3Rpb25zOiB0cnVlCiAgICBib29rZG93bjo6cGRmX2RvY3VtZW50MjoKICAgICAgdG9jOiB0cnVlCiAgICAgIG51bWJlcl9zZWN0aW9uczogdHJ1ZQogICAgICBsYXRleF9lbmdpbmU6IHhlbGF0ZXgKYWx3YXlzX2FsbG93X2h0bWw6IHRydWUKLS0tCgoKPGEgcmVsPSJsaWNlbnNlIiBocmVmPSJodHRwczovL2NyZWF0aXZlY29tbW9ucy5vcmcvbGljZW5zZXMvYnktbmMtc2EvNC4wIj48aW1nIGFsdD0iQ3JlYXRpdmUgQ29tbW9ucyBMaWNlbnNlIiBzdHlsZT0iYm9yZGVyLXdpZHRoOjAiIHNyYz0iaHR0cHM6Ly9pLmNyZWF0aXZlY29tbW9ucy5vcmcvbC9ieS1uYy1zYS80LjAvODh4MzEucG5nIiAvPjwvYT4KClRoaXMgaXMgcGFydCBvZiB0aGUgb25saW5lIGNvdXJzZSBbU3RhdGlzdGljYWwgR2Vub21pY3MgMjAyMiAoU0dBKV0oaHR0cHM6Ly9zdGF0b21pY3MuZ2l0aHViLmlvL1NHQS8pCgojIEJyZWFzdCBjYW5jZXIgZXhhbXBsZQoKLSBwYXJ0IG9mIHN0dWR5IGh0dHBzOi8vZG9pLm9yZy8xMC4xMDkzL2puY2kvZGpqMDUyKQotIEhpc3RvbG9naWMgZ3JhZGUgaW4gYnJlYXN0IGNhbmNlciBjbGluaWNhbGx5IHByb2dub3N0aWMuCkFzc29jaWF0aW9uIG9mIGhpc3RvbG9naWMgZ3JhZGUgb24gZXhwcmVzc2lvbiBvZiBLUE5BMiBnZW5lIHRoYXQgaXMga25vd24gdG8gYmUgYXNzb2NpYXRlZCB3aXRoIHBvb3IgQkMgcHJvZ25vc2lzLgotIFBvcHVsYXRpb246IGFsbCBjdXJyZW50IGFuZCBmdXR1cmUgYnJlYXN0IGNhbmNlciBwYXRpZW50cwoKLS0tCgoKIVtdKC4vZmlndXJlcy9zdGF0R2Vub21pY3NHZW50MjAxNzE4LTEuanBlZykKCi0tLQoKIVtdKC4vZmlndXJlcy9zdGF0R2Vub21pY3NHZW50MjAxNzE4LTIuanBlZykKCi0tLQoKIVtdKC4vZmlndXJlcy9zdGF0R2Vub21pY3NHZW50MjAxNzE4LTMuanBlZykKCi0tLQoKCiFbXSguL2ZpZ3VyZXMvc3RhdEdlbm9taWNzR2VudDIwMTcxOC00LmpwZWcpCgotLS0KCgohW10oLi9maWd1cmVzL3N0YXRHZW5vbWljc0dlbnQyMDE3MTgtNS5qcGVnKQoKLS0tCgoKIVtdKC4vZmlndXJlcy9zdGF0R2Vub21pY3NHZW50MjAxNzE4LTYuanBlZykKCgojIERhdGEgRXhwbG9yYXRpb24KCiMjIEltcG9ydAoKYGBge3J9CmxpYnJhcnkodGlkeXZlcnNlKQpnZW5lIDwtIHJlYWQudGFibGUoImh0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9zdGF0T21pY3MvU0dBMjEvbWFzdGVyL2RhdGEva3BuYTIudHh0IixoZWFkZXI9VFJVRSkKaGVhZChnZW5lKQpgYGAKCldlIHdpbGwgdHJhbnNmb3JtIHRoZSB2YXJpYWJsZSBncmFkZSBhbmQgbm9kZSB0byBhIGZhY3RvcgoKYGBge3J9CmdlbmUkZ3JhZGUgPC0gYXMuZmFjdG9yKGdlbmUkZ3JhZGUpCmdlbmUkbm9kZSA8LSBhcy5mYWN0b3IoZ2VuZSRub2RlKQpgYGAKCiMjIFN1bW1hcnkgc3RhdGlzdGljcwoKYGBge3J9CmdlbmVTdW0gPC0gZ2VuZSAlPiUKICBncm91cF9ieShncmFkZSkgJT4lCiAgc3VtbWFyaXplKG1lYW4gPSBtZWFuKGdlbmUpLAogICAgICAgICAgICBzZCA9IHNkKGdlbmUpLAogICAgICAgICAgICBuPWxlbmd0aChnZW5lKQogICAgICAgICAgICApICU+JQogIG11dGF0ZShzZSA9IHNkL3NxcnQobikpCmdlbmVTdW0KYGBgCgojIyBWaXN1YWxpc2F0aW9uCgpgYGB7cn0KZ2VuZSAlPiUKICBnZ3Bsb3QoYWVzKHg9Z3JhZGUseT1nZW5lKSkgKwogIGdlb21fYm94cGxvdChvdXRsaWVyLnNoYXBlPU5BKSArCiAgZ2VvbV9qaXR0ZXIoKQpgYGAKCldlIGNhbiBhbHNvIHNhdmUgdGhlIHBsb3RzIGFzIG9iamVjdHMgZm9yIGxhdGVyIHVzZSEKCmBgYHtyfQpwMSA8LSBnZW5lICU+JQogIGdncGxvdChhZXMoeD1ncmFkZSx5PWdlbmUpKSArCiAgZ2VvbV9ib3hwbG90KG91dGxpZXIuc2hhcGU9TkEpICsKICBnZW9tX2ppdHRlcigpCgpwMiA8LSBnZW5lICU+JQogIGdncGxvdChhZXMoc2FtcGxlPWdlbmUpKSArCiAgZ2VvbV9xcSgpICsKICBnZW9tX3FxX2xpbmUoKSArCiAgZmFjZXRfd3JhcCh+Z3JhZGUpCgpwMQpwMgpgYGAKCgojIyBSZXNlYXJjaCBxdWVzdGlvbnMKClJlc2VhcmNoZXJzIHdhbnQgdG8gYXNzZXNzIHRoZSBhc3NvY2lhdGlvbiBvZiB0aGUgaGlzdG9sb2dpY2FsIGdyYWRlIG9uIEtQTkEyIGdlbmUgZXhwcmVzc2lvbgoKCgoKIVtdKC4vZmlndXJlcy9zdGF0R2Vub21pY3NHZW50MjAxNzE4LTYuanBlZykKCi0tLQoKIyMgRXN0aW1hdGlvbiBvZiBlZmZlY3Qgc2l6ZSBhbmQgc3RhbmRhcmQgZXJyb3IKCmBgYHtyfQplZmZlY3RTaXplIDwtIHRpYmJsZSgKICBkZWx0YSA9IGdlbmVTdW0kbWVhblsyXS0gZ2VuZVN1bSRtZWFuWzFdLAogIHNlRGVsdGEgPSBnZW5lU3VtICU+JQogICAgcHVsbChzZSkgJT4lCiAgICAuXjIgJT4lCiAgICBzdW0gJT4lCiAgICBzcXJ0CiAgKQplZmZlY3RTaXplCmBgYAoKIyBTdGF0aXN0aWNhbCBJbmZlcmVuY2UKCi0gUmVzZWFyY2hlcnMgd2FudCB0byBhc3Nlc3MgdGhlIGFzc29jaWF0aW9uIG9mIGhpc3RvbG9naWNhbCBncmFkZSBvbiBLUE5BMiBnZW5lIGV4cHJlc3Npb24KLSBJbmZlcmVuY2U/CgotLS0KCiFbXSguL2ZpZ3VyZXMvc3RhdEdlbm9taWNzR2VudDIwMTcxOC03LmpwZWcpCgoKLS0tCgoKLSBSZXNlYXJjaGVycyB3YW50IHRvIGFzc2VzcyB0aGUgYXNzb2NpYXRpb24gb2YgaGlzdG9sb2dpY2FsIGdyYWRlIG9uIEtQTkEyIGdlbmUgZXhwcmVzc2lvbgotIEluZmVyZW5jZT8KLSB0ZXN0aW5nICsgQ0kgJCBccmlnaHRhcnJvdyAkIEFzc3VtcHRpb25zCgotLS0KCi0gSW4gZ2VuZXJhbCB3ZSBzdGFydCBmcm9tICoqYWx0ZXJuYXRpdmUgaHlwb3RoZXNlKiogJEhfQSQ6IHdlIHdhbnQgdG8gc2hvdyBhbiBhc3NvY2lhdGlvbgotIEdlbmUgZXhwcmVzc2lvbiBvZiBncmFkZSAxIGFuZCBncmFkZSAzIHBhdGllbnRzIGlzIG9uIGF2ZXJhZ2UgZGlmZmVyZW50CgotIEJ1dCwgd2Ugd2lsbCBhc3Nlc3MgaXQgYnkgZmFsc2lmeWluZyB0aGUgb3Bwb3NpdGU6CgotIFRoZSBhdmVyYWdlIEtQTkEyIGdlbmUgZXhwcmVzc2lvbiBvZiAgZ3JhZGUgMSBhbmQgZ3JhZGUgMyBwYXRpZW50cyBpcyBlcXVhbAoKLS0tCgotIEhvdyBsaWtlbHkgaXMgaXQgdG8gb2JzZXJ2ZSBhbiBlcXVhbCBvciBtb3JlIGV4dHJlbWUgYXNzb2NpYXRpb24gdGhhbiB0aGUgb25lIG9ic2VydmVkIGluIHRoZSBzYW1wbGUgd2hlbiB0aGUgbnVsbCBoeXBvdGhlc2lzIGlzIHRydWU/CgotIFdoZW4gd2UgbWFrZSBhc3N1bXB0aW9ucyBhYm91dCB0aGUgZGlzdHJpYnV0aW9uIG9mIG91ciB0ZXN0IHN0YXRpc3RpYyB3ZSBjYW4gcXVhbnRpZnkgdGhpcyBwcm9iYWJpbGl0eTogKipwLXZhbHVlKiouCi0gSWYgdGhlIHAtdmFsdWUgaXMgYmVsb3cgYSBzaWduaWZpY2FuY2UgdGhyZXNob2xkICRcYWxwaGEkIHdlIHJlamVjdCB0aGUgbnVsbCBoeXBvdGhlc2lzCgoqV2UgY29udHJvbCB0aGUgcHJvYmFiaWxpdHkgb24gYSBmYWxzZSBwb3NpdGl2ZSByZXN1bHQgYXQgdGhlICRcYWxwaGEkLWxldmVsICh0eXBlIEkgZXJyb3IpKgoKLSBUaGUgcC12YWx1ZSB3aWxsIG9ubHkgYmUgY2FsY3VsYXRlZCBjb3JyZWN0bHkgaWYgdGhlIHVuZGVybHlpbmcgYXNzdW1wdGlvbnMgaG9sZCEKCmBgYHtyfQpsaWJyYXJ5KGdyaWRFeHRyYSkKcDEKcDIKYGBgCgpgYGB7cn0KdC50ZXN0KGdlbmV+Z3JhZGUsZGF0YT1nZW5lKQoKZWZmZWN0U2l6ZSA8LSBlZmZlY3RTaXplICU+JQogIG11dGF0ZSh0LnN0YXQ9ZGVsdGEvc2VEZWx0YSkgJT4lCiAgbXV0YXRlKHAudmFsdWU9IHB0KC1hYnModC5zdGF0KSwyMS4zNTIpKjIpCgplZmZlY3RTaXplCmBgYAoKLSBJbnRlbnNpdGllcyBhcmUgb2Z0ZW4gbm90IG5vcm1hbGx5IGRpc3RyaWJ1dGVkIGFuZCBoYXZlIGEgbWVhbiB2YXJpYW5jZSByZWxhdGlvbgotIENvbW1vbmx5IGxvZzItdHJhbnNmb3JtZWQKLSBEaWZmZXJlbmNlcyBvbiBsb2cgc2NhbGU6CgokJApcbG9nXzIoQikgLSBcbG9nXzIoQSkgPSBcbG9nXzIgXGZyYWN7Qn17QX0gPSBcbG9nXzIgRkNfe1xmcmFje0J9e0F9fQokJAoKCgohW10oLi9maWd1cmVzL3N0YXRHZW5vbWljc0dlbnQyMDE3MTgtOC5qcGVnKQoKLS0tCgojIyBMb2cgdHJhbnNmb3JtYXRpb24KCmBgYHtyfQpnZW5lIDwtIGdlbmUgJT4lCiAgbXV0YXRlKGxnZW5lID0gbG9nMihnZW5lKSkKCnAxIDwtIGdlbmUgJT4lCiAgZ2dwbG90KGFlcyh4PWdyYWRlLHk9bGdlbmUpKSArCiAgZ2VvbV9ib3hwbG90KG91dGxpZXIuc2hhcGU9TkEpICsKICBnZW9tX2ppdHRlcigpCgpwMiA8LSBnZW5lICU+JQogIGdncGxvdChhZXMoc2FtcGxlPWxnZW5lKSkgKwogIGdlb21fcXEoKSArCiAgZ2VvbV9xcV9saW5lKCkgKwogIGZhY2V0X3dyYXAofmdyYWRlKQoKcDEKcDIKCmxvZ3Rlc3QgPC0gdC50ZXN0KGxnZW5lfmdyYWRlLGRhdGE9Z2VuZSx2YXIuZXF1YWw9VFJVRSkKbG9ndGVzdAoKbG9nMkZDIDwtIGxvZ3Rlc3QkZXN0aW1hdGVbMl0tbG9ndGVzdCRlc3RpbWF0ZVsxXQpsb2cyRkMKbmFtZXMobG9nMkZDKSA8LSAiZzMtZzEiCjJebG9nMkZDCmBgYAoKIyMgQ29uY2x1c2lvbgoKVGhlcmUgaXMgYSBleHRyZW1lbHkgc2lnbmlmaWNhbnQgYXNzb2NpYXRpb24gb2YgdGhlIGhpc3RvbG9naWNhbCBncmFkZSBvbiB0aGUgZ2VuZSBleHByZXNzaW9uIGluIHR1bW9yIHRpc3N1ZS4gIE9uIGF2ZXJhZ2UsIHRoZSBnZW5lIGV4cHJlc3Npb24gZm9yIHRoZSBncmFkZSAzIHBhdGllbnRzIGlzIGByIHJvdW5kKDJebG9nMkZDLDIpYCB0aW1lcyBoaWdoZXIgdGhhbiB0aGUgZ2VuZSBleHByZXNzaW9uIGluIGdyYWRlIDEgcGF0aWVudHMgKDk1XCUgQ0kgIFtgciBwYXN0ZShyb3VuZCgyXi1sb2d0ZXN0JGNvbmYuaW50WzI6MV0sMiksY29sbGFwc2U9IiwgIilgXSwgJHA8PDAuMDAxJCkuCgoKCgohW10oLi9maWd1cmVzL3N0YXRHZW5vbWljc0dlbnQyMDE3MTgtMTAuanBlZykgCgotLS0KCgohW10oLi9maWd1cmVzL3N0YXRHZW5vbWljc0dlbnQyMDE3MTgtMTEuanBlZykKCi0tLQoKVGhlIHBhdGllbnRzIGFsc28gZGlmZmVyIGluIHRoZSB0aGVpciBseW1waCBub2RlIHN0YXR1cy4gSGVuY2UsIHdlIGhhdmUgYSB0d28gZmFjdG9yaWFsIGRlc2lnbjogZ3JhZGUgeCBseW1waCBub2RlIHN0YXR1cyEhIQoKU29sdXRpb24/PwoKIVtdKC4vZmlndXJlcy9zdGF0R2Vub21pY3NHZW50MjAxNzE4LTEyLmpwZWcpCgotLS0KCiMgR2VuZXJhbCBMaW5lYXIgTW9kZWwKCkhvdyBjYW4gd2UgaW50ZWdyYXRlIG11bHRpcGxlIGZhY3RvcnMgYW5kIGNvbnRpbnVvdXMgY292YXJpYXRlcyBpbiBsaW5lYXIgbW9kZWwuCgpcWwp5X2k9IFxiZXRhXzAgKyBcYmV0YV8xIHhfe2ksMX0gKyBcYmV0YV8yIHhfe2ksMn0gKyBcYmV0YV97MTJ9eF97aSwxfXhfe2ksMn0rXGVwc2lsb25faSwKXF0Kd2l0aAoKLSAkeF97aSwxfSQgYSBkdW1teSB2YXJpYWJsZSBmb3IgaGlzdG9sb2dpY2FsIGdyYWRlOiAkeF97aSwxfT1cYmVnaW57Y2FzZXN9CjAmIFx0ZXh0e2dyYWRlIDF9XFwKMSYgXHRleHR7Z3JhZGUgM30KXGVuZHtjYXNlc30kCi0gJHhfe2ksMn0kIGEgZHVtbXkgdmFyaWFibGUgZm9yIDogJHhfe2ksMn09XGJlZ2lue2Nhc2VzfQowJiBcdGV4dHtseW1waCBub2RlcyB3ZXJlIG5vdCByZW1vdmVkfVxcCjEmIFx0ZXh0e2x5bXBoIG5vZGVzIHdlcmUgcmVtb3ZlZH0KXGVuZHtjYXNlc30kCi0gJFxlcHNpbG9uX2kkPwoKLS0tCgojIyBJbXBsZW1lbnRhdGlvbiBpbiBSCgpgYGB7cn0KbG0xIDwtIGxtKGdlbmV+Z3JhZGUqbm9kZSxkYXRhPWdlbmUpCnN1bW1hcnkobG0xKQpgYGAKCi0tLQoKIyMgQXNzdW1wdGlvbnMKCmBgYHtyfQpwbG90KGxtMSkKYGBgCgotLS0KCiMjIEJyZWFzdCBjYW5jZXIgZXhhbXBsZQoKLSAgUGFwZXI6IGh0dHBzOi8vZG9pLm9yZy8xMC4xMDkzL2puY2kvZGpqMDUyCi0gSGlzdG9sb2dpYyBncmFkZSBpbiBicmVhc3QgY2FuY2VyIHByb3ZpZGVzIGNsaW5pY2FsbHkgaW1wb3J0YW50IHByb2dub3N0aWMgaW5mb3JtYXRpb24uIFR3byBmYWN0b3JzIGhhdmUgdG8gYmUgY29uY2lkZXJlZDogSGlzdG9sb2dpYyBncmFkZSAoZ3JhZGUgMSBhbmQgZ3JhZGUgMykgYW5kIGx5bXBoIG5vZGUgc3RhdHVzICgwIHZzIDEpLiBUaGUgcmVzZWFyY2hlcnMgYXNzZXNzZWQgZ2VuZSBleHByZXNzaW9uIG9mIHRoZSBLUE5BMiBnZW5lIGEgcHJvdGVpbi1jb2RpbmcgZ2VuZSBhc3NvY2lhdGVkIHdpdGggYnJlYXN0IGNhbmNlciBhbmQgYXJlIG1haW5seSBpbnRlcmVzdGVkIGluIHRoZSBhc3NvY2lhdGlvbiBvZiBoaXN0b2xvZ2ljYWwgZ3JhZGUuIE5vdGUsIHRoYXQgdGhlIGdlbmUgdmFyaWFibGUgY29uc2lzdHMgb2YgYmFja2dyb3VuZCBjb3JyZWN0ZWQgbm9ybWFsaXplZCBpbnRlbnNpdGllcyBvYnRhaW5lZCB3aXRoIGEgbWljcm9hcnJheSBwbGF0Zm9ybS4gVXBvbiBsb2ctdHJhbnNmb3JtYXRpb24sIHRoZXkgYXJlIGtub3duIHRvIGJlIGEgZ29vZCBwcm94eSBmb3IgdGhlICRcbG9nJCB0cmFuc2Zvcm1lZCBjb25jZW50cmF0aW9uIG9mIGdlbmUgZXhwcmVzc2lvbiBwcm9kdWN0IG9mIHRoZSBLUE5BMiBnZW5lLgotIFJlc2VhcmNoIHF1ZXN0aW9ucyBhbmQgdHJhbnNsYXRlIHRoZW0gdG93YXJkcyBtb2RlbCBwYXJhbWV0ZXJzIChjb250cmFzdHMpPwotIE1ha2UgYW4gUiBtYXJrZG93biBmaWxlIHRvIGFuc3dlciB0aGUgcmVzZWFyY2ggcXVlc3Rpb25zCgoKYGBge3J9CmxpYnJhcnkoRXhwbG9yZU1vZGVsTWF0cml4KQpleHBsTXggPC0gVmlzdWFsaXplRGVzaWduKGdlbmUsZGVzaWduRm9ybXVsYSA9IH5ncmFkZSpub2RlKQpleHBsTXgkcGxvdGxpc3QKYGBgCgpZb3UgY2FuIGFsc28gZXhwbG9yZSB0aGUgbW9kZWwgbWF0cml4IGludGVyYWN0aXZlbHk6CgpgYGB7ciBldmFsPUZBTFNFfQpFeHBsb3JlTW9kZWxNYXRyaXgoZ2VuZSxkZXNpZ25Gb3JtdWxhID0gfmdyYWRlKm5vZGUpCmBgYAotLS0KCiMgTGluZWFyIHJlZ3Jlc3Npb24gaW4gbWF0cml4IGZvcm0KCiMjIFNjYWxhciBmb3JtCgotIENvbnNpZGVyIGEgdmVjdG9yIG9mIHByZWRpY3RvcnMgJFxtYXRoYmZ7eH09KHhfMSxcbGRvdHMseF9wKV5UJCBhbmQKLSBhIHJlYWwtdmFsdWVkIHJlc3BvbnNlICRZJAotIHRoZW4gdGhlIGxpbmVhciByZWdyZXNzaW9uIG1vZGVsIGNhbiBiZSB3cml0dGVuIGFzClxbClk9ZihcbWF0aGJme3h9KSArXGVwc2lsb249XGJldGFfMCtcc3VtXGxpbWl0c197aj0xfV5wIHhfalxiZXRhX2ogKyBcZXBzaWxvbgpcXQp3aXRoIGkuaS5kLiAkXGVwc2lsb25cc2ltIE4oMCxcc2lnbWFeMikkCgojIyBNYXRyaXggZm9ybQoKLSAkbiQgb2JzZXJ2YXRpb25zICQoXG1hdGhiZnt4fV8xLHlfMSkgXGxkb3RzIChcbWF0aGJme3h9X24seV9uKSQKLSBSZWdyZXNzaW9uIGluIG1hdHJpeCBub3RhdGlvbgpcW1xtYXRoYmZ7WX09XG1hdGhiZntYXGJldGF9ICsgXG1hdGhiZntcZXBzaWxvbn1cXQp3aXRoICRcbWF0aGJme1l9PVxsZWZ0W1xiZWdpbnthcnJheX17Y315XzFcXCBcdmRvdHNcXHlfblxlbmR7YXJyYXl9XHJpZ2h0XSQsCiRcbWF0aGJme1h9PVxsZWZ0W1xiZWdpbnthcnJheX17Y2NjY30gMSZ4X3sxMX0mXGxkb3RzJnhfezFwfVxcClx2ZG90cyZcdmRvdHMmJlx2ZG90c1xcCjEmeF97bjF9JlxsZG90cyZ4X3tucH0KXGVuZHthcnJheX1ccmlnaHRdJCwKJFxtYXRoYmZ7XGJldGF9PVxsZWZ0W1xiZWdpbnthcnJheX17Y31cYmV0YV8wXFwgXHZkb3RzXFwgXGJldGFfcFxlbmR7YXJyYXl9XHJpZ2h0XSQgYW5kCiRcbWF0aGJme1xlcHNpbG9ufT1cbGVmdFtcYmVnaW57YXJyYXl9e2N9IFxlcHNpbG9uXzEgXFwgXHZkb3RzIFxcIFxlcHNpbG9uX25cZW5ke2FycmF5fVxyaWdodF0kCgojIyBMZWFzdCBTcXVhcmVzIChMUykKLSBNaW5pbWl6ZSB0aGUgcmVzaWR1YWwgc3VtIG9mIHNxdWFyZXMKXGJlZ2lue2VxbmFycmF5Kn0KUlNTKFxtYXRoYmZ7XGJldGF9KSY9JlxzdW1cbGltaXRzX3tpPTF9Xm4gZV4yX2lcXAomPSZcc3VtXGxpbWl0c197aT0xfV5uIFxsZWZ0KHlfaS1cYmV0YV8wLVxzdW1cbGltaXRzX3tqPTF9XnAgeF97aWp9XGJldGFfalxyaWdodCleMgpcZW5ke2VxbmFycmF5Kn0KLSBvciBpbiBtYXRyaXggbm90YXRpb24KXGJlZ2lue2VxbmFycmF5Kn0KUlNTKFxtYXRoYmZ7XGJldGF9KSY9JihcbWF0aGJme1l9LVxtYXRoYmZ7WFxiZXRhfSleVChcbWF0aGJme1l9LVxtYXRoYmZ7WFxiZXRhfSlcXAomPSZcVmVydCBcbWF0aGJme1l9LVxtYXRoYmZ7WFxiZXRhfVxWZXJ0XjJfMgpcZW5ke2VxbmFycmF5Kn0Kd2l0aCB0aGUgJExfMiQtbm9ybSBvZiBhICRwJC1kaW0uIHZlY3RvciAkdiQgJFxWZXJ0IFxtYXRoYmZ7dn0gXFZlcnQ9XHNxcnR7dl8xXjIrXGxkb3RzK3ZfcF4yfSQKJFxyaWdodGFycm93JCAkXGhhdHtcbWF0aGJme1xiZXRhfX09XHRleHR7YXJnbWlufV9cYmV0YSBcVmVydCBcbWF0aGJme1l9LVxtYXRoYmZ7WFxiZXRhfVxWZXJ0XjJfMiQKCi0tLQoKIyMjIE1pbmltaXplIFJTUwpcWwpcYmVnaW57YXJyYXl9e2NjY30KXGZyYWN7XHBhcnRpYWwgUlNTfXtccGFydGlhbCBcbWF0aGJme1xiZXRhfX0mPSZcbWF0aGJmezB9XFxcXApcZnJhY3soXG1hdGhiZntZfS1cbWF0aGJme1hcYmV0YX0pXlQoXG1hdGhiZntZfS1cbWF0aGJme1hcYmV0YX0pfXtccGFydGlhbCBcbWF0aGJme1xiZXRhfX0mPSZcbWF0aGJmezB9XFxcXAotMlxtYXRoYmZ7WH1eVChcbWF0aGJme1l9LVxtYXRoYmZ7WFxiZXRhfSkmPSZcbWF0aGJmezB9XFxcXApcbWF0aGJme1h9XlRcbWF0aGJme1hcYmV0YX0mPSZcbWF0aGJme1h9XlRcbWF0aGJme1l9XFxcXApcaGF0e1xtYXRoYmZ7XGJldGF9fSY9JihcbWF0aGJme1h9XlRcbWF0aGJme1h9KV57LTF9XG1hdGhiZntYfV5UXG1hdGhiZntZfQpcZW5ke2FycmF5fQpcXQoKLS0tCgojIyMgR2VvbWV0cmljIEludGVycHJldGF0aW9uCgojIyMjIFRveSBkYXRhc2V0CgpXZSB3aWxsIGlsbHVzdHJhdGUgdGhpcyBvbiBhIHRveSBkYXRhc2V0CgpgYGB7cn0KbGlicmFyeSh0aWR5dmVyc2UpCmRhdGEgPC0gZGF0YS5mcmFtZSh4PTE6Myx5PWMoMSwyLDIpKQpkYXRhCmBgYAoKIyMjIyBNYXRyaXggZm9ybSBmb3IgdG95IGRhdGFzZXQgCgpXZSBjYW4gYWxzbyB3cml0ZSB0aGlzIGluIG1hdHJpeCBmb3JtCgokJApcbWF0aGJme1l9ID0gXG1hdGhiZntYfVxib2xkc3ltYm9se1xiZXRhfStcYm9sZHN5bWJvbHtcZXBzaWxvbn0KJCQKCndpdGgKCiQkClxtYXRoYmZ7WX09XGxlZnRbClxiZWdpbnthcnJheX17Y30KMVxcCjJcXAoyXFwKXGVuZHthcnJheX1ccmlnaHRdLApccXVhZApcbWF0aGJme1h9PSBcbGVmdFsKXGJlZ2lue2FycmF5fXtjY30KMSYxXFwKMSYyXFwKMSYzXFwKXGVuZHthcnJheX0KXHJpZ2h0XSwKXHF1YWQgXGJvbGRzeW1ib2x7XGJldGF9ID0gXGxlZnRbClxiZWdpbnthcnJheX17Y30KXGJldGFfMFxcClxiZXRhXzFcXApcZW5ke2FycmF5fQpccmlnaHRdClxxdWFkClx0ZXh0e2FuZH0KXHF1YWQKXGJvbGRzeW1ib2x7XGVwc2lsb259PQpcbGVmdFsKXGJlZ2lue2FycmF5fXtjfQpcZXBzaWxvbl8xXFwKXGVwc2lsb25fMlxcClxlcHNpbG9uXzMKXGVuZHthcnJheX0KXHJpZ2h0XQokJAoKIyMjIyBDbGFzc2ljYWwgaW50ZXJwcmV0YXRpb24KCgpNb2RlbCBmaXQgYW5kIHByZWRpY3Rpb25zIGJhc2VkIG9uIHRoZSB0b3kgZGF0YXNldCAKCmBgYHtyfQpsbTEgPC0gbG0oeX54LGRhdGEpCmRhdGEkeWhhdCA8LSBsbTEkZml0dGVkCgpkYXRhICU+JQogIGdncGxvdChhZXMoeCx5KSkgKwogIGdlb21fcG9pbnQoKSArCiAgeWxpbSgwLDQpICsKICB4bGltKDAsNCkgKwogIHN0YXRfc21vb3RoKG1ldGhvZCA9ICJsbSIsIGNvbG9yID0gInJlZCIsIGZ1bGxyYW5nZSA9IFRSVUUpICsKICBnZW9tX3BvaW50KGFlcyh4PXgsIHkgPXloYXQpLCBwY2ggPSAyLCBzaXplID0gMywgY29sb3IgPSAicmVkIikgKwogIGdlb21fc2VnbWVudChkYXRhID0gZGF0YSwgYWVzKHggPSB4LCB4ZW5kID0geCwgeSA9IHksIHllbmQgPSB5aGF0KSwgbHR5ID0gMiApCmBgYAoKCgojIyMgUHJvamVjdGlvbgoKVGhlcmUgaXMgYWxzbyBhbm90aGVyIHBpY3R1cmUgdG8gcmVncmVzc2lvbjoKCi0gSW5zdGVhZCBvZiBwbG90dGluZyBlYWNoIG9ic2VydmF0aW9uICRpPSAxIFxsZG90cyBuJCBhcyBhIGRhdGEtcG9pbnQgaW4gJFxtYXRoYmJ7Un1ecCQgd2l0aCBkaW1lbnNpb25zICQxIFxsZG90cyBwJCBmb3IgZXZlcnkgdmFyaWFibGUvZmVhdHVyZSB0aGF0IGlzIHJlY29yZGVkIGZvciBlYWNoIG9ic2VydmF0aW9uICAKCi0gV2UgY2FuIGFsc28gcGxvdCAkXG1hdGhiZntZfSQsICRcaGF0e1xtYXRoYmZ7WX19JCBhbmQgZWFjaCBjb2x1bW4gb2YgJFxtYXRoYmZ7WH0kOiAkXG1hdGhiZntYfV9qJCB3aXRoICRqPTEgXGxkb3RzIHAkIGFzIGEgdmVjdG9yIGluICRcbWF0aGJie1J9Xm4kIHdpdGggZGltZW5zaW9ucyAkMSBcbGRvdHMgbiQgZm9yIGV2ZXJ5IG9ic2VydmF0aW9uLiAgCgotIEluIHRoaXMgcmVwcmVzZW50YXRpb24gbGluZWFyIHJlZ3Jlc3Npb24gY2FuIGJlIGludGVycHJldGVkIGFzIGEgcHJvamVjdGlvbiBvZiB0aGUgdmVjdG9yICRcbWF0aGJme1l9JCBvbnRvIHRoZSBzdWJzcGFjZSBvZiAkXG1hdGhiYntSfV5uJCB0aGF0IGlzIHNwYW5uZWQgYnkgdGhlIHZlY3RvcnMgZm9yIHRoZSBwcmVkaWN0b3JzICRcbWF0aGJme1h9XzEgXGxkb3RzIFxtYXRoYmZ7WH1fcCQuICAKCi0gVGhlIHNwYWNlICRcbWF0aGJme1h9XzEgXGxkb3RzIFxtYXRoYmZ7WH1fcCQgaXMgYWxzbyByZWZlcnJlZCB0byBhcyB0aGUgY29sdW1uIHNwYWNlIG9mICRcbWF0aGJme1h9JCwgdGhlIHNwYWNlIHRoYXQgY29uc2lzdHMgb2YgYWxsIGxpbmVhciBjb21iaW5hdGlvbnMgb2YgdGhlIHZlY3RvcnMgb2YgdGhlIHByZWRpY3RvcnMgb3IgY29sdW1ucyAkXG1hdGhiZntYfV8xIFxsZG90cyBcbWF0aGJme1h9X3AkLiAgCgojIyMjIEludGVybWV6em86IFByb2plY3Rpb24gb2YgdmVjdG9yIG9uIFggIGFuZCBZIGF4aXMKCiQkClxtYXRoYmZ7ZX09XGxlZnRbXGJlZ2lue2FycmF5fXtjfSBlXzFcXGVfMlxlbmR7YXJyYXl9XHJpZ2h0XSwgXG1hdGhiZnt1fV8xID0gIFxsZWZ0W1xiZWdpbnthcnJheX17Y30gMVxcMFxlbmR7YXJyYXl9XHJpZ2h0XSwgXG1hdGhiZnt1fV8yID0gIFxsZWZ0W1xiZWdpbnthcnJheX17Y30gMFxcMVxlbmR7YXJyYXl9XHJpZ2h0XQokJAoKCmBgYHtyIGVjaG89RkFMU0V9CnBsb3RkYXRhIDwtIGRhdGEuZnJhbWUoZTE9MyxlMj0yKQpwbG90ZGF0YSAlPiUgZ2dwbG90KGFlcyh4PWUxLHk9ZTIpKSArCiAgZ2VvbV9wb2ludCgpICsgCiAgZ2VvbV9zZWdtZW50KGFlcyh4ID0gMywgeSA9IDIsIHhlbmQgPSAzLCB5ZW5kID0gMCksY29sb3I9Im9yYW5nZSIsbGluZXR5cGU9MixzaXplPTIpICsKZ2VvbV9zZWdtZW50KGFlcyh4ID0gMywgeSA9IDIsIHhlbmQgPSAwLCB5ZW5kID0gMiksY29sb3I9Im9yYW5nZSIsbGluZXR5cGU9MixzaXplPTIpICsKIGdlb21fc2VnbWVudChhZXMoeCA9IDAsIHkgPSAwLCB4ZW5kID0gMywgeWVuZCA9IDIpLAogICAgICAgICAgICAgICAgICBhcnJvdyA9IGFycm93KGxlbmd0aCA9IHVuaXQoMC41LCAiY20iKSksY29sb3I9InJlZCIsc2l6ZT0yKSArCiBnZW9tX3NlZ21lbnQoYWVzKHggPSAwLCB5ID0gMCwgeGVuZCA9IDMsIHllbmQgPSAwKSwKICAgICAgICAgICAgICAgICAgYXJyb3cgPSBhcnJvdyhsZW5ndGggPSB1bml0KDAuNSwgImNtIikpLGNvbG9yPSJvcmFuZ2UiLHNpemU9MikgKwogZ2VvbV9zZWdtZW50KGFlcyh4ID0gMCwgeSA9IDAsIHhlbmQgPSAwLCB5ZW5kID0gMiksCiAgICAgICAgICAgICAgICAgIGFycm93ID0gYXJyb3cobGVuZ3RoID0gdW5pdCgwLjUsICJjbSIpKSxjb2xvcj0ib3JhbmdlIixzaXplPTIpICArCiBnZW9tX3NlZ21lbnQoYWVzKHggPSAwLCB5ID0gMCwgeGVuZCA9IDAsIHllbmQgPSAxKSwKICAgICAgICAgICAgICAgICAgYXJyb3cgPSBhcnJvdyhsZW5ndGggPSB1bml0KDAuNSwgImNtIikpLHNpemU9MikgKwpnZW9tX3NlZ21lbnQoYWVzKHggPSAwLCB5ID0gMCwgeGVuZCA9IDEsIHllbmQgPSAwKSwKICAgICAgICAgICAgICAgICAgYXJyb3cgPSBhcnJvdyhsZW5ndGggPSB1bml0KDAuNSwgImNtIikpLHNpemU9MikgKwogZ2VvbV90ZXh0KGFlcyh4PTMuMDUseT0yLjIsbGFiZWw9ImU9KGUxLGUyKSIpLGNvbG9yPSJyZWQiLHNpemUgPSA3LGhqdXN0PSJsZWZ0IikgKyAKIGdlb21fdGV4dChhZXMoeD0zLjA1LHk9MCxsYWJlbD0iKGUxLDApIiksY29sb3I9Im9yYW5nZSIsc2l6ZSA9IDcsaGp1c3Q9ImxlZnQiLHZqdXN0PSJ0b3AiKSArCiBnZW9tX3RleHQoYWVzKHg9MCx5PTIuMixsYWJlbD0iKDAsZTIpIiksY29sb3I9Im9yYW5nZSIsc2l6ZSA9IDcsaGp1c3Q9ImxlZnQiKSArCiBnZW9tX3RleHQoYWVzKHg9MC4wNSx5PTEuMixsYWJlbD0idTI9KDAsMSkiKSxzaXplID0gNyxoanVzdD0ibGVmdCIpICsgCiAgIGdlb21fdGV4dChhZXMoeD0xLjA1LHk9MC4yLGxhYmVsPSJ1MT0oMSwwKSIpLHNpemUgPSA3LGhqdXN0PSJsZWZ0IikgICsKICAgY29vcmRfZml4ZWQoKSArCiAgeGxpbSgtLjUsNCkgKwogIHlsaW0oLS41LDIuNSkKYGBgCgoKCjEuIFByb2plY3Rpb24gb2YgZXJyb3Igb24geC1heGlzIAoKXGJlZ2lue2VxbmFycmF5Kn0KXG1hdGhiZnt1fV8xXlQgXG1hdGhiZntlfSAmPSYgXFZlcnQgXG1hdGhiZnt1fV8xXFZlcnRfMiAgXFZlcnQgXG1hdGhiZntlfV8xXFZlcnRfMiBcY29zIDxcbWF0aGJme3V9XzEsXG1hdGhiZntlfV8xPlxcCiY9JlxsZWZ0W1xiZWdpbnthcnJheX17Y2N9IDEmMFxlbmR7YXJyYXl9XHJpZ2h0XSBcbGVmdFtcYmVnaW57YXJyYXl9e2N9IGVfMVxcZV8yXGVuZHthcnJheX1ccmlnaHRdXFwgJj0mIDFcdGltZXMgZV8xICsgMCBcdGltZXMgZV8yIFxcCiY9JiBlXzFcXApcZW5ke2VxbmFycmF5Kn0KCjIuIFByb2plY3Rpb24gb2YgZXJyb3Igb24geS1heGlzIAoKXGJlZ2lue2VxbmFycmF5Kn0KXG1hdGhiZnt1fV8yXlQgXG1hdGhiZntlfSAmPSYgXGxlZnRbXGJlZ2lue2FycmF5fXtjY30gMCYxXGVuZHthcnJheX1ccmlnaHRdIFxsZWZ0W1xiZWdpbnthcnJheX17Y30gZV8xXFxlXzJcZW5ke2FycmF5fVxyaWdodF1cXCAmPSYgMFx0aW1lcyBlXzEgKyAxIFx0aW1lcyBlXzIgXFwKJj0mIGVfMgpcZW5ke2VxbmFycmF5Kn0KCjMuIFByb2plY3Rpb24gb2YgZXJyb3Igb24gaXRzZWxmIAoKClxiZWdpbntlcW5hcnJheSp9ClxtYXRoYmZ7ZX1eVCBcbWF0aGJme2V9ICY9JlxsZWZ0W1xiZWdpbnthcnJheX17Y2N9IGVfMSZlXzJcZW5ke2FycmF5fVxyaWdodF0gXGxlZnRbXGJlZ2lue2FycmF5fXtjfSBlXzFcXGVfMlxlbmR7YXJyYXl9XHJpZ2h0XVxcCiY9JmVfMV4yK2VfMl4yXFwKJj0mXFZlcnQgZSBcVmVydF4yXzIgXHJpZ2h0YXJyb3cgXHRleHR7IFB5dGhhZ29yZWFuIHRoZW9yZW19ClxlbmR7ZXFuYXJyYXkqfSAKCi0tLQoKIyMjIyBJbnRlcnByZXRhdGlvbiBvZiBsZWFzdCBzcXVhcmVzIGFzIGEgcHJvamVjdGlvbgoKRml0dGVkIHZhbHVlczoKCiQkClxiZWdpbnthcnJheX17bGNsfQpcaGF0e1xtYXRoYmZ7WX19ICY9JiBcbWF0aGJme1h9XGhhdHtcYm9sZHN5bWJvbHtcYmV0YX19XFwKJj0mIFxtYXRoYmZ7WH0gKFxtYXRoYmZ7WH1eVFxtYXRoYmZ7WH0pXnstMX1cbWF0aGJme1h9XlRcbWF0aGJme1l9XFwKJj0mIFxtYXRoYmZ7SFl9ClxlbmR7YXJyYXl9CiQkCndpdGggJFxtYXRoYmZ7SH0kIHRoZSBwcm9qZWN0aW9uIG1hdHJpeCBhbHNvIHJlZmVycmVkIHRvIGFzIHRoZSBoYXQgbWF0cml4LgoKCmBgYHtyfQpYIDwtIG1vZGVsLm1hdHJpeCh+eCxkYXRhKQpYCmBgYAoKYGBge3J9Clh0WCA8LSB0KFgpJSolWApYdFgKYGBgCgpgYGB7cn0KWHRYaW52IDwtIHNvbHZlKHQoWCklKiVYKQpYdFhpbnYKYGBgCgpgYGB7cn0KSCA8LSBYICUqJSBYdFhpbnYgJSolIHQoWCkKSApgYGAKCgpgYGB7cn0KWSA8LSBkYXRhJHkKWWhhdCA8LSBIJSolWQpZaGF0CmBgYAoKCi0gV2UgY2FuIGFsc28gaW50ZXJwcmV0IHRoZSBmaXQgYXMgdGhlIHByb2plY3Rpb24gb2YgdGhlICRuXHRpbWVzIDEkIHZlY3RvciAkXG1hdGhiZntZfSQgb24gdGhlIGNvbHVtbiBzcGFjZSBvZiB0aGUgbWF0cml4ICRcbWF0aGJme1h9JC4KCi0gU28gZWFjaCBjb2x1bW4gaW4gJFxtYXRoYmZ7WH0kIGlzIGFsc28gYW4gJG5cdGltZXMgMSQgdmVjdG9yLgoKLSBGb3IgdGhlIHRveSBleGFtcGxlIG49MyBhbmQgcD0yLgogIFRoZSBvdGhlciBwaWN0dXJlIHRvIGxpbmVhciByZWdyZXNzaW9uIGlzIHRvIGNvbnNpZGVyICRYXzAkLCAkWF8xJCBhbmQgJFkkIGFzIHZlY3RvcnMgaW4gdGhlIHNwYWNlIG9mIHRoZSBkYXRhICRcbWF0aGJie1J9Xm4kLCBoZXJlICRcbWF0aGJie1J9XjMkIGJlY2F1c2Ugd2UgaGF2ZSB0aHJlZSBkYXRhIHBvaW50cy4KU28gdGhlIGNvbHVtbiBzcGFjZSBvZiBYIGlzIGEgcGxhbmUgaW4gdGhlIHRocmVlIGRpbWVuc2lvbmFsIHNwYWNlLgoKXFsKXGhhdHtcbWF0aGJme1l9fSA9IFxtYXRoYmZ7WH0gKFxtYXRoYmZ7WH1eVFxtYXRoYmZ7WH0pXnstMX0gXG1hdGhiZntYfV5UIFxtYXRoYmZ7WX0KXF0KCjEuIFBsYW5lIHNwYW5uZWQgYnkgY29sdW1uIHNwYWNlOgoKCmBgYHtyfQpvcmlnaW5SbiA8LSBkYXRhLmZyYW1lKFgxPTAsWDI9MCxYMz0wKQpkYXRhJHgwIDwtIDEKZGF0YVJuIDwtIGRhdGEuZnJhbWUodChkYXRhKSkKCmxpYnJhcnkocGxvdGx5KQoKcDEgPC0gcGxvdF9seSgKICAgIG9yaWdpblJuLAogICAgeCA9IH4gWDEsCiAgICB5ID0gfiBYMiwKICAgIHo9IH4gWDMsIG5hbWU9Im9yaWdpbiIpICU+JQogIGFkZF9tYXJrZXJzKHR5cGU9InNjYXR0ZXIzZCIpICU+JQogIGxheW91dCgKICAgIHNjZW5lID0gbGlzdCgKICAgICAgYXNwZWN0bW9kZT0iY3ViZSIsCiAgICAgIHhheGlzID0gbGlzdChyYW5nZT1jKC00LDQpKSwgeWF4aXMgPSBsaXN0KHJhbmdlPWMoLTQsNCkpLCB6YXhpcyA9IGxpc3QocmFuZ2U9YygtNCw0KSkKICAgICAgKQogICAgKQpwMSA8LSBwMSAlPiUKICBhZGRfdHJhY2UoCiAgICB4ID0gYygwLDEpLAogICAgeSA9IGMoMCwwKSwKICAgIHogPSBjKDAsMCksCiAgICBtb2RlID0gImxpbmVzIiwKICAgIGxpbmUgPSBsaXN0KHdpZHRoID0gNSwgY29sb3IgPSAiZ3JleSIpLAogICAgdHlwZT0ic2NhdHRlcjNkIiwKICAgIG5hbWUgPSAib2JzMSIpICU+JQogIGFkZF90cmFjZSgKICAgIHggPSBjKDAsMCksCiAgICB5ID0gYygwLDEpLAogICAgeiA9IGMoMCwwKSwKICAgIG1vZGUgPSAibGluZXMiLAogICAgbGluZSA9IGxpc3Qod2lkdGggPSA1LCBjb2xvciA9ICJncmV5IiksCiAgICB0eXBlPSJzY2F0dGVyM2QiLAogICAgbmFtZSA9ICJvYnMyIikgJT4lCiAgYWRkX3RyYWNlKAogICAgeCA9IGMoMCwwKSwKICAgIHkgPSBjKDAsMCksCiAgICB6ID0gYygwLDEpLAogICAgbW9kZSA9ICJsaW5lcyIsCiAgICBsaW5lID0gbGlzdCh3aWR0aCA9IDUsIGNvbG9yID0gImdyZXkiKSwKICAgIHR5cGU9InNjYXR0ZXIzZCIsCiAgICBuYW1lID0gIm9iczMiKSAlPiUKICBhZGRfdHJhY2UoCiAgICB4ID0gYygwLDEpLAogICAgeSA9IGMoMCwxKSwKICAgIHogPSBjKDAsMSksCiAgICBtb2RlID0gImxpbmVzIiwKICAgIGxpbmUgPSBsaXN0KHdpZHRoID0gNSwgY29sb3IgPSAiYmxhY2siKSwKICAgIHR5cGU9InNjYXR0ZXIzZCIsCiAgICBuYW1lID0gIlgxIikgJT4lCiAgICBhZGRfdHJhY2UoCiAgICB4ID0gYygwLDEpLAogICAgeSA9IGMoMCwyKSwKICAgIHogPSBjKDAsMyksCiAgICBtb2RlID0gImxpbmVzIiwKICAgIGxpbmUgPSBsaXN0KHdpZHRoID0gNSwgY29sb3IgPSAiYmxhY2siKSwKICAgIHR5cGU9InNjYXR0ZXIzZCIsCiAgICBuYW1lID0gIlgyIikKcDEKYGBgCgoKMi4gVmVjdG9yIG9mIFk6CgpBY3R1YWwgdmFsdWVzIG9mICRcbWF0aGJme1l9JDoKCmBgYHtyfQpkYXRhJHkKYGBgCgpcWwpcbWF0aGJme1l9PVxsZWZ0W1xiZWdpbnthcnJheX17Y30KYHIgZGF0YSR5WzFdYCBcXApgciBkYXRhJHlbMl1gIFxcCmByIGRhdGEkeVszXWAKXGVuZHthcnJheX1ccmlnaHRdClxdCgpgYGB7cn0KcDIgPC0gcDEgJT4lCiAgYWRkX3RyYWNlKAogICAgeCA9IGMoMCxZWzFdKSwKICAgIHkgPSBjKDAsWVsyXSksCiAgICB6ID0gYygwLFlbM10pLAogICAgbW9kZSA9ICJsaW5lcyIsCiAgICBsaW5lID0gbGlzdCh3aWR0aCA9IDUsIGNvbG9yID0gInJlZCIpLAogICAgdHlwZT0ic2NhdHRlcjNkIiwKICAgIG5hbWUgPSAiWSIpIApwMgpgYGAKCjMuIFByb2plY3Rpb24gb2YgWSBvbnRvIGNvbHVtbiBzcGFjZQoKQWN0dWFsIHZhbHVlcyBvZiBmaXR0ZWQgdmFsdWVzICRcbWF0aGJme1xoYXR7WX19JDoKCmBgYHtyfQpkYXRhJHloYXQKYGBgCgpcWwpcbWF0aGJme1l9PVxsZWZ0W1xiZWdpbnthcnJheX17Y30KYHIgZGF0YSR5aGF0WzFdYCBcXApgciBkYXRhJHloYXRbMl1gIFxcCmByIGRhdGEkeWhhdFszXWAKXGVuZHthcnJheX1ccmlnaHRdClxdCgpgYGB7cn0KcDIgPC0gcDIgJT4lCiAgYWRkX3RyYWNlKAogICAgeCA9IGMoMCxZaGF0WzFdKSwKICAgIHkgPSBjKDAsWWhhdFsyXSksCiAgICB6ID0gYygwLFloYXRbM10pLAogICAgbW9kZSA9ICJsaW5lcyIsCiAgICBsaW5lID0gbGlzdCh3aWR0aCA9IDUsIGNvbG9yID0gIm9yYW5nZSIpLAogICAgdHlwZT0ic2NhdHRlcjNkIiwKICAgIG5hbWU9IlloYXQiKSAlPiUgCiAgICBhZGRfdHJhY2UoCiAgICB4ID0gYyhZWzFdLFloYXRbMV0pLAogICAgeSA9IGMoWVsyXSxZaGF0WzJdKSwKICAgIHogPSBjKFlbM10sWWhhdFszXSksCiAgICBtb2RlID0gImxpbmVzIiwKICAgIGxpbmUgPSBsaXN0KHdpZHRoID0gNSwgY29sb3IgPSAicmVkIiwgZGFzaD0iZGFzaCIpLAogICAgdHlwZT0ic2NhdHRlcjNkIiwKICAgIG5hbWU9IlkgLT4gWWhhdCIKICAgICkKcDIKYGBgCgokXG1hdGhiZntZfSQgaXMgcHJvamVjdGVkIGluIHRoZSBjb2x1bW4gc3BhY2Ugb2YgJFxtYXRoYmZ7WH0kISBzcGFubmVkIGJ5IHRoZSBjb2x1bW5zLgoKIyMjIyBIb3cgZG9lcyB0aGlzIHByb2plY3Rpb24gd29ya3M/IAoKJCQKXGJlZ2lue2FycmF5fXtsY2x9ClxoYXR7XG1hdGhiZntZfX0gJj0mIFxtYXRoYmZ7WH0gKFxtYXRoYmZ7WH1eVFxtYXRoYmZ7WH0pXnstMX1cbWF0aGJme1h9XlRcbWF0aGJme1l9XFwKJj0mIFxtYXRoYmZ7WH0oXG1hdGhiZntYfV5UXG1hdGhiZntYfSleey0xLzJ9KFxtYXRoYmZ7WH1eVFxtYXRoYmZ7WH0pXnstMS8yfVxtYXRoYmZ7WH1eVFxtYXRoYmZ7WX1cXAomPSYgXG1hdGhiZntVfVxtYXRoYmZ7VX1eVFxtYXRoYmZ7WX0KXGVuZHthcnJheX0KJCQKCgotICRcbWF0aGJme1V9JCBpcyBhIG5ldyBvcnRob25vcm1hbCBiYXNpcyBpbiAkXG1hdGhiYntSfV4yJCwgYSBzdWJzcGFjZSBvZiAkXG1hdGhiYntSfV4zJAoKLSBUaGUgc3BhY2Ugc3Bhbm5lZCBieSBVIGFuZCBYIGlzIHRoZSBjb2x1bW4gc3BhY2Ugb2YgWCwgZS5nLiBpdCBjb250YWlucyBhbGwgcG9zc2libGUgbGluZWFyIGNvbWJpbmFudGlvbnMgb2YgWC4KJFxtYXRoYmZ7VX1edFxtYXRoYmZ7WX0kIGlzIHRoZSBwcm9qZWN0aW9uIG9mIFkgb24gdGhpcyBuZXcgb3J0aG9ub3JtYWwgYmFzaXMKCmBgYHtyfQplaWdlblh0WCA8LSBlaWdlbihYdFgpClh0WGludlNxcnQgPC0gZWlnZW5YdFgkdmVjdG9ycyAlKiVkaWFnKDEvZWlnZW5YdFgkdmFsdWVzXi41KSUqJXQoZWlnZW5YdFgkdmVjdG9ycykKVSA8LSBYICUqJSBYdFhpbnZTcXJ0CmBgYAoKCi0gJFxtYXRoYmZ7VX0kIG9ydGhvbm9ybWFsIGJhc2lzCgpgYGB7cn0KVQp0KFUpJSolVQpgYGAKCi0gJFxtYXRoYmZ7VVV9XlQkIGVxdWFscyBwcm9qZWN0aW9uIG1hdHJpeCAKCmBgYHtyfQpVJSoldChVKQpICmBgYAoKCmBgYHtyfQpwMyA8LSBwMSAlPiUKICBhZGRfdHJhY2UoCiAgICB4ID0gYygwLFVbMSwxXSksCiAgICB5ID0gYygwLFVbMiwxXSksCiAgICB6ID0gYygwLFVbMywxXSksCiAgICBtb2RlID0gImxpbmVzIiwKICAgIGxpbmUgPSBsaXN0KHdpZHRoID0gNSwgY29sb3IgPSAiYmx1ZSIpLAogICAgdHlwZT0ic2NhdHRlcjNkIiwKICAgIG5hbWUgPSAiVTEiKSAlPiUKICBhZGRfdHJhY2UoCiAgICB4ID0gYygwLFVbMSwyXSksCiAgICB5ID0gYygwLFVbMiwyXSksCiAgICB6ID0gYygwLFVbMywyXSksCiAgICBtb2RlID0gImxpbmVzIiwKICAgIGxpbmUgPSBsaXN0KHdpZHRoID0gNSwgY29sb3IgPSAiYmx1ZSIpLAogICAgdHlwZT0ic2NhdHRlcjNkIiwKICAgIG5hbWUgPSAiVTIiKQoKcDMKYGBgCgoKLSAkXG1hdGhiZntVfV5UXG1hdGhiZntZfSQgaXMgdGhlIHByb2plY3Rpb24gb2YgJFxtYXRoYmZ7WX0kIGluIHRoZSBzcGFjZSBzcGFubmVkIGJ5ICRcbWF0aGJme1V9JC4KLSBJbmRlZWQgJFxtYXRoYmZ7VX1fMV5UXG1hdGhiZntZfSQKCmBgYHtyfQpwNCA8LSBwMyAlPiUKICBhZGRfdHJhY2UoCiAgICB4ID0gYygwLFlbMV0pLAogICAgeSA9IGMoMCxZWzJdKSwKICAgIHogPSBjKDAsWVszXSksCiAgICBtb2RlID0gImxpbmVzIiwKICAgIGxpbmUgPSBsaXN0KHdpZHRoID0gNSwgY29sb3IgPSAicmVkIiksCiAgICB0eXBlPSJzY2F0dGVyM2QiLAogICAgbmFtZSA9ICJZIikgJT4lCiAgYWRkX3RyYWNlKAogICAgeCA9IGMoMCxVWzEsMV0qKFVbLDFdJSolWSkpLAogICAgeSA9IGMoMCxVWzIsMV0qKFVbLDFdJSolWSkpLAogICAgeiA9IGMoMCxVWzMsMV0qKFVbLDFdJSolWSkpLAogICAgbW9kZSA9ICJsaW5lcyIsCiAgICBsaW5lID0gbGlzdCh3aWR0aCA9IDUsIGNvbG9yID0gInJlZCIsZGFzaD0iZGFzaCIpLAogICAgdHlwZT0ic2NhdHRlcjNkIiwKICAgIG5hbWU9IlkgLT4gVTEiKSAlPiUgYWRkX3RyYWNlKAogICAgeCA9IGMoWVsxXSxVWzEsMV0qKFVbLDFdJSolWSkpLAogICAgeSA9IGMoWVsyXSxVWzIsMV0qKFVbLDFdJSolWSkpLAogICAgeiA9IGMoWVszXSxVWzMsMV0qKFVbLDFdJSolWSkpLAogICAgbW9kZSA9ICJsaW5lcyIsCiAgICBsaW5lID0gbGlzdCh3aWR0aCA9IDUsIGNvbG9yID0gInJlZCIsIGRhc2g9ImRhc2giKSwKICAgIHR5cGU9InNjYXR0ZXIzZCIsCiAgICBuYW1lPSJZIC0+IFUxIikKcDQKYGBgCgotIGFuZCAkXG1hdGhiZntVfV8yXlRcbWF0aGJme1l9JApgYGB7cn0KcDUgPC0gcDQgJT4lCiAgYWRkX3RyYWNlKAogICAgeCA9IGMoMCxVWzEsMl0qKFVbLDJdJSolWSkpLAogICAgeSA9IGMoMCxVWzIsMl0qKFVbLDJdJSolWSkpLAogICAgeiA9IGMoMCxVWzMsMl0qKFVbLDJdJSolWSkpLAogICAgbW9kZSA9ICJsaW5lcyIsCiAgICBsaW5lID0gbGlzdCh3aWR0aCA9IDUsIGNvbG9yID0gInJlZCIsZGFzaD0iZGFzaCIpLAogICAgdHlwZT0ic2NhdHRlcjNkIiwKICAgIG5hbWU9IlkgLT4gVTIiKSAlPiUgYWRkX3RyYWNlKAogICAgeCA9IGMoWVsxXSxVWzEsMl0qKFVbLDJdJSolWSkpLAogICAgeSA9IGMoWVsyXSxVWzIsMl0qKFVbLDJdJSolWSkpLAogICAgeiA9IGMoWVszXSxVWzMsMl0qKFVbLDJdJSolWSkpLAogICAgbW9kZSA9ICJsaW5lcyIsCiAgICBsaW5lID0gbGlzdCh3aWR0aCA9IDUsIGNvbG9yID0gInJlZCIsIGRhc2g9ImRhc2giKSwKICAgIHR5cGU9InNjYXR0ZXIzZCIsCiAgICBuYW1lPSJZIC0+IFUyIikKcDUKYGBgCgotICRcaGF0e1xtYXRoYmZ7WX19JCBpcyB0aGUgcmVzdWx0aW5nIHZlY3RvciB0aGF0IGxpZXMgaW4gdGhlIHBsYW5lIHNwYW5uZWQgYnkgJFxtYXRoYmZ7VX1fMSQgYW5kICRcbWF0aGJme1V9XzIkIGFuZCB0aHVzIGFsc28gaW4gdGhlIGNvbHVtbiBzcGFjZSBvZiAkXG1hdGhiZntYfSQuCgpgYGB7cn0KcDYgPC0gcDUgJT4lCiAgYWRkX3RyYWNlKAogICAgeCA9IGMoMCxZaGF0WzFdKSwKICAgIHkgPSBjKDAsWWhhdFsyXSksCiAgICB6ID0gYygwLFloYXRbM10pLAogICAgbW9kZSA9ICJsaW5lcyIsCiAgICBsaW5lID0gbGlzdCh3aWR0aCA9IDUsIGNvbG9yID0gIm9yYW5nZSIpLAogICAgdHlwZT0ic2NhdHRlcjNkIiwKICAgIG5hbWUgPSAiWWhhdCIpICU+JQogIGFkZF90cmFjZSgKICAgIHggPSBjKFlbMV0sWWhhdFsxXSksCiAgICB5ID0gYyhZWzJdLFloYXRbMl0pLAogICAgeiA9IGMoWVszXSxZaGF0WzNdKSwKICAgIG1vZGUgPSAibGluZXMiLAogICAgbGluZSA9IGxpc3Qod2lkdGggPSA1LCBjb2xvciA9ICJtYXJvb24yIiksCiAgICB0eXBlPSJzY2F0dGVyM2QiLAogICAgbmFtZSA9ICJlIikgJT4lCiAgYWRkX3RyYWNlKAogICAgeCA9IGMoVVsxLDFdKihVWywxXSUqJVkpLFloYXRbMV0pLAogICAgeSA9IGMoVVsyLDFdKihVWywxXSUqJVkpLFloYXRbMl0pLAogICAgeiA9IGMoVVszLDFdKihVWywxXSUqJVkpLFloYXRbM10pLAogICAgbW9kZSA9ICJsaW5lcyIsCiAgICBsaW5lID0gbGlzdCh3aWR0aCA9IDUsIGNvbG9yID0gIm9yYW5nZSIsIGRhc2g9ImRhc2giKSwKICAgIHR5cGU9InNjYXR0ZXIzZCIsCiAgICBuYW1lID0gIlkgLT4gVSIpICAlPiUKICBhZGRfdHJhY2UoCiAgICB4ID0gYyhVWzEsMl0qKFVbLDJdJSolWSksWWhhdFsxXSksCiAgICB5ID0gYyhVWzIsMl0qKFVbLDJdJSolWSksWWhhdFsyXSksCiAgICB6ID0gYyhVWzMsMl0qKFVbLDJdJSolWSksWWhhdFszXSksCiAgICBtb2RlID0gImxpbmVzIiwKICAgIGxpbmUgPSBsaXN0KHdpZHRoID0gNSwgY29sb3IgPSAib3JhbmdlIiwgZGFzaD0iZGFzaCIpLAogICAgdHlwZT0ic2NhdHRlcjNkIiwKICAgIG5hbWUgPSAiWSAtPiBVIikKcDYKYGBgCgojIyMgRXJyb3IgCgpOb3RlLCB0aGF0IGl0IGlzIGFsc28gY2xlYXIgZnJvbSB0aGUgZXF1YXRpb24gaW4gdGhlIGRlcml2YXRpb24gb2YgdGhlIGxlYXN0IHNxdWFyZXMgc29sdXRpb24gdGhhdCB0aGUgcmVzaWR1YWwgaXMgb3J0aG9nb25hbCBvbiB0aGUgY29sdW1uIHNwYWNlOgoKXFsKIC0yIFxtYXRoYmZ7WH1eVChcbWF0aGJme1l9LVxtYXRoYmZ7WH1cYm9sZHN5bWJvbHtcYmV0YX0pID0gMApcXQoKCi0tLQoKIyMgVmFyaWFuY2UgRXN0aW1hdG9yPwpcWwpcYmVnaW57YXJyYXl9e2NjbH0KXGhhdHtcYm9sZHN5bWJvbHtcU2lnbWF9fV97XGhhdHtcbWF0aGJme1xiZXRhfX19CiY9Jlx0ZXh0e3Zhcn1cbGVmdFsoXG1hdGhiZntYfV5UXG1hdGhiZntYfSleey0xfVxtYXRoYmZ7WH1eVFxtYXRoYmZ7WX1ccmlnaHRdXFxcXAomPSYoXG1hdGhiZntYfV5UXG1hdGhiZntYfSleey0xfVxtYXRoYmZ7WH1eVFx0ZXh0e3Zhcn1cbGVmdFtcbWF0aGJme1l9XHJpZ2h0XVxtYXRoYmZ7WH0oXG1hdGhiZntYfV5UXG1hdGhiZntYfSleey0xfVxcXFwKJj0mKFxtYXRoYmZ7WH1eVFxtYXRoYmZ7WH0pXnstMX1cbWF0aGJme1h9XlQoXG1hdGhiZntJfVxzaWdtYV4yKVxtYXRoYmZ7WH0oXG1hdGhiZntYfV5UXG1hdGhiZntYfSleey0xfQpcXFxcCiY9JihcbWF0aGJme1h9XlRcbWF0aGJme1h9KV57LTF9XG1hdGhiZntYfV5UXG1hdGhiZntJfVxxdWFkXG1hdGhiZntYfShcbWF0aGJme1h9XlRcbWF0aGJme1h9KV57LTF9XHNpZ21hXjJcXFxcCiVcaGF0e1xib2xkbWF0aHtcU2lnbWF9fV97XGhhdHtcbWF0aGJme1xiZXRhfX19Jj0mKFxtYXRoYmZ7WH1eVFxtYXRoYmZ7WH0pXnstMX1cbWF0aGJme1h9XlRcdmFyXGxlZnRbXG1hdGhiZntZfVxyaWdodF0oXG1hdGhiZntYfV5UXG1hdGhiZntYfSleey0xfVxtYXRoYmZ7WH1cXAomPSYoXG1hdGhiZntYfV5UXG1hdGhiZntYfSleey0xfVxtYXRoYmZ7WH1eVFxtYXRoYmZ7WH0oXG1hdGhiZntYfV5UXG1hdGhiZntYfSleey0xfVxzaWdtYV4yXFxcXAomPSYoXG1hdGhiZntYfV5UXG1hdGhiZntYfSleey0xfVxzaWdtYV4yClxlbmR7YXJyYXl9ClxdCgotLS0KCiMjIENvbnRyYXN0cwoKSHlwb3RoZXNlcyBvZnRlbiBpbnZvbHZlIGxpbmVhciBjb21iaW5hdGlvbnMgb2YgdGhlIG1vZGVsIHBhcmFtZXRlcnMhCgplLmcuCgotICRIXzA6IFxsb2dfMntGQ31fe2czbjEtZzFuMX09IFxiZXRhX3tnM30gKyBcaGF0XGJldGFfe2czbjF9PTAkICRccmlnaHRhcnJvdyQgImdyYWRlMytncmFkZTM6bm9kZTEgPSAwIgoKLSBMZXQgXFsKXGJvbGRzeW1ib2x7XGJldGF9ID0gXGxlZnRbClxiZWdpbnthcnJheX17Y30KXGJldGFfezB9XFwKXGJldGFfe2czfVxcClxiZXRhX3tuMX1cXApcYmV0YV97ZzM6bjF9ClxlbmR7YXJyYXl9ClxyaWdodF1cXQotIHdlIGNhbiB3cml0ZSB0aGF0IGNvbnRyYXN0IHVzaW5nIGEgY29udHJhc3QgbWF0cml4OgpcWwpcbWF0aGJme0x9PVxsZWZ0W1xiZWdpbnthcnJheX17Y30wXFwxXFwwXFwxXGVuZHthcnJheX1ccmlnaHRdIFxyaWdodGFycm93IFxtYXRoYmZ7TH1eVFxib2xkc3ltYm9se1xiZXRhfSBcXQoKLSBUaGVuIHRoZSB2YXJpYW5jZSBiZWNvbWVzOgpcWwpcdGV4dHt2YXJ9X3tcbWF0aGJme0x9XlRcYm9sZHN5bWJvbHtcaGF0XGJldGF9fT0gXG1hdGhiZntMfV5UIFxib2xkc3ltYm9se1xTaWdtYX1fe1xib2xkc3ltYm9se1xoYXRcYmV0YX19XG1hdGhiZntMfQpcXQoKCi0tLQoKIyBIb21ld29yazogQWRvcHQgdGhlIGdlbmUgYW5hbHlzaXMgb24gbG9nIHNjYWxlIGluIG1hdHJpeCBmb3JtIQoKMS4gU3R1ZHkgdGhlIHNvbHV0aW9uIG9mIHRoZSBleGVyY2lzZSB0byB1bmRlcnN0YW5kIHRoZSBhbmFseXNpcyBpbiBSCgoKMi4gQ2FsY3VsYXRlCi0gbW9kZWwgcGFyYW1ldGVycyBhbmQgY29udHJhc3RzIG9mIGludGVyZXN0Ci0gc3RhbmRhcmQgZXJyb3JzLCBzdGFuZGFyZCBlcnJvcnMgb24gY29udHJhc3RzCi0gdC10ZXN0IHN0YXRpc3RpY3Mgb24gdGhlIG1vZGVsIHBhcmFtZXRlcnMgYW5kIGNvbnRyYXN0cyBvZiBpbnRlcmVzdAoKMy4gQ29tcGFyZSB5b3VyIHJlc3VsdHMgd2l0aCB0aGUgb3V0cHV0IG9mIHRoZSBsbSguKSBmdW5jdGlvbgoKCi0tLQoKIyMgSW5zcGlyYXRpb24KClRpcDogZGV0YWlscyBvbiB0aGUgaW1wbGVtZW50YXRpb24gY2FuIGJlIGZvdW5kIGluIHRoZSBib29rIG9mIEZhcmF3YXkgKGNoYXB0ZXIgMikuIGh0dHBzOi8vcGVvcGxlLmJhdGguYWMudWsvampmMjMvYm9vay8KCi0gRGVzaWduIG1hdHJpeAoKYGBge3J9ClggPC0gbW9kZWwubWF0cml4KH5ncmFkZSpub2RlLGRhdGE9Z2VuZSkKYGBgCgotIFRyYW5zcG9zZSBvZiBhIG1hdHJpeDogdXNlIGZ1bmN0aW9uIHQoLikKCmBgYHtyfQp0KFgpCmBgYAoKLSBNYXRyaXggcHJvZHVjdCAlXColIG9wZXJhdG9yCgpgYGB7cn0KdChYKSUqJVgKYGBgCgotIERlZ3JlZXMgb2YgZnJlZWRvbSBvZiBhIG1vZGVsPwoKJCQgZGYgPSAgbi1wJCQKCmBgYHtyfQpzdW1tYXJ5KGxtMSkKZGZSZXMgPC0gKG5yb3coWCktbmNvbChYKSkKZGZSZXMKYGBgCgotIFZhcmlhbmNlIGVzdGltYXRvcjogTVNFCgokJApcaGF0IFxzaWdtYV4yID0gXGZyYWN7XHN1bVxsaW1pdHNfe2k9MX1eblxlcHNpbG9uX2leMn17bi1wfQokJAoKCi0gSW52ZXJ0IG1hdHJpeDogdXNlIGZ1bmN0aW9uIHNvbHZlKC4pCgotIERpYWdvbmFsIGVsZW1lbnRzIG9mIGEgbWF0cml4OiB1c2UgZnVuY3Rpb24gZGlhZyguKQoKYGBge3J9CnQoWCklKiVYCmRpYWcodChYKSUqJVgpCmBgYAo=