Paula Cazali

Fiabilidad

4.6.1 The Stock Market Data

library(ISLR)
names(Smarket)
[1] "Year"      "Lag1"      "Lag2"      "Lag3"      "Lag4"      "Lag5"      "Volume"    "Today"     "Direction"
summary(Smarket)
      Year           Lag1                Lag2                Lag3                Lag4                Lag5              Volume      
 Min.   :2001   Min.   :-4.922000   Min.   :-4.922000   Min.   :-4.922000   Min.   :-4.922000   Min.   :-4.92200   Min.   :0.3561  
 1st Qu.:2002   1st Qu.:-0.639500   1st Qu.:-0.639500   1st Qu.:-0.640000   1st Qu.:-0.640000   1st Qu.:-0.64000   1st Qu.:1.2574  
 Median :2003   Median : 0.039000   Median : 0.039000   Median : 0.038500   Median : 0.038500   Median : 0.03850   Median :1.4229  
 Mean   :2003   Mean   : 0.003834   Mean   : 0.003919   Mean   : 0.001716   Mean   : 0.001636   Mean   : 0.00561   Mean   :1.4783  
 3rd Qu.:2004   3rd Qu.: 0.596750   3rd Qu.: 0.596750   3rd Qu.: 0.596750   3rd Qu.: 0.596750   3rd Qu.: 0.59700   3rd Qu.:1.6417  
 Max.   :2005   Max.   : 5.733000   Max.   : 5.733000   Max.   : 5.733000   Max.   : 5.733000   Max.   : 5.73300   Max.   :3.1525  
     Today           Direction 
 Min.   :-4.922000   Down:602  
 1st Qu.:-0.639500   Up  :648  
 Median : 0.038500             
 Mean   : 0.003138             
 3rd Qu.: 0.596750             
 Max.   : 5.733000             
pairs(Smarket)

Correlaciones entre las variables.

cor(Smarket)
Error in cor(Smarket) : 'x' must be numeric

Da error porque la variable Direction es cualitativa

cor(Smarket[,-9])
             Year         Lag1         Lag2         Lag3         Lag4         Lag5      Volume        Today
Year   1.00000000  0.029699649  0.030596422  0.033194581  0.035688718  0.029787995  0.53900647  0.030095229
Lag1   0.02969965  1.000000000 -0.026294328 -0.010803402 -0.002985911 -0.005674606  0.04090991 -0.026155045
Lag2   0.03059642 -0.026294328  1.000000000 -0.025896670 -0.010853533 -0.003557949 -0.04338321 -0.010250033
Lag3   0.03319458 -0.010803402 -0.025896670  1.000000000 -0.024051036 -0.018808338 -0.04182369 -0.002447647
Lag4   0.03568872 -0.002985911 -0.010853533 -0.024051036  1.000000000 -0.027083641 -0.04841425 -0.006899527
Lag5   0.02978799 -0.005674606 -0.003557949 -0.018808338 -0.027083641  1.000000000 -0.02200231 -0.034860083
Volume 0.53900647  0.040909908 -0.043383215 -0.041823686 -0.048414246 -0.022002315  1.00000000  0.014591823
Today  0.03009523 -0.026155045 -0.010250033 -0.002447647 -0.006899527 -0.034860083  0.01459182  1.000000000

Como se esperaba las correlaciones entre las variables Lag y Today son muy cercanas a cero. Parece que hay poca correlacion entre el retorno de ese dia y el returno del dia anterior. La unica correlacion sustancial es entre las variables Year y Volume. Con la grafica se puede ver que el Volumen esta incrementando con el tiempo.

NOTA: attach() carga a memoria el dataset, asi se podra referenciar a las columnas de este sin necesidad de poner dataset$columna. Usar solo en este caso.

attach(Smarket)
The following objects are masked from Weekly (pos = 14):

    Direction, Lag1, Lag2, Lag3, Lag4, Lag5, Today, Volume, Year

The following objects are masked from Weekly (pos = 15):

    Direction, Lag1, Lag2, Lag3, Lag4, Lag5, Today, Volume, Year

The following objects are masked from Smarket (pos = 16):

    Direction, Lag1, Lag2, Lag3, Lag4, Lag5, Today, Volume, Year

The following objects are masked from Smarket (pos = 17):

    Direction, Lag1, Lag2, Lag3, Lag4, Lag5, Today, Volume, Year

The following objects are masked from Smarket (pos = 18):

    Direction, Lag1, Lag2, Lag3, Lag4, Lag5, Today, Volume, Year
plot(Volume)

4.6.2 Logistic Regression

Predecir Direction usando todas las variables Lag y Volume. Usar la funcion glm(). La sintaxis es similar a la de la funcion lm(), con la diferencia que necesita el argumento family=binomial para que R corra una regresion logistica en vez de otro tipo de modelo lineal generalizado.

glm.fit <- glm(Direction ~ Lag1 + Lag2 + Lag3 + Lag4 + Lag5 + Volume,
               data = Smarket,
               family = binomial)
summary(glm.fit)

Call:
glm(formula = Direction ~ Lag1 + Lag2 + Lag3 + Lag4 + Lag5 + 
    Volume, family = binomial, data = Smarket)

Deviance Residuals: 
   Min      1Q  Median      3Q     Max  
-1.446  -1.203   1.065   1.145   1.326  

Coefficients:
             Estimate Std. Error z value Pr(>|z|)
(Intercept) -0.126000   0.240736  -0.523    0.601
Lag1        -0.073074   0.050167  -1.457    0.145
Lag2        -0.042301   0.050086  -0.845    0.398
Lag3         0.011085   0.049939   0.222    0.824
Lag4         0.009359   0.049974   0.187    0.851
Lag5         0.010313   0.049511   0.208    0.835
Volume       0.135441   0.158360   0.855    0.392

(Dispersion parameter for binomial family taken to be 1)

    Null deviance: 1731.2  on 1249  degrees of freedom
Residual deviance: 1727.6  on 1243  degrees of freedom
AIC: 1741.6

Number of Fisher Scoring iterations: 3

Coeficientes del modelo:

coef(glm.fit)
 (Intercept)         Lag1         Lag2         Lag3         Lag4         Lag5       Volume 
-0.126000257 -0.073073746 -0.042301344  0.011085108  0.009358938  0.010313068  0.135440659 

Se puede accesar tambien a informacion especifica del modelo por medio de:

summary(glm.fit)$coef
                Estimate Std. Error    z value  Pr(>|z|)
(Intercept) -0.126000257 0.24073574 -0.5233966 0.6006983
Lag1        -0.073073746 0.05016739 -1.4565986 0.1452272
Lag2        -0.042301344 0.05008605 -0.8445733 0.3983491
Lag3         0.011085108 0.04993854  0.2219750 0.8243333
Lag4         0.009358938 0.04997413  0.1872757 0.8514445
Lag5         0.010313068 0.04951146  0.2082966 0.8349974
Volume       0.135440659 0.15835970  0.8552723 0.3924004

p-values (prueba para que el coeficiente sea significativo) para los coeficientes:

summary(glm.fit)$coef[,4]
(Intercept)        Lag1        Lag2        Lag3        Lag4        Lag5      Volume 
  0.6006983   0.1452272   0.3983491   0.8243333   0.8514445   0.8349974   0.3924004 

La funcion predict() puede ser usada para predecir que probabilidad tienen el mercado de subir, dados los valores de los predictores. Desplegando las primeras 10 probabilidades:

glm.probs <- predict(glm.fit, type="response")
glm.probs[1:10]
        1         2         3         4         5         6         7         8         9        10 
0.5070841 0.4814679 0.4811388 0.5152224 0.5107812 0.5069565 0.4926509 0.5092292 0.5176135 0.4888378 

