Goals:

  • Illustrate (conditional) independence in general and for causal inference with binary treatments in particular


Acknowledgements: I thank Henri Pfleiderer for his assistance in preparing this notebook.


Formal definition of (conditional) independence

Discrete Variables

Independence of two discrete variables \(Y\) and \(W\):

\[ Y \perp\!\!\!\!\perp W \iff P(Y=y, W=w) = P(Y = y) \cdot P(W = w)\]

This implies by Bayes’ law:

\[ P(Y=y \mid W=w) = \frac{P(Y=y, W=w)}{P(W=w)} = \frac{P(Y = y) \cdot P(W = w)}{P(W=w)} = P(Y = y) \]


Continuous Variables

Equivalently, two continuous variables \(Y\) and \(W\) being independent means:

\[ Y \perp\!\!\!\!\perp W \iff f_{Y,W}(y,w) = f_Y(y) \cdot f_W(w) \] where \(f\) denotes probability density functions.


Mixed Case

In addition to these two cases there is a mixed case. Consider for example a continuous \(Y\) and a discrete \(W\). This is a very common setup in causal inference. We may think of \(Y\) as a continuous outcome and \(W\) a binary treatment indicator. In this case:

\[ Y \perp\!\!\!\!\perp W \iff f_{Y,W}(y,w) = f_Y(y) \cdot P(W=w) \]

Using the definition of the conditional density or probability respectively, this implies:

\[ f_{Y \mid W}(y \mid w) = \frac{f_{Y,W}(y,w)}{P(W=w)} = \frac{f_Y(y) \cdot P(W=w)}{P(W=w)} =f_Y(y) \] and

\[ P(W = w \mid Y = y) = \frac{f_{Y,W}(y,w)}{f_Y(y)} = \frac{f_Y(y) \cdot P(W=w)}{f_Y(y)} = P(W =w) \]



Conditional Independence

For compactness, we focus on the mixed case here. We have three random variables, a continuous \(Y\) and binary \(X\) and \(W\). We say that \(Y\) and \(W\) are independent conditional on \(X\) iff the following holds:

\[ Y \perp\!\!\!\!\perp W \mid X \iff f_{Y,W \mid X}(y, w \mid x) = f_{Y \mid X}(y \mid x) \cdot P(W = w \mid X = x) \]

This also implies for all \(w\) with \(P(W = w \mid X = x)\) that

\[f_{Y\mid W, X}(y \mid w,x) = \frac{f_{Y,W \mid X}(y,w \mid x)}{P(W = w \mid X = x)} = \frac{ f_{Y \mid X}(y \mid x) \cdot P(W = w \mid X = x)}{P(W = w \mid X = x)} = f_{Y \mid X} (y \mid x)\] i.e., the conditioning on \(W\) doesn’t matter once we condition on \(X\). As the (unconditional) independence above, this works similarly for the cases of only continuous and only discrete variables using only densities or probabilities, respectively.


Conditional independence implies conditional mean independence

Conditional independence of \(Y\) and \(W\) given \(X\) implies mean independence, meaning: \(E[Y \mid X,W] = E[Y \mid X]\):

\[ E[Y \mid W,X] = \int_{-\infty}^{+\infty} y \underbrace{f_{Y \mid W,X}(y\mid w,x)}_{=f_{Y \mid X} (y \mid x), \ cond. \ indep.} dy = \int_{-\infty}^{+\infty} y \ \ f_{Y \mid X} (y \mid x) \ dy = E[Y \mid X] \]

All of this is illustrated in the following examples.


General Example

Let’s consider the following DGP:

\[\begin{align} Y= X + U \ \ \ with \ \ \ U \sim \mathcal{N}(0,0.5) \\ X = \mathbf{1}(V\geq 0) \\ W = \mathbf{1}(Z\geq 0) \\ [V, Z]'\sim \mathcal{N}\left([0~~0]',\left[ \begin{array} {rrr} 1 & \rho \\ \rho & 1 \end{array}\right]\right) \end{align}\]

First, consider the case where \(Y\) and \(W\) are independent (i.e. \(\rho =0\)). This is illustrated by drawing a large sample from the described DGP with \(\rho = 0\):

if (!require("tidyverse")) install.packages("tidyverse", dependencies = TRUE); library(tidyverse)
if (!require("ggridges")) install.packages("ggridges", dependencies = TRUE); library(ggridges)
if (!require("MASS")) install.packages("MASS", dependencies = TRUE); library(MASS)

set.seed(1234)
n = 1000000
mu = c(0,0)
rho = 0
sigma = matrix(c(1,rho,rho,1), nrow = 2)
draw = mvrnorm(n, mu, sigma)
X = 1*(draw[,1]>0)
W = 1*(draw[,2] > 0)
Y = X + rnorm(n, sd = 0.5)

Let’s plot the distributions of \(Y\) for \(W=0\) and \(W=1\):

tibble(Y,W,X) %>% mutate(W = as.factor(W)) %>% 
  ggplot(aes(x = Y, y = fct_rev(W), fill = W))+
  geom_density_ridges(alpha = 0.6)+
  xlab("Y") + ylab("Density")

The distribution of \(Y\) is the same when conditioning on different values of \(W\). Consequently, \(Y\) and \(W\) are independent. This is a feature of the (conditional) distribution of \(Y\) and thus also holds for the (conditional) expected value:

\[ E[Y \mid W=1] = E[Y \mid W=0] = E[Y] = 0.5 \]

mean(Y)
[1] 0.5002274
mean(Y[W==1])
[1] 0.49979
mean(Y[W==0])
[1] 0.5006651

Now, let’s introduce some correlation between the variables \(V\) and \(Z\) by setting \(\rho = 0.6\). This creates dependence between \(Y\) and \(W\):

set.seed(1234)
n = 1000000
mu = c(0,0)
rho = 0.6
sigma = matrix(c(1,rho,rho,1), nrow = 2)
draw = mvrnorm(n, mu, sigma)
X =  1*(draw[,1]>0) 
W = 1*(draw[,2] > 0)
Y = X + rnorm(n, sd = 0.5)
tibble(Y,W,X) %>% mutate(W = as.factor(W)) %>% 
  ggplot(aes(x = Y, y = fct_rev(W), fill = W))+
  geom_density_ridges(alpha = 0.6)+
  xlab("Y")

The distributions are not the same anymore, \(Y\) is not independent of \(W\).

It is however, once we condition on \(X\). In the present context, this can be seen by looking at the distribution of \(Y\) in the 4 different subgroups formed by the 2 binary variables \(W\) and \(X\).

tibble(Y,W,X) %>%  mutate(W = as.factor(W), X = as.factor(X), Subgroup = interaction(W,X)) %>%
  ggplot(aes(x = Y, y = fct_rev(Subgroup), fill = Subgroup))+
  geom_density_ridges(alpha = 0.6)+
  scale_fill_discrete(labels = c("W=0, X=0","W=1, X=0","W=0, X=1","W=1, X=1"))+
  xlab("Y") + ylab("Density")

