Introduction

The key idea in this topic is that not all datapoints in the dataset need have the same information value. Some data rows may contain very precise data, while data in the other rows may be less precise.

We therefore adjust our regression analysis to give more “weight” to those datapoints which are more reliable or informative.

Weighting by Sample Size

The CPS5 dataset is famous in econometrics, referred to often in the book “Introductory Econometrics” by Goldberger.

The dataset has individual-level data on 528 employees EDucation level, whether or not they are in the SOuth of the United States, BL=whether the person was non-white and non-Hispanic 1, otherwise 0, HP= whether or not the person was Hispanic, whether or not the employee was FEmale, years of work EXperience, MS= marital status (1=married, 0 not), whether or not they are members of a UNion, and WG=hourly wage.

Goldberger begins his text by calculating the average WG value for individuals at the various levels of education. (6 years to 18 years of education). Note that most people had 12, 14 or 16 years of education, corresponding to high-school graduates, college graduates and university graduates respectively.

Goldberger then regresses the average WG on ED.

Download CPS5grouped.csv

## CPS5grouped = read.csv("CPS5grouped.csv", header = TRUE)
CPS5grouped
   EDgroup  AvWage  SEMean   StDev   N
1        6  4.4567 0.79805 1.38226   3
2        7  5.7700 0.83690 1.87136   5
3        8  5.9787 0.62779 2.43142  15
4        9  7.3317 1.29904 4.50000  12
5       10  7.3182 0.64444 2.65711  17
6       11  6.5844 0.64084 3.32992  27
7       12  7.8182 0.25206 3.72159 218
8       13  7.8351 0.66765 4.06114  37
9       14 11.0223 0.92157 6.89643  56
10      15 10.6738 1.39739 5.03837  13
11      16 10.8361 0.63981 5.35304  70
12      17 13.6150 1.42417 6.97699  24
13      18 13.5310 1.09328 6.08715  31

ggplot(data = CPS5grouped, mapping = aes(x = EDgroup, y = AvWage)) + geom_point(mapping = aes(size = N)) +
    geom_smooth(method = "lm") + labs(x = "Education (hours)", y = "Average wage ($)")

unlabelled

The data appears to be quite close to the line, which often happens when we have data which have been averaged. The regression suggests we have a high \(R^2 >0.9\).

grouped.lm <- lm(AvWage ~ EDgroup, data = CPS5grouped)
summary(grouped.lm)

Call:
lm(formula = AvWage ~ EDgroup, data = CPS5grouped)

Residuals:
    Min      1Q  Median      3Q     Max 
-1.5637 -0.7350  0.1266  0.7158  1.3198 

Coefficients:
            Estimate Std. Error t value Pr(>|t|)    
(Intercept) -0.01445    0.87462  -0.017    0.987    
EDgroup      0.72410    0.06958  10.406 4.96e-07 ***
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Residual standard error: 0.9387 on 11 degrees of freedom
Multiple R-squared:  0.9078,    Adjusted R-squared:  0.8994 
F-statistic: 108.3 on 1 and 11 DF,  p-value: 4.958e-07

However note that the left-hand points represent only a few individuals compared to some of the right-hand points, represented by the size of the circles scaling with the number of individuals \(N\) contributing to the average. We would like the regression line to be closer to those points that represent far more people.

The line is close to the first category (which has only three people in it) and far from the 12, 14, 16-year categories which have the most people. This doesn’t really make sense.

If we use the ungrouped data the relationship between Wage and Education is much weaker in terms of goodness of fit of the model.

Download CPS5.csv

## CPS5 = read.csv("CPS5.csv", header = TRUE)
head(CPS5)
  ED SO BL HP FE MS EX UN   WG
1 10  0  0  0  0  1 27  0  9.0
2 12  0  0  0  0  1 20  0  5.5
3 12  0  0  0  1  0  4  0  3.8
4 12  0  0  0  1  1 29  0 10.5
5 12  0  0  0  0  1 40  1 15.0
6 16  0  0  0  1  1 27  0  9.0

ggplot(data = CPS5, mapping = aes(x = ED, y = WG)) + geom_point() + geom_smooth(method = "lm") +
    labs(x = "Education (years)", y = "Wage ($)")
`geom_smooth()` using formula = 'y ~ x'

unlabelled

cps5.lm = lm(WG ~ ED, data = CPS5)
summary(cps5.lm)

Call:
lm(formula = WG ~ ED, data = CPS5)

Residuals:
   Min     1Q Median     3Q    Max 
-8.068 -3.163 -0.700  2.289 34.709 

Coefficients:
            Estimate Std. Error t value Pr(>|t|)    
(Intercept) -1.60468    1.10318  -1.455    0.146    
ED           0.81395    0.08281   9.829   <2e-16 ***
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Residual standard error: 4.733 on 526 degrees of freedom
Multiple R-squared:  0.1552,    Adjusted R-squared:  0.1536 
F-statistic:  96.6 on 1 and 526 DF,  p-value: < 2.2e-16

We see there is an outlier, which could be a typo (decimal point in the wrong place?) However the main focus here is that there is a lot more variation among the individual data than there is among the averaged data, so the \(R^2\) is much lower. Also the regression coefficients have different values.

Why Grouped Regression Results Differ

Formulas not examinable but conclusion is important.

To explain the difference between these two regressions, we consider the regression of \(Y\) vs \(X\) (\(n\) data) and \(\bar Y\) versus \(\bar X\) (where the data have been divided into \(m\) groups - a much smaller number than \(n\).) In Least Squares we choose \(\beta_0\) and \(\beta_1\) to minimise
\[SS_{error} = \sum^n_{i=1}(y_i - \beta_0- \beta_1 x_i)^2 ~~~~ (1) \] However the regression for the averages treats all \(m\) groups as being of equal worth: \[ \sum^m_{j=1}(\bar y_j - \beta_0- \beta_1 \bar x_j)^2 ~~~~~~ (2) \]
That is, the computations will try just as hard to fit a value that arises from a small group of data as a value from a large group.

But in fact we don’t have all the \(y_{ij}\) equal. Instead what we do is to add and subtract \(\bar y_j\) inside the parentheses in equation (3), and then expand the square: \[\begin{aligned}SS_{error} &= \sum^m_{j=1}\sum^{n_j}_{i=1}(y_{ij} -\bar y_j + \bar y_j - \beta_0- \beta_1 x_{ij})^2\\&= \sum^m_{j=1}\sum^{n_j}_{i=1}(y_{ij} -\bar y_j)^2 + \sum^m_{j=1}\sum^{n_j}_{i=1}(\bar y_j - \beta_0- \beta_1 \bar x_j)^2 ~~~~~(*)\\&= \sum^m_{j=1}\sum^{n_j}_{i=1}(y_{ij} -\bar y_j)^2 + \sum^m_{j=1} n_j~(\bar y_j - \beta_0- \beta_1 \bar x_j)^2\end{aligned}\]

(*) Equality happens because the cross-product term \(\sum^m_{j=1} \sum^{n_j}_{i=1} ~2~(y_{ij} -\bar y_j) (\bar y_j - \beta_0- \beta_1 \bar x_j) =0\).

The key fact is that the error SS splits into two parts:

\[SS_{error} = \sum^m_{j=1}\sum^{n_j}_{i=1}(y_{ij} -\bar y_j)^2 + \sum^m_{j=1} n_j~(\bar y_j - \beta_0- \beta_1 \bar x_j)^2\] - The first term is the “pure error” SS for \(y_{ij}\) and relates to the variation in the \(y_{ij}\)s around the group means \(\bar y_j\), and

Note that estimation of \(\beta_0\) and \(\beta_1\) depends only on the second term. Therefore the regression coefficients you get from weighted regression should be exactly the same as those from handling the whole data.

However the estimate of goodness of fit (\(R^2\)) would be entirely wrong as the variance is underestimated (missing the first term).

Weighted regression in R

To do a weighted regression we have to specify weights in the lm() command. Below we compare the weighted least squares (WLS) estimates from the averaged data to the ungrouped ordinary least squares (OLS) estimates from the raw data.

cps.wls = lm(AvWage ~ EDgroup, weights = N, data = CPS5grouped)
summary(cps.wls)

Call:
lm(formula = AvWage ~ EDgroup, data = CPS5grouped, weights = N)

Weighted Residuals:
   Min     1Q Median     3Q    Max 
-6.944 -3.971  2.699  4.151  9.217 

Coefficients:
            Estimate Std. Error t value Pr(>|t|)    
