Quiero que quede claro que no tengo idea de lo que estoy apunto de escribir, únicamente es una compilación de documentos que he encontrado sobre modelado de control sintético. Por lo tanto, descubriremos juntos y al mismo tiempo (eso no es verdad) que hace cada una de las siguientes lineas. Por favor no juzgues, tuve la decencia de advertirte.

-C







Para el modelado de control sintético utilizarémos dos métodos con dos paqueterias distintas el primero Synth de Jens Hainmueller and Alexis Diamond de las univesidades Stanford y Harvard, respectivamente. La documentación al respecto se encuentra aqui

La segunda paquetería, tidysynth que esta construida sobre la paquetería anterior. Hasta donde entiendo, la creación de esta paqueteria le corresponde a Eric Dunford de Meta y puedes consultar el readme completo aqui




Paquetería Synth

Lo primero será instalar la librería:

library(Synth) 

Una vez cargada la librería podremos acceder a la base de datos panel mediante el siguiente comando:

data("synth.data")

que es un data frame con datos por año desde 1980 hasta el año 2000, tanto para el grupo de control (\(\textit{donor pool}\)), como para la unidad de tratamiento (treated.region).

La organización de la base de datos anterior está determinada de la siguiente manera:

Vamos a tomar los valores medios de cada predictor para todos los años, es decir:

\[\frac{1}{T}\sum_{t=1}^{T}x_{ijt}\] es la entrada de la fila \(i\) y la columna \(j\) de la matriz \(X_{0}\), que es la matriz de predictores para las unidades \(j=1,2,...,J\) e \(i=1,2,3\)

En este ejemplo la matriz de predictores \(X_{1}\) está determinada como sigue:

X1 <- filter(synth.data, name == "treated.region") %>% 
  select("X1","X2","X3")
X1.1 <- matrix(rep(0,3),ncol=3) 
for (i in  1:ncol(X1.1)){
  X1.1[,i]<-mean(X1[,i],na.rm = T)
}

X1.1
         [,1]     [,2]     [,3]
[1,] 0.267079 15.61667 21.68571

mientras que la matriz de predictores con las mismas variables para \(j=2,...,8\), \(X_{0}\), la construimos de la siguiente manera:

X2 <- filter(synth.data, name == "control.region.northeast") %>% 
  select("X1","X2","X3")              
X2.1 <- matrix(rep(0,3),ncol=3) 
for (i in  1:ncol(X2.1)){
  X2.1[,i]<-mean(X2[,i],na.rm = T)
}
colnames(X2.1) <- colnames(X2)                    #Estas son las matrices con los valores medios por columna

X3 <- filter(synth.data, name == "control.region.southcentral") %>% 
  select("X1","X2","X3")
X3.1 <- matrix(rep(0,3),ncol=3) 
for (i in  1:ncol(X3.1)){
  X3.1[,i]<-mean(X3[,i],na.rm = T)
}


X4 <- filter(synth.data, name == "control.region.west") %>% 
  select("X1","X2","X3")
X4.1 <- matrix(rep(0,3),ncol=3) 
for (i in  1:ncol(X4.1)){
  X4.1[,i]<-mean(X4[,i],na.rm = T)
}


X5 <- filter(synth.data, name == "control.region.southeast") %>% 
  select("X1","X2","X3")
X5.1 <- matrix(rep(0,3),ncol=3) 
for (i in  1:ncol(X5.1)){
  X5.1[,i]<-mean(X5[,i],na.rm = T)
}


X6 <- filter(synth.data, name == "control.region.central") %>% 
  select("X1","X2","X3")
X6.1 <- matrix(rep(0,3),ncol=3) 
for (i in  1:ncol(X6.1)){
  X6.1[,i]<-mean(X6[,i],na.rm = T)
}


X7 <- filter(synth.data, name == "control.region.south") %>% 
  select("X1","X2","X3")
X7.1 <- matrix(rep(0,3),ncol=3) 
for (i in  1:ncol(X7.1)){
  X7.1[,i]<-mean(X7[,i],na.rm = T)
}


X8 <- filter(synth.data, name == "control.region.east") %>% 
  select("X1","X2","X3")
X8.1 <- matrix(rep(0,3),ncol=3) 
for (i in  1:ncol(X8.1)){
  X8.1[,i]<-mean(X8[,i],na.rm = T)
}


X0 <- rbind(X2.1,X3.1,X4.1,X5.1,X6.1,X7.1,X8.1)
X0
            X1        X2       X3
[1,] 0.2420620 20.122222 19.30000
[2,] 0.2579195 17.905555 19.12143
[3,] 0.2614878 23.894445 22.72143
[4,] 0.2657048 10.650000 23.95000
[5,] 0.2551758 17.811111 21.16429
[6,] 0.2702358 10.900000 22.41429
[7,] 0.2588663  9.822222 29.91429

Ahora vamos a preparar de manera correcta los datos utilizando la función dataprep:

dataprep.out <- dataprep(foo = synth.data,                        #Cargar los datos
                         predictors = c("X1", "X2", "X3"),        #Matriz de predictores 
                         predictors.op = "mean",                  #El operador que se utilizará en los predictores 
                         dependent = "Y",                         #Outcome
                         unit.variable = "unit.num",              
                         time.variable = "year",                  #Frecuencia temporal de la base
                         special.predictors = list(               #Predictores numéricos adicionales
                           list("Y", 1991, "mean"),               #que asocia periodos previos al
                           list("Y", 1985, "mean"),               #tratamiento y operadores "mean".
                           list("Y", 1980, "mean")),
                         treatment.identifier = 7,                #id de la unidad de tratamiento
                         controls.identifier = c(2, 13, 17, 
                                                 29, 32, 36, 38),  #id de las unidades de control
                         time.predictors.prior = c(1980:1989),    #Un vector numérico que contiene los periodos previos al tratamiento sobre los que el predictor será promediado.
                         time.optimize.ssr = c(1980:1989),        #Un vector numérico que identifica los periodos de la variable dependiente sobre los cuales la función de perdida se minimiza (i.e., la minimización de la suma de los residuos al cuadrado entre la unidad de tratamiento y la unidad de control.)
                         unit.names.variable = "name",            #Nombre de las unidades de control
                         time.plot = 1980:1996)                   #Un vector que identifica los períodos durante los cuales se trazarán los resultados con gaps.plot y path.plot.

 Missing data- treated unit; predictor: X1 ; for period: 1980 
 We ignore (na.rm = TRUE) all missing values for predictors.op.

 Missing data- treated unit; predictor: X3 ; for period: 1980 
 We ignore (na.rm = TRUE) all missing values for predictors.op.

 Missing data- treated unit; predictor: X3 ; for period: 1981 
 We ignore (na.rm = TRUE) all missing values for predictors.op.

 Missing data- treated unit; predictor: X3 ; for period: 1982 
 We ignore (na.rm = TRUE) all missing values for predictors.op.

 Missing data- treated unit; predictor: X3 ; for period: 1983 
 We ignore (na.rm = TRUE) all missing values for predictors.op.

 Missing data - control unit: 2 ; predictor: X1 ; for period: 1980 
 We ignore (na.rm = TRUE) all missing values for predictors.op.

 Missing data - control unit: 2 ; predictor: X3 ; for period: 1980 
 We ignore (na.rm = TRUE) all missing values for predictors.op.

 Missing data - control unit: 2 ; predictor: X3 ; for period: 1981 
 We ignore (na.rm = TRUE) all missing values for predictors.op.

 Missing data - control unit: 2 ; predictor: X3 ; for period: 1982 
 We ignore (na.rm = TRUE) all missing values for predictors.op.

Note que para generar las matrices \(X_{0}\) tomamos los promedios por año de las observaciones para cada variable \(X_{j}\) con \(j=1,2,3\) y las variables especiales son simplemente las observaciones para \(Y\) de los años 1980, 1985 y 1990.

Para generar el control sintetico, es decir el vector de ponderadores \(W=(\omega_{2},...,\omega_{J+1})^{T}\) tal que

\[Y_{1t}^{N}=\sum_{j=2}^{J+1} \omega_{j} Y_{jt}\]

donde \(Y_{1t}^{N}\) es el outcome de interés en ausencia de la intevención, para \(t>T_{0}\), el periodo en el que ocurre la intervención.

synth.out <- synth(dataprep.out)            #Genera el vector de 6x1 entradas del control sintetico para cada predictor

X1, X0, Z1, Z0 all come directly from dataprep object.


**************** 
 searching for synthetic control unit  
 

**************** 
**************** 
**************** 

MSPE (LOSS V): 3.320744 

solution.v:
 1.231e-07 0.001232258 0.003208833 0.3528442 0.005254982 0.6374596 

solution.w:
 0.104716 0.005893509 0.03580648 0.06678495 0.5510233 0.1552163 0.08055942 
round(synth.out$solution.w,2)               #Redondea y presenta las variables a dos caracteres después del punto decimal
   w.weight
2      0.10
13     0.01
17     0.04
29     0.07
32     0.55
36     0.16
38     0.08
gaps<- dataprep.out$Y1plot-(
        dataprep.out$Y0plot%*%synth.out$solution.w)  #Esta ecuación es simplemente el tau_1t para todos los periodos.

Como habíamos convenido en la entrada de teoría de control sintético uno de los requerimientos para valorar el vector \(W^{*}\) es la regla del convex hull, es decir

\[X_{1}-X_{0}W^{*}\approx 0\]