Once we condition on \(X\), i.e. look at the distribution of \(Y\) in the 2 subgroups with \(X=0\) and \(X=1\), the distributions don’t change when conditioning on different values of \(W\). \(Y\) and \(W\) are independent conditional on \(X\): \(Y \perp\!\!\!\!\perp W \mid X\)

In this case, \(E[Y \mid W=1] \neq E[Y \mid W=0] \neq E[Y]\):

mean(Y)
[1] 0.4995484
mean(Y[W==1])
[1] 0.7044881
mean(Y[W==0])
[1] 0.2947923

Conditional on X, we can see that: \(E[Y \mid W= 0, X=x] = E[Y \mid W= 1, X=x] = E[Y \mid X=x]\) for \(x \in \{0,1\}\):

tibble(Y,W,X) %>% group_by(X,W) %>% summarise(mean_Y = mean(Y))
tibble(Y,W,X) %>% group_by(X) %>% summarise(mean_Y = mean(Y))


Example: Potential Outcomes and causal inference

Let’s look at the importance of the above concept in a causal inference context. \(Y\) denotes the outcome, \(W\) the treatment and \(X\) some confounding variable. \(Y(1)\) and \(Y(0)\) are the potential outcomes in the treated and untreated case respectively, so \(Y = W \cdot Y(1) + (1-W) \cdot Y(0)\).

set.seed(1234)
n = 1000000
mu = c(0,0)
rho = 0
sigma = matrix(c(1,rho,rho,1), nrow = 2)
draw = mvrnorm(n, mu, sigma)
X =  1*(draw[,1] > 0)
W = 1*(draw[,2] > 0)
Y0 = X + rnorm(n, sd = 0.5)
Y1 = 0.5 + X + rnorm(n, sd = 0.5)
Y = W*Y1 + (1-W)*Y0

Consider the following DGP:

  • \(Y(0) = X + U_0\) with \(U_0 \sim \mathcal{N}(0,0.5)\).

  • \(Y(1) = 0.5 + X + U_1\) with \(U_1 \sim \mathcal{N}(0,0.5)\).

and \[\begin{align} X = \mathbf{1}(V\geq 0) \\ W = \mathbf{1}(Z\geq 0) \\ [V, Z]'\sim \mathcal{N}\left([0~~0]',\left[ \begin{array} {rrr} 1 & \rho \\ \rho & 1 \end{array}\right]\right) \end{align}\]

First, consider again the case with \(\rho = 0\) to illustrate independence in the sense that \(Y(w) \perp\!\!\!\!\perp W\).

To see that this holds in the first example, plot the distributions of \(Y(1)\) for the treated and the untreated separately. As we have independence, they should look identical:

tibble(Y1,Y0,Y,W,X) %>% mutate(W = as.factor(W)) %>% 
  ggplot(aes(x = Y1, y = fct_rev(W), fill = W))+
  geom_density_ridges(alpha = 0.6)+
  xlab("Y(1)")+ ylab("Density")

Same for \(Y(0)\):

tibble(Y1,Y0,Y,W,X) %>% mutate(W = as.factor(W)) %>% 
  ggplot(aes(x = Y0, y = fct_rev(W), fill = W))+
  geom_density_ridges(alpha=.6)+
  xlab("Y(0)")+ ylab("Density")

These graphs illustrate that the potential outcomes are independent of the treatment indicator.

The individual treatment effects are given as: \(Y(1) - Y(0) = 0.5 + X + U_1 - X - U_0 = 0.5 + U_1 - U_0\). The ATE is \(E[Y(1)-Y(0)] = 0.5\).

As the potential outcomes are independent of \(W\), we can estimate the ATE using a simple mean comparison between the treated and the untreated group:

TE_mean_comparison = mean(Y[W==1]) - mean(Y[W==0])
print(TE_mean_comparison)
[1] 0.4984061


As above, let’s introduce some correlation between \(V\) and \(Z\) by setting $= 0.6. Now, \(X\) and \(W\) are not independent:

rho = 0.6
sigma = matrix(c(1,rho,rho,1), nrow = 2)
draw = mvrnorm(n, mu, sigma)
X =  1*(draw[,1] > 0)
W = 1*(draw[,2] > 0)
Y0 = X + rnorm(n, sd = 0.5)
Y1 = 0.5 + X + rnorm(n, sd = 0.5)
Y = W*Y1 + (1-W)*Y0

Intuitively, observations with a high \(X\) (and thus a high \(Y\)) are now more likely to be treated, as \(V\) and \(Z\), the two variables determining \(X\) and \(W\), are positively correlated. Comparing means between treated and untreated will give an incorrect, too large, estimate of the ATE. In a first step, let’s again illustrate the lack of independence between the potential outcomes and \(W\):

tibble(Y1,Y0,Y,W,X) %>% mutate(W = as.factor(W)) %>% 
  ggplot(aes(x = Y1, y = fct_rev(W), fill = W))+
  geom_density_ridges(alpha=.6)+
  xlab("Y(1)")+ylab("Density")

Same for \(Y(0)\):

tibble(Y1,Y0,Y,W,X) %>% mutate(W = as.factor(W)) %>% 
  ggplot(aes(x = Y0, y = fct_rev(W), fill = W))+
  geom_density_ridges(alpha=.6)+
  xlab("Y(0)")+ylab("Density")

The potential outcomes are not independent of \(W\). Estimate the ATE by mean comparison:

TE_mean_comparison = mean(Y[W==1]) - mean(Y[W==0])
print(TE_mean_comparison)
[1] 0.9111342

Let’s condition on \(X\) and see whether they are independent. For this purpose, look at the distributions of \(Y(w)\) for the different subgroups formed by \(W\) and \(X\):

tibble(Y,Y1,Y0,W,X) %>%  mutate(W = as.factor(W), X = as.factor(X), Subgroup = interaction(W,X)) %>% 
  ggplot(aes(x = Y1, y = fct_rev(Subgroup), fill = Subgroup))+
  geom_density_ridges(alpha = 0.6)+
  scale_fill_discrete(labels = c("W=0, X=0","W=1, X=0","W=0, X=1","W=1, X=1"))+
  xlab("Y(1)")+ylab("Density")

tibble(Y,Y1,Y0,W,X) %>%  mutate(W = as.factor(W), X = as.factor(X), Subgroup = interaction(W,X)) %>% 
  ggplot(aes(x = Y0, y = fct_rev(Subgroup), fill = Subgroup))+
  geom_density_ridges(alpha = 0.7)+
  scale_fill_discrete(labels = c("W=0, X=0","W=1, X=0","W=0, X=1","W=1, X=1"))+
  xlab("Y(0)")+ylab("Density")