(Intercept) -1.60468    1.27442  -1.259    0.234    
EDgroup      0.81395    0.09567   8.508 3.62e-06 ***
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Residual standard error: 5.467 on 11 degrees of freedom
Multiple R-squared:  0.8681,    Adjusted R-squared:  0.8561 
F-statistic: 72.39 on 1 and 11 DF,  p-value: 3.621e-06

summary(cps5.lm)

Call:
lm(formula = WG ~ ED, data = CPS5)

Residuals:
   Min     1Q Median     3Q    Max 
-8.068 -3.163 -0.700  2.289 34.709 

Coefficients:
            Estimate Std. Error t value Pr(>|t|)    
(Intercept) -1.60468    1.10318  -1.455    0.146    
ED           0.81395    0.08281   9.829   <2e-16 ***
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Residual standard error: 4.733 on 526 degrees of freedom
Multiple R-squared:  0.1552,    Adjusted R-squared:  0.1536 
F-statistic:  96.6 on 1 and 526 DF,  p-value: < 2.2e-16

We see that the coefficient estimates are the same, but the standard errors and the \(R^2\) are different.

The following plot shows the averaged data with the unweighted (dashed) and weighted (solid red) lines. The weighted line is closer to where more data are.

ggplot(data = CPS5grouped, mapping = aes(x = AvWage, y = EDgroup)) + geom_point(mapping = aes(size = N)) +
    geom_smooth(method = "lm", mapping = aes(weight = N), col = "red", se = FALSE) +
    geom_smooth(method = "lm", linetype = "dashed", se = FALSE) + labs(x = "Education (years)",
    y = "Average wage ($)")

unlabelled

Residuals for Weighted Regression

Clearly a plot of ordinary residuals could be misleading in the context of weighted least squares. The low weight points on the left are badly fitted and this could make them look like influential outliers.

augment(cps.wls, CPS5grouped) |>
    ggplot(mapping = aes(x = EDgroup, y = .resid)) + geom_point() + geom_hline(yintercept = 0,
    linetype = "dotted") + labs(y = "Simple residuals", x = "Education group (years)")

unlabelled

Instead we use “weighted” or Pearson” Residuals \(r_j = \sqrt{w_j}\varepsilon_j\).

augment(cps.wls, CPS5grouped) |>
    mutate(pearson = .resid * sqrt(N)) |>
    ggplot(mapping = aes(x = EDgroup, y = pearson)) + geom_point() + geom_hline(yintercept = 0,
    linetype = "dotted") + labs(y = "Pearson (weighted) residuals", x = "Education group (years)")

unlabelled

This has given a proper sense of importance to each residual. We see that the low-weight points on the left are not particularly important.

Unfortunately the \(Y\) axis scale is now meaningless so all we can look at is the shape and the relative importance or lack of fit for each point.

Weighting For Non-Constant Variance

Suppose we have data for which the variance is non-constant. So we want to give more weight to those points which have small variance (or are known more precisely) and give less weight to those points that have bigger variance (or are known less precisely).

This still fits with the idea that Weight represents the amount of information carried by each datapoint.

Supervisors and Workers

The following data is on the number of supervisors \(Y\) (e.g. team leaders) and the number of workers \(X\) at a number of industrial establishments. The data are from the book by Chatterjee and Price “Regression Analysis by Example”

Download supervisors.csv

## supervisors = read.csv("supervisors.csv")
supervisors
      X   Y
1   294  30
2   247  32
3   267  37
4   358  44
5   423  47
6   311  49
7   450  56
8   534  62
9   438  68
10  697  78
11  688  80
12  630  84
13  709  88
14  627  97
15  615 100
16  999 109
17 1022 114
18 1015 117
19  700 106
20  850 128
21  980 130
22 1025 160
23 1021  97
24 1200 180
25 1250 112
26 1500 210
27 1650 135

ggplot(supervisors, mapping = aes(x = X, y = Y)) + geom_point() + geom_smooth(method = "lm") +
    labs(x = "Number of workers (X)", y = "Number of supervisors (Y)")

unlabelled

Note that the line is overshooting the cluster of points on the left. The residual plots show curvature and heteroscedasticity. The fourth graph shows a simple linear model is highly influenced by the points at the right, which have very large residuals.

sup.lm <- lm(Y ~ X, data = supervisors)
par(mfrow = c(1, 2))
plot(sup.lm, which = c(1, 5))

unlabelled

Considering the heteroscedasticity, look at the amount of vertical spread of residuals when \(x\)= 400, 800 and 1600. As \(X\) doubles, the spread roughly doubles. This suggests that \(\mbox{sd}(\varepsilon) \propto x\), or in other words \(\sigma = k x\) for some constant \(k\).

Now let us suppose we want to fit a straight-line model \[y_i = \beta_0 + \beta_1 x_i + \varepsilon_i ~ \] We would ordinarily solve this by minimising the \[SS_{error} = \sum( y_i - \beta_0 - \beta_1 x_i)^2\] where the components of the sum (the residuals \(y_i - \beta_0 - \beta_1 x_i\)) have constant standard deviation. Except now the standard deviation is not constant.

But what we could do is divide through by \(x_i\). The model becomes \[\begin{aligned}\frac{y_i}{x_i} &= \frac{\beta_0}{x_i} + \beta_1\frac{x_i}{x_i} + \frac{\varepsilon_i}{x_i}\\Y_i &=\beta_0 X_i + \beta_1 + \eta_i\end{aligned}\] where \(Y_i = \frac{y_i}{x_i}\), \(X_i = \frac{1}{x_i}\) and \(\eta_i = \frac{\varepsilon_i}{x_i}\).

The error term \(\eta_i\) now has constant standard deviation as

\[\mbox{sd}\left( \frac{\varepsilon_i}{x_i}\right) = \frac{ kx_i}{x_i} = k.\]

We now have a linear model with constant variance so can solve by least squares in the usual way.

We could do the transformation ourselves and then just use lm() normally, but another way to look at it is we’re minimising

\[\sum \left(\frac{y_i}{x_i} - \frac{\beta_0}{x_i} - \beta_1 \right)^2 ~~= ~~ \sum \frac{1}{x_i^2}\left(y_i - \beta_0 -\beta_1 x_i \right)^2\]

which is a weighted regression with \(w_i = 1/ x_i^2\). So the task with this transformation is the same as weighted least squares with these weights.

sup.wls = lm(Y ~ X, data = supervisors, weights = 1/X^2)
summary(sup.wls)

Call:
lm(formula = Y ~ X, data = supervisors, weights = 1/X^2)

Weighted Residuals:
      Min        1Q    Median        3Q       Max 
-0.041477 -0.013852 -0.004998  0.024671  0.035427 

Coefficients:
            Estimate Std. Error t value Pr(>|t|)    
(Intercept) 3.803296   4.569745   0.832    0.413    
X           0.120990   0.008999  13.445 6.04e-13 ***
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Residual standard error: 0.02266 on 25 degrees of freedom
Multiple R-squared:  0.8785,    Adjusted R-squared:  0.8737 
F-statistic: 180.8 on 1 and 25 DF,  p-value: 6.044e-13

ggplot(data = supervisors, mapping = aes(x = X, y = Y)) + geom_point() + geom_smooth(method = "lm",
    mapping = aes(weight = 1/X^2), col = "red", se = FALSE) + geom_smooth(method = "lm",
    linetype = "dotted", se = FALSE) + labs(x = "Number of workers (X)", y = "Number of supervisors (Y)")

unlabelled

The plot shows that weighted line (red, solid) is now going closer to the data at the left, where the random error is smallest, whereas the OLS line (dotted) overshot the data at left.

The plot of weighted residuals below shows constant variance (with perhaps a little curvature).

augment(sup.wls) |>
    mutate(pearson = .resid/X) |>
    ggplot(mapping = aes(x = X, y = pearson)) + geom_point() + geom_hline(yintercept = 0,
    linetype = "dotted")

unlabelled

labs(y = "Pearson (weighted) residuals", x = "Number of workers (X)")
$y
[1] "Pearson (weighted) residuals"

$x
[1] "Number of workers (X)"

attr(,"class")
[1] "labels"

Basic rule for Weighting

Assume weights \[w_i = \frac{n_i}{\mbox{variance}_i} ~~~~~ (6) \]

where, if \(n\) doesn’t vary between data rows then just replace by \(n=1\), and if the precision of data (1/variance) does not differ between data rows then just drop that term.

Application: Meta Analysis

You may have heard of the field of meta-analysis, which is concerned with combining the results of many small studies in order to get an overall result.