Se sabe que las probabilidades indican si sube o no ya que la funcion contrasts() indica que una probabilidad de 1 es para que el mercado suba.

contrasts(Direction)
     Up
Down  0
Up    1

Para hacer predicciones en base a si el mercado va a subir o a bajar en un dia en particular se necesita convertir estas probabilidades en labels de clase: Up y Down. Se crea un vector con “Down” y al tener una probabilidad mayor a 0.5 se asigna “Up”.

glm.pred <- rep("Down", 1250)
glm.pred[glm.probs > 0.5] <- "Up"

Ahora se usa la funcion table() la cual produce una matriz de confucion para determinar cuantas observaciones fueron correctamente clasificadas y cuales no.

table(glm.pred, Direction)
        Direction
glm.pred Down  Up
    Down  145 141
    Up    457 507

Los elementos en la diagonal de la matriz de confusion indica las predicciones correctas, mientras los otros valores indican las predicciones incorrectas.

Media de las predicciones acertadas:

(507+145)/1250
[1] 0.5216
mean(glm.pred == Direction)
[1] 0.5216

Por lo que la tasa de error del training es de:

100-52.2
[1] 47.8

Para mejorar la presicion del modelo de regresion logistica se puede usar solo una parte de los datos para el entrenamiento y luego la otra parte para hacer las pruebas.

Se va a crear un training set con las observaciones desde el anio 2001 hasta el 2004. El objeto train es un vector de 1250 elementos que corresponden a las observaciones del dataset. Loe elementos de ese vector que corresponden a las observaciones que ocurrieron antes del 2005 son TRUE y las que no son FALSE. Los vectores booleanos pueden ser usados para hacer subsets de las filas y las columnas de una matriz. Smartek[train,] sera una submatriz del data set de stock market.

train <- Year < 2005
Smarket.2005 <- Smarket[!train,]
dim(Smarket.2005)
[1] 252   9
Direction.2005 <- Direction[!train]

Ahora se creara un modelo de regresion logistica ussando solo el subset de las observaciones que corresponden a fechas antes del 2005, usando el argumento subset. Luego se predicen las probabilidades de que el mercado de acciones suba para cada uno de eso dias en el test set.

glm.fit <- glm(Direction ~ Lag1 + Lag2 + Lag3 + Lag4 + Lag5 + Volume,
               data=Smarket,
               family=binomial,
               subset=train)
glm.probs <- predict(glm.fit, Smarket.2005, type = "response")

Ahora se hacen las predicciones y se despliega la matriz de confusion:

glm.pred <- rep("Down",252)
glm.pred[glm.probs > 0.5] <- "Up"
table(glm.pred, Direction.2005)
        Direction.2005
glm.pred Down Up
    Down   77 97
    Up     34 44

El promedio de predicciones y la tasa de error del modelo:

mean(glm.pred == Direction.2005)
[1] 0.4801587
mean(glm.pred!=Direction.2005)
[1] 0.5198413

El uso de predictores que no tiene relacion con la respuesta tiende a causar que la tasa de error empeore, ya que los predictores causan un aumento en la varianza sin una disminucion en el bias.

Ahora solo se usaran las variables Lag1 y Lag2 los cuales parecen ser los estadisticamente mas significativos.

glm.fit <- glm(Direction ~ Lag1 + Lag2,
               data = Smarket,
               family = binomial,
               subset = train)
glm.probs <- predict(glm.fit, Smarket.2005, type = "response")
glm.pred <- rep("Down", 252)
glm.pred[glm.probs > 0.5] <- "Up"
table(glm.pred,Direction.2005)
        Direction.2005
glm.pred Down  Up
    Down   35  35
    Up     76 106
mean(glm.pred == Direction.2005)
[1] 0.5595238
106/(106+76)
[1] 0.5824176

Ahora las predicciones tendran un porcentaje mayor: 56% Suponiendo que queremos predecir la direccion del mercado en un dia determinado cuando Lag1 y Lag2 son iguales a 1.2 y 1.1 respectivamente y en otro dia son iguales a 1.5 y -0.8.

predict(glm.fit, newdata = data.frame(Lag1 = c(1.2,1.5), 
                                      Lag2 = c(1.1, -0.8)),
        type = "response")
        1         2 
0.4791462 0.4960939 

4.6.3 Linear Discriminant Analysis

Ahora se usará LDA con el dataset de Smarket. Usaremos lda() de la libreria MASS. Esta funcion se parece a la funcion lm() y glm() excepto por el argumento de family. Usaremos las observaciones de antes del año 2005.

library(MASS)
lda.fit <- lda(Direction ~ Lag1 + Lag2,
               data = Smarket,
               subset = train)
lda.fit
Call:
lda(Direction ~ Lag1 + Lag2, data = Smarket, subset = train)

Prior probabilities of groups:
    Down       Up 
0.491984 0.508016 

Group means:
            Lag1        Lag2
Down  0.04279022  0.03389409
Up   -0.03954635 -0.03132544

Coefficients of linear discriminants:
            LD1
Lag1 -0.6420190
Lag2 -0.5135293
plot(lda.fit)

Los coeficientes del discriminante lineal son una combinacion lineal de Lag1 y Lag2. Si \(???0.642Lag_1???0.514Lag_2\) es mayor el clasificador LDA va a predecir una elevacion en el mercado y si es pequeño va a predecir una declinacion para el mercado. La funcion predict() devuelve una lista de tres elementos. El primer elemento class contiene los predictores de LDA acerca del movimiento del mercado. El segundo elemento posterior es una matriz donde la k-esima columna contiene la probabilidad posterior que corresponde a una observacion que pertenece a la clase k-esima. Finalmente x contiene los discriminates lineales.

lda.pred <- predict(lda.fit, Smarket.2005)
names(lda.pred)
[1] "class"     "posterior" "x"        
lda.class <- lda.pred$class 
table(lda.class,Direction.2005)
         Direction.2005
lda.class Down  Up
     Down   35  35
     Up     76 106
mean(lda.class == Direction.2005)
[1] 0.5595238

Aplicando el threshold de 0.5 para tener las predicciones:

sum(lda.pred$posterior[,1] >= 0.5)
[1] 70
sum(lda.pred$posterior[,1] < 0.5)
[1] 182
lda.pred$posterior[1:20,1]
      999      1000      1001      1002      1003      1004      1005      1006      1007      1008      1009      1010      1011 
0.4901792 0.4792185 0.4668185 0.4740011 0.4927877 0.4938562 0.4951016 0.4872861 0.4907013 0.4844026 0.4906963 0.5119988 0.4895152 
     1012      1013      1014      1015      1016      1017      1018 
0.4706761 0.4744593 0.4799583 0.4935775 0.5030894 0.4978806 0.4886331 
lda.class[1:20]
 [1] Up   Up   Up   Up   Up   Up   Up   Up   Up   Up   Up   Down Up   Up   Up   Up   Up   Down Up   Up  
Levels: Down Up

Suponer que queremos predecir que el mercado va a decrementar solamente si estamos certeron que el mercado va a decrecer en ese dia, si la probabilidad posterior es al menos del 90%

sum(lda.pred$posterior[,1] > 0.9)
[1] 0

Por lo que ningun dia del 2005 coincide con ese threshold.

4.6.4 Quadratic Discriminant Analysis

Usaremos la funcion qda() la cual tambien esta en la libreria MASS. La sintaxis de esta funcion es identica a la lda().

qda.fit <- qda(Direction ~ Lag1 + Lag2,
               data = Smarket,
               subset = train)
qda.fit
Call:
qda(Direction ~ Lag1 + Lag2, data = Smarket, subset = train)

Prior probabilities of groups:
    Down       Up 
0.491984 0.508016 

Group means:
            Lag1        Lag2
Down  0.04279022  0.03389409
Up   -0.03954635 -0.03132544

