Explicamos con ejemplos algunos comandos de R. Iremos completando este archivo durante el curso.

Tipos de Datos Básicos

num <- 1.2
print(num)
[1] 1.2

Se puede chequear el tipo de dato con el comando class()

class(num)
[1] "numeric"
int <- as.integer(2.2)
print(int)
[1] 2
class(int)
[1] "integer"

Tener en cuenta que el tipo integer no se asigna directamente

a <- 2
class(a)
[1] "numeric"
char <- "datacamp"
print(char)
[1] "datacamp"
class(char)
[1] "character"
char <- "1234"
class(char)
[1] "character"
log_true <- TRUE
print(log_true)
[1] TRUE
class(log_true)
[1] "logical"

Para acceder a todas las variables u objetos que están definidos en el workspace se puede usar el comando ls()

ls()
[1] "a"        "char"     "int"      "log_true" "num"     

Más tipos de datos

lis1 <- 1:5  # Integer Vector
lis1
[1] 1 2 3 4 5
class(lis1)
[1] "integer"
lis2 <- letters[1:5]
class(lis2)
[1] "character"
combined_list <- list(lis1,lis2)
combined_list
[[1]]
[1] 1 2 3 4 5

[[2]]
[1] "a" "b" "c" "d" "e"

Para acceder a un elemento particular de una lista, usar

lis1[[2]]
[1] 2
combined_list[[1]]
[1] 1 2 3 4 5
combined_list[[2]][3]
[1] "c"

Podemos convertir combined_list en una lista plana

flat_list <- unlist(combined_list)
flat_list
 [1] "1" "2" "3" "4" "5" "a" "b" "c" "d" "e"
flat_list[[5]]
[1] "5"
class(flat_list)
[1] "character"
marks <- c(88,65,90,40,65)
marks
[1] 88 65 90 40 65
class(marks)
[1] "numeric"

Para obtener la longitud de un vector

length(marks)
[1] 5

y para acceder a una o varias componente usamos

marks[4]
[1] 40
marks[2:4]
[1] 65 90 40

También podemos crear un vector con enteros de 1001 a 2000 así

vec <- c(1000:1999)
length(vec)
[1] 1000
vec[length(vec)/2 + 1]
[1] 1500

Si queremos solo números impares, podemos usar la función seq() que tiene tres parámetros: start, end y el paso

seq(1,10,by = 2)
[1] 1 3 5 7 9

Veamos la diferencia entre un vector y una lista

v1 <- c(1,"2")
v1
[1] "1" "2"
class(v1)
[1] "character"
l1 <- list(1,"2")
l1
[[1]]
[1] 1

[[2]]
[1] "2"
class(l1)
[1] "list"
M = matrix( c('AI','ML','DL','Tensorflow','Pytorch','Keras'), nrow = 2, ncol = 3, byrow = TRUE)
print(M)
     [,1]         [,2]      [,3]   
[1,] "AI"         "ML"      "DL"   
[2,] "Tensorflow" "Pytorch" "Keras"

Podemos acceder a una entrada o a una submatriz, a una fila o a una columna

M[1,3]
[1] "DL"
M[1:2,2:3]
     [,1]      [,2]   
[1,] "ML"      "DL"   
[2,] "Pytorch" "Keras"
M[2,]
[1] "Tensorflow" "Pytorch"    "Keras"     
M[,3]
[1] "DL"    "Keras"

Algunos comandos útiles

nrow(M)
[1] 2
ncol(M)
[1] 3
length(M)
[1] 6
dataset <- data.frame(
   Person = c("Aditya", "Ayush","Akshay"),
   Age = c(26, 26, 27),
   Weight = c(81,85, 90),
   Height = c(6,5.8,6.2),
   Salary = c(50000, 80000, 100000)
)
print(dataset)

Podemos combinar DataFrames por filas o columnas

df1 <- rbind(dataset,dataset)
df1
df2 <- cbind(dataset,dataset)
df2

Para ver solo algunas filas, las dos primeras o las dos últimas

head(df1,2)
tail(dataset,2)

Para ver algunas estadísticas de la tabla

summary(dataset)
    Person               Age            Weight     
 Length:3           Min.   :26.00   Min.   :81.00  
 Class :character   1st Qu.:26.00   1st Qu.:83.00  
 Mode  :character   Median :26.00   Median :85.00  
                    Mean   :26.33   Mean   :85.33  
                    3rd Qu.:26.50   3rd Qu.:87.50  
                    Max.   :27.00   Max.   :90.00  
     Height        Salary      
 Min.   :5.8   Min.   : 50000  
 1st Qu.:5.9   1st Qu.: 65000  
 Median :6.0   Median : 80000  
 Mean   :6.0   Mean   : 76667  
 3rd Qu.:6.1   3rd Qu.: 90000  
 Max.   :6.2   Max.   :100000  

Loops

Veamos algunos ejemplos de loops en R

 # Create a vector filled with random normal values
u1 <- rnorm(30)
print(u1)
 [1] -0.08355241  0.12287755  0.41484391 -0.45175587  0.31656793
 [6] -0.78208086 -0.96967252 -0.60717833  0.80946710 -0.66751340
[11] -0.18284893  0.18866932 -0.40542095 -2.01813661  0.63311748
[16]  0.45466150  0.86487274  0.59858002 -0.80370830 -0.69154357
[21]  1.51927394 -0.13430134  1.33450035 -0.17345346 -1.28554066
[26] -1.07423761  0.19504794 -0.10920017  0.91206408 -0.02167046
print("This loop calculates the square of the first 10 elements of vector u1")
[1] "This loop calculates the square of the first 10 elements of vector u1"
usq <- 0
for(i in 1:10) {
  # i-th element of `u1` squared into `i`-th position of `usq`
  usq[i] <- u1[i]*u1[i]
}

print(usq)
 [1] 0.006981005 0.015098893 0.172095470 0.204083369 0.100215251
 [6] 0.611650464 0.940264791 0.368665518 0.655236978 0.445574146
# cuántos números aleatorios uniformes entre 0 y 9 tenemos que generar hasta obtener un 5?
n <- as.integer(runif(1)*10)
instancias <- 1
while(n!=0){
  n <- as.integer(runif(1)*10)
  instancias <- instancias+1
}
print(instancias)
[1] 24
# idem anterior
instancias <- 0
repeat{
  x <- as.integer(runif(1)*10)
  instancias <- instancias+1
  if(x==5) break()
}
print(instancias)
[1] 2

La función break sale del loop y ejecuta la instrucción siguiente al mismo. En el caso de loops anidados, break solo sale del loop interno

