Creative Commons License

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

1 Randomized complete block designs

\[\sigma^2= \sigma^2_{bio}+\sigma^2_\text{lab} +\sigma^2_\text{extraction} + \sigma^2_\text{run} + \ldots\]

  • Biologisch: fluctuaties in proteïne niveau tussen muizen, fluctaties in proteïne concentratie tussen cellen, …
  • Technisch: kooi-effect, lab effect, week effect, plasma extractie, MS-run, …

2 Nature methods: Points of significance - Blocking

https://www.nature.com/articles/nmeth.3005.pdf

  • Oneway anova is een speciaal geval van een “completely randomized design” (CRD):

    • experimentele eenheden zijn at random getrokken uit de populatie.
    • behandelingen worden at random toegewezen aan de experimentele eenheden
    • Elke experimentele eenheid krijgt 1 behandeling en wordt 1 keer gemeten.
  • In een blokdesign zijn de blokken blokken die at random worden gesampled uit de populatie van alle mogelijke blokken.

    • De “randomized complete block design” (RCB) beperkt de randomisatie: de behandelingen worden binnen de blokken gerandomiseerd.
    • het kan niet als een one-way anova worden geanalyseerd.
    • Een gepaard design is het meest eenvoudige RCB: met blokgrootte 2.
    • Binnen block effecten kunnen worden geschat met de lm functie in R.

3 Muis voorbeeld

3.1 Intro

Duguet et al. (2017) MCP 16(8):1416-1432. doi: 10.1074/mcp.m116.062745

  • Alle behandelingen (hier celtypes) van interesse zijn binnen blok!
  • We kunnen het effect schatten van de behandeling binnen blok!

We focussen op één proteïne

  • De gemeten intensiteiten zijn reeds log2 getransformeerd. Verschillen in de intensiteiten kunnen dus als log2 FC (fold changes) worden geïnterpreteerd.
  • P16045 or Galectin-1.
  • Functie: “Lectin that binds beta-galactoside and a wide array of complex carbohydmousees. Plays a role in regulating apoptosis, cell proliferation and cell differentiation. Inhibits CD45 protein phosphatase activity and therefore the dephosphorylation of Lyn kinase. Strong inducer of T-cell apoptosis.” (source: uniprot)

3.2 Data Exploratie

mouse <- read_tsv("https://raw.githubusercontent.com/statOmics/PSLS21/data/mouseP16045.txt")
## Rows: 14 Columns: 3
## ── Column specification ────────────────────────────────────────────────────────
## Delimiter: "\t"
## chr (2): celltype, mouse
## dbl (1): intensity
## 
## ℹ Use `spec()` to retrieve the full column specification for this data.
## ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
mouse
mouse %>% 
  ggplot(aes(x = mouse, y= intensity, col=celltype)) +
  geom_point()

mouse %>%  
    ggplot(aes(x = celltype, y= intensity)) +
    geom_boxplot(aes(col=celltype),outlier.shape = NA) +
    geom_line(aes(group = mouse)) +
    geom_point(aes(col=celltype))

De plots tonen evidentie voor

  • opregulatie van proteïne expressie in regulatorische T-cells en
  • veel variabiliteit in de expressie tussen de proefdieren!

3.3 Gepaarde analyse

Het is een gepaard design, het meest eenvoudige RCB.

In de H5 van de basiscursus statistiek zouden we de data analyseren door gebruikt te maken van het verschil in expressie tussen de celtypes.

mouseWide <- mouse %>% 
  spread(celltype,intensity) %>%
  mutate(delta = Treg - Tcon)
mouseWide

3.3.1 Data exploratie

  • Boxplot van de verschillen
mouseWide %>% 
  ggplot(aes(x="",y=delta)) +
  geom_boxplot(outlier.shape = NA) + 
  geom_jitter()

  • Summary statistics
deltaSum <- mouseWide %>% 
  summarize_at("delta", 
               list(
                    mean = ~ mean(.,na.rm = TRUE),
                    sd = ~ sd(.,na.rm = TRUE),
                    n = ~ is.na(.) %>% `!` %>% sum
                   )
               ) %>% 
  mutate(se = sd/sqrt(n))
deltaSum

Merk op, dat de intensiteitsdata niet onafhankelijk zijn omdat we de expressie hebben gemeten in twee celtypes van hetzelfde proefdier.

  • Covariantie en correlatie van de expressie in beide celtypes
cor(mouseWide[,c("Tcon","Treg")])
##         Tcon    Treg
## Tcon 1.00000 0.93874
## Treg 0.93874 1.00000
var(mouseWide[,c("Tcon","Treg")])
##           Tcon      Treg
## Tcon 0.6101531 0.7245316
## Treg 0.7245316 0.9763042
var(mouseWide[,c("Tcon","Treg")]) %>% 
  diag %>% 
  sqrt
##      Tcon      Treg 
## 0.7811230 0.9880811
  • Er is inderdaad een grote correlatie in de expressie voor het proteïne in conventionele and regulatorische T-cellen.

  • Standarddeviatie van verschil?

\[ \begin{array}{lcl} \text{sd}_{ax_r + bx_c} &=& \sqrt{a^2\hat \sigma_r^2 + b^2 \hat \sigma_c^2 + 2ab \hat\sigma_{r,c}} \\ \text{sd}_{x_r - x_c} &=&\sqrt{1^2\hat \sigma_r^2 + (-1)^2 \hat \sigma_c^2 + 2 \times 1 \times -1 \times \hat\sigma_{r,c}}\\ &=&\sqrt{\hat \sigma_r^2 + \hat \sigma_c^2 - 2 \times \hat\sigma_{r,c}} \end{array} \]

sdDelta2 <- (c(-1,1) %*% var(mouseWide[,c("Tcon","Treg")]) %*% c(-1,1)) %>%
  sqrt
sdDelta2
##           [,1]
## [1,] 0.3706672
seDeltaBar <- sdDelta2 / sqrt(deltaSum$n)
seDeltaBar
##          [,1]
## [1,] 0.140099
deltaSum
  • De standarddeviatie van het verschil is veel kleiner door de sterke correlatie in expressie in beide celtypes.
  • Merk op, dat het gepaarde design ons toelaat om de log2 FC te berekenen tussen beide celtypes voor elk proefdier.

3.4 RCB analyse

We kunnen de data ook analyseren met een lineair model met

  • een hoofdeffect voor celtype en
  • een hoofdeffect voor de blokfactor muis

Omdat we de expressie in elk celtype hebben gemeten kunnen we dus het gemiddelde effect schatten voor elke muis.

lmRCB <- lm(intensity ~ celltype + mouse, mouse)
plot(lmRCB, which = c(1,2,3))

Als je twijfelt of voldaan is aan de aannames van het model is het nuttig om gebruikt te maken van simulaties waarbij we data simuleren uit een model waar alle aannames zijn voldaan.

design <-  model.matrix(intensity ~ celltype + mouse, mouse) 
sigmaMouse <- sqrt(car::Anova(lmRCB, type = "III")["mouse","Sum Sq"]/car::Anova(lmRCB, type = "III")["mouse","Df"]) 
betas <- lmRCB$coefficients
nMouse <- mouse$mouse %>% unique %>% length

par(mfrow=c(3,3))
for (i in 1:9)
{
    mouseEffect <- rnorm(nMouse, sd = sigmaMouse)
    betasMouse <- mouseEffect[-1]-mouseEffect[1]
    betas[-c(1:2)] <- betasMouse 
    ysim <- design %*% betas  + rnorm(nrow(design),sd=sigma(lmRCB))
    plot(lm(ysim ~ -1 + design), which = 1) 
}

De deviaties die we in de residuplot zien voor ons experiment zijn vergelijkbaar met afwijkingen die we kunnen observeren uit gesimuleerde data uit een gelijkaardige proef waarbij aan alle aannames van het model is voldaan.

3.4.1 Inferentie

anovaRCB <- car::Anova(lmRCB, type = "III")
summary(lmRCB)
## 
## Call:
## lm(formula = intensity ~ celltype + mouse, data = mouse)
## 
## Residuals:
##     Min      1Q  Median      3Q     Max 
## -0.2356 -0.1387  0.0000  0.1387  0.2356 
## 
## Coefficients:
##              Estimate Std. Error t value Pr(>|t|)    
## (Intercept)    9.3671     0.1981  47.277 6.00e-09 ***
## celltypeTreg   1.2029     0.1401   8.586 0.000137 ***
## mousem2        0.5055     0.2621   1.929 0.102036    
## mousem3        1.0255     0.2621   3.913 0.007869 ** 
## mousem4        0.8545     0.2621   3.260 0.017245 *  
## mousem5        0.7880     0.2621   3.006 0.023809 *  
## mousem6        2.1055     0.2621   8.033 0.000199 ***
## mousem7        2.4475     0.2621   9.338 8.55e-05 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 0.2621 on 6 degrees of freedom
## Multiple R-squared:  0.9717, Adjusted R-squared:  0.9388 
## F-statistic: 29.47 on 7 and 6 DF,  p-value: 0.000309
t.test(mouseWide$delta)
## 
##  One Sample t-test
## 
## data:  mouseWide$delta
## t = 8.5858, df = 6, p-value = 0.0001372
## alternative hypothesis: true mean is not equal to 0
## 95 percent confidence interval:
##  0.8600472 1.5456671
## sample estimates:
## mean of x 
##  1.202857
anovaRCB

Merk op dat

  1. de puntschatter, se, t-test statistiek en p-waarde voor het celtype effect exact overeenkomt met de analyse a.d.h.v. een gepaarde t-test!

  2. de anova analyse toont dat we met het model de totale variabiliteit in de expressie opsplitsen in variabiliteit door celtype (CT), variabiliteit tussen muizen (M) en residuele variabiliteit (R) binnen muis die we niet kunnen verklaren met het model:

\[ \begin{array}{lcl} \text{SSTot} &=& \text{SSCT} + \text{SSM} + \text{SSE}\\ 14.6 &=& 5.1 + 9.1 + 0.4 \end{array} \]

Het celtype en muis effect verklaren respectievelijk \[ \begin{array}{ll} \frac{\text{SSCT}}{\text{SSTot}}\times 100&\frac{\text{SSM}}{\text{SSTot}}\times 100\\\\ 34.7& 62.4\\ \end{array} \]

percent van de variabiliteit in log2 proteïne-intensiteiten en

\[ \frac{\text{SSE}}{\text{SSTot}} \times 100 = 2.8 \] percent kan niet worden verklaard: residuele variabiliteit.

Merk op, dat

  • de variabiliteit tussen muizen de grootste bron van variabiliteit is in de data,
  • Deze variabiliteit kunnen we schatten met het RCB design en
  • we kunnen deze variabiliteit dus isoleren van de residuele variabiliteit
  • Dat leidt tot een veel hogere precisie van de schatter voor de gemiddelde log2 FC tussen regulatorische eb conventionele T-cellen dan wat zou worden verkregen bij een CRD design!

Merk ook op dat het RCB ook een aantal vrijheidsgraden dient op te offeren om het muiseffect te kunnen schatten, hier 6 DF.

De winst in power van een RCB is dus een afweging tussen de variabiliteit die kan worden verklaart met het blokeffect en het verlies in vrijheidsgraden.

Als je je de formule voor de variantie covariantie matrix van de parameter schatters bijhaalt zien we \[ \hat{\boldsymbol{\Sigma}}^2_{\hat{\boldsymbol{\beta}}} =\left(\mathbf{X}^T\mathbf{X}\right)^{-1}\hat\sigma^2 \]

dat het RCB een impact heeft op

  • \(\left(\mathbf{X}^T\mathbf{X}\right)^{-1}\) alsook op
  • \(\hat\sigma^2\) van de residuen!

\(\rightarrow\) We kunnen de variantie in de expressie tussen de proefdieren/blokken isoleren uit onze analyse!

\(\rightarrow\) Dat reduceert de variantie van de residuen en leidt tot een toename in power als de variabiliteit tussen muizen/blokken groot is.

Merk op dat,

\[ \hat\sigma^2 = \frac{\text{SSE}}{n-p} = \frac{SSTot - SSM - SSCT}{n-p} \]

  • Blokken is dus nuttig als de reductie in SSE groot is in vergelijking met het verlies in vrijheidsgraden.
  • Dus als SSM een groot deel in van de totale variabiliteit kan verklaren.

Verder heeft het verlies in vrijheidsgraden ook een impact op de t-verdeling die zal worden gebruikt voor inferentie, die hierdoor bredere staarten zal hebben.

4 Vergelijking van de power tussen een CRD en RCB

4.1 Voorbeelden van de twee designs

Hier gaan we de data gebruiken om twee experimenten te simuleren:

  • een randomized complete block design (RCB) met 3 muizen

  • een completely randomized design (CRD) met 6 muizen maar waarbij we enkel één celtype per muis meten.

set.seed(859)
mRcb <- mouse %>% 
  pull(mouse) %>%
  unique %>%
  sample(size=3)

rcbSmall <- mouse %>% filter(mouse%in%mRcb)
rcbSmall
mCrd <- mouse %>% 
  pull(mouse) %>%
  unique %>%
  sample(size=6)


crdSmall <- 
  bind_rows(
    mouse %>% 
      filter(mouse%in%mCrd[1:3]) %>% 
      filter(celltype=="Tcon"),
    mouse %>% 
      filter(mouse%in%mCrd[-(1:3)]) %>% 
      filter(celltype=="Treg"))
crdSmall

In beide experimenten dienen we dus 6 massa-spectrometrie runs te doen.

lmCRDSmall <- lm(intensity ~ celltype, crdSmall)
summary(lmCRDSmall)
## 
## Call:
## lm(formula = intensity ~ celltype, data = crdSmall)
## 
## Residuals:
##      1      2      3      4      5      6 
## -0.507 -0.201  0.708 -1.189 -0.275  1.464 
## 
## Coefficients:
##              Estimate Std. Error t value Pr(>|t|)    
## (Intercept)   10.5290     0.6077  17.326 6.51e-05 ***
## celltypeTreg   1.1300     0.8594   1.315    0.259    
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 1.053 on 4 degrees of freedom
## Multiple R-squared:  0.3018, Adjusted R-squared:  0.1272 
## F-statistic: 1.729 on 1 and 4 DF,  p-value: 0.2589
anova(lmCRDSmall)
lmRCBSmall <- lm(intensity ~ celltype + mouse, rcbSmall)
anova(lmRCBSmall)
summary(lmRCBSmall)
## 
## Call:
## lm(formula = intensity ~ celltype + mouse, data = rcbSmall)
## 
## Residuals:
##        1        2        3        4        5        6 
##  0.06433  0.12633 -0.19067 -0.06433 -0.12633  0.19067 
## 
## Coefficients:
##              Estimate Std. Error t value Pr(>|t|)    
## (Intercept)    9.9577     0.1940  51.329 0.000379 ***
## celltypeTreg   1.0327     0.1940   5.323 0.033527 *  
## mousem3        0.5200     0.2376   2.189 0.160094    
## mousem7        1.9420     0.2376   8.173 0.014641 *  
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 0.2376 on 2 degrees of freedom
## Multiple R-squared:  0.9804, Adjusted R-squared:  0.951 
## F-statistic: 33.32 on 3 and 2 DF,  p-value: 0.02928