t(X1.1)-(t(X0)%*%synth.out$solution.w)
       w.weight
X1  0.009696174
X2 -0.460281992
X3 -0.412026145

De acuerdo con la teoría, para obtener mayor aproximación tendriamos que utilizar más información para antes del periodo de intervención.

Finalmente, para presentar de manera gráfica los datos utilizamos las funciones path.plot() para graficar las trayectorias observadas y las sintéticas, y gaps.plot() para gráficar las diferencias entre la serie observada y la sintética en todo el horizonte temoporal.

synth.tables <- synth.tab(
      dataprep.res = dataprep.out,
      synth.res = synth.out)                       #Presenta los resultados del modelo en una tabla
print(synth.tables)
$tab.pred
               Treated Synthetic Sample Mean
X1               0.267     0.257       0.259
X2              16.140    17.079      16.554
X3              21.717    22.046      22.795
special.Y.1991 115.000   115.016     117.371
special.Y.1985 128.800   127.715     132.457
special.Y.1980 134.000   134.028     145.757

$tab.v
               v.weights
X1             0        
X2             0.001    
X3             0.003    
special.Y.1991 0.353    
special.Y.1985 0.005    
special.Y.1980 0.637    

$tab.w
   w.weights                  unit.names unit.numbers
2      0.105    control.region.northeast            2
13     0.006 control.region.southcentral           13
17     0.036         control.region.west           17
29     0.067    control.region.southeast           29
32     0.551      control.region.central           32
36     0.155        control.region.south           36
38     0.081         control.region.east           38

$tab.loss
           Loss W   Loss V
[1,] 6.647013e-05 3.320744
path.plot(dataprep.res = dataprep.out,
          synth.res = synth.out,
          Main = "Trayectorias de control sintético")                  #Grafica las trayectorias de la unidad tratada y su trayectoria sintética
abline(v = 1991, col = "red", lty = 2)
text(1991, 100,"Periodo de \n Tratamiento (T0) ",
     pos = 2, col ="red")



gaps.plot(dataprep.res = dataprep.out,
          synth.res = synth.out,
          Main = "Brechas del cotrol sintético")                  #Grafica las discrepancias entre la unidad tratada y su trayectoria sintética
abline(v = 1991, col = "red", lty = 2)
text(1991, 20, "Periodo de \n Tratamiento (T0) ",
     pos = 2, col ="red")

Paquetería tidysynth

Ahora vamos a trabajar con la libreria tidysynth, lo pimero que hay que hacer es instalar la libreria:

library(tidysynth)

La base de datos que vamos a utilizar proviene de Abadie et al. (2010). La base de datos se compone de la siguiente manera:

En este ejemplo, el outcome de interés es la venta de cigarrillos per-capita, y la unidad de tratamiento es el estado de California, mientras que las unidades de conrrol serán el resto de los estaods de Estados Unidos. Lo que se intenta estimar es la trayectoria sintética de la venta de cigarrillos en California de no haberse aplicado el tratamiento, que es, la llamada Proposition 99 que consistió en un aumento de 25 centavos por paquete a través de un impuesto.

data("smoking")

Para preparar los datos como en la paquetería Synth utilizamos la funciónm synthetic_control() de la manera siguiente, la matriz predictores \(X_{0}\) y \(X_{1}\) con la función generate_predictors(), y generar los ponderadores con la función generate_controls(). Dado que la paqueteria es una implementación tidy la concatenación de las funciones se hace a través del comando %>%, como se muestra a continuación:

smoking_out <-
  
  smoking %>%                                      #Este es el data frame que vamos a utilizar
  synthetic_control(outcome = cigsale,             #El outcome de interés
                    unit = state,                  #Vamos a utilizar la columna state como indice de las unidades
                    time = year,                   #Con indice de tiempo de la columna year
                    i_unit = "California",         #La unidad de tratamiento j=1
                    i_time = 1988,                 #El periodo de tratamiento T0
                    generate_placebos=T            #Generaremos placebos para hacer inferencia.
                    ) %>%
  
  # Vamos a generar los predictores agregados para ajustar los ponderadores:
  # promedio del logaritmo del ingreso, el precio de los cigarrillos y la 
  # prporcion de la población entre 15 y 24 años de 1980 - 1988
  
  generate_predictor(time_window = 1980:1988,                 # Los periodos hasta T0=1988
                     ln_income = mean(lnincome, na.rm = T),   #log del ingreso
                     ret_price = mean(retprice, na.rm = T),   #el precio de los cigarrillos
                     youth = mean(age15to24, na.rm = T)) %>%  #proporcion de la pob entre 15 - 24 años
  
  # promedio del consumo de cerveza en el donnor pool de 1984 - 1988
  generate_predictor(time_window = 1984:1988,
                     beer_sales = mean(beer, na.rm = T)) %>%
  
  # las ventas de cigarrillos atrasadas 
  generate_predictor(time_window = 1975,
                     cigsale_1975 = cigsale) %>%
  generate_predictor(time_window = 1980,
                     cigsale_1980 = cigsale) %>%
  generate_predictor(time_window = 1988,
                     cigsale_1988 = cigsale) %>%
  
  
  # genera los ponderadores
  generate_weights(optimization_window = 1970:1988,  # el periodo para optimización de los ponderadores
                   margin_ipop = .02, sigf_ipop = 7, 
                   bound_ipop = 6                    # opciones de optimización
  ) %>%
  
  # Generar el control sintético
  generate_control()

Para graficar las trayectorias de las series observadas y la serie sintética del problema anterior utilizamos el siguiente comando:

smoking_out %>% plot_trends() 

Además, podemos graficar las diferencias absolutas entre la serie observada y la serie sintética:

smoking_out %>% plot_differences()

Los siguientes histogramas muestral la ponderación que el programa le da a las diferentes unidades, siendo Utah el que recibe mayor ponderación y Conecticut el que menos pero estrictamente positivo. Además, del lado derecho se muestran las ponderaciones para las diferntes variables en la matriz de predictores para todas las unidades \([X_{0}:X_{1}]\)

Recoremos que tanto este método como el de la paquetería Synth generan los pesos bajo las siguientes condiciones:

\[\sum_{j=2}^{J+1}\omega_{j}=1\] y \[\omega_{j}\geq 0 \quad \text{con} \quad j=2,...,J+1\]

smoking_out %>% plot_weights()

De acuerdo con Abadie et. al., (2010) el método de control sintético produce ponderadores iguales a cero para la mayoría de las unidades del donor pool.

smoking_out %>% grab_balance_table()

Inferencia

En cuanto a la inferencia, el método se basa en repetir el método para cada donante en el grupo de donantes exactamente de la misma manera que se hizo para la unidad tratada, es decir, generando controles sintéticos de placebo. Al establecer generate_placebos = TRUE al inicializar la tubería ‘synth’ con synthetic_control(), los casos de placebo se generan automáticamente al construir el control sintético de interés. Esto facilita la exploración de cuán única es la diferencia entre la unidad observada y la unidad sintética en comparación con los placebos

smoking_out %>% 
  plot_placebos()

Ten en cuenta que la función plot_placebos() automáticamente elimina cualquier placebo que se ajuste mal a los datos en el período previo a la intervención. La razón para hacerlo es puramente visual: esas unidades tienden a distorsionar la escala al trazar los placebos. Para realizar la eliminación, la función examina el error cuadrático medio de predicción (MSPE) en el período previo a la intervención (es decir, una métrica que refleja cuán bien se ajusta el control sintético a la serie de tiempo de resultados observados en el período previo a la intervención). Si un control de placebo tiene un MSPE que es dos veces mayor que el caso objetivo (por ejemplo, “California”), entonces se elimina. Para desactivar este comportamiento, establece prune = FALSE.

smoking_out %>% plot_placebos(prune = FALSE)

Finalmente, Adabie et al. en 2010 describen una manera de construir los valores p exactos de Fisher dividiendo el MSPE posterior a la intervención por el MSPE anterior a la intervención y luego clasificando todos los casos en orden descendente según esta proporción. Se construye un valor p tomando la clasificación entre el total de casos. La idea subyacente es que si el control sintético se ajusta bien a la serie de tiempo observada (bajo MSPE en el período previo) y se desvía en el período posterior (alto MSPE en el período posterior), entonces hay un efecto significativo debido a la intervención. Si la intervención no tuvo efecto, entonces el período posterior y el período previo deberían continuar mapeándose bastante bien, lo que daría como resultado una proporción cercana a 1. Si las unidades de placebo se ajustan de manera similar a los datos, entonces no podemos rechazar la hipótesis nula de que la intervención no tuvo efecto.

Esta proporción se puede representar fácilmente utilizando plot_mspe_ratio(), lo que ofrece información sobre la rareza del caso en el que la intervención realmente ocurrió.

smoking_out %>% plot_mspe_ratio()

Un segundo ejemplo:

library(Synth)
data("synth.data")

synth.data <- filter(synth.data, 
                     year <= 1996)
  