When we look at conditional distributions of the potential outcomes, they are the same for treated and untreated. This shows that conditional on \(X\), the potential outcomes are independent of \(W\). What does this imply for the estimation of the ATE?

We can estimate the ATE by comparing conditional means:

TE_cond_mean_comparison_1 = mean(Y[W==1 & X==1]) - mean(Y[W==0 & X==1])
print(TE_cond_mean_comparison_1)
[1] 0.5019498
TE_cond_mean_comparison_0 = mean(Y[W==1 & X==0]) - mean(Y[W==0 & X==0])
print(TE_cond_mean_comparison_0)
[1] 0.4993736
TE_cond_mean_comparison = mean(X)*TE_cond_mean_comparison_1 + mean(1-X)*TE_cond_mean_comparison_0
print(TE_cond_mean_comparison)
[1] 0.5006614

Alternatively, use a regression model with \(X\) as a control variable:

summary(lm(Y~ W + X))

Call:
lm(formula = Y ~ W + X)

Residuals:
     Min       1Q   Median       3Q      Max 
-2.40876 -0.33723 -0.00068  0.33697  2.46799 

Coefficients:
             Estimate Std. Error t value Pr(>|t|)    
(Intercept) -0.001105   0.000778  -1.421    0.155    
W            0.500661   0.001097 456.274   <2e-16 ***
X            1.000412   0.001097 911.718   <2e-16 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 0.5003 on 999997 degrees of freedom
Multiple R-squared:  0.6241,    Adjusted R-squared:  0.6241 
F-statistic: 8.301e+05 on 2 and 999997 DF,  p-value: < 2.2e-16

This recovers the true ATE. Note, that here the true structural model is actually linear and OLS can be used to recover the true coefficients on \(X\) and \(W\):

\[ Y = W\cdot (0.5 + X + U_{1}) + (1-W) \cdot (X + U_{0}) = 0.5W + X +\underbrace{WU_{1} + (1-W)U_{0}}_{=U} = 0.5W + X + U\]

