Does clustering matter?

  • You want to measure how students respond to a new type of active learning method (computer based) in math class
  • You measure students math scores (DV) and the proportion of time (IV) they spend using the computer
    • You expect that the more time they spend doing the active learning method, the higher their math test scores will be
  • You have access to different classrooms, but only 20 students per class (too small to publish)
    • So you decide to treat all students like they are from the same classroom
  • Lets go collect (simulate) our study.

Simulation

  • Set the same slope per class, but set a different intercept per class. Create 4 linear regressions and merge them.
  • Note: You would not “know” that the slopes were the same and the intercepts differed.
#Set seed so your answers are all the same
set.seed(9)
# Sample Per class room people
n1 <- 20; n2 <- 20; n3 <- 20; n4 <- 20
N<-n1+n2+n3+n4 # Total N
# Uniform distrobution of proportion of time per classroom
X1 <- runif(n1,  0, .35)
X2 <- runif(n2, .3, .55)
X3 <- runif(n3, .5, .75)
X4 <- runif(n4, .7,1.0)
# noise per classroom
e1 <- rnorm(n1, 0, sd=2.5)
e2 <- rnorm(n2, 0, sd=2.5)
e3 <- rnorm(n3, 0, sd=2.5)
e4 <- rnorm(n4, 0, sd=2.5)
# Intercepts per classroom
B0.1 <- 80
B0.2 <- 70
B0.3 <- 60
B0.4 <- 50
# Same slope per classroom
B1=10
# Our equation to  create Y for each classroom
Y1 = B1*scale(X1,scale=F)  + B0.1 + e1
Y2 = B1*scale(X2,scale=F)  + B0.2 + e2
Y3 = B1*scale(X3,scale=F)  + B0.3 + e3
Y4 = B1*scale(X4,scale=F)  + B0.4 + e4
# Merge classrooms into 1 data.frame
Math.Data<-data.frame(Math=c(Y1,Y2,Y3,Y4),ActiveTime=c(X1,X2,X3,X4),
                      Classroom=c(rep("C1",n1),rep("C2",n2),rep("C3",n3),rep("C4",n4)),
                      StudentID=as.factor(1:N))

Step 1: Plot our results

  • We will ignore classroom and simply plot the results
    • Note: *ggplot2 lets us plot the results with a regression line automatically added
      • ggplot2 works by adding “layers” to our data
library(ggplot2)
theme_set(theme_bw())
ClassRoom.Plot.1 <-ggplot(data = Math.Data, 
                          aes(x = ActiveTime, y=Math))+ #scaffold
  geom_point()+ # add layer of scatterplot
  geom_smooth(method = "lm", se = TRUE)+ # add regression line
  xlab("Proportion of Time Engaged in Active Learning")+
  ylab("Math Score") # add labels
ClassRoom.Plot.1 #call plot

Step 2: Run Regression

  • We will run the regression across all classrooms
library(stargazer)
Class.All<-lm(Math~ActiveTime, data = Math.Data)
stargazer(Class.All,type="latex",
          intercept.bottom = FALSE, single.row=TRUE, 
          star.cutoffs=c(.05,.01,.001), notes.append = FALSE,
          header=FALSE)
  • Results show that a high intercept = 84.52 and a strong negative slope = -38.23 and a very high \(R^2\) = 0.8
  • You expected a positive slope, but the graph shows a negative one (and so clearly at that!)
  • With your massive \(R^2\) and counterintuitively strong negative slope you rush to publish in Psych Science and report, “Active learning is a flop! Dont waste your class time!”
    • Impact: Every math teacher in America changes their teaching, and the next generation of math students score worse than the last generation of student. You ruined America’s future. Good job!
    • What went wrong? Your \(R^2\) and slope were so huge you could not be possible wrong?!

What is Random?

  • Each kid add noise (is a random effect)
  • But are all the kids from all the classroom adding independent noise?
    • No! Students are not independent of each of other within the classroom
      • Error could be systematic (e.g., class clowns, class moral, room temp, time of day for the class) or unsystematic error, but that error that is unrelated to your main question
  • Each classroom is, therefore a “cluster” where the kids scores are inter-related based their shared experience
    • The kids are nested in the classroom!
      • Another way to think about it: The kids are repeated measures of their classroom

Re-examine by Cluster

  • Hierarchical designs: Students nested in classrooms [Clustered data] with student-level predictors Level 1
ClassRoom.Plot.2 <-ggplot(data = Math.Data, 
                          aes(x = ActiveTime, y=Math))+
  geom_point(aes(colour = Classroom, shape=Classroom))+ # we add color by cluster
  geom_smooth(method = "lm", se = TRUE)+
  xlab("Proportion of Time Engaged in Active Learning")+
  ylab("Math Score")+ # add labels
  theme(legend.position = "top")
ClassRoom.Plot.2

  • Clearly there are difference between classrooms (Note: real data is never this well clustered)
  • One solution that was used (back before mixed models) was to run the regression for each cluster
ClassRoom.Plot <-ggplot(data = Math.Data, 
                        aes(x = ActiveTime, y=Math))+ 
  geom_point(aes(colour = Classroom, shape=Classroom))+
  geom_smooth(method = "lm", se = TRUE, aes(group = Classroom))+# we add group level
  xlab("Proportion of Time Engaged in Active Learning")+ylab("Math Score")+ # add labels
  theme(legend.position = "top")
ClassRoom.Plot

  • The slopes look positive now! Lets run our regression for each classroom and check
Class1<-lm(Math~ActiveTime, data = subset(Math.Data,Classroom=="C1"))
Class2<-lm(Math~ActiveTime, data = subset(Math.Data,Classroom=="C2"))
Class3<-lm(Math~ActiveTime, data = subset(Math.Data,Classroom=="C3"))
Class4<-lm(Math~ActiveTime, data = subset(Math.Data,Classroom=="C4"))

stargazer(Class1,Class2,Class3,Class4,type="latex",
          intercept.bottom = FALSE, single.row=TRUE, 
          star.cutoffs=c(.05,.01,.001), notes.append = FALSE,
          header=FALSE)
  • Our intercepts are clearly different across classrooms
  • Our slopes are all around 10 (simulation lets them vary randomly), but in only one case is it significant
    • Why so few significant resulty; Only 1/4 classroom? That does not look very supportive our theory!
      • With so few kids per classroom, our result was really underpowered
    • What to do now?

Old School Approach 1

  • Extract the intercepts and slopes from each model and test if these 4 are difference from zero (Note: this makes more sense when you have 30 classroom)
  • One sample t-test (against 0)
    • R code note: we have to unname the coef from each from each model because the word intercept/slope might follow the numbers and we want to remove that for further analysis (i.e., strip the attribute)
intercepts.Math.Data<-unname(c(Class1$coef[1],Class2$coef[1],
                               Class3$coef[1],Class4$coef[1]))
M.Intercept<-mean(intercepts.Math.Data)
SD.Intercept<-sd(intercepts.Math.Data)
library(apa)
t.intercept.Math.Data<-t_apa(t.test(intercepts.Math.Data), print=FALSE)
  • The intercept mean = 57.87 with a SD = 17.58, and the one sample t-test, t(3) = 6.58, p = .007, d = 3.29
Slope.Math.Data<-unname(c(Class1$coef[2],Class2$coef[2],
                          Class3$coef[2],Class4$coef[2]))
M.Slope<-mean(Slope.Math.Data)
SD.Slopet<-sd(Slope.Math.Data)
t.Slope.Math.Data<-t_apa(t.test(Slope.Math.Data), print=FALSE)
  • The slope mean = 12.34 with a SD = 1.97, and the one sample t-test, t(3) = 12.51, p = .001, d = 6.25

  • \(R^2\) is alittle harder as you cannot simply take a mean and SD of the r values. You must do a Fisher’s r’-to-z \[ r.to.z' = \frac{1}{2}[log_e(1+r) - log_e(1-r)] = archtanh(r) \] and back again

\[ z'.to.r = tanh(z') \]

#Functions for fishers transforms
FisherRtoZ <-function(r) {.5*((log(1+r)-log(1-r)))}
FisherZtoR <-function(z) {tanh(z)}

R2.Math.Data<-FisherRtoZ(c(summary(Class1)$r.squared^.5,summary(Class2)$r.squared^.5,
                           summary(Class3)$r.squared^.5,summary(Class4)$r.squared^.5))
M.R2<-FisherZtoR(mean(R2.Math.Data))^2
SD.R2<-FisherZtoR(sd(R2.Math.Data))^2
t.R2.Math.Data<-t_apa(t.test(R2.Math.Data), print=FALSE)
  • The \(R^2\) mean = 0.17 with a SD = 0.01, and the one sample t-test, t(3) = 10.74, p = .002, d = 5.37

In all cases the \(R^2\), slope, and intercept were significantly different from zero, but is this analysis make sense? What have we lost? Well, we lost the SE from regression models. This is an anti-conservative approach (you dumped all the variance from the regressions)

Old School Approach 2

  • Since the intercepts differ, why can’t we just center the DV relative to each classroom?
  • Lets see what happens
    • Note: to make this happen in R, we will use the plyr package and mutate the data on the fly. This will add a new column to the data frame with the mean Math Score per class room. Then we can subtract the Math Score of each kid from the mean Math Score
### Rescale DV
library(plyr)
# Create a new variable in the data which is the mean of Math Score per class room (at each subject)
Math.Data<-ddply(Math.Data,.(Classroom), mutate, ClassMeanMath = mean(Math))
# next we center relative to each student relative to their class Math mean (how much to they differ from their cohort)
Math.Data$Math.Class.Centered<-Math.Data$Math-Math.Data$ClassMeanMath
  • Plot of the centered Math Scores
ClassRoom.Plot.3 <-ggplot(data = Math.Data, aes(x = ActiveTime, y=Math.Class.Centered))+ 
  geom_point(aes(colour = Classroom, shape=Classroom))+
  geom_smooth(method = "lm", se = TRUE)+# we add group level
  xlab("Proportion of Time Engaged in Active Learning")+
  ylab("Math Score (Classroom Centered)")+
  theme(legend.position = "top")
ClassRoom.Plot.3

  • We will re-run the regression across all classrooms on the centered scores
Class.All.C<-lm(Math.Class.Centered~ActiveTime, data = Math.Data)
stargazer(Class.All.C,type="latex",
          intercept.bottom = FALSE, single.row=TRUE, 
          star.cutoffs=c(.05,.01,.001), notes.append = FALSE,
          header=FALSE)
  • That also did not make any sense! Why? Look here
ClassRoom.Plot.4 <-ggplot(data = Math.Data, 
                          aes(x = ActiveTime, y=Math.Class.Centered))+ 
  geom_point(aes(colour = Classroom, shape=Classroom))+
  geom_smooth(method = "lm", se = TRUE)+# we add group level
  geom_smooth(method = "lm", se = TRUE, aes(group = Classroom, color=Classroom))+
  xlab("Proportion of Time Engaged in Active Learning")+
  ylab("Math Score (Classroom Centered)")+ # add labels
  theme(legend.position = "top")
ClassRoom.Plot.4

  • The problem is the range of time is different from each classroom.
    • We can try to recenter that as well
### Rescale IV
Math.Data<-ddply(Math.Data,.(Classroom), mutate, ClassActiveTime = mean(ActiveTime))
Math.Data$ActiveTime.Class.Centered<-Math.Data$ActiveTime-Math.Data$ClassActiveTime
  • Plot of the centered Math Scores and Active Time
ClassRoom.Plot.5 <-ggplot(data = Math.Data, 
                          aes(x = ActiveTime.Class.Centered, y=Math.Class.Centered))+ 
  geom_point(aes(colour = Classroom, shape=Classroom))+
  geom_smooth(method = "lm", se = TRUE)+# we add group level
  xlab("Proportion of Time Engaged in Active Learning\n(Centered per Classroom)")+
  ylab("Math Score (Classroom Centered")+ # add labels
  theme(legend.position = "top")
ClassRoom.Plot.5

  • Regression results
Class.All.C2<-lm(Math.Class.Centered~ActiveTime.Class.Centered, data = Math.Data)
stargazer(Class.All.C2,type="latex",
          intercept.bottom = FALSE, single.row=TRUE, 
          star.cutoffs=c(.05,.01,.001), notes.append = FALSE,
          header=FALSE)

Well now what do have? Well, our slope makes sense, our degrees of freedom are bigger (but wrong), but our intercept is always going to be zero! We lost our intercept.

Failures of Old School Approach

  • Losing the intercept is a huge problem (as it may be meaningful)
  • What do we assume for this analysis to make sense?
    • Slopes between classrooms are the same (they might not be)
    • DF are wrong because should be not be penalized for the number of schools we are testing
  • Think how confusing this analysis would be when we have like 50 classrooms
  • What happens when we want to test multiple predictors? Which do center and which do we not?
  • Big problem 1. What if you have different sample sizes per classroom.
  • Big problem 2. What if you have multiple classrooms at different schools [3 levels of data].

Hierarchical Linear Modeling

  • The old school approach work under very specific cases (so basically never useable)
  • HLM: is a special case of mixed models where we have nested data (clusters)
  • We will focus today only 2 levels
  • Next week Multilevel Models [MLM]: Students nested in classrooms with student-level and classroom-level predictors.
    • Today only student-level predictors

Levels

  • Level 1 = Smallest level (often subjects/students)
  • Level 2 = The group/cluster the students belong too (classrooms)
  • You can have higher levels as well, such as Level 3 = Classrooms nested in schools

HLM with only Level 1 predictors

  • Controlling for classroom differences, but making predictions about students

Equations (two level model)

  • Quick review:

\[ Y_i = \beta_1X_1 +\beta_0 + \epsilon\]

Level 1

  • Within each Group/Cluster (Students = \(i\) within Classroom 1 = \(j\), and we would replace \(j\) with \(g\) for Classroom 2 and so forth for each classroom!) \[y_{ij} = B_{1j}X_{ij} + B_{0j} + r_{ij} \]

  • Where \(B_{0j}\) = intercept in group j

  • Where \(B_{ij}\) = slope of students in group j

  • Where \(r_{ij}\) = residuals of each i (student) within group j

Level 2

  • Each Group/Cluster (Students = \(i\) within Classroom 1 = \(j\), Classroom 2 = \(g\), and so forth for each classroom)

Level 2 Intercept

\[B_{0j} = \gamma_{00}+u_{0j} \]

  • Where \(\gamma_{00}\) = intercept of the classroom
  • Where \(u_{0j}\) = random deviation of the classroom intercept from fixed population intercept
  • We assume students will vary randomly around the population intercept of classroom

Level 2 Slope

\[B_{1j} = \gamma_{10}+u_{1j} \]

  • Where \(\gamma_{10}\) = slope of the classroom
  • Where \(u_{1j}\) = random deviation of the classroom slope from fixed population slope
  • We assume students will vary randomly around the population intercept of classroom
  • Note: We would repeat this for each level of classroom we have (g, etc)

Mixed Equation

  • We put level 1 and level 2 together \[y_{ij} = (\gamma_{10}+u_{1j})X_{ij} + (\gamma_{00}+u_{0j})+ r_{ij} \]

