What does Linearity & Non-Linearity Mean?

  • The majority of psychologists often use these terms to describe the relationship between Y~X
    • Linearity = mathematical relationship between variables can be fit with a straight line
    • Non-Linear (Curvilinear) = mathematical relationship between variables can be fit with non-straight (Curvy) lines
      • However, be careful as for other fields or specialty areas of psychology these terms can mean different things: When people say non-linear, ask them what they mean

Non-Linear Fit Types that can be transformed into linear regression (basic types)

  • Polynomials: Power & Orthogonal
  • Growth: Exponential, Power, Logarithmic
  • Rates: Reciprocal
  • Correlations

Power Polynomials

  • Models that simply curves, such as quadratic or cubic effects
  • Linear: \(Y =B_{1}X + B_0\)
  • Quadratic: \(Y = B_{1.2}X + B_{2.1}X^2 + B_0\)
  • Cubic: \(Y = B_{1.23}X + B_{2.13}X^2 + B_{3.12}X^3 + B_0\)

Simulate a Exam Score ~ Anxiety

\(Y = 17.5X -2.5X^2 + 40 + \epsilon\)

Where, \(X\) = Uniform distribution of Likert scores between 0 to 7 (0 = No anxiety) & \(\epsilon\) = Random Normal [\(M = 0\) & \(S=10\)]

set.seed(42)    
n <- 250 # 250 people
x <- runif(n, 0, 7)
# Our equation to create Y
y <- -2.5*x^2 + 17.5*x+40+rnorm(n, sd=10)
#Build our data frame
Quad.Data<-data.frame(Anxiety=x,ExamScore=y)

Visualize Simulation/Data

Beyond visualizing the scatter plot, we need some way to estimate if the effect might be linear or non-linear

Loess Line

LOESS = Locally Weighted Scatterplot Smoothing

