Loading the ISLR package. We would be using the dataset Wage
require(ISLR)
Loading required package: ISLR
Polynomial regression
With one response age, and we fit a 4 degree polynomial.
poly_fit <- lm(wage~poly(age, 4), data = Wage)
summary(poly_fit)
Call:
lm(formula = wage ~ poly(age, 4), data = Wage)
Residuals:
Min 1Q Median 3Q Max
-98.707 -24.626 -4.993 15.217 203.693
Coefficients:
Estimate Std. Error t value Pr(>|t|)
(Intercept) 111.7036 0.7287 153.283 < 2e-16 ***
poly(age, 4)1 447.0679 39.9148 11.201 < 2e-16 ***
poly(age, 4)2 -478.3158 39.9148 -11.983 < 2e-16 ***
poly(age, 4)3 125.5217 39.9148 3.145 0.00168 **
poly(age, 4)4 -77.9112 39.9148 -1.952 0.05104 .
---
Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
Residual standard error: 39.91 on 2995 degrees of freedom
Multiple R-squared: 0.08626, Adjusted R-squared: 0.08504
F-statistic: 70.69 on 4 and 2995 DF, p-value: < 2.2e-16
This looks to be significant up to 3 degrees at 0.05 level.
Plotting response
#Getting the range of age and create a sequence of ages in between to use as new_data for prediction
age_range <- range(Wage$age)
age_grid <- seq(from = age_range[1], to = age_range[2])
poly_fit_preds <- predict(poly_fit, newdata = list(age = age_grid), se = T)
poly_fit_se_bands <- cbind(poly_fit_preds$fit + 1.96 * poly_fit_preds$se, poly_fit_preds$fit - 1.96 * poly_fit_preds$se)
plot(Wage$age, Wage$wage, col = "darkgrey")
lines(age_grid, poly_fit_preds$fit, lwd = 2, col = "blue")
matlines(age_grid, poly_fit_se_bands, col = "blue", lty = 2)

Another way to achieve this in R, because there are so many packages and methods -
fit_age_2=lm(wage~age+I(age^2)+I(age^3)+I(age^4),data=Wage)
summary(fit_age_2)
Call:
lm(formula = wage ~ age + I(age^2) + I(age^3) + I(age^4), data = Wage)
Residuals:
Min 1Q Median 3Q Max
-98.707 -24.626 -4.993 15.217 203.693
Coefficients:
Estimate Std. Error t value Pr(>|t|)
(Intercept) -1.842e+02 6.004e+01 -3.067 0.002180 **
age 2.125e+01 5.887e+00 3.609 0.000312 ***
I(age^2) -5.639e-01 2.061e-01 -2.736 0.006261 **
I(age^3) 6.811e-03 3.066e-03 2.221 0.026398 *
I(age^4) -3.204e-05 1.641e-05 -1.952 0.051039 .
---
Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
Residual standard error: 39.91 on 2995 degrees of freedom
Multiple R-squared: 0.08626, Adjusted R-squared: 0.08504
F-statistic: 70.69 on 4 and 2995 DF, p-value: < 2.2e-16
I() is an Identity function, because “^” (caret) sign has a different meaning in R. So, age^2 is not really age squared, but I(age^2) is.
In the above summary, the coefficients and the p-value are not the same as the previous code block’s result. Because the basis is different.
But the fitted value remains the same. We should get a straight line when we compare this with our previous fit.

So, we see that only the first three degrees are significant and useful. But we tested that separately using a simple fit. This only works with a single variable. If we really need to compare two models (Is 1 degree model better than a 2 degree model), we need to use an anova. This can be used when there are multiple variables in the model!
In case of nested linear models, where one model is an addition to an existing model, we use anova like -
fit_edu <- lm(wage ~ education, data = Wage)
fit_edu_age <- lm(wage ~ education + age, data = Wage)
poly_fit_edu_age <- lm(wage ~ education + poly(age, 2), data = Wage)
poly_fit_edu_age3 <- lm(wage ~ education + poly(age, 3), data = Wage)
Anova test
H0 - The additional variable has no impact on the model (current model and reduced model are the same) Ha - The additional variable does impact the model
anova(fit_edu, fit_edu_age, poly_fit_edu_age, poly_fit_edu_age3)
Analysis of Variance Table
Model 1: wage ~ education
Model 2: wage ~ education + age
Model 3: wage ~ education + poly(age, 2)
Model 4: wage ~ education + poly(age, 3)
Res.Df RSS Df Sum of Sq F Pr(>F)
1 2995 3995721
2 2994 3867992 1 127729 102.7378 <2e-16 ***
3 2993 3725395 1 142597 114.6969 <2e-16 ***
4 2992 3719809 1 5587 4.4936 0.0341 *
---
Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
It is clear that the first two linear models are significant. A polynomial on age, with degree 2 is also significant. But the fourth, with age^3, is only just marginally significant at the 0.05 level. We may want to choose and remove this degree.
Polynomial Logistic Regression
We can also proceed to use the same function for a logistic regression. Here we create a binary response of wage based on the plot we see. Big earners with wage > 250k tagged as 1, and remaining 0