La funcion qda contiene las medias de los grupos. Pero no contiene los coeficientes del discriminante lineal, ya que el clasificador QDA involucra una funcion cuadratica de predictores. La funcion predict() funciona exactamente igual que LDA.

qda.class <- predict(qda.fit, Smarket.2005)$class
table(qda.class, Direction.2005)
         Direction.2005
qda.class Down  Up
     Down   30  20
     Up     81 121
mean(qda.class == Direction.2005)
[1] 0.5992063

Por lo que se puede ver que el modelo QDA es preciso el 60% de las veces incluso sin los datos del 2005. Por lo que se puede decir que el modelo QDA captura de forma mas precisa la relacion de las variables, de mejor manera que LDA y la regresion logistica.

4.6.5 K-Nearest Neighbors

Ahora se usara KNN usando la funcion knn() que es parte de la libreria class. Esta funcion es distinta a las otras ya que no necesita de dos pasos para hacer las predicciones. Necesita cuatro inputs.

  1. Una matriz que contenga los predictores asociadss con los datos con el train data. En este ejercicio se llamara train.X.

  2. Una matriz que contenga los predictores asociados con los datos con los cuales se quieren hacer predicciones. Esta matriz se llamara test.X.

  3. Un vector que contiene las etiquetas de clase para las observaciones del training, train.Direction.

  4. Un valor de k, el numero de vecindades cercanas para usar en el clasificador.

Se usara la funcion cbind() para juntar las variables Lag1 y Lag2 en dos matrices:

library(class)
train.X <- cbind(Lag1, Lag2)[train,]
test.X <- cbind(Lag1,Lag2)[!train,]
train.Direction <- Direction[train]

Ahora usamos la funcion knn() para predecir los movimientos del mercado en 2005. Hacemos un set de un random antes de aplicar la funcion, esto asegura que se pueda reproducir siempre el mismo resultado.

set.seed(1)
knn.pred <- knn(train.X, test.X, train.Direction, k = 1)
table(knn.pred, Direction.2005)
        Direction.2005
knn.pred Down Up
    Down   43 58
    Up     68 83
(43+83)/252
[1] 0.5

Las predicciones hechas con \(k=1\) no son muy buenas ya que solo el \(50\%\) de las predicciones son correctas. Ahora se reproduce el modelo usando \(k=3\)

knn.pred <- knn(train.X, test.X, train.Direction, k = 3)
table(knn.pred, Direction.2005)
        Direction.2005
knn.pred Down Up
    Down   48 54
    Up     63 87
mean(knn.pred == Direction.2005)
[1] 0.5357143

Se puede ver que hubo una pequeña mejora. Por lo que se puede concluir que para este analisis el mejor modelo es el QDA.

4.6.6 An Application to Caravan Insurance Data

Finalmente se va a aplicar el metodo KNN al dataset Caravan, que es parte de la libreria ISLR. Este set de datos incluye \(85\) predictores que miden las caracteristicas demograficas de \(5822\) individuos. La variable de respuesta sera Purchase, que indica si un individuo compra o no una poliza de seguro de caravana. Solo el \(6\%\) de las personas compraron un seguro de caravana.

dim(Caravan)
[1] 5822   86
attach(Caravan)
summary(Purchase)
  No  Yes 
5474  348 
348/5822
[1] 0.05977327

Para este problema se va a estandarizar, para que todas las variables tengan una media de \(0\) y una desviacion estandar de \(1\). Esto lo hace la funcion scale().

standardized.X <- scale(Caravan[,-86])
var(Caravan[,1])
[1] 165.0378
var(Caravan[,2])
[1] 0.1647078
var(standardized.X[,1])
[1] 1
var(standardized.X[,2])
[1] 1

Ahora cada columna del dataset standardized.X tiene una media de \(0\) y una desviacion estandar de \(1\). Ahora se va a crear un test data que contenga las primeras \(1000\) observaciones y un training set que contenga el resto.

test <- 1:1000
train.X <- standardized.X[-test,]
test.X <- standardized.X[test,]
train.Y <- Purchase[-test]
test.Y <- Purchase[test]
set.seed(1)
knn.pred <- knn(train.X, test.X, train.Y, k=1)
mean(test.Y != knn.pred)
[1] 0.118
mean(test.Y != "No")
[1] 0.059

Obtenemos la matriz de confusion:

table(knn.pred, test.Y)
        test.Y
knn.pred  No Yes
     No  873  50
     Yes  68   9
9/(68+9)
[1] 0.1168831

Usando ahora \(k=3\)

knn.pred <- knn(train.X, test.X, train.Y, k=3)
table(knn.pred, test.Y)
        test.Y
knn.pred  No Yes
     No  920  54
     Yes  21   5
5/26
[1] 0.1923077
knn.pred <- knn (train.X, test.X, train.Y, k = 5)
table(knn.pred, test.Y)
        test.Y
knn.pred  No Yes
     No  930  55
     Yes  11   4
4/15
[1] 0.2666667

Para comparar tambien se usara una regresion logistica. Usamos el valor de \(0.5\) para la probabilidad del clasificador

glm.fit <- glm(Purchase ~ ., 
               data = Caravan,
               family =binomial,
               subset = -test)
glm.fit: fitted probabilities numerically 0 or 1 occurred
glm.probs <- predict (glm.fit, Caravan[test,], type = "response")
glm.pred <- rep ("No ", 1000)
glm.pred[glm.probs > 0.5] <- "Yes"
table(glm.pred, test.Y)
        test.Y
glm.pred  No Yes
     No  934  59
     Yes   7   0
glm.pred <- rep("No", 1000)
glm.pred[glm.probs > 0.25] <- "Yes"
table(glm.pred, test.Y)
        test.Y
glm.pred  No Yes
     No  919  48
     Yes  22  11
11/(22+11)
[1] 0.3333333

Por lo que podemos predecir de manera correcta en \(33\%\) de estas personas. Lo cual es mucho mejor que adivinar random.