# Make a lower triangular matrix (zeroes in upper right corner)
m=10 
n=10

# A counter to count the assignment
ctr=0

# Create a 10 x 10 matrix with zeroes 
mymat = matrix(0,m,n)

for(i in 1:m) {
  for(j in 1:n) {   
    if(i==j) { 
      break;
    } else {
       # you assign the values only when i<>j
      mymat[i,j] = i*j
      ctr=ctr+1
      }
  }
}

# Print how many matrix cells were assigned
print(mymat)
      [,1] [,2] [,3] [,4] [,5] [,6] [,7] [,8] [,9] [,10]
 [1,]    0    0    0    0    0    0    0    0    0     0
 [2,]    2    0    0    0    0    0    0    0    0     0
 [3,]    3    6    0    0    0    0    0    0    0     0
 [4,]    4    8   12    0    0    0    0    0    0     0
 [5,]    5   10   15   20    0    0    0    0    0     0
 [6,]    6   12   18   24   30    0    0    0    0     0
 [7,]    7   14   21   28   35   42    0    0    0     0
 [8,]    8   16   24   32   40   48   56    0    0     0
 [9,]    9   18   27   36   45   54   63   72    0     0
[10,]   10   20   30   40   50   60   70   80   90     0

Evitando loops

Cuando es posible, conviene evitar los loops. Aquí algunas herramientas


# construimos una matriz de 4x5, que contiene números de 1 a 5
mymat<-matrix(rep(seq(5), 4), ncol = 5)

# aplicamos la función `sum` a las filas de `mymat` (observar el '1') 
apply(mymat, 1, sum)
[1] 15 15 15 15
# aplicamos la función `sum` a las columnas de `mymat` (observar el '2')
apply(mymat, 2, sum)
[1] 10 11 12 13 14
# Podemos aplicar una función definida por el usuario alas filas o columnas. en este caso sumamos `y=4.5` a la 'sum' de cada fila de `mymat` 
apply(mymat, 1, function(x, y) sum(x) + y, y=4.5)
[1] 19.5 19.5 19.5 19.5
# Aplicamos la función `summary` a cada columna
apply(mymat, 2, function(x) summary(x))
        [,1] [,2] [,3] [,4] [,5]
Min.    1.00 1.00 1.00 1.00 2.00
1st Qu. 1.75 1.75 1.75 2.50 2.75
Median  2.50 2.50 3.00 3.50 3.50
Mean    2.50 2.75 3.00 3.25 3.50
3rd Qu. 3.25 3.50 4.25 4.25 4.25
Max.    4.00 5.00 5.00 5.00 5.00

Gráficos sencillos con Plot

Plot

x <- seq(-pi,pi,0.1)
plot(x, sin(x))

Agregando título y nombre a los ejes

plot(x, sin(x),
main="La función seno",
ylab="sin(x)")

Cambiando el color y tipo

El color se puede cambiar con el parámetro col, como col="blue", y el tipo con el parámetro type, que acepta los siguientes valores

  • “p” - points
  • “l” - lines
  • “b” - both points and lines
  • “c” - empty points joined by lines
  • “o” - overplotted points and lines
  • “s” and “S” - stair steps
  • “h” - histogram-like vertical lines
  • “n” - does not produce any points or lines
plot(x, sin(x),
main="The Sine Function",
ylab="sin(x)",
type="l",
col="blue")

Superponiendo gráficos y la función legend()

plot(x, sin(x),
main="Overlaying Graphs",
ylab="",
type="l",
col="blue")
lines(x,cos(x), col="red")
legend("topleft",
c("sin(x)","cos(x)"),
fill=c("blue","red")
)

Múltiples plots

par(mfrow=c(1,2))
plot(x, sin(x),
     main="Función seno",
     ylab="",
     type="l",
     col="blue")
plot(x,cos(x), 
     main="Función coseno",
     ylab="",
     type="l",
     col="red")

Resolviendo problemas de valores iniciales

Tomado de Package deSolve: Solving Initial Value Differential Equations in R por K. Soetaert, T. Petzoldt y R. Woodrow Setzer.

Consideremos las ecuaciones de Lorenz que representan un comportamiento idealizado de la atmósfera terrestre. Este modelo describe la dinámica de tres variables de estado, \(X\), \(Y\) y \(Z\):

\[\begin{eqnarray*} \frac{dX}{dt}&=&aX+YZ\\ \frac{dY}{dt}&=&b(Y-Z)\\ \frac{dZ}{dt}&=&-XY+cY-Z \end{eqnarray*}\] con condiciones iniciales \[ X(0)=Y(0)=Z(0)=1 \] y con \(a,b\) y \(c\) que son parámetros con valores \(-\frac83, -10\) y \(28\) respectivamente.

La implementación de este problemas de valores iniciales puede en dos partes: la especificación del modelo y la aplicación del modelo.

Especificación del modelo

Definimos dos vectores conteniendo los parámetros del modelo y las variables de estado con las condiciones iniciales

Parámetros

parameters <- c(a = -8/3, b = -10, c = 28)

Variables de estado

state <- c(X = 1, Y = 1, Z = 1)

Ecuaciones

Las ecuaciones se especifican por una función Lorenz que, dados los parámetros y variables de estado, devuelve las derivadas de las mismas.

Lorenz <- function(t, state, parameters) {
  # para poder usar los nombres a,b,c,X,Y,Z
  with(as.list(c(state, parameters)),{
  # rate of change
  dX <- a*X + Y*Z
  dY <- b * (Y-Z)
  dZ <- -X*Y + c*Y - Z
 
  # devuelve las derivadas dX,dY,dZ como vector
  list(c(dX, dY, dZ))
  }) # end with(as.list ...
  }

Aplicación del modelo

Rango de tiempo

times <- seq(0, 20, by = 0.01)

Integración

Usaremos la función ode del paquete deSolve que no está presente en la distribución básica, por eso agregamos este paquete y luego aplicamos la función

library(deSolve)
out <- ode(y = state, times = times, func = Lorenz, parms = parameters)
head(out)
     time         X        Y        Z
[1,] 0.00 1.0000000 1.000000 1.000000
[2,] 0.01 0.9848912 1.012567 1.259918
[3,] 0.02 0.9731148 1.048823 1.523999
[4,] 0.03 0.9651593 1.107207 1.798314
[5,] 0.04 0.9617377 1.186866 2.088545
[6,] 0.05 0.9638068 1.287555 2.400161
class(out)
[1] "deSolve" "matrix" 

ode devuelve un objeto de la clase deSolve con una matriz que contiene una columna para el tiempo y una columna por cada variable de estado. Cada fila de la matriz contiene los valores de las variables en el tiempo correspondiente.