Merk op dat

  • we de opregulatie van het proteïne in regulatorische T-cellen op kunnen pikken met het RCB maar niet met het CRD.
  • de standarderror van de \(\log_2\text{FC}_\text{Treg-Tcon}\) schatting is een factor 4.4 kleiner voor het RCB design!

Een slechte data analyst die in zijn analyse vergeet om rekening te houden met het blokeffect is terug naar af:

wrongRcb <- lm(intensity ~ celltype, rcbSmall)
anova(wrongRcb)
summary(wrongRcb)
## 
## Call:
## lm(formula = intensity ~ celltype, data = rcbSmall)
## 
## Residuals:
##       1       2       3       4       5       6 
## -0.7563 -0.1743  0.9307 -0.8850 -0.4270  1.3120 
## 
## Coefficients:
##              Estimate Std. Error t value Pr(>|t|)    
## (Intercept)   10.7783     0.5885  18.316 5.23e-05 ***
## celltypeTreg   1.0327     0.8322   1.241    0.282    
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 1.019 on 4 degrees of freedom
## Multiple R-squared:  0.2779, Adjusted R-squared:  0.09743 
## F-statistic:  1.54 on 1 and 4 DF,  p-value: 0.2825
  • In deze analyse wordt de variabiliteit tussen de blokken geabsorbeerd in de variantieschatter voor de residuele variabiliteit.

  • Uiteraard mogen we een RCB design nooit analyseren zonder de blokfactor op te nemen in het model omdat het design een randomisatierestrictie impliceert.

4.2 Winst in power door blokking?

4.2.1 Power voor completely randomized design (CRD)

varBetweenPlusWithin <- sum(car::Anova(lmRCB,type="III")[c("mouse","Residuals"),"Sum Sq"])/sum(car::Anova(lmRCB,type="III")[c("mouse","Residuals"),"Df"])

alpha <- 0.05
nSim <- 20000
b0 <- 0
sd <- sqrt(varBetweenPlusWithin)
ns <-  c(3,7)  
deltas <- lmRCB$coefficients["celltypeTreg"]

L <- limma::makeContrasts("celltypeTreg",levels=c("(Intercept)","celltypeTreg"))
## Warning in limma::makeContrasts("celltypeTreg", levels = c("(Intercept)", :
## Renaming (Intercept) to Intercept
powerFast <- matrix(NA,nrow=length(ns)*length(deltas),ncol=3) %>% as.data.frame
names(powerFast) <- c("b1","n","power")

i <- 0
for (n in ns)
{
  n1 <- n2 <-  n
  
  ### Simulation
  predictorData <- data.frame(celltype = rep(c("Tcon","Treg"),c(n1,n2)) %>% as.factor)
  design <- model.matrix(~celltype,predictorData)
  
  for (b1 in deltas)
  {
    ySim <- rnorm(nrow(predictorData)*nSim,sd=sd)
    dim(ySim) <-c(nrow(predictorData),nSim)
    ySim <- ySim + c(design %*%c(b0,b1))
    ySim <- t(ySim)
  
    ### Fitting
    fitAll <- limma::lmFit(ySim,design)
  
    ### Inference
    varUnscaled <- c(t(L)%*%fitAll$cov.coefficients%*%L)
    contrasts <- fitAll$coefficients %*%L
    seContrasts <- varUnscaled^.5*fitAll$sigma
    tstats <- contrasts/seContrasts
    pvals <- pt(abs(tstats),fitAll$df.residual,lower.tail = FALSE)*2
    
    i <- i+1
    powerFast[i,] <- c(b1,n,mean(pvals < alpha))
  }
}
powerFast

Omdat we hier een eenvoudige vergelijking tussen twee groepen doen kunnen we de power ook berekenen met de power.t.test functie.

power.t.test(n = 3,delta = lmRCB$coefficients["celltypeTreg"], sd = sqrt(varBetweenPlusWithin))
## 
##      Two-sample t test power calculation 
## 
##               n = 3
##           delta = 1.202857
##              sd = 0.8906339
##       sig.level = 0.05
##           power = 0.2477638
##     alternative = two.sided
## 
## NOTE: n is number in *each* group
power.t.test(n = 7,delta = lmRCB$coefficients["celltypeTreg"], sd = sqrt(varBetweenPlusWithin))
## 
##      Two-sample t test power calculation 
## 
##               n = 7
##           delta = 1.202857
##              sd = 0.8906339
##       sig.level = 0.05
##           power = 0.6411438
##     alternative = two.sided
## 
## NOTE: n is number in *each* group

4.3 Power voor randomized complete block design (RCB)

alpha <- 0.05
nSim <- 20000
b0 <- 0
sd <- sigma(lmRCB)
sdMouse<- sqrt(car::Anova(lmRCB)["mouse","Sum Sq"]/car::Anova(lmRCB)["mouse","Df"]) 
ns <-  c(3,7)  
deltas <- lmRCB$coefficients["celltypeTreg"]


powerFastBlocking <- matrix(NA,nrow=length(ns)*length(deltas),ncol=3) %>% as.data.frame
names(powerFastBlocking) <- c("b1","n","power")

i <- 0
for (n in ns)
{
  
  ### Simulation
  predictorData <- data.frame(celltype = rep(c("Tcon","Treg"),each=n) %>% as.factor, mouse = paste0("m",rep(1:n,2)))
  design <- model.matrix(~ celltype + mouse,predictorData)
  L <- limma::makeContrasts("celltypeTreg",levels=colnames(design))

  for (b1 in deltas)
  {
    ySim <- rnorm(nrow(predictorData)*nSim,sd=sd)
    dim(ySim) <-c(nrow(predictorData),nSim)
    mouseEffect <- rnorm(n, sd = sdMouse)
    betasMouse <- mouseEffect[-1]-mouseEffect[1]
    ySim <- ySim + c(design %*%c(b0,b1,betasMouse))
    ySim <- t(ySim)
  
    ### Fitting
    fitAll <- limma::lmFit(ySim,design)
  
    ### Inference
    varUnscaled <- c(t(L)%*%fitAll$cov.coefficients%*%L)
    contrasts <- fitAll$coefficients %*%L
    seContrasts <- varUnscaled^.5*fitAll$sigma
    tstats <- contrasts/seContrasts
    pvals <- pt(abs(tstats),fitAll$df.residual,lower.tail = FALSE)*2
    
    i <- i+1
    powerFastBlocking[i,] <- c(b1,n,mean(pvals < alpha))
  }
}
## Warning in limma::makeContrasts("celltypeTreg", levels = colnames(design)):
## Renaming (Intercept) to Intercept
## Warning in limma::makeContrasts("celltypeTreg", levels = colnames(design)):
## Renaming (Intercept) to Intercept
powerFastBlocking

Merk op dat de power veel hoger is voor het RCB zowel voor een design met 6 als 14 massa-spectrometrie runs.

Omdat het RCB een blokgrootte heeft van 2 (gepaard design) kunnen we de power ook berekenen met de power.t.test function met type = "one.sample" en sd equal de standard deviatie van het verschil.

power.t.test(n = 3,delta = mean(mouseWide$delta), sd = sd(mouseWide$delta))
## 
##      Two-sample t test power calculation 
## 
##               n = 3
##           delta = 1.202857
##              sd = 0.3706672
##       sig.level = 0.05
##           power = 0.8389961
##     alternative = two.sided
## 
## NOTE: n is number in *each* group
power.t.test(n = 7,delta = mean(mouseWide$delta), sd = sd(mouseWide$delta))
## 
##      Two-sample t test power calculation 
## 
##               n = 7
##           delta = 1.202857
##              sd = 0.3706672
##       sig.level = 0.05
##           power = 0.999826
##     alternative = two.sided
## 
## NOTE: n is number in *each* group

Merk op dat de power iets anders is omdat we met de power.t.test functie hebben conditioneerd op de muizen uit het originele experiment terwijl we de in de simulatie-studie hebben gegenereerd door het muiseffect te simuleren uit een normale verdeling.

4.4 Impact van de hoeveelheid variabiliteit dat het blokeffect verklaart op de power?

We variëren de hoeveelheid variabiliteit die het blokeffect verklaart:
\[ \frac{\sigma^2_\text{between}}{\sigma^2_\text{between}+\sigma^2_\text{within}}=1-\frac{\sigma^2_\text{within}}{\sigma^2_\text{between}+\sigma^2_\text{within}} \]

In ons voorbeeld is de ratio in de variabiliteit tussen muizen en de sum van de variabiliteit tussen en binnen muizen. Merk op dat de variantie binnen muizen gelijk is aan de variantie van de errors van het RCB. De ratio voor ons experiment is

varBetweenPlusWithin <- sum(car::Anova(lmRCB,type="III")[c("mouse","Residuals"),"Sum Sq"])/sum(car::Anova(lmRCB,type="III")[c("mouse","Residuals"),"Df"])
varWithin <- car::Anova(lmRCB)["Residuals","Sum Sq"]/car::Anova(lmRCB)["Residuals","Df"]
varBetweenPlusWithin
## [1] 0.7932287
varWithin
## [1] 0.06869707
1- varWithin/varBetweenPlusWithin
## [1] 0.9133956
alpha <- 0.05
nSim <- 20000
b0 <- 0
varBetweenPlusWithin <- sum(car::Anova(lmRCB,type="III")[c("mouse","Residuals"),"Sum Sq"])/sum(car::Anova(lmRCB,type="III")[c("mouse","Residuals"),"Df"])
 

ns <-  c(3,7)  
deltas <- lmRCB$coefficients["celltypeTreg"]

fracVars <- seq(0,.95,.05)

powerFastBlockingLow <- matrix(NA,nrow=length(ns)*length(fracVars),ncol=3) %>% as.data.frame
names(powerFastBlockingLow) <- c("fracVars","n","power")

i <- 0


for (n in ns)
{
  
  ### Simulation
  predictorData <- data.frame(celltype = rep(c("Tcon","Treg"),each=n) %>% as.factor, mouse = paste0("m",rep(1:n,2)))
  design <- model.matrix(~ celltype + mouse,predictorData)
  L <- limma::makeContrasts("celltypeTreg",levels=colnames(design))
  for (fracVar in fracVars)
  {
  sd <- sqrt(varBetweenPlusWithin*(1-fracVar))
  sdMouse <- sqrt(varBetweenPlusWithin*fracVar) 
  for (b1 in deltas)
  {
    ySim <- rnorm(nrow(predictorData)*nSim,sd=sd)
    dim(ySim) <-c(nrow(predictorData),nSim)
    mouseEffect <- rnorm(n, sd = sdMouse)
    betasMouse <- mouseEffect[-1]-mouseEffect[1]
    ySim <- ySim + c(design %*%c(b0,b1,betasMouse))
    ySim <- t(ySim)
  
    ### Fitting
    fitAll <- limma::lmFit(ySim,design)
  
    ### Inference
    varUnscaled <- c(t(L)%*%fitAll$cov.coefficients%*%L)
    contrasts <- fitAll$coefficients %*%L
    seContrasts <- varUnscaled^.5*fitAll$sigma
    tstats <- contrasts/seContrasts
    pvals <- pt(abs(tstats),fitAll$df.residual,lower.tail = FALSE)*2
    
    i <- i+1
    powerFastBlockingLow[i,] <- c(fracVar,n,mean(pvals < alpha))
  }
  }
}
## Warning in limma::makeContrasts("celltypeTreg", levels = colnames(design)):
## Renaming (Intercept) to Intercept
## Warning in limma::makeContrasts("celltypeTreg", levels = colnames(design)):
## Renaming (Intercept) to Intercept
powerFastBlockingLow
gg_color_hue <- function(n) {
  hues = seq(15, 375, length = n + 1)
  hcl(h = hues, l = 65, c = 100)[1:n]
}
cols <- gg_color_hue(2)

powerFastBlockingLow %>% 
  as.data.frame %>%
  mutate(n = as.factor(n)) %>%
  ggplot(aes(fracVars,power,group=n,color=n)) +
  geom_line() +
  geom_hline(yintercept = powerFast %>% filter(n==3) %>% pull(power),color=cols[1]) +
  annotate("text", label = "CRD (n=3)",
    x = 0.05, y = powerFast %>% filter(n==3) %>% pull(power) +.02, size = 3, colour = cols[1]) +
    geom_hline(yintercept = powerFast %>% filter(n==7) %>% pull(power),color=cols[2]) +
    annotate("text", label = "CRD (n=7)",
    x = 0.05, y = powerFast %>% filter(n==7) %>% pull(power) +.02, size = 3, colour = cols[2]) +
  xlab(expression(~sigma[between]^2/(sigma[between]^2+sigma[within]^2))) +
  geom_vline(xintercept=1-varWithin/varBetweenPlusWithin) +
  xlim(0,1)

  • Als de variantie die wordt verklaard door het blokeffect klein is dan verlies je power in vergelijking met het CRD design. Inderdaad,

    • SSE verkleint dan niet veel en
    • n\(_\text{blocks}\)-1 vrijheidsgraden werden opgeofferd.
  • Van zodra dat het blokeffect een behoorlijk aandeel van de variabiliteit verklaart is het RCB heel erg nuttig!

  • Merk op dat hetzelfde aantal MS runs nodig zijn voor beide designs. Maar voor het RCB hebben we maar de helft van de ratten nodig.

5 Penicilline voorbeeld

Voor de productie van piniciline wordt corn steep liquor (CSL) gebruikt. CSL wordt in blends geproduceerd en er is een behoorlijk grote variabiliteit van blend tot blend.

Stel dat

  • vier methoden moeten worden geëvalueerd om penicilline te produceren (A-D),
  • één blend voldoende is voor 4 runs met een penicilline reactor en
  • dat 20 runs worden voorzien voor het experiment.

Hoe zou je de methoden toekennen aan de CSL blends.

data(penicillin, package="faraway")
table(penicillin$blend,penicillin$treat)
##         
##          A B C D
##   Blend1 1 1 1 1
##   Blend2 1 1 1 1
##   Blend3 1 1 1 1
##   Blend4 1 1 1 1
##   Blend5 1 1 1 1

5.1 Data

head(penicillin)
matrix(penicillin$yield,nrow=5,ncol=4,byrow=TRUE,dimnames=list(levels(penicillin$blend),levels(penicillin$treat)))
##         A  B  C  D
## Blend1 89 88 97 94
## Blend2 84 77 92 79
## Blend3 81 87 87 85
## Blend4 87 92 89 84
## Blend5 79 81 80 88
penicillin %>% 
  ggplot(aes(x = blend, y = yield, group = treat, color = treat)) + 
  geom_line() +
  geom_point()