LS0tDQp0aXRsZTogIjQuNiBMYWI6IExvZ2lzdGljIFJlZ3Jlc3Npb24sIExEQSwgUURBLCBhbmQgS05OIg0Kb3V0cHV0OiBodG1sX25vdGVib29rDQotLS0NCg0KIyMgUGF1bGEgQ2F6YWxpIA0KIyMjIEZpYWJpbGlkYWQNCg0KIyMgNC42LjEgVGhlIFN0b2NrIE1hcmtldCBEYXRhDQoNCmBgYHtyfQ0KbGlicmFyeShJU0xSKQ0KYGBgDQoNCmBgYHtyfQ0KbmFtZXMoU21hcmtldCkNCnN1bW1hcnkoU21hcmtldCkNCmBgYA0KDQpgYGB7cn0NCnBhaXJzKFNtYXJrZXQpDQpgYGANCg0KQ29ycmVsYWNpb25lcyBlbnRyZSBsYXMgdmFyaWFibGVzLg0KYGBge3J9DQpjb3IoU21hcmtldCkNCmBgYA0KDQpEYSBlcnJvciBwb3JxdWUgbGEgdmFyaWFibGUgKkRpcmVjdGlvbiogZXMgY3VhbGl0YXRpdmENCmBgYHtyfQ0KY29yKFNtYXJrZXRbLC05XSkNCmBgYA0KDQpDb21vIHNlIGVzcGVyYWJhIGxhcyBjb3JyZWxhY2lvbmVzIGVudHJlIGxhcyB2YXJpYWJsZXMgKipMYWcqKiB5ICoqVG9kYXkqKiBzb24gbXV5IGNlcmNhbmFzIGEgY2Vyby4gUGFyZWNlIHF1ZSBoYXkgcG9jYSBjb3JyZWxhY2lvbiBlbnRyZSBlbCByZXRvcm5vIGRlIGVzZSBkaWEgeSBlbCByZXR1cm5vIGRlbCBkaWEgYW50ZXJpb3IuIA0KTGEgdW5pY2EgY29ycmVsYWNpb24gc3VzdGFuY2lhbCBlcyBlbnRyZSBsYXMgdmFyaWFibGVzICoqWWVhcioqIHkgKipWb2x1bWUqKi4NCkNvbiBsYSBncmFmaWNhIHNlIHB1ZWRlIHZlciBxdWUgZWwgVm9sdW1lbiBlc3RhIGluY3JlbWVudGFuZG8gY29uIGVsIHRpZW1wby4NCg0KTk9UQTogYXR0YWNoKCkgY2FyZ2EgYSBtZW1vcmlhIGVsIGRhdGFzZXQsIGFzaSBzZSBwb2RyYSByZWZlcmVuY2lhciBhIGxhcyBjb2x1bW5hcyBkZSBlc3RlIHNpbiBuZWNlc2lkYWQgZGUgcG9uZXIgZGF0YXNldCRjb2x1bW5hLiBVc2FyIHNvbG8gZW4gZXN0ZSBjYXNvLg0KYGBge3J9DQphdHRhY2goU21hcmtldCkNCnBsb3QoVm9sdW1lKQ0KYGBgDQoNCiMjIDQuNi4yIExvZ2lzdGljIFJlZ3Jlc3Npb24NClByZWRlY2lyICoqRGlyZWN0aW9uKiogdXNhbmRvIHRvZGFzIGxhcyB2YXJpYWJsZXMgKipMYWcqKiB5ICoqVm9sdW1lKiouIFVzYXIgbGEgZnVuY2lvbiBnbG0oKS4gTGEgc2ludGF4aXMgZXMgc2ltaWxhciBhIGxhIGRlIGxhIGZ1bmNpb24gbG0oKSwgY29uIGxhIGRpZmVyZW5jaWEgcXVlIG5lY2VzaXRhIGVsIGFyZ3VtZW50byBgZmFtaWx5PWJpbm9taWFsYCBwYXJhIHF1ZSBSIGNvcnJhIHVuYSByZWdyZXNpb24gbG9naXN0aWNhIGVuIHZleiBkZSBvdHJvIHRpcG8gZGUgbW9kZWxvIGxpbmVhbCBnZW5lcmFsaXphZG8uDQoNCmBgYHtyfQ0KZ2xtLmZpdCA8LSBnbG0oRGlyZWN0aW9uIH4gTGFnMSArIExhZzIgKyBMYWczICsgTGFnNCArIExhZzUgKyBWb2x1bWUsDQogICAgICAgICAgICAgICBkYXRhID0gU21hcmtldCwNCiAgICAgICAgICAgICAgIGZhbWlseSA9IGJpbm9taWFsKQ0Kc3VtbWFyeShnbG0uZml0KQ0KYGBgDQoNCkNvZWZpY2llbnRlcyBkZWwgbW9kZWxvOg0KYGBge3J9DQpjb2VmKGdsbS5maXQpDQpgYGANCg0KU2UgcHVlZGUgYWNjZXNhciB0YW1iaWVuIGEgaW5mb3JtYWNpb24gZXNwZWNpZmljYSBkZWwgbW9kZWxvIHBvciBtZWRpbyBkZToNCmBgYHtyfQ0Kc3VtbWFyeShnbG0uZml0KSRjb2VmDQpgYGANCg0KcC12YWx1ZXMgKHBydWViYSBwYXJhIHF1ZSBlbCBjb2VmaWNpZW50ZSBzZWEgc2lnbmlmaWNhdGl2bykgcGFyYSBsb3MgY29lZmljaWVudGVzOg0KYGBge3J9DQpzdW1tYXJ5KGdsbS5maXQpJGNvZWZbLDRdDQpgYGANCg0KTGEgZnVuY2lvbiAqKnByZWRpY3QoKSoqIHB1ZWRlIHNlciB1c2FkYSBwYXJhIHByZWRlY2lyIHF1ZSBwcm9iYWJpbGlkYWQgdGllbmVuIGVsIG1lcmNhZG8gZGUgc3ViaXIsIGRhZG9zIGxvcyB2YWxvcmVzIGRlIGxvcyBwcmVkaWN0b3Jlcy4NCkRlc3BsZWdhbmRvIGxhcyBwcmltZXJhcyAxMCBwcm9iYWJpbGlkYWRlczoNCmBgYHtyfQ0KZ2xtLnByb2JzIDwtIHByZWRpY3QoZ2xtLmZpdCwgdHlwZT0icmVzcG9uc2UiKQ0KZ2xtLnByb2JzWzE6MTBdDQpgYGANCg0KU2Ugc2FiZSBxdWUgbGFzIHByb2JhYmlsaWRhZGVzIGluZGljYW4gc2kgc3ViZSBvIG5vIHlhIHF1ZSBsYSBmdW5jaW9uICoqY29udHJhc3RzKCkqKiBpbmRpY2EgcXVlIHVuYSBwcm9iYWJpbGlkYWQgZGUgMSBlcyBwYXJhIHF1ZSBlbCBtZXJjYWRvIHN1YmEuDQpgYGB7cn0NCmNvbnRyYXN0cyhEaXJlY3Rpb24pDQpgYGANCg0KUGFyYSBoYWNlciBwcmVkaWNjaW9uZXMgZW4gYmFzZSBhIHNpIGVsIG1lcmNhZG8gdmEgYSBzdWJpciBvIGEgYmFqYXIgZW4gdW4gZGlhIGVuIHBhcnRpY3VsYXIgc2UgbmVjZXNpdGEgY29udmVydGlyIGVzdGFzIHByb2JhYmlsaWRhZGVzIGVuIGxhYmVscyBkZSBjbGFzZTogKipVcCoqIHkgKipEb3duKiouDQpTZSBjcmVhIHVuIHZlY3RvciBjb24gIkRvd24iIHkgYWwgdGVuZXIgdW5hIHByb2JhYmlsaWRhZCBtYXlvciBhIDAuNSBzZSBhc2lnbmEgIlVwIi4NCmBgYHtyfQ0KZ2xtLnByZWQgPC0gcmVwKCJEb3duIiwgMTI1MCkNCmdsbS5wcmVkW2dsbS5wcm9icyA+IDAuNV0gPC0gIlVwIg0KYGBgDQoNCkFob3JhIHNlIHVzYSBsYSBmdW5jaW9uICoqdGFibGUoKSoqIGxhIGN1YWwgcHJvZHVjZSB1bmEgbWF0cml6IGRlIGNvbmZ1Y2lvbiBwYXJhIGRldGVybWluYXIgY3VhbnRhcyBvYnNlcnZhY2lvbmVzIGZ1ZXJvbiBjb3JyZWN0YW1lbnRlIGNsYXNpZmljYWRhcyB5IGN1YWxlcyBuby4NCmBgYHtyfQ0KdGFibGUoZ2xtLnByZWQsIERpcmVjdGlvbikNCmBgYA0KDQpMb3MgZWxlbWVudG9zIGVuIGxhIGRpYWdvbmFsIGRlIGxhIG1hdHJpeiBkZSBjb25mdXNpb24gaW5kaWNhIGxhcyBwcmVkaWNjaW9uZXMgY29ycmVjdGFzLCBtaWVudHJhcyBsb3Mgb3Ryb3MgdmFsb3JlcyBpbmRpY2FuIGxhcyBwcmVkaWNjaW9uZXMgaW5jb3JyZWN0YXMuDQoNCk1lZGlhIGRlIGxhcyBwcmVkaWNjaW9uZXMgYWNlcnRhZGFzOg0KYGBge3J9DQooNTA3KzE0NSkvMTI1MA0KYGBgDQpgYGB7cn0NCm1lYW4oZ2xtLnByZWQgPT0gRGlyZWN0aW9uKQ0KYGBgDQpQb3IgbG8gcXVlIGxhIHRhc2EgZGUgZXJyb3IgZGVsIHRyYWluaW5nIGVzIGRlOg0KYGBge3J9DQoxMDAtNTIuMg0KYGBgDQoNClBhcmEgbWVqb3JhciBsYSBwcmVzaWNpb24gZGVsIG1vZGVsbyBkZSByZWdyZXNpb24gbG9naXN0aWNhIHNlIHB1ZWRlIHVzYXIgc29sbyB1bmEgcGFydGUgZGUgbG9zIGRhdG9zIHBhcmEgZWwgZW50cmVuYW1pZW50byB5IGx1ZWdvIGxhIG90cmEgcGFydGUgcGFyYSBoYWNlciBsYXMgcHJ1ZWJhcy4NCg0KU2UgdmEgYSBjcmVhciB1biB0cmFpbmluZyBzZXQgY29uIGxhcyBvYnNlcnZhY2lvbmVzIGRlc2RlIGVsIGFuaW8gMjAwMSBoYXN0YSBlbCAyMDA0LiANCkVsIG9iamV0byAqKnRyYWluKiogZXMgdW4gdmVjdG9yIGRlIDEyNTAgZWxlbWVudG9zIHF1ZSBjb3JyZXNwb25kZW4gYSBsYXMgb2JzZXJ2YWNpb25lcyBkZWwgZGF0YXNldC4gTG9lIGVsZW1lbnRvcyBkZSBlc2UgdmVjdG9yIHF1ZSBjb3JyZXNwb25kZW4gYSBsYXMgb2JzZXJ2YWNpb25lcyBxdWUgb2N1cnJpZXJvbiBhbnRlcyBkZWwgMjAwNSBzb24gVFJVRSB5IGxhcyBxdWUgbm8gc29uIEZBTFNFLiBMb3MgdmVjdG9yZXMgYm9vbGVhbm9zIHB1ZWRlbiBzZXIgdXNhZG9zIHBhcmEgaGFjZXIgc3Vic2V0cyBkZSBsYXMgZmlsYXMgeSBsYXMgY29sdW1uYXMgZGUgdW5hIG1hdHJpei4gDQpgU21hcnRla1t0cmFpbixdYCBzZXJhIHVuYSBzdWJtYXRyaXogZGVsIGRhdGEgc2V0IGRlIHN0b2NrIG1hcmtldC4gDQpgYGB7cn0NCnRyYWluIDwtIFllYXIgPCAyMDA1DQpTbWFya2V0LjIwMDUgPC0gU21hcmtldFshdHJhaW4sXQ0KZGltKFNtYXJrZXQuMjAwNSkNCkRpcmVjdGlvbi4yMDA1IDwtIERpcmVjdGlvblshdHJhaW5dDQpgYGANCg0KQWhvcmEgc2UgY3JlYXJhIHVuIG1vZGVsbyBkZSByZWdyZXNpb24gbG9naXN0aWNhIHVzc2FuZG8gc29sbyBlbCBzdWJzZXQgZGUgbGFzIG9ic2VydmFjaW9uZXMgcXVlIGNvcnJlc3BvbmRlbiBhIGZlY2hhcyBhbnRlcyBkZWwgMjAwNSwgdXNhbmRvIGVsIGFyZ3VtZW50byBzdWJzZXQuDQpMdWVnbyBzZSBwcmVkaWNlbiBsYXMgcHJvYmFiaWxpZGFkZXMgZGUgcXVlIGVsIG1lcmNhZG8gZGUgYWNjaW9uZXMgc3ViYSBwYXJhIGNhZGEgdW5vIGRlIGVzbyBkaWFzIGVuIGVsIHRlc3Qgc2V0Lg0KYGBge3J9DQpnbG0uZml0IDwtIGdsbShEaXJlY3Rpb24gfiBMYWcxICsgTGFnMiArIExhZzMgKyBMYWc0ICsgTGFnNSArIFZvbHVtZSwNCiAgICAgICAgICAgICAgIGRhdGE9U21hcmtldCwNCiAgICAgICAgICAgICAgIGZhbWlseT1iaW5vbWlhbCwNCiAgICAgICAgICAgICAgIHN1YnNldD10cmFpbikNCmdsbS5wcm9icyA8LSBwcmVkaWN0KGdsbS5maXQsIFNtYXJrZXQuMjAwNSwgdHlwZSA9ICJyZXNwb25zZSIpDQpgYGANCg0KQWhvcmEgc2UgaGFjZW4gbGFzIHByZWRpY2Npb25lcyB5IHNlIGRlc3BsaWVnYSBsYSBtYXRyaXogZGUgY29uZnVzaW9uOg0KYGBge3J9DQpnbG0ucHJlZCA8LSByZXAoIkRvd24iLDI1MikNCmdsbS5wcmVkW2dsbS5wcm9icyA+IDAuNV0gPC0gIlVwIg0KdGFibGUoZ2xtLnByZWQsIERpcmVjdGlvbi4yMDA1KQ0KYGBgDQoNCkVsIHByb21lZGlvIGRlIHByZWRpY2Npb25lcyB5IGxhIHRhc2EgZGUgZXJyb3IgZGVsIG1vZGVsbzoNCmBgYHtyfQ0KbWVhbihnbG0ucHJlZCA9PSBEaXJlY3Rpb24uMjAwNSkNCm1lYW4oZ2xtLnByZWQhPURpcmVjdGlvbi4yMDA1KQ0KYGBgDQoNCkVsIHVzbyBkZSBwcmVkaWN0b3JlcyBxdWUgbm8gdGllbmUgcmVsYWNpb24gY29uIGxhIHJlc3B1ZXN0YSB0aWVuZGUgYSBjYXVzYXIgcXVlIGxhIHRhc2EgZGUgZXJyb3IgZW1wZW9yZSwgeWEgcXVlIGxvcyBwcmVkaWN0b3JlcyBjYXVzYW4gdW4gYXVtZW50byBlbiBsYSB2YXJpYW56YSBzaW4gdW5hIGRpc21pbnVjaW9uIGVuIGVsIGJpYXMuDQoNCkFob3JhIHNvbG8gc2UgdXNhcmFuIGxhcyB2YXJpYWJsZXMgKkxhZzEqIHkgKkxhZzIqIGxvcyBjdWFsZXMgcGFyZWNlbiBzZXIgbG9zIGVzdGFkaXN0aWNhbWVudGUgbWFzIHNpZ25pZmljYXRpdm9zLg0KDQpgYGB7cn0NCmdsbS5maXQgPC0gZ2xtKERpcmVjdGlvbiB+IExhZzEgKyBMYWcyLA0KICAgICAgICAgICAgICAgZGF0YSA9IFNtYXJrZXQsDQogICAgICAgICAgICAgICBmYW1pbHkgPSBiaW5vbWlhbCwNCiAgICAgICAgICAgICAgIHN1YnNldCA9IHRyYWluKQ0KZ2xtLnByb2JzIDwtIHByZWRpY3QoZ2xtLmZpdCwgU21hcmtldC4yMDA1LCB0eXBlID0gInJlc3BvbnNlIikNCmdsbS5wcmVkIDwtIHJlcCgiRG93biIsIDI1MikNCmdsbS5wcmVkW2dsbS5wcm9icyA+IDAuNV0gPC0gIlVwIg0KdGFibGUoZ2xtLnByZWQsRGlyZWN0aW9uLjIwMDUpDQpgYGANCg0KYGBge3J9DQptZWFuKGdsbS5wcmVkID09IERpcmVjdGlvbi4yMDA1KQ0KYGBgDQoNCmBgYHtyfQ0KMTA2LygxMDYrNzYpDQpgYGANCg0KQWhvcmEgbGFzIHByZWRpY2Npb25lcyB0ZW5kcmFuIHVuIHBvcmNlbnRhamUgbWF5b3I6IDU2JQ0KU3Vwb25pZW5kbyBxdWUgcXVlcmVtb3MgcHJlZGVjaXIgbGEgZGlyZWNjaW9uIGRlbCBtZXJjYWRvIGVuIHVuIGRpYSBkZXRlcm1pbmFkbyBjdWFuZG8gYExhZzFgIHkgYExhZzJgIHNvbiBpZ3VhbGVzIGEgMS4yICB5IDEuMSByZXNwZWN0aXZhbWVudGUgeSBlbiBvdHJvIGRpYSBzb24gaWd1YWxlcyBhIDEuNSB5IC0wLjguIA0KDQpgYGB7cn0NCnByZWRpY3QoZ2xtLmZpdCwgbmV3ZGF0YSA9IGRhdGEuZnJhbWUoTGFnMSA9IGMoMS4yLDEuNSksIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBMYWcyID0gYygxLjEsIC0wLjgpKSwNCiAgICAgICAgdHlwZSA9ICJyZXNwb25zZSIpDQpgYGANCg0KIyMgNC42LjMgTGluZWFyIERpc2NyaW1pbmFudCBBbmFseXNpcw0KDQpBaG9yYSBzZSB1c2Fy4SBMREEgY29uIGVsIGRhdGFzZXQgZGUgU21hcmtldC4gVXNhcmVtb3MgYGxkYSgpYCBkZSBsYSBsaWJyZXJpYSBgTUFTU2AuIEVzdGEgZnVuY2lvbiBzZSBwYXJlY2UgYSBsYSBmdW5jaW9uIGBsbSgpYCB5IGBnbG0oKWAgZXhjZXB0byBwb3IgZWwgYXJndW1lbnRvIGRlIGBmYW1pbHlgLiBVc2FyZW1vcyBsYXMgb2JzZXJ2YWNpb25lcyBkZSBhbnRlcyBkZWwgYfFvIDIwMDUuDQoNCmBgYHtyfQ0KbGlicmFyeShNQVNTKQ0KYGBgDQoNCmBgYHtyfQ0KbGRhLmZpdCA8LSBsZGEoRGlyZWN0aW9uIH4gTGFnMSArIExhZzIsDQogICAgICAgICAgICAgICBkYXRhID0gU21hcmtldCwNCiAgICAgICAgICAgICAgIHN1YnNldCA9IHRyYWluKQ0KbGRhLmZpdA0KYGBgDQoNCmBgYHtyfQ0KcGxvdChsZGEuZml0KQ0KYGBgDQoNCkxvcyBjb2VmaWNpZW50ZXMgZGVsIGRpc2NyaW1pbmFudGUgbGluZWFsIHNvbiB1bmEgY29tYmluYWNpb24gbGluZWFsIGRlIGBMYWcxYCB5IGBMYWcyYC4NClNpICQ/Pz8wLjY0MkxhZ18xPz8/MC41MTRMYWdfMiQgZXMgbWF5b3IgZWwgY2xhc2lmaWNhZG9yIExEQSB2YSBhIHByZWRlY2lyIHVuYSBlbGV2YWNpb24gZW4gZWwgbWVyY2FkbyB5IHNpIGVzIHBlcXVl8W8gdmEgYSBwcmVkZWNpciB1bmEgZGVjbGluYWNpb24gcGFyYSBlbCBtZXJjYWRvLiANCkxhIGZ1bmNpb24gYHByZWRpY3QoKWAgZGV2dWVsdmUgdW5hIGxpc3RhIGRlIHRyZXMgZWxlbWVudG9zLiBFbCBwcmltZXIgZWxlbWVudG8gKmNsYXNzKiBjb250aWVuZSBsb3MgcHJlZGljdG9yZXMgZGUgTERBIGFjZXJjYSBkZWwgbW92aW1pZW50byBkZWwgbWVyY2Fkby4gRWwgc2VndW5kbyBlbGVtZW50byAqcG9zdGVyaW9yKiBlcyB1bmEgbWF0cml6IGRvbmRlIGxhIGstZXNpbWEgY29sdW1uYSBjb250aWVuZSBsYSBwcm9iYWJpbGlkYWQgcG9zdGVyaW9yIHF1ZSBjb3JyZXNwb25kZSBhIHVuYSBvYnNlcnZhY2lvbiBxdWUgcGVydGVuZWNlIGEgbGEgY2xhc2Ugay1lc2ltYS4gRmluYWxtZW50ZSAqeCogY29udGllbmUgbG9zIGRpc2NyaW1pbmF0ZXMgbGluZWFsZXMuDQoNCmBgYHtyfQ0KbGRhLnByZWQgPC0gcHJlZGljdChsZGEuZml0LCBTbWFya2V0LjIwMDUpDQpuYW1lcyhsZGEucHJlZCkNCmBgYA0KDQpgYGB7cn0NCmxkYS5jbGFzcyA8LSBsZGEucHJlZCRjbGFzcyANCnRhYmxlKGxkYS5jbGFzcyxEaXJlY3Rpb24uMjAwNSkNCmBgYA0KDQpgYGB7cn0NCm1lYW4obGRhLmNsYXNzID09IERpcmVjdGlvbi4yMDA1KQ0KYGBgDQoNCkFwbGljYW5kbyBlbCB0aHJlc2hvbGQgZGUgMC41IHBhcmEgdGVuZXIgbGFzIHByZWRpY2Npb25lczoNCmBgYHtyfQ0Kc3VtKGxkYS5wcmVkJHBvc3RlcmlvclssMV0gPj0gMC41KQ0KYGBgDQpgYGB7cn0NCnN1bShsZGEucHJlZCRwb3N0ZXJpb3JbLDFdIDwgMC41KQ0KYGBgDQoNCmBgYHtyfQ0KbGRhLnByZWQkcG9zdGVyaW9yWzE6MjAsMV0NCmxkYS5jbGFzc1sxOjIwXQ0KYGBgDQoNClN1cG9uZXIgcXVlIHF1ZXJlbW9zIHByZWRlY2lyIHF1ZSBlbCBtZXJjYWRvIHZhIGEgZGVjcmVtZW50YXIgc29sYW1lbnRlIHNpIGVzdGFtb3MgY2VydGVyb24gcXVlIGVsIG1lcmNhZG8gdmEgYSBkZWNyZWNlciBlbiBlc2UgZGlhLCBzaSBsYSBwcm9iYWJpbGlkYWQgcG9zdGVyaW9yIGVzIGFsIG1lbm9zIGRlbCA5MCUNCmBgYHtyfQ0Kc3VtKGxkYS5wcmVkJHBvc3RlcmlvclssMV0gPiAwLjkpDQpgYGANClBvciBsbyBxdWUgbmluZ3VuIGRpYSBkZWwgMjAwNSBjb2luY2lkZSBjb24gZXNlIHRocmVzaG9sZC4gDQoNCiMjIDQuNi40IFF1YWRyYXRpYyBEaXNjcmltaW5hbnQgQW5hbHlzaXMgDQoNClVzYXJlbW9zIGxhIGZ1bmNpb24gYHFkYSgpYCBsYSBjdWFsIHRhbWJpZW4gZXN0YSBlbiBsYSBsaWJyZXJpYSBgTUFTU2AuIExhIHNpbnRheGlzIGRlIGVzdGEgZnVuY2lvbiBlcyBpZGVudGljYSBhIGxhIGBsZGEoKWAuDQpgYGB7cn0NCnFkYS5maXQgPC0gcWRhKERpcmVjdGlvbiB+IExhZzEgKyBMYWcyLA0KICAgICAgICAgICAgICAgZGF0YSA9IFNtYXJrZXQsDQogICAgICAgICAgICAgICBzdWJzZXQgPSB0cmFpbikNCnFkYS5maXQNCmBgYA0KDQpMYSBmdW5jaW9uIGBxZGFgIGNvbnRpZW5lIGxhcyBtZWRpYXMgZGUgbG9zIGdydXBvcy4gUGVybyBubyBjb250aWVuZSBsb3MgY29lZmljaWVudGVzIGRlbCBkaXNjcmltaW5hbnRlIGxpbmVhbCwgeWEgcXVlIGVsIGNsYXNpZmljYWRvciBRREEgaW52b2x1Y3JhIHVuYSBmdW5jaW9uIGN1YWRyYXRpY2EgZGUgcHJlZGljdG9yZXMuIExhIGZ1bmNpb24gYHByZWRpY3QoKWAgZnVuY2lvbmEgZXhhY3RhbWVudGUgaWd1YWwgcXVlIExEQS4NCmBgYHtyfQ0KcWRhLmNsYXNzIDwtIHByZWRpY3QocWRhLmZpdCwgU21hcmtldC4yMDA1KSRjbGFzcw0KdGFibGUocWRhLmNsYXNzLCBEaXJlY3Rpb24uMjAwNSkNCmBgYA0KDQpgYGB7cn0NCm1lYW4ocWRhLmNsYXNzID09IERpcmVjdGlvbi4yMDA1KQ0KYGBgDQpQb3IgbG8gcXVlIHNlIHB1ZWRlIHZlciBxdWUgZWwgbW9kZWxvIFFEQSBlcyBwcmVjaXNvIGVsIDYwJSBkZSBsYXMgdmVjZXMgaW5jbHVzbyBzaW4gbG9zIGRhdG9zIGRlbCAyMDA1LiBQb3IgbG8gcXVlIHNlIHB1ZWRlIGRlY2lyIHF1ZSBlbCBtb2RlbG8gUURBIGNhcHR1cmEgZGUgZm9ybWEgbWFzIHByZWNpc2EgbGEgcmVsYWNpb24gZGUgbGFzIHZhcmlhYmxlcywgZGUgbWVqb3IgbWFuZXJhIHF1ZSBMREEgeSBsYSByZWdyZXNpb24gbG9naXN0aWNhLg0KDQojIyA0LjYuNSBLLU5lYXJlc3QgTmVpZ2hib3JzDQoNCkFob3JhIHNlIHVzYXJhIEtOTiB1c2FuZG8gbGEgZnVuY2lvbiBga25uKClgIHF1ZSBlcyBwYXJ0ZSBkZSBsYSBsaWJyZXJpYSBgY2xhc3NgLiBFc3RhIGZ1bmNpb24gZXMgZGlzdGludGEgYSBsYXMgb3RyYXMgeWEgcXVlIG5vIG5lY2VzaXRhIGRlIGRvcyBwYXNvcyBwYXJhIGhhY2VyIGxhcyBwcmVkaWNjaW9uZXMuIE5lY2VzaXRhIGN1YXRybyBpbnB1dHMuDQoNCjEuIFVuYSBtYXRyaXogcXVlIGNvbnRlbmdhIGxvcyBwcmVkaWN0b3JlcyBhc29jaWFkc3MgY29uIGxvcyBkYXRvcyBjb24gZWwgdHJhaW4gZGF0YS4gRW4gZXN0ZSBlamVyY2ljaW8gc2UgbGxhbWFyYSBgdHJhaW4uWGAuDQoNCjIuIFVuYSBtYXRyaXogcXVlIGNvbnRlbmdhIGxvcyBwcmVkaWN0b3JlcyBhc29jaWFkb3MgY29uIGxvcyBkYXRvcyBjb24gbG9zIGN1YWxlcyBzZSBxdWllcmVuIGhhY2VyIHByZWRpY2Npb25lcy4gRXN0YSBtYXRyaXogc2UgbGxhbWFyYSBgdGVzdC5YYC4NCg0KMy4gVW4gdmVjdG9yIHF1ZSBjb250aWVuZSBsYXMgZXRpcXVldGFzIGRlIGNsYXNlIHBhcmEgbGFzIG9ic2VydmFjaW9uZXMgZGVsIHRyYWluaW5nLCBgdHJhaW4uRGlyZWN0aW9uYC4NCg0KNC4gVW4gdmFsb3IgZGUgaywgZWwgbnVtZXJvIGRlIHZlY2luZGFkZXMgY2VyY2FuYXMgcGFyYSB1c2FyIGVuIGVsIGNsYXNpZmljYWRvci4NCg0KU2UgdXNhcmEgbGEgZnVuY2lvbiBgY2JpbmQoKWAgcGFyYSBqdW50YXIgbGFzIHZhcmlhYmxlcyBgTGFnMWAgeSBgTGFnMmAgZW4gZG9zIG1hdHJpY2VzOg0KYGBge3J9DQpsaWJyYXJ5KGNsYXNzKQ0KdHJhaW4uWCA8LSBjYmluZChMYWcxLCBMYWcyKVt0cmFpbixdDQp0ZXN0LlggPC0gY2JpbmQoTGFnMSxMYWcyKVshdHJhaW4sXQ0KdHJhaW4uRGlyZWN0aW9uIDwtIERpcmVjdGlvblt0cmFpbl0NCmBgYA0KDQpBaG9yYSB1c2Ftb3MgbGEgZnVuY2lvbiBga25uKClgIHBhcmEgcHJlZGVjaXIgbG9zIG1vdmltaWVudG9zIGRlbCBtZXJjYWRvIGVuIDIwMDUuIEhhY2Vtb3MgdW4gc2V0IGRlIHVuIHJhbmRvbSBhbnRlcyBkZSBhcGxpY2FyIGxhIGZ1bmNpb24sIGVzdG8gYXNlZ3VyYSBxdWUgc2UgcHVlZGEgcmVwcm9kdWNpciBzaWVtcHJlIGVsIG1pc21vIHJlc3VsdGFkby4NCmBgYHtyfQ0Kc2V0LnNlZWQoMSkNCmtubi5wcmVkIDwtIGtubih0cmFpbi5YLCB0ZXN0LlgsIHRyYWluLkRpcmVjdGlvbiwgayA9IDEpDQp0YWJsZShrbm4ucHJlZCwgRGlyZWN0aW9uLjIwMDUpDQpgYGANCg0KYGBge3J9DQooNDMrODMpLzI1Mg0KYGBgDQoNCkxhcyBwcmVkaWNjaW9uZXMgaGVjaGFzIGNvbiAkaz0xJCBubyBzb24gbXV5IGJ1ZW5hcyB5YSBxdWUgc29sbyBlbCAkNTBcJSQgZGUgbGFzIHByZWRpY2Npb25lcyBzb24gY29ycmVjdGFzLiBBaG9yYSBzZSByZXByb2R1Y2UgZWwgbW9kZWxvIHVzYW5kbyAkaz0zJA0KYGBge3J9DQprbm4ucHJlZCA8LSBrbm4odHJhaW4uWCwgdGVzdC5YLCB0cmFpbi5EaXJlY3Rpb24sIGsgPSAzKQ0KdGFibGUoa25uLnByZWQsIERpcmVjdGlvbi4yMDA1KQ0KYGBgDQoNCmBgYHtyfQ0KbWVhbihrbm4ucHJlZCA9PSBEaXJlY3Rpb24uMjAwNSkNCmBgYA0KDQpTZSBwdWVkZSB2ZXIgcXVlIGh1Ym8gdW5hIHBlcXVl8WEgbWVqb3JhLg0KUG9yIGxvIHF1ZSBzZSBwdWVkZSBjb25jbHVpciBxdWUgcGFyYSBlc3RlIGFuYWxpc2lzIGVsIG1lam9yIG1vZGVsbyBlcyBlbCBRREEuDQoNCiMjIDQuNi42IEFuIEFwcGxpY2F0aW9uIHRvIENhcmF2YW4gSW5zdXJhbmNlIERhdGENCg0KRmluYWxtZW50ZSBzZSB2YSBhIGFwbGljYXIgZWwgbWV0b2RvIEtOTiBhbCBkYXRhc2V0IENhcmF2YW4sIHF1ZSBlcyBwYXJ0ZSBkZSBsYSBsaWJyZXJpYSBJU0xSLiBFc3RlIHNldCBkZSBkYXRvcyBpbmNsdXllICQ4NSQgcHJlZGljdG9yZXMgcXVlIG1pZGVuIGxhcyBjYXJhY3RlcmlzdGljYXMgZGVtb2dyYWZpY2FzIGRlICQ1ODIyJCBpbmRpdmlkdW9zLiBMYSB2YXJpYWJsZSBkZSByZXNwdWVzdGEgc2VyYSBgUHVyY2hhc2VgLCBxdWUgaW5kaWNhIHNpIHVuIGluZGl2aWR1byBjb21wcmEgbyBubyB1bmEgcG9saXphIGRlIHNlZ3VybyBkZSBjYXJhdmFuYS4gU29sbyBlbCAkNlwlJCBkZSBsYXMgcGVyc29uYXMgY29tcHJhcm9uIHVuIHNlZ3VybyBkZSBjYXJhdmFuYS4NCmBgYHtyfQ0KZGltKENhcmF2YW4pDQpgYGANCg0KYGBge3J9DQphdHRhY2goQ2FyYXZhbikNCmBgYA0KDQpgYGB7cn0NCnN1bW1hcnkoUHVyY2hhc2UpDQpgYGANCg0KYGBge3J9DQozNDgvNTgyMg0KYGBgDQoNClBhcmEgZXN0ZSBwcm9ibGVtYSBzZSB2YSBhIGVzdGFuZGFyaXphciwgcGFyYSBxdWUgdG9kYXMgbGFzIHZhcmlhYmxlcyB0ZW5nYW4gdW5hIG1lZGlhIGRlICQwJCB5IHVuYSBkZXN2aWFjaW9uIGVzdGFuZGFyIGRlICQxJC4gRXN0byBsbyBoYWNlIGxhIGZ1bmNpb24gYHNjYWxlKClgLg0KYGBge3J9DQpzdGFuZGFyZGl6ZWQuWCA8LSBzY2FsZShDYXJhdmFuWywtODZdKQ0KdmFyKENhcmF2YW5bLDFdKQ0KdmFyKENhcmF2YW5bLDJdKQ0KdmFyKHN0YW5kYXJkaXplZC5YWywxXSkNCnZhcihzdGFuZGFyZGl6ZWQuWFssMl0pDQpgYGANCg0KQWhvcmEgY2FkYSBjb2x1bW5hIGRlbCBkYXRhc2V0IGBzdGFuZGFyZGl6ZWQuWGAgdGllbmUgdW5hIG1lZGlhIGRlICQwJCB5IHVuYSBkZXN2aWFjaW9uIGVzdGFuZGFyIGRlICQxJC4NCkFob3JhIHNlIHZhIGEgY3JlYXIgdW4gdGVzdCBkYXRhIHF1ZSBjb250ZW5nYSBsYXMgcHJpbWVyYXMgJDEwMDAkIG9ic2VydmFjaW9uZXMgeSB1biB0cmFpbmluZyBzZXQgcXVlIGNvbnRlbmdhIGVsIHJlc3RvLg0KYGBge3J9DQp0ZXN0IDwtIDE6MTAwMA0KdHJhaW4uWCA8LSBzdGFuZGFyZGl6ZWQuWFstdGVzdCxdDQp0ZXN0LlggPC0gc3RhbmRhcmRpemVkLlhbdGVzdCxdDQp0cmFpbi5ZIDwtIFB1cmNoYXNlWy10ZXN0XQ0KdGVzdC5ZIDwtIFB1cmNoYXNlW3Rlc3RdDQpzZXQuc2VlZCgxKQ0Ka25uLnByZWQgPC0ga25uKHRyYWluLlgsIHRlc3QuWCwgdHJhaW4uWSwgaz0xKQ0KbWVhbih0ZXN0LlkgIT0ga25uLnByZWQpDQptZWFuKHRlc3QuWSAhPSAiTm8iKQ0KYGBgDQoNCk9idGVuZW1vcyBsYSBtYXRyaXogZGUgY29uZnVzaW9uOg0KYGBge3J9DQp0YWJsZShrbm4ucHJlZCwgdGVzdC5ZKQ0KYGBgDQpgYGB7cn0NCjkvKDY4KzkpDQpgYGANClVzYW5kbyBhaG9yYSAkaz0zJA0KYGBge3J9DQprbm4ucHJlZCA8LSBrbm4odHJhaW4uWCwgdGVzdC5YLCB0cmFpbi5ZLCBrPTMpDQp0YWJsZShrbm4ucHJlZCwgdGVzdC5ZKQ0KYGBgDQoNCmBgYHtyfQ0KNS8yNg0KYGBgDQoNCmBgYHtyfQ0Ka25uLnByZWQgPC0ga25uICh0cmFpbi5YLCB0ZXN0LlgsIHRyYWluLlksIGsgPSA1KQ0KdGFibGUoa25uLnByZWQsIHRlc3QuWSkNCjQvMTUNCmBgYA0KDQpQYXJhIGNvbXBhcmFyIHRhbWJpZW4gc2UgdXNhcmEgdW5hIHJlZ3Jlc2lvbiBsb2dpc3RpY2EuIFVzYW1vcyBlbCB2YWxvciBkZSAkMC41JCBwYXJhIGxhIHByb2JhYmlsaWRhZCBkZWwgY2xhc2lmaWNhZG9yDQpgYGB7cn0NCmdsbS5maXQgPC0gZ2xtKFB1cmNoYXNlIH4gLiwgDQogICAgICAgICAgICAgICBkYXRhID0gQ2FyYXZhbiwNCiAgICAgICAgICAgICAgIGZhbWlseSA9Ymlub21pYWwsDQogICAgICAgICAgICAgICBzdWJzZXQgPSAtdGVzdCkNCmdsbS5wcm9icyA8LSBwcmVkaWN0IChnbG0uZml0LCBDYXJhdmFuW3Rlc3QsXSwgdHlwZSA9ICJyZXNwb25zZSIpDQpnbG0ucHJlZCA8LSByZXAgKCJObyAiLCAxMDAwKQ0KZ2xtLnByZWRbZ2xtLnByb2JzID4gMC41XSA8LSAiWWVzIg0KdGFibGUoZ2xtLnByZWQsIHRlc3QuWSkNCmdsbS5wcmVkIDwtIHJlcCgiTm8iLCAxMDAwKQ0KZ2xtLnByZWRbZ2xtLnByb2JzID4gMC4yNV0gPC0gIlllcyINCnRhYmxlKGdsbS5wcmVkLCB0ZXN0LlkpDQoxMS8oMjIrMTEpDQpgYGANClBvciBsbyBxdWUgcG9kZW1vcyBwcmVkZWNpciBkZSBtYW5lcmEgY29ycmVjdGEgZW4gJDMzXCUkIGRlIGVzdGFzIHBlcnNvbmFzLiBMbyBjdWFsIGVzIG11Y2hvIG1lam9yIHF1ZSBhZGl2aW5hciByYW5kb20uDQo=