Gráficos

par(oma = c(0, 0, 1, 0))
plot(out, xlab = "time", ylab = "-")
plot(out[, "X"], out[, "Z"], pch = ".")
mtext(outer = TRUE, side = 3, "Lorenz model", cex = 1.0)

La función ode implementa un método por default. Sin embargo es posible que el método sea elegido por el usuario usando el parámetro method. Por ejemplo, si se quiere usar el método de Runge-Kutta de cuarto orden, podemos escribir

out <- ode(y = state, times = times, func = Lorenz, parms = parameters, method = "rk4")

Es posible usar los siguientes métodos: “lsoda” (default), “lsode”, “lsodes”, “lsodar”, “vode”, “daspk”, “euler”, “rk4”, “ode23”, “ode45”, “radau”, “bdf”, “bdf_d”, “adams”, “impAdams”, “impAdams_d”, “iteration”

Para ver las características de un método de Runge-Kutta utilizar la función rkMethod()

rkMethod("rk4")
$ID
[1] "rk4"

$varstep
[1] FALSE

$A
[1] 0.0 0.5 0.5 1.0

$b1
[1] 0.1666667 0.3333333 0.3333333 0.1666667

$c
[1] 0.0 0.5 0.5 1.0

$stage
[1] 4

$Qerr
[1] 4

attr(,"class")
[1] "list"     "rkMethod"

Los métodos euler y rk4 utilizan un paso en el tiempo fijo. Pueden implementarse como un método de Runge-Kutta general, con la función ode especificando un paso de tiempo independiente del argunmento times utilizando el argunmento hini. También pueden implementarse con las funciones simplificadas euler o rk4, en las que el paso en el tiempo está determinada por el times span vector times. Los demás métodos utilizan un paso de tiempo variable (adaptativo).

Operaciones con Matrices

Suponiendo que \(A\) y \(B\) son matrices y \(x\) y \(b\) vectores, estas son algunas operaciones que podemos realizar.