summary(poly_logit_fit)
Call:
glm(formula = I(wage > 250) ~ poly(age, 3), family = binomial,
data = Wage)
Deviance Residuals:
Min 1Q Median 3Q Max
-0.2808 -0.2736 -0.2487 -0.1758 3.2868
Coefficients:
Estimate Std. Error z value Pr(>|z|)
(Intercept) -3.8486 0.1597 -24.100 < 2e-16 ***
poly(age, 3)1 37.8846 11.4818 3.300 0.000968 ***
poly(age, 3)2 -29.5129 10.5626 -2.794 0.005205 **
poly(age, 3)3 9.7966 8.9990 1.089 0.276317
---
Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
(Dispersion parameter for binomial family taken to be 1)
Null deviance: 730.53 on 2999 degrees of freedom
Residual deviance: 707.92 on 2996 degrees of freedom
AIC: 715.92
Number of Fisher Scoring iterations: 8
Getting standard error bands
poly_logit_preds <- predict(poly_logit_fit,list(age=age_grid),se=T)
poly_logit_se_bands <- poly_logit_preds$fit + cbind(fit=0,lower=-1.96*poly_logit_preds$se,upper=1.96*poly_logit_preds$se)
poly_logit_se_bands[1:5,]
fit lower upper
1 -7.664756 -10.697925 -4.631588
2 -7.324776 -10.051060 -4.598491
3 -7.001732 -9.443000 -4.560465
4 -6.695229 -8.872719 -4.517739
5 -6.404868 -8.339214 -4.470521
This predict is on the logit scale. We need to transform or perform the inverse logit in order to get the reverse mapping and get the probability values.
Inverse Logit function is
\[p=\frac{e^\eta}{1+e^\eta}.\]
prob_poly_logit_preds <- exp(poly_logit_se_bands)/(1+exp(poly_logit_se_bands))
matplot(age_grid, prob_poly_logit_preds, col = "blue", lwd = c(2,1,1), lty = c(1,2,2), type = "l", ylim = c(0,.1))
The blue line is the estimate of the fitted probability, and the dotted lines are the corresponding 95% confidence interval. We basically calculate everything on the logit scale and transform them back into the probability scale.
Adding the same points to the plot. The jitter function takes a point, adds some random uniform noise to it so that the points get kind of blown up to give the picture about its density. Think of it as a boxplot, but with noise now instead of all points concentrated at a single location.
matplot(age_grid, prob_poly_logit_preds, col = "blue", lwd = c(2,1,1), lty = c(1,2,2), type = "l", ylim = c(0,.1))
points(jitter(Wage$age), I(Wage$wage > 250)/10, pch = "|", cex = .5)
Only a max 4% of the population across all ages earned above 250k. In the above plot, we have divided the Wage scale by 10 in order to bring the points into the picture. Otherwise, it will be way up above the plot, and we wont be able to see it.
Splines
Fitting splines (cubic) on the same dataset. Splines are more flexible than polynomials.
require(splines)
#This fits a cubic spline at knots of age 25, 40 and 60
spline_fit <- lm(wage ~ bs(age, knots = c(25, 40, 60)), data = Wage)
plot(Wage$age, Wage$wage, col = "darkgrey")
lines(age_grid, predict(spline_fit, list(age = age_grid)), col = "darkgreen", lwd = 2)
abline(v = c(25,40,60), lty = 2, col = "darkgreen")

Spline - cubic polynomial. The bs() gives basis for cubic polynomial. Knots - Where we think discontinuity will happen (@ third derivative). Basically, we have a polynomial to the degree 3 at each of the region, constrained to “meet” or be continous at the knot. So this means they are more local than polynomials.
Comparing the polynomial fit with that of the GAM model
For the Wages dataset, it seems similar. But for a dataset with more sparse tail observations, the polynomial fit would be a little unpredictable, while the splines will be a little more flexible and not so “wagging”
par(mfrow = c(1,2))
plot(Wage$age, Wage$wage, col = "darkgrey")
lines(age_grid, poly_fit_preds$fit, lwd = 2, col = "blue")
matlines(age_grid, poly_fit_se_bands, col = "blue", lty = 2)
plot(Wage$age, Wage$wage, col = "darkgrey")
lines(age_grid, predict(spline_fit, list(age = age_grid)), col = "darkgreen", lwd = 2)
abline(v = c(25,40,60), lty = 2, col = "darkgreen")

In the previous fit, we fixed the knots. We can also have smoothing splines!
Smoothing splines considers every point to be a knot, and they have a roughness penalty to control the smoothness of the function. Basically, we do not have to manually select the knots. We specify the penalty through the effective degree of freedom.
smooth_spline_fit=smooth.spline(Wage$age, Wage$wage,df=16)
Adding the above fit to the fixed splines
par(mfrow = c(1,2))
plot(Wage$age, Wage$wage, col = "darkgrey")
lines(smooth_spline_fit, col = "red", lwd = 2)
plot(Wage$age, Wage$wage, col = "darkgrey")
lines(age_grid, predict(spline_fit, list(age = age_grid)), col = "darkgreen", lwd = 2)
abline(v = c(25,40,60), lty = 2, col = "darkgreen")
lines(smooth_spline_fit, col = "red", lwd = 2)

16 degrees of freedom is a lot, which results in the fit in being too “Wiggly”
Another effective way to determine the degree (instead of forcing it to take a particular value manually), is through cross-validation. This selects the smoothing parameter for us automatically.
smooth_spline_cv_fit=smooth.spline(Wage$age, Wage$wage,cv=TRUE)
cross-validation with non-unique 'x' values seems doubtful
Adding the smooth spline with df chosen through cv to the above plot
par(mfrow = c(1,2))
plot(Wage$age, Wage$wage, col = "darkgrey")
lines(smooth_spline_cv_fit, col = "purple", lwd = 2)
plot(Wage$age, Wage$wage, col = "darkgrey")
lines(age_grid, predict(spline_fit, list(age = age_grid)), col = "darkgreen", lwd = 2)
abline(v = c(25,40,60), lty = 2, col = "darkgreen")
lines(smooth_spline_fit,col="red",lwd=2)
lines(smooth_spline_cv_fit,col="purple",lwd=2)

The warning is because there are many ties in x
smooth_spline_cv_fit
Call:
smooth.spline(x = Wage$age, y = Wage$wage, cv = TRUE)
Smoothing Parameter spar= 0.6988943 lambda= 0.02792303 (12 iterations)
Equivalent Degrees of Freedom (Df): 6.794596
Penalized Criterion (RSS): 75215.9
PRESS(l.o.o. CV): 1593.383
The effective degrees of freedom in the cv smoothing spline is 6.79 (heuristic for how rough the function is). For the fixed knot model, the degree of freedom was 6.
Generalized Additive Models
GAM package helps in fitting multiple non-linear functions (one for each variable) and makes it additive. It also provides good plotting functions.
We create a model with - a smooth term with age (4 degrees of freedom), a smooth term with year(4 degrees of freedom) and a linear term education (which is a factor variable, so it makes dummy variables)
#install.packages("gam")
require(gam)
Loading required package: gam
Loading required package: foreach
Loaded gam 1.16.1
gam_fit=gam(wage~s(age,df=4)+s(year,df=4)+education,data=Wage)
non-list contrasts argument ignored
When we plot the GAM, we will be able to see a fitted plot for each of the variable
par(mfrow=c(1,3))
plot(gam_fit,se=T)