Does lots of regressions on small sections of the scatter plot to get the best fit (curvy) line. Allows you visualize complex relationships, but it can overfit the relationship (so be careful. Thus its a good diagnostic tool, but rarely used in papers.

library(ggpubr) #graph data
ggscatter(Quad.Data, x = "Anxiety", y = "ExamScore",
   add = "loess",  # Add loess
   add.params = list(color = "blue", fill = "lightblue"), # Customize reg. line
   conf.int = TRUE, # Add confidence interval
   cor.coef = FALSE, # Add correlation coefficient. see ?stat_cor
   size = 1 # Size of dots
   )

Fitting Power Polynomials

Forward Selection Approach

  • Model \(1\): Linear fit
  • Model \(2\): Quadratic fit
  • Check the change in \(R^2\) and select the best fit models
  • Model \(i\): keep going up until you are satisfied
    • Note: Higher order terms ALWAYS increase \(R^2\), you need to make sure it is both a significant improvement and meaningful improvement
    • in R there are two ways to add Power Polynomials: I(Term^2) or using the poly command
#Linear model
Model.1<-lm(ExamScore~Anxiety, data= Quad.Data)
#Quad model
Model.2<-lm(ExamScore~Anxiety+I(Anxiety^2), data= Quad.Data)
  • Test to see if the model is improved by the new term
anova(Model.1,Model.2)
## Analysis of Variance Table
## 
## Model 1: ExamScore ~ Anxiety
## Model 2: ExamScore ~ Anxiety + I(Anxiety^2)
##   Res.Df   RSS Df Sum of Sq      F    Pr(>F)    
## 1    248 43354                                  
## 2    247 21319  1     22036 255.31 < 2.2e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
  • Examine model paramaters
Dependent variable:
ExamScore
Linear Quadratic
(1) (2)
Constant 60.333*** (1.686) 39.681*** (1.753)
Anxiety -0.137 (0.411) 17.753*** (1.156)
I(Anxiety2) -2.559*** (0.160)
Observations 250 250
R2 0.0004 0.508
Adjusted R2 -0.004 0.505
Residual Std. Error 13.222 (df = 248) 9.290 (df = 247)
F Statistic 0.111 (df = 1; 248) 127.767*** (df = 2; 247)
Note: p<0.1; p<0.05; p<0.01
  • The change in \(R^2\) = 0.5080438 and we can see that the change was significant
  • Would the third order term be any better?
#Cubic model
Model.3<-lm(ExamScore~Anxiety+I(Anxiety^2)+I(Anxiety^3), data= Quad.Data)
anova(Model.1,Model.2,Model.3)
## Analysis of Variance Table
## 
## Model 1: ExamScore ~ Anxiety
## Model 2: ExamScore ~ Anxiety + I(Anxiety^2)
## Model 3: ExamScore ~ Anxiety + I(Anxiety^2) + I(Anxiety^3)
##   Res.Df   RSS Df Sum of Sq        F Pr(>F)    
## 1    248 43354                                 
## 2    247 21319  1   22035.8 255.0382 <2e-16 ***
## 3    246 21255  1      63.8   0.7378 0.3912    
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
Dependent variable:
ExamScore
Linear Quadratic Cubic
(1) (2) (3)
Constant 60.333*** (1.686) 39.681*** (1.753) 38.386*** (2.312)
Anxiety -0.137 (0.411) 17.753*** (1.156) 20.023*** (2.885)
I(Anxiety2) -2.559*** (0.160) -3.371*** (0.959)
I(Anxiety3) 0.077 (0.090)
Observations 250 250 250
R2 0.0004 0.508 0.510
Adjusted R2 -0.004 0.505 0.504
Residual Std. Error 13.222 (df = 248) 9.290 (df = 247) 9.295 (df = 246)
F Statistic 0.111 (df = 1; 248) 127.767*** (df = 2; 247) 85.333*** (df = 3; 246)
Note: p<0.1; p<0.05; p<0.01

Second order power polynomial was our best fit

Model 2 in Detail

Since we simulated our model we know what the answers should be \(Y = 17.5X -2.5X^2 + 40 +\epsilon\)

  • Extract our terms from our regression and compare to our simulated values.
IT<-round(Model.2$coefficients[1],2)
LT<-round(Model.2$coefficients[2],2)
QT<-round(Model.2$coefficients[3],2)

Regression estimated values: \(Y =\) 17.75\(X\) -2.56\(X^2 +\) 39.68 \(+\epsilon\)

  • Our regression did a good job, but what if our sample was smaller?
  • Redo our analysis with n = 5 to 60 and see how the terms change [see simulation in separate R file]
  • the gray band = 95% CI


It’s important to know that in small samples polynomial estimates can be very unstable. Also note: If you flip the quadratic term, the line will flip direction!

How to plot this function (and not the loess line)

  • You can solve the regression equation for different values of x
  • in R you can call the effects package to do work for you to solve the regression equation at different values of \(X\) [0 to 7].
library(effects)
plot(effect("Anxiety", Model.2,
            xlevels=list(Anxiety=seq(0,7,1))))

Code approach to power polynomials

You can use a command called poly which will do both power and orthogonal polynomials. But you cannot have missing data to use this code. The function has serval important arguments we will focus on: poly(x, degree = 1, raw = FALSE, simple=FALSE). degree = order number. So degree = 3 tests the linear, quadratic, and cubic. raw = FALSE tests the orthogonal versions. To keep them as power polynomials we must say raw = TRUE. Lastly, there is simple=FALSE, this is the default and keeps the attributes assigned the new vectors you have created. I suggest leaving this alone, meaning leave the default as is, but it is useful to know as sometimes when we pass the results of our model this can create problems with other packages (but I have yet to see this).

#Cubic model
Model.3.P<-lm(ExamScore~poly(Anxiety,3,raw=TRUE), data= Quad.Data)
summary(Model.3.P)
## 
## Call:
## lm(formula = ExamScore ~ poly(Anxiety, 3, raw = TRUE), data = Quad.Data)
## 
## Residuals:
##      Min       1Q   Median       3Q      Max 
## -25.7936  -6.7479  -0.0587   6.5252  24.9911 
## 
## Coefficients:
##                               Estimate Std. Error t value Pr(>|t|)    
## (Intercept)                   38.38640    2.31249  16.600  < 2e-16 ***
## poly(Anxiety, 3, raw = TRUE)1 20.02317    2.88488   6.941 3.44e-11 ***
## poly(Anxiety, 3, raw = TRUE)2 -3.37082    0.95900  -3.515 0.000523 ***
## poly(Anxiety, 3, raw = TRUE)3  0.07736    0.09006   0.859 0.391190    
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 9.295 on 246 degrees of freedom
## Multiple R-squared:   0.51,  Adjusted R-squared:  0.504 
## F-statistic: 85.33 on 3 and 246 DF,  p-value: < 2.2e-16

Above you can see the names of the paramaters in the model have changed.

  • poly(Anxiety, 3, raw = TRUE)1 = linear
  • poly(Anxiety, 3, raw = TRUE)2 = quadratic
  • poly(Anxiety, 3, raw = TRUE)3 = cubic.

Orthogonal Polynomials

Problem with Power polynomials is interpreting them independently. The higher order terms depend on, the lower order terms. Thus linear term can be significant cause the higher order term is significant, but there is no linear trend in the data (just like in today’s example). This is because power polynomials correlate with each other (they are are not unique predictors)

Linear.Values<-c(1,2,3,4,5,6,7)
Quad.Values<-Linear.Values^2

L.vs.Q<-data.frame(Linear.Values=Linear.Values,Quad.Values=Quad.Values)
ggscatter(L.vs.Q, x = "Linear.Values", y = "Quad.Values",
   add = "reg.line",  # Add loess
   add.params = list(color = "blue", fill = "lightblue"), # Customize reg. line
   conf.int = TRUE, # Add confidence interval
   cor.coef = TRUE, # Add correlation coefficient. see ?stat_cor
   size = 2 # Size of dots
   )

  • Thus they are basically multicollinear
  • Let’s run our VIF to verify
library(car)
vif(Model.2)
##      Anxiety I(Anxiety^2) 
##     16.05275     16.05275

Technically, it’s OK that they are multicollinear, but you cannot interpret each term without looking at the other (the p-values will be problematic).

  • The solution to make the linear and quadratic terms correlate with each at zero (just as we did with ANOVA)
  • We can use the poly code in R to make them Orthogonal.
  • Here is an example of what R is going to do:
LinearTerms<-c(1,2,3,4,5,6,7)
#make them Orthogonal
O.Poly<-poly(LinearTerms,2)

L.vs.Q.Ortho<-data.frame(Linear.Values.Ortho=O.Poly[,1],Quad.Values.Ortho=O.Poly[,2])

ggscatter(L.vs.Q.Ortho, x = "Linear.Values.Ortho", y = "Quad.Values.Ortho",
   add = "reg.line",  # Add loess
   add.params = list(color = "blue", fill = "lightblue"), # Customize reg. line
   conf.int = TRUE, # Add confidence interval
   cor.coef = TRUE, # Add correlation coefficient. see ?stat_cor
   size = 2 # Size of dots
   )

Correlation between linear and quadratic is now, r = 0!

#lets make a new poly vector
Model.1.O<-lm(ExamScore~poly(Anxiety, 1), data= Quad.Data)
Model.2.O<-lm(ExamScore~poly(Anxiety, 2), data= Quad.Data)

anova(Model.1.O,Model.2.O)
## Analysis of Variance Table
## 
## Model 1: ExamScore ~ poly(Anxiety, 1)
## Model 2: ExamScore ~ poly(Anxiety, 2)
##   Res.Df   RSS Df Sum of Sq      F    Pr(>F)    
## 1    248 43354                                  
## 2    247 21319  1     22036 255.31 < 2.2e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
Dependent variable:
ExamScore
Linear.Ortho Quadratic.Ortho
(1) (2)
Constant 59.845*** (0.836) 59.845*** (0.588)
poly(Anxiety, 1) -4.403 (13.222)
poly(Anxiety, 2)1 -4.403 (9.290)
poly(Anxiety, 2)2 -148.444*** (9.290)
Observations 250 250
R2 0.0004 0.508
Adjusted R2 -0.004 0.505
Residual Std. Error 13.222 (df = 248) 9.290 (df = 247)
F Statistic 0.111 (df = 1; 248) 127.767*** (df = 2; 247)
Note: p<0.1; p<0.05; p<0.01
  • Notice that the linear term is not significant, but the quadratic is
    • but the actual estimate of the slopes are nonsensical
  • This is because you have rescaled the x-values in a funny way (see plot above)
    • But no more multicollinearity
  • Let’s run our VIF to verify
    • Note: poly gets read to vif as one factor, so I will manually enter these into the model
Quad.Data$Anxiety.O.1<-poly(Quad.Data$Anxiety,2)[,1]
Quad.Data$Anxiety.O.2<-poly(Quad.Data$Anxiety,2)[,2]
Model.2.O.2<-lm(ExamScore~Anxiety.O.1+Anxiety.O.2, data= Quad.Data)
vif(Model.2.O.2)
## Anxiety.O.1 Anxiety.O.2 
##           1           1
  • Also the \(R^2\) do not differ between the power or orthogonal polynomials
  • Other pros of using orthogonal polynomials will come when we start looking at interactions

Coding shortcut (Use this for your analysis)

  • You don’t need to premake the vector as I did above
  • You can put the poly code right into the model
    • but the labeling will change
  • Also much easier to makes plots of the final model this way
Model.2.O.S<-lm(ExamScore~poly(Anxiety,2), data= Quad.Data)
Dependent variable:
ExamScore
Quadratic.Ortho
Constant 59.845*** (0.588)
poly(Anxiety, 2)1 -4.403 (9.290)
poly(Anxiety, 2)2 -148.444*** (9.290)
Observations 250
R2 0.508
Adjusted R2 0.505
Residual Std. Error 9.290 (df = 247)
F Statistic 127.767*** (df = 2; 247)
Note: p<0.1; p<0.05; p<0.01

The effects package will “know” you used poly command and convert the results back for you automatically

plot(effect("poly(Anxiety,2)", Model.2.O.S,
           xlevels=list(Anxiety=seq(0,7,1))))

  • Also, you could have said poly(Anxiety,2,raw=TRUE) to get Power polynomial on the fly

Diagnostics

  • When you have the wrong number of polynomials you will get funny looking residuals
library(ggfortify)
autoplot(Model.1, which = 1:2, label.size = 1) + theme_bw()

  • When you have the right number of polynomials, you will get correct looking residuals
  • Here is when we use 2 when we need 2
autoplot(Model.2.O.S, which = 1:2, label.size = 1) + theme_bw()

Dealing Growth Curves (Part 2)

  • These are models that cannot be fit with polynomials
  • Exponential growth: \(Y = ce^{dx}\)
  • Severity of panic attack (0 to 500) predicted by anxiety level
  • Exponential Example: \(Y = .09e^{1.2x+\epsilon}\)
  • Note: Euler’s number, \(e =\) 2.7182818, is an irrational math constant and the base value of the natural log
set.seed(42)
n <- 250
x <- runif(n, 0, 7)
#lets make our e value (not the error term)
e<-exp(1)
y <-.09*e^(1.20*(x+rnorm(n, sd=.25)))
Exp.Data<-data.frame(Anxiety=x,PanicAttack=y)

ggscatter(Exp.Data, x = "Anxiety", y = "PanicAttack",
   add = "loess",  # Add loess
   add.params = list(color = "blue", fill = "lightblue"), # Customize reg. line
   conf.int = TRUE, # Add confidence interval
   cor.coef = FALSE, # Add correlation coefficient. see ?stat_cor
   size = 1 # Size of dots
   )

Do Polynomials fail us?

  • Let’s try a second order fit since it one curve
Model.Panic.Poly<-lm(PanicAttack~poly(Anxiety,2), data= Exp.Data)
Dependent variable:
PanicAttack
Quadratic.Ortho
Constant 50.000*** (2.515)
poly(Anxiety, 2)1 1,006.757*** (39.762)
poly(Anxiety, 2)2 772.384*** (39.762)
Observations 250
R2 0.805
Adjusted R2 0.803
Residual Std. Error 39.762 (df = 247)
F Statistic 509.199*** (df = 2; 247)
Note: p<0.1; p<0.05; p<0.01
  • Using the poly right into the package can be useful for the effects package because we can now view independent impact of the linear and quadratic terms
plot(effect("poly(Anxiety,2)", Model.Panic.Poly,
           xlevels=list(Anxiety=seq(0,7,1))))

- Yikes, based on this you might tell people having zero anxiety is worse than having lower anxiety

  • Let check the residuals
autoplot(Model.Panic.Poly, which = 1:2, label.size = 1) + theme_bw()

  • Also looks pretty odd

Transformations of Exponential Model

  • The solution to this problem is often to transform the offending variable
  • The type of transform depends on the type of growth model you have
  • Exponential model: Dependent variable = log(y) [in r, log = natural log]
  • Thus: \(log(Y) = B_0 + BX\)
  • And to convert from log into meaningful values:
  • \(Y = e^{B_0 + BX}\)
  • Let’s look at Log(Y) by Anxiety [we will use the scatterplot function in car package to see this more quickly]
library(car)
scatterplot(log(PanicAttack)~Anxiety, data= Exp.Data)

  • Looks like a line now
  • Let’s look at the model fit
Model.Panic.Log<-lm(log(PanicAttack)~Anxiety, data= Exp.Data)
Dependent variable:
log(PanicAttack)
Constant -2.403*** (0.035)
Anxiety 1.195*** (0.009)
Observations 250
R2 0.987
Adjusted R2 0.987
Residual Std. Error 0.278 (df = 248)
F Statistic 19,129.080*** (df = 1; 248)
Note: p<0.1; p<0.05; p<0.01

Plot the fit

plot(effect("Anxiety", Model.Panic.Log,
           xlevels=list(Anxiety=seq(0,7,1))))

You can plot it non-log units (to unlog use exp function), but you have to tell the package to convert it back to raw score.

plot(effect("Anxiety", Model.Panic.Log,
          transformation=list(link=log, inverse=exp)))

Plot the fit and check the residuals

autoplot(Model.Panic.Log, which = 1:2, label.size = 1) + theme_bw()

Those look as they should.

Challenge of Working with Transforms

  • Problem is our scores are in log (not raw score)
  • to convert from log into meaningful values:
  • \(Y = e^{B_0 + BX}\)
#Intercept
IT.L<-round(Model.Panic.Log$coefficients[1],2)
#slope
LT.L<-round(Model.Panic.Log$coefficients[2],2)
  • \(Y = e\)^(-2.4 + 1.2X)
  • For Anxiety score of X = 0, Y = 0.090718
  • For Anxiety score of X = 4, Y = 11.0231764
  • For Anxiety score of X = 7, Y = 403.4287935
  • [Note: Be careful about which log you use (natural or base 10, you can use either but make sure to specify)]

Power law

  • Common function in motor control and perception experiments
  • \(Y = cX^d\)
  • Severity of sweating (0 to 100) predicted by anxiety level
  • Exponential Example: \(Y = .07(X)^{1.7}+\epsilon\)
  • Note: Natural log of 0 = -inf, so if you have zeros you need to always add 1
set.seed(42)
n <- 250
x <- runif(n, 0, 7)+1
y <-.07*(x)^1.7
y = y * (.05* (5+rnorm(n, sd=.25)))
Power.Data<-data.frame(Anxiety=x,Sweating=y)

ggscatter(Power.Data, x = "Anxiety", y = "Sweating",
   add = "loess",  # Add loess
   add.params = list(color = "blue", fill = "lightblue"), # Customize reg. line
   conf.int = TRUE, # Add confidence interval
   cor.coef = FALSE, # Add correlation coefficient. see ?stat_cor
   size = 1 # Size of dots
   )

Transformations of Power Law

  • The solution to this problem is often to transform the offending variable
  • Power law model: DV and IV get logged: log(y) & log(x)
  • Thus: \(log(Y) = B_0 + B(logX)\)
  • And to convert from log into meaningful values:
  • \(Y = e^{B_0 + B(logX)}\)
  • Lets look at the log-log scatterplot
scatterplot(log(Sweating)~log(Anxiety), data= Power.Data)

  • Looks like a line now!
  • Let’s look at the model fit
Model.Sweat.LL<-lm(log(Sweating)~log(Anxiety), data= Power.Data)
Dependent variable:
log(Sweating)
Log-Log Transform
Constant -4.046*** (0.008)
log(Anxiety) 1.698*** (0.005)
Observations 250
R2 0.998
Adjusted R2 0.998
Residual Std. Error 0.046 (df = 248)
F Statistic 100,505.500*** (df = 1; 248)
Note: p<0.1; p<0.05; p<0.01

Plot the fit and tell the package that you did a transform

plot(effect("Anxiety", Model.Sweat.LL,
           transformation=list(link=log, inverse=exp)))

Check the residuals

autoplot(Model.Sweat.LL, which = 1:2, label.size = 1) + theme_bw()

They look OK.

Logarithmic

  • These functions are often physical (sound intensity, earthquakes, etc.)
  • \(c^Y = dX\)
  • Breathing rate (in ms between breaths) predicted by anxiety level
  • Logarithmic Example: \(Y = .05(X*\epsilon)^{3.8}\)
set.seed(42)
n <- 250
x <- runif(n, 0, 7)+1
y <-200*log(x*(5+rnorm(n, sd=1)))
Logarithmic.Data<-data.frame(Anxiety=x,Breathing=y)

ggscatter(Logarithmic.Data, x = "Anxiety", y = "Breathing",
   add = "loess",  # Add loess
   add.params = list(color = "blue", fill = "lightblue"), # Customize reg. line
   conf.int = TRUE, # Add confidence interval
   cor.coef = FALSE, # Add correlation coefficient. see ?stat_cor
   size = 1 # Size of dots
   )

  • Plot Breathing by log(Anxiety)
scatterplot(Breathing~log(Anxiety), data= Logarithmic.Data)

  • Looks like a line now
  • Let’s look at the model fit
Model.Breath.Log<-lm(Breathing~log(Anxiety), data= Logarithmic.Data)
Dependent variable:
Breathing
Log Transform
Constant 319.641*** (6.656)
log(Anxiety) 197.847*** (4.456)
Observations 250
R2 0.888
Adjusted R2 0.888
Residual Std. Error 38.680 (df = 248)
F Statistic 1,971.251*** (df = 1; 248)
Note: p<0.1; p<0.05; p<0.01
  • Let’s plot the fit
plot(effect("Anxiety", Model.Breath.Log))

  • Let’s check the residuals
autoplot(Model.Breath.Log, which = 1:2, label.size = 1) + theme_bw()

Reciprocals

  • Common fix for rate data (things involving time)
  • \(1/Y\)
  • Adrenaline level in the blood predicted by mins after a panic attack (mins)
  • Reciprocal Example: \(Y = (1/X)*\epsilon\)
set.seed(42)
n <- 250
# Length of panic attack
x <- 15*(runif(n, 1, 4))
y <- 1/(x)*rnorm(n,5,sd=.1)
Reciprocal.Data<-data.frame(PostPanic=x,Adrenaline=y)

ggscatter(Reciprocal.Data, x = "PostPanic", y = "Adrenaline",
   add = "loess",  # Add loess
   add.params = list(color = "blue", fill = "lightblue"), # Customize reg. line
   conf.int = TRUE, # Add confidence interval
   cor.coef = FALSE, # Add correlation coefficient. see ?stat_cor
   size = 1 # Size of dots
   )

We can take the Reciprocal of Adrenaline on the fly and replot

scatterplot(1/Adrenaline~PostPanic, data= Reciprocal.Data)

  • Looks like a line now
  • Let’s look at the model fit
Model.Calm.Rep<-lm(1/Adrenaline~PostPanic, data= Reciprocal.Data)
Dependent variable:
1/Adrenaline
Constant -0.015 (0.029)
PostPanic 0.201*** (0.001)
Observations 250
R2 0.997
Adjusted R2 0.997
Residual Std. Error 0.149 (df = 248)
F Statistic 77,222.570*** (df = 1; 248)
Note: p<0.1; p<0.05; p<0.01

Plot the fit

plot(effect("PostPanic", Model.Calm.Rep))

Check the residuals

autoplot(Model.Calm.Rep, which = 1:2, label.size = 1) + theme_bw()

A little off (but I add in noise in a simple way)

Weird Bulges in the data (not full on curves)

  • Data do not always fit our nice predefined functions
  • Sometimes they clearly have bulges, but are we sure those are real?
  • Bulges can be not real in small samples or can result from some latent factor you are not accounting for
  • Let’s assume that if the data has a bulge that is real, meaningful, and we need to account for it
  • We can use the Box-Cox Transform (fits different polynomials which we call \(\lambda\))
  • \(Y = Y^\lambda - 1 / \lambda\), where \(\lambda \neq 0\) OR \(Y = lnY\), where \(\lambda = 0\)
  • In this case, we will allow \(\lambda\) to be selected based on which makes the data most normal
  • Let’s make a case with a weird power polynomial of 1.3 (something you would not notice in your raw data)
set.seed(42)
# 250 people
n <- 250
x <- runif(n, 0, 7)
y <- -4*x^1.3+ 20*x+rnorm(n, sd=2)+20
SortaQuad.Data<-data.frame(Anxiety=x,ExamScore=y)

ggscatter(SortaQuad.Data, x = "Anxiety", y = "ExamScore",
   add = "loess",  # Add loess
   add.params = list(color = "blue", fill = "lightblue"), # Customize reg. line
   conf.int = TRUE, # Add confidence interval
   cor.coef = FALSE, # Add correlation coefficient. see ?stat_cor
   size = 1 # Size of dots
   )

  • Let’s try a linear model
Model.SortaQuad<-lm(ExamScore~Anxiety, data= SortaQuad.Data)
  • Plot the Residuals
autoplot(Model.SortaQuad, which = 1:2, label.size = 1) + theme_bw()

  • It seems there is a slight curve in our data
library(MASS)
bc<-boxcox(ExamScore ~Anxiety, data = SortaQuad.Data,
       lambda = seq(-2, 2, len = 20))
#we can extract the lambda
(lambda <- bc$x[which.max(bc$y)])
## [1] 1.272727

Plot the scatter and residuals from our transformed DV

scatterplot(ExamScore^lambda~Anxiety, data= SortaQuad.Data, reg.line=FALSE, smoother=loessLine)
Model.SortaQuad.BC<-lm(ExamScore^lambda ~ Anxiety, data= SortaQuad.Data)

autoplot(Model.SortaQuad.BC, which = 1:2, label.size = 1) + theme_bw()

Some issues

  • Box-Cox is a guess and requires some fine tuning (like adjusting the range of lambdas)
  • You have to keep track of your zeros in the DV (so you can add constants to fix it)
  • Most importantly, you have to make it harder to make sense of the DV (but your fits will be far better using it)
LS0tDQp0aXRsZTogJ05vbi1MaW5lYXIgTW9kZWxzJw0Kb3V0cHV0Og0KICBodG1sX2RvY3VtZW50Og0KICAgIGNvZGVfZG93bmxvYWQ6IHllcw0KICAgIGZvbnRzaXplOiA4cHQNCiAgICBoaWdobGlnaHQ6IHRleHRtYXRlDQogICAgbnVtYmVyX3NlY3Rpb25zOiBubw0KICAgIHRoZW1lOiBmbGF0bHkNCiAgICB0b2M6IHllcw0KICAgIHRvY19mbG9hdDoNCiAgICAgIGNvbGxhcHNlZDogbm8NCi0tLQ0KDQpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0NCmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvID0gVFJVRSkgI1Nob3cgYWxsIHNjcmlwdCBieSBkZWZhdWx0DQprbml0cjo6b3B0c19jaHVuayRzZXQobWVzc2FnZSA9IEZBTFNFKSAjaGlkZSBtZXNzYWdlcyANCmtuaXRyOjpvcHRzX2NodW5rJHNldCh3YXJuaW5nID0gIEZBTFNFKSAjaGlkZSBwYWNrYWdlIHdhcm5pbmdzIA0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGZpZy53aWR0aD0zLjUpICNTZXQgZGVmYXVsdCBmaWd1cmUgc2l6ZXMNCmtuaXRyOjpvcHRzX2NodW5rJHNldChmaWcuaGVpZ2h0PTIuNzUpICNTZXQgZGVmYXVsdCBmaWd1cmUgc2l6ZXMNCmtuaXRyOjpvcHRzX2NodW5rJHNldChmaWcuYWxpZ249J2NlbnRlcicpICNTZXQgZGVmYXVsdCBmaWd1cmUNCmtuaXRyOjpvcHRzX2NodW5rJHNldChmaWcuc2hvdyA9ICJob2xkIikgI1NldCBkZWZhdWx0IGZpZ3VyZQ0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KHJlc3VsdHMgPSAiaG9sZCIpICNTZXQgZGVmYXVsdCBmaWd1cmUNCmBgYA0KDQpccGFnZWJyZWFrDQoNCiMgV2hhdCBkb2VzIExpbmVhcml0eSAmIE5vbi1MaW5lYXJpdHkgTWVhbj8NCi0gVGhlIG1ham9yaXR5IG9mIHBzeWNob2xvZ2lzdHMgb2Z0ZW4gdXNlIHRoZXNlIHRlcm1zIHRvIGRlc2NyaWJlIHRoZSByZWxhdGlvbnNoaXAgYmV0d2VlbiBZflgNCiAgICAtIExpbmVhcml0eSA9IG1hdGhlbWF0aWNhbCByZWxhdGlvbnNoaXAgYmV0d2VlbiB2YXJpYWJsZXMgY2FuIGJlIGZpdCB3aXRoIGEgKnN0cmFpZ2h0KiBsaW5lDQogICAgLSBOb24tTGluZWFyIChDdXJ2aWxpbmVhcikgPSBtYXRoZW1hdGljYWwgcmVsYXRpb25zaGlwIGJldHdlZW4gdmFyaWFibGVzIGNhbiBiZSBmaXQgd2l0aCAqbm9uLXN0cmFpZ2h0KiAoKipDdXJ2eSoqKSBsaW5lcyANCiAgICAgICAgLSBIb3dldmVyLCBiZSBjYXJlZnVsIGFzIGZvciBvdGhlciBmaWVsZHMgb3Igc3BlY2lhbHR5IGFyZWFzIG9mIHBzeWNob2xvZ3kgdGhlc2UgdGVybXMgY2FuIG1lYW4gZGlmZmVyZW50IHRoaW5nczogV2hlbiBwZW9wbGUgc2F5IG5vbi1saW5lYXIsIGFzayB0aGVtIHdoYXQgdGhleSBtZWFuDQoNCiMgTm9uLUxpbmVhciBGaXQgVHlwZXMgdGhhdCBjYW4gYmUgdHJhbnNmb3JtZWQgaW50byBsaW5lYXIgcmVncmVzc2lvbiAoYmFzaWMgdHlwZXMpDQotIFBvbHlub21pYWxzOiBQb3dlciAmIE9ydGhvZ29uYWwgDQotIEdyb3d0aDogRXhwb25lbnRpYWwsIFBvd2VyLCBMb2dhcml0aG1pYw0KLSBSYXRlczogUmVjaXByb2NhbA0KLSBDb3JyZWxhdGlvbnMNCg0KIyMgUG93ZXIgUG9seW5vbWlhbHMgDQotIE1vZGVscyB0aGF0IHNpbXBseSBjdXJ2ZXMsIHN1Y2ggYXMgcXVhZHJhdGljIG9yIGN1YmljIGVmZmVjdHMgIA0KLSBMaW5lYXI6ICRZID1CX3sxfVggKyBCXzAkDQotIFF1YWRyYXRpYzogJFkgPSBCX3sxLjJ9WCArIEJfezIuMX1YXjIgKyBCXzAkDQotIEN1YmljOiAkWSA9IEJfezEuMjN9WCArIEJfezIuMTN9WF4yICsgQl97My4xMn1YXjMgKyBCXzAkDQoNCiMjIyBTaW11bGF0ZSBhIEV4YW0gU2NvcmUgfiBBbnhpZXR5IA0KDQokWSA9IDE3LjVYIC0yLjVYXjIgKyA0MCArIFxlcHNpbG9uJA0KDQpXaGVyZSwgJFgkID0gVW5pZm9ybSBkaXN0cmlidXRpb24gb2YgTGlrZXJ0IHNjb3JlcyBiZXR3ZWVuIDAgdG8gNyAoMCA9IE5vIGFueGlldHkpICYgJFxlcHNpbG9uJCA9IFJhbmRvbSBOb3JtYWwgWyRNID0gMCQgJiAkUz0xMCRdDQoNCmBgYHtyLCBlY2hvPVRSVUUsIHdhcm5pbmc9RkFMU0V9DQpzZXQuc2VlZCg0MikJDQpuIDwtIDI1MCAjIDI1MCBwZW9wbGUNCnggPC0gcnVuaWYobiwgMCwgNykNCiMgT3VyIGVxdWF0aW9uIHRvIGNyZWF0ZSBZDQp5IDwtIC0yLjUqeF4yICsgMTcuNSp4KzQwK3Jub3JtKG4sIHNkPTEwKQ0KI0J1aWxkIG91ciBkYXRhIGZyYW1lDQpRdWFkLkRhdGE8LWRhdGEuZnJhbWUoQW54aWV0eT14LEV4YW1TY29yZT15KQ0KYGBgDQoNCiMjIFZpc3VhbGl6ZSBTaW11bGF0aW9uL0RhdGENCkJleW9uZCB2aXN1YWxpemluZyB0aGUgc2NhdHRlciBwbG90LCB3ZSBuZWVkIHNvbWUgd2F5IHRvIGVzdGltYXRlIGlmIHRoZSBlZmZlY3QgbWlnaHQgYmUgbGluZWFyIG9yIG5vbi1saW5lYXINCg0KIyMjIExvZXNzIExpbmUNCkxPRVNTID0gKipMbyoqY2FsbHkgVyoqZSoqaWdodGVkICoqUyoqY2F0dGVycGxvdCAqKlMqKm1vb3RoaW5nIA0KDQpEb2VzIGxvdHMgb2YgcmVncmVzc2lvbnMgb24gc21hbGwgc2VjdGlvbnMgb2YgdGhlIHNjYXR0ZXIgcGxvdCB0byBnZXQgdGhlIGJlc3QgZml0IChjdXJ2eSkgbGluZS4gQWxsb3dzIHlvdSB2aXN1YWxpemUgY29tcGxleCByZWxhdGlvbnNoaXBzLCBidXQgaXQgY2FuICpvdmVyZml0KiB0aGUgcmVsYXRpb25zaGlwIChzbyBiZSBjYXJlZnVsLiBUaHVzIGl0cyBhIGdvb2QgZGlhZ25vc3RpYyB0b29sLCBidXQgcmFyZWx5IHVzZWQgaW4gcGFwZXJzLg0KDQpgYGB7cn0NCmxpYnJhcnkoZ2dwdWJyKSAjZ3JhcGggZGF0YQ0KZ2dzY2F0dGVyKFF1YWQuRGF0YSwgeCA9ICJBbnhpZXR5IiwgeSA9ICJFeGFtU2NvcmUiLA0KICAgYWRkID0gImxvZXNzIiwgICMgQWRkIGxvZXNzDQogICBhZGQucGFyYW1zID0gbGlzdChjb2xvciA9ICJibHVlIiwgZmlsbCA9ICJsaWdodGJsdWUiKSwgIyBDdXN0b21pemUgcmVnLiBsaW5lDQogICBjb25mLmludCA9IFRSVUUsICMgQWRkIGNvbmZpZGVuY2UgaW50ZXJ2YWwNCiAgIGNvci5jb2VmID0gRkFMU0UsICMgQWRkIGNvcnJlbGF0aW9uIGNvZWZmaWNpZW50LiBzZWUgP3N0YXRfY29yDQogICBzaXplID0gMSAjIFNpemUgb2YgZG90cw0KICAgKQ0KYGBgDQoNCg0KIyMgRml0dGluZyBQb3dlciBQb2x5bm9taWFscyANCkZvcndhcmQgU2VsZWN0aW9uIEFwcHJvYWNoDQoNCi0gTW9kZWwgJDEkOiBMaW5lYXIgZml0DQotIE1vZGVsICQyJDogUXVhZHJhdGljIGZpdA0KLSBDaGVjayB0aGUgY2hhbmdlIGluICRSXjIkIGFuZCBzZWxlY3QgdGhlIGJlc3QgZml0IG1vZGVscw0KLSBNb2RlbCAkaSQ6IGtlZXAgZ29pbmcgdXAgdW50aWwgeW91IGFyZSBzYXRpc2ZpZWQgDQogICAgLSBOb3RlOiBIaWdoZXIgb3JkZXIgdGVybXMgKipBTFdBWVMqKiBpbmNyZWFzZSAkUl4yJCwgeW91IG5lZWQgdG8gbWFrZSBzdXJlIGl0IGlzIGJvdGggYSBzaWduaWZpY2FudCBpbXByb3ZlbWVudCBhbmQgbWVhbmluZ2Z1bCBpbXByb3ZlbWVudCANCiAgICAtIGluIFIgdGhlcmUgYXJlIHR3byB3YXlzIHRvIGFkZCBQb3dlciBQb2x5bm9taWFsczogKkkoVGVybV4yKSogb3IgdXNpbmcgdGhlICpwb2x5KiBjb21tYW5kDQoNCmBgYHtyfQ0KI0xpbmVhciBtb2RlbA0KTW9kZWwuMTwtbG0oRXhhbVNjb3JlfkFueGlldHksIGRhdGE9IFF1YWQuRGF0YSkNCiNRdWFkIG1vZGVsDQpNb2RlbC4yPC1sbShFeGFtU2NvcmV+QW54aWV0eStJKEFueGlldHleMiksIGRhdGE9IFF1YWQuRGF0YSkNCmBgYA0KDQotIFRlc3QgdG8gc2VlIGlmIHRoZSBtb2RlbCBpcyBpbXByb3ZlZCBieSB0aGUgbmV3IHRlcm0NCg0KYGBge3J9DQphbm92YShNb2RlbC4xLE1vZGVsLjIpDQpgYGANCg0KLSBFeGFtaW5lIG1vZGVsIHBhcmFtYXRlcnMNCg0KYGBge3IsIGVjaG89RkFMU0UsIHJlc3VsdHM9J2FzaXMnfQ0KbGlicmFyeShzdGFyZ2F6ZXIpDQpzdGFyZ2F6ZXIoTW9kZWwuMSxNb2RlbC4yLHR5cGU9Imh0bWwiLA0KICAgICAgICAgIGNvbHVtbi5sYWJlbHMgPSBjKCJMaW5lYXIiLCAiUXVhZHJhdGljIiksDQogICAgICAgICAgaW50ZXJjZXB0LmJvdHRvbSA9IEZBTFNFLCBzaW5nbGUucm93PVRSVUUsIA0KICAgICAgICAgIG5vdGVzLmFwcGVuZCA9IEZBTFNFLCBoZWFkZXI9RkFMU0UpDQpgYGANCg0KLSBUaGUgY2hhbmdlIGluICRSXjIkID0gYHIgc3VtbWFyeShNb2RlbC4yKSRyLnNxdWFyZSAtIHN1bW1hcnkoTW9kZWwuMSkkci5zcXVhcmVgIGFuZCB3ZSBjYW4gc2VlIHRoYXQgdGhlIGNoYW5nZSB3YXMgc2lnbmlmaWNhbnQNCi0gV291bGQgdGhlIHRoaXJkIG9yZGVyIHRlcm0gYmUgYW55IGJldHRlcj8gDQoNCmBgYHtyfQ0KI0N1YmljIG1vZGVsDQpNb2RlbC4zPC1sbShFeGFtU2NvcmV+QW54aWV0eStJKEFueGlldHleMikrSShBbnhpZXR5XjMpLCBkYXRhPSBRdWFkLkRhdGEpDQphbm92YShNb2RlbC4xLE1vZGVsLjIsTW9kZWwuMykNCmBgYA0KDQoNCmBgYHtyLCBlY2hvPUZBTFNFLHJlc3VsdHM9J2hvbGQnLHJlc3VsdHM9J2FzaXMnfQ0Kc3RhcmdhemVyKE1vZGVsLjEsTW9kZWwuMixNb2RlbC4zLHR5cGU9Imh0bWwiLA0KICAgICAgICAgIGNvbHVtbi5sYWJlbHMgPSBjKCJMaW5lYXIiLCAiUXVhZHJhdGljIiwiQ3ViaWMiKSwNCiAgICAgICAgICBpbnRlcmNlcHQuYm90dG9tID0gRkFMU0UsIHNpbmdsZS5yb3c9VFJVRSwgDQogICAgICAgICAgbm90ZXMuYXBwZW5kID0gRkFMU0UsIGhlYWRlcj1GQUxTRSkNCmBgYA0KDQoqKlNlY29uZCBvcmRlciBwb3dlciBwb2x5bm9taWFsIHdhcyBvdXIgYmVzdCBmaXQqKg0KDQojIyMgTW9kZWwgMiBpbiBEZXRhaWwNClNpbmNlIHdlIHNpbXVsYXRlZCBvdXIgbW9kZWwgd2Uga25vdyB3aGF0IHRoZSBhbnN3ZXJzICoqc2hvdWxkKiogYmUgJFkgPSAxNy41WCAtMi41WF4yICsgNDAgK1xlcHNpbG9uJA0KDQotIEV4dHJhY3Qgb3VyIHRlcm1zIGZyb20gb3VyIHJlZ3Jlc3Npb24gYW5kIGNvbXBhcmUgdG8gb3VyIHNpbXVsYXRlZCB2YWx1ZXMuDQoNCmBgYHtyfQ0KSVQ8LXJvdW5kKE1vZGVsLjIkY29lZmZpY2llbnRzWzFdLDIpDQpMVDwtcm91bmQoTW9kZWwuMiRjb2VmZmljaWVudHNbMl0sMikNClFUPC1yb3VuZChNb2RlbC4yJGNvZWZmaWNpZW50c1szXSwyKQ0KYGBgDQoNClJlZ3Jlc3Npb24gZXN0aW1hdGVkIHZhbHVlczogJFkgPSQgYHIgTFRgJFgkIGByIFFUYCRYXjIgKyQgYHIgSVRgICQrXGVwc2lsb24kDQoNCi0gT3VyIHJlZ3Jlc3Npb24gZGlkIGEgZ29vZCBqb2IsIGJ1dCB3aGF0IGlmIG91ciBzYW1wbGUgd2FzIHNtYWxsZXI/DQotIFJlZG8gb3VyIGFuYWx5c2lzIHdpdGggbiA9IDUgdG8gNjAgYW5kIHNlZSBob3cgdGhlIHRlcm1zIGNoYW5nZSBbc2VlIHNpbXVsYXRpb24gaW4gc2VwYXJhdGUgUiBmaWxlXQ0KLSB0aGUgZ3JheSBiYW5kID0gOTUlIENJDQoNCiFbXShSZWdyZXNzaW9uQ2xhc3MvU2ltcGxvdDEuanBlZykNClwNCg0KDQpJdCdzIGltcG9ydGFudCB0byBrbm93IHRoYXQgaW4gc21hbGwgc2FtcGxlcyBwb2x5bm9taWFsIGVzdGltYXRlcyBjYW4gYmUgdmVyeSB1bnN0YWJsZS4gQWxzbyBub3RlOiAqSWYgeW91IGZsaXAgdGhlIHF1YWRyYXRpYyB0ZXJtLCB0aGUgbGluZSB3aWxsIGZsaXAgZGlyZWN0aW9uKiENCg0KDQojIyMgSG93IHRvIHBsb3QgdGhpcyBmdW5jdGlvbiAoYW5kIG5vdCB0aGUgbG9lc3MgbGluZSkNCi0gWW91IGNhbiBzb2x2ZSB0aGUgcmVncmVzc2lvbiBlcXVhdGlvbiBmb3IgZGlmZmVyZW50IHZhbHVlcyBvZiB4DQotIGluIFIgeW91IGNhbiBjYWxsIHRoZSBgZWZmZWN0c2AgcGFja2FnZSB0byBkbyB3b3JrIGZvciB5b3UgdG8gc29sdmUgdGhlIHJlZ3Jlc3Npb24gZXF1YXRpb24gYXQgZGlmZmVyZW50IHZhbHVlcyBvZiAkWCQgWzAgdG8gN10uIA0KDQpgYGB7cn0NCmxpYnJhcnkoZWZmZWN0cykNCnBsb3QoZWZmZWN0KCJBbnhpZXR5IiwgTW9kZWwuMiwNCiAgICAgICAgICAgIHhsZXZlbHM9bGlzdChBbnhpZXR5PXNlcSgwLDcsMSkpKSkNCmBgYA0KDQojIyMgQ29kZSBhcHByb2FjaCB0byBwb3dlciBwb2x5bm9taWFscw0KWW91IGNhbiB1c2UgYSBjb21tYW5kIGNhbGxlZCBgcG9seWAgd2hpY2ggd2lsbCBkbyBib3RoIHBvd2VyIGFuZCBvcnRob2dvbmFsIHBvbHlub21pYWxzLiBCdXQgeW91IGNhbm5vdCBoYXZlIG1pc3NpbmcgZGF0YSB0byB1c2UgdGhpcyBjb2RlLiBUaGUgZnVuY3Rpb24gaGFzIHNlcnZhbCBpbXBvcnRhbnQgYXJndW1lbnRzIHdlIHdpbGwgZm9jdXMgb246IGBwb2x5KHgsIGRlZ3JlZSA9IDEsIHJhdyA9IEZBTFNFLCBzaW1wbGU9RkFMU0UpYC4gZGVncmVlID0gb3JkZXIgbnVtYmVyLiBTbyBgZGVncmVlID0gM2AgdGVzdHMgdGhlIGxpbmVhciwgcXVhZHJhdGljLCBhbmQgY3ViaWMuIGByYXcgPSBGQUxTRWAgdGVzdHMgdGhlIG9ydGhvZ29uYWwgdmVyc2lvbnMuIFRvIGtlZXAgdGhlbSBhcyBwb3dlciBwb2x5bm9taWFscyB3ZSBtdXN0IHNheSBgIHJhdyA9IFRSVUVgLiBMYXN0bHksIHRoZXJlIGlzIGBzaW1wbGU9RkFMU0VgLCB0aGlzIGlzIHRoZSBkZWZhdWx0IGFuZCBrZWVwcyB0aGUgYXR0cmlidXRlcyBhc3NpZ25lZCB0aGUgbmV3IHZlY3RvcnMgeW91IGhhdmUgY3JlYXRlZC4gIEkgc3VnZ2VzdCBsZWF2aW5nIHRoaXMgYWxvbmUsIG1lYW5pbmcgbGVhdmUgdGhlIGRlZmF1bHQgYXMgaXMsIGJ1dCBpdCBpcyB1c2VmdWwgdG8ga25vdyBhcyBzb21ldGltZXMgd2hlbiB3ZSBwYXNzIHRoZSByZXN1bHRzIG9mIG91ciBtb2RlbCB0aGlzIGNhbiBjcmVhdGUgcHJvYmxlbXMgd2l0aCBvdGhlciBwYWNrYWdlcyAoYnV0IEkgaGF2ZSB5ZXQgdG8gc2VlIHRoaXMpLiANCg0KYGBge3J9DQojQ3ViaWMgbW9kZWwNCk1vZGVsLjMuUDwtbG0oRXhhbVNjb3JlfnBvbHkoQW54aWV0eSwzLHJhdz1UUlVFKSwgZGF0YT0gUXVhZC5EYXRhKQ0Kc3VtbWFyeShNb2RlbC4zLlApDQpgYGANCg0KQWJvdmUgeW91IGNhbiBzZWUgdGhlIG5hbWVzIG9mIHRoZSBwYXJhbWF0ZXJzIGluIHRoZSBtb2RlbCBoYXZlIGNoYW5nZWQuIA0KDQotIHBvbHkoQW54aWV0eSwgMywgcmF3ID0gVFJVRSkxID0gbGluZWFyDQotIHBvbHkoQW54aWV0eSwgMywgcmF3ID0gVFJVRSkyID0gcXVhZHJhdGljDQotIHBvbHkoQW54aWV0eSwgMywgcmF3ID0gVFJVRSkzID0gY3ViaWMuIA0KDQojIyBPcnRob2dvbmFsIFBvbHlub21pYWxzDQpQcm9ibGVtIHdpdGggUG93ZXIgcG9seW5vbWlhbHMgaXMgaW50ZXJwcmV0aW5nIHRoZW0gaW5kZXBlbmRlbnRseS4gVGhlIGhpZ2hlciBvcmRlciB0ZXJtcyBkZXBlbmQgb24sIHRoZSBsb3dlciBvcmRlciB0ZXJtcy4gVGh1cyBsaW5lYXIgdGVybSBjYW4gYmUgc2lnbmlmaWNhbnQgY2F1c2UgdGhlIGhpZ2hlciBvcmRlciB0ZXJtIGlzIHNpZ25pZmljYW50LCBidXQgdGhlcmUgaXMgbm8gbGluZWFyIHRyZW5kIGluIHRoZSBkYXRhIChqdXN0IGxpa2UgaW4gdG9kYXkncyBleGFtcGxlKS4gKipUaGlzIGlzIGJlY2F1c2UgcG93ZXIgcG9seW5vbWlhbHMgY29ycmVsYXRlIHdpdGggZWFjaCBvdGhlciAodGhleSBhcmUgYXJlIG5vdCB1bmlxdWUgcHJlZGljdG9ycykqKg0KDQpgYGB7cn0NCkxpbmVhci5WYWx1ZXM8LWMoMSwyLDMsNCw1LDYsNykNClF1YWQuVmFsdWVzPC1MaW5lYXIuVmFsdWVzXjINCg0KTC52cy5RPC1kYXRhLmZyYW1lKExpbmVhci5WYWx1ZXM9TGluZWFyLlZhbHVlcyxRdWFkLlZhbHVlcz1RdWFkLlZhbHVlcykNCmdnc2NhdHRlcihMLnZzLlEsIHggPSAiTGluZWFyLlZhbHVlcyIsIHkgPSAiUXVhZC5WYWx1ZXMiLA0KICAgYWRkID0gInJlZy5saW5lIiwgICMgQWRkIGxvZXNzDQogICBhZGQucGFyYW1zID0gbGlzdChjb2xvciA9ICJibHVlIiwgZmlsbCA9ICJsaWdodGJsdWUiKSwgIyBDdXN0b21pemUgcmVnLiBsaW5lDQogICBjb25mLmludCA9IFRSVUUsICMgQWRkIGNvbmZpZGVuY2UgaW50ZXJ2YWwNCiAgIGNvci5jb2VmID0gVFJVRSwgIyBBZGQgY29ycmVsYXRpb24gY29lZmZpY2llbnQuIHNlZSA/c3RhdF9jb3INCiAgIHNpemUgPSAyICMgU2l6ZSBvZiBkb3RzDQogICApDQpgYGANCg0KLSBUaHVzIHRoZXkgYXJlIGJhc2ljYWxseSBtdWx0aWNvbGxpbmVhcg0KLSBMZXQncyBydW4gb3VyIFZJRiB0byB2ZXJpZnkNCg0KYGBge3J9DQpsaWJyYXJ5KGNhcikNCnZpZihNb2RlbC4yKQ0KYGBgDQoNClRlY2huaWNhbGx5LCBpdCdzIE9LIHRoYXQgdGhleSBhcmUgbXVsdGljb2xsaW5lYXIsIGJ1dCB5b3UgY2Fubm90IGludGVycHJldCBlYWNoIHRlcm0gd2l0aG91dCBsb29raW5nIGF0IHRoZSBvdGhlciAodGhlIHAtdmFsdWVzIHdpbGwgYmUgcHJvYmxlbWF0aWMpLg0KDQotIFRoZSBzb2x1dGlvbiB0byBtYWtlIHRoZSBsaW5lYXIgYW5kIHF1YWRyYXRpYyB0ZXJtcyBjb3JyZWxhdGUgd2l0aCBlYWNoIGF0IHplcm8gKGp1c3QgYXMgd2UgZGlkIHdpdGggQU5PVkEpDQotIFdlIGNhbiB1c2UgdGhlIHBvbHkgY29kZSBpbiBSIHRvIG1ha2UgdGhlbSBPcnRob2dvbmFsLiANCi0gSGVyZSBpcyBhbiBleGFtcGxlIG9mIHdoYXQgUiBpcyBnb2luZyB0byBkbzogDQoNCmBgYHtyLCBlY2hvPVRSVUUsIHdhcm5pbmc9RkFMU0V9DQpMaW5lYXJUZXJtczwtYygxLDIsMyw0LDUsNiw3KQ0KI21ha2UgdGhlbSBPcnRob2dvbmFsDQpPLlBvbHk8LXBvbHkoTGluZWFyVGVybXMsMikNCg0KTC52cy5RLk9ydGhvPC1kYXRhLmZyYW1lKExpbmVhci5WYWx1ZXMuT3J0aG89Ty5Qb2x5WywxXSxRdWFkLlZhbHVlcy5PcnRobz1PLlBvbHlbLDJdKQ0KDQpnZ3NjYXR0ZXIoTC52cy5RLk9ydGhvLCB4ID0gIkxpbmVhci5WYWx1ZXMuT3J0aG8iLCB5ID0gIlF1YWQuVmFsdWVzLk9ydGhvIiwNCiAgIGFkZCA9ICJyZWcubGluZSIsICAjIEFkZCBsb2Vzcw0KICAgYWRkLnBhcmFtcyA9IGxpc3QoY29sb3IgPSAiYmx1ZSIsIGZpbGwgPSAibGlnaHRibHVlIiksICMgQ3VzdG9taXplIHJlZy4gbGluZQ0KICAgY29uZi5pbnQgPSBUUlVFLCAjIEFkZCBjb25maWRlbmNlIGludGVydmFsDQogICBjb3IuY29lZiA9IFRSVUUsICMgQWRkIGNvcnJlbGF0aW9uIGNvZWZmaWNpZW50LiBzZWUgP3N0YXRfY29yDQogICBzaXplID0gMiAjIFNpemUgb2YgZG90cw0KICAgKQ0KYGBgDQoNCkNvcnJlbGF0aW9uIGJldHdlZW4gbGluZWFyIGFuZCBxdWFkcmF0aWMgaXMgbm93LCByID0gMCEgDQoNCmBgYHtyLCBlY2hvPVRSVUUsIHdhcm5pbmc9RkFMU0V9DQojbGV0cyBtYWtlIGEgbmV3IHBvbHkgdmVjdG9yDQpNb2RlbC4xLk88LWxtKEV4YW1TY29yZX5wb2x5KEFueGlldHksIDEpLCBkYXRhPSBRdWFkLkRhdGEpDQpNb2RlbC4yLk88LWxtKEV4YW1TY29yZX5wb2x5KEFueGlldHksIDIpLCBkYXRhPSBRdWFkLkRhdGEpDQoNCmFub3ZhKE1vZGVsLjEuTyxNb2RlbC4yLk8pDQpgYGANCg0KXHBhZ2VicmVhaw0KDQpgYGB7ciwgZWNobz1GQUxTRSwgcmVzdWx0cz0nYXNpcyd9DQpzdGFyZ2F6ZXIoTW9kZWwuMS5PLE1vZGVsLjIuTyx0eXBlPSJodG1sIiwNCiAgICAgICAgICBjb2x1bW4ubGFiZWxzID0gYygiTGluZWFyLk9ydGhvIiwgIlF1YWRyYXRpYy5PcnRobyIpLA0KICAgICAgICAgIGludGVyY2VwdC5ib3R0b20gPSBGQUxTRSwgc2luZ2xlLnJvdz1UUlVFLCANCiAgICAgICAgICBub3Rlcy5hcHBlbmQgPSBGQUxTRSwgICAgaGVhZGVyPUZBTFNFKQ0KYGBgDQoNCi0gTm90aWNlIHRoYXQgdGhlIGxpbmVhciB0ZXJtIGlzIG5vdCBzaWduaWZpY2FudCwgYnV0IHRoZSBxdWFkcmF0aWMgaXMNCiAgICAtIGJ1dCB0aGUgYWN0dWFsIGVzdGltYXRlIG9mIHRoZSBzbG9wZXMgYXJlIG5vbnNlbnNpY2FsDQotIFRoaXMgaXMgYmVjYXVzZSB5b3UgaGF2ZSAqKnJlc2NhbGVkKiogdGhlIHgtdmFsdWVzIGluIGEgZnVubnkgd2F5IChzZWUgcGxvdCBhYm92ZSkNCiAgICAtIEJ1dCBubyBtb3JlIG11bHRpY29sbGluZWFyaXR5DQotIExldCdzIHJ1biBvdXIgVklGIHRvIHZlcmlmeQ0KICAgIC0gTm90ZTogcG9seSBnZXRzIHJlYWQgdG8gdmlmIGFzIG9uZSBmYWN0b3IsIHNvIEkgd2lsbCBtYW51YWxseSBlbnRlciB0aGVzZSBpbnRvIHRoZSBtb2RlbA0KDQpgYGB7cn0NClF1YWQuRGF0YSRBbnhpZXR5Lk8uMTwtcG9seShRdWFkLkRhdGEkQW54aWV0eSwyKVssMV0NClF1YWQuRGF0YSRBbnhpZXR5Lk8uMjwtcG9seShRdWFkLkRhdGEkQW54aWV0eSwyKVssMl0NCk1vZGVsLjIuTy4yPC1sbShFeGFtU2NvcmV+QW54aWV0eS5PLjErQW54aWV0eS5PLjIsIGRhdGE9IFF1YWQuRGF0YSkNCnZpZihNb2RlbC4yLk8uMikNCmBgYA0KDQotIEFsc28gdGhlICRSXjIkIGRvIG5vdCBkaWZmZXIgYmV0d2VlbiB0aGUgcG93ZXIgb3Igb3J0aG9nb25hbCBwb2x5bm9taWFscw0KLSBPdGhlciBwcm9zIG9mIHVzaW5nIG9ydGhvZ29uYWwgcG9seW5vbWlhbHMgd2lsbCBjb21lIHdoZW4gd2Ugc3RhcnQgbG9va2luZyBhdCBpbnRlcmFjdGlvbnMNCg0KIyMjIENvZGluZyBzaG9ydGN1dCAqKFVzZSB0aGlzIGZvciB5b3VyIGFuYWx5c2lzKSoNCi0gWW91IGRvbid0IG5lZWQgdG8gcHJlbWFrZSB0aGUgdmVjdG9yIGFzIEkgZGlkIGFib3ZlDQotIFlvdSBjYW4gcHV0IHRoZSBwb2x5IGNvZGUgcmlnaHQgaW50byB0aGUgbW9kZWwNCiAgICAtIGJ1dCB0aGUgbGFiZWxpbmcgd2lsbCBjaGFuZ2UNCi0gQWxzbyBtdWNoIGVhc2llciB0byBtYWtlcyBwbG90cyBvZiB0aGUgZmluYWwgbW9kZWwgdGhpcyB3YXkNCg0KYGBge3J9DQpNb2RlbC4yLk8uUzwtbG0oRXhhbVNjb3JlfnBvbHkoQW54aWV0eSwyKSwgZGF0YT0gUXVhZC5EYXRhKQ0KYGBgDQoNCmBgYHtyLCBlY2hvPUZBTFNFLCByZXN1bHRzPSdhc2lzJ30NCnN0YXJnYXplcihNb2RlbC4yLk8uUyx0eXBlPSJodG1sIiwNCiAgICAgICAgICBjb2x1bW4ubGFiZWxzID0gYygiUXVhZHJhdGljLk9ydGhvIiksDQogICAgICAgICAgaW50ZXJjZXB0LmJvdHRvbSA9IEZBTFNFLA0KICAgICAgICAgIHNpbmdsZS5yb3c9VFJVRSwgDQogICAgICAgICAgbm90ZXMuYXBwZW5kID0gRkFMU0UsICAgICAgICAgDQogICAgICAgICAgaGVhZGVyPUZBTFNFKQ0KYGBgDQoNClRoZSBlZmZlY3RzIHBhY2thZ2Ugd2lsbCAia25vdyIgeW91IHVzZWQgYHBvbHlgIGNvbW1hbmQgYW5kIGNvbnZlcnQgdGhlIHJlc3VsdHMgYmFjayBmb3IgeW91IGF1dG9tYXRpY2FsbHkNCg0KYGBge3J9DQpwbG90KGVmZmVjdCgicG9seShBbnhpZXR5LDIpIiwgTW9kZWwuMi5PLlMsDQogICAgICAgICAgIHhsZXZlbHM9bGlzdChBbnhpZXR5PXNlcSgwLDcsMSkpKSkNCmBgYA0KDQotIEFsc28sIHlvdSBjb3VsZCBoYXZlIHNhaWQgcG9seShBbnhpZXR5LDIscmF3PVRSVUUpIHRvIGdldCBQb3dlciBwb2x5bm9taWFsIG9uIHRoZSBmbHkNCg0KIyMgRGlhZ25vc3RpY3MNCg0KLSBXaGVuIHlvdSBoYXZlIHRoZSAqKndyb25nKiogbnVtYmVyIG9mIHBvbHlub21pYWxzIHlvdSB3aWxsIGdldCBmdW5ueSBsb29raW5nIHJlc2lkdWFscw0KDQpgYGB7ciwgZmlnLndpZHRoPTZ9DQpsaWJyYXJ5KGdnZm9ydGlmeSkNCmF1dG9wbG90KE1vZGVsLjEsIHdoaWNoID0gMToyLCBsYWJlbC5zaXplID0gMSkgKyB0aGVtZV9idygpDQpgYGANCg0KDQotIFdoZW4geW91IGhhdmUgdGhlICoqcmlnaHQqKiBudW1iZXIgb2YgcG9seW5vbWlhbHMsIHlvdSB3aWxsIGdldCBjb3JyZWN0IGxvb2tpbmcgcmVzaWR1YWxzDQotIEhlcmUgaXMgd2hlbiB3ZSB1c2UgMiB3aGVuIHdlIG5lZWQgMg0KDQpgYGB7ciwgZmlnLndpZHRoPTZ9DQphdXRvcGxvdChNb2RlbC4yLk8uUywgd2hpY2ggPSAxOjIsIGxhYmVsLnNpemUgPSAxKSArIHRoZW1lX2J3KCkNCmBgYA0KDQpccGFnZWJyZWFrDQoNCiMgRGVhbGluZyBHcm93dGggQ3VydmVzIChQYXJ0IDIpDQotIFRoZXNlIGFyZSBtb2RlbHMgdGhhdCBjYW5ub3QgYmUgZml0IHdpdGggcG9seW5vbWlhbHMNCi0gRXhwb25lbnRpYWwgZ3Jvd3RoOiAkWSA9IGNlXntkeH0kDQotIFNldmVyaXR5IG9mIHBhbmljIGF0dGFjayAoMCB0byA1MDApIHByZWRpY3RlZCBieSBhbnhpZXR5IGxldmVsDQotIEV4cG9uZW50aWFsIEV4YW1wbGU6ICRZID0gLjA5ZV57MS4yeCtcZXBzaWxvbn0kDQotIE5vdGU6IEV1bGVyJ3MgbnVtYmVyLCAkZSA9JCBgciAgZXhwKDEpYCwgaXMgYW4gaXJyYXRpb25hbCBtYXRoIGNvbnN0YW50IGFuZCB0aGUgYmFzZSB2YWx1ZSBvZiB0aGUgbmF0dXJhbCBsb2cNCg0KYGBge3J9DQpzZXQuc2VlZCg0MikNCm4gPC0gMjUwDQp4IDwtIHJ1bmlmKG4sIDAsIDcpDQojbGV0cyBtYWtlIG91ciBlIHZhbHVlIChub3QgdGhlIGVycm9yIHRlcm0pDQplPC1leHAoMSkNCnkgPC0uMDkqZV4oMS4yMCooeCtybm9ybShuLCBzZD0uMjUpKSkNCkV4cC5EYXRhPC1kYXRhLmZyYW1lKEFueGlldHk9eCxQYW5pY0F0dGFjaz15KQ0KDQpnZ3NjYXR0ZXIoRXhwLkRhdGEsIHggPSAiQW54aWV0eSIsIHkgPSAiUGFuaWNBdHRhY2siLA0KICAgYWRkID0gImxvZXNzIiwgICMgQWRkIGxvZXNzDQogICBhZGQucGFyYW1zID0gbGlzdChjb2xvciA9ICJibHVlIiwgZmlsbCA9ICJsaWdodGJsdWUiKSwgIyBDdXN0b21pemUgcmVnLiBsaW5lDQogICBjb25mLmludCA9IFRSVUUsICMgQWRkIGNvbmZpZGVuY2UgaW50ZXJ2YWwNCiAgIGNvci5jb2VmID0gRkFMU0UsICMgQWRkIGNvcnJlbGF0aW9uIGNvZWZmaWNpZW50LiBzZWUgP3N0YXRfY29yDQogICBzaXplID0gMSAjIFNpemUgb2YgZG90cw0KICAgKQ0KYGBgDQoNCiMjIERvIFBvbHlub21pYWxzIGZhaWwgdXM/DQotIExldCdzIHRyeSBhIHNlY29uZCBvcmRlciBmaXQgc2luY2UgaXQgb25lIGN1cnZlDQpgYGB7cn0NCk1vZGVsLlBhbmljLlBvbHk8LWxtKFBhbmljQXR0YWNrfnBvbHkoQW54aWV0eSwyKSwgZGF0YT0gRXhwLkRhdGEpDQpgYGANCg0KYGBge3IsIGVjaG89RkFMU0UsIHJlc3VsdHM9J2FzaXMnfQ0Kc3RhcmdhemVyKE1vZGVsLlBhbmljLlBvbHksdHlwZT0iaHRtbCIsDQogICAgICAgICAgY29sdW1uLmxhYmVscyA9IGMoIlF1YWRyYXRpYy5PcnRobyIpLA0KICAgICAgICAgIGludGVyY2VwdC5ib3R0b20gPSBGQUxTRSwgc2luZ2xlLnJvdz1UUlVFLCANCiAgICAgICAgICBub3Rlcy5hcHBlbmQgPSBGQUxTRSwgaGVhZGVyPUZBTFNFKQ0KYGBgDQoNCi0gVXNpbmcgdGhlIHBvbHkgcmlnaHQgaW50byB0aGUgcGFja2FnZSBjYW4gYmUgdXNlZnVsIGZvciB0aGUgZWZmZWN0cyBwYWNrYWdlIGJlY2F1c2Ugd2UgY2FuIG5vdyB2aWV3IGluZGVwZW5kZW50IGltcGFjdCBvZiB0aGUgbGluZWFyIGFuZCBxdWFkcmF0aWMgdGVybXMNCg0KYGBge3J9DQpwbG90KGVmZmVjdCgicG9seShBbnhpZXR5LDIpIiwgTW9kZWwuUGFuaWMuUG9seSwNCiAgICAgICAgICAgeGxldmVscz1saXN0KEFueGlldHk9c2VxKDAsNywxKSkpKQ0KYGBgDQotIFlpa2VzLCBiYXNlZCBvbiB0aGlzIHlvdSBtaWdodCB0ZWxsIHBlb3BsZSBoYXZpbmcgemVybyBhbnhpZXR5IGlzIHdvcnNlIHRoYW4gaGF2aW5nIGxvd2VyIGFueGlldHkNCg0KLSBMZXQgY2hlY2sgdGhlIHJlc2lkdWFscw0KYGBge3IsIGZpZy53aWR0aD02fQ0KYXV0b3Bsb3QoTW9kZWwuUGFuaWMuUG9seSwgd2hpY2ggPSAxOjIsIGxhYmVsLnNpemUgPSAxKSArIHRoZW1lX2J3KCkNCmBgYA0KDQotIEFsc28gbG9va3MgcHJldHR5IG9kZA0KDQojIyBUcmFuc2Zvcm1hdGlvbnMgb2YgRXhwb25lbnRpYWwgTW9kZWwNCi0gVGhlIHNvbHV0aW9uIHRvIHRoaXMgcHJvYmxlbSBpcyBvZnRlbiB0byB0cmFuc2Zvcm0gdGhlIG9mZmVuZGluZyB2YXJpYWJsZQ0KLSBUaGUgdHlwZSBvZiB0cmFuc2Zvcm0gZGVwZW5kcyBvbiB0aGUgdHlwZSBvZiBncm93dGggbW9kZWwgeW91IGhhdmUNCi0gRXhwb25lbnRpYWwgbW9kZWw6IERlcGVuZGVudCB2YXJpYWJsZSA9IGxvZyh5KSBbaW4gciwgbG9nID0gbmF0dXJhbCBsb2ddDQotIFRodXM6ICRsb2coWSkgPSBCXzAgKyBCWCQNCi0gQW5kIHRvIGNvbnZlcnQgZnJvbSBsb2cgaW50byBtZWFuaW5nZnVsIHZhbHVlczoNCi0gJFkgPSBlXntCXzAgKyBCWH0kDQotIExldCdzIGxvb2sgYXQgTG9nKFkpIGJ5IEFueGlldHkgW3dlIHdpbGwgdXNlIHRoZSBzY2F0dGVycGxvdCBmdW5jdGlvbiBpbiBgY2FyYCBwYWNrYWdlIHRvIHNlZSB0aGlzIG1vcmUgcXVpY2tseV0NCg0KYGBge3J9DQpsaWJyYXJ5KGNhcikNCnNjYXR0ZXJwbG90KGxvZyhQYW5pY0F0dGFjayl+QW54aWV0eSwgZGF0YT0gRXhwLkRhdGEpDQpgYGANCg0KLSBMb29rcyBsaWtlIGEgbGluZSBub3cNCi0gTGV0J3MgbG9vayBhdCB0aGUgbW9kZWwgZml0DQoNCmBgYHtyfQ0KTW9kZWwuUGFuaWMuTG9nPC1sbShsb2coUGFuaWNBdHRhY2spfkFueGlldHksIGRhdGE9IEV4cC5EYXRhKQ0KYGBgDQoNCmBgYHtyLCBlY2hvPUZBTFNFLCByZXN1bHRzPSdhc2lzJ30NCk1vZGVsLlBhbmljLkxvZzwtbG0obG9nKFBhbmljQXR0YWNrKX5BbnhpZXR5LCBkYXRhPSBFeHAuRGF0YSkNCnN0YXJnYXplcihNb2RlbC5QYW5pYy5Mb2csdHlwZT0iaHRtbCIsDQogICAgICAgICAgaW50ZXJjZXB0LmJvdHRvbSA9IEZBTFNFLCBzaW5nbGUucm93PVRSVUUsIA0KICAgICAgICAgIG5vdGVzLmFwcGVuZCA9IEZBTFNFLCBoZWFkZXI9RkFMU0UpDQpgYGANCg0KUGxvdCB0aGUgZml0DQoNCmBgYHtyfQ0KcGxvdChlZmZlY3QoIkFueGlldHkiLCBNb2RlbC5QYW5pYy5Mb2csDQogICAgICAgICAgIHhsZXZlbHM9bGlzdChBbnhpZXR5PXNlcSgwLDcsMSkpKSkNCmBgYA0KDQpZb3UgY2FuIHBsb3QgaXQgbm9uLWxvZyB1bml0cyAodG8gdW5sb2cgdXNlIGV4cCBmdW5jdGlvbiksIGJ1dCB5b3UgaGF2ZSB0byB0ZWxsIHRoZSBwYWNrYWdlIHRvIGNvbnZlcnQgaXQgYmFjayB0byByYXcgc2NvcmUuDQoNCmBgYHtyLCBlY2hvPVRSVUUsbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCnBsb3QoZWZmZWN0KCJBbnhpZXR5IiwgTW9kZWwuUGFuaWMuTG9nLA0KICAgICAgICAgIHRyYW5zZm9ybWF0aW9uPWxpc3QobGluaz1sb2csIGludmVyc2U9ZXhwKSkpDQpgYGANCg0KUGxvdCB0aGUgZml0IGFuZCBjaGVjayB0aGUgcmVzaWR1YWxzDQoNCmBgYHtyLCBmaWcud2lkdGg9Nn0NCmF1dG9wbG90KE1vZGVsLlBhbmljLkxvZywgd2hpY2ggPSAxOjIsIGxhYmVsLnNpemUgPSAxKSArIHRoZW1lX2J3KCkNCmBgYA0KDQpUaG9zZSBsb29rIGFzIHRoZXkgc2hvdWxkLiANCg0KIyMjIENoYWxsZW5nZSBvZiBXb3JraW5nIHdpdGggVHJhbnNmb3Jtcw0KDQotIFByb2JsZW0gaXMgb3VyIHNjb3JlcyBhcmUgaW4gbG9nIChub3QgcmF3IHNjb3JlKQ0KLSB0byBjb252ZXJ0IGZyb20gbG9nIGludG8gbWVhbmluZ2Z1bCB2YWx1ZXM6DQotICRZID0gZV57Ql8wICsgQlh9JA0KDQpgYGB7ciwgZWNobz1UUlVFfQ0KI0ludGVyY2VwdA0KSVQuTDwtcm91bmQoTW9kZWwuUGFuaWMuTG9nJGNvZWZmaWNpZW50c1sxXSwyKQ0KI3Nsb3BlDQpMVC5MPC1yb3VuZChNb2RlbC5QYW5pYy5Mb2ckY29lZmZpY2llbnRzWzJdLDIpDQpgYGANCg0KLSAkWSA9IGUkXihgciBJVC5MYCArIGByIExULkxgWCkNCi0gRm9yIEFueGlldHkgc2NvcmUgb2YgWCA9IDAsIFkgPSBgciBleHAoMSleKElULkwgKyBMVC5MKjApYA0KLSBGb3IgQW54aWV0eSBzY29yZSBvZiBYID0gNCwgWSA9IGByIGV4cCgxKV4oSVQuTCArIExULkwqNClgDQotIEZvciBBbnhpZXR5IHNjb3JlIG9mIFggPSA3LCBZID0gYHIgZXhwKDEpXihJVC5MICsgTFQuTCo3KWANCi0gW05vdGU6IEJlIGNhcmVmdWwgYWJvdXQgd2hpY2ggbG9nIHlvdSB1c2UgKG5hdHVyYWwgb3IgYmFzZSAxMCwgeW91IGNhbiB1c2UgZWl0aGVyIGJ1dCBtYWtlIHN1cmUgdG8gc3BlY2lmeSldDQoNCiMjIFBvd2VyIGxhdw0KLSBDb21tb24gZnVuY3Rpb24gaW4gbW90b3IgY29udHJvbCBhbmQgcGVyY2VwdGlvbiBleHBlcmltZW50cw0KLSAkWSA9IGNYXmQkDQotIFNldmVyaXR5IG9mIHN3ZWF0aW5nICgwIHRvIDEwMCkgcHJlZGljdGVkIGJ5IGFueGlldHkgbGV2ZWwNCi0gRXhwb25lbnRpYWwgRXhhbXBsZTogJFkgPSAuMDcoWCleezEuN30rXGVwc2lsb24kDQotIE5vdGU6IE5hdHVyYWwgbG9nIG9mIDAgPSAgLWluZiwgc28gaWYgeW91IGhhdmUgemVyb3MgeW91IG5lZWQgdG8gYWx3YXlzIGFkZCAxDQoNCmBgYHtyLCBlY2hvPVRSVUUsIHdhcm5pbmc9RkFMU0V9DQpzZXQuc2VlZCg0MikNCm4gPC0gMjUwDQp4IDwtIHJ1bmlmKG4sIDAsIDcpKzENCnkgPC0uMDcqKHgpXjEuNw0KeSA9IHkgKiAoLjA1KiAoNStybm9ybShuLCBzZD0uMjUpKSkNClBvd2VyLkRhdGE8LWRhdGEuZnJhbWUoQW54aWV0eT14LFN3ZWF0aW5nPXkpDQoNCmdnc2NhdHRlcihQb3dlci5EYXRhLCB4ID0gIkFueGlldHkiLCB5ID0gIlN3ZWF0aW5nIiwNCiAgIGFkZCA9ICJsb2VzcyIsICAjIEFkZCBsb2Vzcw0KICAgYWRkLnBhcmFtcyA9IGxpc3QoY29sb3IgPSAiYmx1ZSIsIGZpbGwgPSAibGlnaHRibHVlIiksICMgQ3VzdG9taXplIHJlZy4gbGluZQ0KICAgY29uZi5pbnQgPSBUUlVFLCAjIEFkZCBjb25maWRlbmNlIGludGVydmFsDQogICBjb3IuY29lZiA9IEZBTFNFLCAjIEFkZCBjb3JyZWxhdGlvbiBjb2VmZmljaWVudC4gc2VlID9zdGF0X2Nvcg0KICAgc2l6ZSA9IDEgIyBTaXplIG9mIGRvdHMNCiAgICkNCmBgYA0KDQojIyMgVHJhbnNmb3JtYXRpb25zIG9mIFBvd2VyIExhdw0KLSBUaGUgc29sdXRpb24gdG8gdGhpcyBwcm9ibGVtIGlzIG9mdGVuIHRvIHRyYW5zZm9ybSB0aGUgb2ZmZW5kaW5nIHZhcmlhYmxlDQotIFBvd2VyIGxhdyBtb2RlbDogRFYgYW5kIElWIGdldCBsb2dnZWQ6IGxvZyh5KSAmIGxvZyh4KSANCi0gVGh1czogJGxvZyhZKSA9IEJfMCArIEIobG9nWCkkDQotIEFuZCB0byBjb252ZXJ0IGZyb20gbG9nIGludG8gbWVhbmluZ2Z1bCB2YWx1ZXM6DQotICRZID0gZV57Ql8wICsgQihsb2dYKX0kDQotIExldHMgbG9vayBhdCB0aGUgbG9nLWxvZyBzY2F0dGVycGxvdA0KDQpgYGB7ciwgZWNobz1UUlVFLCB3YXJuaW5nPUZBTFNFfQ0Kc2NhdHRlcnBsb3QobG9nKFN3ZWF0aW5nKX5sb2coQW54aWV0eSksIGRhdGE9IFBvd2VyLkRhdGEpDQpgYGANCg0KLSBMb29rcyBsaWtlIGEgbGluZSBub3chDQotIExldCdzIGxvb2sgYXQgdGhlIG1vZGVsIGZpdA0KDQpgYGB7cn0NCk1vZGVsLlN3ZWF0LkxMPC1sbShsb2coU3dlYXRpbmcpfmxvZyhBbnhpZXR5KSwgZGF0YT0gUG93ZXIuRGF0YSkNCmBgYA0KDQpgYGB7ciwgZWNobz1GQUxTRSxyZXN1bHRzPSdhc2lzJ30NCnN0YXJnYXplcihNb2RlbC5Td2VhdC5MTCx0eXBlPSJodG1sIiwNCiAgICAgICAgICBjb2x1bW4ubGFiZWxzID0gYygiTG9nLUxvZyBUcmFuc2Zvcm0iKSwNCiAgICAgICAgICBpbnRlcmNlcHQuYm90dG9tID0gRkFMU0UsIHNpbmdsZS5yb3c9VFJVRSwgbm90ZXMuYXBwZW5kID0gRkFMU0UsIA0KICAgICAgICAgIGhlYWRlcj1GQUxTRSkNCmBgYA0KDQpQbG90IHRoZSBmaXQgYW5kIHRlbGwgdGhlIHBhY2thZ2UgdGhhdCB5b3UgZGlkIGEgdHJhbnNmb3JtDQoNCmBgYHtyLCBlY2hvPVRSVUUsbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCnBsb3QoZWZmZWN0KCJBbnhpZXR5IiwgTW9kZWwuU3dlYXQuTEwsDQogICAgICAgICAgIHRyYW5zZm9ybWF0aW9uPWxpc3QobGluaz1sb2csIGludmVyc2U9ZXhwKSkpDQpgYGANCg0KQ2hlY2sgdGhlIHJlc2lkdWFscw0KDQpgYGB7ciwgZmlnLndpZHRoPTZ9DQphdXRvcGxvdChNb2RlbC5Td2VhdC5MTCwgd2hpY2ggPSAxOjIsIGxhYmVsLnNpemUgPSAxKSArIHRoZW1lX2J3KCkNCmBgYA0KDQpUaGV5IGxvb2sgT0suIA0KDQojIyBMb2dhcml0aG1pYyANCi0gVGhlc2UgZnVuY3Rpb25zIGFyZSBvZnRlbiBwaHlzaWNhbCAoc291bmQgaW50ZW5zaXR5LCBlYXJ0aHF1YWtlcywgZXRjLikNCi0gJGNeWSA9IGRYJA0KLSBCcmVhdGhpbmcgcmF0ZSAoaW4gbXMgYmV0d2VlbiBicmVhdGhzKSBwcmVkaWN0ZWQgYnkgYW54aWV0eSBsZXZlbA0KLSBMb2dhcml0aG1pYyBFeGFtcGxlOiAkWSA9IC4wNShYKlxlcHNpbG9uKV57My44fSQNCg0KYGBge3J9DQpzZXQuc2VlZCg0MikNCm4gPC0gMjUwDQp4IDwtIHJ1bmlmKG4sIDAsIDcpKzENCnkgPC0yMDAqbG9nKHgqKDUrcm5vcm0obiwgc2Q9MSkpKQ0KTG9nYXJpdGhtaWMuRGF0YTwtZGF0YS5mcmFtZShBbnhpZXR5PXgsQnJlYXRoaW5nPXkpDQoNCmdnc2NhdHRlcihMb2dhcml0aG1pYy5EYXRhLCB4ID0gIkFueGlldHkiLCB5ID0gIkJyZWF0aGluZyIsDQogICBhZGQgPSAibG9lc3MiLCAgIyBBZGQgbG9lc3MNCiAgIGFkZC5wYXJhbXMgPSBsaXN0KGNvbG9yID0gImJsdWUiLCBmaWxsID0gImxpZ2h0Ymx1ZSIpLCAjIEN1c3RvbWl6ZSByZWcuIGxpbmUNCiAgIGNvbmYuaW50ID0gVFJVRSwgIyBBZGQgY29uZmlkZW5jZSBpbnRlcnZhbA0KICAgY29yLmNvZWYgPSBGQUxTRSwgIyBBZGQgY29ycmVsYXRpb24gY29lZmZpY2llbnQuIHNlZSA/c3RhdF9jb3INCiAgIHNpemUgPSAxICMgU2l6ZSBvZiBkb3RzDQogICApDQpgYGANCg0KLSBQbG90IEJyZWF0aGluZyBieSBsb2coQW54aWV0eSkNCmBgYHtyfQ0Kc2NhdHRlcnBsb3QoQnJlYXRoaW5nfmxvZyhBbnhpZXR5KSwgZGF0YT0gTG9nYXJpdGhtaWMuRGF0YSkNCmBgYA0KDQotIExvb2tzIGxpa2UgYSBsaW5lIG5vdw0KLSBMZXQncyBsb29rIGF0IHRoZSBtb2RlbCBmaXQNCg0KYGBge3J9DQpNb2RlbC5CcmVhdGguTG9nPC1sbShCcmVhdGhpbmd+bG9nKEFueGlldHkpLCBkYXRhPSBMb2dhcml0aG1pYy5EYXRhKQ0KYGBgDQpgYGB7ciwgZWNobz1GQUxTRSwgcmVzdWx0cz0nYXNpcyd9DQpzdGFyZ2F6ZXIoTW9kZWwuQnJlYXRoLkxvZyx0eXBlPSJodG1sIiwNCiAgICAgICAgICBjb2x1bW4ubGFiZWxzID0gYygiTG9nIFRyYW5zZm9ybSIpLA0KICAgICAgICAgIGludGVyY2VwdC5ib3R0b20gPSBGQUxTRSwNCiAgICAgICAgICBzaW5nbGUucm93PVRSVUUsIA0KICAgICAgICAgIG5vdGVzLmFwcGVuZCA9IEZBTFNFLCAgICAgICAgICAgDQogICAgICAgICAgaGVhZGVyPUZBTFNFKQ0KYGBgDQoNCi0gTGV0J3MgcGxvdCB0aGUgZml0IA0KYGBge3J9DQpwbG90KGVmZmVjdCgiQW54aWV0eSIsIE1vZGVsLkJyZWF0aC5Mb2cpKQ0KYGBgDQoNCi0gTGV0J3MgY2hlY2sgdGhlIHJlc2lkdWFscw0KDQpgYGB7ciwgZmlnLndpZHRoPTZ9DQphdXRvcGxvdChNb2RlbC5CcmVhdGguTG9nLCB3aGljaCA9IDE6MiwgbGFiZWwuc2l6ZSA9IDEpICsgdGhlbWVfYncoKQ0KYGBgDQoNCiMjIFJlY2lwcm9jYWxzDQotIENvbW1vbiBmaXggZm9yIHJhdGUgZGF0YSAodGhpbmdzIGludm9sdmluZyB0aW1lKQ0KLSAkMS9ZJA0KLSBBZHJlbmFsaW5lIGxldmVsIGluIHRoZSBibG9vZCBwcmVkaWN0ZWQgYnkgbWlucyBhZnRlciBhIHBhbmljIGF0dGFjayAobWlucykNCi0gUmVjaXByb2NhbCBFeGFtcGxlOiAkWSA9ICgxL1gpKlxlcHNpbG9uJA0KDQpgYGB7cn0NCnNldC5zZWVkKDQyKQ0KbiA8LSAyNTANCiMgTGVuZ3RoIG9mIHBhbmljIGF0dGFjaw0KeCA8LSAxNSoocnVuaWYobiwgMSwgNCkpDQp5IDwtIDEvKHgpKnJub3JtKG4sNSxzZD0uMSkNClJlY2lwcm9jYWwuRGF0YTwtZGF0YS5mcmFtZShQb3N0UGFuaWM9eCxBZHJlbmFsaW5lPXkpDQoNCmdnc2NhdHRlcihSZWNpcHJvY2FsLkRhdGEsIHggPSAiUG9zdFBhbmljIiwgeSA9ICJBZHJlbmFsaW5lIiwNCiAgIGFkZCA9ICJsb2VzcyIsICAjIEFkZCBsb2Vzcw0KICAgYWRkLnBhcmFtcyA9IGxpc3QoY29sb3IgPSAiYmx1ZSIsIGZpbGwgPSAibGlnaHRibHVlIiksICMgQ3VzdG9taXplIHJlZy4gbGluZQ0KICAgY29uZi5pbnQgPSBUUlVFLCAjIEFkZCBjb25maWRlbmNlIGludGVydmFsDQogICBjb3IuY29lZiA9IEZBTFNFLCAjIEFkZCBjb3JyZWxhdGlvbiBjb2VmZmljaWVudC4gc2VlID9zdGF0X2Nvcg0KICAgc2l6ZSA9IDEgIyBTaXplIG9mIGRvdHMNCiAgICkNCmBgYA0KDQpXZSBjYW4gdGFrZSB0aGUgUmVjaXByb2NhbCBvZiBBZHJlbmFsaW5lIG9uIHRoZSBmbHkgYW5kIHJlcGxvdA0KDQpgYGB7cn0NCnNjYXR0ZXJwbG90KDEvQWRyZW5hbGluZX5Qb3N0UGFuaWMsIGRhdGE9IFJlY2lwcm9jYWwuRGF0YSkNCmBgYA0KDQotIExvb2tzIGxpa2UgYSBsaW5lIG5vdw0KLSBMZXQncyBsb29rIGF0IHRoZSBtb2RlbCBmaXQNCg0KYGBge3J9DQpNb2RlbC5DYWxtLlJlcDwtbG0oMS9BZHJlbmFsaW5lflBvc3RQYW5pYywgZGF0YT0gUmVjaXByb2NhbC5EYXRhKQ0KYGBgDQoNCmBgYHtyLCBlY2hvPUZBTFNFLHJlc3VsdHM9J2FzaXMnfQ0Kc3RhcmdhemVyKE1vZGVsLkNhbG0uUmVwLHR5cGU9Imh0bWwiLA0KICAgICAgICAgIGludGVyY2VwdC5ib3R0b20gPSBGQUxTRSwNCiAgICAgICAgICBzaW5nbGUucm93PVRSVUUsIA0KICAgICAgICAgIG5vdGVzLmFwcGVuZCA9IEZBTFNFLCAgICAgICAgICANCiAgICAgICAgICBoZWFkZXI9RkFMU0UpDQpgYGANCg0KUGxvdCB0aGUgZml0IA0KYGBge3IsIGVjaG89VFJVRSxtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KcGxvdChlZmZlY3QoIlBvc3RQYW5pYyIsIE1vZGVsLkNhbG0uUmVwKSkNCmBgYA0KDQpDaGVjayB0aGUgcmVzaWR1YWxzDQpgYGB7ciwgZmlnLndpZHRoPTZ9DQphdXRvcGxvdChNb2RlbC5DYWxtLlJlcCwgd2hpY2ggPSAxOjIsIGxhYmVsLnNpemUgPSAxKSArIHRoZW1lX2J3KCkNCmBgYA0KDQpBIGxpdHRsZSBvZmYgKGJ1dCBJIGFkZCBpbiBub2lzZSBpbiBhIHNpbXBsZSB3YXkpDQoNCiMjIFdlaXJkIEJ1bGdlcyBpbiB0aGUgZGF0YSAobm90IGZ1bGwgb24gY3VydmVzKQ0KLSBEYXRhIGRvIG5vdCBhbHdheXMgZml0IG91ciBuaWNlIHByZWRlZmluZWQgZnVuY3Rpb25zDQotIFNvbWV0aW1lcyB0aGV5IGNsZWFybHkgaGF2ZSAqKmJ1bGdlcyoqLCBidXQgYXJlIHdlIHN1cmUgdGhvc2UgYXJlIHJlYWw/DQotIEJ1bGdlcyBjYW4gYmUgbm90IHJlYWwgaW4gc21hbGwgc2FtcGxlcyBvciBjYW4gcmVzdWx0IGZyb20gc29tZSBsYXRlbnQgZmFjdG9yIHlvdSBhcmUgbm90IGFjY291bnRpbmcgZm9yDQotIExldCdzIGFzc3VtZSB0aGF0IGlmIHRoZSBkYXRhIGhhcyBhIGJ1bGdlIHRoYXQgaXMgcmVhbCwgbWVhbmluZ2Z1bCwgYW5kIHdlIG5lZWQgdG8gYWNjb3VudCBmb3IgaXQNCi0gV2UgY2FuIHVzZSB0aGUgQm94LUNveCBUcmFuc2Zvcm0gKGZpdHMgZGlmZmVyZW50IHBvbHlub21pYWxzIHdoaWNoIHdlIGNhbGwgJFxsYW1iZGEkKQ0KLSAkWSA9IFleXGxhbWJkYSAtIDEgLyBcbGFtYmRhJCwgd2hlcmUgJFxsYW1iZGEgXG5lcSAwJCBPUiAkWSA9IGxuWSQsIHdoZXJlICRcbGFtYmRhID0gMCQNCi0gSW4gdGhpcyBjYXNlLCB3ZSB3aWxsIGFsbG93ICRcbGFtYmRhJCB0byBiZSBzZWxlY3RlZCBiYXNlZCBvbiB3aGljaCBtYWtlcyB0aGUgZGF0YSBtb3N0IG5vcm1hbA0KLSBMZXQncyBtYWtlIGEgY2FzZSB3aXRoIGEgd2VpcmQgcG93ZXIgcG9seW5vbWlhbCBvZiAxLjMgKHNvbWV0aGluZyB5b3Ugd291bGQgbm90IG5vdGljZSBpbiB5b3VyIHJhdyBkYXRhKQ0KDQpgYGB7cn0NCnNldC5zZWVkKDQyKQ0KIyAyNTAgcGVvcGxlDQpuIDwtIDI1MA0KeCA8LSBydW5pZihuLCAwLCA3KQ0KeSA8LSAtNCp4XjEuMysgMjAqeCtybm9ybShuLCBzZD0yKSsyMA0KU29ydGFRdWFkLkRhdGE8LWRhdGEuZnJhbWUoQW54aWV0eT14LEV4YW1TY29yZT15KQ0KDQpnZ3NjYXR0ZXIoU29ydGFRdWFkLkRhdGEsIHggPSAiQW54aWV0eSIsIHkgPSAiRXhhbVNjb3JlIiwNCiAgIGFkZCA9ICJsb2VzcyIsICAjIEFkZCBsb2Vzcw0KICAgYWRkLnBhcmFtcyA9IGxpc3QoY29sb3IgPSAiYmx1ZSIsIGZpbGwgPSAibGlnaHRibHVlIiksICMgQ3VzdG9taXplIHJlZy4gbGluZQ0KICAgY29uZi5pbnQgPSBUUlVFLCAjIEFkZCBjb25maWRlbmNlIGludGVydmFsDQogICBjb3IuY29lZiA9IEZBTFNFLCAjIEFkZCBjb3JyZWxhdGlvbiBjb2VmZmljaWVudC4gc2VlID9zdGF0X2Nvcg0KICAgc2l6ZSA9IDEgIyBTaXplIG9mIGRvdHMNCiAgICkNCmBgYA0KDQotIExldCdzIHRyeSBhIGxpbmVhciBtb2RlbA0KDQpgYGB7cn0NCk1vZGVsLlNvcnRhUXVhZDwtbG0oRXhhbVNjb3JlfkFueGlldHksIGRhdGE9IFNvcnRhUXVhZC5EYXRhKQ0KYGBgDQotIFBsb3QgdGhlIFJlc2lkdWFscw0KDQpgYGB7ciwgZmlnLndpZHRoPTZ9DQphdXRvcGxvdChNb2RlbC5Tb3J0YVF1YWQsIHdoaWNoID0gMToyLCBsYWJlbC5zaXplID0gMSkgKyB0aGVtZV9idygpDQpgYGANCg0KLSBJdCBzZWVtcyB0aGVyZSBpcyBhIHNsaWdodCBjdXJ2ZSBpbiBvdXIgZGF0YQ0KDQpgYGB7cn0NCmxpYnJhcnkoTUFTUykNCmJjPC1ib3hjb3goRXhhbVNjb3JlIH5BbnhpZXR5LCBkYXRhID0gU29ydGFRdWFkLkRhdGEsDQogICAgICAgbGFtYmRhID0gc2VxKC0yLCAyLCBsZW4gPSAyMCkpDQojd2UgY2FuIGV4dHJhY3QgdGhlIGxhbWJkYQ0KKGxhbWJkYSA8LSBiYyR4W3doaWNoLm1heChiYyR5KV0pDQpgYGANCg0KUGxvdCB0aGUgc2NhdHRlciBhbmQgcmVzaWR1YWxzIGZyb20gb3VyIHRyYW5zZm9ybWVkIERWDQpgYGB7cn0NCnNjYXR0ZXJwbG90KEV4YW1TY29yZV5sYW1iZGF+QW54aWV0eSwgZGF0YT0gU29ydGFRdWFkLkRhdGEsIHJlZy5saW5lPUZBTFNFLCBzbW9vdGhlcj1sb2Vzc0xpbmUpDQpNb2RlbC5Tb3J0YVF1YWQuQkM8LWxtKEV4YW1TY29yZV5sYW1iZGEgfiBBbnhpZXR5LCBkYXRhPSBTb3J0YVF1YWQuRGF0YSkNCmBgYA0KDQpgYGB7ciwgZmlnLndpZHRoPTZ9DQphdXRvcGxvdChNb2RlbC5Tb3J0YVF1YWQuQkMsIHdoaWNoID0gMToyLCBsYWJlbC5zaXplID0gMSkgKyB0aGVtZV9idygpDQpgYGANCg0KIyMjIFNvbWUgaXNzdWVzDQotIEJveC1Db3ggaXMgYSBndWVzcyBhbmQgcmVxdWlyZXMgc29tZSBmaW5lIHR1bmluZyAobGlrZSBhZGp1c3RpbmcgdGhlIHJhbmdlIG9mIGxhbWJkYXMpDQotIFlvdSBoYXZlIHRvIGtlZXAgdHJhY2sgb2YgeW91ciB6ZXJvcyBpbiB0aGUgRFYgKHNvIHlvdSBjYW4gYWRkIGNvbnN0YW50cyB0byBmaXggaXQpDQotIE1vc3QgaW1wb3J0YW50bHksIHlvdSBoYXZlIHRvIG1ha2UgaXQgaGFyZGVyIHRvIG1ha2Ugc2Vuc2Ugb2YgdGhlIERWIChidXQgeW91ciBmaXRzIHdpbGwgYmUgZmFyIGJldHRlciB1c2luZyBpdCkNCg0KPHNjcmlwdD4NCiAgKGZ1bmN0aW9uKGkscyxvLGcscixhLG0pe2lbJ0dvb2dsZUFuYWx5dGljc09iamVjdCddPXI7aVtyXT1pW3JdfHxmdW5jdGlvbigpew0KICAoaVtyXS5xPWlbcl0ucXx8W10pLnB1c2goYXJndW1lbnRzKX0saVtyXS5sPTEqbmV3IERhdGUoKTthPXMuY3JlYXRlRWxlbWVudChvKSwNCiAgbT1zLmdldEVsZW1lbnRzQnlUYWdOYW1lKG8pWzBdO2EuYXN5bmM9MTthLnNyYz1nO20ucGFyZW50Tm9kZS5pbnNlcnRCZWZvcmUoYSxtKQ0KICB9KSh3aW5kb3csZG9jdW1lbnQsJ3NjcmlwdCcsJ2h0dHBzOi8vd3d3Lmdvb2dsZS1hbmFseXRpY3MuY29tL2FuYWx5dGljcy5qcycsJ2dhJyk7DQoNCiAgZ2EoJ2NyZWF0ZScsICdVQS05MDQxNTE2MC0xJywgJ2F1dG8nKTsNCiAgZ2EoJ3NlbmQnLCAncGFnZXZpZXcnKTsNCg0KPC9zY3JpcHQ+DQoNCg==