Meta-analysis uses weights of the form (6) to represent the differing amounts and quality of data from the different studies.

Revisiting the CPS5grouped data

For these data, there was a column of standard deviations (StDev) as well as sample sizes N. So we could use weights \(W_i = N_i/ \mbox{StDev}_i^2\)

CPS5weighted <- CPS5grouped |> mutate(Wt = N/ StDev^2)
cps.wls2 <- lm(AvWage ~ EDgroup, weights=Wt, data=CPS5weighted)
summary(cps.wls2)

Call:
lm(formula = AvWage ~ EDgroup, data = CPS5weighted, weights = Wt)

Weighted Residuals:
    Min      1Q  Median      3Q     Max 
-1.4367  0.1153  0.7399  1.0895  1.7058 

Coefficients:
            Estimate Std. Error t value Pr(>|t|)    
(Intercept)  0.26605    0.93427   0.285    0.781    
EDgroup      0.65602    0.07828   8.381 4.19e-06 ***
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Residual standard error: 1.21 on 11 degrees of freedom
Multiple R-squared:  0.8646,    Adjusted R-squared:  0.8523 
F-statistic: 70.24 on 1 and 11 DF,  p-value: 4.186e-06

ggplot(data = CPS5weighted, mapping = aes(x = EDgroup, y = AvWage)) + geom_point(mapping = aes(size = N)) +
    geom_smooth(method = "lm", mapping = aes(weight = Wt), col = "red", se = FALSE) +
    geom_smooth(method = "lm", mapping = aes(weight = N), linetype = "dotted", se = FALSE) +
    labs(x = "Education group (years)", y = "Average Wage ($)")

unlabelled

The standard deviations are much higher for the highly-educated groups, so that counteracts some of the effect of higher sample sizes.