synth.data.out <- 
  synth.data %>% 
  synthetic_control(outcome = Y,
                    unit = name,
                    time = year,
                    i_unit = "treated.region",
                    i_time = 1991,
                    generate_placebos = T
                    ) %>%
  generate_predictor(time_window = 1980:1991,
                   x1 = mean(X1, na.rm = T),
                   x2 = mean(X2, na.rm = T),
                   x3 = mean(X3, na.rm = T)
                   )  %>%
  generate_predictor(time_window = 1991,
                     Y_91 = Y) %>%
  generate_predictor(time_window = 1985,
                     Y_85 = Y) %>%
  generate_predictor(time_window = 1980,
                     Y_80 = Y) %>%
  generate_weights(optimization_window = 1970:1991,
                   margin_ipop = .02, sigf_ipop = 7, 
                   bound_ipop = 6
                   ) %>%
  generate_control()

synth.data.out %>%
  plot_trends()

synth.data.out %>%
  plot_differences()

synth.data.out %>%
  plot_weights()

synth.data.out %>%
  grab_balance_table()







Cuidate bb

LS0tCnRpdGxlOiAiQ29udHJvbCBTaW50w6l0aWNvIgpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKLS0tCgo8YnI+PGJyPjxicj4KCjxwIHN0eWxlPSJ0ZXh0LWFsaWduOmNlbnRlcjsiPgoqUXVpZXJvIHF1ZSBxdWVkZSBjbGFybyBxdWUgbm8gdGVuZ28gaWRlYSBkZSBsbyBxdWUgZXN0b3kgYXB1bnRvIGRlIGVzY3JpYmlyLCDDum5pY2FtZW50ZSBlcyB1bmEgY29tcGlsYWNpw7NuIGRlIGRvY3VtZW50b3MgcXVlIGhlIGVuY29udHJhZG8gc29icmUgbW9kZWxhZG8gZGUgY29udHJvbCBzaW50w6l0aWNvLiBQb3IgbG8gdGFudG8sIGRlc2N1YnJpcmVtb3MganVudG9zIHkgYWwgbWlzbW8gdGllbXBvIChlc28gbm8gZXMgdmVyZGFkKSBxdWUgaGFjZSBjYWRhIHVuYSBkZSBsYXMgc2lndWllbnRlcyBsaW5lYXMuIFBvciBmYXZvciBubyBqdXpndWVzLCB0dXZlIGxhIGRlY2VuY2lhIGRlIGFkdmVydGlydGUuKgo8L3A+Cgo8cCBzdHlsZT0idGV4dC1hbGlnbjpjZW50ZXI7Ij4KLUMKPC9wPgoKPGJyPjxicj48YnI+PGJyPjxicj48YnI+CgpQYXJhIGVsIG1vZGVsYWRvIGRlIGNvbnRyb2wgc2ludMOpdGljbyB1dGlsaXphcsOpbW9zIGRvcyBtw6l0b2RvcyBjb24gZG9zIHBhcXVldGVyaWFzIGRpc3RpbnRhcyBlbCBwcmltZXJvIApgU3ludGhgIGRlIEplbnMgSGFpbm11ZWxsZXIgYW5kIEFsZXhpcyBEaWFtb25kIGRlIGxhcyB1bml2ZXNpZGFkZXMgU3RhbmZvcmQgeSBIYXJ2YXJkLCByZXNwZWN0aXZhbWVudGUuIExhIGRvY3VtZW50YWNpw7NuIGFsIHJlc3BlY3RvIHNlIGVuY3VlbnRyYSBbYXF1aV0oaHR0cHM6Ly9jcmFuLnItcHJvamVjdC5vcmcvd2ViL3BhY2thZ2VzL1N5bnRoL1N5bnRoLnBkZi4gKSAKCkxhIHNlZ3VuZGEgcGFxdWV0ZXLDrWEsIGB0aWR5c3ludGhgIHF1ZSBlc3RhIGNvbnN0cnVpZGEgc29icmUgbGEgcGFxdWV0ZXLDrWEgYW50ZXJpb3IuIEhhc3RhIGRvbmRlIGVudGllbmRvLCBsYSBjcmVhY2nDs24gZGUgZXN0YSBwYXF1ZXRlcmlhIGxlIGNvcnJlc3BvbmRlIGEgRXJpYyBEdW5mb3JkIGRlIE1ldGEgeSBwdWVkZXMgY29uc3VsdGFyIGVsIGByZWFkbWVgIGNvbXBsZXRvIFthcXVpXShodHRwczovL2dpdGh1Yi5jb20vZWR1bmZvcmQvdGlkeXN5bnRoI3JlYWRtZSkKCjxicj48YnI+PGJyPgoKIyMjIFBhcXVldGVyw61hIGBTeW50aGAKCkxvIHByaW1lcm8gc2Vyw6EgaW5zdGFsYXIgbGEgbGlicmVyw61hOgpgYGB7cn0KbGlicmFyeShTeW50aCkgCmBgYAoKVW5hIHZleiBjYXJnYWRhIGxhIGxpYnJlcsOtYSBwb2RyZW1vcyBhY2NlZGVyIGEgbGEgYmFzZSBkZSBkYXRvcyBwYW5lbCBtZWRpYW50ZSBlbCBzaWd1aWVudGUgY29tYW5kbzoKYGBge3J9CmRhdGEoInN5bnRoLmRhdGEiKQpgYGAKcXVlIGVzIHVuIGRhdGEgZnJhbWUgY29uIGRhdG9zIHBvciBhw7FvIGRlc2RlIDE5ODAgaGFzdGEgZWwgYcOxbyAyMDAwLCB0YW50byBwYXJhIGVsIGdydXBvIGRlIGNvbnRyb2wgKCRcdGV4dGl0e2Rvbm9yIHBvb2x9JCksIGNvbW8gcGFyYSBsYSB1bmlkYWQgZGUgdHJhdGFtaWVudG8gKGB0cmVhdGVkLnJlZ2lvbmApLgoKTGEgb3JnYW5pemFjacOzbiBkZSBsYSBiYXNlIGRlIGRhdG9zIGFudGVyaW9yIGVzdMOhIGRldGVybWluYWRhIGRlIGxhIHNpZ3VpZW50ZSBtYW5lcmE6CgoqIGB1bml0Lm51bWAgZXMgZWwgaWRlbnRpZmljYWRvciBkZSBjYWRhIHVuaWRhZCBxdWUgc2UgY29tcG9uZSBkZSB1biB2ZWN0b3IgZGUgOCBlbnRyYWRhcyBzaW4gcmVwZXRpY2nDs24sIGVzIGRlY2lyLCAkaj1cezIsICA3LCAxMywgMTcsIDI5LCAzMiwgMzYsIDM4XH0kLCBzaWVuZG8gbGEgdW5pZGFkIGRlIHRyYXRhbWllbnRvIGxhIHVuaWRhZCAkaj03JCwKCiogYHllYXJgIGVzIHVuIHZlY3RvciBudW3DqXJpY28gY29uIGxvcyBpZGVudGlmaWNhZG9yZXMgZGUgY2FkYSBhw7FvIHBhcmEgJHQ9MTk4MCwxOTgxLC4uLiwyMDAwJCBwYXJhIGNhZGEgdW5pZGFkLAoKKiBgbmFtZWAgY29udGllbmUgZWwgbm9tYnJlIGRlIGxhcyByZWdpb25lcywKCiogYFlgIGVzIGxhIHNlcmllIHBhcmEgZWwgJFx0ZXh0aXR7b3V0Y29tZX0kIGRlIGludGVyw6lzLAoKKiBsYXMgY29sdW1uYXMgYFgxYCxgWDJgIHkgYFgzYCBzb24gbG9zIHByZWRpY3RvcmVzIGRlIGBZYC4KCgoKVmFtb3MgYSB0b21hciBsb3MgdmFsb3JlcyBtZWRpb3MgZGUgY2FkYSBwcmVkaWN0b3IgcGFyYSB0b2RvcyBsb3MgYcOxb3MsIGVzIGRlY2lyOgoKJCRcZnJhY3sxfXtUfVxzdW1fe3Q9MX1ee1R9eF97aWp0fSQkCmVzIGxhIGVudHJhZGEgZGUgbGEgZmlsYSAkaSQgeSBsYSBjb2x1bW5hICRqJCBkZSBsYSBtYXRyaXogJFhfezB9JCwgcXVlIGVzIGxhIG1hdHJpeiBkZSBwcmVkaWN0b3JlcyBwYXJhIGxhcyB1bmlkYWRlcyAkaj0xLDIsLi4uLEokIGUgJGk9MSwyLDMkCgoKRW4gZXN0ZSBlamVtcGxvIGxhIG1hdHJpeiBkZSBwcmVkaWN0b3JlcyAkWF97MX0kIGVzdMOhIGRldGVybWluYWRhIGNvbW8gc2lndWU6CmBgYHtyfQpYMSA8LSBmaWx0ZXIoc3ludGguZGF0YSwgbmFtZSA9PSAidHJlYXRlZC5yZWdpb24iKSAlPiUgCiAgc2VsZWN0KCJYMSIsIlgyIiwiWDMiKQpYMS4xIDwtIG1hdHJpeChyZXAoMCwzKSxuY29sPTMpIApmb3IgKGkgaW4gIDE6bmNvbChYMS4xKSl7CiAgWDEuMVssaV08LW1lYW4oWDFbLGldLG5hLnJtID0gVCkKfQoKWDEuMQpgYGAKbWllbnRyYXMgcXVlIGxhIG1hdHJpeiBkZSBwcmVkaWN0b3JlcyBjb24gbGFzIG1pc21hcyB2YXJpYWJsZXMgcGFyYSAkaj0yLC4uLiw4JCwgJFhfezB9JCwgbGEgY29uc3RydWltb3MgZGUgbGEgc2lndWllbnRlIG1hbmVyYToKCmBgYHtyfQpYMiA8LSBmaWx0ZXIoc3ludGguZGF0YSwgbmFtZSA9PSAiY29udHJvbC5yZWdpb24ubm9ydGhlYXN0IikgJT4lIAogIHNlbGVjdCgiWDEiLCJYMiIsIlgzIikgICAgICAgICAgICAgIApYMi4xIDwtIG1hdHJpeChyZXAoMCwzKSxuY29sPTMpIApmb3IgKGkgaW4gIDE6bmNvbChYMi4xKSl7CiAgWDIuMVssaV08LW1lYW4oWDJbLGldLG5hLnJtID0gVCkKfQpjb2xuYW1lcyhYMi4xKSA8LSBjb2xuYW1lcyhYMikgICAgICAgICAgICAgICAgICAgICNFc3RhcyBzb24gbGFzIG1hdHJpY2VzIGNvbiBsb3MgdmFsb3JlcyBtZWRpb3MgcG9yIGNvbHVtbmEKClgzIDwtIGZpbHRlcihzeW50aC5kYXRhLCBuYW1lID09ICJjb250cm9sLnJlZ2lvbi5zb3V0aGNlbnRyYWwiKSAlPiUgCiAgc2VsZWN0KCJYMSIsIlgyIiwiWDMiKQpYMy4xIDwtIG1hdHJpeChyZXAoMCwzKSxuY29sPTMpIApmb3IgKGkgaW4gIDE6bmNvbChYMy4xKSl7CiAgWDMuMVssaV08LW1lYW4oWDNbLGldLG5hLnJtID0gVCkKfQoKClg0IDwtIGZpbHRlcihzeW50aC5kYXRhLCBuYW1lID09ICJjb250cm9sLnJlZ2lvbi53ZXN0IikgJT4lIAogIHNlbGVjdCgiWDEiLCJYMiIsIlgzIikKWDQuMSA8LSBtYXRyaXgocmVwKDAsMyksbmNvbD0zKSAKZm9yIChpIGluICAxOm5jb2woWDQuMSkpewogIFg0LjFbLGldPC1tZWFuKFg0WyxpXSxuYS5ybSA9IFQpCn0KCgpYNSA8LSBmaWx0ZXIoc3ludGguZGF0YSwgbmFtZSA9PSAiY29udHJvbC5yZWdpb24uc291dGhlYXN0IikgJT4lIAogIHNlbGVjdCgiWDEiLCJYMiIsIlgzIikKWDUuMSA8LSBtYXRyaXgocmVwKDAsMyksbmNvbD0zKSAKZm9yIChpIGluICAxOm5jb2woWDUuMSkpewogIFg1LjFbLGldPC1tZWFuKFg1WyxpXSxuYS5ybSA9IFQpCn0KCgpYNiA8LSBmaWx0ZXIoc3ludGguZGF0YSwgbmFtZSA9PSAiY29udHJvbC5yZWdpb24uY2VudHJhbCIpICU+JSAKICBzZWxlY3QoIlgxIiwiWDIiLCJYMyIpClg2LjEgPC0gbWF0cml4KHJlcCgwLDMpLG5jb2w9MykgCmZvciAoaSBpbiAgMTpuY29sKFg2LjEpKXsKICBYNi4xWyxpXTwtbWVhbihYNlssaV0sbmEucm0gPSBUKQp9CgoKWDcgPC0gZmlsdGVyKHN5bnRoLmRhdGEsIG5hbWUgPT0gImNvbnRyb2wucmVnaW9uLnNvdXRoIikgJT4lIAogIHNlbGVjdCgiWDEiLCJYMiIsIlgzIikKWDcuMSA8LSBtYXRyaXgocmVwKDAsMyksbmNvbD0zKSAKZm9yIChpIGluICAxOm5jb2woWDcuMSkpewogIFg3LjFbLGldPC1tZWFuKFg3WyxpXSxuYS5ybSA9IFQpCn0KCgpYOCA8LSBmaWx0ZXIoc3ludGguZGF0YSwgbmFtZSA9PSAiY29udHJvbC5yZWdpb24uZWFzdCIpICU+JSAKICBzZWxlY3QoIlgxIiwiWDIiLCJYMyIpClg4LjEgPC0gbWF0cml4KHJlcCgwLDMpLG5jb2w9MykgCmZvciAoaSBpbiAgMTpuY29sKFg4LjEpKXsKICBYOC4xWyxpXTwtbWVhbihYOFssaV0sbmEucm0gPSBUKQp9CgoKWDAgPC0gcmJpbmQoWDIuMSxYMy4xLFg0LjEsWDUuMSxYNi4xLFg3LjEsWDguMSkKWDAKYGBgCgoKQWhvcmEgdmFtb3MgYSBwcmVwYXJhciBkZSBtYW5lcmEgY29ycmVjdGEgbG9zIGRhdG9zIHV0aWxpemFuZG8gbGEgZnVuY2nDs24gYGRhdGFwcmVwYDoKCmBgYHtyfQpkYXRhcHJlcC5vdXQgPC0gZGF0YXByZXAoZm9vID0gc3ludGguZGF0YSwgICAgICAgICAgICAgICAgICAgICAgICAjQ2FyZ2FyIGxvcyBkYXRvcwogICAgICAgICAgICAgICAgICAgICAgICAgcHJlZGljdG9ycyA9IGMoIlgxIiwgIlgyIiwgIlgzIiksICAgICAgICAjTWF0cml6IGRlIHByZWRpY3RvcmVzIAogICAgICAgICAgICAgICAgICAgICAgICAgcHJlZGljdG9ycy5vcCA9ICJtZWFuIiwgICAgICAgICAgICAgICAgICAjRWwgb3BlcmFkb3IgcXVlIHNlIHV0aWxpemFyw6EgZW4gbG9zIHByZWRpY3RvcmVzIAogICAgICAgICAgICAgICAgICAgICAgICAgZGVwZW5kZW50ID0gIlkiLCAgICAgICAgICAgICAgICAgICAgICAgICAjT3V0Y29tZQogICAgICAgICAgICAgICAgICAgICAgICAgdW5pdC52YXJpYWJsZSA9ICJ1bml0Lm51bSIsICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgIHRpbWUudmFyaWFibGUgPSAieWVhciIsICAgICAgICAgICAgICAgICAgI0ZyZWN1ZW5jaWEgdGVtcG9yYWwgZGUgbGEgYmFzZQogICAgICAgICAgICAgICAgICAgICAgICAgc3BlY2lhbC5wcmVkaWN0b3JzID0gbGlzdCggICAgICAgICAgICAgICAjUHJlZGljdG9yZXMgbnVtw6lyaWNvcyBhZGljaW9uYWxlcwogICAgICAgICAgICAgICAgICAgICAgICAgICBsaXN0KCJZIiwgMTk5MSwgIm1lYW4iKSwgICAgICAgICAgICAgICAjcXVlIGFzb2NpYSBwZXJpb2RvcyBwcmV2aW9zIGFsCiAgICAgICAgICAgICAgICAgICAgICAgICAgIGxpc3QoIlkiLCAxOTg1LCAibWVhbiIpLCAgICAgICAgICAgICAgICN0cmF0YW1pZW50byB5IG9wZXJhZG9yZXMgIm1lYW4iLgogICAgICAgICAgICAgICAgICAgICAgICAgICBsaXN0KCJZIiwgMTk4MCwgIm1lYW4iKSksCiAgICAgICAgICAgICAgICAgICAgICAgICB0cmVhdG1lbnQuaWRlbnRpZmllciA9IDcsICAgICAgICAgICAgICAgICNpZCBkZSBsYSB1bmlkYWQgZGUgdHJhdGFtaWVudG8KICAgICAgICAgICAgICAgICAgICAgICAgIGNvbnRyb2xzLmlkZW50aWZpZXIgPSBjKDIsIDEzLCAxNywgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAyOSwgMzIsIDM2LCAzOCksICAjaWQgZGUgbGFzIHVuaWRhZGVzIGRlIGNvbnRyb2wKICAgICAgICAgICAgICAgICAgICAgICAgIHRpbWUucHJlZGljdG9ycy5wcmlvciA9IGMoMTk4MDoxOTg5KSwgICAgI1VuIHZlY3RvciBudW3DqXJpY28gcXVlIGNvbnRpZW5lIGxvcyBwZXJpb2RvcyBwcmV2aW9zIGFsIHRyYXRhbWllbnRvIHNvYnJlIGxvcyBxdWUgZWwgcHJlZGljdG9yIHNlcsOhIHByb21lZGlhZG8uCiAgICAgICAgICAgICAgICAgICAgICAgICB0aW1lLm9wdGltaXplLnNzciA9IGMoMTk4MDoxOTg5KSwgICAgICAgICNVbiB2ZWN0b3IgbnVtw6lyaWNvIHF1ZSBpZGVudGlmaWNhIGxvcyBwZXJpb2RvcyBkZSBsYSB2YXJpYWJsZSBkZXBlbmRpZW50ZSBzb2JyZSBsb3MgY3VhbGVzIGxhIGZ1bmNpw7NuIGRlIHBlcmRpZGEgc2UgbWluaW1pemEgKGkuZS4sIGxhIG1pbmltaXphY2nDs24gZGUgbGEgc3VtYSBkZSBsb3MgcmVzaWR1b3MgYWwgY3VhZHJhZG8gZW50cmUgbGEgdW5pZGFkIGRlIHRyYXRhbWllbnRvIHkgbGEgdW5pZGFkIGRlIGNvbnRyb2wuKQogICAgICAgICAgICAgICAgICAgICAgICAgdW5pdC5uYW1lcy52YXJpYWJsZSA9ICJuYW1lIiwgICAgICAgICAgICAjTm9tYnJlIGRlIGxhcyB1bmlkYWRlcyBkZSBjb250cm9sCiAgICAgICAgICAgICAgICAgICAgICAgICB0aW1lLnBsb3QgPSAxOTgwOjE5OTYpICAgICAgICAgICAgICAgICAgICNVbiB2ZWN0b3IgcXVlIGlkZW50aWZpY2EgbG9zIHBlcsOtb2RvcyBkdXJhbnRlIGxvcyBjdWFsZXMgc2UgdHJhemFyw6FuIGxvcyByZXN1bHRhZG9zIGNvbiBnYXBzLnBsb3QgeSBwYXRoLnBsb3QuCgpgYGAKTm90ZSBxdWUgcGFyYSBnZW5lcmFyIGxhcyBtYXRyaWNlcyAkWF97MH0kIHRvbWFtb3MgbG9zIHByb21lZGlvcyBwb3IgYcOxbyBkZSBsYXMgb2JzZXJ2YWNpb25lcyBwYXJhIGNhZGEgdmFyaWFibGUgJFhfe2p9JCBjb24gJGo9MSwyLDMkIHkgbGFzIHZhcmlhYmxlcyBlc3BlY2lhbGVzIHNvbiBzaW1wbGVtZW50ZSBsYXMgb2JzZXJ2YWNpb25lcyBwYXJhICRZJCBkZSBsb3MgYcOxb3MgMTk4MCwgMTk4NSB5IDE5OTAuIAoKUGFyYSBnZW5lcmFyIGVsIGNvbnRyb2wgc2ludGV0aWNvLCBlcyBkZWNpciBlbCB2ZWN0b3IgZGUgcG9uZGVyYWRvcmVzICRXPShcb21lZ2FfezJ9LC4uLixcb21lZ2Ffe0orMX0pXntUfSQgdGFsIHF1ZSAKIAokJFlfezF0fV57Tn09XHN1bV97aj0yfV57SisxfSBcb21lZ2Ffe2p9IFlfe2p0fSQkCgoKZG9uZGUgJFlfezF0fV57Tn0kIGVzIGVsIG91dGNvbWUgZGUgaW50ZXLDqXMgZW4gYXVzZW5jaWEgZGUgbGEgaW50ZXZlbmNpw7NuLCBwYXJhICR0PlRfezB9JCwgZWwgcGVyaW9kbyBlbiBlbCBxdWUgb2N1cnJlIGxhIGludGVydmVuY2nDs24uCiAKCmBgYHtyfQpzeW50aC5vdXQgPC0gc3ludGgoZGF0YXByZXAub3V0KSAgICAgICAgICAgICNHZW5lcmEgZWwgdmVjdG9yIGRlIDZ4MSBlbnRyYWRhcyBkZWwgY29udHJvbCBzaW50ZXRpY28gcGFyYSBjYWRhIHByZWRpY3RvcgoKcm91bmQoc3ludGgub3V0JHNvbHV0aW9uLncsMikgICAgICAgICAgICAgICAjUmVkb25kZWEgeSBwcmVzZW50YSBsYXMgdmFyaWFibGVzIGEgZG9zIGNhcmFjdGVyZXMgZGVzcHXDqXMgZGVsIHB1bnRvIGRlY2ltYWwKCgpnYXBzPC0gZGF0YXByZXAub3V0JFkxcGxvdC0oCiAgICAgICAgZGF0YXByZXAub3V0JFkwcGxvdCUqJXN5bnRoLm91dCRzb2x1dGlvbi53KSAgI0VzdGEgZWN1YWNpw7NuIGVzIHNpbXBsZW1lbnRlIGVsIHRhdV8xdCBwYXJhIHRvZG9zIGxvcyBwZXJpb2Rvcy4KYGBgCgoKQ29tbyBoYWLDrWFtb3MgY29udmVuaWRvIGVuIGxhIGVudHJhZGEgZGUgdGVvcsOtYSBkZSBjb250cm9sIHNpbnTDqXRpY28gdW5vIGRlIGxvcyByZXF1ZXJpbWllbnRvcyBwYXJhIHZhbG9yYXIgZWwgdmVjdG9yICRXXnsqfSQgZXMgbGEgcmVnbGEgZGVsICpjb252ZXggaHVsbCosIGVzIGRlY2lyCgokJFhfezF9LVhfezB9V157Kn1cYXBwcm94IDAkJApgYGB7cn0KdChYMS4xKS0odChYMCklKiVzeW50aC5vdXQkc29sdXRpb24udykKYGBgCkRlIGFjdWVyZG8gY29uIGxhIHRlb3LDrWEsIHBhcmEgb2J0ZW5lciBtYXlvciBhcHJveGltYWNpw7NuIHRlbmRyaWFtb3MgcXVlIHV0aWxpemFyIG3DoXMgaW5mb3JtYWNpw7NuIHBhcmEgYW50ZXMgZGVsIHBlcmlvZG8gZGUgaW50ZXJ2ZW5jacOzbi4KCgpGaW5hbG1lbnRlLCBwYXJhIHByZXNlbnRhciBkZSBtYW5lcmEgZ3LDoWZpY2EgbG9zIGRhdG9zIHV0aWxpemFtb3MgbGFzIGZ1bmNpb25lcyBgcGF0aC5wbG90KClgIHBhcmEgZ3JhZmljYXIgbGFzIHRyYXllY3RvcmlhcyBvYnNlcnZhZGFzIHkgbGFzIHNpbnTDqXRpY2FzLCB5IGBnYXBzLnBsb3QoKWAgcGFyYSBncsOhZmljYXIgbGFzIGRpZmVyZW5jaWFzIGVudHJlIGxhIHNlcmllIG9ic2VydmFkYSB5IGxhIHNpbnTDqXRpY2EgZW4gdG9kbyBlbCBob3Jpem9udGUgdGVtb3BvcmFsLgoKYGBge3J9CnN5bnRoLnRhYmxlcyA8LSBzeW50aC50YWIoCiAgICAgIGRhdGFwcmVwLnJlcyA9IGRhdGFwcmVwLm91dCwKICAgICAgc3ludGgucmVzID0gc3ludGgub3V0KSAgICAgICAgICAgICAgICAgICAgICAgI1ByZXNlbnRhIGxvcyByZXN1bHRhZG9zIGRlbCBtb2RlbG8gZW4gdW5hIHRhYmxhCnByaW50KHN5bnRoLnRhYmxlcykKCgpwYXRoLnBsb3QoZGF0YXByZXAucmVzID0gZGF0YXByZXAub3V0LAogICAgICAgICAgc3ludGgucmVzID0gc3ludGgub3V0LAogICAgICAgICAgTWFpbiA9ICJUcmF5ZWN0b3JpYXMgZGUgY29udHJvbCBzaW50w6l0aWNvIikgICAgICAgICAgICAgICAgICAjR3JhZmljYSBsYXMgdHJheWVjdG9yaWFzIGRlIGxhIHVuaWRhZCB0cmF0YWRhIHkgc3UgdHJheWVjdG9yaWEgc2ludMOpdGljYQphYmxpbmUodiA9IDE5OTEsIGNvbCA9ICJyZWQiLCBsdHkgPSAyKQp0ZXh0KDE5OTEsIDEwMCwiUGVyaW9kbyBkZSBcbiBUcmF0YW1pZW50byAoVDApICIsCiAgICAgcG9zID0gMiwgY29sID0icmVkIikKCgpnYXBzLnBsb3QoZGF0YXByZXAucmVzID0gZGF0YXByZXAub3V0LAogICAgICAgICAgc3ludGgucmVzID0gc3ludGgub3V0LAogICAgICAgICAgTWFpbiA9ICJCcmVjaGFzIGRlbCBjb3Ryb2wgc2ludMOpdGljbyIpICAgICAgICAgICAgICAgICAgI0dyYWZpY2EgbGFzIGRpc2NyZXBhbmNpYXMgZW50cmUgbGEgdW5pZGFkIHRyYXRhZGEgeSBzdSB0cmF5ZWN0b3JpYSBzaW50w6l0aWNhCmFibGluZSh2ID0gMTk5MSwgY29sID0gInJlZCIsIGx0eSA9IDIpCnRleHQoMTk5MSwgMjAsICJQZXJpb2RvIGRlIFxuIFRyYXRhbWllbnRvIChUMCkgIiwKICAgICBwb3MgPSAyLCBjb2wgPSJyZWQiKQoKYGBgCgoKIyMjIFBhcXVldGVyw61hIGB0aWR5c3ludGhgCgpBaG9yYSB2YW1vcyBhIHRyYWJhamFyIGNvbiBsYSBsaWJyZXJpYSBgdGlkeXN5bnRoYCwgbG8gcGltZXJvIHF1ZSBoYXkgcXVlIGhhY2VyIGVzIGluc3RhbGFyIGxhIGxpYnJlcmlhOgoKCmBgYHtyfQpsaWJyYXJ5KHRpZHlzeW50aCkKYGBgCgoKTGEgYmFzZSBkZSBkYXRvcyBxdWUgdmFtb3MgYSB1dGlsaXphciBwcm92aWVuZSBkZSBBYmFkaWUgZXQgYWwuICgyMDEwKS4gTGEgYmFzZSBkZSBkYXRvcyBzZSBjb21wb25lIGRlIGxhIHNpZ3VpZW50ZSBtYW5lcmE6CgoKKiBMYSBjb2x1bW5hIGBzdGF0ZWAgZGUgY2xhc2UgKiJjaGFyYWN0ZXIqIiBjb250aWVuZSBsb3Mgbm9tYnJlcyBkZSBsb3MgZGlzdGludG9zIGVzdGFkb3MgZGUgRXN0YWRvcyBVbmlkb3MsCgoqIGxhIGNvbHVtbmEgYG51bWVyaWNgIGRlIGNsYXNlICIqbnVtZXJpYyoiIGNvbnRpZW5lIGVsIGHDsW8gZW4gcXVlIHNlIHJlYWxpemFuIGxhcyBvYnNlcnZhY2lvbmVzIGRlc2RlIGVsIGHDsW8gMTk3MCBhbCBhw7FvIDIwMDAsCgoqIGxhIGNvbHVtbmEgYGNpZ3NhbGVgIGRlIGNsYXNlICIqbnVtZXJpYyoiIGNvbnRpZW5lIGVsIHRvdGFsIGRlIGxhIHZlbnRhIGRlIGNpZ2FycmlsbG9zIHBlci1jYXBpdGEgcG9yIGVzdGFkbywKCiogbGEgY29sdW1uYSBgbG5pbmNvbWVgIGRlIGNsYXNlICIqbnVtZXJpYyoiIGNvbnRpZW5lIGVsIGxvZ2FyaXRtbyBkZWwgaW5ncmVzbyBwZXJzb25hbCBkZSBjYWRhIGVzdGFkbwoKKiBsYSBjb2x1bW5hIGBiZWVyYCBkZSBjbGFzZSAiKm51bWVyaWMqIiBjb250aWVuZSBlbCBjb25zdW1vIGRlIGNlcnZlemEgcGVyLWNhcGl0YQoKKiBsYSBjb2x1bW5hIGBhZ2UxNXRvMjRgIGRlIGNsYXNlICIqbnVtZXJpYyoiIGNvbnRpZW5lIGNvbnRpZW5lIGVsIHBvcmNlbnRhamUgZGUgbGEgcG9ibGFjacOzbiBlbnRyZSAxNSB5IDI0IGHDsW9zCgoqIGxhIGNvbHVtbmEgYHJldHByaWNlYCBkZSBjbGFzZSAiKm51bWVyaWMqIiBjb250aWVuZSBlbCBwcmVjaW8gcHJvbWVkaW8gZGUgKnJldGFpbCogZGUgbG9zIGNpZ2FycmlsbG9zLgoKRW4gZXN0ZSBlamVtcGxvLCBlbCAqb3V0Y29tZSogZGUgaW50ZXLDqXMgZXMgbGEgdmVudGEgZGUgY2lnYXJyaWxsb3MgcGVyLWNhcGl0YSwgeSBsYSB1bmlkYWQgZGUgdHJhdGFtaWVudG8gZXMgZWwgZXN0YWRvIGRlIENhbGlmb3JuaWEsIG1pZW50cmFzIHF1ZSBsYXMgdW5pZGFkZXMgZGUgY29ucnJvbCBzZXLDoW4gZWwgcmVzdG8gZGUgbG9zIGVzdGFvZHMgZGUgRXN0YWRvcyBVbmlkb3MuIExvIHF1ZSBzZSBpbnRlbnRhIGVzdGltYXIgZXMgbGEgdHJheWVjdG9yaWEgc2ludMOpdGljYSBkZSBsYSB2ZW50YSBkZSBjaWdhcnJpbGxvcyBlbiBDYWxpZm9ybmlhIGRlIG5vIGhhYmVyc2UgYXBsaWNhZG8gZWwgdHJhdGFtaWVudG8sIHF1ZSBlcywgbGEgbGxhbWFkYSAqUHJvcG9zaXRpb24gOTkqIHF1ZSBjb25zaXN0acOzIGVuIHVuIGF1bWVudG8gZGUgMjUgY2VudGF2b3MgcG9yIHBhcXVldGUgYSB0cmF2w6lzIGRlIHVuIGltcHVlc3RvLiAKCmBgYHtyfQpkYXRhKCJzbW9raW5nIikKYGBgCgpQYXJhIHByZXBhcmFyIGxvcyBkYXRvcyBjb21vIGVuIGxhIHBhcXVldGVyw61hIGBTeW50aGAgdXRpbGl6YW1vcyBsYSBmdW5jacOzbm0gYHN5bnRoZXRpY19jb250cm9sKClgIGRlIGxhIG1hbmVyYSBzaWd1aWVudGUsIGxhIG1hdHJpeiBwcmVkaWN0b3JlcyAkWF97MH0kIHkgJFhfezF9JCBjb24gbGEgZnVuY2nDs24gYGdlbmVyYXRlX3ByZWRpY3RvcnMoKWAsIHkgZ2VuZXJhciBsb3MgcG9uZGVyYWRvcmVzIGNvbiBsYSBmdW5jacOzbiBgZ2VuZXJhdGVfY29udHJvbHMoKWAuIERhZG8gcXVlIGxhIHBhcXVldGVyaWEgZXMgdW5hIGltcGxlbWVudGFjacOzbiBgdGlkeWAgbGEgY29uY2F0ZW5hY2nDs24gZGUgbGFzIGZ1bmNpb25lcyBzZSBoYWNlIGEgdHJhdsOpcyBkZWwgY29tYW5kbyBgJT4lYCwgY29tbyBzZSBtdWVzdHJhIGEgY29udGludWFjacOzbjoKCgpgYGB7cn0Kc21va2luZ19vdXQgPC0KICAKICBzbW9raW5nICU+JSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgI0VzdGUgZXMgZWwgZGF0YSBmcmFtZSBxdWUgdmFtb3MgYSB1dGlsaXphcgogIHN5bnRoZXRpY19jb250cm9sKG91dGNvbWUgPSBjaWdzYWxlLCAgICAgICAgICAgICAjRWwgb3V0Y29tZSBkZSBpbnRlcsOpcwogICAgICAgICAgICAgICAgICAgIHVuaXQgPSBzdGF0ZSwgICAgICAgICAgICAgICAgICAjVmFtb3MgYSB1dGlsaXphciBsYSBjb2x1bW5hIHN0YXRlIGNvbW8gaW5kaWNlIGRlIGxhcyB1bmlkYWRlcwogICAgICAgICAgICAgICAgICAgIHRpbWUgPSB5ZWFyLCAgICAgICAgICAgICAgICAgICAjQ29uIGluZGljZSBkZSB0aWVtcG8gZGUgbGEgY29sdW1uYSB5ZWFyCiAgICAgICAgICAgICAgICAgICAgaV91bml0ID0gIkNhbGlmb3JuaWEiLCAgICAgICAgICNMYSB1bmlkYWQgZGUgdHJhdGFtaWVudG8gaj0xCiAgICAgICAgICAgICAgICAgICAgaV90aW1lID0gMTk4OCwgICAgICAgICAgICAgICAgICNFbCBwZXJpb2RvIGRlIHRyYXRhbWllbnRvIFQwCiAgICAgICAgICAgICAgICAgICAgZ2VuZXJhdGVfcGxhY2Vib3M9VCAgICAgICAgICAgICNHZW5lcmFyZW1vcyBwbGFjZWJvcyBwYXJhIGhhY2VyIGluZmVyZW5jaWEuCiAgICAgICAgICAgICAgICAgICAgKSAlPiUKICAKICAjIFZhbW9zIGEgZ2VuZXJhciBsb3MgcHJlZGljdG9yZXMgYWdyZWdhZG9zIHBhcmEgYWp1c3RhciBsb3MgcG9uZGVyYWRvcmVzOgogICMgcHJvbWVkaW8gZGVsIGxvZ2FyaXRtbyBkZWwgaW5ncmVzbywgZWwgcHJlY2lvIGRlIGxvcyBjaWdhcnJpbGxvcyB5IGxhIAogICMgcHJwb3JjaW9uIGRlIGxhIHBvYmxhY2nDs24gZW50cmUgMTUgeSAyNCBhw7FvcyBkZSAxOTgwIC0gMTk4OAogIAogIGdlbmVyYXRlX3ByZWRpY3Rvcih0aW1lX3dpbmRvdyA9IDE5ODA6MTk4OCwgICAgICAgICAgICAgICAgICMgTG9zIHBlcmlvZG9zIGhhc3RhIFQwPTE5ODgKICAgICAgICAgICAgICAgICAgICAgbG5faW5jb21lID0gbWVhbihsbmluY29tZSwgbmEucm0gPSBUKSwgICAjbG9nIGRlbCBpbmdyZXNvCiAgICAgICAgICAgICAgICAgICAgIHJldF9wcmljZSA9IG1lYW4ocmV0cHJpY2UsIG5hLnJtID0gVCksICAgI2VsIHByZWNpbyBkZSBsb3MgY2lnYXJyaWxsb3MKICAgICAgICAgICAgICAgICAgICAgeW91dGggPSBtZWFuKGFnZTE1dG8yNCwgbmEucm0gPSBUKSkgJT4lICAjcHJvcG9yY2lvbiBkZSBsYSBwb2IgZW50cmUgMTUgLSAyNCBhw7FvcwogIAogICMgcHJvbWVkaW8gZGVsIGNvbnN1bW8gZGUgY2VydmV6YSBlbiBlbCBkb25ub3IgcG9vbCBkZSAxOTg0IC0gMTk4OAogIGdlbmVyYXRlX3ByZWRpY3Rvcih0aW1lX3dpbmRvdyA9IDE5ODQ6MTk4OCwKICAgICAgICAgICAgICAgICAgICAgYmVlcl9zYWxlcyA9IG1lYW4oYmVlciwgbmEucm0gPSBUKSkgJT4lCiAgCiAgIyBsYXMgdmVudGFzIGRlIGNpZ2FycmlsbG9zIGF0cmFzYWRhcyAKICBnZW5lcmF0ZV9wcmVkaWN0b3IodGltZV93aW5kb3cgPSAxOTc1LAogICAgICAgICAgICAgICAgICAgICBjaWdzYWxlXzE5NzUgPSBjaWdzYWxlKSAlPiUKICBnZW5lcmF0ZV9wcmVkaWN0b3IodGltZV93aW5kb3cgPSAxOTgwLAogICAgICAgICAgICAgICAgICAgICBjaWdzYWxlXzE5ODAgPSBjaWdzYWxlKSAlPiUKICBnZW5lcmF0ZV9wcmVkaWN0b3IodGltZV93aW5kb3cgPSAxOTg4LAogICAgICAgICAgICAgICAgICAgICBjaWdzYWxlXzE5ODggPSBjaWdzYWxlKSAlPiUKICAKICAKICAjIGdlbmVyYSBsb3MgcG9uZGVyYWRvcmVzCiAgZ2VuZXJhdGVfd2VpZ2h0cyhvcHRpbWl6YXRpb25fd2luZG93ID0gMTk3MDoxOTg4LCAgIyBlbCBwZXJpb2RvIHBhcmEgb3B0aW1pemFjacOzbiBkZSBsb3MgcG9uZGVyYWRvcmVzCiAgICAgICAgICAgICAgICAgICBtYXJnaW5faXBvcCA9IC4wMiwgc2lnZl9pcG9wID0gNywgCiAgICAgICAgICAgICAgICAgICBib3VuZF9pcG9wID0gNiAgICAgICAgICAgICAgICAgICAgIyBvcGNpb25lcyBkZSBvcHRpbWl6YWNpw7NuCiAgKSAlPiUKICAKICAjIEdlbmVyYXIgZWwgY29udHJvbCBzaW50w6l0aWNvCiAgZ2VuZXJhdGVfY29udHJvbCgpCmBgYAoKUGFyYSBncmFmaWNhciBsYXMgdHJheWVjdG9yaWFzIGRlIGxhcyBzZXJpZXMgb2JzZXJ2YWRhcyB5IGxhIHNlcmllIHNpbnTDqXRpY2EgZGVsIHByb2JsZW1hIGFudGVyaW9yIHV0aWxpemFtb3MgZWwgc2lndWllbnRlIGNvbWFuZG86CgpgYGB7cn0Kc21va2luZ19vdXQgJT4lIHBsb3RfdHJlbmRzKCkgCmBgYApBZGVtw6FzLCBwb2RlbW9zIGdyYWZpY2FyIGxhcyBkaWZlcmVuY2lhcyBhYnNvbHV0YXMgZW50cmUgbGEgc2VyaWUgb2JzZXJ2YWRhIHkgbGEgc2VyaWUgc2ludMOpdGljYToKCmBgYHtyfQpzbW9raW5nX291dCAlPiUgcGxvdF9kaWZmZXJlbmNlcygpCmBgYAoKTG9zIHNpZ3VpZW50ZXMgaGlzdG9ncmFtYXMgbXVlc3RyYWwgbGEgcG9uZGVyYWNpw7NuIHF1ZSBlbCBwcm9ncmFtYSBsZSBkYSBhIGxhcyBkaWZlcmVudGVzIHVuaWRhZGVzLCBzaWVuZG8gVXRhaCBlbCBxdWUgcmVjaWJlIG1heW9yIHBvbmRlcmFjacOzbiB5IENvbmVjdGljdXQgZWwgcXVlIG1lbm9zIHBlcm8gZXN0cmljdGFtZW50ZSBwb3NpdGl2by4gQWRlbcOhcywgZGVsIGxhZG8gZGVyZWNobyBzZSBtdWVzdHJhbiBsYXMgcG9uZGVyYWNpb25lcyBwYXJhIGxhcyBkaWZlcm50ZXMgdmFyaWFibGVzIGVuIGxhIG1hdHJpeiBkZSBwcmVkaWN0b3JlcyBwYXJhIHRvZGFzIGxhcyB1bmlkYWRlcyAkW1hfezB9OlhfezF9XSQKClJlY29yZW1vcyBxdWUgdGFudG8gZXN0ZSBtw6l0b2RvIGNvbW8gZWwgZGUgbGEgcGFxdWV0ZXLDrWEgYFN5bnRoYCBnZW5lcmFuIGxvcyBwZXNvcyBiYWpvIGxhcyBzaWd1aWVudGVzIGNvbmRpY2lvbmVzOgoKJCRcc3VtX3tqPTJ9XntKKzF9XG9tZWdhX3tqfT0xJCQKIHkgCiQkXG9tZWdhX3tqfVxnZXEgMCBccXVhZCBcdGV4dHtjb259IFxxdWFkIGo9MiwuLi4sSisxJCQKYGBge3J9CnNtb2tpbmdfb3V0ICU+JSBwbG90X3dlaWdodHMoKQpgYGAKRGUgYWN1ZXJkbyBjb24gQWJhZGllIGV0LiBhbC4sICgyMDEwKSBlbCBtw6l0b2RvIGRlIGNvbnRyb2wgc2ludMOpdGljbyBwcm9kdWNlIHBvbmRlcmFkb3JlcyBpZ3VhbGVzIGEgY2VybyBwYXJhIGxhIG1heW9yw61hIGRlIGxhcyB1bmlkYWRlcyBkZWwgKmRvbm9yIHBvb2wqLgoKCgpgYGB7cn0Kc21va2luZ19vdXQgJT4lIGdyYWJfYmFsYW5jZV90YWJsZSgpCmBgYAojIyMjIEluZmVyZW5jaWEKCkVuIGN1YW50byBhIGxhIGluZmVyZW5jaWEsIGVsIG3DqXRvZG8gc2UgYmFzYSBlbiByZXBldGlyIGVsIG3DqXRvZG8gcGFyYSBjYWRhIGRvbmFudGUgZW4gZWwgZ3J1cG8gZGUgZG9uYW50ZXMgZXhhY3RhbWVudGUgZGUgbGEgbWlzbWEgbWFuZXJhIHF1ZSBzZSBoaXpvIHBhcmEgbGEgdW5pZGFkIHRyYXRhZGEsIGVzIGRlY2lyLCBnZW5lcmFuZG8gY29udHJvbGVzIHNpbnTDqXRpY29zIGRlIHBsYWNlYm8uIEFsIGVzdGFibGVjZXIgYGdlbmVyYXRlX3BsYWNlYm9zID0gVFJVRWAgYWwgaW5pY2lhbGl6YXIgbGEgdHViZXLDrWEgJ3N5bnRoJyBjb24gYHN5bnRoZXRpY19jb250cm9sKClgLCBsb3MgY2Fzb3MgZGUgcGxhY2VibyBzZSBnZW5lcmFuIGF1dG9tw6F0aWNhbWVudGUgYWwgY29uc3RydWlyIGVsIGNvbnRyb2wgc2ludMOpdGljbyBkZSBpbnRlcsOpcy4gRXN0byBmYWNpbGl0YSBsYSBleHBsb3JhY2nDs24gZGUgY3XDoW4gw7puaWNhIGVzIGxhIGRpZmVyZW5jaWEgZW50cmUgbGEgdW5pZGFkIG9ic2VydmFkYSB5IGxhIHVuaWRhZCBzaW50w6l0aWNhIGVuIGNvbXBhcmFjacOzbiBjb24gbG9zIHBsYWNlYm9zCgpgYGB7cn0Kc21va2luZ19vdXQgJT4lIAogIHBsb3RfcGxhY2Vib3MoKQpgYGAKCgpUZW4gZW4gY3VlbnRhIHF1ZSBsYSBmdW5jacOzbiBgcGxvdF9wbGFjZWJvcygpYCBhdXRvbcOhdGljYW1lbnRlIGVsaW1pbmEgY3VhbHF1aWVyIHBsYWNlYm8gcXVlIHNlIGFqdXN0ZSBtYWwgYSBsb3MgZGF0b3MgZW4gZWwgcGVyw61vZG8gcHJldmlvIGEgbGEgaW50ZXJ2ZW5jacOzbi4gTGEgcmF6w7NuIHBhcmEgaGFjZXJsbyBlcyBwdXJhbWVudGUgdmlzdWFsOiBlc2FzIHVuaWRhZGVzIHRpZW5kZW4gYSBkaXN0b3JzaW9uYXIgbGEgZXNjYWxhIGFsIHRyYXphciBsb3MgcGxhY2Vib3MuIFBhcmEgcmVhbGl6YXIgbGEgZWxpbWluYWNpw7NuLCBsYSBmdW5jacOzbiBleGFtaW5hIGVsIGVycm9yIGN1YWRyw6F0aWNvIG1lZGlvIGRlIHByZWRpY2Npw7NuIChNU1BFKSBlbiBlbCBwZXLDrW9kbyBwcmV2aW8gYSBsYSBpbnRlcnZlbmNpw7NuIChlcyBkZWNpciwgdW5hIG3DqXRyaWNhIHF1ZSByZWZsZWphIGN1w6FuIGJpZW4gc2UgYWp1c3RhIGVsIGNvbnRyb2wgc2ludMOpdGljbyBhIGxhIHNlcmllIGRlIHRpZW1wbyBkZSByZXN1bHRhZG9zIG9ic2VydmFkb3MgZW4gZWwgcGVyw61vZG8gcHJldmlvIGEgbGEgaW50ZXJ2ZW5jacOzbikuIFNpIHVuIGNvbnRyb2wgZGUgcGxhY2VibyB0aWVuZSB1biBNU1BFIHF1ZSBlcyBkb3MgdmVjZXMgbWF5b3IgcXVlIGVsIGNhc28gb2JqZXRpdm8gKHBvciBlamVtcGxvLCAiQ2FsaWZvcm5pYSIpLCBlbnRvbmNlcyBzZSBlbGltaW5hLiBQYXJhIGRlc2FjdGl2YXIgZXN0ZSBjb21wb3J0YW1pZW50bywgZXN0YWJsZWNlIGBwcnVuZSA9IEZBTFNFYC4KCmBgYHtyfQpzbW9raW5nX291dCAlPiUgcGxvdF9wbGFjZWJvcyhwcnVuZSA9IEZBTFNFKQpgYGAKCgpGaW5hbG1lbnRlLCBBZGFiaWUgZXQgYWwuIGVuIDIwMTAgZGVzY3JpYmVuIHVuYSBtYW5lcmEgZGUgY29uc3RydWlyIGxvcyB2YWxvcmVzIHAgZXhhY3RvcyBkZSBGaXNoZXIgZGl2aWRpZW5kbyBlbCBNU1BFIHBvc3RlcmlvciBhIGxhIGludGVydmVuY2nDs24gcG9yIGVsIE1TUEUgYW50ZXJpb3IgYSBsYSBpbnRlcnZlbmNpw7NuIHkgbHVlZ28gY2xhc2lmaWNhbmRvIHRvZG9zIGxvcyBjYXNvcyBlbiBvcmRlbiBkZXNjZW5kZW50ZSBzZWfDum4gZXN0YSBwcm9wb3JjacOzbi4gU2UgY29uc3RydXllIHVuIHZhbG9yIHAgdG9tYW5kbyBsYSBjbGFzaWZpY2FjacOzbiBlbnRyZSBlbCB0b3RhbCBkZSBjYXNvcy4gTGEgaWRlYSBzdWJ5YWNlbnRlIGVzIHF1ZSBzaSBlbCBjb250cm9sIHNpbnTDqXRpY28gc2UgYWp1c3RhIGJpZW4gYSBsYSBzZXJpZSBkZSB0aWVtcG8gb2JzZXJ2YWRhIChiYWpvIE1TUEUgZW4gZWwgcGVyw61vZG8gcHJldmlvKSB5IHNlIGRlc3bDrWEgZW4gZWwgcGVyw61vZG8gcG9zdGVyaW9yIChhbHRvIE1TUEUgZW4gZWwgcGVyw61vZG8gcG9zdGVyaW9yKSwgZW50b25jZXMgaGF5IHVuIGVmZWN0byBzaWduaWZpY2F0aXZvIGRlYmlkbyBhIGxhIGludGVydmVuY2nDs24uIFNpIGxhIGludGVydmVuY2nDs24gbm8gdHV2byBlZmVjdG8sIGVudG9uY2VzIGVsIHBlcsOtb2RvIHBvc3RlcmlvciB5IGVsIHBlcsOtb2RvIHByZXZpbyBkZWJlcsOtYW4gY29udGludWFyIG1hcGXDoW5kb3NlIGJhc3RhbnRlIGJpZW4sIGxvIHF1ZSBkYXLDrWEgY29tbyByZXN1bHRhZG8gdW5hIHByb3BvcmNpw7NuIGNlcmNhbmEgYSAxLiBTaSBsYXMgdW5pZGFkZXMgZGUgcGxhY2VibyBzZSBhanVzdGFuIGRlIG1hbmVyYSBzaW1pbGFyIGEgbG9zIGRhdG9zLCBlbnRvbmNlcyBubyBwb2RlbW9zIHJlY2hhemFyIGxhIGhpcMOzdGVzaXMgbnVsYSBkZSBxdWUgbGEgaW50ZXJ2ZW5jacOzbiBubyB0dXZvIGVmZWN0by4KCkVzdGEgcHJvcG9yY2nDs24gc2UgcHVlZGUgcmVwcmVzZW50YXIgZsOhY2lsbWVudGUgdXRpbGl6YW5kbyBgcGxvdF9tc3BlX3JhdGlvKClgLCBsbyBxdWUgb2ZyZWNlIGluZm9ybWFjacOzbiBzb2JyZSBsYSByYXJlemEgZGVsIGNhc28gZW4gZWwgcXVlIGxhIGludGVydmVuY2nDs24gcmVhbG1lbnRlIG9jdXJyacOzLgoKYGBge3J9CnNtb2tpbmdfb3V0ICU+JSBwbG90X21zcGVfcmF0aW8oKQpgYGAKCgpVbiBzZWd1bmRvIGVqZW1wbG86CgoKYGBge3J9CmxpYnJhcnkoU3ludGgpCmRhdGEoInN5bnRoLmRhdGEiKQoKc3ludGguZGF0YSA8LSBmaWx0ZXIoc3ludGguZGF0YSwgCiAgICAgICAgICAgICAgICAgICAgIHllYXIgPD0gMTk5NikKICAKc3ludGguZGF0YS5vdXQgPC0gCiAgc3ludGguZGF0YSAlPiUgCiAgc3ludGhldGljX2NvbnRyb2wob3V0Y29tZSA9IFksCiAgICAgICAgICAgICAgICAgICAgdW5pdCA9IG5hbWUsCiAgICAgICAgICAgICAgICAgICAgdGltZSA9IHllYXIsCiAgICAgICAgICAgICAgICAgICAgaV91bml0ID0gInRyZWF0ZWQucmVnaW9uIiwKICAgICAgICAgICAgICAgICAgICBpX3RpbWUgPSAxOTkxLAogICAgICAgICAgICAgICAgICAgIGdlbmVyYXRlX3BsYWNlYm9zID0gVAogICAgICAgICAgICAgICAgICAgICkgJT4lCiAgZ2VuZXJhdGVfcHJlZGljdG9yKHRpbWVfd2luZG93ID0gMTk4MDoxOTkxLAogICAgICAgICAgICAgICAgICAgeDEgPSBtZWFuKFgxLCBuYS5ybSA9IFQpLAogICAgICAgICAgICAgICAgICAgeDIgPSBtZWFuKFgyLCBuYS5ybSA9IFQpLAogICAgICAgICAgICAgICAgICAgeDMgPSBtZWFuKFgzLCBuYS5ybSA9IFQpCiAgICAgICAgICAgICAgICAgICApICAlPiUKICBnZW5lcmF0ZV9wcmVkaWN0b3IodGltZV93aW5kb3cgPSAxOTkxLAogICAgICAgICAgICAgICAgICAgICBZXzkxID0gWSkgJT4lCiAgZ2VuZXJhdGVfcHJlZGljdG9yKHRpbWVfd2luZG93ID0gMTk4NSwKICAgICAgICAgICAgICAgICAgICAgWV84NSA9IFkpICU+JQogIGdlbmVyYXRlX3ByZWRpY3Rvcih0aW1lX3dpbmRvdyA9IDE5ODAsCiAgICAgICAgICAgICAgICAgICAgIFlfODAgPSBZKSAlPiUKICBnZW5lcmF0ZV93ZWlnaHRzKG9wdGltaXphdGlvbl93aW5kb3cgPSAxOTcwOjE5OTEsCiAgICAgICAgICAgICAgICAgICBtYXJnaW5faXBvcCA9IC4wMiwgc2lnZl9pcG9wID0gNywgCiAgICAgICAgICAgICAgICAgICBib3VuZF9pcG9wID0gNgogICAgICAgICAgICAgICAgICAgKSAlPiUKICBnZW5lcmF0ZV9jb250cm9sKCkKCnN5bnRoLmRhdGEub3V0ICU+JQogIHBsb3RfdHJlbmRzKCkKYGBgCgoKYGBge3J9CnN5bnRoLmRhdGEub3V0ICU+JQogIHBsb3RfZGlmZmVyZW5jZXMoKQpgYGAKCgpgYGB7cn0Kc3ludGguZGF0YS5vdXQgJT4lCiAgcGxvdF93ZWlnaHRzKCkKYGBgCgoKCmBgYHtyfQpzeW50aC5kYXRhLm91dCAlPiUKICBncmFiX2JhbGFuY2VfdGFibGUoKQpgYGAKCjxicj48YnI+PGJyPjxicj48YnI+PGJyPgoKPHAgc3R5bGU9InRleHQtYWxpZ246Y2VudGVyOyI+CipDdWlkYXRlIGJiKgo8L3A+CgoK