LS0tDQp0aXRsZTogIihDb25kaXRpb25hbCkgSW5kZXBlbmRlbmNlIg0Kc3VidGl0bGU6ICJTaW11bGF0aW9uIG5vdGVib29rIg0KYXV0aG9yOiAiTWljaGFlbCBLbmF1cyINCmRhdGU6ICJgciBmb3JtYXQoU3lzLnRpbWUoKSwgJyVtLyV5JylgIg0Kb3V0cHV0OiANCiAgaHRtbF9ub3RlYm9vazoNCiAgICB0b2M6IHRydWUNCiAgICB0b2NfZmxvYXQ6IHRydWUNCiAgICBjb2RlX2ZvbGRpbmc6IHNob3cNCi0tLQ0KDQo8YnI+DQoNCg0KR29hbHM6DQoNCi0gSWxsdXN0cmF0ZSAoY29uZGl0aW9uYWwpIGluZGVwZW5kZW5jZSBpbiBnZW5lcmFsIGFuZCBmb3IgY2F1c2FsIGluZmVyZW5jZSB3aXRoIGJpbmFyeSB0cmVhdG1lbnRzIGluIHBhcnRpY3VsYXINCg0KPGJyPg0KDQoqQWNrbm93bGVkZ2VtZW50czogSSB0aGFuayBIZW5yaSBQZmxlaWRlcmVyIGZvciBoaXMgYXNzaXN0YW5jZSBpbiBwcmVwYXJpbmcgdGhpcyBub3RlYm9vay4qDQoNCjxicj4NCg0KIyMgRm9ybWFsIGRlZmluaXRpb24gb2YgKGNvbmRpdGlvbmFsKSBpbmRlcGVuZGVuY2UNCiMjIyBEaXNjcmV0ZSBWYXJpYWJsZXMNCg0KSW5kZXBlbmRlbmNlIG9mIHR3byBkaXNjcmV0ZSB2YXJpYWJsZXMgJFkkIGFuZCAkVyQ6IA0KDQokJCBZIFxwZXJwXCFcIVwhXCFccGVycCBXIFxpZmYgUChZPXksIFc9dykgPSBQKFkgPSB5KSBcY2RvdCBQKFcgPSB3KSQkDQoNClRoaXMgaW1wbGllcyBieSBCYXllcycgbGF3Og0KDQokJCBQKFk9eSBcbWlkIFc9dykgPSAgXGZyYWN7UChZPXksIFc9dyl9e1AoVz13KX0gPSBcZnJhY3tQKFkgPSB5KSBcY2RvdCBQKFcgPSB3KX17UChXPXcpfSA9IFAoWSA9IHkpICQkDQoNCjxicj4NCg0KIyMjIENvbnRpbnVvdXMgVmFyaWFibGVzDQoNCkVxdWl2YWxlbnRseSwgdHdvIGNvbnRpbnVvdXMgdmFyaWFibGVzICRZJCBhbmQgJFckIGJlaW5nIGluZGVwZW5kZW50IG1lYW5zOg0KDQokJCBZIFxwZXJwXCFcIVwhXCFccGVycCBXICBcaWZmIGZfe1ksV30oeSx3KSA9IGZfWSh5KSBcY2RvdCBmX1codykgJCQNCndoZXJlICRmJCBkZW5vdGVzIHByb2JhYmlsaXR5IGRlbnNpdHkgZnVuY3Rpb25zLg0KDQo8YnI+DQoNCiMjIyBNaXhlZCBDYXNlDQoNCkluIGFkZGl0aW9uIHRvIHRoZXNlIHR3byBjYXNlcyB0aGVyZSBpcyBhIG1peGVkIGNhc2UuIENvbnNpZGVyIGZvciBleGFtcGxlIGEgY29udGludW91cyAkWSQgYW5kIGEgZGlzY3JldGUgJFckLiBUaGlzIGlzIGEgdmVyeSBjb21tb24gc2V0dXAgaW4gY2F1c2FsIGluZmVyZW5jZS4gV2UgbWF5IHRoaW5rIG9mICRZJCBhcyBhIGNvbnRpbnVvdXMgb3V0Y29tZSBhbmQgJFckIGEgYmluYXJ5IHRyZWF0bWVudCBpbmRpY2F0b3IuIEluIHRoaXMgY2FzZToNCg0KJCQgWSBccGVycFwhXCFcIVwhXHBlcnAgVyAgXGlmZiBmX3tZLFd9KHksdykgPSBmX1koeSkgXGNkb3QgUChXPXcpICQkDQoNClVzaW5nIHRoZSBkZWZpbml0aW9uIG9mIHRoZSBjb25kaXRpb25hbCBkZW5zaXR5IG9yIHByb2JhYmlsaXR5IHJlc3BlY3RpdmVseSwgdGhpcyBpbXBsaWVzOg0KDQokJCBmX3tZIFxtaWQgV30oeSBcbWlkIHcpID0gXGZyYWN7Zl97WSxXfSh5LHcpfXtQKFc9dyl9ID0gXGZyYWN7Zl9ZKHkpIFxjZG90IFAoVz13KX17UChXPXcpfSA9Zl9ZKHkpICAkJA0KYW5kIA0KDQokJCAgUChXID0gdyBcbWlkIFkgPSB5KSA9IFxmcmFje2Zfe1ksV30oeSx3KX17Zl9ZKHkpfSA9IFxmcmFje2ZfWSh5KSBcY2RvdCBQKFc9dyl9e2ZfWSh5KX0gPSBQKFcgPXcpICQkDQoNCjxicj4NCjxicj4NCg0KIyMjIENvbmRpdGlvbmFsIEluZGVwZW5kZW5jZQ0KDQpGb3IgY29tcGFjdG5lc3MsIHdlIGZvY3VzIG9uIHRoZSBtaXhlZCBjYXNlIGhlcmUuIFdlIGhhdmUgdGhyZWUgcmFuZG9tIHZhcmlhYmxlcywgYSBjb250aW51b3VzICRZJCBhbmQgYmluYXJ5ICRYJCBhbmQgJFckLiBXZSBzYXkgdGhhdCAkWSQgYW5kICRXJCBhcmUgaW5kZXBlbmRlbnQgY29uZGl0aW9uYWwgb24gJFgkIGlmZiB0aGUgZm9sbG93aW5nIGhvbGRzOg0KDQokJCBZIFxwZXJwXCFcIVwhXCFccGVycCBXIFxtaWQgWCBcaWZmIGZfe1ksVyBcbWlkIFh9KHksIHcgXG1pZCB4KSA9IGZfe1kgXG1pZCBYfSh5IFxtaWQgeCkgXGNkb3QgUChXID0gdyBcbWlkIFggPSB4KQ0KJCQNCg0KVGhpcyBhbHNvIGltcGxpZXMgZm9yIGFsbCAkdyQgd2l0aCAkUChXID0gdyBcbWlkIFggPSB4KSQgdGhhdA0KDQokJGZfe1lcbWlkIFcsIFh9KHkgXG1pZCB3LHgpID0gXGZyYWN7Zl97WSxXIFxtaWQgWH0oeSx3IFxtaWQgeCl9e1AoVyA9IHcgXG1pZCBYID0geCl9ID0gXGZyYWN7IGZfe1kgXG1pZCBYfSh5IFxtaWQgeCkgXGNkb3QgUChXID0gdyBcbWlkIFggPSB4KX17UChXID0gdyBcbWlkIFggPSB4KX0gPSBmX3tZIFxtaWQgWH0gKHkgXG1pZCB4KSQkDQppLmUuLCB0aGUgY29uZGl0aW9uaW5nIG9uICRXJCBkb2Vzbid0IG1hdHRlciBvbmNlIHdlIGNvbmRpdGlvbiBvbiAkWCQuIEFzIHRoZSAodW5jb25kaXRpb25hbCkgaW5kZXBlbmRlbmNlIGFib3ZlLCB0aGlzIHdvcmtzIHNpbWlsYXJseSBmb3IgdGhlIGNhc2VzIG9mIG9ubHkgY29udGludW91cyBhbmQgb25seSBkaXNjcmV0ZSB2YXJpYWJsZXMgdXNpbmcgb25seSBkZW5zaXRpZXMgb3IgcHJvYmFiaWxpdGllcywgcmVzcGVjdGl2ZWx5Lg0KDQo8YnI+DQoNCiMjIyBDb25kaXRpb25hbCBpbmRlcGVuZGVuY2UgaW1wbGllcyBjb25kaXRpb25hbCAqbWVhbiogaW5kZXBlbmRlbmNlDQoNCkNvbmRpdGlvbmFsIGluZGVwZW5kZW5jZSBvZiAkWSQgYW5kICRXJCBnaXZlbiAkWCQgaW1wbGllcyBtZWFuIGluZGVwZW5kZW5jZSwgbWVhbmluZzogJEVbWSBcbWlkIFgsV10gPSBFW1kgXG1pZCBYXSQ6DQoNCiQkDQpFW1kgXG1pZCBXLFhdID0gXGludF97LVxpbmZ0eX1eeytcaW5mdHl9IHkgXHVuZGVyYnJhY2V7Zl97WSBcbWlkIFcsWH0oeVxtaWQgdyx4KX1fez1mX3tZIFxtaWQgWH0gKHkgXG1pZCB4KSwgXCBjb25kLiBcIGluZGVwLn0gZHkgPSBcaW50X3stXGluZnR5fV57K1xpbmZ0eX0geSBcIFwgZl97WSBcbWlkIFh9ICh5IFxtaWQgeCkgXCBkeSA9IEVbWSBcbWlkIFhdDQokJA0KDQoNCkFsbCBvZiB0aGlzIGlzIGlsbHVzdHJhdGVkIGluIHRoZSBmb2xsb3dpbmcgZXhhbXBsZXMuDQoNCjxicj4NCg0KIyMgR2VuZXJhbCBFeGFtcGxlIA0KDQpMZXQncyBjb25zaWRlciB0aGUgZm9sbG93aW5nIERHUDogDQoNCiQkXGJlZ2lue2FsaWdufSBZPSBYICsgVSBcIFwgXCB3aXRoIFwgXCBcIFUgXHNpbSBcbWF0aGNhbHtOfSgwLDAuNSkgXFwNClggPSBcbWF0aGJmezF9KFZcZ2VxIDApIFxcDQpXID0gXG1hdGhiZnsxfShaXGdlcSAwKSBcXA0KIFtWLCBaXSdcc2ltIFxtYXRoY2Fse059XGxlZnQoWzB+fjBdJyxcbGVmdFsgXGJlZ2lue2FycmF5fQ0Ke3Jycn0NCjEgJiBccmhvICBcXA0KXHJobyAmIDEgDQpcZW5ke2FycmF5fVxyaWdodF1ccmlnaHQpIA0KXGVuZHthbGlnbn0kJA0KDQpGaXJzdCwgY29uc2lkZXIgdGhlIGNhc2Ugd2hlcmUgJFkkIGFuZCAkVyQgYXJlIGluZGVwZW5kZW50IChpLmUuICRccmhvID0wJCkuIFRoaXMgaXMgaWxsdXN0cmF0ZWQgYnkgZHJhd2luZyBhIGxhcmdlIHNhbXBsZSBmcm9tIHRoZSBkZXNjcmliZWQgREdQIHdpdGggJFxyaG8gPSAwJDoNCg0KYGBge3IsIHdhcm5pbmcgPSBGQUxTRSwgbWVzc2FnZT1GQUxTRX0NCmlmICghcmVxdWlyZSgidGlkeXZlcnNlIikpIGluc3RhbGwucGFja2FnZXMoInRpZHl2ZXJzZSIsIGRlcGVuZGVuY2llcyA9IFRSVUUpOyBsaWJyYXJ5KHRpZHl2ZXJzZSkNCmlmICghcmVxdWlyZSgiZ2dyaWRnZXMiKSkgaW5zdGFsbC5wYWNrYWdlcygiZ2dyaWRnZXMiLCBkZXBlbmRlbmNpZXMgPSBUUlVFKTsgbGlicmFyeShnZ3JpZGdlcykNCmlmICghcmVxdWlyZSgiTUFTUyIpKSBpbnN0YWxsLnBhY2thZ2VzKCJNQVNTIiwgZGVwZW5kZW5jaWVzID0gVFJVRSk7IGxpYnJhcnkoTUFTUykNCg0Kc2V0LnNlZWQoMTIzNCkNCm4gPSAxMDAwMDAwDQptdSA9IGMoMCwwKQ0KcmhvID0gMA0Kc2lnbWEgPSBtYXRyaXgoYygxLHJobyxyaG8sMSksIG5yb3cgPSAyKQ0KZHJhdyA9IG12cm5vcm0obiwgbXUsIHNpZ21hKQ0KWCA9IDEqKGRyYXdbLDFdPjApDQpXID0gMSooZHJhd1ssMl0gPiAwKQ0KWSA9IFggKyBybm9ybShuLCBzZCA9IDAuNSkNCmBgYA0KDQpMZXQncyBwbG90IHRoZSBkaXN0cmlidXRpb25zIG9mICRZJCBmb3IgJFc9MCQgYW5kICRXPTEkOg0KDQpgYGB7ciwgbWVzc2FnZSA9IEZBTFNFfQ0KdGliYmxlKFksVyxYKSAlPiUgbXV0YXRlKFcgPSBhcy5mYWN0b3IoVykpICU+JSANCiAgZ2dwbG90KGFlcyh4ID0gWSwgeSA9IGZjdF9yZXYoVyksIGZpbGwgPSBXKSkrDQogIGdlb21fZGVuc2l0eV9yaWRnZXMoYWxwaGEgPSAwLjYpKw0KICB4bGFiKCJZIikgKyB5bGFiKCJEZW5zaXR5IikNCmBgYA0KDQpUaGUgZGlzdHJpYnV0aW9uIG9mICRZJCBpcyB0aGUgc2FtZSB3aGVuIGNvbmRpdGlvbmluZyBvbiBkaWZmZXJlbnQgdmFsdWVzIG9mICRXJC4gQ29uc2VxdWVudGx5LCAkWSQgYW5kICRXJCBhcmUgaW5kZXBlbmRlbnQuIFRoaXMgaXMgYSBmZWF0dXJlIG9mIHRoZSAoY29uZGl0aW9uYWwpIGRpc3RyaWJ1dGlvbiBvZiAkWSQgYW5kIHRodXMgYWxzbyBob2xkcyBmb3IgdGhlIChjb25kaXRpb25hbCkgZXhwZWN0ZWQgdmFsdWU6DQoNCiQkIEVbWSBcbWlkIFc9MV0gPSBFW1kgXG1pZCBXPTBdID0gRVtZXSA9IDAuNSAkJA0KYGBge3J9DQptZWFuKFkpDQptZWFuKFlbVz09MV0pDQptZWFuKFlbVz09MF0pDQpgYGANCg0KTm93LCBsZXQncyBpbnRyb2R1Y2Ugc29tZSBjb3JyZWxhdGlvbiBiZXR3ZWVuIHRoZSB2YXJpYWJsZXMgJFYkIGFuZCAkWiQgYnkgc2V0dGluZyAkXHJobyA9IDAuNiQuIFRoaXMgY3JlYXRlcyBkZXBlbmRlbmNlIGJldHdlZW4gJFkkIGFuZCAkVyQ6DQoNCmBgYHtyLCB3YXJuaW5nID0gRkFMU0UsIG1lc3NhZ2U9RkFMU0V9DQpzZXQuc2VlZCgxMjM0KQ0KbiA9IDEwMDAwMDANCm11ID0gYygwLDApDQpyaG8gPSAwLjYNCnNpZ21hID0gbWF0cml4KGMoMSxyaG8scmhvLDEpLCBucm93ID0gMikNCmRyYXcgPSBtdnJub3JtKG4sIG11LCBzaWdtYSkNClggPSAgMSooZHJhd1ssMV0+MCkgDQpXID0gMSooZHJhd1ssMl0gPiAwKQ0KWSA9IFggKyBybm9ybShuLCBzZCA9IDAuNSkNCg0KYGBgDQoNCmBgYHtyLCBtZXNzYWdlID0gRkFMU0V9DQp0aWJibGUoWSxXLFgpICU+JSBtdXRhdGUoVyA9IGFzLmZhY3RvcihXKSkgJT4lIA0KICBnZ3Bsb3QoYWVzKHggPSBZLCB5ID0gZmN0X3JldihXKSwgZmlsbCA9IFcpKSsNCiAgZ2VvbV9kZW5zaXR5X3JpZGdlcyhhbHBoYSA9IDAuNikrDQogIHhsYWIoIlkiKQ0KYGBgDQoNClRoZSBkaXN0cmlidXRpb25zIGFyZSBub3QgdGhlIHNhbWUgYW55bW9yZSwgJFkkIGlzIG5vdCBpbmRlcGVuZGVudCBvZiAkVyQuDQoNCkl0IGlzIGhvd2V2ZXIsIG9uY2Ugd2UgY29uZGl0aW9uIG9uICRYJC4gSW4gdGhlIHByZXNlbnQgY29udGV4dCwgdGhpcyBjYW4gYmUgc2VlbiBieSBsb29raW5nIGF0IHRoZSBkaXN0cmlidXRpb24gb2YgJFkkIGluIHRoZSA0IGRpZmZlcmVudCBzdWJncm91cHMgZm9ybWVkIGJ5IHRoZSAyIGJpbmFyeSB2YXJpYWJsZXMgJFckIGFuZCAkWCQuDQoNCmBgYHtyLCBtZXNzYWdlID0gRkFMU0V9DQp0aWJibGUoWSxXLFgpICU+JSAgbXV0YXRlKFcgPSBhcy5mYWN0b3IoVyksIFggPSBhcy5mYWN0b3IoWCksIFN1Ymdyb3VwID0gaW50ZXJhY3Rpb24oVyxYKSkgJT4lDQogIGdncGxvdChhZXMoeCA9IFksIHkgPSBmY3RfcmV2KFN1Ymdyb3VwKSwgZmlsbCA9IFN1Ymdyb3VwKSkrDQogIGdlb21fZGVuc2l0eV9yaWRnZXMoYWxwaGEgPSAwLjYpKw0KICBzY2FsZV9maWxsX2Rpc2NyZXRlKGxhYmVscyA9IGMoIlc9MCwgWD0wIiwiVz0xLCBYPTAiLCJXPTAsIFg9MSIsIlc9MSwgWD0xIikpKw0KICB4bGFiKCJZIikgKyB5bGFiKCJEZW5zaXR5IikNCmBgYA0KDQpPbmNlIHdlIGNvbmRpdGlvbiBvbiAkWCQsIGkuZS4gbG9vayBhdCB0aGUgZGlzdHJpYnV0aW9uIG9mICRZJCBpbiB0aGUgMiBzdWJncm91cHMgd2l0aCAkWD0wJCBhbmQgJFg9MSQsIHRoZSBkaXN0cmlidXRpb25zIGRvbid0IGNoYW5nZSB3aGVuIGNvbmRpdGlvbmluZyBvbiBkaWZmZXJlbnQgdmFsdWVzIG9mICRXJC4gJFkkIGFuZCAkVyQgYXJlIGluZGVwZW5kZW50IGNvbmRpdGlvbmFsIG9uICRYJDogJFkgXHBlcnBcIVwhXCFcIVxwZXJwIFcgXG1pZCBYJCANCg0KSW4gdGhpcyBjYXNlLCAkRVtZIFxtaWQgVz0xXSBcbmVxIEVbWSBcbWlkIFc9MF0gXG5lcSBFW1ldJDoNCg0KYGBge3J9DQptZWFuKFkpDQptZWFuKFlbVz09MV0pDQptZWFuKFlbVz09MF0pDQpgYGANCg0KQ29uZGl0aW9uYWwgb24gWCwgd2UgY2FuIHNlZSB0aGF0OiAkRVtZIFxtaWQgVz0gMCwgWD14XSA9IEVbWSBcbWlkIFc9IDEsIFg9eF0gPSBFW1kgXG1pZCBYPXhdJCBmb3IgJHggXGluIFx7MCwxXH0kOg0KDQpgYGB7ciwgd2FybmluZyA9RkFMU0UsIG1lc3NhZ2UgPSBGQUxTRX0NCnRpYmJsZShZLFcsWCkgJT4lIGdyb3VwX2J5KFgsVykgJT4lIHN1bW1hcmlzZShtZWFuX1kgPSBtZWFuKFkpKQ0KdGliYmxlKFksVyxYKSAlPiUgZ3JvdXBfYnkoWCkgJT4lIHN1bW1hcmlzZShtZWFuX1kgPSBtZWFuKFkpKQ0KYGBgDQoNCjxicj4NCg0KIyMgRXhhbXBsZTogUG90ZW50aWFsIE91dGNvbWVzIGFuZCBjYXVzYWwgaW5mZXJlbmNlDQoNCkxldCdzIGxvb2sgYXQgdGhlIGltcG9ydGFuY2Ugb2YgdGhlIGFib3ZlIGNvbmNlcHQgaW4gYSBjYXVzYWwgaW5mZXJlbmNlIGNvbnRleHQuICRZJCBkZW5vdGVzIHRoZSBvdXRjb21lLCAkVyQgdGhlIHRyZWF0bWVudCBhbmQgJFgkIHNvbWUgY29uZm91bmRpbmcgdmFyaWFibGUuICRZKDEpJCBhbmQgJFkoMCkkIGFyZSB0aGUgcG90ZW50aWFsIG91dGNvbWVzIGluIHRoZSB0cmVhdGVkIGFuZCB1bnRyZWF0ZWQgY2FzZSByZXNwZWN0aXZlbHksIHNvICRZID0gVyBcY2RvdCBZKDEpICsgKDEtVykgXGNkb3QgWSgwKSQuDQoNCmBgYHtyLCB3YXJuaW5nID0gRkFMU0UsIG1lc3NhZ2U9RkFMU0V9DQpzZXQuc2VlZCgxMjM0KQ0KbiA9IDEwMDAwMDANCm11ID0gYygwLDApDQpyaG8gPSAwDQpzaWdtYSA9IG1hdHJpeChjKDEscmhvLHJobywxKSwgbnJvdyA9IDIpDQpkcmF3ID0gbXZybm9ybShuLCBtdSwgc2lnbWEpDQpYID0gIDEqKGRyYXdbLDFdID4gMCkNClcgPSAxKihkcmF3WywyXSA+IDApDQpZMCA9IFggKyBybm9ybShuLCBzZCA9IDAuNSkNClkxID0gMC41ICsgWCArIHJub3JtKG4sIHNkID0gMC41KQ0KWSA9IFcqWTEgKyAoMS1XKSpZMA0KYGBgDQoNCg0KQ29uc2lkZXIgdGhlIGZvbGxvd2luZyBER1A6DQoNCi0gJFkoMCkgPSBYICsgVV8wJCB3aXRoICRVXzAgXHNpbSBcbWF0aGNhbHtOfSgwLDAuNSkkLg0KDQotICRZKDEpID0gMC41ICsgWCArIFVfMSQgd2l0aCAkVV8xIFxzaW0gXG1hdGhjYWx7Tn0oMCwwLjUpJC4gDQoNCmFuZA0KJCRcYmVnaW57YWxpZ259DQpYID0gXG1hdGhiZnsxfShWXGdlcSAwKSBcXA0KVyA9IFxtYXRoYmZ7MX0oWlxnZXEgMCkgXFwNCiBbViwgWl0nXHNpbSBcbWF0aGNhbHtOfVxsZWZ0KFswfn4wXScsXGxlZnRbIFxiZWdpbnthcnJheX0NCntycnJ9DQoxICYgXHJobyAgXFwNClxyaG8gJiAxIA0KXGVuZHthcnJheX1ccmlnaHRdXHJpZ2h0KSANClxlbmR7YWxpZ259JCQNCg0KRmlyc3QsIGNvbnNpZGVyIGFnYWluIHRoZSBjYXNlIHdpdGggJFxyaG8gPSAwJCB0byBpbGx1c3RyYXRlIGluZGVwZW5kZW5jZSBpbiB0aGUgc2Vuc2UgdGhhdCAkWSh3KSBccGVycFwhXCFcIVwhXHBlcnAgVyQuDQoNClRvIHNlZSB0aGF0IHRoaXMgaG9sZHMgaW4gdGhlIGZpcnN0IGV4YW1wbGUsIHBsb3QgdGhlIGRpc3RyaWJ1dGlvbnMgb2YgJFkoMSkkIGZvciB0aGUgdHJlYXRlZCBhbmQgdGhlIHVudHJlYXRlZCBzZXBhcmF0ZWx5LiBBcyB3ZSBoYXZlIGluZGVwZW5kZW5jZSwgdGhleSBzaG91bGQgbG9vayBpZGVudGljYWw6DQoNCmBgYHtyLCBtZXNzYWdlID0gRkFMU0V9DQp0aWJibGUoWTEsWTAsWSxXLFgpICU+JSBtdXRhdGUoVyA9IGFzLmZhY3RvcihXKSkgJT4lIA0KICBnZ3Bsb3QoYWVzKHggPSBZMSwgeSA9IGZjdF9yZXYoVyksIGZpbGwgPSBXKSkrDQogIGdlb21fZGVuc2l0eV9yaWRnZXMoYWxwaGEgPSAwLjYpKw0KICB4bGFiKCJZKDEpIikrIHlsYWIoIkRlbnNpdHkiKQ0KYGBgDQoNClNhbWUgZm9yICRZKDApJDoNCg0KYGBge3IsIG1lc3NhZ2UgPSBGQUxTRX0NCnRpYmJsZShZMSxZMCxZLFcsWCkgJT4lIG11dGF0ZShXID0gYXMuZmFjdG9yKFcpKSAlPiUgDQogIGdncGxvdChhZXMoeCA9IFkwLCB5ID0gZmN0X3JldihXKSwgZmlsbCA9IFcpKSsNCiAgZ2VvbV9kZW5zaXR5X3JpZGdlcyhhbHBoYT0uNikrDQogIHhsYWIoIlkoMCkiKSsgeWxhYigiRGVuc2l0eSIpDQpgYGANCg0KVGhlc2UgZ3JhcGhzIGlsbHVzdHJhdGUgdGhhdCB0aGUgcG90ZW50aWFsIG91dGNvbWVzIGFyZSBpbmRlcGVuZGVudCBvZiB0aGUgdHJlYXRtZW50IGluZGljYXRvci4NCg0KVGhlIGluZGl2aWR1YWwgdHJlYXRtZW50IGVmZmVjdHMgYXJlIGdpdmVuIGFzOiAkWSgxKSAtIFkoMCkgPSAwLjUgKyBYICsgVV8xIC0gWCAtIFVfMCA9IDAuNSArIFVfMSAtIFVfMCQuIFRoZSBBVEUgaXMgJEVbWSgxKS1ZKDApXSA9IDAuNSQuDQoNCkFzIHRoZSBwb3RlbnRpYWwgb3V0Y29tZXMgYXJlIGluZGVwZW5kZW50IG9mICRXJCwgd2UgY2FuIGVzdGltYXRlIHRoZSBBVEUgdXNpbmcgYSBzaW1wbGUgbWVhbiBjb21wYXJpc29uIGJldHdlZW4gdGhlIHRyZWF0ZWQgYW5kIHRoZSB1bnRyZWF0ZWQgZ3JvdXA6DQoNCmBgYHtyfQ0KVEVfbWVhbl9jb21wYXJpc29uID0gbWVhbihZW1c9PTFdKSAtIG1lYW4oWVtXPT0wXSkNCnByaW50KFRFX21lYW5fY29tcGFyaXNvbikNCmBgYA0KPGJyPg0KDQpBcyBhYm92ZSwgbGV0J3MgaW50cm9kdWNlIHNvbWUgY29ycmVsYXRpb24gYmV0d2VlbiAkViQgYW5kICRaJCBieSBzZXR0aW5nICRccmhvID0gMC42LiBOb3csICRYJCBhbmQgJFckIGFyZSBub3QgaW5kZXBlbmRlbnQ6DQoNCmBgYHtyLCB3YXJuaW5nID0gRkFMU0UsIG1lc3NhZ2U9RkFMU0V9DQpyaG8gPSAwLjYNCnNpZ21hID0gbWF0cml4KGMoMSxyaG8scmhvLDEpLCBucm93ID0gMikNCmRyYXcgPSBtdnJub3JtKG4sIG11LCBzaWdtYSkNClggPSAgMSooZHJhd1ssMV0gPiAwKQ0KVyA9IDEqKGRyYXdbLDJdID4gMCkNClkwID0gWCArIHJub3JtKG4sIHNkID0gMC41KQ0KWTEgPSAwLjUgKyBYICsgcm5vcm0obiwgc2QgPSAwLjUpDQpZID0gVypZMSArICgxLVcpKlkwDQpgYGANCg0KSW50dWl0aXZlbHksIG9ic2VydmF0aW9ucyB3aXRoIGEgaGlnaCAkWCQgKGFuZCB0aHVzIGEgaGlnaCAkWSQpIGFyZSBub3cgbW9yZSBsaWtlbHkgdG8gYmUgdHJlYXRlZCwgYXMgJFYkIGFuZCAkWiQsIHRoZSB0d28gdmFyaWFibGVzIGRldGVybWluaW5nICRYJCBhbmQgJFckLCBhcmUgcG9zaXRpdmVseSBjb3JyZWxhdGVkLiBDb21wYXJpbmcgbWVhbnMgYmV0d2VlbiB0cmVhdGVkIGFuZCB1bnRyZWF0ZWQgd2lsbCBnaXZlIGFuIGluY29ycmVjdCwgdG9vIGxhcmdlLCBlc3RpbWF0ZSBvZiB0aGUgQVRFLiBJbiBhIGZpcnN0IHN0ZXAsIGxldCdzIGFnYWluIGlsbHVzdHJhdGUgdGhlIGxhY2sgb2YgaW5kZXBlbmRlbmNlIGJldHdlZW4gdGhlIHBvdGVudGlhbCBvdXRjb21lcyBhbmQgJFckOg0KDQpgYGB7ciwgbWVzc2FnZSA9IEZBTFNFfQ0KdGliYmxlKFkxLFkwLFksVyxYKSAlPiUgbXV0YXRlKFcgPSBhcy5mYWN0b3IoVykpICU+JSANCiAgZ2dwbG90KGFlcyh4ID0gWTEsIHkgPSBmY3RfcmV2KFcpLCBmaWxsID0gVykpKw0KICBnZW9tX2RlbnNpdHlfcmlkZ2VzKGFscGhhPS42KSsNCiAgeGxhYigiWSgxKSIpK3lsYWIoIkRlbnNpdHkiKQ0KYGBgDQoNClNhbWUgZm9yICRZKDApJDoNCg0KYGBge3IsIG1lc3NhZ2UgPSBGQUxTRX0NCnRpYmJsZShZMSxZMCxZLFcsWCkgJT4lIG11dGF0ZShXID0gYXMuZmFjdG9yKFcpKSAlPiUgDQogIGdncGxvdChhZXMoeCA9IFkwLCB5ID0gZmN0X3JldihXKSwgZmlsbCA9IFcpKSsNCiAgZ2VvbV9kZW5zaXR5X3JpZGdlcyhhbHBoYT0uNikrDQogIHhsYWIoIlkoMCkiKSt5bGFiKCJEZW5zaXR5IikNCmBgYA0KDQpUaGUgcG90ZW50aWFsIG91dGNvbWVzIGFyZSBub3QgaW5kZXBlbmRlbnQgb2YgJFckLiBFc3RpbWF0ZSB0aGUgQVRFIGJ5IG1lYW4gY29tcGFyaXNvbjoNCg0KYGBge3J9DQpURV9tZWFuX2NvbXBhcmlzb24gPSBtZWFuKFlbVz09MV0pIC0gbWVhbihZW1c9PTBdKQ0KcHJpbnQoVEVfbWVhbl9jb21wYXJpc29uKQ0KYGBgDQoNCkxldCdzIGNvbmRpdGlvbiBvbiAkWCQgYW5kIHNlZSB3aGV0aGVyIHRoZXkgYXJlIGluZGVwZW5kZW50LiBGb3IgdGhpcyBwdXJwb3NlLCBsb29rIGF0IHRoZSBkaXN0cmlidXRpb25zIG9mICRZKHcpJCBmb3IgdGhlIGRpZmZlcmVudCBzdWJncm91cHMgZm9ybWVkIGJ5ICRXJCBhbmQgJFgkOg0KDQpgYGB7ciwgbWVzc2FnZSA9IEZBTFNFfQ0KdGliYmxlKFksWTEsWTAsVyxYKSAlPiUgIG11dGF0ZShXID0gYXMuZmFjdG9yKFcpLCBYID0gYXMuZmFjdG9yKFgpLCBTdWJncm91cCA9IGludGVyYWN0aW9uKFcsWCkpICU+JSANCiAgZ2dwbG90KGFlcyh4ID0gWTEsIHkgPSBmY3RfcmV2KFN1Ymdyb3VwKSwgZmlsbCA9IFN1Ymdyb3VwKSkrDQogIGdlb21fZGVuc2l0eV9yaWRnZXMoYWxwaGEgPSAwLjYpKw0KICBzY2FsZV9maWxsX2Rpc2NyZXRlKGxhYmVscyA9IGMoIlc9MCwgWD0wIiwiVz0xLCBYPTAiLCJXPTAsIFg9MSIsIlc9MSwgWD0xIikpKw0KICB4bGFiKCJZKDEpIikreWxhYigiRGVuc2l0eSIpDQpgYGANCg0KYGBge3IsIG1lc3NhZ2UgPSBGQUxTRX0NCnRpYmJsZShZLFkxLFkwLFcsWCkgJT4lICBtdXRhdGUoVyA9IGFzLmZhY3RvcihXKSwgWCA9IGFzLmZhY3RvcihYKSwgU3ViZ3JvdXAgPSBpbnRlcmFjdGlvbihXLFgpKSAlPiUgDQogIGdncGxvdChhZXMoeCA9IFkwLCB5ID0gZmN0X3JldihTdWJncm91cCksIGZpbGwgPSBTdWJncm91cCkpKw0KICBnZW9tX2RlbnNpdHlfcmlkZ2VzKGFscGhhID0gMC43KSsNCiAgc2NhbGVfZmlsbF9kaXNjcmV0ZShsYWJlbHMgPSBjKCJXPTAsIFg9MCIsIlc9MSwgWD0wIiwiVz0wLCBYPTEiLCJXPTEsIFg9MSIpKSsNCiAgeGxhYigiWSgwKSIpK3lsYWIoIkRlbnNpdHkiKQ0KYGBgDQoNCldoZW4gd2UgbG9vayBhdCBjb25kaXRpb25hbCBkaXN0cmlidXRpb25zIG9mIHRoZSBwb3RlbnRpYWwgb3V0Y29tZXMsIHRoZXkgYXJlIHRoZSBzYW1lIGZvciB0cmVhdGVkIGFuZCB1bnRyZWF0ZWQuIFRoaXMgc2hvd3MgdGhhdCBjb25kaXRpb25hbCBvbiAkWCQsIHRoZSBwb3RlbnRpYWwgb3V0Y29tZXMgYXJlIGluZGVwZW5kZW50IG9mICRXJC4gV2hhdCBkb2VzIHRoaXMgaW1wbHkgZm9yIHRoZSBlc3RpbWF0aW9uIG9mIHRoZSBBVEU/DQoNCldlIGNhbiBlc3RpbWF0ZSB0aGUgQVRFIGJ5IGNvbXBhcmluZyBjb25kaXRpb25hbCBtZWFuczoNCg0KYGBge3J9DQpURV9jb25kX21lYW5fY29tcGFyaXNvbl8xID0gbWVhbihZW1c9PTEgJiBYPT0xXSkgLSBtZWFuKFlbVz09MCAmIFg9PTFdKQ0KcHJpbnQoVEVfY29uZF9tZWFuX2NvbXBhcmlzb25fMSkNCmBgYA0KYGBge3J9DQpURV9jb25kX21lYW5fY29tcGFyaXNvbl8wID0gbWVhbihZW1c9PTEgJiBYPT0wXSkgLSBtZWFuKFlbVz09MCAmIFg9PTBdKQ0KcHJpbnQoVEVfY29uZF9tZWFuX2NvbXBhcmlzb25fMCkNCmBgYA0KYGBge3J9DQpURV9jb25kX21lYW5fY29tcGFyaXNvbiA9IG1lYW4oWCkqVEVfY29uZF9tZWFuX2NvbXBhcmlzb25fMSArIG1lYW4oMS1YKSpURV9jb25kX21lYW5fY29tcGFyaXNvbl8wDQpwcmludChURV9jb25kX21lYW5fY29tcGFyaXNvbikNCmBgYA0KDQpBbHRlcm5hdGl2ZWx5LCB1c2UgYSByZWdyZXNzaW9uIG1vZGVsIHdpdGggJFgkIGFzIGEgY29udHJvbCB2YXJpYWJsZToNCg0KYGBge3J9DQpzdW1tYXJ5KGxtKFl+IFcgKyBYKSkNCmBgYA0KDQpUaGlzIHJlY292ZXJzIHRoZSB0cnVlIEFURS4gTm90ZSwgdGhhdCBoZXJlIHRoZSB0cnVlIHN0cnVjdHVyYWwgbW9kZWwgaXMgYWN0dWFsbHkgbGluZWFyIGFuZCBPTFMgY2FuIGJlIHVzZWQgdG8gcmVjb3ZlciB0aGUgdHJ1ZSBjb2VmZmljaWVudHMgb24gJFgkIGFuZCAkVyQ6DQoNCiQkIFkgPSBXXGNkb3QgKDAuNSArIFggKyBVX3sxfSkgKyAoMS1XKSBcY2RvdCAoWCArIFVfezB9KSA9IDAuNVcgKyBYICtcdW5kZXJicmFjZXtXVV97MX0gKyAoMS1XKVVfezB9fV97PVV9ID0gIDAuNVcgKyBYICsgVSQkDQoNCg==