Variance Components

  • We now have multiple sources of error in our equation
  • \(\sigma^2\) = variance of \(r_{ij}\), AKA variance due to error at level 1
  • \(\tau_{00}\) = variance of \(u_{0j}\), AKA variance of random intercepts at level 2
  • \(\tau_{11}\) = variance of \(u_{1j}\), AKA variance of random slopes at level 2
  • \(\tau_{01}\) = co-variance of \(u_{0j}\) and \(u_{1j}\), AKA random slopes and random intercept can correlate

ICC

  • The degree of clustering is measured via the inter-class correlation ICC

  • The proportion of total variance that is between the groups \[ ICC = \frac{\tau}{\tau + \sigma^2}\]

  • \(\tau\) = variance in a variable due to differences between a group

  • \(\sigma^2\) = total variance across groups

  • We would fit this on the null model (no level 1 predictors)

    • OLS regression assumes, ICC = 0: each classroom is unrelated to the others
    • If this value is large, it means that variance can be attributed to level 2

HLM analysis [Random Intecepts only Model]

  • Linear regressions only has fixed-effects [continuous] or fixed-factors [groups]: variables of interest (often your manipulation)
    • The only random factor assumed is your subject’s response
  • What if you have other effect or factors that might add random variation?
    • Random-effects/factors: Levels randomly sampled from a much larger population: Something you want to generalize across: Subjects, Classrooms, Words, Pictures, etc

LME4 package

  • LME4 was created as a modern approach to mixed models (replacing the nlme package).
  • LME4 was designed specifically to be flexible in how we can control the random effect [Crossed & Nested] and how they correlated with other. nlme handles nested structures well but does not handle complex structures well
  • There is a trade-off (we cannot use predefine covariance structures, but we will come back to that in a few weeks)
  • Also, there are no pvalues (yet)

lmer function

  • It is similar the lm function, but we add additional random effects
  • lmer(DV ~ IV +(1|RandomFactor), data = X, REML= FALSE)
    • (1|RandomFactor), means let the intercept of the random factor vary a function of the group (cluster).
    • In our case, (1|Classroom), means that we let each classroom have its own intercept
    • REML = FALSE, means use a Maximum Likelihood estimation (not redistricted ML. More on that later) [and of course not OLS]
    • Note: Kids were coded from 1 to total number of kids. We implicitly nested kids in that classrooms. Kid 1 is in classroom 1. Kids 21 is in classroom 2. Had we re-started the numbering to be 1, that might confuse lmer to think kid “1”” was in ALL classrooms (repeated) [if you set kids a random effect, which in this case you should not]

Analysis

  • Lets run our analysis first without centering the IVs so we can compare to our previous regressions

Null Model

  • No fixed factors (intercept only)
library(lme4)     #mixed model package by Douglas Bates et al
Model.Null<-lmer(Math ~1+(1|Classroom),  
                   data=Math.Data, REML=FALSE)
summary(Model.Null)
## Linear mixed model fit by maximum likelihood . t-tests use Satterthwaite's
##   method [lmerModLmerTest]
## Formula: Math ~ 1 + (1 | Classroom)
##    Data: Math.Data
## 
##      AIC      BIC   logLik deviance df.resid 
##    408.6    415.7   -201.3    402.6       77 
## 
## Scaled residuals: 
##      Min       1Q   Median       3Q      Max 
## -2.34755 -0.72263  0.01011  0.73079  2.21975 
## 
## Random effects:
##  Groups    Name        Variance Std.Dev.
##  Classroom (Intercept) 126.884  11.264  
##  Residual                6.668   2.582  
## Number of obs: 80, groups:  Classroom, 4
## 
## Fixed effects:
##             Estimate Std. Error    df t value Pr(>|t|)    
## (Intercept)    64.70       5.64  4.00   11.47 0.000329 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
  • Rule of thumb: t-values > |2| are significant
  • Next we examine the intercepts for each classroom (random effect)
ranef(Model.Null)
## $Classroom
##    (Intercept)
## C1   14.959676
## C2    5.307215
## C3   -5.190059
## C4  -15.076832
## 
## with conditional variances for "Classroom"
  • This is the difference from the grand mean
  • Look at ICC: The var and sd of these values are what was seen in the table above
library(performance)
ICC.Null<-icc(Model.Null)
ICC.Null
## # Intraclass Correlation Coefficient
## 
##     Adjusted ICC: 0.950
##   Unadjusted ICC: 0.950
  • \(ICC = \frac{\tau}{\tau + \sigma^2}\), where \(\tau\) = 126.884 & \(\sigma^2\) = 6.668
  • The ICC = 0.95 > 0, meaning we were correct to think of this as HLM problem

Visualizing the random effects

  • Our model fit the fixed and random effect and if we add in fixed factors, our random effects will change
  • In other words, the random effects (means, variances and covariances) are estimated based on the specification of the model
    • To understand the theory of the process of getting the estimates, see Best Linear Unbiased Predictors [BLUP]
library(sjPlot)
library(glmmTMB)
plot_model(Model.Null, type ='re',
         facet.grid = FALSE,
         sort.est = "sort.all",
         y.offset = .4)

Test Model

Model.1<-lmer(Math ~ActiveTime+(1|Classroom),  
                   data=Math.Data, REML=FALSE)
summary(Model.1)
## Linear mixed model fit by maximum likelihood . t-tests use Satterthwaite's
##   method [lmerModLmerTest]
## Formula: Math ~ ActiveTime + (1 | Classroom)
##    Data: Math.Data
## 
##      AIC      BIC   logLik deviance df.resid 
##    399.3    408.8   -195.6    391.3       76 
## 
## Scaled residuals: 
##      Min       1Q   Median       3Q      Max 
## -2.13111 -0.77650 -0.04481  0.61218  2.50097 
## 
## Random effects:
##  Groups    Name        Variance Std.Dev.
##  Classroom (Intercept) 200.00   14.142  
##  Residual                5.61    2.368  
## Number of obs: 80, groups:  Classroom, 4
## 
## Fixed effects:
##             Estimate Std. Error     df t value Pr(>|t|)    
## (Intercept)   58.862      7.261  4.315   8.107 0.000907 ***
## ActiveTime    11.269      3.140 77.695   3.589 0.000579 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Correlation of Fixed Effects:
##            (Intr)
## ActiveTime -0.224
  • This looks like our final lm model (but we have an intercept)
  • Lets sum it up.
    • Our intercept was 11.27, which was significant
    • Our slope was 58.86, which was significant
    • Our random effects changed (now we added fixed effects)
plot_model(Model.1, type ='re',
         facet.grid = FALSE,
         sort.est = "sort.all",
         y.offset = .4)

What happened to \(R^2\)?

Plot the final model and \(R^2\) problems

  • I find “effects” package most accurate
library(effects)
Results.Model.1<-Effect(c("ActiveTime"),Model.1,
     xlevels=list(ActiveTime=seq(0,1,.2)))
     
Results.Model.1<-as.data.frame(Results.Model.1)

Final.Fixed.Plot.1 <-ggplot(data = Results.Model.1, 
                            aes(x = ActiveTime, y =fit))+
  geom_line(size=2)+
  coord_cartesian(xlim = c(0, 1),ylim = c(0, 100))+ 
  geom_ribbon(aes(ymin=fit-se, ymax=fit+se),alpha=.2)+
  xlab("Proportion of Time Engaged in Active Learning")+
  ylab("Math Score")+ 
  theme(legend.position = "none")
Final.Fixed.Plot.1

  • Someone will ask you, “I want to see the real data”, meaning they want to see how the line fits the data.
  • We plot the fixed effect on top of the raw data
#Fixed only, removed random effects
Math.Data$Model.1.Fitted<-predict(Model.1, re.form=NA)
     
ClassRoom.Plot.Bad <-ggplot(data = Math.Data)+ 
  coord_cartesian(xlim = c(0, 1),ylim = c(0, 100))+ 
  geom_point( aes(x = ActiveTime, y=Math, 
                                   colour = Classroom, shape=Classroom))+
  geom_line(aes(x = ActiveTime, y=Model.1.Fitted))+
  xlab("Proportion of Time Engaged in Active Learning")+
  ylab("Math Score")+ 
  theme(legend.position = "top")
ClassRoom.Plot.Bad

  • Next, they will say, “Wow your model sucks and your \(R^2\) must be terrible. You suck as a modeler,”
  • To test the \(R^2\) of the fixed effect of the final model over the raw data (meaning we IGNORE the random effects), all we have to do is run a linear regression of the fitted data fixed data to the raw data
  • Nakagawa and Schielzeth (2013): function from https://github.com/jslefche/rsquared.glmm/blob/master/rsquaredglmm.R
    • \(R^2_{Marginal}\) = (fixed variance / Total variance)

\[ R^2_{Marginal} = \frac{\sigma^2_{fixed}} {\sigma^2_{fixed}+\sigma^2_{random}+\sigma^2_{residual}} \]

source('RsquaredHelper.R')
rsquared.glmm(Model.1)
##             Class   Family     Link   Marginal Conditional      AIC
## 1 lmerModLmerTest gaussian identity 0.04359631   0.9739071 399.2721
  • The Marginal \(R^2\) is terrible.

  • Often you cannot simply add a scatterplot. However, there are ways around this (but you end up plotting every classroom)

  • There is a fast and dirty method (but it will not work well when you have lots of control variables)

# Fixed + Random effects
Math.Data$Model.1.Fitted.Random<-predict(Model.1) 
     
ClassRoom.Plot.Better <-ggplot(data = Math.Data)+ 
  coord_cartesian(xlim = c(0, 1),ylim = c(0, 100))+ 
  geom_point(aes(x = ActiveTime, y=Math, 
                                   colour = Classroom, shape=Classroom))+
  geom_line(aes(x = ActiveTime, y=Model.1.Fitted.Random, 
                                  colour = Classroom))+
  xlab("Proportion of Time Engaged in Active Learning")+
  ylab("Math Score")+
  theme(legend.position = "top")
ClassRoom.Plot.Better

  • This is what the model basically fit (controling for the random effect)!

  • a more accurate way to graph it is show the data in its clusters

ClassRoom.Plot.Best <-ggplot(data = Math.Data, group=Classroom)+ 
  facet_grid(Classroom~.)+
  coord_cartesian(xlim = c(0, 1),ylim = c(0, 100))+ 
  geom_point(aes(x = ActiveTime, y=Math, 
                                   colour = Classroom, shape=Classroom))+
  geom_line(aes(x = ActiveTime, y=Model.1.Fitted.Random, 
                                  colour = Classroom))+
  xlab("Proportion of Time Engaged in Active Learning")+
  ylab("Math Score")+
  theme(legend.position = "top")
ClassRoom.Plot.Best

  • The conditional \(R^2\) will be crazy high now (fixed variance + random variance/ Total variance)

\[ R^2_{conditional} = \frac{\sigma^2_{fixed}+\sigma^2_{random}} {\sigma^2_{fixed}+\sigma^2_{random}+\sigma^2_{residual}} \]

rsquared.glmm(Model.1)
##             Class   Family     Link   Marginal Conditional      AIC
## 1 lmerModLmerTest gaussian identity 0.04359631   0.9739071 399.2721

\(R^2\)

  • Both \(R^2\) values (fixed or fixed + random) dont really have any meaning in the traditional sense
    • I showed you before with ICC how much of the variance was explained by the random effect
  • Now I am just making it look like I fit all the data and I am the best modeler in the world.
    • In reality it is hard to really how good our fit is, so instead, we ask the question is the model with our predicts a better “fit” than our null or lower order model.

Deviance testing (-2 Log-Likelihood)

  • We will cover the theory of this more next week
  • For now just look at the pvalue (is model 1 better than the null model)
anova(Model.Null,Model.1)
## Data: Math.Data
## Models:
## Model.Null: Math ~ 1 + (1 | Classroom)
## Model.1: Math ~ ActiveTime + (1 | Classroom)
##            npar    AIC    BIC  logLik deviance  Chisq Df Pr(>Chisq)    
## Model.Null    3 408.59 415.74 -201.30   402.59                         
## Model.1       4 399.27 408.80 -195.64   391.27 11.321  1  0.0007662 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Centering

  • I have not yet shown you how to center the IV correctly
  • There are two approaches: Grand mean vs Group centering
    • When to center at the grand mean or the group?
      • This depends on your question and the data

Grand mean

  • You may want to ask a grand mean question (how all people are impacted by the predictor regardless of group), but the data may not allow that question to be asked (if the predictor is confounded with group)
    • For example, in our example its dangerous to make predictions as ActiveTime range is confounded with classroom!
      • Remember be careful about making predictions about data you do not have
Math.Data$ActiveTime.C<-scale(Math.Data$ActiveTime, center=TRUE, scale=FALSE)
Model.1.GM<-lmer(Math ~ActiveTime.C+(1|Classroom),  
                   data=Math.Data, REML=FALSE)
summary(Model.1.GM)
## Linear mixed model fit by maximum likelihood . t-tests use Satterthwaite's
##   method [lmerModLmerTest]
## Formula: Math ~ ActiveTime.C + (1 | Classroom)
##    Data: Math.Data
## 
##      AIC      BIC   logLik deviance df.resid 
##    399.3    408.8   -195.6    391.3       76 
## 
## Scaled residuals: 
##      Min       1Q   Median       3Q      Max 
## -2.13111 -0.77650 -0.04481  0.61218  2.50097 
## 
## Random effects:
##  Groups    Name        Variance Std.Dev.
##  Classroom (Intercept) 200.00   14.142  
##  Residual                5.61    2.368  
## Number of obs: 80, groups:  Classroom, 4
## 
## Fixed effects:
##              Estimate Std. Error     df t value Pr(>|t|)    
## (Intercept)    64.704      7.076  3.897   9.144 0.000896 ***
## ActiveTime.C   11.269      3.140 77.695   3.589 0.000579 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Correlation of Fixed Effects:
##             (Intr)
## ActiveTim.C 0.000
  • You will notice the slope is the same as before, but intercept changed
  • The intercept is now the mean of ActiveTime = 0.5183501
  • Why? See below:
Math.Data$Model.1.Fitted.Random.GM<-predict(Model.1.GM) #plots fixed Random effects
     
ClassRoom.Plot.Better <-ggplot()+ 
  coord_cartesian(xlim = c(-.5,.5),ylim = c(0, 100))+ 
  geom_point(data = Math.Data, aes(x = ActiveTime.C, y=Math, 
                                   colour = Classroom, shape=Classroom))+
  geom_line(data = Math.Data, aes(x = ActiveTime.C, y=Model.1.Fitted.Random.GM, 
                                  colour = Classroom))+
  xlab("Proportion of Time Engaged in Active Learning\n(Grand Mean Centered)")+
  ylab("Math Score")+
  theme(legend.position = "top")
ClassRoom.Plot.Better

Group centering

  • Group centering asks how does the within-group variation of the predictor predicts the outcome
    • This is often a very interesting question and may not always differ in story from the grand mean
  • However, technically its meaning has changed!
    • So, you must think about what it means for your data to have centered within group
  • I how shown you how to code this already actually, reminder of code:
### Rescale IV
library(plyr)
Math.Data<-ddply(Math.Data,.(Classroom), mutate, ClassActiveTime = mean(ActiveTime))
Math.Data$ActiveTime.Class.Centered<-Math.Data$ActiveTime-Math.Data$ClassActiveTime
  • In this case our model will change a bit
Model.1.GC<-lmer(Math ~ActiveTime.Class.Centered+(1|Classroom),  
                   data=Math.Data, REML=FALSE)
summary(Model.1.GC)
## Linear mixed model fit by maximum likelihood . t-tests use Satterthwaite's
##   method [lmerModLmerTest]
## Formula: Math ~ ActiveTime.Class.Centered + (1 | Classroom)
##    Data: Math.Data
## 
##      AIC      BIC   logLik deviance df.resid 
##    397.4    406.9   -194.7    389.4       76 
## 
## Scaled residuals: 
##      Min       1Q   Median       3Q      Max 
## -2.16057 -0.78325 -0.04672  0.62451  2.50992 
## 
## Random effects:
##  Groups    Name        Variance Std.Dev.
##  Classroom (Intercept) 126.937  11.267  
##  Residual                5.606   2.368  
## Number of obs: 80, groups:  Classroom, 4
## 
## Fixed effects:
##                           Estimate Std. Error     df t value Pr(>|t|)    
## (Intercept)                 64.704      5.640  4.000  11.473 0.000329 ***
## ActiveTime.Class.Centered   11.990      3.159 76.000   3.795 0.000295 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Correlation of Fixed Effects:
##             (Intr)
## ActvTm.Cl.C 0.000
  • The slope and intercept changed. Why? See below:
Math.Data$Model.1.Fitted.Random.GC<-predict(Model.1.GC) #plots fixed Random effects
     
ClassRoom.Plot.Better <-ggplot()+ 
  coord_cartesian(xlim = c(-.3,.3),ylim = c(0, 100))+ 
  geom_point(data = Math.Data, aes(x = ActiveTime.Class.Centered, y=Math, 
                                   colour = Classroom, shape=Classroom))+
  geom_line(data = Math.Data, aes(x = ActiveTime.Class.Centered, y=Model.1.Fitted.Random.GC, 
                                  colour = Classroom))+
  xlab("Proportion of Time Engaged in Active Learning\n(Centered per Classroom)")+
  ylab("Math Score")+
  theme(legend.position = "top")
ClassRoom.Plot.Better

  • Make sure to pay attention to the x-axis!