There are three plots - one for each term in the GAM. We see that salaries tend to increase with year other than the dip. Salary also increases with education.
We can also fit GAMs with logistic regression model

This is plotting the contribution to the logit of the probability. The standard errors are actually very wide.
Using GAM, we can do some tests. Lets see if we need a nonlinear terms for year. So we fit the same model as before, except instead of having a smooth term with year, we have a linear term with year.
ANOVA with test = “Chisq” (because logistic regression)
anova(gam_fit2_linearYear,gam_fit2,test="Chisq")
Analysis of Deviance Table
Model 1: I(wage > 250) ~ s(age, df = 4) + year + education
Model 2: I(wage > 250) ~ s(age, df = 4) + s(year, df = 4) + education
Resid. Df Resid. Dev Df Deviance Pr(>Chi)
1 2990 603.78
2 2987 602.87 3 0.90498 0.8242
We see that from the test resuts, the non_linear function for year is not needed. Linear term is good enough!
Plotting is powerful using the GAM package, which is not restricted to plotting GAM objects, but can be used even with LM or GLM objects
sample_lm_fit=lm(wage~ns(age,df=4)+ns(year,df=4)+education,data=Wage)
par(mfrow=c(1,3))
plot.Gam(sample_lm_fit,se=T)

ns() gives a basis of natural splines
LS0tDQp0aXRsZTogIk5vbiBMaW5lYXIgTW9kZWxzIg0Kb3V0cHV0OiBodG1sX25vdGVib29rDQotLS0NCg0KTG9hZGluZyB0aGUgSVNMUiBwYWNrYWdlLiBXZSB3b3VsZCBiZSB1c2luZyB0aGUgZGF0YXNldCBXYWdlDQoNCmBgYHtyfQ0KcmVxdWlyZShJU0xSKQ0KDQpkYXRhKFdhZ2UpDQpgYGANCg0KLS0tDQoNCiMjI1BvbHlub21pYWwgcmVncmVzc2lvbg0KDQpXaXRoIG9uZSByZXNwb25zZSBhZ2UsIGFuZCB3ZSBmaXQgYSA0IGRlZ3JlZSBwb2x5bm9taWFsLg0KDQpgYGB7cn0NCg0KcG9seV9maXQgPC0gbG0od2FnZX5wb2x5KGFnZSwgNCksIGRhdGEgPSBXYWdlKQ0Kc3VtbWFyeShwb2x5X2ZpdCkNCmBgYA0KDQpUaGlzIGxvb2tzIHRvIGJlIHNpZ25pZmljYW50IHVwIHRvIDMgZGVncmVlcyBhdCAwLjA1IGxldmVsLg0KDQpQbG90dGluZyByZXNwb25zZQ0KDQpgYGB7cn0NCiNHZXR0aW5nIHRoZSByYW5nZSBvZiBhZ2UgYW5kIGNyZWF0ZSBhIHNlcXVlbmNlIG9mIGFnZXMgaW4gYmV0d2VlbiB0byB1c2UgYXMgbmV3X2RhdGEgZm9yIHByZWRpY3Rpb24NCmFnZV9yYW5nZSA8LSByYW5nZShXYWdlJGFnZSkNCmFnZV9ncmlkIDwtIHNlcShmcm9tID0gYWdlX3JhbmdlWzFdLCB0byA9IGFnZV9yYW5nZVsyXSkNCg0KcG9seV9maXRfcHJlZHMgPC0gcHJlZGljdChwb2x5X2ZpdCwgbmV3ZGF0YSA9IGxpc3QoYWdlID0gYWdlX2dyaWQpLCBzZSA9IFQpDQpwb2x5X2ZpdF9zZV9iYW5kcyA8LSBjYmluZChwb2x5X2ZpdF9wcmVkcyRmaXQgKyAxLjk2ICogcG9seV9maXRfcHJlZHMkc2UsIHBvbHlfZml0X3ByZWRzJGZpdCAtIDEuOTYgKiBwb2x5X2ZpdF9wcmVkcyRzZSkNCg0KcGxvdChXYWdlJGFnZSwgV2FnZSR3YWdlLCBjb2wgPSAiZGFya2dyZXkiKQ0KbGluZXMoYWdlX2dyaWQsIHBvbHlfZml0X3ByZWRzJGZpdCwgbHdkID0gMiwgY29sID0gImJsdWUiKQ0KbWF0bGluZXMoYWdlX2dyaWQsIHBvbHlfZml0X3NlX2JhbmRzLCBjb2wgPSAiYmx1ZSIsIGx0eSA9IDIpDQoNCmBgYA0KDQotLS0NCg0KQW5vdGhlciB3YXkgdG8gYWNoaWV2ZSB0aGlzIGluIFIsIGJlY2F1c2UgdGhlcmUgYXJlIHNvIG1hbnkgcGFja2FnZXMgYW5kIG1ldGhvZHMgLSANCg0KYGBge3J9DQpmaXRfYWdlXzI9bG0od2FnZX5hZ2UrSShhZ2VeMikrSShhZ2VeMykrSShhZ2VeNCksZGF0YT1XYWdlKQ0Kc3VtbWFyeShmaXRfYWdlXzIpDQpgYGANCg0KLSBJKCkgaXMgYW4gSWRlbnRpdHkgZnVuY3Rpb24sIGJlY2F1c2UgIl4iIChjYXJldCkgc2lnbiBoYXMgYSBkaWZmZXJlbnQgbWVhbmluZyBpbiBSLiBTbywgYWdlXjIgaXMgbm90IHJlYWxseSBhZ2Ugc3F1YXJlZCwgYnV0IEkoYWdlXjIpIGlzLg0KDQotIEluIHRoZSBhYm92ZSBzdW1tYXJ5LCB0aGUgY29lZmZpY2llbnRzIGFuZCB0aGUgcC12YWx1ZSBhcmUgbm90IHRoZSBzYW1lIGFzIHRoZSBwcmV2aW91cyBjb2RlIGJsb2NrJ3MgcmVzdWx0LiBCZWNhdXNlIHRoZSBiYXNpcyBpcyBkaWZmZXJlbnQuDQoNCkJ1dCB0aGUgZml0dGVkIHZhbHVlIHJlbWFpbnMgdGhlIHNhbWUuIFdlIHNob3VsZCBnZXQgYSBzdHJhaWdodCBsaW5lIHdoZW4gd2UgY29tcGFyZSB0aGlzIHdpdGggb3VyIHByZXZpb3VzIGZpdC4NCg0KYGBge3J9DQpwbG90KGZpdHRlZChwb2x5X2ZpdCksIGZpdHRlZChmaXRfYWdlXzIpKQ0KYGBgDQoNCg0KU28sIHdlIHNlZSB0aGF0IG9ubHkgdGhlIGZpcnN0IHRocmVlIGRlZ3JlZXMgYXJlIHNpZ25pZmljYW50IGFuZCB1c2VmdWwuIEJ1dCB3ZSB0ZXN0ZWQgdGhhdCBzZXBhcmF0ZWx5IHVzaW5nIGEgc2ltcGxlIGZpdC4gVGhpcyBvbmx5IHdvcmtzIHdpdGggYSBzaW5nbGUgdmFyaWFibGUuIElmIHdlIHJlYWxseSBuZWVkIHRvIGNvbXBhcmUgdHdvIG1vZGVscyAoSXMgMSBkZWdyZWUgbW9kZWwgYmV0dGVyIHRoYW4gYSAyIGRlZ3JlZSBtb2RlbCksIHdlIG5lZWQgdG8gdXNlIGFuIGFub3ZhLiBUaGlzIGNhbiBiZSB1c2VkIHdoZW4gdGhlcmUgYXJlIG11bHRpcGxlIHZhcmlhYmxlcyBpbiB0aGUgbW9kZWwhDQoNCi0tLQ0KDQpJbiBjYXNlIG9mIG5lc3RlZCBsaW5lYXIgbW9kZWxzLCB3aGVyZSBvbmUgbW9kZWwgaXMgYW4gYWRkaXRpb24gdG8gYW4gZXhpc3RpbmcgbW9kZWwsIHdlIHVzZSBhbm92YSBsaWtlIC0gDQoNCmBgYHtyfQ0KZml0X2VkdSA8LSBsbSh3YWdlIH4gZWR1Y2F0aW9uLCBkYXRhID0gV2FnZSkNCmZpdF9lZHVfYWdlIDwtIGxtKHdhZ2UgfiBlZHVjYXRpb24gKyBhZ2UsIGRhdGEgPSBXYWdlKQ0KcG9seV9maXRfZWR1X2FnZSA8LSBsbSh3YWdlIH4gZWR1Y2F0aW9uICsgcG9seShhZ2UsIDIpLCBkYXRhID0gV2FnZSkNCnBvbHlfZml0X2VkdV9hZ2UzIDwtIGxtKHdhZ2UgfiBlZHVjYXRpb24gKyBwb2x5KGFnZSwgMyksIGRhdGEgPSBXYWdlKQ0KYGBgDQoNCkFub3ZhIHRlc3QNCg0KSDAgLSBUaGUgYWRkaXRpb25hbCB2YXJpYWJsZSBoYXMgbm8gaW1wYWN0IG9uIHRoZSBtb2RlbCAoY3VycmVudCBtb2RlbCBhbmQgcmVkdWNlZCBtb2RlbCBhcmUgdGhlIHNhbWUpDQpIYSAtIFRoZSBhZGRpdGlvbmFsIHZhcmlhYmxlIGRvZXMgaW1wYWN0IHRoZSBtb2RlbA0KDQpgYGB7cn0NCmFub3ZhKGZpdF9lZHUsIGZpdF9lZHVfYWdlLCBwb2x5X2ZpdF9lZHVfYWdlLCBwb2x5X2ZpdF9lZHVfYWdlMykNCmBgYA0KDQpJdCBpcyBjbGVhciB0aGF0IHRoZSBmaXJzdCB0d28gbGluZWFyIG1vZGVscyBhcmUgc2lnbmlmaWNhbnQuIEEgcG9seW5vbWlhbCBvbiBhZ2UsIHdpdGggZGVncmVlIDIgaXMgYWxzbyBzaWduaWZpY2FudC4gQnV0IHRoZSBmb3VydGgsIHdpdGggYWdlXjMsIGlzIG9ubHkganVzdCBtYXJnaW5hbGx5IHNpZ25pZmljYW50IGF0IHRoZSAwLjA1IGxldmVsLiBXZSBtYXkgd2FudCB0byBjaG9vc2UgYW5kIHJlbW92ZSB0aGlzIGRlZ3JlZS4gDQoNCi0tLQ0KDQojIyNQb2x5bm9taWFsIExvZ2lzdGljIFJlZ3Jlc3Npb24NCg0KV2UgY2FuIGFsc28gcHJvY2VlZCB0byB1c2UgdGhlIHNhbWUgZnVuY3Rpb24gZm9yIGEgbG9naXN0aWMgcmVncmVzc2lvbi4gSGVyZSB3ZSBjcmVhdGUgYSBiaW5hcnkgcmVzcG9uc2Ugb2Ygd2FnZSBiYXNlZCBvbiB0aGUgcGxvdCB3ZSBzZWUuIEJpZyBlYXJuZXJzIHdpdGggd2FnZSA+IDI1MGsgdGFnZ2VkIGFzIDEsIGFuZCByZW1haW5pbmcgMA0KDQpgYGB7cn0NCnBsb3QoV2FnZSRhZ2UsIFdhZ2Ukd2FnZSkNCmBgYA0KDQpgYGB7cn0NCnBvbHlfbG9naXRfZml0PWdsbShJKHdhZ2U+MjUwKSB+IHBvbHkoYWdlLDMpLCBkYXRhPVdhZ2UsIGZhbWlseT1iaW5vbWlhbCkNCnN1bW1hcnkocG9seV9sb2dpdF9maXQpDQpgYGANCg0KDQpHZXR0aW5nIHN0YW5kYXJkIGVycm9yIGJhbmRzDQpgYGB7cn0NCnBvbHlfbG9naXRfcHJlZHMgPC0gcHJlZGljdChwb2x5X2xvZ2l0X2ZpdCxsaXN0KGFnZT1hZ2VfZ3JpZCksc2U9VCkNCnBvbHlfbG9naXRfc2VfYmFuZHMgPC0gcG9seV9sb2dpdF9wcmVkcyRmaXQgKyBjYmluZChmaXQ9MCxsb3dlcj0tMS45Nipwb2x5X2xvZ2l0X3ByZWRzJHNlLHVwcGVyPTEuOTYqcG9seV9sb2dpdF9wcmVkcyRzZSkNCnBvbHlfbG9naXRfc2VfYmFuZHNbMTo1LF0NCmBgYA0KDQpUaGlzIHByZWRpY3QgaXMgb24gdGhlIGxvZ2l0IHNjYWxlLiBXZSBuZWVkIHRvIHRyYW5zZm9ybSBvciBwZXJmb3JtIHRoZSBpbnZlcnNlIGxvZ2l0IGluIG9yZGVyIHRvIGdldCB0aGUgcmV2ZXJzZSBtYXBwaW5nIGFuZCBnZXQgdGhlIHByb2JhYmlsaXR5IHZhbHVlcy4NCg0KSW52ZXJzZSBMb2dpdCBmdW5jdGlvbiBpcw0KDQokJHA9XGZyYWN7ZV5cZXRhfXsxK2VeXGV0YX0uJCQNCg0KYGBge3J9DQpwcm9iX3BvbHlfbG9naXRfcHJlZHMgPC0gZXhwKHBvbHlfbG9naXRfc2VfYmFuZHMpLygxK2V4cChwb2x5X2xvZ2l0X3NlX2JhbmRzKSkNCg0KbWF0cGxvdChhZ2VfZ3JpZCwgcHJvYl9wb2x5X2xvZ2l0X3ByZWRzLCBjb2wgPSAiYmx1ZSIsIGx3ZCA9IGMoMiwxLDEpLCBsdHkgPSBjKDEsMiwyKSwgdHlwZSA9ICJsIiwgeWxpbSA9IGMoMCwuMSkpDQpgYGANCg0KVGhlIGJsdWUgbGluZSBpcyB0aGUgZXN0aW1hdGUgb2YgdGhlIGZpdHRlZCBwcm9iYWJpbGl0eSwgYW5kIHRoZSBkb3R0ZWQgbGluZXMgYXJlIHRoZSBjb3JyZXNwb25kaW5nIDk1JSBjb25maWRlbmNlIGludGVydmFsLiBXZSBiYXNpY2FsbHkgY2FsY3VsYXRlIGV2ZXJ5dGhpbmcgb24gdGhlIGxvZ2l0IHNjYWxlIGFuZCB0cmFuc2Zvcm0gdGhlbSBiYWNrIGludG8gdGhlIHByb2JhYmlsaXR5IHNjYWxlLg0KDQpBZGRpbmcgdGhlIHNhbWUgcG9pbnRzIHRvIHRoZSBwbG90LiBUaGUgaml0dGVyIGZ1bmN0aW9uIHRha2VzIGEgcG9pbnQsIGFkZHMgc29tZSByYW5kb20gdW5pZm9ybSBub2lzZSB0byBpdCBzbyB0aGF0IHRoZSBwb2ludHMgZ2V0IGtpbmQgb2YgYmxvd24gdXAgdG8gZ2l2ZSB0aGUgcGljdHVyZSBhYm91dCBpdHMgZGVuc2l0eS4gVGhpbmsgb2YgaXQgYXMgYSBib3hwbG90LCBidXQgd2l0aCBub2lzZSBub3cgaW5zdGVhZCBvZiBhbGwgcG9pbnRzIGNvbmNlbnRyYXRlZCBhdCBhIHNpbmdsZSBsb2NhdGlvbi4gDQoNCmBgYHtyfQ0KbWF0cGxvdChhZ2VfZ3JpZCwgcHJvYl9wb2x5X2xvZ2l0X3ByZWRzLCBjb2wgPSAiYmx1ZSIsIGx3ZCA9IGMoMiwxLDEpLCBsdHkgPSBjKDEsMiwyKSwgdHlwZSA9ICJsIiwgeWxpbSA9IGMoMCwuMSkpDQpwb2ludHMoaml0dGVyKFdhZ2UkYWdlKSwgSShXYWdlJHdhZ2UgPiAyNTApLzEwLCBwY2ggPSAifCIsIGNleCA9IC41KQ0KYGBgDQoNCk9ubHkgYSBtYXggNCUgb2YgdGhlIHBvcHVsYXRpb24gYWNyb3NzIGFsbCBhZ2VzIGVhcm5lZCBhYm92ZSAyNTBrLiBJbiB0aGUgYWJvdmUgcGxvdCwgd2UgaGF2ZSBkaXZpZGVkIHRoZSBXYWdlIHNjYWxlIGJ5IDEwIGluIG9yZGVyIHRvIGJyaW5nIHRoZSBwb2ludHMgaW50byB0aGUgcGljdHVyZS4gT3RoZXJ3aXNlLCBpdCB3aWxsIGJlIHdheSB1cCBhYm92ZSB0aGUgcGxvdCwgYW5kIHdlIHdvbnQgYmUgYWJsZSB0byBzZWUgaXQuDQoNCg0KLS0tDQoNCiMjI1NwbGluZXMNCg0KDQpGaXR0aW5nIHNwbGluZXMgKGN1YmljKSBvbiB0aGUgc2FtZSBkYXRhc2V0LiBTcGxpbmVzIGFyZSBtb3JlIGZsZXhpYmxlIHRoYW4gcG9seW5vbWlhbHMuDQoNCg0KYGBge3J9DQpyZXF1aXJlKHNwbGluZXMpDQoNCiNUaGlzIGZpdHMgYSBjdWJpYyBzcGxpbmUgYXQga25vdHMgb2YgYWdlIDI1LCA0MCBhbmQgNjANCnNwbGluZV9maXQgPC0gbG0od2FnZSB+IGJzKGFnZSwga25vdHMgPSBjKDI1LCA0MCwgNjApKSwgZGF0YSA9IFdhZ2UpDQpgYGANCg0KYGBge3J9DQpwbG90KFdhZ2UkYWdlLCBXYWdlJHdhZ2UsIGNvbCA9ICJkYXJrZ3JleSIpDQpsaW5lcyhhZ2VfZ3JpZCwgcHJlZGljdChzcGxpbmVfZml0LCBsaXN0KGFnZSA9IGFnZV9ncmlkKSksIGNvbCA9ICJkYXJrZ3JlZW4iLCBsd2QgPSAyKQ0KYWJsaW5lKHYgPSBjKDI1LDQwLDYwKSwgbHR5ID0gMiwgY29sID0gImRhcmtncmVlbiIpDQpgYGANCg0KU3BsaW5lIC0gY3ViaWMgcG9seW5vbWlhbC4gVGhlIGJzKCkgZ2l2ZXMgYmFzaXMgZm9yIGN1YmljIHBvbHlub21pYWwuDQpLbm90cyAtIFdoZXJlIHdlIHRoaW5rIGRpc2NvbnRpbnVpdHkgd2lsbCBoYXBwZW4gKEAgdGhpcmQgZGVyaXZhdGl2ZSkuIEJhc2ljYWxseSwgd2UgaGF2ZSBhIHBvbHlub21pYWwgdG8gdGhlIGRlZ3JlZSAzIGF0IGVhY2ggb2YgdGhlIHJlZ2lvbiwgY29uc3RyYWluZWQgdG8gIm1lZXQiIG9yIGJlIGNvbnRpbm91cyBhdCB0aGUga25vdC4gU28gdGhpcyBtZWFucyB0aGV5IGFyZSBtb3JlIGxvY2FsIHRoYW4gcG9seW5vbWlhbHMuDQoNCkNvbXBhcmluZyB0aGUgcG9seW5vbWlhbCBmaXQgd2l0aCB0aGF0IG9mIHRoZSBHQU0gbW9kZWwNCg0KRm9yIHRoZSBXYWdlcyBkYXRhc2V0LCBpdCBzZWVtcyBzaW1pbGFyLiBCdXQgZm9yIGEgZGF0YXNldCB3aXRoIG1vcmUgc3BhcnNlIHRhaWwgb2JzZXJ2YXRpb25zLCB0aGUgcG9seW5vbWlhbCBmaXQgd291bGQgYmUgYSBsaXR0bGUgdW5wcmVkaWN0YWJsZSwgd2hpbGUgdGhlIHNwbGluZXMgd2lsbCBiZSBhIGxpdHRsZSBtb3JlIGZsZXhpYmxlIGFuZCBub3Qgc28gIndhZ2dpbmciDQoNCmBgYHtyfQ0KcGFyKG1mcm93ID0gYygxLDIpKQ0KDQpwbG90KFdhZ2UkYWdlLCBXYWdlJHdhZ2UsIGNvbCA9ICJkYXJrZ3JleSIpDQpsaW5lcyhhZ2VfZ3JpZCwgcG9seV9maXRfcHJlZHMkZml0LCBsd2QgPSAyLCBjb2wgPSAiYmx1ZSIpDQptYXRsaW5lcyhhZ2VfZ3JpZCwgcG9seV9maXRfc2VfYmFuZHMsIGNvbCA9ICJibHVlIiwgbHR5ID0gMikNCg0KcGxvdChXYWdlJGFnZSwgV2FnZSR3YWdlLCBjb2wgPSAiZGFya2dyZXkiKQ0KbGluZXMoYWdlX2dyaWQsIHByZWRpY3Qoc3BsaW5lX2ZpdCwgbGlzdChhZ2UgPSBhZ2VfZ3JpZCkpLCBjb2wgPSAiZGFya2dyZWVuIiwgbHdkID0gMikNCmFibGluZSh2ID0gYygyNSw0MCw2MCksIGx0eSA9IDIsIGNvbCA9ICJkYXJrZ3JlZW4iKQ0KDQpgYGANCg0KLS0tDQoNCkluIHRoZSBwcmV2aW91cyBmaXQsIHdlIGZpeGVkIHRoZSBrbm90cy4gV2UgY2FuIGFsc28gaGF2ZSBzbW9vdGhpbmcgc3BsaW5lcyENCg0KU21vb3RoaW5nIHNwbGluZXMgY29uc2lkZXJzIGV2ZXJ5IHBvaW50IHRvIGJlIGEga25vdCwgYW5kIHRoZXkgaGF2ZSBhIHJvdWdobmVzcyBwZW5hbHR5IHRvIGNvbnRyb2wgdGhlIHNtb290aG5lc3Mgb2YgdGhlIGZ1bmN0aW9uLiBCYXNpY2FsbHksIHdlIGRvIG5vdCBoYXZlIHRvIG1hbnVhbGx5IHNlbGVjdCB0aGUga25vdHMuIFdlIHNwZWNpZnkgdGhlIHBlbmFsdHkgdGhyb3VnaCB0aGUgZWZmZWN0aXZlIGRlZ3JlZSBvZiBmcmVlZG9tLg0KDQpgYGB7cn0NCnNtb290aF9zcGxpbmVfZml0PXNtb290aC5zcGxpbmUoV2FnZSRhZ2UsIFdhZ2Ukd2FnZSxkZj0xNikNCmBgYA0KDQpBZGRpbmcgdGhlIGFib3ZlIGZpdCB0byB0aGUgZml4ZWQgc3BsaW5lcw0KYGBge3J9DQpwYXIobWZyb3cgPSBjKDEsMikpDQpwbG90KFdhZ2UkYWdlLCBXYWdlJHdhZ2UsIGNvbCA9ICJkYXJrZ3JleSIpDQpsaW5lcyhzbW9vdGhfc3BsaW5lX2ZpdCwgY29sID0gInJlZCIsIGx3ZCA9IDIpDQoNCnBsb3QoV2FnZSRhZ2UsIFdhZ2Ukd2FnZSwgY29sID0gImRhcmtncmV5IikNCmxpbmVzKGFnZV9ncmlkLCBwcmVkaWN0KHNwbGluZV9maXQsIGxpc3QoYWdlID0gYWdlX2dyaWQpKSwgY29sID0gImRhcmtncmVlbiIsIGx3ZCA9IDIpDQphYmxpbmUodiA9IGMoMjUsNDAsNjApLCBsdHkgPSAyLCBjb2wgPSAiZGFya2dyZWVuIikNCmxpbmVzKHNtb290aF9zcGxpbmVfZml0LCBjb2wgPSAicmVkIiwgbHdkID0gMikNCmBgYA0KDQoxNiBkZWdyZWVzIG9mIGZyZWVkb20gaXMgYSBsb3QsIHdoaWNoIHJlc3VsdHMgaW4gdGhlIGZpdCBpbiBiZWluZyB0b28gIldpZ2dseSINCg0KQW5vdGhlciBlZmZlY3RpdmUgd2F5IHRvIGRldGVybWluZSB0aGUgZGVncmVlIChpbnN0ZWFkIG9mIGZvcmNpbmcgaXQgdG8gdGFrZSBhIHBhcnRpY3VsYXIgdmFsdWUgbWFudWFsbHkpLCBpcyB0aHJvdWdoIGNyb3NzLXZhbGlkYXRpb24uIFRoaXMgc2VsZWN0cyB0aGUgc21vb3RoaW5nIHBhcmFtZXRlciBmb3IgdXMgYXV0b21hdGljYWxseS4NCg0KYGBge3J9DQpzbW9vdGhfc3BsaW5lX2N2X2ZpdD1zbW9vdGguc3BsaW5lKFdhZ2UkYWdlLCBXYWdlJHdhZ2UsY3Y9VFJVRSkNCmBgYA0KDQpBZGRpbmcgdGhlIHNtb290aCBzcGxpbmUgd2l0aCBkZiBjaG9zZW4gdGhyb3VnaCBjdiB0byB0aGUgYWJvdmUgcGxvdA0KYGBge3J9DQoNCnBhcihtZnJvdyA9IGMoMSwyKSkNCnBsb3QoV2FnZSRhZ2UsIFdhZ2Ukd2FnZSwgY29sID0gImRhcmtncmV5IikNCmxpbmVzKHNtb290aF9zcGxpbmVfY3ZfZml0LCBjb2wgPSAicHVycGxlIiwgbHdkID0gMikNCg0KDQpwbG90KFdhZ2UkYWdlLCBXYWdlJHdhZ2UsIGNvbCA9ICJkYXJrZ3JleSIpDQpsaW5lcyhhZ2VfZ3JpZCwgcHJlZGljdChzcGxpbmVfZml0LCBsaXN0KGFnZSA9IGFnZV9ncmlkKSksIGNvbCA9ICJkYXJrZ3JlZW4iLCBsd2QgPSAyKQ0KYWJsaW5lKHYgPSBjKDI1LDQwLDYwKSwgbHR5ID0gMiwgY29sID0gImRhcmtncmVlbiIpDQpsaW5lcyhzbW9vdGhfc3BsaW5lX2ZpdCxjb2w9InJlZCIsbHdkPTIpDQpsaW5lcyhzbW9vdGhfc3BsaW5lX2N2X2ZpdCxjb2w9InB1cnBsZSIsbHdkPTIpDQpgYGANCg0KVGhlIHdhcm5pbmcgaXMgYmVjYXVzZSB0aGVyZSBhcmUgbWFueSB0aWVzIGluIHgNCg0KYGBge3J9DQpzbW9vdGhfc3BsaW5lX2N2X2ZpdA0KYGBgDQoNClRoZSBlZmZlY3RpdmUgZGVncmVlcyBvZiBmcmVlZG9tIGluIHRoZSBjdiBzbW9vdGhpbmcgc3BsaW5lIGlzIDYuNzkgKGhldXJpc3RpYyBmb3IgaG93IHJvdWdoIHRoZSBmdW5jdGlvbiBpcykuIEZvciB0aGUgZml4ZWQga25vdCBtb2RlbCwgdGhlIGRlZ3JlZSBvZiBmcmVlZG9tIHdhcyA2Lg0KDQotLS0NCg0KIyMjR2VuZXJhbGl6ZWQgQWRkaXRpdmUgTW9kZWxzDQoNCkdBTSBwYWNrYWdlIGhlbHBzIGluIGZpdHRpbmcgbXVsdGlwbGUgbm9uLWxpbmVhciBmdW5jdGlvbnMgKG9uZSBmb3IgZWFjaCB2YXJpYWJsZSkgYW5kIG1ha2VzIGl0IGFkZGl0aXZlLiBJdCBhbHNvIHByb3ZpZGVzIGdvb2QgcGxvdHRpbmcgZnVuY3Rpb25zLg0KDQpXZSBjcmVhdGUgYSBtb2RlbCB3aXRoIC0gYSBzbW9vdGggdGVybSB3aXRoIGFnZSAoNCBkZWdyZWVzIG9mIGZyZWVkb20pLCBhIHNtb290aCB0ZXJtIHdpdGggeWVhcig0IGRlZ3JlZXMgb2YgZnJlZWRvbSkgYW5kIGEgbGluZWFyIHRlcm0gZWR1Y2F0aW9uICh3aGljaCBpcyBhIGZhY3RvciB2YXJpYWJsZSwgc28gaXQgbWFrZXMgZHVtbXkgdmFyaWFibGVzKQ0KDQpgYGB7cn0NCiNpbnN0YWxsLnBhY2thZ2VzKCJnYW0iKQ0KYGBgDQoNCmBgYHtyfQ0KcmVxdWlyZShnYW0pDQpnYW1fZml0PWdhbSh3YWdlfnMoYWdlLGRmPTQpK3MoeWVhcixkZj00KStlZHVjYXRpb24sZGF0YT1XYWdlKQ0KYGBgDQoNCldoZW4gd2UgcGxvdCB0aGUgR0FNLCB3ZSB3aWxsIGJlIGFibGUgdG8gc2VlIGEgZml0dGVkIHBsb3QgZm9yIGVhY2ggb2YgdGhlIHZhcmlhYmxlDQoNCmBgYHtyfQ0KcGFyKG1mcm93PWMoMSwzKSkNCnBsb3QoZ2FtX2ZpdCxzZT1UKQ0KYGBgDQoNClRoZXJlIGFyZSB0aHJlZSBwbG90cyAtIG9uZSBmb3IgZWFjaCB0ZXJtIGluIHRoZSBHQU0uIFdlIHNlZSB0aGF0IHNhbGFyaWVzIHRlbmQgdG8gaW5jcmVhc2Ugd2l0aCB5ZWFyIG90aGVyIHRoYW4gdGhlIGRpcC4gU2FsYXJ5IGFsc28gaW5jcmVhc2VzIHdpdGggZWR1Y2F0aW9uLg0KDQotLS0NCg0KV2UgY2FuIGFsc28gZml0IEdBTXMgd2l0aCBsb2dpc3RpYyByZWdyZXNzaW9uIG1vZGVsDQoNCmBgYHtyfQ0KZ2FtX2ZpdDI9Z2FtKEkod2FnZT4yNTApfnMoYWdlLGRmPTQpK3MoeWVhcixkZj00KStlZHVjYXRpb24sZGF0YT1XYWdlLGZhbWlseT1iaW5vbWlhbCkNCg0KcGFyKG1mcm93ID0gYygyLDMpKQ0KcGxvdChnYW1fZml0MikNCnBsb3QoZ2FtX2ZpdDIsIHNlID0gVCkNCmBgYA0KDQpUaGlzIGlzIHBsb3R0aW5nIHRoZSBjb250cmlidXRpb24gdG8gdGhlIGxvZ2l0IG9mIHRoZSBwcm9iYWJpbGl0eS4gVGhlIHN0YW5kYXJkIGVycm9ycyBhcmUgYWN0dWFsbHkgdmVyeSB3aWRlLg0KDQotLS0NCg0KVXNpbmcgR0FNLCB3ZSBjYW4gZG8gc29tZSB0ZXN0cy4gTGV0cyBzZWUgaWYgd2UgbmVlZCBhIG5vbmxpbmVhciB0ZXJtcyBmb3IgeWVhci4gU28gd2UgZml0IHRoZSBzYW1lIG1vZGVsIGFzIGJlZm9yZSwgZXhjZXB0IGluc3RlYWQgb2YgaGF2aW5nIGEgc21vb3RoIHRlcm0gd2l0aCB5ZWFyLCB3ZSBoYXZlIGEgbGluZWFyIHRlcm0gd2l0aCB5ZWFyLg0KDQpBTk9WQSB3aXRoIHRlc3QgPSAiQ2hpc3EiIChiZWNhdXNlIGxvZ2lzdGljIHJlZ3Jlc3Npb24pDQpgYGB7cn0NCmdhbV9maXQyX2xpbmVhclllYXI9Z2FtKEkod2FnZT4yNTApfnMoYWdlLGRmPTQpK3llYXIrZWR1Y2F0aW9uLGRhdGE9V2FnZSxmYW1pbHk9Ymlub21pYWwpDQphbm92YShnYW1fZml0Ml9saW5lYXJZZWFyLGdhbV9maXQyLHRlc3Q9IkNoaXNxIikNCmBgYA0KDQpXZSBzZWUgdGhhdCBmcm9tIHRoZSB0ZXN0IHJlc3V0cywgdGhlIG5vbl9saW5lYXIgZnVuY3Rpb24gZm9yIHllYXIgaXMgbm90IG5lZWRlZC4gTGluZWFyIHRlcm0gaXMgZ29vZCBlbm91Z2ghDQoNCi0tLQ0KDQpQbG90dGluZyBpcyBwb3dlcmZ1bCB1c2luZyB0aGUgR0FNIHBhY2thZ2UsIHdoaWNoIGlzIG5vdCByZXN0cmljdGVkIHRvIHBsb3R0aW5nIEdBTSBvYmplY3RzLCBidXQgY2FuIGJlIHVzZWQgZXZlbiB3aXRoIExNIG9yIEdMTSBvYmplY3RzDQoNCmBgYHtyIGZpZy53aWR0aD0xMCwgZmlnLmhlaWdodD01fQ0Kc2FtcGxlX2xtX2ZpdD1sbSh3YWdlfm5zKGFnZSxkZj00KStucyh5ZWFyLGRmPTQpK2VkdWNhdGlvbixkYXRhPVdhZ2UpDQoNCnBhcihtZnJvdz1jKDEsMykpDQpwbG90LkdhbShzYW1wbGVfbG1fZml0LHNlPVQpDQpgYGANCg0KbnMoKSBnaXZlcyBhIGJhc2lzIG9mIG5hdHVyYWwgc3BsaW5lcw0KDQoNCg0KDQogDQo=