LS0tDQp0aXRsZTogIkxlY3R1cmUgMzA6IFdlaWdodGVkIHJlZ3Jlc3Npb24iDQpzdWJ0aXRsZTogMTYxLjI1MSBSZWdyZXNzaW9uIE1vZGVsbGluZw0KYXV0aG9yOiAiUHJlc2VudGVkIGJ5IEpvbmF0aGFuIE1hcnNoYWxsIDxKLkMubWFyc2hhbGxAbWFzc2V5LmFjLm56PiIgIA0KZGF0ZTogIldlZWsgMTEgb2YgU2VtZXN0ZXIgMiwgYHIgbHVicmlkYXRlOjp5ZWFyKGx1YnJpZGF0ZTo6bm93KCkpYCINCm91dHB1dDoNCiAgaHRtbF9kb2N1bWVudDoNCiAgICBjb2RlX2Rvd25sb2FkOiB0cnVlDQogICAgdGhlbWU6IHlldGkNCiAgICBoaWdobGlnaHQ6IHRhbmdvDQogIGh0bWxfbm90ZWJvb2s6DQogICAgY29kZV9kb3dubG9hZDogdHJ1ZQ0KICAgIHRoZW1lOiB5ZXRpDQogICAgaGlnaGxpZ2h0OiB0YW5nbw0KICBpb3NsaWRlc19wcmVzZW50YXRpb246DQogICAgd2lkZXNjcmVlbjogdHJ1ZQ0KICAgIHNtYWxsZXI6IHRydWUNCiAgd29yZF9kb2N1bWVudDogZGVmYXVsdA0KICBzbGlkeV9wcmVzZW50YXRpb246IA0KICAgIHRoZW1lOiB5ZXRpDQogICAgaGlnaGxpZ2h0OiB0YW5nbw0KICBwZGZfZG9jdW1lbnQ6IGRlZmF1bHQNCi0tLQ0KDQoNCg0KDQo8IS0tLSBEYXRhIGlzIG9uDQpodHRwczovL3ItcmVzb3VyY2VzLm1hc3NleS5hYy5uei9kYXRhLzE2MTI1MS8NCi0tLT4NCg0KYGBge3Igc2V0dXAsIHB1cmw9RkFMU0UsIGluY2x1ZGU9RkFMU0V9DQpsaWJyYXJ5KGtuaXRyKQ0Kb3B0c19jaHVuayRzZXQoZGV2PWMoInBuZyIsICJwZGYiKSkNCm9wdHNfY2h1bmskc2V0KGZpZy5oZWlnaHQ9NiwgZmlnLndpZHRoPTcsIGZpZy5wYXRoPSJGaWd1cmVzLyIsIGZpZy5hbHQ9InVubGFiZWxsZWQiKQ0Kb3B0c19jaHVuayRzZXQoY29tbWVudD0iIiwgZmlnLmFsaWduPSJjZW50ZXIiLCB0aWR5PVRSVUUpDQpvcHRpb25zKGtuaXRyLmthYmxlLk5BID0gJycpDQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCmxpYnJhcnkoYnJvb20pDQpgYGANCg0KDQo8IS0tLSBEbyBub3QgZWRpdCBhbnl0aGluZyBhYm92ZSB0aGlzIGxpbmUuIC0tLT4NCgojIyBJbnRyb2R1Y3Rpb24KCi0gV2VpZ2h0aW5nIGJ5IHNhbXBsZSBzaXplLgotIEludGVycHJldGF0aW9uIG9mIG91dHB1dC4KLSBXZWlnaHRpbmcgdG8gY291bnRlciBoZXRlcm9zY2VkYXN0aWNpdHkuCgpUaGUga2V5IGlkZWEgaW4gdGhpcyB0b3BpYyBpcyB0aGF0IG5vdCBhbGwgZGF0YXBvaW50cyBpbiB0aGUgZGF0YXNldCBuZWVkIGhhdmUgdGhlIHNhbWUgaW5mb3JtYXRpb24gdmFsdWUuICBTb21lIGRhdGEgcm93cyBtYXkgY29udGFpbiB2ZXJ5IHByZWNpc2UgZGF0YSwgd2hpbGUgZGF0YSBpbiB0aGUgb3RoZXIgcm93cyBtYXkgYmUgbGVzcyBwcmVjaXNlLgoKV2UgdGhlcmVmb3JlIGFkanVzdCBvdXIgcmVncmVzc2lvbiBhbmFseXNpcyB0byBnaXZlIG1vcmUgIndlaWdodCIgdG8gdGhvc2UgZGF0YXBvaW50cyB3aGljaCBhcmUgbW9yZSByZWxpYWJsZSBvciBpbmZvcm1hdGl2ZS4gCgojIyBXZWlnaHRpbmcgYnkgU2FtcGxlIFNpemUgCgpUaGUgQ1BTNSBkYXRhc2V0IGlzIGZhbW91cyBpbiBlY29ub21ldHJpY3MsIHJlZmVycmVkIHRvIG9mdGVuIGluIHRoZSBib29rICJJbnRyb2R1Y3RvcnkgRWNvbm9tZXRyaWNzIiBieSBHb2xkYmVyZ2VyLiAKClRoZSBkYXRhc2V0IGhhcyBpbmRpdmlkdWFsLWxldmVsIGRhdGEgb24gNTI4IGVtcGxveWVlcyBgRURgdWNhdGlvbiBsZXZlbCwgd2hldGhlciBvciBub3QgdGhleSBhcmUgaW4gdGhlIGBTT2B1dGggb2YgdGhlIFVuaXRlZCBTdGF0ZXMsIGBCTGA9d2hldGhlciB0aGUgcGVyc29uIHdhcyBub24td2hpdGUgYW5kIG5vbi1IaXNwYW5pYyAxLCBvdGhlcndpc2UgMCwgYEhQYD0gd2hldGhlciBvciBub3QgdGhlIHBlcnNvbiB3YXMgSGlzcGFuaWMsIHdoZXRoZXIgb3Igbm90IHRoZSBlbXBsb3llZSB3YXMgYEZFYG1hbGUsICB5ZWFycyBvZiB3b3JrIGBFWGBwZXJpZW5jZSwgYE1TYD0gbWFyaXRhbCBzdGF0dXMgKDE9bWFycmllZCwgMCBub3QpLCAgd2hldGhlciBvciBub3QgdGhleSBhcmUgbWVtYmVycyBvZiBhIGBVTmBpb24sIGFuZCBgV0dgPWhvdXJseSB3YWdlLgoKR29sZGJlcmdlciBiZWdpbnMgaGlzIHRleHQgYnkgY2FsY3VsYXRpbmcgdGhlIGF2ZXJhZ2UgYFdHYCB2YWx1ZSBmb3IgaW5kaXZpZHVhbHMgYXQgdGhlIHZhcmlvdXMgbGV2ZWxzIG9mIGVkdWNhdGlvbi4gICg2IHllYXJzIHRvIDE4IHllYXJzIG9mIGVkdWNhdGlvbikuICBOb3RlIHRoYXQgbW9zdCBwZW9wbGUgaGFkIDEyLCAxNCBvciAxNiB5ZWFycyBvZiBlZHVjYXRpb24sIGNvcnJlc3BvbmRpbmcgdG8gaGlnaC1zY2hvb2wgZ3JhZHVhdGVzLCBjb2xsZWdlIGdyYWR1YXRlcyBhbmQgdW5pdmVyc2l0eSBncmFkdWF0ZXMgcmVzcGVjdGl2ZWx5LgoKR29sZGJlcmdlciB0aGVuIHJlZ3Jlc3NlcyB0aGUgYXZlcmFnZSBgV0dgIG9uIGBFRGAuIAoKIyMKCmByIHhmdW46OmVtYmVkX2ZpbGUoIi4uLy4uL2RhdGEvQ1BTNWdyb3VwZWQuY3N2IilgCgpgYGB7ciByZWFkIENQUzVncm91cGVkLCBldmFsPS0xLCBlY2hvPS0yfQpDUFM1Z3JvdXBlZCA9cmVhZC5jc3YoIkNQUzVncm91cGVkLmNzdiIsaGVhZGVyPVRSVUUpCkNQUzVncm91cGVkID1yZWFkLmNzdigiLi4vLi4vZGF0YS9DUFM1Z3JvdXBlZC5jc3YiKQpDUFM1Z3JvdXBlZApgYGAKCiMjCgpgYGB7ciwgbWVzc2FnZT1GQUxTRX0KZ2dwbG90KGRhdGE9Q1BTNWdyb3VwZWQsCiAgICAgICBtYXBwaW5nPWFlcyh4PUVEZ3JvdXAsIHk9QXZXYWdlKSkgKwogIGdlb21fcG9pbnQobWFwcGluZyA9IGFlcyhzaXplID0gTikpICsKICBnZW9tX3Ntb290aChtZXRob2Q9J2xtJykgKwogIGxhYnMoeD0iRWR1Y2F0aW9uIChob3VycykiLCB5ID0gIkF2ZXJhZ2Ugd2FnZSAoJCkiKQpgYGAKCiMjCgpUaGUgZGF0YSBhcHBlYXJzIHRvIGJlIHF1aXRlIGNsb3NlIHRvIHRoZSBsaW5lLCB3aGljaCBvZnRlbiBoYXBwZW5zIHdoZW4gd2UgaGF2ZSBkYXRhIHdoaWNoIGhhdmUgYmVlbiBhdmVyYWdlZC4gVGhlIHJlZ3Jlc3Npb24gc3VnZ2VzdHMgd2UgaGF2ZSBhIGhpZ2ggJFJeMiA+MC45JC4gICAKCmBgYHtyIGdyb3VwZWQgc3VtbWFyeX0KZ3JvdXBlZC5sbSA8LSBsbShBdldhZ2UgfiBFRGdyb3VwLCBkYXRhPUNQUzVncm91cGVkKQpzdW1tYXJ5KGdyb3VwZWQubG0pCmBgYAoKIyMKCkhvd2V2ZXIgbm90ZSB0aGF0IHRoZSBsZWZ0LWhhbmQgcG9pbnRzIHJlcHJlc2VudCBvbmx5IGEgZmV3IGluZGl2aWR1YWxzIGNvbXBhcmVkIHRvIHNvbWUgb2YgdGhlIHJpZ2h0LWhhbmQgcG9pbnRzLCByZXByZXNlbnRlZCBieSB0aGUgc2l6ZSBvZiB0aGUgY2lyY2xlcyBzY2FsaW5nIHdpdGggdGhlIG51bWJlciBvZiBpbmRpdmlkdWFscyAkTiQgY29udHJpYnV0aW5nIHRvIHRoZSBhdmVyYWdlLiBXZSB3b3VsZCBsaWtlIHRoZSByZWdyZXNzaW9uIGxpbmUgdG8gYmUgY2xvc2VyIHRvIHRob3NlIHBvaW50cyB0aGF0IHJlcHJlc2VudCBmYXIgbW9yZSBwZW9wbGUuCgpUaGUgbGluZSBpcyBjbG9zZSB0byB0aGUgZmlyc3QgY2F0ZWdvcnkgKHdoaWNoIGhhcyBvbmx5IHRocmVlIHBlb3BsZSBpbiBpdCkgYW5kIGZhciBmcm9tIHRoZSAxMiwgMTQsIDE2LXllYXIgY2F0ZWdvcmllcyB3aGljaCBoYXZlIHRoZSBtb3N0IHBlb3BsZS4gVGhpcyBkb2Vzbid0IHJlYWxseSBtYWtlIHNlbnNlLiAKCklmIHdlIHVzZSB0aGUgdW5ncm91cGVkIGRhdGEgdGhlIHJlbGF0aW9uc2hpcCBiZXR3ZWVuIFdhZ2UgYW5kIEVkdWNhdGlvbiBpcyBtdWNoIHdlYWtlciBpbiB0ZXJtcyBvZiBnb29kbmVzcyBvZiBmaXQgb2YgdGhlIG1vZGVsLgoKIyMKCmByIHhmdW46OmVtYmVkX2ZpbGUoIi4uLy4uL2RhdGEvQ1BTNS5jc3YiKWAKCmBgYHtyIHJlYWQgQ1BTNSwgZXZhbD0tMSwgZWNobz0tMn0KQ1BTNSA9IHJlYWQuY3N2KCJDUFM1LmNzdiIsIGhlYWRlcj1UUlVFKQpDUFM1ID0gcmVhZC5jc3YoIi4uLy4uL2RhdGEvQ1BTNS5jc3YiLCBoZWFkZXI9VFJVRSkKaGVhZChDUFM1KTsKYGBgCgojIwoKYGBge3J9CmdncGxvdChkYXRhPUNQUzUsCiAgICAgICBtYXBwaW5nPWFlcyh4PUVELCB5PVdHKSkgKwogIGdlb21fcG9pbnQoKSArCiAgZ2VvbV9zbW9vdGgobWV0aG9kPSdsbScpICsKICBsYWJzKHg9IkVkdWNhdGlvbiAoeWVhcnMpIiwgeT0iV2FnZSAoJCkiKQpgYGAKCiMjCgpgYGB7cn0KY3BzNS5sbSAgPSBsbShXRyB+IEVELCBkYXRhPUNQUzUpCnN1bW1hcnkoY3BzNS5sbSkKYGBgCgpXZSBzZWUgdGhlcmUgaXMgYW4gb3V0bGllciwgd2hpY2ggY291bGQgYmUgYSB0eXBvIChkZWNpbWFsIHBvaW50IGluIHRoZSB3cm9uZyBwbGFjZT8pIEhvd2V2ZXIgdGhlIG1haW4gZm9jdXMgaGVyZSBpcyB0aGF0IHRoZXJlIGlzIGEgbG90IG1vcmUgdmFyaWF0aW9uIGFtb25nIHRoZSBpbmRpdmlkdWFsIGRhdGEgdGhhbiB0aGVyZSBpcyBhbW9uZyB0aGUgYXZlcmFnZWQgZGF0YSwgc28gdGhlICRSXjIkIGlzIG11Y2ggbG93ZXIuIEFsc28gdGhlIHJlZ3Jlc3Npb24gY29lZmZpY2llbnRzIGhhdmUgZGlmZmVyZW50IHZhbHVlcy4gCgojIyBXaHkgR3JvdXBlZCBSZWdyZXNzaW9uIFJlc3VsdHMgRGlmZmVyCgoqRm9ybXVsYXMgbm90IGV4YW1pbmFibGUgYnV0IGNvbmNsdXNpb24gaXMgaW1wb3J0YW50LioKClRvIGV4cGxhaW4gdGhlIGRpZmZlcmVuY2UgYmV0d2VlbiB0aGVzZSB0d28gcmVncmVzc2lvbnMsIHdlIGNvbnNpZGVyIHRoZSByZWdyZXNzaW9uIG9mICRZJCB2cyAkWCQgICgkbiQgZGF0YSkgIGFuZCAkXGJhciBZJCB2ZXJzdXMgJFxiYXIgWCQgICAod2hlcmUgdGhlIGRhdGEgaGF2ZSBiZWVuIGRpdmlkZWQgaW50byAgJG0kIGdyb3VwcyAgLSBhIG11Y2ggc21hbGxlciBudW1iZXIgdGhhbiAkbiQuKQpJbiBMZWFzdCBTcXVhcmVzIHdlIGNob29zZSAkXGJldGFfMCQgYW5kICRcYmV0YV8xJCAgdG8gbWluaW1pc2UgICAgICAgICAgCiQkU1Nfe2Vycm9yfSA9ICBcc3VtXm5fe2k9MX0oeV9pIC0gXGJldGFfMC0gXGJldGFfMSB4X2kpXjIgfn5+fiAgICgxKSAgJCQKSG93ZXZlciB0aGUgcmVncmVzc2lvbiBmb3IgdGhlIGF2ZXJhZ2VzIHRyZWF0cyBhbGwgJG0kIGdyb3VwcyBhcyBiZWluZyBvZiBlcXVhbCB3b3J0aDoKICQkIFxzdW1ebV97aj0xfShcYmFyIHlfaiAtIFxiZXRhXzAtIFxiZXRhXzEgXGJhciB4X2opXjIgfn5+fn5+ICAgKDIpICAkJCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIApUaGF0IGlzLCAgdGhlIGNvbXB1dGF0aW9ucyB3aWxsIHRyeSBqdXN0IGFzIGhhcmQgdG8gZml0IGEgdmFsdWUgdGhhdCBhcmlzZXMgZnJvbSBhIHNtYWxsIGdyb3VwIG9mIGRhdGEgYXMgYSAgdmFsdWUgZnJvbSBhIGxhcmdlIGdyb3VwLiAKCiMjIEhvdyBhcmUgdGhlc2UgdHdvIHN1bXMgb2Ygc3F1YXJlcyByZWxhdGVkPyAgCgpJZiB3ZSB0YWtlIHRoZSBzdW0gaW4gKDEpIGFuZCByZS1vcmRlciBpdCBpbnRvIEVkdWNhdGlvbiBsZXZlbCBncm91cHMgd2UgZ2V0CiQkU1Nfe2Vycm9yfSA9ICBcc3VtXm1fe2o9MX1cc3VtXntuX2p9X3tpPTF9KHlfe2lqfSAtIFxiZXRhXzAtIFxiZXRhXzEgeF97aWp9KV4yIH5+fn4gICAoMykgICQkCiAKTm93IHN1cHBvc2UgZm9yIGEgbW9tZW50IHdlIHByZXRlbmQgdGhhdCBhbGwgdGhlICR5X3tpan0kIHZhbHVlcyBhcmUgdGhlIHNhbWUgYXMgdGhlIGdyb3VwIG1lYW4gJFxiYXIgeV9qJCAgIChhbmQgbm90ZSB0aGF0IGFscmVhZHkgJHhfe2lqfSA9IFxiYXIgeF9qJCApLiAgVGhlbiB0aGUgc3VtIG9uIHRoZSByaWdodC1oYW5kIHNpZGUgcmVkdWNlcyB0byAKJCRTU197ZXJyb3J9ID0gIFxzdW1ebV97aj0xfSAgbl9qKFxiYXIgeV97IGp9IC0gXGJldGFfMCAtIFxiZXRhXzEgXGJhciB4X2opXjIgfn5+fiAgICg0KSAgJCQKCgpTbyB0aGUgZGlmZmVyZW5jZSBiZXR3ZWVuIHRoaXMgc3VtIGFuZCB0aGUgc3VtICgyKSBpcyB0aGF0IHRoZSBzcXVhcmVkIHJlc2lkdWFsICBpcyBtdWx0aXBsaWVkIGJ5ICAkbl9qJCwgIHRoZSBudW1iZXIgb2YgcGVvcGxlIGluIHRoYXQgZ3JvdXAuICBXZSBjYWxsIHRoaXMgbXVsdGlwbGllciB0aGUgKip3ZWlnaHQqKiAgJHdfaiQuCgpFc3NlbnRpYWxseSB3ZSBpbnRlcnByZXQgdGhlIHdlaWdodCBhcyB0aGUgKiphbW91bnQgb2YgaW5mb3JtYXRpb24gcmVwcmVzZW50ZWQgYnkgdGhlIGRhdGFwb2ludCoqLiAgU28gaGVyZSBpbiB0aGlzIHNpbXBsZSBleGFtcGxlICJhbW91bnQgb2YgaW5mb3JtYXRpb24iIGlzIGVhc2lseSBtZWFzdXJlZCBhcyB0aGUgc2FtcGxlIHNpemUgJHdfaiA9IG5faiQgYXQgZWFjaCBlZHVjYXRpb24gbGV2ZWwuICAgICAgIAoKIyMKCkJ1dCBpbiBmYWN0IHdlIGRvbid0IGhhdmUgYWxsIHRoZSAkeV97aWp9JCBlcXVhbC4gSW5zdGVhZCB3aGF0IHdlIGRvIGlzIHRvIGFkZCBhbmQgc3VidHJhY3QgJFxiYXIgeV9qJCBpbnNpZGUgdGhlIHBhcmVudGhlc2VzIGluIGVxdWF0aW9uICgzKSwgYW5kIHRoZW4gZXhwYW5kIHRoZSBzcXVhcmU6CiQkXGJlZ2lue2FsaWduZWR9U1Nfe2Vycm9yfSAmPSAgXHN1bV5tX3tqPTF9XHN1bV57bl9qfV97aT0xfSh5X3tpan0gLVxiYXIgeV9qICsgXGJhciB5X2ogLSBcYmV0YV8wLSBcYmV0YV8xIHhfe2lqfSleMlxcJj0gIFxzdW1ebV97aj0xfVxzdW1ee25fan1fe2k9MX0oeV97aWp9IC1cYmFyIHlfaileMiAgKyBcc3VtXm1fe2o9MX1cc3VtXntuX2p9X3tpPTF9KFxiYXIgeV9qIC0gXGJldGFfMC0gXGJldGFfMSBcYmFyIHhfaileMiB+fn5+figqKVxcJj0gIFxzdW1ebV97aj0xfVxzdW1ee25fan1fe2k9MX0oeV97aWp9IC1cYmFyIHlfaileMiAgKyBcc3VtXm1fe2o9MX0gbl9qfihcYmFyIHlfaiAtIFxiZXRhXzAtIFxiZXRhXzEgXGJhciB4X2opXjJcZW5ke2FsaWduZWR9JCQKCigqKSBFcXVhbGl0eSBoYXBwZW5zIGJlY2F1c2UgdGhlIGNyb3NzLXByb2R1Y3QgdGVybSAkXHN1bV5tX3tqPTF9IFxzdW1ee25fan1fe2k9MX0gfjJ+KHlfe2lqfSAtXGJhciB5X2opIChcYmFyIHlfaiAtIFxiZXRhXzAtIFxiZXRhXzEgXGJhciB4X2opID0wJC4gCgojIwoKVGhlIGtleSBmYWN0IGlzIHRoYXQgdGhlIGVycm9yIFNTIHNwbGl0cyBpbnRvIHR3byBwYXJ0czogCgokJFNTX3tlcnJvcn0gPSAgXHN1bV5tX3tqPTF9XHN1bV57bl9qfV97aT0xfSh5X3tpan0gLVxiYXIgeV9qKV4yICArIFxzdW1ebV97aj0xfSBuX2p+KFxiYXIgeV9qIC0gXGJldGFfMC0gXGJldGFfMSBcYmFyIHhfaileMiQkCi0JVGhlIGZpcnN0IHRlcm0gaXMgdGhlICJwdXJlIGVycm9yIiBTUyBmb3IgJHlfe2lqfSQgYW5kIHJlbGF0ZXMgdG8gdGhlIHZhcmlhdGlvbiBpbiB0aGUgJHlfe2lqfSRzIGFyb3VuZCB0aGUgZ3JvdXAgbWVhbnMgJFxiYXIgeV9qJCwgYW5kIAoKLQl0aGUgc2Vjb25kIHRlcm0gaXMgdGhlIHdlaWdodGVkIFNTIHRoYXQgb25lIGdldHMgZnJvbSBwcmV0ZW5kaW5nIGFsbCB0aGUgJHlfe2lqfSRzICAgb2NjdXIgYXQgJFxiYXIgeV9qJCAuCgpOb3RlIHRoYXQgZXN0aW1hdGlvbiBvZiAkXGJldGFfMCQgYW5kICRcYmV0YV8xJCAqZGVwZW5kcyBvbmx5IG9uIHRoZSBzZWNvbmQgdGVybSouICAgVGhlcmVmb3JlICB0aGUgcmVncmVzc2lvbiBjb2VmZmljaWVudHMgeW91IGdldCBmcm9tIHdlaWdodGVkIHJlZ3Jlc3Npb24gc2hvdWxkIGJlICpleGFjdGx5IHRoZSBzYW1lKiBhcyB0aG9zZSBmcm9tIGhhbmRsaW5nIHRoZSB3aG9sZSBkYXRhLgoKSG93ZXZlciB0aGUgZXN0aW1hdGUgb2YgZ29vZG5lc3Mgb2YgZml0ICgkUl4yJCkgd291bGQgYmUgZW50aXJlbHkgd3JvbmcgYXMgdGhlIHZhcmlhbmNlIGlzIHVuZGVyZXN0aW1hdGVkIChtaXNzaW5nIHRoZSBmaXJzdCB0ZXJtKS4KCiMjIFdlaWdodGVkIHJlZ3Jlc3Npb24gaW4gUgoKVG8gZG8gYSB3ZWlnaHRlZCByZWdyZXNzaW9uIHdlIGhhdmUgdG8gc3BlY2lmeSBgd2VpZ2h0c2AgaW4gdGhlIGBsbSgpYCBjb21tYW5kLiAgIEJlbG93IHdlIGNvbXBhcmUgdGhlIHdlaWdodGVkIGxlYXN0IHNxdWFyZXMgKCoqV0xTKiopIGVzdGltYXRlcyBmcm9tIHRoZSBhdmVyYWdlZCBkYXRhIHRvIHRoZSB1bmdyb3VwZWQgb3JkaW5hcnkgbGVhc3Qgc3F1YXJlcyAoKipPTFMqKikgZXN0aW1hdGVzIGZyb20gdGhlIHJhdyBkYXRhLgoKYGBge3Igd2VpZ2h0ZWR9CmNwcy53bHMgPSBsbShBdldhZ2UgfiBFRGdyb3VwLCB3ZWlnaHRzPU4sIGRhdGE9Q1BTNWdyb3VwZWQpCnN1bW1hcnkoY3BzLndscykKYGBgCgojIwoKYGBge3J9CnN1bW1hcnkoY3BzNS5sbSkKYGBgCgpXZSBzZWUgdGhhdCB0aGUgY29lZmZpY2llbnQgZXN0aW1hdGVzIGFyZSB0aGUgc2FtZSwgYnV0IHRoZSBzdGFuZGFyZCBlcnJvcnMgYW5kIHRoZSAkUl4yJCBhcmUgZGlmZmVyZW50LiAKClRoZSBmb2xsb3dpbmcgcGxvdCBzaG93cyB0aGUgYXZlcmFnZWQgZGF0YSB3aXRoIHRoZSB1bndlaWdodGVkIChkYXNoZWQpIGFuZCB3ZWlnaHRlZCAoc29saWQgcmVkKSBsaW5lcy4gVGhlIHdlaWdodGVkIGxpbmUgaXMgY2xvc2VyIHRvIHdoZXJlIG1vcmUgZGF0YSBhcmUuIAoKIyMKCmBgYHtyIFdMUyB2cyBPTFMsIG1lc3NhZ2U9RkFMU0V9CmdncGxvdChkYXRhPUNQUzVncm91cGVkLAogICAgICAgbWFwcGluZz1hZXMoeD1BdldhZ2UsIHk9RURncm91cCkpICsKICBnZW9tX3BvaW50KG1hcHBpbmc9YWVzKHNpemU9TikpICsKICBnZW9tX3Ntb290aChtZXRob2Q9J2xtJywgbWFwcGluZz1hZXMod2VpZ2h0PU4pLCBjb2w9J3JlZCcsIHNlPUZBTFNFKSArCiAgZ2VvbV9zbW9vdGgobWV0aG9kPSdsbScsIGxpbmV0eXBlID0gJ2Rhc2hlZCcgLCBzZT1GQUxTRSkgKwogIGxhYnMoeD0iRWR1Y2F0aW9uICh5ZWFycykiLCB5PSJBdmVyYWdlIHdhZ2UgKCQpIikKYGBgCgojIyBSZXNpZHVhbHMgZm9yIFdlaWdodGVkIFJlZ3Jlc3Npb24KCkNsZWFybHkgYSBwbG90IG9mIG9yZGluYXJ5IHJlc2lkdWFscyBjb3VsZCBiZSBtaXNsZWFkaW5nIGluIHRoZSBjb250ZXh0IG9mIHdlaWdodGVkIGxlYXN0IHNxdWFyZXMuICBUaGUgbG93IHdlaWdodCBwb2ludHMgb24gdGhlIGxlZnQgYXJlIGJhZGx5IGZpdHRlZCBhbmQgdGhpcyBjb3VsZCBtYWtlIHRoZW0gbG9vayBsaWtlIGluZmx1ZW50aWFsIG91dGxpZXJzLiAKIApgYGB7ciByZXNpZHVhbHMgZnJvbSBXTFMsIGZpZy5oZWlnaHQ9NH0KYXVnbWVudChjcHMud2xzLCBDUFM1Z3JvdXBlZCkgfD4KICBnZ3Bsb3QobWFwcGluZyA9IGFlcyh4PUVEZ3JvdXAsIHk9LnJlc2lkKSkgKwogIGdlb21fcG9pbnQoKSArCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0PTAsIGxpbmV0eXBlPSJkb3R0ZWQiKSArCiAgbGFicyh5PSJTaW1wbGUgcmVzaWR1YWxzIiwgeD0iRWR1Y2F0aW9uIGdyb3VwICh5ZWFycykiKQpgYGAKCiMjCgpJbnN0ZWFkIHdlIHVzZSAid2VpZ2h0ZWQiIG9yICBQZWFyc29uIiBSZXNpZHVhbHMgJHJfaiA9IFxzcXJ0e3dfan1cdmFyZXBzaWxvbl9qJC4KIApgYGB7ciB3ZWlnaHRlZCByZXNpZHVhbHMsIGZpZy5oZWlnaHQ9NC41fQphdWdtZW50KGNwcy53bHMsIENQUzVncm91cGVkKSB8PgogIG11dGF0ZShwZWFyc29uID0gLnJlc2lkKnNxcnQoTikpIHw+CiAgZ2dwbG90KG1hcHBpbmcgPSBhZXMoeD1FRGdyb3VwLCB5PXBlYXJzb24pKSArCiAgZ2VvbV9wb2ludCgpICsKICBnZW9tX2hsaW5lKHlpbnRlcmNlcHQ9MCwgbGluZXR5cGU9ImRvdHRlZCIpICsKICBsYWJzKHk9IlBlYXJzb24gKHdlaWdodGVkKSByZXNpZHVhbHMiLCB4PSJFZHVjYXRpb24gZ3JvdXAgKHllYXJzKSIpCmBgYAoKIyMKClRoaXMgaGFzIGdpdmVuIGEgcHJvcGVyIHNlbnNlIG9mIGltcG9ydGFuY2UgdG8gZWFjaCByZXNpZHVhbC4gV2Ugc2VlIHRoYXQgdGhlIGxvdy13ZWlnaHQgcG9pbnRzIG9uIHRoZSBsZWZ0IGFyZSBub3QgcGFydGljdWxhcmx5IGltcG9ydGFudC4gCgpVbmZvcnR1bmF0ZWx5IHRoZSAkWSQgYXhpcyBzY2FsZSBpcyBub3cgbWVhbmluZ2xlc3Mgc28gYWxsIHdlIGNhbiBsb29rIGF0IGlzIHRoZSAqc2hhcGUqIGFuZCB0aGUgKnJlbGF0aXZlKiBpbXBvcnRhbmNlIG9yIGxhY2sgb2YgZml0IGZvciBlYWNoIHBvaW50LiAKCiMjIFdlaWdodGluZyBGb3IgTm9uLUNvbnN0YW50IFZhcmlhbmNlCgpTdXBwb3NlIHdlIGhhdmUgZGF0YSBmb3Igd2hpY2ggdGhlIHZhcmlhbmNlIGlzIG5vbi1jb25zdGFudC4gClNvIHdlIHdhbnQgdG8gZ2l2ZSBtb3JlIHdlaWdodCB0byB0aG9zZSBwb2ludHMgd2hpY2ggaGF2ZSBzbWFsbCB2YXJpYW5jZSAob3IgYXJlIGtub3duIG1vcmUgcHJlY2lzZWx5KSAgYW5kIGdpdmUgbGVzcyB3ZWlnaHQgdG8gdGhvc2UgcG9pbnRzIHRoYXQgaGF2ZSBiaWdnZXIgdmFyaWFuY2UgIChvciBhcmUga25vd24gbGVzcyBwcmVjaXNlbHkpLgoKVGhpcyBzdGlsbCBmaXRzIHdpdGggdGhlIGlkZWEgdGhhdCBXZWlnaHQgcmVwcmVzZW50cyB0aGUgIGFtb3VudCBvZiBpbmZvcm1hdGlvbiBjYXJyaWVkIGJ5IGVhY2ggZGF0YXBvaW50LiAKCiMjIyBTdXBlcnZpc29ycyBhbmQgV29ya2VycwoKVGhlIGZvbGxvd2luZyBkYXRhIGlzIG9uIHRoZSBudW1iZXIgb2Ygc3VwZXJ2aXNvcnMgJFkkIChlLmcuIHRlYW0gbGVhZGVycykgYW5kIHRoZSBudW1iZXIgb2Ygd29ya2VycyAkWCQgYXQgYSBudW1iZXIgb2YgaW5kdXN0cmlhbCBlc3RhYmxpc2htZW50cy4gICBUaGUgZGF0YSBhcmUgZnJvbSB0aGUgYm9vayBieSBDaGF0dGVyamVlIGFuZCBQcmljZSAiUmVncmVzc2lvbiBBbmFseXNpcyBieSBFeGFtcGxlIgoKYHIgeGZ1bjo6ZW1iZWRfZmlsZSgiLi4vLi4vZGF0YS9zdXBlcnZpc29ycy5jc3YiKWAKCmBgYHtyIHJlYWQgU3VwZXJ2aXNvcnMsIGV2YWw9LTEsIGVjaG89LTJ9CnN1cGVydmlzb3JzID0gcmVhZC5jc3YoInN1cGVydmlzb3JzLmNzdiIpCnN1cGVydmlzb3JzID0gcmVhZC5jc3YoIi4uLy4uL2RhdGEvc3VwZXJ2aXNvcnMuY3N2IikKc3VwZXJ2aXNvcnMKYGBgCgojIwoKYGBge3IsIG1lc3NhZ2U9RkFMU0V9CmdncGxvdChzdXBlcnZpc29ycywKICAgICAgIG1hcHBpbmc9YWVzKHg9WCwgeT1ZKSkgKwogIGdlb21fcG9pbnQoKSArCiAgZ2VvbV9zbW9vdGgobWV0aG9kPSdsbScpICsKICBsYWJzKHggPSAiTnVtYmVyIG9mIHdvcmtlcnMgKFgpIiwgeSA9ICJOdW1iZXIgb2Ygc3VwZXJ2aXNvcnMgKFkpIikKYGBgCgojIwoKTm90ZSB0aGF0IHRoZSBsaW5lIGlzIG92ZXJzaG9vdGluZyB0aGUgY2x1c3RlciBvZiBwb2ludHMgb24gdGhlIGxlZnQuICBUaGUgcmVzaWR1YWwgcGxvdHMgc2hvdyBjdXJ2YXR1cmUgYW5kIGhldGVyb3NjZWRhc3RpY2l0eS4gIFRoZSBmb3VydGggZ3JhcGggc2hvd3MgYSBzaW1wbGUgbGluZWFyIG1vZGVsIGlzIGhpZ2hseSBpbmZsdWVuY2VkIGJ5IHRoZSBwb2ludHMgYXQgdGhlIHJpZ2h0LCB3aGljaCBoYXZlIHZlcnkgbGFyZ2UgcmVzaWR1YWxzLgoKYGBge3IgU3VwZXJ2aXNvciByZXNpZHVhbHMsIGZpZy5oZWlnaHQ9NH0Kc3VwLmxtIDwtIGxtKFkgfiBYLCBkYXRhPXN1cGVydmlzb3JzKQpwYXIobWZyb3c9YygxLDIpKQpwbG90KHN1cC5sbSwgd2hpY2g9YygxLDUpKQpgYGAKCiMjCgpDb25zaWRlcmluZyB0aGUgaGV0ZXJvc2NlZGFzdGljaXR5LCBsb29rIGF0IHRoZSBhbW91bnQgb2YgdmVydGljYWwgc3ByZWFkIG9mIHJlc2lkdWFscyB3aGVuICR4JD0gNDAwLCA4MDAgYW5kIDE2MDAuIEFzICRYJCBkb3VibGVzLCB0aGUgc3ByZWFkIHJvdWdobHkgZG91Ymxlcy4gIFRoaXMgc3VnZ2VzdHMgdGhhdCAkXG1ib3h7c2R9KFx2YXJlcHNpbG9uKSBccHJvcHRvIHgkLCAgb3IgaW4gb3RoZXIgd29yZHMgJFxzaWdtYSA9IGsgeCQgZm9yIHNvbWUgY29uc3RhbnQgJGskLgoKTm93IGxldCB1cyBzdXBwb3NlIHdlIHdhbnQgdG8gZml0IGEgc3RyYWlnaHQtbGluZSBtb2RlbCAkJHlfaSA9IFxiZXRhXzAgKyBcYmV0YV8xIHhfaSAgKyBcdmFyZXBzaWxvbl9pIH4gICAkJApXZSB3b3VsZCBvcmRpbmFyaWx5IHNvbHZlIHRoaXMgYnkgbWluaW1pc2luZyB0aGUgCiQkU1Nfe2Vycm9yfSA9IFxzdW0oIHlfaSAtIFxiZXRhXzAgLSBcYmV0YV8xIHhfaSleMiQkIHdoZXJlIHRoZSBjb21wb25lbnRzIG9mIHRoZSBzdW0gKHRoZSByZXNpZHVhbHMgJHlfaSAtIFxiZXRhXzAgLSBcYmV0YV8xIHhfaSQpIGhhdmUgY29uc3RhbnQgc3RhbmRhcmQgZGV2aWF0aW9uLiAgRXhjZXB0IG5vdyB0aGUgc3RhbmRhcmQgZGV2aWF0aW9uIGlzIG5vdCBjb25zdGFudC4gCgpCdXQgd2hhdCB3ZSBjb3VsZCBkbyBpcyBkaXZpZGUgdGhyb3VnaCBieSAkeF9pJC4gIFRoZSBtb2RlbCBiZWNvbWVzCiQkXGJlZ2lue2FsaWduZWR9XGZyYWN7eV9pfXt4X2l9ICY9IFxmcmFje1xiZXRhXzB9e3hfaX0gKyBcYmV0YV8xXGZyYWN7eF9pfXt4X2l9ICArIFxmcmFje1x2YXJlcHNpbG9uX2l9e3hfaX1cXFlfaSAmPVxiZXRhXzAgWF9pICsgXGJldGFfMSArIFxldGFfaVxlbmR7YWxpZ25lZH0kJAp3aGVyZSAkWV9pID0gXGZyYWN7eV9pfXt4X2l9JCwgJFhfaSA9IFxmcmFjezF9e3hfaX0kIGFuZCAkXGV0YV9pID0gXGZyYWN7XHZhcmVwc2lsb25faX17eF9pfSQuCgojIwoKVGhlIGVycm9yIHRlcm0gJFxldGFfaSQgbm93IGhhcyBjb25zdGFudCBzdGFuZGFyZCBkZXZpYXRpb24gYXMKCiQkXG1ib3h7c2R9XGxlZnQoIFxmcmFje1x2YXJlcHNpbG9uX2l9e3hfaX1ccmlnaHQpID0gXGZyYWN7IGt4X2l9e3hfaX0gPSBrLiQkCgpXZSBub3cgaGF2ZSBhIGxpbmVhciBtb2RlbCB3aXRoIGNvbnN0YW50IHZhcmlhbmNlIHNvIGNhbiBzb2x2ZSBieSBsZWFzdCBzcXVhcmVzIGluIHRoZSB1c3VhbCB3YXkuCgpXZSBjb3VsZCBkbyB0aGUgdHJhbnNmb3JtYXRpb24gb3Vyc2VsdmVzIGFuZCB0aGVuIGp1c3QgdXNlIGBsbSgpYCBub3JtYWxseSwgYnV0IGFub3RoZXIgd2F5IHRvIGxvb2sgYXQgaXQgaXMgd2UncmUgbWluaW1pc2luZwoKJCRcc3VtIFxsZWZ0KFxmcmFje3lfaX17eF9pfSAtIFxmcmFje1xiZXRhXzB9e3hfaX0gLSBcYmV0YV8xIFxyaWdodCleMiAgIH5+PSB+fiBcc3VtIFxmcmFjezF9e3hfaV4yfVxsZWZ0KHlfaSAtIFxiZXRhXzAgLVxiZXRhXzEgeF9pIFxyaWdodCleMiQkCgp3aGljaCBpcyBhIHdlaWdodGVkIHJlZ3Jlc3Npb24gd2l0aCAkd19pID0gMS8geF9pXjIkLiAgU28gdGhlIHRhc2sgd2l0aCB0aGlzIHRyYW5zZm9ybWF0aW9uIGlzIHRoZSBzYW1lIGFzIHdlaWdodGVkIGxlYXN0IHNxdWFyZXMgd2l0aCB0aGVzZSB3ZWlnaHRzLgoKIyMKCmBgYHtyIHdscyBmb3Igc3VwZXJ2aXNvcnN9CnN1cC53bHMgPSBsbShZIH4gWCAgLCBkYXRhPXN1cGVydmlzb3JzLCB3ZWlnaHRzID0gMS9YXjIgKQpzdW1tYXJ5KHN1cC53bHMpCmBgYAoKIyMKCmBgYHtyLCBtZXNzYWdlPUZBTFNFfQpnZ3Bsb3QoZGF0YT1zdXBlcnZpc29ycywKICAgICAgIG1hcHBpbmc9YWVzKHg9WCwgeT1ZKSkgKwogIGdlb21fcG9pbnQoKSArCiAgZ2VvbV9zbW9vdGgobWV0aG9kPSdsbScsIG1hcHBpbmc9YWVzKHdlaWdodD0xL1heMiksIGNvbD0ncmVkJywgc2U9RkFMU0UpICsKICBnZW9tX3Ntb290aChtZXRob2Q9J2xtJywgbGluZXR5cGU9J2RvdHRlZCcsIHNlPUZBTFNFKSArCiAgbGFicyh4PSJOdW1iZXIgb2Ygd29ya2VycyAoWCkiLCB5ID0gIk51bWJlciBvZiBzdXBlcnZpc29ycyAoWSkiKQpgYGAKCiMjCgpUaGUgcGxvdCBzaG93cyB0aGF0IHdlaWdodGVkIGxpbmUgKHJlZCwgc29saWQpIGlzIG5vdyBnb2luZyBjbG9zZXIgdG8gdGhlIGRhdGEgYXQgdGhlIGxlZnQsIHdoZXJlIHRoZSByYW5kb20gZXJyb3IgaXMgc21hbGxlc3QsIHdoZXJlYXMgdGhlIE9MUyBsaW5lIChkb3R0ZWQpIG92ZXJzaG90IHRoZSBkYXRhIGF0IGxlZnQuCgpUaGUgcGxvdCBvZiB3ZWlnaHRlZCByZXNpZHVhbHMgYmVsb3cgc2hvd3MgY29uc3RhbnQgdmFyaWFuY2UgKHdpdGggcGVyaGFwcyBhIGxpdHRsZSBjdXJ2YXR1cmUpLiAKCmBgYHtyIFd0IFJlcyBmb3IgU3VwLndscywgZmlnLmhlaWdodD00fQphdWdtZW50KHN1cC53bHMpIHw+CiAgbXV0YXRlKHBlYXJzb24gPSAucmVzaWQvWCkgfD4KICBnZ3Bsb3QobWFwcGluZyA9IGFlcyh4PVgsIHk9cGVhcnNvbikpICsKICBnZW9tX3BvaW50KCkgKwogIGdlb21faGxpbmUoeWludGVyY2VwdD0wLCBsaW5ldHlwZT0iZG90dGVkIikKICBsYWJzKHk9IlBlYXJzb24gKHdlaWdodGVkKSByZXNpZHVhbHMiLCB4PSJOdW1iZXIgb2Ygd29ya2VycyAoWCkiKQpgYGAKCiMjIEJhc2ljIHJ1bGUgZm9yIFdlaWdodGluZwoKQXNzdW1lIHdlaWdodHMgICQkd19pID0gXGZyYWN7bl9pfXtcbWJveHt2YXJpYW5jZX1faX0gfn5+fn4gKDYpICQkCgp3aGVyZSwgaWYgJG4kIGRvZXNuJ3QgdmFyeSBiZXR3ZWVuIGRhdGEgcm93cyB0aGVuIGp1c3QgcmVwbGFjZSBieSAkbj0xJCwgIGFuZCBpZiB0aGUgcHJlY2lzaW9uIG9mIGRhdGEgKDEvdmFyaWFuY2UpIGRvZXMgbm90IGRpZmZlciBiZXR3ZWVuIGRhdGEgcm93cyB0aGVuIGp1c3QgZHJvcCB0aGF0IHRlcm0uCgojIyMgQXBwbGljYXRpb246IE1ldGEgQW5hbHlzaXMKCllvdSBtYXkgaGF2ZSBoZWFyZCBvZiB0aGUgIGZpZWxkIG9mICoqbWV0YS1hbmFseXNpcyoqLCB3aGljaCAgaXMgY29uY2VybmVkIHdpdGggY29tYmluaW5nIHRoZSByZXN1bHRzIG9mIG1hbnkgc21hbGwgc3R1ZGllcyBpbiBvcmRlciB0byBnZXQgYW4gb3ZlcmFsbCByZXN1bHQuICAKCk1ldGEtYW5hbHlzaXMgdXNlcyB3ZWlnaHRzIG9mIHRoZSBmb3JtICg2KSB0byByZXByZXNlbnQgdGhlIGRpZmZlcmluZyBhbW91bnRzIGFuZCBxdWFsaXR5IG9mIGRhdGEgZnJvbSB0aGUgZGlmZmVyZW50IHN0dWRpZXMuCgojIyBSZXZpc2l0aW5nIHRoZSBDUFM1Z3JvdXBlZCBkYXRhCgpGb3IgdGhlc2UgZGF0YSwgdGhlcmUgd2FzIGEgY29sdW1uIG9mICBzdGFuZGFyZCBkZXZpYXRpb25zIChgU3REZXZgKSBhcyB3ZWxsIGFzIHNhbXBsZSBzaXplcyBgTmAuICBTbyB3ZSBjb3VsZCB1c2Ugd2VpZ2h0cyAkV19pID0gTl9pLyBcbWJveHtTdERldn1faV4yJAoKYGBge3IgQ1BTNSByZS13ZWlnaHRlZCwgdGlkeT1GQUxTRX0KQ1BTNXdlaWdodGVkIDwtIENQUzVncm91cGVkIHw+IG11dGF0ZShXdCA9IE4vIFN0RGV2XjIpCmNwcy53bHMyIDwtIGxtKEF2V2FnZSB+IEVEZ3JvdXAsIHdlaWdodHM9V3QsIGRhdGE9Q1BTNXdlaWdodGVkKQpzdW1tYXJ5KGNwcy53bHMyKQpgYGAKCiMjCgpgYGB7ciwgbWVzc2FnZT1GQUxTRSwgZmlnLmhlaWdodD00fQpnZ3Bsb3QoZGF0YT1DUFM1d2VpZ2h0ZWQsCiAgICAgICBtYXBwaW5nPWFlcyh4PUVEZ3JvdXAsIHk9QXZXYWdlKSkgKwogIGdlb21fcG9pbnQobWFwcGluZz1hZXMoc2l6ZT1OKSkgKwogIGdlb21fc21vb3RoKG1ldGhvZD0nbG0nLCBtYXBwaW5nPWFlcyh3ZWlnaHQ9V3QpLCBjb2w9J3JlZCcsIHNlPUZBTFNFKSArCiAgZ2VvbV9zbW9vdGgobWV0aG9kPSdsbScsIG1hcHBpbmc9YWVzKHdlaWdodD1OKSwgbGluZXR5cGU9J2RvdHRlZCcsIHNlPUZBTFNFKSArCiAgbGFicyh4PSJFZHVjYXRpb24gZ3JvdXAgKHllYXJzKSIsIHk9IkF2ZXJhZ2UgV2FnZSAoJCkiKQpgYGAKClRoZSBzdGFuZGFyZCBkZXZpYXRpb25zIGFyZSBtdWNoIGhpZ2hlciBmb3IgdGhlIGhpZ2hseS1lZHVjYXRlZCBncm91cHMsIHNvIHRoYXQgY291bnRlcmFjdHMgc29tZSBvZiB0aGUgZWZmZWN0IG9mIGhpZ2hlciBzYW1wbGUgc2l6ZXMuCg==