LS0tDQp0aXRsZTogJ0hpZXJhcmNoaWNhbCBMaW5lYXIgTW9kZWxpbmcnDQpvdXRwdXQ6DQogIGh0bWxfZG9jdW1lbnQ6DQogICAgY29kZV9kb3dubG9hZDogeWVzDQogICAgZm9udHNpemU6IDhwdA0KICAgIGhpZ2hsaWdodDogdGV4dG1hdGUNCiAgICBudW1iZXJfc2VjdGlvbnM6IG5vDQogICAgdGhlbWU6IGZsYXRseQ0KICAgIHRvYzogeWVzDQogICAgdG9jX2Zsb2F0Og0KICAgICAgY29sbGFwc2VkOiBubw0KLS0tDQoNCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFfQ0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGNhY2hlPVRSVUUpDQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUpDQprbml0cjo6b3B0c19jaHVuayRzZXQobWVzc2FnZSA9IEZBTFNFKQ0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KHdhcm5pbmcgPSAgRkFMU0UpDQprbml0cjo6b3B0c19jaHVuayRzZXQoZmlnLndpZHRoPTQuMjUpDQprbml0cjo6b3B0c19jaHVuayRzZXQoZmlnLmhlaWdodD00LjApDQprbml0cjo6b3B0c19jaHVuayRzZXQoZmlnLmFsaWduPSdjZW50ZXInKSANCmBgYA0KDQoNCiMgRG9lcyBjbHVzdGVyaW5nIG1hdHRlcj8NCi0gWW91IHdhbnQgdG8gbWVhc3VyZSBob3cgc3R1ZGVudHMgcmVzcG9uZCB0byBhIG5ldyB0eXBlIG9mIGFjdGl2ZSBsZWFybmluZyBtZXRob2QgKGNvbXB1dGVyIGJhc2VkKSAgaW4gbWF0aCBjbGFzcw0KLSBZb3UgbWVhc3VyZSBzdHVkZW50cyAqKm1hdGggc2NvcmVzKiogKERWKSBhbmQgdGhlICoqcHJvcG9ydGlvbiBvZiB0aW1lKiogKElWKSB0aGV5IHNwZW5kIHVzaW5nIHRoZSBjb21wdXRlcg0KICAgIC0gWW91IGV4cGVjdCB0aGF0IHRoZSBtb3JlIHRpbWUgdGhleSBzcGVuZCBkb2luZyB0aGUgYWN0aXZlIGxlYXJuaW5nIG1ldGhvZCwgdGhlIGhpZ2hlciB0aGVpciBtYXRoIHRlc3Qgc2NvcmVzIHdpbGwgYmUNCi0gWW91IGhhdmUgYWNjZXNzIHRvIGRpZmZlcmVudCBjbGFzc3Jvb21zLCBidXQgb25seSAqKjIwKiogc3R1ZGVudHMgcGVyIGNsYXNzICh0b28gc21hbGwgdG8gcHVibGlzaCkNCiAgICAtIFNvIHlvdSBkZWNpZGUgdG8gdHJlYXQgYWxsIHN0dWRlbnRzIGxpa2UgdGhleSBhcmUgZnJvbSB0aGUgc2FtZSBjbGFzc3Jvb20NCi0gTGV0cyBnbyAqY29sbGVjdCogKHNpbXVsYXRlKSBvdXIgc3R1ZHkuIA0KDQojIyBTaW11bGF0aW9uIA0KLSBTZXQgdGhlIHNhbWUgc2xvcGUgcGVyIGNsYXNzLCBidXQgc2V0IGEgZGlmZmVyZW50IGludGVyY2VwdCBwZXIgY2xhc3MuIENyZWF0ZSA0IGxpbmVhciByZWdyZXNzaW9ucyBhbmQgbWVyZ2UgdGhlbS4gDQotICpOb3RlKjogWW91IHdvdWxkIG5vdCAia25vdyIgdGhhdCB0aGUgc2xvcGVzIHdlcmUgdGhlIHNhbWUgYW5kIHRoZSBpbnRlcmNlcHRzIGRpZmZlcmVkLiANCg0KYGBge3J9DQojU2V0IHNlZWQgc28geW91ciBhbnN3ZXJzIGFyZSBhbGwgdGhlIHNhbWUNCnNldC5zZWVkKDkpDQojIFNhbXBsZSBQZXIgY2xhc3Mgcm9vbSBwZW9wbGUNCm4xIDwtIDIwOyBuMiA8LSAyMDsgbjMgPC0gMjA7IG40IDwtIDIwDQpOPC1uMStuMituMytuNCAjIFRvdGFsIE4NCiMgVW5pZm9ybSBkaXN0cm9idXRpb24gb2YgcHJvcG9ydGlvbiBvZiB0aW1lIHBlciBjbGFzc3Jvb20NClgxIDwtIHJ1bmlmKG4xLCAgMCwgLjM1KQ0KWDIgPC0gcnVuaWYobjIsIC4zLCAuNTUpDQpYMyA8LSBydW5pZihuMywgLjUsIC43NSkNClg0IDwtIHJ1bmlmKG40LCAuNywxLjApDQojIG5vaXNlIHBlciBjbGFzc3Jvb20NCmUxIDwtIHJub3JtKG4xLCAwLCBzZD0yLjUpDQplMiA8LSBybm9ybShuMiwgMCwgc2Q9Mi41KQ0KZTMgPC0gcm5vcm0objMsIDAsIHNkPTIuNSkNCmU0IDwtIHJub3JtKG40LCAwLCBzZD0yLjUpDQojIEludGVyY2VwdHMgcGVyIGNsYXNzcm9vbQ0KQjAuMSA8LSA4MA0KQjAuMiA8LSA3MA0KQjAuMyA8LSA2MA0KQjAuNCA8LSA1MA0KIyBTYW1lIHNsb3BlIHBlciBjbGFzc3Jvb20NCkIxPTEwDQojIE91ciBlcXVhdGlvbiB0byAgY3JlYXRlIFkgZm9yIGVhY2ggY2xhc3Nyb29tDQpZMSA9IEIxKnNjYWxlKFgxLHNjYWxlPUYpICArIEIwLjEgKyBlMQ0KWTIgPSBCMSpzY2FsZShYMixzY2FsZT1GKSAgKyBCMC4yICsgZTINClkzID0gQjEqc2NhbGUoWDMsc2NhbGU9RikgICsgQjAuMyArIGUzDQpZNCA9IEIxKnNjYWxlKFg0LHNjYWxlPUYpICArIEIwLjQgKyBlNA0KIyBNZXJnZSBjbGFzc3Jvb21zIGludG8gMSBkYXRhLmZyYW1lDQpNYXRoLkRhdGE8LWRhdGEuZnJhbWUoTWF0aD1jKFkxLFkyLFkzLFk0KSxBY3RpdmVUaW1lPWMoWDEsWDIsWDMsWDQpLA0KICAgICAgICAgICAgICAgICAgICAgIENsYXNzcm9vbT1jKHJlcCgiQzEiLG4xKSxyZXAoIkMyIixuMikscmVwKCJDMyIsbjMpLHJlcCgiQzQiLG40KSksDQogICAgICAgICAgICAgICAgICAgICAgU3R1ZGVudElEPWFzLmZhY3RvcigxOk4pKQ0KYGBgDQoNCiMjIyBTdGVwIDE6IFBsb3Qgb3VyIHJlc3VsdHMNCi0gV2Ugd2lsbCBpZ25vcmUgY2xhc3Nyb29tIGFuZCBzaW1wbHkgcGxvdCB0aGUgcmVzdWx0cw0KICAgIC0gKk5vdGUqOiAqKipnZ3Bsb3QyKiogbGV0cyB1cyBwbG90IHRoZSByZXN1bHRzIHdpdGggYSByZWdyZXNzaW9uIGxpbmUgYXV0b21hdGljYWxseSBhZGRlZA0KICAgICAgICAgLSAqKmdncGxvdDIqKiB3b3JrcyBieSBhZGRpbmcgImxheWVycyIgdG8gb3VyIGRhdGENCiAgICANCmBgYHtyfQ0KbGlicmFyeShnZ3Bsb3QyKQ0KdGhlbWVfc2V0KHRoZW1lX2J3KCkpDQpDbGFzc1Jvb20uUGxvdC4xIDwtZ2dwbG90KGRhdGEgPSBNYXRoLkRhdGEsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICBhZXMoeCA9IEFjdGl2ZVRpbWUsIHk9TWF0aCkpKyAjc2NhZmZvbGQNCiAgZ2VvbV9wb2ludCgpKyAjIGFkZCBsYXllciBvZiBzY2F0dGVycGxvdA0KICBnZW9tX3Ntb290aChtZXRob2QgPSAibG0iLCBzZSA9IFRSVUUpKyAjIGFkZCByZWdyZXNzaW9uIGxpbmUNCiAgeGxhYigiUHJvcG9ydGlvbiBvZiBUaW1lIEVuZ2FnZWQgaW4gQWN0aXZlIExlYXJuaW5nIikrDQogIHlsYWIoIk1hdGggU2NvcmUiKSAjIGFkZCBsYWJlbHMNCkNsYXNzUm9vbS5QbG90LjEgI2NhbGwgcGxvdA0KYGBgDQoNCg0KDQojIyMgIFN0ZXAgMjogUnVuIFJlZ3Jlc3Npb24NCi0gV2Ugd2lsbCBydW4gdGhlIHJlZ3Jlc3Npb24gKiphY3Jvc3MqKiBhbGwgY2xhc3Nyb29tcw0KYGBge3IsIHJlc3VsdHM9ImFzaXMifQ0KbGlicmFyeShzdGFyZ2F6ZXIpDQpDbGFzcy5BbGw8LWxtKE1hdGh+QWN0aXZlVGltZSwgZGF0YSA9IE1hdGguRGF0YSkNCnN0YXJnYXplcihDbGFzcy5BbGwsdHlwZT0ibGF0ZXgiLA0KICAgICAgICAgIGludGVyY2VwdC5ib3R0b20gPSBGQUxTRSwgc2luZ2xlLnJvdz1UUlVFLCANCiAgICAgICAgICBzdGFyLmN1dG9mZnM9YyguMDUsLjAxLC4wMDEpLCBub3Rlcy5hcHBlbmQgPSBGQUxTRSwNCiAgICAgICAgICBoZWFkZXI9RkFMU0UpDQpgYGANCg0KLSBSZXN1bHRzIHNob3cgdGhhdCBhIGhpZ2ggaW50ZXJjZXB0ID0gYHIgcm91bmQoQ2xhc3MuQWxsJGNvZWZbMV0sMilgIGFuZCBhIHN0cm9uZyBuZWdhdGl2ZSBzbG9wZSA9IGByIHJvdW5kKENsYXNzLkFsbCRjb2VmWzJdLDIpYCBhbmQgYSB2ZXJ5IGhpZ2ggJFJeMiQgPSBgciByb3VuZChzdW1tYXJ5KENsYXNzLkFsbCkkci5zcXVhcmVkLDIpYA0KLSBZb3UgZXhwZWN0ZWQgYSBwb3NpdGl2ZSBzbG9wZSwgYnV0IHRoZSBncmFwaCBzaG93cyBhIG5lZ2F0aXZlIG9uZSAoYW5kIHNvIGNsZWFybHkgYXQgdGhhdCEpDQotIFdpdGggeW91ciBtYXNzaXZlICRSXjIkIGFuZCBjb3VudGVyaW50dWl0aXZlbHkgc3Ryb25nIG5lZ2F0aXZlIHNsb3BlIHlvdSBydXNoIHRvIHB1Ymxpc2ggaW4gKipQc3ljaCBTY2llbmNlKiogYW5kIHJlcG9ydCwgIkFjdGl2ZSBsZWFybmluZyBpcyBhIGZsb3AhIERvbnQgd2FzdGUgeW91ciBjbGFzcyB0aW1lISINCiAgICAtICpJbXBhY3QqOiBFdmVyeSBtYXRoIHRlYWNoZXIgaW4gQW1lcmljYSBjaGFuZ2VzIHRoZWlyIHRlYWNoaW5nLCBhbmQgdGhlIG5leHQgZ2VuZXJhdGlvbiBvZiBtYXRoIHN0dWRlbnRzIHNjb3JlICp3b3JzZSogdGhhbiB0aGUgbGFzdCBnZW5lcmF0aW9uIG9mIHN0dWRlbnQuIFlvdSBydWluZWQgQW1lcmljYSdzIGZ1dHVyZS4gR29vZCBqb2IhIA0KICAgIC0gKipXaGF0IHdlbnQgd3Jvbmc/IFlvdXIgJFJeMiQgYW5kIHNsb3BlIHdlcmUgc28gaHVnZSB5b3UgY291bGQgbm90IGJlIHBvc3NpYmxlIHdyb25nPyEqKiANCg0KIyMgV2hhdCBpcyBSYW5kb20/DQotIEVhY2gga2lkIGFkZCBub2lzZSAoaXMgYSByYW5kb20gZWZmZWN0KQ0KLSBCdXQgYXJlIGFsbCB0aGUga2lkcyBmcm9tIGFsbCB0aGUgY2xhc3Nyb29tIGFkZGluZyBpbmRlcGVuZGVudCBub2lzZT8gDQogICAgLSBObyEgU3R1ZGVudHMgYXJlIG5vdCBpbmRlcGVuZGVudCBvZiBlYWNoIG9mIG90aGVyIHdpdGhpbiB0aGUgY2xhc3Nyb29tIA0KICAgICAgICAtIEVycm9yIGNvdWxkIGJlIHN5c3RlbWF0aWMgKGUuZy4sIGNsYXNzIGNsb3ducywgY2xhc3MgbW9yYWwsIHJvb20gdGVtcCwgdGltZSBvZiBkYXkgZm9yIHRoZSBjbGFzcykgb3IgdW5zeXN0ZW1hdGljIGVycm9yLCBidXQgdGhhdCBlcnJvciB0aGF0IGlzICp1bnJlbGF0ZWQqIHRvIHlvdXIgbWFpbiBxdWVzdGlvbg0KLSBFYWNoIGNsYXNzcm9vbSBpcywgdGhlcmVmb3JlIGEgImNsdXN0ZXIiIHdoZXJlIHRoZSBraWRzIHNjb3JlcyBhcmUgaW50ZXItcmVsYXRlZCBiYXNlZCB0aGVpciBzaGFyZWQgZXhwZXJpZW5jZQ0KICAgIC0gVGhlIGtpZHMgYXJlICoqbmVzdGVkKiogaW4gdGhlIGNsYXNzcm9vbSENCiAgICAgICAgLSBBbm90aGVyIHdheSB0byB0aGluayBhYm91dCBpdDogVGhlIGtpZHMgYXJlIHJlcGVhdGVkIG1lYXN1cmVzIG9mIHRoZWlyIGNsYXNzcm9vbQ0KICAgICAgICANCiMjIFJlLWV4YW1pbmUgYnkgQ2x1c3Rlcg0KLSBIaWVyYXJjaGljYWwgZGVzaWduczogU3R1ZGVudHMgbmVzdGVkIGluIGNsYXNzcm9vbXMgW0NsdXN0ZXJlZCBkYXRhXSB3aXRoICpzdHVkZW50LWxldmVsIHByZWRpY3RvcnMqIFtMZXZlbCAxXQ0KDQpgYGB7cn0NCkNsYXNzUm9vbS5QbG90LjIgPC1nZ3Bsb3QoZGF0YSA9IE1hdGguRGF0YSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgIGFlcyh4ID0gQWN0aXZlVGltZSwgeT1NYXRoKSkrDQogIGdlb21fcG9pbnQoYWVzKGNvbG91ciA9IENsYXNzcm9vbSwgc2hhcGU9Q2xhc3Nyb29tKSkrICMgd2UgYWRkIGNvbG9yIGJ5IGNsdXN0ZXINCiAgZ2VvbV9zbW9vdGgobWV0aG9kID0gImxtIiwgc2UgPSBUUlVFKSsNCiAgeGxhYigiUHJvcG9ydGlvbiBvZiBUaW1lIEVuZ2FnZWQgaW4gQWN0aXZlIExlYXJuaW5nIikrDQogIHlsYWIoIk1hdGggU2NvcmUiKSsgIyBhZGQgbGFiZWxzDQogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJ0b3AiKQ0KQ2xhc3NSb29tLlBsb3QuMg0KYGBgDQoNCi0gQ2xlYXJseSB0aGVyZSBhcmUgZGlmZmVyZW5jZSBiZXR3ZWVuIGNsYXNzcm9vbXMgKE5vdGU6IHJlYWwgZGF0YSBpcyBuZXZlciB0aGlzIHdlbGwgY2x1c3RlcmVkKQ0KLSBPbmUgc29sdXRpb24gdGhhdCB3YXMgdXNlZCAoYmFjayBiZWZvcmUgbWl4ZWQgbW9kZWxzKSB3YXMgdG8gcnVuIHRoZSByZWdyZXNzaW9uIGZvciBlYWNoIGNsdXN0ZXINCg0KYGBge3J9DQpDbGFzc1Jvb20uUGxvdCA8LWdncGxvdChkYXRhID0gTWF0aC5EYXRhLCANCiAgICAgICAgICAgICAgICAgICAgICAgIGFlcyh4ID0gQWN0aXZlVGltZSwgeT1NYXRoKSkrIA0KICBnZW9tX3BvaW50KGFlcyhjb2xvdXIgPSBDbGFzc3Jvb20sIHNoYXBlPUNsYXNzcm9vbSkpKw0KICBnZW9tX3Ntb290aChtZXRob2QgPSAibG0iLCBzZSA9IFRSVUUsIGFlcyhncm91cCA9IENsYXNzcm9vbSkpKyMgd2UgYWRkIGdyb3VwIGxldmVsDQogIHhsYWIoIlByb3BvcnRpb24gb2YgVGltZSBFbmdhZ2VkIGluIEFjdGl2ZSBMZWFybmluZyIpK3lsYWIoIk1hdGggU2NvcmUiKSsgIyBhZGQgbGFiZWxzDQogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJ0b3AiKQ0KQ2xhc3NSb29tLlBsb3QNCmBgYA0KDQotIFRoZSBzbG9wZXMgbG9vayBwb3NpdGl2ZSBub3chIExldHMgcnVuIG91ciByZWdyZXNzaW9uIGZvciBlYWNoIGNsYXNzcm9vbSBhbmQgY2hlY2sNCg0KYGBge3IsIHJlc3VsdHM9ImFzaXMifQ0KQ2xhc3MxPC1sbShNYXRofkFjdGl2ZVRpbWUsIGRhdGEgPSBzdWJzZXQoTWF0aC5EYXRhLENsYXNzcm9vbT09IkMxIikpDQpDbGFzczI8LWxtKE1hdGh+QWN0aXZlVGltZSwgZGF0YSA9IHN1YnNldChNYXRoLkRhdGEsQ2xhc3Nyb29tPT0iQzIiKSkNCkNsYXNzMzwtbG0oTWF0aH5BY3RpdmVUaW1lLCBkYXRhID0gc3Vic2V0KE1hdGguRGF0YSxDbGFzc3Jvb209PSJDMyIpKQ0KQ2xhc3M0PC1sbShNYXRofkFjdGl2ZVRpbWUsIGRhdGEgPSBzdWJzZXQoTWF0aC5EYXRhLENsYXNzcm9vbT09IkM0IikpDQoNCnN0YXJnYXplcihDbGFzczEsQ2xhc3MyLENsYXNzMyxDbGFzczQsdHlwZT0ibGF0ZXgiLA0KICAgICAgICAgIGludGVyY2VwdC5ib3R0b20gPSBGQUxTRSwgc2luZ2xlLnJvdz1UUlVFLCANCiAgICAgICAgICBzdGFyLmN1dG9mZnM9YyguMDUsLjAxLC4wMDEpLCBub3Rlcy5hcHBlbmQgPSBGQUxTRSwNCiAgICAgICAgICBoZWFkZXI9RkFMU0UpDQoNCmBgYA0KDQotIE91ciBpbnRlcmNlcHRzIGFyZSBjbGVhcmx5IGRpZmZlcmVudCBhY3Jvc3MgY2xhc3Nyb29tcw0KLSBPdXIgc2xvcGVzIGFyZSBhbGwgYXJvdW5kIDEwIChzaW11bGF0aW9uIGxldHMgdGhlbSB2YXJ5IHJhbmRvbWx5KSwgYnV0IGluIG9ubHkgb25lIGNhc2UgaXMgaXQgc2lnbmlmaWNhbnQNCiAgICAtIFdoeSBzbyBmZXcgc2lnbmlmaWNhbnQgcmVzdWx0eTsgT25seSAxLzQgY2xhc3Nyb29tPyBUaGF0IGRvZXMgbm90IGxvb2sgdmVyeSBzdXBwb3J0aXZlIG91ciB0aGVvcnkhIA0KICAgICAgICAtIFdpdGggc28gZmV3IGtpZHMgcGVyIGNsYXNzcm9vbSwgb3VyIHJlc3VsdCB3YXMgcmVhbGx5IHVuZGVycG93ZXJlZA0KICAgIC0gV2hhdCB0byBkbyBub3c/DQoNCiMjIyBPbGQgU2Nob29sIEFwcHJvYWNoIDENCi0gRXh0cmFjdCB0aGUgaW50ZXJjZXB0cyBhbmQgc2xvcGVzIGZyb20gZWFjaCBtb2RlbCBhbmQgdGVzdCBpZiB0aGVzZSA0IGFyZSBkaWZmZXJlbmNlIGZyb20gemVybyAoTm90ZTogdGhpcyBtYWtlcyBtb3JlIHNlbnNlIHdoZW4geW91IGhhdmUgMzAgY2xhc3Nyb29tKQ0KLSBPbmUgc2FtcGxlIHQtdGVzdCAoYWdhaW5zdCAwKSANCiAgICAtIFIgY29kZSBub3RlOiB3ZSBoYXZlIHRvICoqdW5uYW1lKiogdGhlICoqY29lZioqIGZyb20gZWFjaCBmcm9tIGVhY2ggbW9kZWwgYmVjYXVzZSB0aGUgd29yZCBpbnRlcmNlcHQvc2xvcGUgbWlnaHQgZm9sbG93IHRoZSBudW1iZXJzIGFuZCB3ZSB3YW50IHRvIHJlbW92ZSB0aGF0IGZvciBmdXJ0aGVyIGFuYWx5c2lzIChpLmUuLCBzdHJpcCB0aGUgYXR0cmlidXRlKQ0KYGBge3J9DQppbnRlcmNlcHRzLk1hdGguRGF0YTwtdW5uYW1lKGMoQ2xhc3MxJGNvZWZbMV0sQ2xhc3MyJGNvZWZbMV0sDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgQ2xhc3MzJGNvZWZbMV0sQ2xhc3M0JGNvZWZbMV0pKQ0KTS5JbnRlcmNlcHQ8LW1lYW4oaW50ZXJjZXB0cy5NYXRoLkRhdGEpDQpTRC5JbnRlcmNlcHQ8LXNkKGludGVyY2VwdHMuTWF0aC5EYXRhKQ0KbGlicmFyeShhcGEpDQp0LmludGVyY2VwdC5NYXRoLkRhdGE8LXRfYXBhKHQudGVzdChpbnRlcmNlcHRzLk1hdGguRGF0YSksIHByaW50PUZBTFNFKQ0KYGBgDQoNCi0gVGhlIGludGVyY2VwdCBtZWFuID0gYHIgcm91bmQoTS5JbnRlcmNlcHQsMilgIHdpdGggYSBTRCA9IGByIHJvdW5kKFNELkludGVyY2VwdCwyKWAsIGFuZCB0aGUgb25lIHNhbXBsZSB0LXRlc3QsIGByIHQuaW50ZXJjZXB0Lk1hdGguRGF0YWANCg0KYGBge3J9DQpTbG9wZS5NYXRoLkRhdGE8LXVubmFtZShjKENsYXNzMSRjb2VmWzJdLENsYXNzMiRjb2VmWzJdLA0KICAgICAgICAgICAgICAgICAgICAgICAgICBDbGFzczMkY29lZlsyXSxDbGFzczQkY29lZlsyXSkpDQpNLlNsb3BlPC1tZWFuKFNsb3BlLk1hdGguRGF0YSkNClNELlNsb3BldDwtc2QoU2xvcGUuTWF0aC5EYXRhKQ0KdC5TbG9wZS5NYXRoLkRhdGE8LXRfYXBhKHQudGVzdChTbG9wZS5NYXRoLkRhdGEpLCBwcmludD1GQUxTRSkNCmBgYA0KDQotIFRoZSBzbG9wZSBtZWFuID0gYHIgcm91bmQoTS5TbG9wZSwyKWAgd2l0aCBhIFNEID0gYHIgcm91bmQoU0QuU2xvcGV0LDIpYCwgYW5kIHRoZSBvbmUgc2FtcGxlIHQtdGVzdCwgYHIgdC5TbG9wZS5NYXRoLkRhdGFgDQoNCi0gJFJeMiQgaXMgYWxpdHRsZSBoYXJkZXIgYXMgeW91IGNhbm5vdCBzaW1wbHkgdGFrZSBhIG1lYW4gYW5kIFNEIG9mIHRoZSByIHZhbHVlcy4gWW91IG11c3QgZG8gYSBGaXNoZXIncyByJy10by16DQokJA0Kci50by56JyA9IFxmcmFjezF9ezJ9W2xvZ19lKDErcikgLSBsb2dfZSgxLXIpXSA9IGFyY2h0YW5oKHIpDQokJA0KYW5kIGJhY2sgYWdhaW4NCg0KJCQNCnonLnRvLnIgPSB0YW5oKHonKQ0KJCQNCmBgYHtyfQ0KI0Z1bmN0aW9ucyBmb3IgZmlzaGVycyB0cmFuc2Zvcm1zDQpGaXNoZXJSdG9aIDwtZnVuY3Rpb24ocikgey41KigobG9nKDErciktbG9nKDEtcikpKX0NCkZpc2hlclp0b1IgPC1mdW5jdGlvbih6KSB7dGFuaCh6KX0NCg0KUjIuTWF0aC5EYXRhPC1GaXNoZXJSdG9aKGMoc3VtbWFyeShDbGFzczEpJHIuc3F1YXJlZF4uNSxzdW1tYXJ5KENsYXNzMikkci5zcXVhcmVkXi41LA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgc3VtbWFyeShDbGFzczMpJHIuc3F1YXJlZF4uNSxzdW1tYXJ5KENsYXNzNCkkci5zcXVhcmVkXi41KSkNCk0uUjI8LUZpc2hlclp0b1IobWVhbihSMi5NYXRoLkRhdGEpKV4yDQpTRC5SMjwtRmlzaGVyWnRvUihzZChSMi5NYXRoLkRhdGEpKV4yDQp0LlIyLk1hdGguRGF0YTwtdF9hcGEodC50ZXN0KFIyLk1hdGguRGF0YSksIHByaW50PUZBTFNFKQ0KYGBgDQoNCi0gVGhlICRSXjIkIG1lYW4gPSBgciByb3VuZChNLlIyLDIpYCB3aXRoIGEgU0QgPSBgciByb3VuZChTRC5SMiwyKWAsIGFuZCB0aGUgb25lIHNhbXBsZSB0LXRlc3QsIGByIHQuUjIuTWF0aC5EYXRhYA0KDQo+PiBJbiBhbGwgY2FzZXMgdGhlICRSXjIkLCBzbG9wZSwgYW5kIGludGVyY2VwdCB3ZXJlIHNpZ25pZmljYW50bHkgZGlmZmVyZW50IGZyb20gemVybywgYnV0IGlzIHRoaXMgYW5hbHlzaXMgbWFrZSBzZW5zZT8gV2hhdCBoYXZlIHdlIGxvc3Q/IFdlbGwsIHdlIGxvc3QgdGhlIFNFIGZyb20gcmVncmVzc2lvbiBtb2RlbHMuIFRoaXMgaXMgYW4gYW50aS1jb25zZXJ2YXRpdmUgYXBwcm9hY2ggKHlvdSBkdW1wZWQgYWxsIHRoZSB2YXJpYW5jZSBmcm9tIHRoZSByZWdyZXNzaW9ucykNCg0KIyMjIE9sZCBTY2hvb2wgQXBwcm9hY2ggMg0KLSBTaW5jZSB0aGUgaW50ZXJjZXB0cyBkaWZmZXIsIHdoeSBjYW4ndCB3ZSBqdXN0IGNlbnRlciB0aGUgRFYgcmVsYXRpdmUgdG8gZWFjaCBjbGFzc3Jvb20/DQotIExldHMgc2VlIHdoYXQgaGFwcGVucw0KICAgIC0gKk5vdGUqOiB0byBtYWtlIHRoaXMgaGFwcGVuIGluIFIsIHdlIHdpbGwgdXNlIHRoZSAqKnBseXIqKiBwYWNrYWdlIGFuZCAqbXV0YXRlKiB0aGUgZGF0YSBvbiB0aGUgZmx5LiBUaGlzIHdpbGwgYWRkIGEgbmV3IGNvbHVtbiB0byB0aGUgZGF0YSBmcmFtZSB3aXRoIHRoZSBtZWFuIE1hdGggU2NvcmUgcGVyIGNsYXNzIHJvb20uIFRoZW4gd2UgY2FuIHN1YnRyYWN0IHRoZSBNYXRoIFNjb3JlIG9mIGVhY2gga2lkIGZyb20gdGhlIG1lYW4gTWF0aCBTY29yZSANCmBgYHtyfQ0KIyMjIFJlc2NhbGUgRFYNCmxpYnJhcnkocGx5cikNCiMgQ3JlYXRlIGEgbmV3IHZhcmlhYmxlIGluIHRoZSBkYXRhIHdoaWNoIGlzIHRoZSBtZWFuIG9mIE1hdGggU2NvcmUgcGVyIGNsYXNzIHJvb20gKGF0IGVhY2ggc3ViamVjdCkNCk1hdGguRGF0YTwtZGRwbHkoTWF0aC5EYXRhLC4oQ2xhc3Nyb29tKSwgbXV0YXRlLCBDbGFzc01lYW5NYXRoID0gbWVhbihNYXRoKSkNCiMgbmV4dCB3ZSBjZW50ZXIgcmVsYXRpdmUgdG8gZWFjaCBzdHVkZW50IHJlbGF0aXZlIHRvIHRoZWlyIGNsYXNzIE1hdGggbWVhbiAoaG93IG11Y2ggdG8gdGhleSBkaWZmZXIgZnJvbSB0aGVpciBjb2hvcnQpDQpNYXRoLkRhdGEkTWF0aC5DbGFzcy5DZW50ZXJlZDwtTWF0aC5EYXRhJE1hdGgtTWF0aC5EYXRhJENsYXNzTWVhbk1hdGgNCmBgYA0KDQotIFBsb3Qgb2YgdGhlIGNlbnRlcmVkIE1hdGggU2NvcmVzIA0KDQpgYGB7cn0NCkNsYXNzUm9vbS5QbG90LjMgPC1nZ3Bsb3QoZGF0YSA9IE1hdGguRGF0YSwgYWVzKHggPSBBY3RpdmVUaW1lLCB5PU1hdGguQ2xhc3MuQ2VudGVyZWQpKSsgDQogIGdlb21fcG9pbnQoYWVzKGNvbG91ciA9IENsYXNzcm9vbSwgc2hhcGU9Q2xhc3Nyb29tKSkrDQogIGdlb21fc21vb3RoKG1ldGhvZCA9ICJsbSIsIHNlID0gVFJVRSkrIyB3ZSBhZGQgZ3JvdXAgbGV2ZWwNCiAgeGxhYigiUHJvcG9ydGlvbiBvZiBUaW1lIEVuZ2FnZWQgaW4gQWN0aXZlIExlYXJuaW5nIikrDQogIHlsYWIoIk1hdGggU2NvcmUgKENsYXNzcm9vbSBDZW50ZXJlZCkiKSsNCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gInRvcCIpDQpDbGFzc1Jvb20uUGxvdC4zDQpgYGANCg0KLSBXZSB3aWxsIHJlLXJ1biB0aGUgcmVncmVzc2lvbiAqKmFjcm9zcyoqIGFsbCBjbGFzc3Jvb21zIG9uIHRoZSBjZW50ZXJlZCBzY29yZXMNCmBgYHtyLCByZXN1bHRzPSJhc2lzIn0NCkNsYXNzLkFsbC5DPC1sbShNYXRoLkNsYXNzLkNlbnRlcmVkfkFjdGl2ZVRpbWUsIGRhdGEgPSBNYXRoLkRhdGEpDQpzdGFyZ2F6ZXIoQ2xhc3MuQWxsLkMsdHlwZT0ibGF0ZXgiLA0KICAgICAgICAgIGludGVyY2VwdC5ib3R0b20gPSBGQUxTRSwgc2luZ2xlLnJvdz1UUlVFLCANCiAgICAgICAgICBzdGFyLmN1dG9mZnM9YyguMDUsLjAxLC4wMDEpLCBub3Rlcy5hcHBlbmQgPSBGQUxTRSwNCiAgICAgICAgICBoZWFkZXI9RkFMU0UpDQpgYGANCg0KLSBUaGF0IGFsc28gZGlkIG5vdCBtYWtlIGFueSBzZW5zZSEgV2h5PyBMb29rIGhlcmUNCg0KYGBge3J9DQpDbGFzc1Jvb20uUGxvdC40IDwtZ2dwbG90KGRhdGEgPSBNYXRoLkRhdGEsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICBhZXMoeCA9IEFjdGl2ZVRpbWUsIHk9TWF0aC5DbGFzcy5DZW50ZXJlZCkpKyANCiAgZ2VvbV9wb2ludChhZXMoY29sb3VyID0gQ2xhc3Nyb29tLCBzaGFwZT1DbGFzc3Jvb20pKSsNCiAgZ2VvbV9zbW9vdGgobWV0aG9kID0gImxtIiwgc2UgPSBUUlVFKSsjIHdlIGFkZCBncm91cCBsZXZlbA0KICBnZW9tX3Ntb290aChtZXRob2QgPSAibG0iLCBzZSA9IFRSVUUsIGFlcyhncm91cCA9IENsYXNzcm9vbSwgY29sb3I9Q2xhc3Nyb29tKSkrDQogIHhsYWIoIlByb3BvcnRpb24gb2YgVGltZSBFbmdhZ2VkIGluIEFjdGl2ZSBMZWFybmluZyIpKw0KICB5bGFiKCJNYXRoIFNjb3JlIChDbGFzc3Jvb20gQ2VudGVyZWQpIikrICMgYWRkIGxhYmVscw0KICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAidG9wIikNCkNsYXNzUm9vbS5QbG90LjQNCmBgYA0KDQotIFRoZSBwcm9ibGVtIGlzIHRoZSByYW5nZSBvZiB0aW1lIGlzIGRpZmZlcmVudCBmcm9tIGVhY2ggY2xhc3Nyb29tLiANCiAgICAtIFdlIGNhbiB0cnkgdG8gcmVjZW50ZXIgdGhhdCBhcyB3ZWxsDQoNCmBgYHtyfQ0KIyMjIFJlc2NhbGUgSVYNCk1hdGguRGF0YTwtZGRwbHkoTWF0aC5EYXRhLC4oQ2xhc3Nyb29tKSwgbXV0YXRlLCBDbGFzc0FjdGl2ZVRpbWUgPSBtZWFuKEFjdGl2ZVRpbWUpKQ0KTWF0aC5EYXRhJEFjdGl2ZVRpbWUuQ2xhc3MuQ2VudGVyZWQ8LU1hdGguRGF0YSRBY3RpdmVUaW1lLU1hdGguRGF0YSRDbGFzc0FjdGl2ZVRpbWUNCmBgYA0KDQotIFBsb3Qgb2YgdGhlIGNlbnRlcmVkIE1hdGggU2NvcmVzIGFuZCBBY3RpdmUgVGltZSANCg0KYGBge3J9DQpDbGFzc1Jvb20uUGxvdC41IDwtZ2dwbG90KGRhdGEgPSBNYXRoLkRhdGEsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICBhZXMoeCA9IEFjdGl2ZVRpbWUuQ2xhc3MuQ2VudGVyZWQsIHk9TWF0aC5DbGFzcy5DZW50ZXJlZCkpKyANCiAgZ2VvbV9wb2ludChhZXMoY29sb3VyID0gQ2xhc3Nyb29tLCBzaGFwZT1DbGFzc3Jvb20pKSsNCiAgZ2VvbV9zbW9vdGgobWV0aG9kID0gImxtIiwgc2UgPSBUUlVFKSsjIHdlIGFkZCBncm91cCBsZXZlbA0KICB4bGFiKCJQcm9wb3J0aW9uIG9mIFRpbWUgRW5nYWdlZCBpbiBBY3RpdmUgTGVhcm5pbmdcbihDZW50ZXJlZCBwZXIgQ2xhc3Nyb29tKSIpKw0KICB5bGFiKCJNYXRoIFNjb3JlIChDbGFzc3Jvb20gQ2VudGVyZWQiKSsgIyBhZGQgbGFiZWxzDQogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJ0b3AiKQ0KQ2xhc3NSb29tLlBsb3QuNQ0KYGBgDQoNCg0KLSBSZWdyZXNzaW9uIHJlc3VsdHMNCg0KYGBge3IsIHJlc3VsdHM9ImFzaXMifQ0KQ2xhc3MuQWxsLkMyPC1sbShNYXRoLkNsYXNzLkNlbnRlcmVkfkFjdGl2ZVRpbWUuQ2xhc3MuQ2VudGVyZWQsIGRhdGEgPSBNYXRoLkRhdGEpDQpzdGFyZ2F6ZXIoQ2xhc3MuQWxsLkMyLHR5cGU9ImxhdGV4IiwNCiAgICAgICAgICBpbnRlcmNlcHQuYm90dG9tID0gRkFMU0UsIHNpbmdsZS5yb3c9VFJVRSwgDQogICAgICAgICAgc3Rhci5jdXRvZmZzPWMoLjA1LC4wMSwuMDAxKSwgbm90ZXMuYXBwZW5kID0gRkFMU0UsDQogICAgICAgICAgaGVhZGVyPUZBTFNFKQ0KYGBgDQoNCj4+IFdlbGwgbm93IHdoYXQgZG8gaGF2ZT8gV2VsbCwgb3VyIHNsb3BlIG1ha2VzIHNlbnNlLCBvdXIgZGVncmVlcyBvZiBmcmVlZG9tIGFyZSBiaWdnZXIgKGJ1dCB3cm9uZyksIGJ1dCBvdXIgaW50ZXJjZXB0IGlzIGFsd2F5cyBnb2luZyB0byBiZSB6ZXJvISBXZSBsb3N0IG91ciBpbnRlcmNlcHQuIA0KDQojIyMgRmFpbHVyZXMgb2YgT2xkIFNjaG9vbCBBcHByb2FjaA0KLSBMb3NpbmcgdGhlIGludGVyY2VwdCBpcyBhIGh1Z2UgcHJvYmxlbSAoYXMgaXQgbWF5IGJlIG1lYW5pbmdmdWwpDQotIFdoYXQgZG8gd2UgYXNzdW1lIGZvciB0aGlzIGFuYWx5c2lzIHRvIG1ha2Ugc2Vuc2U/IA0KICAgIC0gU2xvcGVzIGJldHdlZW4gY2xhc3Nyb29tcyBhcmUgdGhlIHNhbWUgKHRoZXkgbWlnaHQgbm90IGJlKQ0KICAgIC0gREYgYXJlIHdyb25nIGJlY2F1c2Ugc2hvdWxkIGJlIG5vdCBiZSBwZW5hbGl6ZWQgZm9yIHRoZSBudW1iZXIgb2Ygc2Nob29scyB3ZSBhcmUgdGVzdGluZw0KLSBUaGluayBob3cgY29uZnVzaW5nIHRoaXMgYW5hbHlzaXMgd291bGQgYmUgd2hlbiB3ZSBoYXZlIGxpa2UgNTAgY2xhc3Nyb29tcw0KLSBXaGF0IGhhcHBlbnMgd2hlbiB3ZSB3YW50IHRvIHRlc3QgbXVsdGlwbGUgcHJlZGljdG9ycz8gV2hpY2ggZG8gY2VudGVyIGFuZCB3aGljaCBkbyB3ZSBub3Q/IA0KLSBCaWcgcHJvYmxlbSAxLiAqKldoYXQgaWYgeW91IGhhdmUgZGlmZmVyZW50IHNhbXBsZSBzaXplcyBwZXIgY2xhc3Nyb29tKiouDQotIEJpZyBwcm9ibGVtIDIuICoqV2hhdCBpZiB5b3UgaGF2ZSBtdWx0aXBsZSBjbGFzc3Jvb21zIGF0IGRpZmZlcmVudCBzY2hvb2xzKiogWzMgbGV2ZWxzIG9mIGRhdGFdLg0KDQojIEhpZXJhcmNoaWNhbCBMaW5lYXIgTW9kZWxpbmcNCi0gVGhlIG9sZCBzY2hvb2wgYXBwcm9hY2ggd29yayB1bmRlciB2ZXJ5IHNwZWNpZmljIGNhc2VzIChzbyBiYXNpY2FsbHkgbmV2ZXIgdXNlYWJsZSkNCi0gSExNOiBpcyBhIHNwZWNpYWwgY2FzZSBvZiBtaXhlZCBtb2RlbHMgd2hlcmUgd2UgaGF2ZSBuZXN0ZWQgZGF0YSAoY2x1c3RlcnMpDQotIFdlIHdpbGwgZm9jdXMgdG9kYXkgb25seSAyIGxldmVscw0KLSBOZXh0IHdlZWsgTXVsdGlsZXZlbCBNb2RlbHMgW01MTV06IFN0dWRlbnRzIG5lc3RlZCBpbiBjbGFzc3Jvb21zIHdpdGggKnN0dWRlbnQtbGV2ZWwqIGFuZCAqY2xhc3Nyb29tLWxldmVsKiBwcmVkaWN0b3JzLiANCiAgICAtIFRvZGF5IG9ubHkgc3R1ZGVudC1sZXZlbCBwcmVkaWN0b3JzICANCg0KIyMgTGV2ZWxzDQotIExldmVsIDEgPSBTbWFsbGVzdCBsZXZlbCAob2Z0ZW4gc3ViamVjdHMvc3R1ZGVudHMpDQotIExldmVsIDIgPSBUaGUgZ3JvdXAvY2x1c3RlciB0aGUgc3R1ZGVudHMgYmVsb25nIHRvbyAoY2xhc3Nyb29tcykNCi0gWW91IGNhbiBoYXZlIGhpZ2hlciBsZXZlbHMgYXMgd2VsbCwgc3VjaCBhcyBMZXZlbCAzID0gQ2xhc3Nyb29tcyBuZXN0ZWQgaW4gc2Nob29scw0KDQojIyMgSExNIHdpdGggb25seSAqKkxldmVsIDEqKiBwcmVkaWN0b3JzIA0KLSBDb250cm9sbGluZyBmb3IgY2xhc3Nyb29tIGRpZmZlcmVuY2VzLCBidXQgbWFraW5nIHByZWRpY3Rpb25zIGFib3V0IHN0dWRlbnRzDQoNCiMjIEVxdWF0aW9ucyAodHdvIGxldmVsIG1vZGVsKQ0KLSBRdWljayByZXZpZXc6DQoNCiQkIFlfaSA9IFxiZXRhXzFYXzEgK1xiZXRhXzAgKyBcZXBzaWxvbiQkDQoNCiMjIyBMZXZlbCAxDQotIFdpdGhpbiBlYWNoIEdyb3VwL0NsdXN0ZXIgKFN0dWRlbnRzID0gJGkkIHdpdGhpbiBDbGFzc3Jvb20gMSA9ICRqJCwgYW5kIHdlIHdvdWxkIHJlcGxhY2UgJGokIHdpdGggJGckIGZvciBDbGFzc3Jvb20gMiBhbmQgc28gZm9ydGggZm9yIGVhY2ggY2xhc3Nyb29tISkNCiQkeV97aWp9ID0gQl97MWp9WF97aWp9ICsgQl97MGp9ICsgcl97aWp9ICQkDQoNCi0gV2hlcmUgJEJfezBqfSQgPSBpbnRlcmNlcHQgaW4gZ3JvdXAgag0KLSBXaGVyZSAkQl97aWp9JCA9IHNsb3BlIG9mIHN0dWRlbnRzIGluIGdyb3VwIGoNCi0gV2hlcmUgJHJfe2lqfSQgPSByZXNpZHVhbHMgb2YgZWFjaCBpIChzdHVkZW50KSB3aXRoaW4gZ3JvdXAgag0KDQojIyMgTGV2ZWwgMg0KLSBFYWNoIEdyb3VwL0NsdXN0ZXIgKFN0dWRlbnRzID0gJGkkIHdpdGhpbiBDbGFzc3Jvb20gMSA9ICRqJCwgQ2xhc3Nyb29tIDIgPSAkZyQsIGFuZCBzbyBmb3J0aCBmb3IgZWFjaCBjbGFzc3Jvb20pDQoNCiMjIyMgTGV2ZWwgMiBJbnRlcmNlcHQNCg0KJCRCX3swan0gPSBcZ2FtbWFfezAwfSt1X3swan0gJCQNCg0KLSBXaGVyZSAkXGdhbW1hX3swMH0kID0gaW50ZXJjZXB0IG9mIHRoZSBjbGFzc3Jvb20NCi0gV2hlcmUgJHVfezBqfSQgPSByYW5kb20gZGV2aWF0aW9uIG9mIHRoZSBjbGFzc3Jvb20gaW50ZXJjZXB0IGZyb20gZml4ZWQgcG9wdWxhdGlvbiBpbnRlcmNlcHQNCi0gV2UgYXNzdW1lIHN0dWRlbnRzIHdpbGwgdmFyeSByYW5kb21seSBhcm91bmQgdGhlIHBvcHVsYXRpb24gaW50ZXJjZXB0IG9mIGNsYXNzcm9vbQ0KIA0KIyMjIyBMZXZlbCAyIFNsb3BlDQoNCiQkQl97MWp9ID0gXGdhbW1hX3sxMH0rdV97MWp9ICQkDQoNCi0gV2hlcmUgJFxnYW1tYV97MTB9JCA9IHNsb3BlIG9mIHRoZSBjbGFzc3Jvb20NCi0gV2hlcmUgJHVfezFqfSQgPSByYW5kb20gZGV2aWF0aW9uIG9mIHRoZSBjbGFzc3Jvb20gc2xvcGUgZnJvbSBmaXhlZCBwb3B1bGF0aW9uIHNsb3BlDQotIFdlIGFzc3VtZSBzdHVkZW50cyB3aWxsIHZhcnkgcmFuZG9tbHkgYXJvdW5kIHRoZSBwb3B1bGF0aW9uIGludGVyY2VwdCBvZiBjbGFzc3Jvb20NCi0gTm90ZTogV2Ugd291bGQgcmVwZWF0IHRoaXMgZm9yIGVhY2ggbGV2ZWwgb2YgY2xhc3Nyb29tIHdlIGhhdmUgKGcsIGV0YykNCg0KIyMjIE1peGVkIEVxdWF0aW9uDQotIFdlIHB1dCBsZXZlbCAxIGFuZCBsZXZlbCAyIHRvZ2V0aGVyDQokJHlfe2lqfSA9IChcZ2FtbWFfezEwfSt1X3sxan0pWF97aWp9ICsgIChcZ2FtbWFfezAwfSt1X3swan0pKyByX3tpan0gJCQNCg0KIyMjIyBWYXJpYW5jZSBDb21wb25lbnRzDQotIFdlIG5vdyBoYXZlIG11bHRpcGxlIHNvdXJjZXMgb2YgZXJyb3IgaW4gb3VyIGVxdWF0aW9uDQotICRcc2lnbWFeMiQgPSB2YXJpYW5jZSBvZiAkcl97aWp9JCwgQUtBIHZhcmlhbmNlIGR1ZSB0byBlcnJvciBhdCBsZXZlbCAxDQotICRcdGF1X3swMH0kID0gdmFyaWFuY2Ugb2YgJHVfezBqfSQsIEFLQSB2YXJpYW5jZSBvZiByYW5kb20gaW50ZXJjZXB0cyBhdCBsZXZlbCAyDQotICRcdGF1X3sxMX0kID0gdmFyaWFuY2Ugb2YgJHVfezFqfSQsIEFLQSB2YXJpYW5jZSBvZiByYW5kb20gc2xvcGVzIGF0IGxldmVsIDINCi0gJFx0YXVfezAxfSQgPSBjby12YXJpYW5jZSBvZiAkdV97MGp9JCBhbmQgJHVfezFqfSQsIEFLQSByYW5kb20gc2xvcGVzIGFuZCByYW5kb20gaW50ZXJjZXB0IGNhbiBjb3JyZWxhdGUNCg0KIyMgSUNDDQotIFRoZSBkZWdyZWUgb2YgY2x1c3RlcmluZyBpcyBtZWFzdXJlZCB2aWEgdGhlIGludGVyLWNsYXNzIGNvcnJlbGF0aW9uICoqSUNDKioNCi0gVGhlIHByb3BvcnRpb24gb2YgdG90YWwgdmFyaWFuY2UgdGhhdCBpcyBiZXR3ZWVuIHRoZSBncm91cHMNCiQkIElDQyA9IFxmcmFje1x0YXV9e1x0YXUgKyBcc2lnbWFeMn0kJA0KDQotICRcdGF1JCA9IHZhcmlhbmNlIGluIGEgdmFyaWFibGUgZHVlIHRvIGRpZmZlcmVuY2VzIGJldHdlZW4gYSBncm91cA0KLSAkXHNpZ21hXjIkID0gdG90YWwgdmFyaWFuY2UgYWNyb3NzIGdyb3Vwcw0KLSBXZSB3b3VsZCBmaXQgdGhpcyBvbiB0aGUgbnVsbCBtb2RlbCAobm8gbGV2ZWwgMSBwcmVkaWN0b3JzKQ0KICAgIC0gT0xTIHJlZ3Jlc3Npb24gYXNzdW1lcywgSUNDID0gMDogIGVhY2ggY2xhc3Nyb29tIGlzIHVucmVsYXRlZCB0byB0aGUgb3RoZXJzDQogICAgLSBJZiB0aGlzIHZhbHVlIGlzIGxhcmdlLCBpdCBtZWFucyB0aGF0IHZhcmlhbmNlIGNhbiBiZSBhdHRyaWJ1dGVkIHRvIGxldmVsIDINCg0KIyBITE0gYW5hbHlzaXMgW1JhbmRvbSBJbnRlY2VwdHMgb25seSBNb2RlbF0NCi0gTGluZWFyIHJlZ3Jlc3Npb25zIG9ubHkgaGFzIGZpeGVkLWVmZmVjdHMgW2NvbnRpbnVvdXNdIG9yIGZpeGVkLWZhY3RvcnMgW2dyb3Vwc106IHZhcmlhYmxlcyBvZiBpbnRlcmVzdCAob2Z0ZW4geW91ciBtYW5pcHVsYXRpb24pDQogICAgLSBUaGUgb25seSByYW5kb20gZmFjdG9yIGFzc3VtZWQgaXMgeW91ciBzdWJqZWN0J3MgcmVzcG9uc2UNCi0gV2hhdCBpZiB5b3UgaGF2ZSBvdGhlciBlZmZlY3Qgb3IgZmFjdG9ycyB0aGF0IG1pZ2h0IGFkZCByYW5kb20gdmFyaWF0aW9uPyANCiAgICAtIFJhbmRvbS1lZmZlY3RzL2ZhY3RvcnM6IExldmVscyByYW5kb21seSBzYW1wbGVkIGZyb20gYSBtdWNoIGxhcmdlciBwb3B1bGF0aW9uOiBTb21ldGhpbmcgeW91IHdhbnQgdG8gZ2VuZXJhbGl6ZSBhY3Jvc3M6IFN1YmplY3RzLCBDbGFzc3Jvb21zLCBXb3JkcywgUGljdHVyZXMsIGV0Yw0KDQojIyBMTUU0IHBhY2thZ2UNCi0gTE1FNCB3YXMgY3JlYXRlZCBhcyBhIG1vZGVybiBhcHByb2FjaCB0byBtaXhlZCBtb2RlbHMgKHJlcGxhY2luZyB0aGUgbmxtZSBwYWNrYWdlKS4gDQotIExNRTQgd2FzIGRlc2lnbmVkIHNwZWNpZmljYWxseSB0byBiZSBmbGV4aWJsZSBpbiBob3cgd2UgY2FuIGNvbnRyb2wgdGhlIHJhbmRvbSBlZmZlY3QgW0Nyb3NzZWQgJiBOZXN0ZWRdIGFuZCBob3cgdGhleSBjb3JyZWxhdGVkIHdpdGggb3RoZXIuIG5sbWUgaGFuZGxlcyBuZXN0ZWQgc3RydWN0dXJlcyB3ZWxsIGJ1dCBkb2VzIG5vdCBoYW5kbGUgY29tcGxleCBzdHJ1Y3R1cmVzIHdlbGwNCi0gVGhlcmUgaXMgYSB0cmFkZS1vZmYgKHdlIGNhbm5vdCB1c2UgcHJlZGVmaW5lIGNvdmFyaWFuY2Ugc3RydWN0dXJlcywgYnV0IHdlIHdpbGwgY29tZSBiYWNrIHRvIHRoYXQgaW4gYSBmZXcgd2Vla3MpDQotIEFsc28sIHRoZXJlIGFyZSBubyBwdmFsdWVzICh5ZXQpDQoNCiMjIyBsbWVyIGZ1bmN0aW9uDQotIEl0IGlzIHNpbWlsYXIgdGhlIGxtIGZ1bmN0aW9uLCBidXQgd2UgYWRkIGFkZGl0aW9uYWwgcmFuZG9tIGVmZmVjdHMNCi0gbG1lcihEViB+IElWICsoMXxSYW5kb21GYWN0b3IpLCBkYXRhID0gWCwgUkVNTD0gRkFMU0UpDQogICAgLSAoMXxSYW5kb21GYWN0b3IpLCBtZWFucyBsZXQgdGhlIGludGVyY2VwdCBvZiB0aGUgcmFuZG9tIGZhY3RvciB2YXJ5IGEgZnVuY3Rpb24gb2YgdGhlIGdyb3VwIChjbHVzdGVyKS4gDQogICAgLSBJbiBvdXIgY2FzZSwgKDF8Q2xhc3Nyb29tKSwgbWVhbnMgdGhhdCB3ZSBsZXQgZWFjaCBjbGFzc3Jvb20gaGF2ZSBpdHMgb3duIGludGVyY2VwdA0KICAgIC0gUkVNTCA9IEZBTFNFLCBtZWFucyB1c2UgYSBNYXhpbXVtIExpa2VsaWhvb2QgZXN0aW1hdGlvbiAobm90IHJlZGlzdHJpY3RlZCBNTC4gTW9yZSBvbiB0aGF0IGxhdGVyKSBbYW5kIG9mIGNvdXJzZSBub3QgT0xTXSANCiAgICAtICpOb3RlKjogS2lkcyB3ZXJlIGNvZGVkIGZyb20gMSB0byB0b3RhbCBudW1iZXIgb2Yga2lkcy4gV2UgKippbXBsaWNpdGx5KiogbmVzdGVkIGtpZHMgaW4gdGhhdCBjbGFzc3Jvb21zLiBLaWQgMSBpcyBpbiBjbGFzc3Jvb20gMS4gS2lkcyAyMSBpcyBpbiBjbGFzc3Jvb20gMi4gSGFkIHdlIHJlLXN0YXJ0ZWQgdGhlIG51bWJlcmluZyB0byBiZSAxLCB0aGF0IG1pZ2h0IGNvbmZ1c2UgbG1lciB0byB0aGluayBraWQgIjEiIiB3YXMgaW4gKkFMTCogY2xhc3Nyb29tcyAocmVwZWF0ZWQpIFtpZiB5b3Ugc2V0IGtpZHMgYSByYW5kb20gZWZmZWN0LCB3aGljaCBpbiB0aGlzIGNhc2UgeW91IHNob3VsZCBub3RdIA0KDQojIyBBbmFseXNpcw0KLSBMZXRzIHJ1biBvdXIgYW5hbHlzaXMgZmlyc3Qgd2l0aG91dCBjZW50ZXJpbmcgdGhlIElWcyBzbyB3ZSBjYW4gY29tcGFyZSB0byBvdXIgcHJldmlvdXMgcmVncmVzc2lvbnMNCg0KIyMjIE51bGwgTW9kZWwNCi0gTm8gZml4ZWQgZmFjdG9ycyAoaW50ZXJjZXB0IG9ubHkpDQoNCmBgYHtyfQ0KbGlicmFyeShsbWU0KSAgICAgI21peGVkIG1vZGVsIHBhY2thZ2UgYnkgRG91Z2xhcyBCYXRlcyBldCBhbA0KTW9kZWwuTnVsbDwtbG1lcihNYXRoIH4xKygxfENsYXNzcm9vbSksICANCiAgICAgICAgICAgICAgICAgICBkYXRhPU1hdGguRGF0YSwgUkVNTD1GQUxTRSkNCnN1bW1hcnkoTW9kZWwuTnVsbCkNCmBgYA0KDQotICpSdWxlIG9mIHRodW1iKjogdC12YWx1ZXMgPiB8MnwgYXJlIHNpZ25pZmljYW50IA0KLSBOZXh0IHdlIGV4YW1pbmUgdGhlIGludGVyY2VwdHMgZm9yIGVhY2ggY2xhc3Nyb29tIChyYW5kb20gZWZmZWN0KQ0KDQpgYGB7cn0NCnJhbmVmKE1vZGVsLk51bGwpDQpgYGANCg0KLSBUaGlzIGlzIHRoZSBkaWZmZXJlbmNlIGZyb20gdGhlIGdyYW5kIG1lYW4NCi0gTG9vayBhdCBJQ0M6IFRoZSB2YXIgYW5kIHNkIG9mIHRoZXNlIHZhbHVlcyBhcmUgd2hhdCB3YXMgc2VlbiBpbiB0aGUgdGFibGUgYWJvdmUNCg0KYGBge3J9DQpsaWJyYXJ5KHBlcmZvcm1hbmNlKQ0KSUNDLk51bGw8LWljYyhNb2RlbC5OdWxsKQ0KSUNDLk51bGwNCmBgYA0KDQotICRJQ0MgPSBcZnJhY3tcdGF1fXtcdGF1ICsgXHNpZ21hXjJ9JCwgd2hlcmUgJFx0YXUkID0gYHIgcm91bmQoYXMubnVtZXJpYyhsYXBwbHkoc3VtbWFyeShNb2RlbC5OdWxsKSR2YXJjb3IsIGRpYWcpKSwzKWAgJiAkXHNpZ21hXjIkID0gYHIgcm91bmQoYXMubnVtZXJpYyhhdHRyKHN1bW1hcnkoTW9kZWwuTnVsbCkkdmFyY29yLCAic2MiKV4yKSwzKWANCi0gVGhlIElDQyA9IGByIHJvdW5kKGFzLm51bWVyaWMoSUNDLk51bGxbMV0pLDIpYCA+IDAsIG1lYW5pbmcgd2Ugd2VyZSBjb3JyZWN0IHRvIHRoaW5rIG9mIHRoaXMgYXMgSExNIHByb2JsZW0NCg0KIyMjIFZpc3VhbGl6aW5nIHRoZSByYW5kb20gZWZmZWN0cw0KLSBPdXIgbW9kZWwgZml0IHRoZSBmaXhlZCBhbmQgcmFuZG9tIGVmZmVjdCBhbmQgaWYgd2UgYWRkIGluIGZpeGVkIGZhY3RvcnMsIG91ciByYW5kb20gZWZmZWN0cyB3aWxsIGNoYW5nZQ0KLSBJbiBvdGhlciB3b3JkcywgdGhlIHJhbmRvbSBlZmZlY3RzIChtZWFucywgdmFyaWFuY2VzIGFuZCBjb3ZhcmlhbmNlcykgYXJlICplc3RpbWF0ZWQqIGJhc2VkIG9uIHRoZSBzcGVjaWZpY2F0aW9uIG9mIHRoZSBtb2RlbA0KICAgIC0gVG8gdW5kZXJzdGFuZCB0aGUgdGhlb3J5IG9mIHRoZSBwcm9jZXNzIG9mIGdldHRpbmcgdGhlIGVzdGltYXRlcywgc2VlICpCZXN0IExpbmVhciBVbmJpYXNlZCBQcmVkaWN0b3JzIFtCTFVQXSogDQoNCmBgYHtyfQ0KbGlicmFyeShzalBsb3QpDQpsaWJyYXJ5KGdsbW1UTUIpDQpwbG90X21vZGVsKE1vZGVsLk51bGwsIHR5cGUgPSdyZScsDQogICAgICAgICBmYWNldC5ncmlkID0gRkFMU0UsDQogICAgICAgICBzb3J0LmVzdCA9ICJzb3J0LmFsbCIsDQogICAgICAgICB5Lm9mZnNldCA9IC40KQ0KYGBgDQoNCg0KIyMjIFRlc3QgTW9kZWwNCmBgYHtyfQ0KTW9kZWwuMTwtbG1lcihNYXRoIH5BY3RpdmVUaW1lKygxfENsYXNzcm9vbSksICANCiAgICAgICAgICAgICAgICAgICBkYXRhPU1hdGguRGF0YSwgUkVNTD1GQUxTRSkNCnN1bW1hcnkoTW9kZWwuMSkNCmBgYA0KDQpgYGB7ciAsZWNobz1GQUxTRX0NCkk8LXJvdW5kKHVubmFtZShmaXhlZihNb2RlbC4xKVsxXSksMikNClM8LXJvdW5kKHVubmFtZShmaXhlZihNb2RlbC4xKVsyXSksMikNCmBgYA0KDQotIFRoaXMgbG9va3MgbGlrZSBvdXIgZmluYWwgbG0gbW9kZWwgKGJ1dCB3ZSBoYXZlIGFuIGludGVyY2VwdCkgDQotIExldHMgc3VtIGl0IHVwLiANCiAgICAtIE91ciBpbnRlcmNlcHQgd2FzIGByIFNgLCB3aGljaCB3YXMgc2lnbmlmaWNhbnQNCiAgICAtIE91ciBzbG9wZSB3YXMgYHIgSWAsIHdoaWNoIHdhcyBzaWduaWZpY2FudA0KICAgIC0gT3VyIHJhbmRvbSBlZmZlY3RzIGNoYW5nZWQgKG5vdyB3ZSBhZGRlZCBmaXhlZCBlZmZlY3RzKQ0KDQpgYGB7cn0NCnBsb3RfbW9kZWwoTW9kZWwuMSwgdHlwZSA9J3JlJywNCiAgICAgICAgIGZhY2V0LmdyaWQgPSBGQUxTRSwNCiAgICAgICAgIHNvcnQuZXN0ID0gInNvcnQuYWxsIiwNCiAgICAgICAgIHkub2Zmc2V0ID0gLjQpDQpgYGANCg0KIyMgV2hhdCBoYXBwZW5lZCB0byAkUl4yJD8gDQoNCiMjIyBQbG90IHRoZSBmaW5hbCBtb2RlbCBhbmQgJFJeMiQgcHJvYmxlbXMNCi0gSSBmaW5kICJlZmZlY3RzIiBwYWNrYWdlIG1vc3QgYWNjdXJhdGUNCg0KYGBge3J9DQpsaWJyYXJ5KGVmZmVjdHMpDQpSZXN1bHRzLk1vZGVsLjE8LUVmZmVjdChjKCJBY3RpdmVUaW1lIiksTW9kZWwuMSwNCiAgICAgeGxldmVscz1saXN0KEFjdGl2ZVRpbWU9c2VxKDAsMSwuMikpKQ0KICAgICANClJlc3VsdHMuTW9kZWwuMTwtYXMuZGF0YS5mcmFtZShSZXN1bHRzLk1vZGVsLjEpDQoNCkZpbmFsLkZpeGVkLlBsb3QuMSA8LWdncGxvdChkYXRhID0gUmVzdWx0cy5Nb2RlbC4xLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBhZXMoeCA9IEFjdGl2ZVRpbWUsIHkgPWZpdCkpKw0KICBnZW9tX2xpbmUoc2l6ZT0yKSsNCiAgY29vcmRfY2FydGVzaWFuKHhsaW0gPSBjKDAsIDEpLHlsaW0gPSBjKDAsIDEwMCkpKyANCiAgZ2VvbV9yaWJib24oYWVzKHltaW49Zml0LXNlLCB5bWF4PWZpdCtzZSksYWxwaGE9LjIpKw0KICB4bGFiKCJQcm9wb3J0aW9uIG9mIFRpbWUgRW5nYWdlZCBpbiBBY3RpdmUgTGVhcm5pbmciKSsNCiAgeWxhYigiTWF0aCBTY29yZSIpKyANCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKQ0KRmluYWwuRml4ZWQuUGxvdC4xDQpgYGANCg0KLSBTb21lb25lIHdpbGwgYXNrIHlvdSwgIkkgd2FudCB0byBzZWUgdGhlICpyZWFsKiBkYXRhIiwgbWVhbmluZyB0aGV5IHdhbnQgdG8gc2VlIGhvdyB0aGUgbGluZSBmaXRzIHRoZSBkYXRhLg0KLSBXZSBwbG90IHRoZSBmaXhlZCBlZmZlY3Qgb24gdG9wIG9mIHRoZSByYXcgZGF0YQ0KICAgIA0KYGBge3J9DQojRml4ZWQgb25seSwgcmVtb3ZlZCByYW5kb20gZWZmZWN0cw0KTWF0aC5EYXRhJE1vZGVsLjEuRml0dGVkPC1wcmVkaWN0KE1vZGVsLjEsIHJlLmZvcm09TkEpDQogICAgIA0KQ2xhc3NSb29tLlBsb3QuQmFkIDwtZ2dwbG90KGRhdGEgPSBNYXRoLkRhdGEpKyANCiAgY29vcmRfY2FydGVzaWFuKHhsaW0gPSBjKDAsIDEpLHlsaW0gPSBjKDAsIDEwMCkpKyANCiAgZ2VvbV9wb2ludCggYWVzKHggPSBBY3RpdmVUaW1lLCB5PU1hdGgsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb2xvdXIgPSBDbGFzc3Jvb20sIHNoYXBlPUNsYXNzcm9vbSkpKw0KICBnZW9tX2xpbmUoYWVzKHggPSBBY3RpdmVUaW1lLCB5PU1vZGVsLjEuRml0dGVkKSkrDQogIHhsYWIoIlByb3BvcnRpb24gb2YgVGltZSBFbmdhZ2VkIGluIEFjdGl2ZSBMZWFybmluZyIpKw0KICB5bGFiKCJNYXRoIFNjb3JlIikrIA0KICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAidG9wIikNCkNsYXNzUm9vbS5QbG90LkJhZA0KYGBgDQoNCi0gTmV4dCwgdGhleSB3aWxsIHNheSwgIldvdyB5b3VyIG1vZGVsIHN1Y2tzIGFuZCB5b3VyICRSXjIkIG11c3QgYmUgdGVycmlibGUuIFlvdSBzdWNrIGFzIGEgbW9kZWxlciwiDQotIFRvIHRlc3QgdGhlICRSXjIkIG9mIHRoZSBmaXhlZCBlZmZlY3Qgb2YgdGhlIGZpbmFsIG1vZGVsIG92ZXIgdGhlIHJhdyBkYXRhIChtZWFuaW5nIHdlIElHTk9SRSB0aGUgcmFuZG9tIGVmZmVjdHMpLCBhbGwgd2UgaGF2ZSB0byBkbyBpcyBydW4gYSBsaW5lYXIgcmVncmVzc2lvbiBvZiB0aGUgZml0dGVkIGRhdGEgZml4ZWQgZGF0YSB0byB0aGUgcmF3IGRhdGENCi0gTmFrYWdhd2EgYW5kIFNjaGllbHpldGggKDIwMTMpOiBmdW5jdGlvbiBmcm9tIGh0dHBzOi8vZ2l0aHViLmNvbS9qc2xlZmNoZS9yc3F1YXJlZC5nbG1tL2Jsb2IvbWFzdGVyL3JzcXVhcmVkZ2xtbS5SDQogICAgLSAkUl4yX3tNYXJnaW5hbH0kID0gKGZpeGVkIHZhcmlhbmNlIC8gVG90YWwgdmFyaWFuY2UpDQoNCiQkIA0KUl4yX3tNYXJnaW5hbH0gID0gXGZyYWN7XHNpZ21hXjJfe2ZpeGVkfX0NCntcc2lnbWFeMl97Zml4ZWR9K1xzaWdtYV4yX3tyYW5kb219K1xzaWdtYV4yX3tyZXNpZHVhbH19DQokJA0KDQpgYGB7cn0NCnNvdXJjZSgnUnNxdWFyZWRIZWxwZXIuUicpDQpyc3F1YXJlZC5nbG1tKE1vZGVsLjEpDQpgYGANCg0KLSBUaGUgTWFyZ2luYWwgJFJeMiQgaXMgdGVycmlibGUuIA0KDQotIE9mdGVuIHlvdSBjYW5ub3Qgc2ltcGx5IGFkZCBhIHNjYXR0ZXJwbG90LiBIb3dldmVyLCB0aGVyZSBhcmUgd2F5cyBhcm91bmQgdGhpcyAoYnV0IHlvdSBlbmQgdXAgcGxvdHRpbmcgZXZlcnkgY2xhc3Nyb29tKQ0KLSBUaGVyZSBpcyBhIGZhc3QgYW5kIGRpcnR5IG1ldGhvZCAoYnV0IGl0IHdpbGwgbm90IHdvcmsgd2VsbCB3aGVuIHlvdSBoYXZlIGxvdHMgb2YgY29udHJvbCB2YXJpYWJsZXMpDQoNCiAgICANCmBgYHtyfQ0KIyBGaXhlZCArIFJhbmRvbSBlZmZlY3RzDQpNYXRoLkRhdGEkTW9kZWwuMS5GaXR0ZWQuUmFuZG9tPC1wcmVkaWN0KE1vZGVsLjEpIA0KICAgICANCkNsYXNzUm9vbS5QbG90LkJldHRlciA8LWdncGxvdChkYXRhID0gTWF0aC5EYXRhKSsgDQogIGNvb3JkX2NhcnRlc2lhbih4bGltID0gYygwLCAxKSx5bGltID0gYygwLCAxMDApKSsgDQogIGdlb21fcG9pbnQoYWVzKHggPSBBY3RpdmVUaW1lLCB5PU1hdGgsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb2xvdXIgPSBDbGFzc3Jvb20sIHNoYXBlPUNsYXNzcm9vbSkpKw0KICBnZW9tX2xpbmUoYWVzKHggPSBBY3RpdmVUaW1lLCB5PU1vZGVsLjEuRml0dGVkLlJhbmRvbSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY29sb3VyID0gQ2xhc3Nyb29tKSkrDQogIHhsYWIoIlByb3BvcnRpb24gb2YgVGltZSBFbmdhZ2VkIGluIEFjdGl2ZSBMZWFybmluZyIpKw0KICB5bGFiKCJNYXRoIFNjb3JlIikrDQogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJ0b3AiKQ0KQ2xhc3NSb29tLlBsb3QuQmV0dGVyDQpgYGANCg0KLSBUaGlzIGlzIHdoYXQgdGhlIG1vZGVsIGJhc2ljYWxseSBmaXQgKGNvbnRyb2xpbmcgZm9yIHRoZSByYW5kb20gZWZmZWN0KSENCg0KLSBhIG1vcmUgYWNjdXJhdGUgd2F5IHRvIGdyYXBoIGl0IGlzIHNob3cgdGhlIGRhdGEgaW4gaXRzIGNsdXN0ZXJzDQoNCmBgYHtyfQ0KQ2xhc3NSb29tLlBsb3QuQmVzdCA8LWdncGxvdChkYXRhID0gTWF0aC5EYXRhLCBncm91cD1DbGFzc3Jvb20pKyANCiAgZmFjZXRfZ3JpZChDbGFzc3Jvb21+LikrDQogIGNvb3JkX2NhcnRlc2lhbih4bGltID0gYygwLCAxKSx5bGltID0gYygwLCAxMDApKSsgDQogIGdlb21fcG9pbnQoYWVzKHggPSBBY3RpdmVUaW1lLCB5PU1hdGgsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb2xvdXIgPSBDbGFzc3Jvb20sIHNoYXBlPUNsYXNzcm9vbSkpKw0KICBnZW9tX2xpbmUoYWVzKHggPSBBY3RpdmVUaW1lLCB5PU1vZGVsLjEuRml0dGVkLlJhbmRvbSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY29sb3VyID0gQ2xhc3Nyb29tKSkrDQogIHhsYWIoIlByb3BvcnRpb24gb2YgVGltZSBFbmdhZ2VkIGluIEFjdGl2ZSBMZWFybmluZyIpKw0KICB5bGFiKCJNYXRoIFNjb3JlIikrDQogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJ0b3AiKQ0KQ2xhc3NSb29tLlBsb3QuQmVzdA0KYGBgDQoNCg0KLSBUaGUgY29uZGl0aW9uYWwgJFJeMiQgd2lsbCBiZSBjcmF6eSBoaWdoIG5vdyAoZml4ZWQgdmFyaWFuY2UgKyByYW5kb20gdmFyaWFuY2UvIFRvdGFsIHZhcmlhbmNlKQ0KDQokJCANCiBSXjJfe2NvbmRpdGlvbmFsfSA9IFxmcmFje1xzaWdtYV4yX3tmaXhlZH0rXHNpZ21hXjJfe3JhbmRvbX19DQp7XHNpZ21hXjJfe2ZpeGVkfStcc2lnbWFeMl97cmFuZG9tfStcc2lnbWFeMl97cmVzaWR1YWx9fQ0KJCQNCg0KYGBge3J9DQpyc3F1YXJlZC5nbG1tKE1vZGVsLjEpDQpgYGANCg0KIyMjICRSXjIkDQotIEJvdGggJFJeMiQgdmFsdWVzIChmaXhlZCBvciBmaXhlZCArIHJhbmRvbSkgZG9udCByZWFsbHkgaGF2ZSBhbnkgbWVhbmluZyBpbiB0aGUgdHJhZGl0aW9uYWwgc2Vuc2UNCiAgICAtIEkgc2hvd2VkIHlvdSBiZWZvcmUgd2l0aCBJQ0MgaG93IG11Y2ggb2YgdGhlIHZhcmlhbmNlIHdhcyBleHBsYWluZWQgYnkgdGhlIHJhbmRvbSBlZmZlY3QNCi0gTm93IEkgYW0ganVzdCBtYWtpbmcgaXQgbG9vayBsaWtlIEkgZml0IGFsbCB0aGUgZGF0YSBhbmQgSSBhbSB0aGUgYmVzdCBtb2RlbGVyIGluIHRoZSB3b3JsZC4gDQogICAgLSBJbiByZWFsaXR5IGl0IGlzIGhhcmQgdG8gcmVhbGx5IGhvdyBnb29kIG91ciBmaXQgaXMsIHNvIGluc3RlYWQsIHdlIGFzayB0aGUgcXVlc3Rpb24gaXMgdGhlIG1vZGVsIHdpdGggb3VyIHByZWRpY3RzIGEgYmV0dGVyICJmaXQiIHRoYW4gb3VyIG51bGwgb3IgbG93ZXIgb3JkZXIgbW9kZWwuIA0KICAgIA0KIyMgRGV2aWFuY2UgdGVzdGluZyAoLTIgTG9nLUxpa2VsaWhvb2QpDQotIFdlIHdpbGwgY292ZXIgdGhlIHRoZW9yeSBvZiB0aGlzIG1vcmUgbmV4dCB3ZWVrIA0KLSBGb3Igbm93IGp1c3QgbG9vayBhdCB0aGUgcHZhbHVlIChpcyBtb2RlbCAxIGJldHRlciB0aGFuIHRoZSBudWxsIG1vZGVsKQ0KDQpgYGB7cn0NCmFub3ZhKE1vZGVsLk51bGwsTW9kZWwuMSkNCmBgYCANCg0KIyMgQ2VudGVyaW5nDQotIEkgaGF2ZSBub3QgeWV0IHNob3duIHlvdSBob3cgdG8gY2VudGVyIHRoZSBJViBjb3JyZWN0bHkNCi0gVGhlcmUgYXJlIHR3byBhcHByb2FjaGVzOiAqKkdyYW5kIG1lYW4qKiB2cyAqKkdyb3VwIGNlbnRlcmluZyoqDQogICAgLSBXaGVuIHRvIGNlbnRlciBhdCB0aGUgZ3JhbmQgbWVhbiBvciB0aGUgZ3JvdXA/DQogICAgICAgICAtIFRoaXMgZGVwZW5kcyBvbiB5b3VyIHF1ZXN0aW9uIGFuZCB0aGUgZGF0YQ0KDQojIyMgR3JhbmQgbWVhbg0KLSBZb3UgbWF5IHdhbnQgdG8gYXNrIGEgZ3JhbmQgbWVhbiBxdWVzdGlvbiAoaG93IGFsbCBwZW9wbGUgYXJlIGltcGFjdGVkIGJ5IHRoZSBwcmVkaWN0b3IgcmVnYXJkbGVzcyBvZiBncm91cCksIGJ1dCB0aGUgZGF0YSBtYXkgbm90IGFsbG93IHRoYXQgcXVlc3Rpb24gdG8gYmUgYXNrZWQgKGlmIHRoZSBwcmVkaWN0b3IgaXMgY29uZm91bmRlZCB3aXRoIGdyb3VwKQ0KICAgIC0gRm9yIGV4YW1wbGUsIGluIG91ciBleGFtcGxlIGl0cyBkYW5nZXJvdXMgdG8gbWFrZSBwcmVkaWN0aW9ucyBhcyBBY3RpdmVUaW1lIHJhbmdlIGlzIGNvbmZvdW5kZWQgd2l0aCBjbGFzc3Jvb20hIA0KICAgICAgICAtICpSZW1lbWJlciBiZSBjYXJlZnVsIGFib3V0IG1ha2luZyBwcmVkaWN0aW9ucyBhYm91dCBkYXRhIHlvdSBkbyBub3QgaGF2ZSoNCg0KYGBge3J9DQpNYXRoLkRhdGEkQWN0aXZlVGltZS5DPC1zY2FsZShNYXRoLkRhdGEkQWN0aXZlVGltZSwgY2VudGVyPVRSVUUsIHNjYWxlPUZBTFNFKQ0KTW9kZWwuMS5HTTwtbG1lcihNYXRoIH5BY3RpdmVUaW1lLkMrKDF8Q2xhc3Nyb29tKSwgIA0KICAgICAgICAgICAgICAgICAgIGRhdGE9TWF0aC5EYXRhLCBSRU1MPUZBTFNFKQ0Kc3VtbWFyeShNb2RlbC4xLkdNKQ0KYGBgDQoNCi0gWW91IHdpbGwgbm90aWNlIHRoZSBzbG9wZSBpcyB0aGUgc2FtZSBhcyBiZWZvcmUsIGJ1dCBpbnRlcmNlcHQgY2hhbmdlZA0KLSBUaGUgaW50ZXJjZXB0IGlzIG5vdyB0aGUgbWVhbiBvZiBBY3RpdmVUaW1lID0gIGByIG1lYW4oTWF0aC5EYXRhJEFjdGl2ZVRpbWUpYA0KLSBXaHk/IFNlZSBiZWxvdzoNCg0KYGBge3J9DQpNYXRoLkRhdGEkTW9kZWwuMS5GaXR0ZWQuUmFuZG9tLkdNPC1wcmVkaWN0KE1vZGVsLjEuR00pICNwbG90cyBmaXhlZCBSYW5kb20gZWZmZWN0cw0KICAgICANCkNsYXNzUm9vbS5QbG90LkJldHRlciA8LWdncGxvdCgpKyANCiAgY29vcmRfY2FydGVzaWFuKHhsaW0gPSBjKC0uNSwuNSkseWxpbSA9IGMoMCwgMTAwKSkrIA0KICBnZW9tX3BvaW50KGRhdGEgPSBNYXRoLkRhdGEsIGFlcyh4ID0gQWN0aXZlVGltZS5DLCB5PU1hdGgsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb2xvdXIgPSBDbGFzc3Jvb20sIHNoYXBlPUNsYXNzcm9vbSkpKw0KICBnZW9tX2xpbmUoZGF0YSA9IE1hdGguRGF0YSwgYWVzKHggPSBBY3RpdmVUaW1lLkMsIHk9TW9kZWwuMS5GaXR0ZWQuUmFuZG9tLkdNLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb2xvdXIgPSBDbGFzc3Jvb20pKSsNCiAgeGxhYigiUHJvcG9ydGlvbiBvZiBUaW1lIEVuZ2FnZWQgaW4gQWN0aXZlIExlYXJuaW5nXG4oR3JhbmQgTWVhbiBDZW50ZXJlZCkiKSsNCiAgeWxhYigiTWF0aCBTY29yZSIpKw0KICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAidG9wIikNCkNsYXNzUm9vbS5QbG90LkJldHRlcg0KYGBgDQoNCg0KIyMjIEdyb3VwIGNlbnRlcmluZw0KLSBHcm91cCBjZW50ZXJpbmcgYXNrcyBob3cgZG9lcyB0aGUgd2l0aGluLWdyb3VwIHZhcmlhdGlvbiBvZiB0aGUgcHJlZGljdG9yIHByZWRpY3RzIHRoZSBvdXRjb21lDQogICAgLSBUaGlzIGlzIG9mdGVuIGEgdmVyeSBpbnRlcmVzdGluZyBxdWVzdGlvbiBhbmQgbWF5IG5vdCBhbHdheXMgZGlmZmVyIGluIHN0b3J5IGZyb20gdGhlIGdyYW5kIG1lYW4NCi0gSG93ZXZlciwgdGVjaG5pY2FsbHkgaXRzIG1lYW5pbmcgaGFzIGNoYW5nZWQhIA0KICAgIC0gU28sIHlvdSBtdXN0IHRoaW5rIGFib3V0IHdoYXQgaXQgbWVhbnMgZm9yIHlvdXIgZGF0YSB0byBoYXZlIGNlbnRlcmVkIHdpdGhpbiBncm91cA0KLSBJIGhvdyBzaG93biB5b3UgaG93IHRvIGNvZGUgdGhpcyBhbHJlYWR5IGFjdHVhbGx5LCByZW1pbmRlciBvZiBjb2RlOg0KDQpgYGB7cn0NCiMjIyBSZXNjYWxlIElWDQpsaWJyYXJ5KHBseXIpDQpNYXRoLkRhdGE8LWRkcGx5KE1hdGguRGF0YSwuKENsYXNzcm9vbSksIG11dGF0ZSwgQ2xhc3NBY3RpdmVUaW1lID0gbWVhbihBY3RpdmVUaW1lKSkNCk1hdGguRGF0YSRBY3RpdmVUaW1lLkNsYXNzLkNlbnRlcmVkPC1NYXRoLkRhdGEkQWN0aXZlVGltZS1NYXRoLkRhdGEkQ2xhc3NBY3RpdmVUaW1lDQpgYGANCiANCiAtIEluIHRoaXMgY2FzZSBvdXIgbW9kZWwgd2lsbCBjaGFuZ2UgYSBiaXQNCiANCmBgYHtyfQ0KTW9kZWwuMS5HQzwtbG1lcihNYXRoIH5BY3RpdmVUaW1lLkNsYXNzLkNlbnRlcmVkKygxfENsYXNzcm9vbSksICANCiAgICAgICAgICAgICAgICAgICBkYXRhPU1hdGguRGF0YSwgUkVNTD1GQUxTRSkNCnN1bW1hcnkoTW9kZWwuMS5HQykNCmBgYA0KDQotIFRoZSBzbG9wZSBhbmQgaW50ZXJjZXB0IGNoYW5nZWQuIFdoeT8gU2VlIGJlbG93Og0KDQpgYGB7cn0NCk1hdGguRGF0YSRNb2RlbC4xLkZpdHRlZC5SYW5kb20uR0M8LXByZWRpY3QoTW9kZWwuMS5HQykgI3Bsb3RzIGZpeGVkIFJhbmRvbSBlZmZlY3RzDQogICAgIA0KQ2xhc3NSb29tLlBsb3QuQmV0dGVyIDwtZ2dwbG90KCkrIA0KICBjb29yZF9jYXJ0ZXNpYW4oeGxpbSA9IGMoLS4zLC4zKSx5bGltID0gYygwLCAxMDApKSsgDQogIGdlb21fcG9pbnQoZGF0YSA9IE1hdGguRGF0YSwgYWVzKHggPSBBY3RpdmVUaW1lLkNsYXNzLkNlbnRlcmVkLCB5PU1hdGgsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb2xvdXIgPSBDbGFzc3Jvb20sIHNoYXBlPUNsYXNzcm9vbSkpKw0KICBnZW9tX2xpbmUoZGF0YSA9IE1hdGguRGF0YSwgYWVzKHggPSBBY3RpdmVUaW1lLkNsYXNzLkNlbnRlcmVkLCB5PU1vZGVsLjEuRml0dGVkLlJhbmRvbS5HQywgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY29sb3VyID0gQ2xhc3Nyb29tKSkrDQogIHhsYWIoIlByb3BvcnRpb24gb2YgVGltZSBFbmdhZ2VkIGluIEFjdGl2ZSBMZWFybmluZ1xuKENlbnRlcmVkIHBlciBDbGFzc3Jvb20pIikrDQogIHlsYWIoIk1hdGggU2NvcmUiKSsNCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gInRvcCIpDQpDbGFzc1Jvb20uUGxvdC5CZXR0ZXINCmBgYA0KDQotIE1ha2Ugc3VyZSB0byBwYXkgYXR0ZW50aW9uIHRvIHRoZSB4LWF4aXMhDQoNCg0KPHNjcmlwdD4NCiAgKGZ1bmN0aW9uKGkscyxvLGcscixhLG0pe2lbJ0dvb2dsZUFuYWx5dGljc09iamVjdCddPXI7aVtyXT1pW3JdfHxmdW5jdGlvbigpew0KICAoaVtyXS5xPWlbcl0ucXx8W10pLnB1c2goYXJndW1lbnRzKX0saVtyXS5sPTEqbmV3IERhdGUoKTthPXMuY3JlYXRlRWxlbWVudChvKSwNCiAgbT1zLmdldEVsZW1lbnRzQnlUYWdOYW1lKG8pWzBdO2EuYXN5bmM9MTthLnNyYz1nO20ucGFyZW50Tm9kZS5pbnNlcnRCZWZvcmUoYSxtKQ0KICB9KSh3aW5kb3csZG9jdW1lbnQsJ3NjcmlwdCcsJ2h0dHBzOi8vd3d3Lmdvb2dsZS1hbmFseXRpY3MuY29tL2FuYWx5dGljcy5qcycsJ2dhJyk7DQoNCiAgZ2EoJ2NyZWF0ZScsICdVQS05MDQxNTE2MC0xJywgJ2F1dG8nKTsNCiAgZ2EoJ3NlbmQnLCAncGFnZXZpZXcnKTsNCg0KPC9zY3JpcHQ+