penicillin %>% 
  ggplot(aes(x = treat, y = yield, group = blend, color = blend)) + 
  geom_line() +
  geom_point()

5.2 Analyse

We analyseren de opbrengst met

  • een factor voor blend en
  • een factor voor de behandeling.
lmPen <- lm(yield~treat + blend, data = penicillin)
plot(lmPen)

car::Anova(lmPen,type="III")

We kunnen besluiten dat het effect van de behandeling op de penicilline opbrengst niet significant is op het 5% significantieniveau. (p = 0.34.

We observeren een groot effect van de blend op de opbrengst.
Blend verklaart 47.1% van de variabiliteit in de penicilline opbrengst.

6 Pseudo-replicatie

Een studie op de pathogeen Francisella tularensis werd door Ramond et al. (2015) uitgevoerd.

  • F. tularensis besmet de cellen van de gastheer via fagocytosis.

  • De auteurs toonden aan dat tularensis deficiënt is aan arginine. Het importeert arginine actief uit de cellen van de gastheer via een arginine transporter, ArgP.

  • Zonder de transporter is de fagosome escape en multiplicatie in het cytoplasma vertraagd.

  • In de studie vergeleken ze het proteome van wild type F. tularensis (WT) met dat van ArgP-gene deleted F. tularensis (knock-out, D8).

  • Elk bio-rep werd in technisch triplicaat gerund op de mass-spectrometer.

  • We gebruiken de data voor het 50S ribosomaal proteïne L5 A0Q4J5

6.1 Data exploratie

franc <- read_tsv("https://raw.githubusercontent.com/statOmics/PSLS21/data/francisellaA0Q4J5.txt")
## Rows: 18 Columns: 3
## ── Column specification ────────────────────────────────────────────────────────
## Delimiter: "\t"
## chr (2): genotype, biorep
## dbl (1): intensityLog2
## 
## ℹ Use `spec()` to retrieve the full column specification for this data.
## ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
franc
franc %>% 
  ggplot(aes(biorep, intensityLog2, color = genotype)) +
  geom_point()

  • Response?
  • Experimentele eenheid?
  • Observationele eenheid?
  • Factoren?

\(\rightarrow\) Pseudo-replicatie, randomisatie naar bio-rep en elke bio-rep werd 3 keer gemeten.

\(\rightarrow\) Als we de data zouden analyseren met een lineair model op basis van elke gemeten intensiteit, dan zouden we doen alsof we 18 bio-reps hadden in de studie. \(\rightarrow\) Effect van interesse tussen de bio-reps. Blok analyse is niet mogelijk!

Als we hetzelfde aantal technische replicaten hebben voor elke bio-rep:

  • kunnen we de technische replicaten eerst uitmiddelen over elke biorep om onafhankelijke metingen te bekomen

  • deze gemiddelden zullen dan eenzelfde precisie hebben

  • we kunnen dan het effect van de behandeling schatten op de samengevatte waarden per bio-rep.

  • LET OP: middel nooit uit over de bio-reps/experimentele eenheden

lmBiorep <- lm(intensityLog2 ~ -1 + biorep, franc)
lmBiorep
## 
## Call:
## lm(formula = intensityLog2 ~ -1 + biorep, data = franc)
## 
## Coefficients:
## biorepD8_n3  biorepD8_n4  biorepD8_n5  biorepWT_n3  biorepWT_n4  biorepWT_n5  
##       27.25        27.43        27.39        27.64        27.54        27.57
francSum <- data.frame(genotype = rep(c("D8","WT"),each=3) %>% as.factor %>% relevel("WT"), intensityLog2 = lmBiorep$coef)
francSum
lmSum <- lm(intensityLog2 ~ genotype, francSum)
summary(lmSum)
## 
## Call:
## lm(formula = intensityLog2 ~ genotype, data = francSum)
## 
## Residuals:
## biorepD8_n3 biorepD8_n4 biorepD8_n5 biorepWT_n3 biorepWT_n4 biorepWT_n5 
##    -0.10541     0.07010     0.03531     0.05566    -0.04531    -0.01034 
## 
## Coefficients:
##             Estimate Std. Error t value Pr(>|t|)    
## (Intercept) 27.58266    0.04333 636.581 3.65e-11 ***
## genotypeD8  -0.22439    0.06128  -3.662   0.0215 *  
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 0.07505 on 4 degrees of freedom
## Multiple R-squared:  0.7702, Adjusted R-squared:  0.7128 
## F-statistic: 13.41 on 1 and 4 DF,  p-value: 0.02154

6.2 Verkeerde analyse

lmWrong <- lm(intensityLog2 ~ genotype, franc)
summary(lmWrong)
## 
## Call:
## lm(formula = intensityLog2 ~ genotype, data = franc)
## 
## Residuals:
##      Min       1Q   Median       3Q      Max 
## -0.21943 -0.04181 -0.01914  0.06537  0.17792 
## 
## Coefficients:
##             Estimate Std. Error t value Pr(>|t|)    
## (Intercept) 27.35826    0.03457  791.48  < 2e-16 ***
## genotypeWT   0.22439    0.04888    4.59 0.000302 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 0.1037 on 16 degrees of freedom
## Multiple R-squared:  0.5684, Adjusted R-squared:  0.5414 
## F-statistic: 21.07 on 1 and 16 DF,  p-value: 0.0003017

Merk op dat de verkeerde analyse geen rekening houdt dat er meerdere technische replicaten zijn voor elke bio-repeat waardoor er een artificieel significant resultaat wordt bekomen omdat het voor deze analyse lijkt alsof er 3 drie zoveel onafhankelijke metingen zijn.

6.2.1 Type I fout!

sigmaWithin <- sigma(lmBiorep)
sigmaBetween <- sigma(lmSum)
xBiorep <- model.matrix(~-1+biorep,franc)
xWrong <- model.matrix(~genotype,franc)


set.seed(2523)
nSim <- 1000
resWrong <- matrix(NA,nSim,4) %>% as.data.frame
names(resWrong) <- c("Estimate","Std. Error","t value","pvalue")
resCorrect <- resWrong
genotype <- franc$genotype
genotypeSum <- francSum$genotype
biorep <- franc$biorep

for (i in 1:nSim)
{
biorepSim <- rnorm(ncol(xBiorep),sd=sigmaBetween)
ySim <- xBiorep%*%biorepSim + rnorm(nrow(xBiorep),sd=sigmaWithin)
ySum <- lm(ySim~biorep)$coefficient
resWrong[i,] <- summary(lm(ySim~genotype))$coefficient[2,]
resCorrect[i,]<-summary(lm(ySum~genotypeSum))$coefficient[2,]
}
mean(resCorrect$pvalue < 0.05)
## [1] 0.042
mean(resWrong$pvalue < 0.05)
## [1] 0.143
qplot(resCorrect$pvalue,geom = "histogram",boundary=c(0,1)) +
  stat_bin(breaks=seq(0,1,.1)) +
  xlab("pvalue") +
  ggtitle("Correct analysis")
## Warning: `qplot()` was deprecated in ggplot2 3.4.0.
## This warning is displayed once every 8 hours.
## Call `lifecycle::last_lifecycle_warnings()` to see where this warning was
## generated.
## `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.
## Warning: Computation failed in `stat_bin()`.
## Caused by error in `seq.default()`:
## ! 'from' must be of length 1

qplot(resWrong$pvalue,geom = "histogram",boundary=c(0,1)) +
  stat_bin(breaks=seq(0,1,.1)) +
  xlab("pvalue") +
  ggtitle("Wrong analysis")
## `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.
## Warning: Computation failed in `stat_bin()`.
## Caused by error in `seq.default()`:
## ! 'from' must be of length 1

  • We observeren dat de analyse die geen rekening houdt met pseudo-replicatie veel te liberaal is!

  • De analyse waarbij we eerst uitmiddellen over de technische replicaten geeft correcte p-waarden en een juiste controle van de type I fout!

6.3 Verschillend aantal technische herhalingen?

  1. subsampelen tot we een gelijk aantal technische herhalingen hebben en dan uitmiddelen (suboptimaal \(\rightarrow\) verlies aan informatie)

  2. Meer geavanceerde methoden zijn noodzakelijk:

    • e.g. mixed models
    • het mixed model framework kan de correlatiestructuur van de data modelleren.
  • Mixed models kunnen ook worden gebruikt voor de analyse van designs waarbij men is geïnteresseerd in effecten die zowel binnen als tussen blokken dienen te worden geschat, e.g. split-plot designs.

  • Mixed models vallen buiten het bestek van deze cursus maar worden behandeld in het opleidingsonderdeel geavanceerde biostatistiek in de master opleiding

LS0tCnRpdGxlOiAiRXhwZXJpbWVudGVlbCBEZXNpZ24gSUk6IFJhbmRvbWl6ZWQgQ29tcGxldGUgQmxvY2sgRGVzaWducyBlbiBQc2V1ZG8tcmVwbGljYXRpZSIKYXV0aG9yOiAiTGlldmVuIENsZW1lbnQiCmRhdGU6ICJzdGF0T21pY3MsIEdoZW50IFVuaXZlcnNpdHkgKGh0dHBzOi8vc3RhdG9taWNzLmdpdGh1Yi5pbykiCm91dHB1dDoKICBodG1sX2RvY3VtZW50OgogICAgY29kZV9kb3dubG9hZDogeWVzCiAgICB0aGVtZTogY29zbW8KICAgIHRvYzogeWVzCiAgICB0b2NfZmxvYXQ6IHllcwogICAgaGlnaGxpZ2h0OiB0YW5nbwogICAgbnVtYmVyX3NlY3Rpb25zOiB5ZXMKCgotLS0KCjxhIHJlbD0ibGljZW5zZSIgaHJlZj0iaHR0cHM6Ly9jcmVhdGl2ZWNvbW1vbnMub3JnL2xpY2Vuc2VzL2J5LW5jLXNhLzQuMCI+PGltZyBhbHQ9IkNyZWF0aXZlIENvbW1vbnMgTGljZW5zZSIgc3R5bGU9ImJvcmRlci13aWR0aDowIiBzcmM9Imh0dHBzOi8vaS5jcmVhdGl2ZWNvbW1vbnMub3JnL2wvYnktbmMtc2EvNC4wLzg4eDMxLnBuZyIgLz48L2E+CgpgYGB7cn0KbGlicmFyeSh0aWR5dmVyc2UpCmBgYAoKIyBSYW5kb21pemVkIGNvbXBsZXRlIGJsb2NrIGRlc2lnbnMKClxbXHNpZ21hXjI9IFxzaWdtYV4yX3tiaW99K1xzaWdtYV4yX1x0ZXh0e2xhYn0gK1xzaWdtYV4yX1x0ZXh0e2V4dHJhY3Rpb259ICsgXHNpZ21hXjJfXHRleHR7cnVufSArIFxsZG90c1xdCgotIEJpb2xvZ2lzY2g6IGZsdWN0dWF0aWVzIGluIHByb3Rlw69uZSBuaXZlYXUgdHVzc2VuIG11aXplbiwgZmx1Y3RhdGllcyBpbiBwcm90ZcOvbmUgY29uY2VudHJhdGllIHR1c3NlbiBjZWxsZW4sIC4uLgotIFRlY2huaXNjaDoga29vaS1lZmZlY3QsIGxhYiBlZmZlY3QsIHdlZWsgZWZmZWN0LCBwbGFzbWEgZXh0cmFjdGllLCBNUy1ydW4sIC4uLgoKIyBOYXR1cmUgbWV0aG9kczogUG9pbnRzIG9mIHNpZ25pZmljYW5jZSAtIEJsb2NraW5nIAoKW2h0dHBzOi8vd3d3Lm5hdHVyZS5jb20vYXJ0aWNsZXMvbm1ldGguMzAwNS5wZGZdKGh0dHBzOi8vd3d3Lm5hdHVyZS5jb20vYXJ0aWNsZXMvbm1ldGguMzAwNS5wZGYpCgotIE9uZXdheSBhbm92YSBpcyBlZW4gc3BlY2lhYWwgZ2V2YWwgdmFuIGVlbiAiY29tcGxldGVseSByYW5kb21pemVkIGRlc2lnbiIgKENSRCk6CgogICAgLSBleHBlcmltZW50ZWxlIGVlbmhlZGVuIHppam4gYXQgcmFuZG9tIGdldHJva2tlbiB1aXQgZGUgcG9wdWxhdGllLiAKICAgIC0gYmVoYW5kZWxpbmdlbiB3b3JkZW4gYXQgcmFuZG9tIHRvZWdld2V6ZW4gYWFuIGRlIGV4cGVyaW1lbnRlbGUgZWVuaGVkZW4gCiAgICAtIEVsa2UgZXhwZXJpbWVudGVsZSBlZW5oZWlkIGtyaWpndCAxIGJlaGFuZGVsaW5nIGVuIHdvcmR0IDEga2VlciBnZW1ldGVuLiAKCi0gSW4gZWVuIGJsb2tkZXNpZ24gemlqbiBkZSBibG9ra2VuIGJsb2trZW4gZGllIGF0IHJhbmRvbSB3b3JkZW4gZ2VzYW1wbGVkIHVpdCBkZSBwb3B1bGF0aWUgdmFuIGFsbGUgbW9nZWxpamtlIGJsb2trZW4uIAogICAgCiAgICAtIERlICJyYW5kb21pemVkIGNvbXBsZXRlIGJsb2NrIGRlc2lnbiIgKFJDQikgYmVwZXJrdCBkZSByYW5kb21pc2F0aWU6IGRlIGJlaGFuZGVsaW5nZW4gd29yZGVuIGJpbm5lbiBkZSBibG9ra2VuIGdlcmFuZG9taXNlZXJkLiAKICAgIC0gaGV0IGthbiBuaWV0IGFscyBlZW4gb25lLXdheSBhbm92YSB3b3JkZW4gZ2VhbmFseXNlZXJkLiAKICAgIC0gRWVuIGdlcGFhcmQgZGVzaWduIGlzIGhldCBtZWVzdCBlZW52b3VkaWdlIFJDQjogbWV0IGJsb2tncm9vdHRlIDIuCiAgICAtIEJpbm5lbiBibG9jayBlZmZlY3RlbiBrdW5uZW4gd29yZGVuIGdlc2NoYXQgbWV0IGRlIGxtIGZ1bmN0aWUgaW4gUi4KCiMgTXVpcyB2b29yYmVlbGQgCgojIyBJbnRybwoKYGBge3IgZWNobz1GQUxTRSwgb3V0LndpZHRoPSI1MCUifQprbml0cjo6aW5jbHVkZV9ncmFwaGljcygiaHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL3N0YXRPbWljcy9QU0xTMjEvbWFzdGVyL2ZpZ3VyZXMvbW91c2VUY2VsbF9SQ0JfZGVzaWduLnBuZyIpCmBgYAoKRHVndWV0IGV0IGFsLiAoMjAxNykgTUNQIDE2KDgpOjE0MTYtMTQzMi4gZG9pOiAxMC4xMDc0L21jcC5tMTE2LjA2Mjc0NQoKLSBBbGxlIGJlaGFuZGVsaW5nZW4gKGhpZXIgY2VsdHlwZXMpIHZhbiBpbnRlcmVzc2UgemlqbiBiaW5uZW4gYmxvayEgIAotIFdlIGt1bm5lbiBoZXQgZWZmZWN0IHNjaGF0dGVuIHZhbiBkZSBiZWhhbmRlbGluZyBiaW5uZW4gYmxvayEgCgpXZSBmb2N1c3NlbiBvcCDDqcOpbiBwcm90ZcOvbmUKCiAgLSBEZSBnZW1ldGVuIGludGVuc2l0ZWl0ZW4gemlqbiByZWVkcyBsb2cyIGdldHJhbnNmb3JtZWVyZC4gVmVyc2NoaWxsZW4gaW4gZGUgaW50ZW5zaXRlaXRlbiBrdW5uZW4gZHVzIGFscyBsb2cyIEZDIChmb2xkIGNoYW5nZXMpIHdvcmRlbiBnZcOvbnRlcnByZXRlZXJkLiAKICAtIFAxNjA0NSBvciBHYWxlY3Rpbi0xLiAKICAtIEZ1bmN0aWU6ICJMZWN0aW4gdGhhdCBiaW5kcyBiZXRhLWdhbGFjdG9zaWRlIGFuZCBhIHdpZGUgYXJyYXkgb2YgY29tcGxleCBjYXJib2h5ZG1vdXNlZXMuIFBsYXlzIGEgcm9sZSBpbiByZWd1bGF0aW5nIGFwb3B0b3NpcywgY2VsbCBwcm9saWZlcmF0aW9uIGFuZCBjZWxsIGRpZmZlcmVudGlhdGlvbi4gSW5oaWJpdHMgQ0Q0NSBwcm90ZWluIHBob3NwaGF0YXNlIGFjdGl2aXR5IGFuZCB0aGVyZWZvcmUgdGhlIGRlcGhvc3Bob3J5bGF0aW9uIG9mIEx5biBraW5hc2UuIFN0cm9uZyBpbmR1Y2VyIG9mIFQtY2VsbCBhcG9wdG9zaXMuIiAoc291cmNlOiBbdW5pcHJvdF0oaHR0cHM6Ly93d3cudW5pcHJvdC5vcmcvdW5pcHJvdC9QMTYwNDUpKQoKIyMgRGF0YSBFeHBsb3JhdGllIAoKYGBge3J9Cm1vdXNlIDwtIHJlYWRfdHN2KCJodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vc3RhdE9taWNzL1BTTFMyMS9kYXRhL21vdXNlUDE2MDQ1LnR4dCIpCm1vdXNlCmBgYAoKCmBgYHtyfQptb3VzZSAlPiUgCiAgZ2dwbG90KGFlcyh4ID0gbW91c2UsIHk9IGludGVuc2l0eSwgY29sPWNlbGx0eXBlKSkgKwogIGdlb21fcG9pbnQoKQpgYGAKCmBgYHtyfQptb3VzZSAlPiUgIAogICAgZ2dwbG90KGFlcyh4ID0gY2VsbHR5cGUsIHk9IGludGVuc2l0eSkpICsKICAgIGdlb21fYm94cGxvdChhZXMoY29sPWNlbGx0eXBlKSxvdXRsaWVyLnNoYXBlID0gTkEpICsKICAgIGdlb21fbGluZShhZXMoZ3JvdXAgPSBtb3VzZSkpICsKICAgIGdlb21fcG9pbnQoYWVzKGNvbD1jZWxsdHlwZSkpCmBgYAoKCkRlIHBsb3RzIHRvbmVuIGV2aWRlbnRpZSB2b29yIAoKLSBvcHJlZ3VsYXRpZSB2YW4gcHJvdGXDr25lIGV4cHJlc3NpZSBpbiByZWd1bGF0b3Jpc2NoZSBULWNlbGxzIGVuIAotIHZlZWwgdmFyaWFiaWxpdGVpdCBpbiBkZSBleHByZXNzaWUgdHVzc2VuIGRlIHByb2VmZGllcmVuISAKCiMjIEdlcGFhcmRlIGFuYWx5c2UKCkhldCBpcyBlZW4gZ2VwYWFyZCBkZXNpZ24sIGhldCBtZWVzdCBlZW52b3VkaWdlIFJDQi4gCgpJbiBkZSBINSB2YW4gZGUgYmFzaXNjdXJzdXMgc3RhdGlzdGllayB6b3VkZW4gd2UgZGUgZGF0YSBhbmFseXNlcmVuIGRvb3IgZ2VicnVpa3QgdGUgbWFrZW4gdmFuIGhldCB2ZXJzY2hpbCBpbiBleHByZXNzaWUgdHVzc2VuIGRlIGNlbHR5cGVzLiAKCmBgYHtyfQptb3VzZVdpZGUgPC0gbW91c2UgJT4lIAogIHNwcmVhZChjZWxsdHlwZSxpbnRlbnNpdHkpICU+JQogIG11dGF0ZShkZWx0YSA9IFRyZWcgLSBUY29uKQptb3VzZVdpZGUKYGBgCgojIyMgRGF0YSBleHBsb3JhdGllCgotIEJveHBsb3QgdmFuIGRlIHZlcnNjaGlsbGVuIAoKYGBge3J9Cm1vdXNlV2lkZSAlPiUgCiAgZ2dwbG90KGFlcyh4PSIiLHk9ZGVsdGEpKSArCiAgZ2VvbV9ib3hwbG90KG91dGxpZXIuc2hhcGUgPSBOQSkgKyAKICBnZW9tX2ppdHRlcigpCmBgYAoKLSBTdW1tYXJ5IHN0YXRpc3RpY3MKCmBgYHtyfQpkZWx0YVN1bSA8LSBtb3VzZVdpZGUgJT4lIAogIHN1bW1hcml6ZV9hdCgiZGVsdGEiLCAKICAgICAgICAgICAgICAgbGlzdCgKICAgICAgICAgICAgICAgICAgICBtZWFuID0gfiBtZWFuKC4sbmEucm0gPSBUUlVFKSwKICAgICAgICAgICAgICAgICAgICBzZCA9IH4gc2QoLixuYS5ybSA9IFRSVUUpLAogICAgICAgICAgICAgICAgICAgIG4gPSB+IGlzLm5hKC4pICU+JSBgIWAgJT4lIHN1bQogICAgICAgICAgICAgICAgICAgKQogICAgICAgICAgICAgICApICU+JSAKICBtdXRhdGUoc2UgPSBzZC9zcXJ0KG4pKQpkZWx0YVN1bQpgYGAgIAoKCk1lcmsgb3AsIGRhdCBkZSBpbnRlbnNpdGVpdHNkYXRhIG5pZXQgb25hZmhhbmtlbGlqayB6aWpuIG9tZGF0IHdlIGRlIGV4cHJlc3NpZSBoZWJiZW4gZ2VtZXRlbiBpbiB0d2VlIGNlbHR5cGVzIHZhbiBoZXR6ZWxmZGUgcHJvZWZkaWVyLiAKCi0gQ292YXJpYW50aWUgZW4gY29ycmVsYXRpZSB2YW4gZGUgZXhwcmVzc2llIGluIGJlaWRlIGNlbHR5cGVzIAoKYGBge3J9CmNvcihtb3VzZVdpZGVbLGMoIlRjb24iLCJUcmVnIildKQp2YXIobW91c2VXaWRlWyxjKCJUY29uIiwiVHJlZyIpXSkKdmFyKG1vdXNlV2lkZVssYygiVGNvbiIsIlRyZWciKV0pICU+JSAKICBkaWFnICU+JSAKICBzcXJ0CmBgYAoKLSBFciBpcyBpbmRlcmRhYWQgZWVuIGdyb3RlIGNvcnJlbGF0aWUgaW4gZGUgZXhwcmVzc2llIHZvb3IgaGV0IHByb3Rlw69uZSBpbiBjb252ZW50aW9uZWxlIGFuZCByZWd1bGF0b3Jpc2NoZSBULWNlbGxlbi4gCgotIFN0YW5kYXJkZGV2aWF0aWUgdmFuIHZlcnNjaGlsPyAKCgokJApcYmVnaW57YXJyYXl9e2xjbH0KXHRleHR7c2R9X3theF9yICsgYnhfY30gJj0mIFxzcXJ0e2FeMlxoYXQgXHNpZ21hX3JeMiArIGJeMiBcaGF0IFxzaWdtYV9jXjIgKyAyYWIgXGhhdFxzaWdtYV97cixjfX0gXFwKXHRleHR7c2R9X3t4X3IgLSB4X2N9ICY9JlxzcXJ0ezFeMlxoYXQgXHNpZ21hX3JeMiArICgtMSleMiBcaGF0IFxzaWdtYV9jXjIgKyAyIFx0aW1lcyAxClx0aW1lcyAtMSAKXHRpbWVzIFxoYXRcc2lnbWFfe3IsY319XFwKJj0mXHNxcnR7XGhhdCBcc2lnbWFfcl4yICsgXGhhdCBcc2lnbWFfY14yIC0gMiBcdGltZXMgXGhhdFxzaWdtYV97cixjfX0KXGVuZHthcnJheX0KJCQKCgoKYGBge3J9CnNkRGVsdGEyIDwtIChjKC0xLDEpICUqJSB2YXIobW91c2VXaWRlWyxjKCJUY29uIiwiVHJlZyIpXSkgJSolIGMoLTEsMSkpICU+JQogIHNxcnQKc2REZWx0YTIKCnNlRGVsdGFCYXIgPC0gc2REZWx0YTIgLyBzcXJ0KGRlbHRhU3VtJG4pCnNlRGVsdGFCYXIKCmRlbHRhU3VtCmBgYAoKLSBEZSBzdGFuZGFyZGRldmlhdGllIHZhbiBoZXQgdmVyc2NoaWwgaXMgdmVlbCBrbGVpbmVyIGRvb3IgZGUgc3RlcmtlIGNvcnJlbGF0aWUgaW4gZXhwcmVzc2llIGluIGJlaWRlIGNlbHR5cGVzLiAKLSBNZXJrIG9wLCBkYXQgaGV0IGdlcGFhcmRlIGRlc2lnbiBvbnMgdG9lbGFhdCBvbSBkZSBsb2cyIEZDIHRlIGJlcmVrZW5lbiB0dXNzZW4gYmVpZGUgY2VsdHlwZXMgdm9vciBlbGsgcHJvZWZkaWVyLiAKCiMjIFJDQiBhbmFseXNlCgpXZSBrdW5uZW4gZGUgZGF0YSBvb2sgYW5hbHlzZXJlbiBtZXQgZWVuIGxpbmVhaXIgbW9kZWwgbWV0IAoKLSBlZW4gaG9vZmRlZmZlY3Qgdm9vciBjZWx0eXBlIGVuCi0gZWVuIGhvb2ZkZWZmZWN0IHZvb3IgZGUgYmxva2ZhY3RvciBtdWlzCgpPbWRhdCB3ZSBkZSBleHByZXNzaWUgaW4gZWxrIGNlbHR5cGUgaGViYmVuIGdlbWV0ZW4ga3VubmVuIHdlIGR1cyBoZXQgZ2VtaWRkZWxkZSBlZmZlY3Qgc2NoYXR0ZW4gdm9vciBlbGtlIG11aXMuIAoKYGBge3J9CmxtUkNCIDwtIGxtKGludGVuc2l0eSB+IGNlbGx0eXBlICsgbW91c2UsIG1vdXNlKQpwbG90KGxtUkNCLCB3aGljaCA9IGMoMSwyLDMpKQpgYGAKCkFscyBqZSB0d2lqZmVsdCBvZiB2b2xkYWFuIGlzIGFhbiBkZSBhYW5uYW1lcyB2YW4gaGV0IG1vZGVsIGlzIGhldCBudXR0aWcgb20gZ2VicnVpa3QgdGUgbWFrZW4gdmFuIHNpbXVsYXRpZXMgd2FhcmJpaiB3ZSBkYXRhIHNpbXVsZXJlbiB1aXQgZWVuIG1vZGVsIHdhYXIgYWxsZSBhYW5uYW1lcyB6aWpuIHZvbGRhYW4uIAoKYGBge3J9CmRlc2lnbiA8LSAgbW9kZWwubWF0cml4KGludGVuc2l0eSB+IGNlbGx0eXBlICsgbW91c2UsIG1vdXNlKSAKc2lnbWFNb3VzZSA8LSBzcXJ0KGNhcjo6QW5vdmEobG1SQ0IsIHR5cGUgPSAiSUlJIilbIm1vdXNlIiwiU3VtIFNxIl0vY2FyOjpBbm92YShsbVJDQiwgdHlwZSA9ICJJSUkiKVsibW91c2UiLCJEZiJdKSAKYmV0YXMgPC0gbG1SQ0IkY29lZmZpY2llbnRzCm5Nb3VzZSA8LSBtb3VzZSRtb3VzZSAlPiUgdW5pcXVlICU+JSBsZW5ndGgKCnBhcihtZnJvdz1jKDMsMykpCmZvciAoaSBpbiAxOjkpCnsKICAgIG1vdXNlRWZmZWN0IDwtIHJub3JtKG5Nb3VzZSwgc2QgPSBzaWdtYU1vdXNlKQogICAgYmV0YXNNb3VzZSA8LSBtb3VzZUVmZmVjdFstMV0tbW91c2VFZmZlY3RbMV0KICAgIGJldGFzWy1jKDE6MildIDwtIGJldGFzTW91c2UgCiAgICB5c2ltIDwtIGRlc2lnbiAlKiUgYmV0YXMgICsgcm5vcm0obnJvdyhkZXNpZ24pLHNkPXNpZ21hKGxtUkNCKSkKICAgIHBsb3QobG0oeXNpbSB+IC0xICsgZGVzaWduKSwgd2hpY2ggPSAxKSAKfQpgYGAKCkRlIGRldmlhdGllcyBkaWUgd2UgaW4gZGUgcmVzaWR1cGxvdCB6aWVuIHZvb3Igb25zIGV4cGVyaW1lbnQgemlqbiB2ZXJnZWxpamtiYWFyIG1ldCBhZndpamtpbmdlbiBkaWUgd2Uga3VubmVuIG9ic2VydmVyZW4gdWl0IGdlc2ltdWxlZXJkZSBkYXRhIHVpdCBlZW4gZ2VsaWprYWFyZGlnZSBwcm9lZiB3YWFyYmlqIGFhbiBhbGxlIGFhbm5hbWVzIHZhbiBoZXQgbW9kZWwgaXMgdm9sZGFhbi4gCgojIyMgSW5mZXJlbnRpZSAKCmBgYHtyfQphbm92YVJDQiA8LSBjYXI6OkFub3ZhKGxtUkNCLCB0eXBlID0gIklJSSIpCnN1bW1hcnkobG1SQ0IpCnQudGVzdChtb3VzZVdpZGUkZGVsdGEpCmFub3ZhUkNCCmBgYAoKTWVyayBvcCBkYXQgCgoxLiBkZSBwdW50c2NoYXR0ZXIsIHNlLCB0LXRlc3Qgc3RhdGlzdGllayBlbiBwLXdhYXJkZSB2b29yIGhldCBjZWx0eXBlIGVmZmVjdCBleGFjdCBvdmVyZWVua29tdCBtZXQgZGUgYW5hbHlzZSBhLmQuaC52LiBlZW4gZ2VwYWFyZGUgdC10ZXN0ISAKCjIuIGRlIGFub3ZhIGFuYWx5c2UgdG9vbnQgZGF0IHdlIG1ldCBoZXQgbW9kZWwgZGUgdG90YWxlIHZhcmlhYmlsaXRlaXQgaW4gZGUgZXhwcmVzc2llIG9wc3BsaXRzZW4gaW4gdmFyaWFiaWxpdGVpdCBkb29yIGNlbHR5cGUgKENUKSwgdmFyaWFiaWxpdGVpdCB0dXNzZW4gbXVpemVuIChNKSBlbiByZXNpZHVlbGUgdmFyaWFiaWxpdGVpdCAoUikgYmlubmVuIG11aXMgZGllIHdlIG5pZXQga3VubmVuIHZlcmtsYXJlbiBtZXQgaGV0IG1vZGVsOiAgCgokJApcYmVnaW57YXJyYXl9e2xjbH0KXHRleHR7U1NUb3R9ICY9JiBcdGV4dHtTU0NUfSArIFx0ZXh0e1NTTX0gKyBcdGV4dHtTU0V9XFwKYHIgcm91bmQoYW5vdmFSQ0JbLTEsIlN1bSBTcSJdICU+JSBzdW0oKSwxKWAgJj0mIGByIHJvdW5kKGFub3ZhUkNCWyJjZWxsdHlwZSIsIlN1bSBTcSJdLDEpYCArIGByIHJvdW5kKGFub3ZhUkNCWyJtb3VzZSIsIlN1bSBTcSJdLDEpYCArIGByIHJvdW5kKGFub3ZhUkNCWyJSZXNpZHVhbHMiLCJTdW0gU3EiXSwxKWAKXGVuZHthcnJheX0KJCQKCkhldCBjZWx0eXBlIGVuIG11aXMgZWZmZWN0IHZlcmtsYXJlbiByZXNwZWN0aWV2ZWxpamsKJCQKXGJlZ2lue2FycmF5fXtsbH0KXGZyYWN7XHRleHR7U1NDVH19e1x0ZXh0e1NTVG90fX1cdGltZXMgMTAwJlxmcmFje1x0ZXh0e1NTTX19e1x0ZXh0e1NTVG90fX1cdGltZXMgMTAwXFxcXApgciByb3VuZChhbm92YVJDQlsiY2VsbHR5cGUiLCJTdW0gU3EiXS9zdW0oYW5vdmFSQ0JbLTEsIlN1bSBTcSJdKSoxMDAsMSlgJgpgciByb3VuZChhbm92YVJDQlsibW91c2UiLCJTdW0gU3EiXS9zdW0oYW5vdmFSQ0JbLTEsIlN1bSBTcSJdKSoxMDAsMSlgXFwKXGVuZHthcnJheX0KJCQKCnBlcmNlbnQgdmFuIGRlIHZhcmlhYmlsaXRlaXQgaW4gbG9nMiBwcm90ZcOvbmUtaW50ZW5zaXRlaXRlbiBlbgoKJCQgClxmcmFje1x0ZXh0e1NTRX19e1x0ZXh0e1NTVG90fX0gXHRpbWVzIDEwMCA9IGByIHJvdW5kKGFub3ZhUkNCWyJSZXNpZHVhbHMiLCJTdW0gU3EiXS9zdW0oYW5vdmFSQ0JbLTEsIlN1bSBTcSJdKSoxMDAsMSlgCiQkCnBlcmNlbnQga2FuIG5pZXQgd29yZGVuIHZlcmtsYWFyZDogcmVzaWR1ZWxlIHZhcmlhYmlsaXRlaXQuIAoKTWVyayBvcCwgZGF0IAoKLSBkZSB2YXJpYWJpbGl0ZWl0IHR1c3NlbiBtdWl6ZW4gZGUgZ3Jvb3RzdGUgYnJvbiB2YW4gdmFyaWFiaWxpdGVpdCBpcyBpbiBkZSBkYXRhLCAKLSBEZXplIHZhcmlhYmlsaXRlaXQga3VubmVuIHdlIHNjaGF0dGVuIG1ldCBoZXQgUkNCIGRlc2lnbiBlbgotIHdlIGt1bm5lbiBkZXplIHZhcmlhYmlsaXRlaXQgZHVzIGlzb2xlcmVuIHZhbiBkZSByZXNpZHVlbGUgdmFyaWFiaWxpdGVpdAotIERhdCBsZWlkdCB0b3QgZWVuIHZlZWwgaG9nZXJlIHByZWNpc2llIHZhbiBkZSBzY2hhdHRlciB2b29yIGRlIGdlbWlkZGVsZGUgbG9nMiBGQyB0dXNzZW4gIHJlZ3VsYXRvcmlzY2hlIGViIGNvbnZlbnRpb25lbGUgVC1jZWxsZW4gZGFuIHdhdCB6b3Ugd29yZGVuIHZlcmtyZWdlbiBiaWogZWVuIENSRCBkZXNpZ24hCgpNZXJrIG9vayBvcCBkYXQgaGV0IFJDQiBvb2sgZWVuIGFhbnRhbCB2cmlqaGVpZHNncmFkZW4gZGllbnQgb3AgdGUgb2ZmZXJlbiBvbSBoZXQgbXVpc2VmZmVjdCB0ZSBrdW5uZW4gc2NoYXR0ZW4sIGhpZXIgNiBERi4KCkRlIHdpbnN0IGluIHBvd2VyIHZhbiBlZW4gUkNCIGlzIGR1cyBlZW4gYWZ3ZWdpbmcgdHVzc2VuIGRlIHZhcmlhYmlsaXRlaXQgZGllIGthbiB3b3JkZW4gdmVya2xhYXJ0IG1ldCBoZXQgYmxva2VmZmVjdCBlbiBoZXQgdmVybGllcyBpbiB2cmlqaGVpZHNncmFkZW4uIAoKQWxzIGplIGplIGRlIGZvcm11bGUgdm9vciBkZSB2YXJpYW50aWUgY292YXJpYW50aWUgbWF0cml4IHZhbiBkZSBwYXJhbWV0ZXIgc2NoYXR0ZXJzIGJpamhhYWx0IHppZW4gd2UKJCQKXGhhdHtcYm9sZHN5bWJvbHtcU2lnbWF9fV4yX3tcaGF0e1xib2xkc3ltYm9se1xiZXRhfX19Cj1cbGVmdChcbWF0aGJme1h9XlRcbWF0aGJme1h9XHJpZ2h0KV57LTF9XGhhdFxzaWdtYV4yCiQkCgpkYXQgaGV0IFJDQiBlZW4gaW1wYWN0IGhlZWZ0IG9wIAoKLSAkXGxlZnQoXG1hdGhiZntYfV5UXG1hdGhiZntYfVxyaWdodCleey0xfSQgYWxzb29rIG9wCi0gICRcaGF0XHNpZ21hXjIkIHZhbiBkZSByZXNpZHVlbiEKCiRccmlnaHRhcnJvdyQgV2Uga3VubmVuIGRlIHZhcmlhbnRpZSBpbiBkZSBleHByZXNzaWUgdHVzc2VuIGRlIHByb2VmZGllcmVuL2Jsb2trZW4gaXNvbGVyZW4gdWl0IG9uemUgYW5hbHlzZSEgCgokXHJpZ2h0YXJyb3ckIERhdCByZWR1Y2VlcnQgZGUgdmFyaWFudGllIHZhbiBkZSByZXNpZHVlbiBlbiBsZWlkdCB0b3QgZWVuIHRvZW5hbWUgaW4gcG93ZXIgYWxzIGRlIHZhcmlhYmlsaXRlaXQgdHVzc2VuIG11aXplbi9ibG9ra2VuIGdyb290IGlzLiAKCk1lcmsgb3AgZGF0LCAKCiQkClxoYXRcc2lnbWFeMiA9IFxmcmFje1x0ZXh0e1NTRX19e24tcH0gPSBcZnJhY3tTU1RvdCAtIFNTTSAtIFNTQ1R9e24tcH0gCiQkIAoKLSBCbG9ra2VuIGlzIGR1cyBudXR0aWcgYWxzIGRlIHJlZHVjdGllIGluIFNTRSBncm9vdCBpcyBpbiB2ZXJnZWxpamtpbmcgbWV0IGhldCB2ZXJsaWVzIGluIHZyaWpoZWlkc2dyYWRlbi4gCi0gRHVzIGFscyBTU00gZWVuIGdyb290IGRlZWwgaW4gdmFuIGRlIHRvdGFsZSB2YXJpYWJpbGl0ZWl0IGthbiB2ZXJrbGFyZW4uIAoKVmVyZGVyIGhlZWZ0IGhldCB2ZXJsaWVzIGluIHZyaWpoZWlkc2dyYWRlbiBvb2sgZWVuIGltcGFjdCBvcCBkZSB0LXZlcmRlbGluZyBkaWUgemFsIHdvcmRlbiBnZWJydWlrdCB2b29yIGluZmVyZW50aWUsIGRpZSBoaWVyZG9vciBicmVkZXJlIHN0YWFydGVuIHphbCBoZWJiZW4uIAoKIyBWZXJnZWxpamtpbmcgdmFuIGRlIHBvd2VyIHR1c3NlbiBlZW4gQ1JEIGVuIFJDQiAKCiMjIFZvb3JiZWVsZGVuIHZhbiBkZSB0d2VlIGRlc2lnbnMKCkhpZXIgZ2FhbiB3ZSBkZSBkYXRhIGdlYnJ1aWtlbiBvbSB0d2VlIGV4cGVyaW1lbnRlbiB0ZSBzaW11bGVyZW46CgotIGVlbiByYW5kb21pemVkIGNvbXBsZXRlIGJsb2NrIGRlc2lnbiAoUkNCKSBtZXQgMyBtdWl6ZW4KCi0gZWVuIGNvbXBsZXRlbHkgcmFuZG9taXplZCBkZXNpZ24gKENSRCkgbWV0IDYgbXVpemVuIG1hYXIgd2FhcmJpaiB3ZSBlbmtlbCDDqcOpbiBjZWx0eXBlIHBlciBtdWlzIG1ldGVuLiAKCmBgYHtyfQpzZXQuc2VlZCg4NTkpCm1SY2IgPC0gbW91c2UgJT4lIAogIHB1bGwobW91c2UpICU+JQogIHVuaXF1ZSAlPiUKICBzYW1wbGUoc2l6ZT0zKQoKcmNiU21hbGwgPC0gbW91c2UgJT4lIGZpbHRlcihtb3VzZSVpbiVtUmNiKQpyY2JTbWFsbApgYGAKCmBgYHtyfQptQ3JkIDwtIG1vdXNlICU+JSAKICBwdWxsKG1vdXNlKSAlPiUKICB1bmlxdWUgJT4lCiAgc2FtcGxlKHNpemU9NikKCgpjcmRTbWFsbCA8LSAKICBiaW5kX3Jvd3MoCiAgICBtb3VzZSAlPiUgCiAgICAgIGZpbHRlcihtb3VzZSVpbiVtQ3JkWzE6M10pICU+JSAKICAgICAgZmlsdGVyKGNlbGx0eXBlPT0iVGNvbiIpLAogICAgbW91c2UgJT4lIAogICAgICBmaWx0ZXIobW91c2UlaW4lbUNyZFstKDE6MyldKSAlPiUgCiAgICAgIGZpbHRlcihjZWxsdHlwZT09IlRyZWciKSkKY3JkU21hbGwKYGBgCgpJbiBiZWlkZSBleHBlcmltZW50ZW4gZGllbmVuIHdlIGR1cyA2IG1hc3NhLXNwZWN0cm9tZXRyaWUgcnVucyB0ZSBkb2VuLiAKCgpgYGB7cn0KbG1DUkRTbWFsbCA8LSBsbShpbnRlbnNpdHkgfiBjZWxsdHlwZSwgY3JkU21hbGwpCnN1bW1hcnkobG1DUkRTbWFsbCkKYW5vdmEobG1DUkRTbWFsbCkKYGBgCgpgYGB7cn0KbG1SQ0JTbWFsbCA8LSBsbShpbnRlbnNpdHkgfiBjZWxsdHlwZSArIG1vdXNlLCByY2JTbWFsbCkKYW5vdmEobG1SQ0JTbWFsbCkKc3VtbWFyeShsbVJDQlNtYWxsKQpgYGAKCk1lcmsgb3AgZGF0IAoKLSB3ZSBkZSBvcHJlZ3VsYXRpZSB2YW4gaGV0IHByb3Rlw69uZSBpbiByZWd1bGF0b3Jpc2NoZSBULWNlbGxlbiBvcCBrdW5uZW4gcGlra2VuIG1ldCBoZXQgUkNCIG1hYXIgbmlldCBtZXQgaGV0IENSRC4gCi0gZGUgc3RhbmRhcmRlcnJvciB2YW4gZGUgJFxsb2dfMlx0ZXh0e0ZDfV9cdGV4dHtUcmVnLVRjb259JCBzY2hhdHRpbmcgaXMgZWVuIGZhY3RvciBgciByb3VuZChzdW1tYXJ5KGxtQ1JEU21hbGwpJGNvZWZbImNlbGx0eXBlVHJlZyIsIlN0ZC4gRXJyb3IiXS9zdW1tYXJ5KGxtUkNCU21hbGwpJGNvZWZbImNlbGx0eXBlVHJlZyIsIlN0ZC4gRXJyb3IiXSwxKWAga2xlaW5lciB2b29yIGhldCBSQ0IgZGVzaWduISAKCgoKRWVuIHNsZWNodGUgZGF0YSBhbmFseXN0IGRpZSBpbiB6aWpuIGFuYWx5c2UgdmVyZ2VldCBvbSByZWtlbmluZyB0ZSBob3VkZW4gbWV0IGhldCBibG9rZWZmZWN0IGlzIHRlcnVnIG5hYXIgYWY6CgpgYGB7cn0Kd3JvbmdSY2IgPC0gbG0oaW50ZW5zaXR5IH4gY2VsbHR5cGUsIHJjYlNtYWxsKQphbm92YSh3cm9uZ1JjYikKc3VtbWFyeSh3cm9uZ1JjYikKYGBgCgotIEluIGRlemUgYW5hbHlzZSB3b3JkdCBkZSB2YXJpYWJpbGl0ZWl0IHR1c3NlbiBkZSBibG9ra2VuIGdlYWJzb3JiZWVyZCBpbiBkZSB2YXJpYW50aWVzY2hhdHRlciB2b29yIGRlIHJlc2lkdWVsZSB2YXJpYWJpbGl0ZWl0LiAKCi0gVWl0ZXJhYXJkIG1vZ2VuIHdlIGVlbiBSQ0IgZGVzaWduIG5vb2l0IGFuYWx5c2VyZW4gem9uZGVyIGRlIGJsb2tmYWN0b3Igb3AgdGUgbmVtZW4gaW4gaGV0IG1vZGVsIG9tZGF0IGhldCBkZXNpZ24gZWVuIHJhbmRvbWlzYXRpZXJlc3RyaWN0aWUgaW1wbGljZWVydC4gCgojIyBXaW5zdCBpbiBwb3dlciBkb29yIGJsb2traW5nPyAKCiMjIyBQb3dlciB2b29yIGNvbXBsZXRlbHkgcmFuZG9taXplZCBkZXNpZ24gKENSRCkKCmBgYHtyfQp2YXJCZXR3ZWVuUGx1c1dpdGhpbiA8LSBzdW0oY2FyOjpBbm92YShsbVJDQix0eXBlPSJJSUkiKVtjKCJtb3VzZSIsIlJlc2lkdWFscyIpLCJTdW0gU3EiXSkvc3VtKGNhcjo6QW5vdmEobG1SQ0IsdHlwZT0iSUlJIilbYygibW91c2UiLCJSZXNpZHVhbHMiKSwiRGYiXSkKCmFscGhhIDwtIDAuMDUKblNpbSA8LSAyMDAwMApiMCA8LSAwCnNkIDwtIHNxcnQodmFyQmV0d2VlblBsdXNXaXRoaW4pCm5zIDwtICBjKDMsNykgIApkZWx0YXMgPC0gbG1SQ0IkY29lZmZpY2llbnRzWyJjZWxsdHlwZVRyZWciXQoKTCA8LSBsaW1tYTo6bWFrZUNvbnRyYXN0cygiY2VsbHR5cGVUcmVnIixsZXZlbHM9YygiKEludGVyY2VwdCkiLCJjZWxsdHlwZVRyZWciKSkKCnBvd2VyRmFzdCA8LSBtYXRyaXgoTkEsbnJvdz1sZW5ndGgobnMpKmxlbmd0aChkZWx0YXMpLG5jb2w9MykgJT4lIGFzLmRhdGEuZnJhbWUKbmFtZXMocG93ZXJGYXN0KSA8LSBjKCJiMSIsIm4iLCJwb3dlciIpCgppIDwtIDAKZm9yIChuIGluIG5zKQp7CiAgbjEgPC0gbjIgPC0gIG4KICAKICAjIyMgU2ltdWxhdGlvbgogIHByZWRpY3RvckRhdGEgPC0gZGF0YS5mcmFtZShjZWxsdHlwZSA9IHJlcChjKCJUY29uIiwiVHJlZyIpLGMobjEsbjIpKSAlPiUgYXMuZmFjdG9yKQogIGRlc2lnbiA8LSBtb2RlbC5tYXRyaXgofmNlbGx0eXBlLHByZWRpY3RvckRhdGEpCiAgCiAgZm9yIChiMSBpbiBkZWx0YXMpCiAgewogICAgeVNpbSA8LSBybm9ybShucm93KHByZWRpY3RvckRhdGEpKm5TaW0sc2Q9c2QpCiAgICBkaW0oeVNpbSkgPC1jKG5yb3cocHJlZGljdG9yRGF0YSksblNpbSkKICAgIHlTaW0gPC0geVNpbSArIGMoZGVzaWduICUqJWMoYjAsYjEpKQogICAgeVNpbSA8LSB0KHlTaW0pCiAgCiAgICAjIyMgRml0dGluZwogICAgZml0QWxsIDwtIGxpbW1hOjpsbUZpdCh5U2ltLGRlc2lnbikKICAKICAgICMjIyBJbmZlcmVuY2UKICAgIHZhclVuc2NhbGVkIDwtIGModChMKSUqJWZpdEFsbCRjb3YuY29lZmZpY2llbnRzJSolTCkKICAgIGNvbnRyYXN0cyA8LSBmaXRBbGwkY29lZmZpY2llbnRzICUqJUwKICAgIHNlQ29udHJhc3RzIDwtIHZhclVuc2NhbGVkXi41KmZpdEFsbCRzaWdtYQogICAgdHN0YXRzIDwtIGNvbnRyYXN0cy9zZUNvbnRyYXN0cwogICAgcHZhbHMgPC0gcHQoYWJzKHRzdGF0cyksZml0QWxsJGRmLnJlc2lkdWFsLGxvd2VyLnRhaWwgPSBGQUxTRSkqMgogICAgCiAgICBpIDwtIGkrMQogICAgcG93ZXJGYXN0W2ksXSA8LSBjKGIxLG4sbWVhbihwdmFscyA8IGFscGhhKSkKICB9Cn0KcG93ZXJGYXN0CmBgYAoKT21kYXQgd2UgaGllciBlZW4gZWVudm91ZGlnZSB2ZXJnZWxpamtpbmcgdHVzc2VuIHR3ZWUgZ3JvZXBlbiBkb2VuIGt1bm5lbiB3ZSBkZSBwb3dlciBvb2sgYmVyZWtlbmVuIG1ldCBkZSBgcG93ZXIudC50ZXN0YCBmdW5jdGllLgoKYGBge3J9CnBvd2VyLnQudGVzdChuID0gMyxkZWx0YSA9IGxtUkNCJGNvZWZmaWNpZW50c1siY2VsbHR5cGVUcmVnIl0sIHNkID0gc3FydCh2YXJCZXR3ZWVuUGx1c1dpdGhpbikpCnBvd2VyLnQudGVzdChuID0gNyxkZWx0YSA9IGxtUkNCJGNvZWZmaWNpZW50c1siY2VsbHR5cGVUcmVnIl0sIHNkID0gc3FydCh2YXJCZXR3ZWVuUGx1c1dpdGhpbikpCmBgYAoKIyMgUG93ZXIgdm9vciByYW5kb21pemVkIGNvbXBsZXRlIGJsb2NrIGRlc2lnbiAoUkNCKQoKCgoKYGBge3J9CmFscGhhIDwtIDAuMDUKblNpbSA8LSAyMDAwMApiMCA8LSAwCnNkIDwtIHNpZ21hKGxtUkNCKQpzZE1vdXNlPC0gc3FydChjYXI6OkFub3ZhKGxtUkNCKVsibW91c2UiLCJTdW0gU3EiXS9jYXI6OkFub3ZhKGxtUkNCKVsibW91c2UiLCJEZiJdKSAKbnMgPC0gIGMoMyw3KSAgCmRlbHRhcyA8LSBsbVJDQiRjb2VmZmljaWVudHNbImNlbGx0eXBlVHJlZyJdCgoKcG93ZXJGYXN0QmxvY2tpbmcgPC0gbWF0cml4KE5BLG5yb3c9bGVuZ3RoKG5zKSpsZW5ndGgoZGVsdGFzKSxuY29sPTMpICU+JSBhcy5kYXRhLmZyYW1lCm5hbWVzKHBvd2VyRmFzdEJsb2NraW5nKSA8LSBjKCJiMSIsIm4iLCJwb3dlciIpCgppIDwtIDAKZm9yIChuIGluIG5zKQp7CiAgCiAgIyMjIFNpbXVsYXRpb24KICBwcmVkaWN0b3JEYXRhIDwtIGRhdGEuZnJhbWUoY2VsbHR5cGUgPSByZXAoYygiVGNvbiIsIlRyZWciKSxlYWNoPW4pICU+JSBhcy5mYWN0b3IsIG1vdXNlID0gcGFzdGUwKCJtIixyZXAoMTpuLDIpKSkKICBkZXNpZ24gPC0gbW9kZWwubWF0cml4KH4gY2VsbHR5cGUgKyBtb3VzZSxwcmVkaWN0b3JEYXRhKQogIEwgPC0gbGltbWE6Om1ha2VDb250cmFzdHMoImNlbGx0eXBlVHJlZyIsbGV2ZWxzPWNvbG5hbWVzKGRlc2lnbikpCgogIGZvciAoYjEgaW4gZGVsdGFzKQogIHsKICAgIHlTaW0gPC0gcm5vcm0obnJvdyhwcmVkaWN0b3JEYXRhKSpuU2ltLHNkPXNkKQogICAgZGltKHlTaW0pIDwtYyhucm93KHByZWRpY3RvckRhdGEpLG5TaW0pCiAgICBtb3VzZUVmZmVjdCA8LSBybm9ybShuLCBzZCA9IHNkTW91c2UpCiAgICBiZXRhc01vdXNlIDwtIG1vdXNlRWZmZWN0Wy0xXS1tb3VzZUVmZmVjdFsxXQogICAgeVNpbSA8LSB5U2ltICsgYyhkZXNpZ24gJSolYyhiMCxiMSxiZXRhc01vdXNlKSkKICAgIHlTaW0gPC0gdCh5U2ltKQogIAogICAgIyMjIEZpdHRpbmcKICAgIGZpdEFsbCA8LSBsaW1tYTo6bG1GaXQoeVNpbSxkZXNpZ24pCiAgCiAgICAjIyMgSW5mZXJlbmNlCiAgICB2YXJVbnNjYWxlZCA8LSBjKHQoTCklKiVmaXRBbGwkY292LmNvZWZmaWNpZW50cyUqJUwpCiAgICBjb250cmFzdHMgPC0gZml0QWxsJGNvZWZmaWNpZW50cyAlKiVMCiAgICBzZUNvbnRyYXN0cyA8LSB2YXJVbnNjYWxlZF4uNSpmaXRBbGwkc2lnbWEKICAgIHRzdGF0cyA8LSBjb250cmFzdHMvc2VDb250cmFzdHMKICAgIHB2YWxzIDwtIHB0KGFicyh0c3RhdHMpLGZpdEFsbCRkZi5yZXNpZHVhbCxsb3dlci50YWlsID0gRkFMU0UpKjIKICAgIAogICAgaSA8LSBpKzEKICAgIHBvd2VyRmFzdEJsb2NraW5nW2ksXSA8LSBjKGIxLG4sbWVhbihwdmFscyA8IGFscGhhKSkKICB9Cn0KcG93ZXJGYXN0QmxvY2tpbmcKYGBgCgpNZXJrIG9wIGRhdCBkZSBwb3dlciB2ZWVsIGhvZ2VyIGlzIHZvb3IgaGV0IFJDQiB6b3dlbCB2b29yIGVlbiBkZXNpZ24gbWV0IDYgYWxzIDE0IG1hc3NhLXNwZWN0cm9tZXRyaWUgcnVucy4gCgpPbWRhdCBoZXQgUkNCIGVlbiBibG9rZ3Jvb3R0ZSBoZWVmdCB2YW4gMiAgKGdlcGFhcmQgZGVzaWduKSBrdW5uZW4gd2UgZGUgcG93ZXIgb29rIGJlcmVrZW5lbiBtZXQgZGUgYHBvd2VyLnQudGVzdCBmdW5jdGlvbmAgbWV0IGB0eXBlID0gIm9uZS5zYW1wbGUiYCBlbiBgc2RgIGVxdWFsIGRlIHN0YW5kYXJkIGRldmlhdGllIHZhbiBoZXQgdmVyc2NoaWwuCgoKYGBge3J9CnBvd2VyLnQudGVzdChuID0gMyxkZWx0YSA9IG1lYW4obW91c2VXaWRlJGRlbHRhKSwgc2QgPSBzZChtb3VzZVdpZGUkZGVsdGEpKQpwb3dlci50LnRlc3QobiA9IDcsZGVsdGEgPSBtZWFuKG1vdXNlV2lkZSRkZWx0YSksIHNkID0gc2QobW91c2VXaWRlJGRlbHRhKSkKYGBgCgpNZXJrIG9wIGRhdCBkZSBwb3dlciBpZXRzIGFuZGVycyBpcyBvbWRhdCB3ZSBtZXQgZGUgcG93ZXIudC50ZXN0IGZ1bmN0aWUgaGViYmVuIGNvbmRpdGlvbmVlcmQgb3AgZGUgbXVpemVuIHVpdCBoZXQgb3JpZ2luZWxlIGV4cGVyaW1lbnQgIHRlcndpamwgd2UgZGUgaW4gZGUgc2ltdWxhdGllLXN0dWRpZSBoZWJiZW4gZ2VnZW5lcmVlcmQgZG9vciBoZXQgbXVpc2VmZmVjdCB0ZSBzaW11bGVyZW4gdWl0IGVlbiBub3JtYWxlIHZlcmRlbGluZy4gCgojIyBJbXBhY3QgdmFuIGRlIGhvZXZlZWxoZWlkIHZhcmlhYmlsaXRlaXQgZGF0IGhldCBibG9rZWZmZWN0IHZlcmtsYWFydCBvcCBkZSBwb3dlcj8KCldlIHZhcmnDq3JlbiBkZSBob2V2ZWVsaGVpZCB2YXJpYWJpbGl0ZWl0IGRpZSBoZXQgYmxva2VmZmVjdCB2ZXJrbGFhcnQ6ICAKJCQKXGZyYWN7XHNpZ21hXjJfXHRleHR7YmV0d2Vlbn19e1xzaWdtYV4yX1x0ZXh0e2JldHdlZW59K1xzaWdtYV4yX1x0ZXh0e3dpdGhpbn19PTEtXGZyYWN7XHNpZ21hXjJfXHRleHR7d2l0aGlufX17XHNpZ21hXjJfXHRleHR7YmV0d2Vlbn0rXHNpZ21hXjJfXHRleHR7d2l0aGlufX0KJCQKCkluIG9ucyB2b29yYmVlbGQgaXMgZGUgcmF0aW8gaW4gZGUgdmFyaWFiaWxpdGVpdCB0dXNzZW4gbXVpemVuIGVuIGRlIHN1bSB2YW4gZGUgdmFyaWFiaWxpdGVpdCB0dXNzZW4gZW4gYmlubmVuIG11aXplbi4gIE1lcmsgb3AgZGF0IGRlIHZhcmlhbnRpZSBiaW5uZW4gbXVpemVuIGdlbGlqayBpcyBhYW4gZGUgdmFyaWFudGllIHZhbiBkZSBlcnJvcnMgdmFuIGhldCBSQ0IuIERlIHJhdGlvIHZvb3Igb25zIGV4cGVyaW1lbnQgaXMgCgpgYGB7cn0KdmFyQmV0d2VlblBsdXNXaXRoaW4gPC0gc3VtKGNhcjo6QW5vdmEobG1SQ0IsdHlwZT0iSUlJIilbYygibW91c2UiLCJSZXNpZHVhbHMiKSwiU3VtIFNxIl0pL3N1bShjYXI6OkFub3ZhKGxtUkNCLHR5cGU9IklJSSIpW2MoIm1vdXNlIiwiUmVzaWR1YWxzIiksIkRmIl0pCnZhcldpdGhpbiA8LSBjYXI6OkFub3ZhKGxtUkNCKVsiUmVzaWR1YWxzIiwiU3VtIFNxIl0vY2FyOjpBbm92YShsbVJDQilbIlJlc2lkdWFscyIsIkRmIl0KdmFyQmV0d2VlblBsdXNXaXRoaW4KdmFyV2l0aGluCjEtIHZhcldpdGhpbi92YXJCZXR3ZWVuUGx1c1dpdGhpbgpgYGAKCgpgYGB7cn0KYWxwaGEgPC0gMC4wNQpuU2ltIDwtIDIwMDAwCmIwIDwtIDAKdmFyQmV0d2VlblBsdXNXaXRoaW4gPC0gc3VtKGNhcjo6QW5vdmEobG1SQ0IsdHlwZT0iSUlJIilbYygibW91c2UiLCJSZXNpZHVhbHMiKSwiU3VtIFNxIl0pL3N1bShjYXI6OkFub3ZhKGxtUkNCLHR5cGU9IklJSSIpW2MoIm1vdXNlIiwiUmVzaWR1YWxzIiksIkRmIl0pCiAKCm5zIDwtICBjKDMsNykgIApkZWx0YXMgPC0gbG1SQ0IkY29lZmZpY2llbnRzWyJjZWxsdHlwZVRyZWciXQoKZnJhY1ZhcnMgPC0gc2VxKDAsLjk1LC4wNSkKCnBvd2VyRmFzdEJsb2NraW5nTG93IDwtIG1hdHJpeChOQSxucm93PWxlbmd0aChucykqbGVuZ3RoKGZyYWNWYXJzKSxuY29sPTMpICU+JSBhcy5kYXRhLmZyYW1lCm5hbWVzKHBvd2VyRmFzdEJsb2NraW5nTG93KSA8LSBjKCJmcmFjVmFycyIsIm4iLCJwb3dlciIpCgppIDwtIDAKCgpmb3IgKG4gaW4gbnMpCnsKICAKICAjIyMgU2ltdWxhdGlvbgogIHByZWRpY3RvckRhdGEgPC0gZGF0YS5mcmFtZShjZWxsdHlwZSA9IHJlcChjKCJUY29uIiwiVHJlZyIpLGVhY2g9bikgJT4lIGFzLmZhY3RvciwgbW91c2UgPSBwYXN0ZTAoIm0iLHJlcCgxOm4sMikpKQogIGRlc2lnbiA8LSBtb2RlbC5tYXRyaXgofiBjZWxsdHlwZSArIG1vdXNlLHByZWRpY3RvckRhdGEpCiAgTCA8LSBsaW1tYTo6bWFrZUNvbnRyYXN0cygiY2VsbHR5cGVUcmVnIixsZXZlbHM9Y29sbmFtZXMoZGVzaWduKSkKICBmb3IgKGZyYWNWYXIgaW4gZnJhY1ZhcnMpCiAgewogIHNkIDwtIHNxcnQodmFyQmV0d2VlblBsdXNXaXRoaW4qKDEtZnJhY1ZhcikpCiAgc2RNb3VzZSA8LSBzcXJ0KHZhckJldHdlZW5QbHVzV2l0aGluKmZyYWNWYXIpIAogIGZvciAoYjEgaW4gZGVsdGFzKQogIHsKICAgIHlTaW0gPC0gcm5vcm0obnJvdyhwcmVkaWN0b3JEYXRhKSpuU2ltLHNkPXNkKQogICAgZGltKHlTaW0pIDwtYyhucm93KHByZWRpY3RvckRhdGEpLG5TaW0pCiAgICBtb3VzZUVmZmVjdCA8LSBybm9ybShuLCBzZCA9IHNkTW91c2UpCiAgICBiZXRhc01vdXNlIDwtIG1vdXNlRWZmZWN0Wy0xXS1tb3VzZUVmZmVjdFsxXQogICAgeVNpbSA8LSB5U2ltICsgYyhkZXNpZ24gJSolYyhiMCxiMSxiZXRhc01vdXNlKSkKICAgIHlTaW0gPC0gdCh5U2ltKQogIAogICAgIyMjIEZpdHRpbmcKICAgIGZpdEFsbCA8LSBsaW1tYTo6bG1GaXQoeVNpbSxkZXNpZ24pCiAgCiAgICAjIyMgSW5mZXJlbmNlCiAgICB2YXJVbnNjYWxlZCA8LSBjKHQoTCklKiVmaXRBbGwkY292LmNvZWZmaWNpZW50cyUqJUwpCiAgICBjb250cmFzdHMgPC0gZml0QWxsJGNvZWZmaWNpZW50cyAlKiVMCiAgICBzZUNvbnRyYXN0cyA8LSB2YXJVbnNjYWxlZF4uNSpmaXRBbGwkc2lnbWEKICAgIHRzdGF0cyA8LSBjb250cmFzdHMvc2VDb250cmFzdHMKICAgIHB2YWxzIDwtIHB0KGFicyh0c3RhdHMpLGZpdEFsbCRkZi5yZXNpZHVhbCxsb3dlci50YWlsID0gRkFMU0UpKjIKICAgIAogICAgaSA8LSBpKzEKICAgIHBvd2VyRmFzdEJsb2NraW5nTG93W2ksXSA8LSBjKGZyYWNWYXIsbixtZWFuKHB2YWxzIDwgYWxwaGEpKQogIH0KICB9Cn0KcG93ZXJGYXN0QmxvY2tpbmdMb3cKYGBgCgpgYGB7cn0KZ2dfY29sb3JfaHVlIDwtIGZ1bmN0aW9uKG4pIHsKICBodWVzID0gc2VxKDE1LCAzNzUsIGxlbmd0aCA9IG4gKyAxKQogIGhjbChoID0gaHVlcywgbCA9IDY1LCBjID0gMTAwKVsxOm5dCn0KY29scyA8LSBnZ19jb2xvcl9odWUoMikKCnBvd2VyRmFzdEJsb2NraW5nTG93ICU+JSAKICBhcy5kYXRhLmZyYW1lICU+JQogIG11dGF0ZShuID0gYXMuZmFjdG9yKG4pKSAlPiUKICBnZ3Bsb3QoYWVzKGZyYWNWYXJzLHBvd2VyLGdyb3VwPW4sY29sb3I9bikpICsKICBnZW9tX2xpbmUoKSArCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gcG93ZXJGYXN0ICU+JSBmaWx0ZXIobj09MykgJT4lIHB1bGwocG93ZXIpLGNvbG9yPWNvbHNbMV0pICsKICBhbm5vdGF0ZSgidGV4dCIsIGxhYmVsID0gIkNSRCAobj0zKSIsCiAgICB4ID0gMC4wNSwgeSA9IHBvd2VyRmFzdCAlPiUgZmlsdGVyKG49PTMpICU+JSBwdWxsKHBvd2VyKSArLjAyLCBzaXplID0gMywgY29sb3VyID0gY29sc1sxXSkgKwogICAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gcG93ZXJGYXN0ICU+JSBmaWx0ZXIobj09NykgJT4lIHB1bGwocG93ZXIpLGNvbG9yPWNvbHNbMl0pICsKICAgIGFubm90YXRlKCJ0ZXh0IiwgbGFiZWwgPSAiQ1JEIChuPTcpIiwKICAgIHggPSAwLjA1LCB5ID0gcG93ZXJGYXN0ICU+JSBmaWx0ZXIobj09NykgJT4lIHB1bGwocG93ZXIpICsuMDIsIHNpemUgPSAzLCBjb2xvdXIgPSBjb2xzWzJdKSArCiAgeGxhYihleHByZXNzaW9uKH5zaWdtYVtiZXR3ZWVuXV4yLyhzaWdtYVtiZXR3ZWVuXV4yK3NpZ21hW3dpdGhpbl1eMikpKSArCiAgZ2VvbV92bGluZSh4aW50ZXJjZXB0PTEtdmFyV2l0aGluL3ZhckJldHdlZW5QbHVzV2l0aGluKSArCiAgeGxpbSgwLDEpCmBgYAoKLSBBbHMgZGUgdmFyaWFudGllIGRpZSB3b3JkdCB2ZXJrbGFhcmQgZG9vciBoZXQgYmxva2VmZmVjdCBrbGVpbiBpcyBkYW4gdmVybGllcyBqZSBwb3dlciBpbiB2ZXJnZWxpamtpbmcgbWV0IGhldCBDUkQgZGVzaWduLiBJbmRlcmRhYWQsICAKICAgIAogICAgLSBTU0UgdmVya2xlaW50IGRhbiBuaWV0IHZlZWwgZW4KICAgIC0gbiRfXHRleHR7YmxvY2tzfSQtMSB2cmlqaGVpZHNncmFkZW4gd2VyZGVuIG9wZ2VvZmZlcmQuIAogICAgCi0gVmFuIHpvZHJhIGRhdCBoZXQgYmxva2VmZmVjdCBlZW4gYmVob29ybGlqayBhYW5kZWVsIHZhbiBkZSB2YXJpYWJpbGl0ZWl0IHZlcmtsYWFydCBpcyBoZXQgUkNCIGhlZWwgZXJnIG51dHRpZyEgCgotIE1lcmsgb3AgZGF0IGhldHplbGZkZSBhYW50YWwgTVMgcnVucyBub2RpZyB6aWpuIHZvb3IgYmVpZGUgZGVzaWducy4gTWFhciB2b29yIGhldCBSQ0IgaGViYmVuIHdlIG1hYXIgZGUgaGVsZnQgdmFuIGRlIHJhdHRlbiBub2RpZy4gCgojIFBlbmljaWxsaW5lIHZvb3JiZWVsZCAKClZvb3IgZGUgIHByb2R1Y3RpZSB2YW4gcGluaWNpbGluZSB3b3JkdCBjb3JuIHN0ZWVwIGxpcXVvciAoQ1NMKSBnZWJydWlrdC4gQ1NMIHdvcmR0IGluIGJsZW5kcyBnZXByb2R1Y2VlcmQgZW4gZXIgaXMgZWVuIGJlaG9vcmxpamsgZ3JvdGUgdmFyaWFiaWxpdGVpdCB2YW4gYmxlbmQgdG90IGJsZW5kLiAKClN0ZWwgZGF0IAoKLSB2aWVyIG1ldGhvZGVuIG1vZXRlbiB3b3JkZW4gZ2XDq3ZhbHVlZXJkIG9tIHBlbmljaWxsaW5lIHRlIHByb2R1Y2VyZW4gKEEtRCksCi0gw6nDqW4gYmxlbmQgdm9sZG9lbmRlIGlzIHZvb3IgNCBydW5zIG1ldCBlZW4gcGVuaWNpbGxpbmUgcmVhY3RvciBlbiAKLSBkYXQgMjAgcnVucyB3b3JkZW4gdm9vcnppZW4gdm9vciBoZXQgZXhwZXJpbWVudC4gCgpIb2Ugem91IGplIGRlIG1ldGhvZGVuIHRvZWtlbm5lbiBhYW4gZGUgQ1NMIGJsZW5kcy4gCgoKYGBge3J9CmRhdGEocGVuaWNpbGxpbiwgcGFja2FnZT0iZmFyYXdheSIpCnRhYmxlKHBlbmljaWxsaW4kYmxlbmQscGVuaWNpbGxpbiR0cmVhdCkKYGBgCgojIyBEYXRhCgpgYGB7cn0KaGVhZChwZW5pY2lsbGluKQptYXRyaXgocGVuaWNpbGxpbiR5aWVsZCxucm93PTUsbmNvbD00LGJ5cm93PVRSVUUsZGltbmFtZXM9bGlzdChsZXZlbHMocGVuaWNpbGxpbiRibGVuZCksbGV2ZWxzKHBlbmljaWxsaW4kdHJlYXQpKSkKYGBgCgpgYGB7cn0KcGVuaWNpbGxpbiAlPiUgCiAgZ2dwbG90KGFlcyh4ID0gYmxlbmQsIHkgPSB5aWVsZCwgZ3JvdXAgPSB0cmVhdCwgY29sb3IgPSB0cmVhdCkpICsgCiAgZ2VvbV9saW5lKCkgKwogIGdlb21fcG9pbnQoKQpgYGAKCmBgYHtyfQpwZW5pY2lsbGluICU+JSAKICBnZ3Bsb3QoYWVzKHggPSB0cmVhdCwgeSA9IHlpZWxkLCBncm91cCA9IGJsZW5kLCBjb2xvciA9IGJsZW5kKSkgKyAKICBnZW9tX2xpbmUoKSArCiAgZ2VvbV9wb2ludCgpCmBgYAoKIyMgQW5hbHlzZQoKV2UgYW5hbHlzZXJlbiBkZSBvcGJyZW5nc3QgbWV0CgotIGVlbiBmYWN0b3Igdm9vciBibGVuZCBlbiAgCi0gZWVuIGZhY3RvciB2b29yIGRlIGJlaGFuZGVsaW5nLiAKCmBgYHtyfQpsbVBlbiA8LSBsbSh5aWVsZH50cmVhdCArIGJsZW5kLCBkYXRhID0gcGVuaWNpbGxpbikKcGxvdChsbVBlbikKY2FyOjpBbm92YShsbVBlbix0eXBlPSJJSUkiKQpgYGAKCldlIGt1bm5lbiBiZXNsdWl0ZW4gZGF0IGhldCBlZmZlY3QgdmFuIGRlIGJlaGFuZGVsaW5nIG9wIGRlIHBlbmljaWxsaW5lIG9wYnJlbmdzdCBuaWV0IHNpZ25pZmljYW50IGlzIG9wIGhldCA1JSBzaWduaWZpY2FudGllbml2ZWF1LiAocCA9IGByIGNhcjo6QW5vdmEobG1QZW4sdHlwZT0iSUlJIilbInRyZWF0IiwiUHIoPkYpIl0gJT4lIHJvdW5kKC4sMilgLgoKV2Ugb2JzZXJ2ZXJlbiBlZW4gZ3Jvb3QgZWZmZWN0IHZhbiBkZSBibGVuZCBvcCBkZSBvcGJyZW5nc3QuICAKQmxlbmQgdmVya2xhYXJ0IGByIHJvdW5kKGNhcjo6QW5vdmEobG1QZW4sdHlwZT0iSUlJIilbImJsZW5kIiwiU3VtIFNxIl0vc3VtKGNhcjo6QW5vdmEobG1QZW4sdHlwZT0iSUlJIilbLTEsIlN1bSBTcSJdKSoxMDAsMSlgJSB2YW4gZGUgdmFyaWFiaWxpdGVpdCBpbiBkZSBwZW5pY2lsbGluZSBvcGJyZW5nc3QuCgoKIyBQc2V1ZG8tcmVwbGljYXRpZQoKRWVuIHN0dWRpZSBvcCBkZSBwYXRob2dlZW4gRnJhbmNpc2VsbGEgdHVsYXJlbnNpcyB3ZXJkIGRvb3IgUmFtb25kIGV0IGFsLiAoMjAxNSkgdWl0Z2V2b2VyZC4gCgotIEYuIHR1bGFyZW5zaXMgYmVzbWV0IGRlIGNlbGxlbiB2YW4gZGUgZ2FzdGhlZXIgdmlhIGZhZ29jeXRvc2lzLiAKLSBEZSBhdXRldXJzIHRvb25kZW4gYWFuIGRhdCB0dWxhcmVuc2lzIGRlZmljacOrbnQgaXMgYWFuIGFyZ2luaW5lLiBIZXQgaW1wb3J0ZWVydCBhcmdpbmluZSBhY3RpZWYgdWl0IGRlIGNlbGxlbiB2YW4gZGUgZ2FzdGhlZXIgdmlhIGVlbiBhcmdpbmluZSB0cmFuc3BvcnRlciwgQXJnUC4gCi0gWm9uZGVyIGRlIHRyYW5zcG9ydGVyIGlzIGRlIGZhZ29zb21lIGVzY2FwZSBlbiBtdWx0aXBsaWNhdGllIGluIGhldCBjeXRvcGxhc21hIHZlcnRyYWFnZC4gCgotIEluIGRlICBzdHVkaWUgdmVyZ2VsZWtlbiB6ZSBoZXQgcHJvdGVvbWUgdmFuIHdpbGQgdHlwZSBGLiB0dWxhcmVuc2lzIChXVCkgbWV0IGRhdCB2YW4gQXJnUC1nZW5lIGRlbGV0ZWQgRi4gdHVsYXJlbnNpcyAoa25vY2stb3V0LCBEOCkuIAoKLSBFbGsgYmlvLXJlcCB3ZXJkIGluIHRlY2huaXNjaCB0cmlwbGljYWF0IGdlcnVuZCBvcCBkZSBtYXNzLXNwZWN0cm9tZXRlci4gCgotIFdlIGdlYnJ1aWtlbiBkZSBkYXRhIHZvb3IgaGV0IDUwUyByaWJvc29tYWFsIHByb3Rlw69uZSBMNSBbQTBRNEo1XShodHRwczovL3d3dy51bmlwcm90Lm9yZy91bmlwcm90L0EwUTRKNSkKCiMjIERhdGEgZXhwbG9yYXRpZQoKYGBge3J9CmZyYW5jIDwtIHJlYWRfdHN2KCJodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vc3RhdE9taWNzL1BTTFMyMS9kYXRhL2ZyYW5jaXNlbGxhQTBRNEo1LnR4dCIpCmZyYW5jCmBgYAoKYGBge3J9CmZyYW5jICU+JSAKICBnZ3Bsb3QoYWVzKGJpb3JlcCwgaW50ZW5zaXR5TG9nMiwgY29sb3IgPSBnZW5vdHlwZSkpICsKICBnZW9tX3BvaW50KCkKYGBgCgotIFJlc3BvbnNlPyAKLSBFeHBlcmltZW50ZWxlIGVlbmhlaWQ/Ci0gT2JzZXJ2YXRpb25lbGUgZWVuaGVpZD8gCi0gRmFjdG9yZW4/IAoKJFxyaWdodGFycm93JCBQc2V1ZG8tcmVwbGljYXRpZSwgcmFuZG9taXNhdGllIG5hYXIgYmlvLXJlcCBlbiBlbGtlIGJpby1yZXAgd2VyZCAzIGtlZXIgZ2VtZXRlbi4gCgokXHJpZ2h0YXJyb3ckIEFscyB3ZSBkZSBkYXRhIHpvdWRlbiBhbmFseXNlcmVuIG1ldCBlZW4gbGluZWFpciBtb2RlbCBvcCBiYXNpcyB2YW4gZWxrZSBnZW1ldGVuIGludGVuc2l0ZWl0LCBkYW4gem91ZGVuIHdlIGRvZW4gYWxzb2Ygd2UgMTggYmlvLXJlcHMgaGFkZGVuIGluIGRlIHN0dWRpZS4gCiRccmlnaHRhcnJvdyQgRWZmZWN0IHZhbiBpbnRlcmVzc2UgdHVzc2VuIGRlIGJpby1yZXBzLiBCbG9rIGFuYWx5c2UgaXMgbmlldCBtb2dlbGlqayEgCgpBbHMgd2UgaGV0emVsZmRlIGFhbnRhbCB0ZWNobmlzY2hlIHJlcGxpY2F0ZW4gaGViYmVuIHZvb3IgZWxrZSBiaW8tcmVwOiAKCi0ga3VubmVuIHdlIGRlIHRlY2huaXNjaGUgcmVwbGljYXRlbiBlZXJzdCB1aXRtaWRkZWxlbiBvdmVyIGVsa2UgYmlvcmVwIG9tIG9uYWZoYW5rZWxpamtlIG1ldGluZ2VuIHRlIGJla29tZW4KLSBkZXplIGdlbWlkZGVsZGVuIHp1bGxlbiBkYW4gZWVuemVsZmRlIHByZWNpc2llIGhlYmJlbiAKLSB3ZSBrdW5uZW4gZGFuIGhldCBlZmZlY3QgdmFuIGRlIGJlaGFuZGVsaW5nIHNjaGF0dGVuIG9wIGRlIHNhbWVuZ2V2YXR0ZSB3YWFyZGVuIHBlciBiaW8tcmVwLiAKCi0gKipMRVQgT1A6IG1pZGRlbCBub29pdCB1aXQgb3ZlciBkZSBiaW8tcmVwcy9leHBlcmltZW50ZWxlIGVlbmhlZGVuKioKCgpgYGB7cn0KbG1CaW9yZXAgPC0gbG0oaW50ZW5zaXR5TG9nMiB+IC0xICsgYmlvcmVwLCBmcmFuYykKbG1CaW9yZXAKYGBgCgpgYGB7cn0KZnJhbmNTdW0gPC0gZGF0YS5mcmFtZShnZW5vdHlwZSA9IHJlcChjKCJEOCIsIldUIiksZWFjaD0zKSAlPiUgYXMuZmFjdG9yICU+JSByZWxldmVsKCJXVCIpLCBpbnRlbnNpdHlMb2cyID0gbG1CaW9yZXAkY29lZikKZnJhbmNTdW0KYGBgCgpgYGB7cn0KbG1TdW0gPC0gbG0oaW50ZW5zaXR5TG9nMiB+IGdlbm90eXBlLCBmcmFuY1N1bSkKc3VtbWFyeShsbVN1bSkKYGBgCgojIyBWZXJrZWVyZGUgYW5hbHlzZSAKCmBgYHtyfQpsbVdyb25nIDwtIGxtKGludGVuc2l0eUxvZzIgfiBnZW5vdHlwZSwgZnJhbmMpCnN1bW1hcnkobG1Xcm9uZykKYGBgCgpNZXJrIG9wIGRhdCBkZSB2ZXJrZWVyZGUgYW5hbHlzZSBnZWVuIHJla2VuaW5nIGhvdWR0IGRhdCBlciBtZWVyZGVyZSB0ZWNobmlzY2hlIHJlcGxpY2F0ZW4gemlqbiB2b29yIGVsa2UgYmlvLXJlcGVhdCB3YWFyZG9vciBlciBlZW4gYXJ0aWZpY2llZWwgc2lnbmlmaWNhbnQgcmVzdWx0YWF0IHdvcmR0IGJla29tZW4gb21kYXQgaGV0IHZvb3IgZGV6ZSBhbmFseXNlIGxpamt0IGFsc29mIGVyIDMgZHJpZSB6b3ZlZWwgb25hZmhhbmtlbGlqa2UgbWV0aW5nZW4gemlqbi4gICAKCiMjIyBUeXBlIEkgZm91dCEKCmBgYHtyfQpzaWdtYVdpdGhpbiA8LSBzaWdtYShsbUJpb3JlcCkKc2lnbWFCZXR3ZWVuIDwtIHNpZ21hKGxtU3VtKQp4QmlvcmVwIDwtIG1vZGVsLm1hdHJpeCh+LTErYmlvcmVwLGZyYW5jKQp4V3JvbmcgPC0gbW9kZWwubWF0cml4KH5nZW5vdHlwZSxmcmFuYykKCgpzZXQuc2VlZCgyNTIzKQpuU2ltIDwtIDEwMDAKcmVzV3JvbmcgPC0gbWF0cml4KE5BLG5TaW0sNCkgJT4lIGFzLmRhdGEuZnJhbWUKbmFtZXMocmVzV3JvbmcpIDwtIGMoIkVzdGltYXRlIiwiU3RkLiBFcnJvciIsInQgdmFsdWUiLCJwdmFsdWUiKQpyZXNDb3JyZWN0IDwtIHJlc1dyb25nCmdlbm90eXBlIDwtIGZyYW5jJGdlbm90eXBlCmdlbm90eXBlU3VtIDwtIGZyYW5jU3VtJGdlbm90eXBlCmJpb3JlcCA8LSBmcmFuYyRiaW9yZXAKCmZvciAoaSBpbiAxOm5TaW0pCnsKYmlvcmVwU2ltIDwtIHJub3JtKG5jb2woeEJpb3JlcCksc2Q9c2lnbWFCZXR3ZWVuKQp5U2ltIDwtIHhCaW9yZXAlKiViaW9yZXBTaW0gKyBybm9ybShucm93KHhCaW9yZXApLHNkPXNpZ21hV2l0aGluKQp5U3VtIDwtIGxtKHlTaW1+YmlvcmVwKSRjb2VmZmljaWVudApyZXNXcm9uZ1tpLF0gPC0gc3VtbWFyeShsbSh5U2ltfmdlbm90eXBlKSkkY29lZmZpY2llbnRbMixdCnJlc0NvcnJlY3RbaSxdPC1zdW1tYXJ5KGxtKHlTdW1+Z2Vub3R5cGVTdW0pKSRjb2VmZmljaWVudFsyLF0KfQptZWFuKHJlc0NvcnJlY3QkcHZhbHVlIDwgMC4wNSkKbWVhbihyZXNXcm9uZyRwdmFsdWUgPCAwLjA1KQpgYGAKCmBgYHtyfQpxcGxvdChyZXNDb3JyZWN0JHB2YWx1ZSxnZW9tID0gImhpc3RvZ3JhbSIsYm91bmRhcnk9YygwLDEpKSArCiAgc3RhdF9iaW4oYnJlYWtzPXNlcSgwLDEsLjEpKSArCiAgeGxhYigicHZhbHVlIikgKwogIGdndGl0bGUoIkNvcnJlY3QgYW5hbHlzaXMiKQogIApxcGxvdChyZXNXcm9uZyRwdmFsdWUsZ2VvbSA9ICJoaXN0b2dyYW0iLGJvdW5kYXJ5PWMoMCwxKSkgKwogIHN0YXRfYmluKGJyZWFrcz1zZXEoMCwxLC4xKSkgKwogIHhsYWIoInB2YWx1ZSIpICsKICBnZ3RpdGxlKCJXcm9uZyBhbmFseXNpcyIpCmBgYAoKLSBXZSBvYnNlcnZlcmVuIGRhdCBkZSBhbmFseXNlIGRpZSBnZWVuIHJla2VuaW5nIGhvdWR0IG1ldCBwc2V1ZG8tcmVwbGljYXRpZSB2ZWVsIHRlIGxpYmVyYWFsIGlzISAKCi0gRGUgYW5hbHlzZSB3YWFyYmlqIHdlIGVlcnN0IHVpdG1pZGRlbGxlbiBvdmVyIGRlIHRlY2huaXNjaGUgcmVwbGljYXRlbiBnZWVmdCBjb3JyZWN0ZSBwLXdhYXJkZW4gZW4gZWVuIGp1aXN0ZSBjb250cm9sZSB2YW4gZGUgdHlwZSBJIGZvdXQhIAoKIyMgVmVyc2NoaWxsZW5kIGFhbnRhbCB0ZWNobmlzY2hlIGhlcmhhbGluZ2VuPyAKCjEuIHN1YnNhbXBlbGVuIHRvdCB3ZSBlZW4gZ2VsaWprIGFhbnRhbCB0ZWNobmlzY2hlIGhlcmhhbGluZ2VuIGhlYmJlbiBlbiBkYW4gdWl0bWlkZGVsZW4gKHN1Ym9wdGltYWFsICRccmlnaHRhcnJvdyQgdmVybGllcyBhYW4gaW5mb3JtYXRpZSkgCgoyLiBNZWVyIGdlYXZhbmNlZXJkZSBtZXRob2RlbiB6aWpuIG5vb2R6YWtlbGlqazogCiAgCiAgICAtIGUuZy4gbWl4ZWQgbW9kZWxzIAogICAgLSBoZXQgbWl4ZWQgbW9kZWwgZnJhbWV3b3JrIGthbiBkZSBjb3JyZWxhdGllc3RydWN0dXVyIHZhbiBkZSBkYXRhIG1vZGVsbGVyZW4uIAogICAgCi0gTWl4ZWQgbW9kZWxzIGt1bm5lbiBvb2sgd29yZGVuIGdlYnJ1aWt0IHZvb3IgZGUgYW5hbHlzZSB2YW4gZGVzaWducyB3YWFyYmlqIG1lbiBpcyBnZcOvbnRlcmVzc2VlcmQgaW4gZWZmZWN0ZW4gZGllIHpvd2VsIGJpbm5lbiBhbHMgdHVzc2VuIGJsb2trZW4gZGllbmVuIHRlIHdvcmRlbiBnZXNjaGF0LCBlLmcuICBzcGxpdC1wbG90IGRlc2lnbnMuIAoKLSBNaXhlZCBtb2RlbHMgdmFsbGVuIGJ1aXRlbiBoZXQgYmVzdGVrIHZhbiBkZXplIGN1cnN1cyBtYWFyIHdvcmRlbiBiZWhhbmRlbGQgaW4gaGV0IG9wbGVpZGluZ3NvbmRlcmRlZWwgZ2VhdmFuY2VlcmRlIGJpb3N0YXRpc3RpZWsgaW4gZGUgbWFzdGVyIG9wbGVpZGluZwoKIyBOYXR1cmUgbWV0aG9kczogU3BsaXQtcGxvdCBkZXNpZ25zCgpbTmF0dXJlIE1ldGhvZHMgLSBQb2ludCBvZiBTaWduaWZpY2FuY2UgLSBTcGxpdC1wbG90IERlc2lnbnNdKGh0dHBzOi8vd3d3Lm5hdHVyZS5jb20vYXJ0aWNsZXMvbm1ldGguMzI5My5wZGYpICAK