LS0tDQp0aXRsZTogIlIiDQpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sNCi0tLQ0KDQpFeHBsaWNhbW9zIGNvbiBlamVtcGxvcyBhbGd1bm9zIGNvbWFuZG9zIGRlIFIuIElyZW1vcyBjb21wbGV0YW5kbyBlc3RlIGFyY2hpdm8gZHVyYW50ZSBlbCBjdXJzby4NCg0KIyMgVGlwb3MgZGUgRGF0b3MgQsOhc2ljb3MNCg0KKiAqKk51bWVyaWMqKjoNCmBgYHtyfQ0KbnVtIDwtIDEuMg0KcHJpbnQobnVtKQ0KYGBgDQoNClNlIHB1ZWRlIGNoZXF1ZWFyIGVsIHRpcG8gZGUgZGF0byBjb24gZWwgY29tYW5kbyAgYGNsYXNzKClgDQoNCmBgYHtyfQ0KY2xhc3MobnVtKQ0KYGBgDQoNCiogKipJbnRlZ2VyKio6DQoNCmBgYHtyfQ0KaW50IDwtIGFzLmludGVnZXIoMi4yKQ0KcHJpbnQoaW50KQ0KYGBgDQoNCmBgYHtyfQ0KY2xhc3MoaW50KQ0KYGBgDQoNCg0KVGVuZXIgZW4gY3VlbnRhIHF1ZSBlbCB0aXBvIGludGVnZXIgbm8gc2UgYXNpZ25hIGRpcmVjdGFtZW50ZQ0KDQpgYGB7cn0NCmEgPC0gMg0KY2xhc3MoYSkNCmBgYA0KDQoqICoqQ2hhcmFjdGVyKio6DQoNCmBgYHtyfQ0KY2hhciA8LSAiZGF0YWNhbXAiDQpwcmludChjaGFyKQ0KYGBgDQoNCmBgYHtyfQ0KY2xhc3MoY2hhcikNCmBgYA0KDQpgYGB7cn0NCmNoYXIgPC0gIjEyMzQiDQpjbGFzcyhjaGFyKQ0KYGBgDQoNCiogKipMb2dpY2FsKio6DQoNCmBgYHtyfQ0KbG9nX3RydWUgPC0gVFJVRQ0KcHJpbnQobG9nX3RydWUpDQpjbGFzcyhsb2dfdHJ1ZSkNCmBgYA0KDQoNClBhcmEgYWNjZWRlciBhIHRvZGFzIGxhcyB2YXJpYWJsZXMgdSBvYmpldG9zIHF1ZSBlc3TDoW4gZGVmaW5pZG9zIGVuIGVsIHdvcmtzcGFjZSBzZSBwdWVkZSB1c2FyIGVsIGNvbWFuZG8gYGxzKClgDQpgYGB7cn0NCmxzKCkNCmBgYA0KDQojIyBNw6FzIHRpcG9zIGRlIGRhdG9zDQoNCiogKipMaXN0YXMqKjogZXMgdW5hIGNvbGVjY2nDs24gb3JkZW5hZGEgZGUgdmFsb3JlcywgcXVlIHB1ZWRlbiBzZXIgZGUgdmFyaW9zIHRpcG9zDQoNCmBgYHtyfQ0KbGlzMSA8LSAxOjUgICMgSW50ZWdlciBWZWN0b3INCmxpczENCmNsYXNzKGxpczEpDQpgYGANCg0KYGBge3J9DQpsaXMyIDwtIGxldHRlcnNbMTo1XQ0KY2xhc3MobGlzMikNCmBgYA0KYGBge3J9DQpjb21iaW5lZF9saXN0IDwtIGxpc3QobGlzMSxsaXMyKQ0KY29tYmluZWRfbGlzdA0KYGBgDQoNClBhcmEgYWNjZWRlciBhIHVuIGVsZW1lbnRvIHBhcnRpY3VsYXIgZGUgdW5hIGxpc3RhLCB1c2FyDQpgYGB7cn0NCmxpczFbWzJdXQ0KY29tYmluZWRfbGlzdFtbMV1dDQpjb21iaW5lZF9saXN0W1syXV1bM10NCmBgYA0KUG9kZW1vcyBjb252ZXJ0aXIgKmNvbWJpbmVkX2xpc3QqIGVuIHVuYSBsaXN0YSAqcGxhbmEqDQoNCmBgYHtyfQ0KZmxhdF9saXN0IDwtIHVubGlzdChjb21iaW5lZF9saXN0KQ0KZmxhdF9saXN0DQpmbGF0X2xpc3RbWzVdXQ0KY2xhc3MoZmxhdF9saXN0KQ0KYGBgDQoNCiogKipWZWN0b3JlcyoqOiBhbG1hY2VuYW4gbcO6bHRpcGxlcyB2YWxvcmVzIGRlbCBtaXNtbyB0aXBvLiBMb3MgcG9kZW1vcyBjb25zdHJ1aXIgY29uIGxhIGZ1bmNpw7NuIGBjKClgICgqYyogZGUgKmNvbWJpbmFyKiBvICpjb25jYXRlbmFyKikNCg0KYGBge3J9DQptYXJrcyA8LSBjKDg4LDY1LDkwLDQwLDY1KQ0KbWFya3MNCmNsYXNzKG1hcmtzKQ0KYGBgDQoNClBhcmEgb2J0ZW5lciBsYSAqbG9uZ2l0dWQqIGRlIHVuIHZlY3Rvcg0KDQpgYGB7cn0NCmxlbmd0aChtYXJrcykNCmBgYA0KeSBwYXJhIGFjY2VkZXIgYSB1bmEgbyB2YXJpYXMgY29tcG9uZW50ZSB1c2Ftb3MNCmBgYHtyfQ0KbWFya3NbNF0NCm1hcmtzWzI6NF0NCmBgYA0KDQpUYW1iacOpbiBwb2RlbW9zIGNyZWFyIHVuIHZlY3RvciBjb24gZW50ZXJvcyBkZSAxMDAxIGEgMjAwMCBhc8OtDQpgYGB7cn0NCnZlYyA8LSBjKDEwMDA6MTk5OSkNCmxlbmd0aCh2ZWMpDQp2ZWNbbGVuZ3RoKHZlYykvMiArIDFdDQpgYGANClNpIHF1ZXJlbW9zIHNvbG8gbsO6bWVyb3MgaW1wYXJlcywgcG9kZW1vcyB1c2FyIGxhIGZ1bmNpw7NuIGBzZXEoKWAgcXVlIHRpZW5lIHRyZXMgcGFyw6FtZXRyb3M6IHN0YXJ0LCBlbmQgeSBlbCBwYXNvDQoNCmBgYHtyfQ0Kc2VxKDEsMTAsYnkgPSAyKQ0KYGBgDQoNCg0KVmVhbW9zIGxhIGRpZmVyZW5jaWEgZW50cmUgdW4gdmVjdG9yIHkgdW5hIGxpc3RhDQoNCmBgYHtyfQ0KdjEgPC0gYygxLCIyIikNCnYxDQpjbGFzcyh2MSkNCmBgYA0KYGBge3J9DQpsMSA8LSBsaXN0KDEsIjIiKQ0KbDENCmNsYXNzKGwxKQ0KYGBgDQoNCiogKipNYXRyaXgqKjogYWxtYWNlbmEgZGF0b3MgZGUgdW4gbWlzbW8gdGlwbyBlbiBmb3JtYSBkZSBtYXRyaXoNCmBgYHtyfQ0KTSA9IG1hdHJpeCggYygnQUknLCdNTCcsJ0RMJywnVGVuc29yZmxvdycsJ1B5dG9yY2gnLCdLZXJhcycpLCBucm93ID0gMiwgbmNvbCA9IDMsIGJ5cm93ID0gVFJVRSkNCnByaW50KE0pDQpgYGANCg0KUG9kZW1vcyBhY2NlZGVyIGEgdW5hIGVudHJhZGEgbyBhIHVuYSBzdWJtYXRyaXosIGEgdW5hIGZpbGEgbyBhIHVuYSBjb2x1bW5hDQpgYGB7cn0NCk1bMSwzXQ0KTVsxOjIsMjozXQ0KTVsyLF0NCk1bLDNdDQpgYGANCg0KQWxndW5vcyBjb21hbmRvcyDDunRpbGVzDQpgYGB7cn0NCm5yb3coTSkNCm5jb2woTSkNCmxlbmd0aChNKQ0KYGBgDQoqICoqRGF0YUZyYW1lcyoqOiBlcyB1bmEgbWF0cml6IGdlbmVyYWxpemFkYSwgbyAqKnRhYmxhKiosIGVuIGxhIHF1ZSBjYWRhIGNvbHVtbmEgcHVlZGUgdGVuZXIgZGlmZXJlbnRlcyB0aXBvcyBkZSBkYXRvcy4gQ2FkYSBjb2x1bW5hIGNvcnJlc3BvbmRlIGEgdW5hIGNhcmFjdGVyw61zdGljYSBvIHZhcmlhYmxlIG9ic2VydmFkYS4gU2UgY3JlYSBjb24gbGEgZnVuY2nDs24gYGRhdGEuZnJhbWUoKWANCmBgYHtyfQ0KZGF0YXNldCA8LSBkYXRhLmZyYW1lKA0KICAgUGVyc29uID0gYygiQWRpdHlhIiwgIkF5dXNoIiwiQWtzaGF5IiksDQogICBBZ2UgPSBjKDI2LCAyNiwgMjcpLA0KICAgV2VpZ2h0ID0gYyg4MSw4NSwgOTApLA0KICAgSGVpZ2h0ID0gYyg2LDUuOCw2LjIpLA0KICAgU2FsYXJ5ID0gYyg1MDAwMCwgODAwMDAsIDEwMDAwMCkNCikNCnByaW50KGRhdGFzZXQpDQpgYGANCg0KUG9kZW1vcyBjb21iaW5hciBEYXRhRnJhbWVzIHBvciBmaWxhcyBvIGNvbHVtbmFzDQoNCmBgYHtyfQ0KZGYxIDwtIHJiaW5kKGRhdGFzZXQsZGF0YXNldCkNCmRmMQ0KYGBgDQoNCg0KYGBge3J9DQpkZjIgPC0gY2JpbmQoZGF0YXNldCxkYXRhc2V0KQ0KZGYyDQpgYGANClBhcmEgdmVyIHNvbG8gYWxndW5hcyBmaWxhcywgbGFzIGRvcyBwcmltZXJhcyBvIGxhcyBkb3Mgw7psdGltYXMNCmBgYHtyfQ0KaGVhZChkZjEsMikNCmBgYA0KDQpgYGB7cn0NCnRhaWwoZGF0YXNldCwyKQ0KYGBgDQpQYXJhIHZlciBhbGd1bmFzIGVzdGFkw61zdGljYXMgZGUgbGEgKip0YWJsYSoqDQpgYGB7cn0NCnN1bW1hcnkoZGF0YXNldCkNCmBgYA0KDQojIyBMb29wcw0KDQpWZWFtb3MgYWxndW5vcyBlamVtcGxvcyBkZSBsb29wcyBlbiBSDQoNCiogKipmb3IqKiBsb29wDQoNCmBgYHtyfQ0KICMgQ3JlYXRlIGEgdmVjdG9yIGZpbGxlZCB3aXRoIHJhbmRvbSBub3JtYWwgdmFsdWVzDQp1MSA8LSBybm9ybSgzMCkNCnByaW50KHUxKQ0KcHJpbnQoIlRoaXMgbG9vcCBjYWxjdWxhdGVzIHRoZSBzcXVhcmUgb2YgdGhlIGZpcnN0IDEwIGVsZW1lbnRzIG9mIHZlY3RvciB1MSIpDQp1c3EgPC0gMA0KZm9yKGkgaW4gMToxMCkgew0KICAjIGktdGggZWxlbWVudCBvZiBgdTFgIHNxdWFyZWQgaW50byBgaWAtdGggcG9zaXRpb24gb2YgYHVzcWANCiAgdXNxW2ldIDwtIHUxW2ldKnUxW2ldDQp9DQoNCnByaW50KHVzcSkNCmBgYA0KDQoqICoqV2hpbGUqKiBsb29wDQpgYGB7cn0NCiMgY3XDoW50b3MgbsO6bWVyb3MgYWxlYXRvcmlvcyB1bmlmb3JtZXMgZW50cmUgMCB5IDkgdGVuZW1vcyBxdWUgZ2VuZXJhciBoYXN0YSBvYnRlbmVyIHVuIDU/DQpuIDwtIGFzLmludGVnZXIocnVuaWYoMSkqMTApDQppbnN0YW5jaWFzIDwtIDENCndoaWxlKG4hPTApew0KICBuIDwtIGFzLmludGVnZXIocnVuaWYoMSkqMTApDQogIGluc3RhbmNpYXMgPC0gaW5zdGFuY2lhcysxDQp9DQpwcmludChpbnN0YW5jaWFzKQ0KYGBgDQoNCg0KKiAqKlJlcGVhdCoqIGxvb3ANCg0KYGBge3J9DQojIGlkZW0gYW50ZXJpb3INCmluc3RhbmNpYXMgPC0gMA0KcmVwZWF0ew0KICB4IDwtIGFzLmludGVnZXIocnVuaWYoMSkqMTApDQogIGluc3RhbmNpYXMgPC0gaW5zdGFuY2lhcysxDQogIGlmKHg9PTUpIGJyZWFrKCkNCn0NCnByaW50KGluc3RhbmNpYXMpDQpgYGANCiogU2FsaWVuZG8gZGUgdW4gbG9vcDogKipicmVhayoqDQoNCkxhIGZ1bmNpw7NuICpicmVhayogc2FsZSBkZWwgbG9vcCB5IGVqZWN1dGEgbGEgaW5zdHJ1Y2Npw7NuIHNpZ3VpZW50ZSBhbCBtaXNtby4gRW4gZWwgY2FzbyBkZSBsb29wcyBhbmlkYWRvcywgKmJyZWFrKiBzb2xvIHNhbGUgZGVsIGxvb3AgaW50ZXJubw0KDQpgYGB7cn0NCiMgTWFrZSBhIGxvd2VyIHRyaWFuZ3VsYXIgbWF0cml4ICh6ZXJvZXMgaW4gdXBwZXIgcmlnaHQgY29ybmVyKQ0KbT0xMCANCm49MTANCg0KIyBBIGNvdW50ZXIgdG8gY291bnQgdGhlIGFzc2lnbm1lbnQNCmN0cj0wDQoNCiMgQ3JlYXRlIGEgMTAgeCAxMCBtYXRyaXggd2l0aCB6ZXJvZXMgDQpteW1hdCA9IG1hdHJpeCgwLG0sbikNCg0KZm9yKGkgaW4gMTptKSB7DQogIGZvcihqIGluIDE6bikgeyAgIA0KICAgIGlmKGk9PWopIHsgDQogICAgICBicmVhazsNCiAgICB9IGVsc2Ugew0KICAgICAgICMgeW91IGFzc2lnbiB0aGUgdmFsdWVzIG9ubHkgd2hlbiBpPD5qDQogICAgICBteW1hdFtpLGpdID0gaSpqDQogICAgICBjdHI9Y3RyKzENCiAgICAgIH0NCiAgfQ0KfQ0KDQojIFByaW50IGhvdyBtYW55IG1hdHJpeCBjZWxscyB3ZXJlIGFzc2lnbmVkDQpwcmludChteW1hdCkNCmBgYA0KDQojIEV2aXRhbmRvIGxvb3BzDQoNCkN1YW5kbyBlcyBwb3NpYmxlLCBjb252aWVuZSBldml0YXIgbG9zIGxvb3BzLiBBcXXDrSBhbGd1bmFzIGhlcnJhbWllbnRhcw0KDQpgYGB7cn0NCg0KIyBjb25zdHJ1aW1vcyB1bmEgbWF0cml6IGRlIDR4NSwgcXVlIGNvbnRpZW5lIG7Dum1lcm9zIGRlIDEgYSA1DQpteW1hdDwtbWF0cml4KHJlcChzZXEoNSksIDQpLCBuY29sID0gNSkNCg0KIyBhcGxpY2Ftb3MgbGEgZnVuY2nDs24gYHN1bWAgYSBsYXMgZmlsYXMgZGUgYG15bWF0YCAob2JzZXJ2YXIgZWwgJzEnKSANCmFwcGx5KG15bWF0LCAxLCBzdW0pDQoNCiMgYXBsaWNhbW9zIGxhIGZ1bmNpw7NuIGBzdW1gIGEgbGFzIGNvbHVtbmFzIGRlIGBteW1hdGAgKG9ic2VydmFyIGVsICcyJykNCmFwcGx5KG15bWF0LCAyLCBzdW0pDQoNCiMgUG9kZW1vcyBhcGxpY2FyIHVuYSBmdW5jacOzbiBkZWZpbmlkYSBwb3IgZWwgdXN1YXJpbyBhbGFzIGZpbGFzIG8gY29sdW1uYXMuIGVuIGVzdGUgY2FzbyBzdW1hbW9zIGB5PTQuNWAgYSBsYSAnc3VtJyBkZSBjYWRhIGZpbGEgZGUgYG15bWF0YCANCmFwcGx5KG15bWF0LCAxLCBmdW5jdGlvbih4LCB5KSBzdW0oeCkgKyB5LCB5PTQuNSkNCg0KIyBBcGxpY2Ftb3MgbGEgZnVuY2nDs24gYHN1bW1hcnlgIGEgY2FkYSBjb2x1bW5hDQphcHBseShteW1hdCwgMiwgZnVuY3Rpb24oeCkgc3VtbWFyeSh4KSkNCmBgYA0KDQojIyBHcsOhZmljb3Mgc2VuY2lsbG9zIGNvbiBQbG90DQoNCiMjIyBQbG90DQoNCmBgYHtyfQ0KeCA8LSBzZXEoLXBpLHBpLDAuMSkNCnBsb3QoeCwgc2luKHgpKQ0KYGBgDQojIyMgQWdyZWdhbmRvIHTDrXR1bG8geSBub21icmUgYSBsb3MgZWplcw0KDQpgYGB7cn0NCnBsb3QoeCwgc2luKHgpLA0KbWFpbj0iTGEgZnVuY2nDs24gc2VubyIsDQp5bGFiPSJzaW4oeCkiKQ0KYGBgDQoNCiMjIyBDYW1iaWFuZG8gZWwgY29sb3IgeSB0aXBvDQoNCkVsIGNvbG9yIHNlIHB1ZWRlIGNhbWJpYXIgY29uIGVsIHBhcsOhbWV0cm8gYGNvbGAsIGNvbW8gYGNvbD0iYmx1ZSJgLCB5IGVsIHRpcG8gY29uIGVsIHBhcsOhbWV0cm8gYHR5cGVgLCBxdWUgYWNlcHRhIGxvcyBzaWd1aWVudGVzIHZhbG9yZXMNCg0KKiAicCIgLSBwb2ludHMNCiogImwiIC0gbGluZXMNCiogImIiIC0gYm90aCBwb2ludHMgYW5kIGxpbmVzDQoqICJjIiAtIGVtcHR5IHBvaW50cyBqb2luZWQgYnkgbGluZXMNCiogIm8iIC0gb3ZlcnBsb3R0ZWQgcG9pbnRzIGFuZCBsaW5lcw0KKiAicyIgYW5kICJTIiAtIHN0YWlyIHN0ZXBzDQoqICJoIiAtIGhpc3RvZ3JhbS1saWtlIHZlcnRpY2FsIGxpbmVzDQoqICJuIiAtIGRvZXMgbm90IHByb2R1Y2UgYW55IHBvaW50cyBvciBsaW5lcw0KDQpgYGB7cn0NCnBsb3QoeCwgc2luKHgpLA0KbWFpbj0iVGhlIFNpbmUgRnVuY3Rpb24iLA0KeWxhYj0ic2luKHgpIiwNCnR5cGU9ImwiLA0KY29sPSJibHVlIikNCmBgYA0KDQojIyMgU3VwZXJwb25pZW5kbyBncsOhZmljb3MgeSBsYSBmdW5jacOzbiBgbGVnZW5kKClgIA0KYGBge3J9DQpwbG90KHgsIHNpbih4KSwNCm1haW49Ik92ZXJsYXlpbmcgR3JhcGhzIiwNCnlsYWI9IiIsDQp0eXBlPSJsIiwNCmNvbD0iYmx1ZSIpDQpsaW5lcyh4LGNvcyh4KSwgY29sPSJyZWQiKQ0KbGVnZW5kKCJ0b3BsZWZ0IiwNCmMoInNpbih4KSIsImNvcyh4KSIpLA0KZmlsbD1jKCJibHVlIiwicmVkIikNCikNCmBgYA0KDQojIyMgTcO6bHRpcGxlcyBwbG90cw0KDQpgYGB7cn0NCnBhcihtZnJvdz1jKDEsMikpDQpwbG90KHgsIHNpbih4KSwNCiAgICAgbWFpbj0iRnVuY2nDs24gc2VubyIsDQogICAgIHlsYWI9IiIsDQogICAgIHR5cGU9ImwiLA0KICAgICBjb2w9ImJsdWUiKQ0KcGxvdCh4LGNvcyh4KSwgDQogICAgIG1haW49IkZ1bmNpw7NuIGNvc2VubyIsDQogICAgIHlsYWI9IiIsDQogICAgIHR5cGU9ImwiLA0KICAgICBjb2w9InJlZCIpDQpgYGANCg0KIyBSZXNvbHZpZW5kbyBwcm9ibGVtYXMgZGUgdmFsb3JlcyBpbmljaWFsZXMNCg0KVG9tYWRvIGRlIFtQYWNrYWdlIGRlU29sdmU6IFNvbHZpbmcgSW5pdGlhbCBWYWx1ZSBEaWZmZXJlbnRpYWwNCkVxdWF0aW9ucyBpbiBSXShodHRwczovL2NyYW4uci1wcm9qZWN0Lm9yZy93ZWIvcGFja2FnZXMvZGVTb2x2ZS92aWduZXR0ZXMvZGVTb2x2ZS5wZGYpIHBvciBLLiBTb2V0YWVydCwgVC4gUGV0em9sZHQgeSBSLiBXb29kcm93IFNldHplci4NCg0KQ29uc2lkZXJlbW9zIGxhcyBlY3VhY2lvbmVzIGRlIExvcmVueiBxdWUgcmVwcmVzZW50YW4gdW4gY29tcG9ydGFtaWVudG8gaWRlYWxpemFkbyBkZSBsYSBhdG3Ds3NmZXJhIHRlcnJlc3RyZS4gRXN0ZSBtb2RlbG8gZGVzY3JpYmUgbGEgZGluw6FtaWNhIGRlIHRyZXMgdmFyaWFibGVzIGRlIGVzdGFkbywgJFgkLCAkWSQgeSAkWiQ6DQoNClxbXGJlZ2lue2VxbmFycmF5Kn0NClxmcmFje2RYfXtkdH0mPSZhWCtZWlxcDQpcZnJhY3tkWX17ZHR9Jj0mYihZLVopXFwNClxmcmFje2RafXtkdH0mPSYtWFkrY1ktWg0KXGVuZHtlcW5hcnJheSp9XF0NCmNvbiBjb25kaWNpb25lcyBpbmljaWFsZXMNClxbDQpYKDApPVkoMCk9WigwKT0xDQpcXQ0KeSBjb24gJGEsYiQgeSAkYyQgcXVlIHNvbiBwYXLDoW1ldHJvcyBjb24gdmFsb3JlcyAkLVxmcmFjODMsIC0xMCQgeSAkMjgkIHJlc3BlY3RpdmFtZW50ZS4NCg0KTGEgaW1wbGVtZW50YWNpw7NuIGRlIGVzdGUgcHJvYmxlbWFzIGRlIHZhbG9yZXMgaW5pY2lhbGVzIHB1ZWRlIGVuIGRvcyBwYXJ0ZXM6IGxhICplc3BlY2lmaWNhY2nDs24gZGVsIG1vZGVsbyogeSBsYSAqYXBsaWNhY2nDs24gZGVsIG1vZGVsbyouDQoNCiogKmVzcGVjaWZpY2FjacOzbiBkZWwgbW9kZWxvKjoNCiAgKyBEZWZpbmlyIGxvcyBwYXLDoW1ldHJvcyB5IHN1cyB2YWxvcmVzDQogICsgRGVmaW5pciBsYXMgdmFyaWFibGVzIGRlIGVzdGFkbyB5IHN1cyBjb25kaWNpb25lcyBpbmljaWFsZXMNCiAgKyBJbXBsZW1lbnRhciBsYXMgZWN1YWNpb25lcyBkZWwgbW9kZWxvIHkgY2FsY3VsYXIgbGFzIHJhem9uZXMgZGUgY2FtYmlvIGRlIGxhcyB2YXJpYWJsZXMgZGUgZXN0YWRvDQoNCiogKmFwbGljYWNpw7NuIGRlbCBtb2RlbG8qOg0KICArIEVzcGVjaWZpY2FyIGVsIHJhbmdvIGRlIHRpZW1wbyBlbiBxdWUgc2UgZGVzZWFyIHJlc29sdmVyIGxhcyBlY3VhY2lvbmVzDQogICsgSW50ZWdyYXIgZWwgbW9kZWxvIA0KICArIEdyYWZpY2FyIGxvcyByZXN1bHRhZG9zDQoNCiMjIEVzcGVjaWZpY2FjacOzbiBkZWwgbW9kZWxvDQoNCkRlZmluaW1vcyBkb3MgdmVjdG9yZXMgY29udGVuaWVuZG8gbG9zIHBhcsOhbWV0cm9zIGRlbCBtb2RlbG8geSBsYXMgdmFyaWFibGVzIGRlIGVzdGFkbyBjb24gbGFzIGNvbmRpY2lvbmVzIGluaWNpYWxlcw0KDQojIyMgUGFyw6FtZXRyb3MNCmBgYHtyfQ0KcGFyYW1ldGVycyA8LSBjKGEgPSAtOC8zLCBiID0gLTEwLCBjID0gMjgpDQpgYGANCiMjIyBWYXJpYWJsZXMgZGUgZXN0YWRvDQpgYGB7cn0NCnN0YXRlIDwtIGMoWCA9IDEsIFkgPSAxLCBaID0gMSkNCmBgYA0KIyMjIEVjdWFjaW9uZXMNCkxhcyBlY3VhY2lvbmVzIHNlIGVzcGVjaWZpY2FuIHBvciB1bmEgZnVuY2nDs24gYExvcmVuemAgcXVlLCBkYWRvcyBsb3MgcGFyw6FtZXRyb3MgeSB2YXJpYWJsZXMgZGUgZXN0YWRvLCBkZXZ1ZWx2ZSBsYXMgZGVyaXZhZGFzIGRlIGxhcyBtaXNtYXMuDQoNCmBgYHtyfQ0KTG9yZW56IDwtIGZ1bmN0aW9uKHQsIHN0YXRlLCBwYXJhbWV0ZXJzKSB7DQogICMgcGFyYSBwb2RlciB1c2FyIGxvcyBub21icmVzIGEsYixjLFgsWSxaDQogIHdpdGgoYXMubGlzdChjKHN0YXRlLCBwYXJhbWV0ZXJzKSksew0KICAjIHJhdGUgb2YgY2hhbmdlDQogIGRYIDwtIGEqWCArIFkqWg0KICBkWSA8LSBiICogKFktWikNCiAgZFogPC0gLVgqWSArIGMqWSAtIFoNCiANCiAgIyBkZXZ1ZWx2ZSBsYXMgZGVyaXZhZGFzIGRYLGRZLGRaIGNvbW8gdmVjdG9yDQogIGxpc3QoYyhkWCwgZFksIGRaKSkNCiAgfSkgIyBlbmQgd2l0aChhcy5saXN0IC4uLg0KICB9DQpgYGANCg0KIyMgQXBsaWNhY2nDs24gZGVsIG1vZGVsbw0KIyMjIFJhbmdvIGRlIHRpZW1wbw0KYGBge3J9DQp0aW1lcyA8LSBzZXEoMCwgMjAsIGJ5ID0gMC4wMSkNCmBgYA0KIyMjIEludGVncmFjacOzbg0KVXNhcmVtb3MgbGEgZnVuY2nDs24gYG9kZWAgZGVsIHBhcXVldGUgKipkZVNvbHZlKiogcXVlIG5vIGVzdMOhIHByZXNlbnRlIGVuIGxhIGRpc3RyaWJ1Y2nDs24gYsOhc2ljYSwgcG9yIGVzbyBhZ3JlZ2Ftb3MgZXN0ZSBwYXF1ZXRlIHkgbHVlZ28gYXBsaWNhbW9zIGxhIGZ1bmNpw7NuDQpgYGB7cn0NCmxpYnJhcnkoZGVTb2x2ZSkNCm91dCA8LSBvZGUoeSA9IHN0YXRlLCB0aW1lcyA9IHRpbWVzLCBmdW5jID0gTG9yZW56LCBwYXJtcyA9IHBhcmFtZXRlcnMpDQpoZWFkKG91dCkNCmNsYXNzKG91dCkNCmBgYA0KYG9kZWAgZGV2dWVsdmUgdW4gb2JqZXRvIGRlIGxhIGNsYXNlICoqZGVTb2x2ZSoqIGNvbiB1bmEgbWF0cml6IHF1ZSBjb250aWVuZSB1bmEgY29sdW1uYSBwYXJhIGVsIHRpZW1wbyB5IHVuYSBjb2x1bW5hIHBvciBjYWRhIHZhcmlhYmxlIGRlIGVzdGFkby4gQ2FkYSBmaWxhIGRlIGxhIG1hdHJpeiBjb250aWVuZSBsb3MgdmFsb3JlcyBkZSBsYXMgdmFyaWFibGVzIGVuIGVsIHRpZW1wbyBjb3JyZXNwb25kaWVudGUuDQoNCiMjIyBHcsOhZmljb3MNCg0KYGBge3J9DQpwYXIob21hID0gYygwLCAwLCAxLCAwKSkNCnBsb3Qob3V0LCB4bGFiID0gInRpbWUiLCB5bGFiID0gIi0iKQ0KcGxvdChvdXRbLCAiWCJdLCBvdXRbLCAiWiJdLCBwY2ggPSAiLiIpDQptdGV4dChvdXRlciA9IFRSVUUsIHNpZGUgPSAzLCAiTG9yZW56IG1vZGVsIiwgY2V4ID0gMS4wKQ0KDQpgYGANCg0KTGEgZnVuY2nDs24gYG9kZWAgaW1wbGVtZW50YSB1biBtw6l0b2RvIHBvciBkZWZhdWx0LiBTaW4gZW1iYXJnbyBlcyBwb3NpYmxlIHF1ZSBlbCBtw6l0b2RvIHNlYSBlbGVnaWRvIHBvciBlbCB1c3VhcmlvIHVzYW5kbyBlbCBwYXLDoW1ldHJvIGBtZXRob2RgLiBQb3IgZWplbXBsbywgc2kgc2UgcXVpZXJlIHVzYXIgZWwgbcOpdG9kbyBkZSBSdW5nZS1LdXR0YSBkZSBjdWFydG8gb3JkZW4sIHBvZGVtb3MgZXNjcmliaXINCmBgYHtyfQ0Kb3V0IDwtIG9kZSh5ID0gc3RhdGUsIHRpbWVzID0gdGltZXMsIGZ1bmMgPSBMb3JlbnosIHBhcm1zID0gcGFyYW1ldGVycywgbWV0aG9kID0gInJrNCIpDQpgYGANCkVzIHBvc2libGUgdXNhciBsb3Mgc2lndWllbnRlcyBtw6l0b2RvczoNCuKAnGxzb2Rh4oCdIChkZWZhdWx0KSwg4oCcbHNvZGXigJ0sIOKAnGxzb2Rlc+KAnSwg4oCcbHNvZGFy4oCdLCDigJx2b2Rl4oCdLCDigJxkYXNwa+KAnSwg4oCcZXVsZXLigJ0sIOKAnHJrNOKAnSwg4oCcb2RlMjPigJ0sIOKAnG9kZTQ14oCdLCDigJxyYWRhdeKAnSwg4oCcYmRm4oCdLCDigJxiZGZfZOKAnSwg4oCcYWRhbXPigJ0sIOKAnGltcEFkYW1z4oCdLCDigJxpbXBBZGFtc19k4oCdLCDigJxpdGVyYXRpb27igJ0NCg0KUGFyYSB2ZXIgbGFzIGNhcmFjdGVyw61zdGljYXMgZGUgdW4gbcOpdG9kbyBkZSBSdW5nZS1LdXR0YSB1dGlsaXphciBsYSBmdW5jacOzbiBgcmtNZXRob2QoKWANCmBgYHtyfQ0KcmtNZXRob2QoInJrNCIpDQpgYGANCkxvcyBtw6l0b2RvcyBgZXVsZXJgIHkgYHJrNGAgdXRpbGl6YW4gdW4gcGFzbyBlbiBlbCB0aWVtcG8gZmlqby4gUHVlZGVuIGltcGxlbWVudGFyc2UgY29tbyB1biBtw6l0b2RvIGRlIFJ1bmdlLUt1dHRhIGdlbmVyYWwsIGNvbiBsYSBmdW5jacOzbiBgb2RlYCBlc3BlY2lmaWNhbmRvIHVuIHBhc28gZGUgdGllbXBvIGluZGVwZW5kaWVudGUgZGVsIGFyZ3VubWVudG8gYHRpbWVzYCB1dGlsaXphbmRvIGVsIGFyZ3VubWVudG8gYGhpbmlgLiBUYW1iacOpbiBwdWVkZW4gaW1wbGVtZW50YXJzZSBjb24gbGFzIGZ1bmNpb25lcyBzaW1wbGlmaWNhZGFzIGBldWxlcmAgbyBgcms0YCwgZW4gbGFzIHF1ZSBlbCBwYXNvIGVuIGVsIHRpZW1wbyBlc3TDoSBkZXRlcm1pbmFkYSBwb3IgZWwgKnRpbWVzIHNwYW4qIHZlY3RvciBgdGltZXNgLiBMb3MgZGVtw6FzIG3DqXRvZG9zIHV0aWxpemFuIHVuIHBhc28gZGUgdGllbXBvIHZhcmlhYmxlIChhZGFwdGF0aXZvKS4gDQoNCiMgT3BlcmFjaW9uZXMgY29uIE1hdHJpY2VzDQoNClN1cG9uaWVuZG8gcXVlICRBJCB5ICRCJCBzb24gbWF0cmljZXMgeSAkeCQgeSAkYiQgdmVjdG9yZXMsIGVzdGFzIHNvbiBhbGd1bmFzIG9wZXJhY2lvbmVzIHF1ZSBwb2RlbW9zIHJlYWxpemFyLg0KDQoqIGBBKkJgICAgTXVsdGlwbGljYSBlbGVtZW50byBwb3IgZWxlbWVudG8gJChBOjpCKSQNCiogYEElKiVCYCAgIE11bHRpcGxpY2EgbGFzIG1hdHJpY2VzICRBJCB5ICRCJA0KKiBgdChBKWAgICAgVHJhc3B1ZXN0YSBkZSAkQSQNCiogYGRpYWcoeClgIENyZWEgdW5hIG1hdHJpeiBkaWFnb25hbCwgY29uIGxvcyBlbGVtZW50b3MgZGUgJHgkIGVuIGxhIGRpYWdvbmFsIHByaW5jaXBhbA0KKiBgZGlhZyhBKWAgRGV2dWVsdmUgZWwgdmVjdG9yIGNvbnRlbmllbmRvIGxvcyBlbGVtZW50b3MgZGUgbGEgZGlhZ29uYWwgcHJpbmNpcGFsIGRlICRBJA0KKiBgZGlhZyhrKWAgQ3JlYSB1bmEgbWF0cml6IGNvbiAkayQgZW4gbGEgZGlhZ29uYWwsIHNpICRrJCBlcyB1biBlc2NhbGFyDQoqIGBzb2x2ZShBLGIpYCBDYWxjdWxhIGxhIHNvbHVjacOzbiAkeCQgZGUgJEF4PWIkDQoqIGBzb2x2ZShBKWAgQ2FsY3VsYSBsYSBpbnZlcnNhIGRlICRBJA0KKiBgeSA8LSBlaWdlbihBKWAgQ2FsY3VsYSBhdXRvdmFsb3JlcyB5IGF1dG92ZWN0b3JlcyBkZSAkQSQsIGB5JHZhbHVlc2AgY29udGllbmUgbG9zIGF1dG92YWxvcmVzLCBgeSR2ZWN0b3JzYCBjb250aWVuZSBsb3MgYXV0b3ZlY3RvcmVzDQoNCg0KDQoNCg0KDQoNCg==