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
- Listas: es una colección ordenada de valores, que pueden ser de varios tipos
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"
- Vectores: almacenan múltiples valores del mismo tipo. Los podemos construir con la función
c()
(c de combinar o concatenar)
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"
- Matrix: almacena datos de un mismo tipo en forma de matriz
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
- DataFrames: es una matriz generalizada, o tabla, en la que cada columna puede tener diferentes tipos de datos. Cada columna corresponde a una característica o variable observada. Se crea con la función
data.frame()
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
- Saliendo de un loop: break
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:
- Definir los parámetros y sus valores
- Definir las variables de estado y sus condiciones iniciales
- Implementar las ecuaciones del modelo y calcular las razones de cambio de las variables de estado
- aplicación del modelo:
- Especificar el rango de tiempo en que se desear resolver las ecuaciones
- Integrar el modelo
- Graficar los resultados
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.
A*B
Multiplica elemento por elemento \((A::B)\)
A%*%B
Multiplica las matrices \(A\) y \(B\)
t(A)
Traspuesta de \(A\)
diag(x)
Crea una matriz diagonal, con los elementos de \(x\) en la diagonal principal
diag(A)
Devuelve el vector conteniendo los elementos de la diagonal principal de \(A\)
diag(k)
Crea una matriz con \(k\) en la diagonal, si \(k\) es un escalar
solve(A,b)
Calcula la solución \(x\) de \(Ax=b\)
solve(A)
Calcula la inversa de \(A\)
y <- eigen(A)
Calcula autovalores y autovectores de \(A\), y$values
contiene los autovalores, y$vectors
contiene los autovectores
LS0tDQp0aXRsZTogIlIiDQpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sNCi0tLQ0KDQpFeHBsaWNhbW9zIGNvbiBlamVtcGxvcyBhbGd1bm9zIGNvbWFuZG9zIGRlIFIuIElyZW1vcyBjb21wbGV0YW5kbyBlc3RlIGFyY2hpdm8gZHVyYW50ZSBlbCBjdXJzby4NCg0KIyMgVGlwb3MgZGUgRGF0b3MgQsOhc2ljb3MNCg0KKiAqKk51bWVyaWMqKjoNCmBgYHtyfQ0KbnVtIDwtIDEuMg0KcHJpbnQobnVtKQ0KYGBgDQoNClNlIHB1ZWRlIGNoZXF1ZWFyIGVsIHRpcG8gZGUgZGF0byBjb24gZWwgY29tYW5kbyAgYGNsYXNzKClgDQoNCmBgYHtyfQ0KY2xhc3MobnVtKQ0KYGBgDQoNCiogKipJbnRlZ2VyKio6DQoNCmBgYHtyfQ0KaW50IDwtIGFzLmludGVnZXIoMi4yKQ0KcHJpbnQoaW50KQ0KYGBgDQoNCmBgYHtyfQ0KY2xhc3MoaW50KQ0KYGBgDQoNCg0KVGVuZXIgZW4gY3VlbnRhIHF1ZSBlbCB0aXBvIGludGVnZXIgbm8gc2UgYXNpZ25hIGRpcmVjdGFtZW50ZQ0KDQpgYGB7cn0NCmEgPC0gMg0KY2xhc3MoYSkNCmBgYA0KDQoqICoqQ2hhcmFjdGVyKio6DQoNCmBgYHtyfQ0KY2hhciA8LSAiZGF0YWNhbXAiDQpwcmludChjaGFyKQ0KYGBgDQoNCmBgYHtyfQ0KY2xhc3MoY2hhcikNCmBgYA0KDQpgYGB7cn0NCmNoYXIgPC0gIjEyMzQiDQpjbGFzcyhjaGFyKQ0KYGBgDQoNCiogKipMb2dpY2FsKio6DQoNCmBgYHtyfQ0KbG9nX3RydWUgPC0gVFJVRQ0KcHJpbnQobG9nX3RydWUpDQpjbGFzcyhsb2dfdHJ1ZSkNCmBgYA0KDQoNClBhcmEgYWNjZWRlciBhIHRvZGFzIGxhcyB2YXJpYWJsZXMgdSBvYmpldG9zIHF1ZSBlc3TDoW4gZGVmaW5pZG9zIGVuIGVsIHdvcmtzcGFjZSBzZSBwdWVkZSB1c2FyIGVsIGNvbWFuZG8gYGxzKClgDQpgYGB7cn0NCmxzKCkNCmBgYA0KDQojIyBNw6FzIHRpcG9zIGRlIGRhdG9zDQoNCiogKipMaXN0YXMqKjogZXMgdW5hIGNvbGVjY2nDs24gb3JkZW5hZGEgZGUgdmFsb3JlcywgcXVlIHB1ZWRlbiBzZXIgZGUgdmFyaW9zIHRpcG9zDQoNCmBgYHtyfQ0KbGlzMSA8LSAxOjUgICMgSW50ZWdlciBWZWN0b3INCmxpczENCmNsYXNzKGxpczEpDQpgYGANCg0KYGBge3J9DQpsaXMyIDwtIGxldHRlcnNbMTo1XQ0KY2xhc3MobGlzMikNCmBgYA0KYGBge3J9DQpjb21iaW5lZF9saXN0IDwtIGxpc3QobGlzMSxsaXMyKQ0KY29tYmluZWRfbGlzdA0KYGBgDQoNClBhcmEgYWNjZWRlciBhIHVuIGVsZW1lbnRvIHBhcnRpY3VsYXIgZGUgdW5hIGxpc3RhLCB1c2FyDQpgYGB7cn0NCmxpczFbWzJdXQ0KY29tYmluZWRfbGlzdFtbMV1dDQpjb21iaW5lZF9saXN0W1syXV1bM10NCmBgYA0KUG9kZW1vcyBjb252ZXJ0aXIgKmNvbWJpbmVkX2xpc3QqIGVuIHVuYSBsaXN0YSAqcGxhbmEqDQoNCmBgYHtyfQ0KZmxhdF9saXN0IDwtIHVubGlzdChjb21iaW5lZF9saXN0KQ0KZmxhdF9saXN0DQpmbGF0X2xpc3RbWzVdXQ0KY2xhc3MoZmxhdF9saXN0KQ0KYGBgDQoNCiogKipWZWN0b3JlcyoqOiBhbG1hY2VuYW4gbcO6bHRpcGxlcyB2YWxvcmVzIGRlbCBtaXNtbyB0aXBvLiBMb3MgcG9kZW1vcyBjb25zdHJ1aXIgY29uIGxhIGZ1bmNpw7NuIGBjKClgICgqYyogZGUgKmNvbWJpbmFyKiBvICpjb25jYXRlbmFyKikNCg0KYGBge3J9DQptYXJrcyA8LSBjKDg4LDY1LDkwLDQwLDY1KQ0KbWFya3MNCmNsYXNzKG1hcmtzKQ0KYGBgDQoNClBhcmEgb2J0ZW5lciBsYSAqbG9uZ2l0dWQqIGRlIHVuIHZlY3Rvcg0KDQpgYGB7cn0NCmxlbmd0aChtYXJrcykNCmBgYA0KeSBwYXJhIGFjY2VkZXIgYSB1bmEgbyB2YXJpYXMgY29tcG9uZW50ZSB1c2Ftb3MNCmBgYHtyfQ0KbWFya3NbNF0NCm1hcmtzWzI6NF0NCmBgYA0KDQpUYW1iacOpbiBwb2RlbW9zIGNyZWFyIHVuIHZlY3RvciBjb24gZW50ZXJvcyBkZSAxMDAxIGEgMjAwMCBhc8OtDQpgYGB7cn0NCnZlYyA8LSBjKDEwMDA6MTk5OSkNCmxlbmd0aCh2ZWMpDQp2ZWNbbGVuZ3RoKHZlYykvMiArIDFdDQpgYGANClNpIHF1ZXJlbW9zIHNvbG8gbsO6bWVyb3MgaW1wYXJlcywgcG9kZW1vcyB1c2FyIGxhIGZ1bmNpw7NuIGBzZXEoKWAgcXVlIHRpZW5lIHRyZXMgcGFyw6FtZXRyb3M6IHN0YXJ0LCBlbmQgeSBlbCBwYXNvDQoNCmBgYHtyfQ0Kc2VxKDEsMTAsYnkgPSAyKQ0KYGBgDQoNCg0KVmVhbW9zIGxhIGRpZmVyZW5jaWEgZW50cmUgdW4gdmVjdG9yIHkgdW5hIGxpc3RhDQoNCmBgYHtyfQ0KdjEgPC0gYygxLCIyIikNCnYxDQpjbGFzcyh2MSkNCmBgYA0KYGBge3J9DQpsMSA8LSBsaXN0KDEsIjIiKQ0KbDENCmNsYXNzKGwxKQ0KYGBgDQoNCiogKipNYXRyaXgqKjogYWxtYWNlbmEgZGF0b3MgZGUgdW4gbWlzbW8gdGlwbyBlbiBmb3JtYSBkZSBtYXRyaXoNCmBgYHtyfQ0KTSA9IG1hdHJpeCggYygnQUknLCdNTCcsJ0RMJywnVGVuc29yZmxvdycsJ1B5dG9yY2gnLCdLZXJhcycpLCBucm93ID0gMiwgbmNvbCA9IDMsIGJ5cm93ID0gVFJVRSkNCnByaW50KE0pDQpgYGANCg0KUG9kZW1vcyBhY2NlZGVyIGEgdW5hIGVudHJhZGEgbyBhIHVuYSBzdWJtYXRyaXosIGEgdW5hIGZpbGEgbyBhIHVuYSBjb2x1bW5hDQpgYGB7cn0NCk1bMSwzXQ0KTVsxOjIsMjozXQ0KTVsyLF0NCk1bLDNdDQpgYGANCg0KQWxndW5vcyBjb21hbmRvcyDDunRpbGVzDQpgYGB7cn0NCm5yb3coTSkNCm5jb2woTSkNCmxlbmd0aChNKQ0KYGBgDQoqICoqRGF0YUZyYW1lcyoqOiBlcyB1bmEgbWF0cml6IGdlbmVyYWxpemFkYSwgbyAqKnRhYmxhKiosIGVuIGxhIHF1ZSBjYWRhIGNvbHVtbmEgcHVlZGUgdGVuZXIgZGlmZXJlbnRlcyB0aXBvcyBkZSBkYXRvcy4gQ2FkYSBjb2x1bW5hIGNvcnJlc3BvbmRlIGEgdW5hIGNhcmFjdGVyw61zdGljYSBvIHZhcmlhYmxlIG9ic2VydmFkYS4gU2UgY3JlYSBjb24gbGEgZnVuY2nDs24gYGRhdGEuZnJhbWUoKWANCmBgYHtyfQ0KZGF0YXNldCA8LSBkYXRhLmZyYW1lKA0KICAgUGVyc29uID0gYygiQWRpdHlhIiwgIkF5dXNoIiwiQWtzaGF5IiksDQogICBBZ2UgPSBjKDI2LCAyNiwgMjcpLA0KICAgV2VpZ2h0ID0gYyg4MSw4NSwgOTApLA0KICAgSGVpZ2h0ID0gYyg2LDUuOCw2LjIpLA0KICAgU2FsYXJ5ID0gYyg1MDAwMCwgODAwMDAsIDEwMDAwMCkNCikNCnByaW50KGRhdGFzZXQpDQpgYGANCg0KUG9kZW1vcyBjb21iaW5hciBEYXRhRnJhbWVzIHBvciBmaWxhcyBvIGNvbHVtbmFzDQoNCmBgYHtyfQ0KZGYxIDwtIHJiaW5kKGRhdGFzZXQsZGF0YXNldCkNCmRmMQ0KYGBgDQoNCg0KYGBge3J9DQpkZjIgPC0gY2JpbmQoZGF0YXNldCxkYXRhc2V0KQ0KZGYyDQpgYGANClBhcmEgdmVyIHNvbG8gYWxndW5hcyBmaWxhcywgbGFzIGRvcyBwcmltZXJhcyBvIGxhcyBkb3Mgw7psdGltYXMNCmBgYHtyfQ0KaGVhZChkZjEsMikNCmBgYA0KDQpgYGB7cn0NCnRhaWwoZGF0YXNldCwyKQ0KYGBgDQpQYXJhIHZlciBhbGd1bmFzIGVzdGFkw61zdGljYXMgZGUgbGEgKip0YWJsYSoqDQpgYGB7cn0NCnN1bW1hcnkoZGF0YXNldCkNCmBgYA0KDQojIyBMb29wcw0KDQpWZWFtb3MgYWxndW5vcyBlamVtcGxvcyBkZSBsb29wcyBlbiBSDQoNCiogKipmb3IqKiBsb29wDQoNCmBgYHtyfQ0KICMgQ3JlYXRlIGEgdmVjdG9yIGZpbGxlZCB3aXRoIHJhbmRvbSBub3JtYWwgdmFsdWVzDQp1MSA8LSBybm9ybSgzMCkNCnByaW50KHUxKQ0KcHJpbnQoIlRoaXMgbG9vcCBjYWxjdWxhdGVzIHRoZSBzcXVhcmUgb2YgdGhlIGZpcnN0IDEwIGVsZW1lbnRzIG9mIHZlY3RvciB1MSIpDQp1c3EgPC0gMA0KZm9yKGkgaW4gMToxMCkgew0KICAjIGktdGggZWxlbWVudCBvZiBgdTFgIHNxdWFyZWQgaW50byBgaWAtdGggcG9zaXRpb24gb2YgYHVzcWANCiAgdXNxW2ldIDwtIHUxW2ldKnUxW2ldDQp9DQoNCnByaW50KHVzcSkNCmBgYA0KDQoqICoqV2hpbGUqKiBsb29wDQpgYGB7cn0NCiMgY3XDoW50b3MgbsO6bWVyb3MgYWxlYXRvcmlvcyB1bmlmb3JtZXMgZW50cmUgMCB5IDkgdGVuZW1vcyBxdWUgZ2VuZXJhciBoYXN0YSBvYnRlbmVyIHVuIDU/DQpuIDwtIGFzLmludGVnZXIocnVuaWYoMSkqMTApDQppbnN0YW5jaWFzIDwtIDENCndoaWxlKG4hPTApew0KICBuIDwtIGFzLmludGVnZXIocnVuaWYoMSkqMTApDQogIGluc3RhbmNpYXMgPC0gaW5zdGFuY2lhcysxDQp9DQpwcmludChpbnN0YW5jaWFzKQ0KYGBgDQoNCg0KKiAqKlJlcGVhdCoqIGxvb3ANCg0KYGBge3J9DQojIGlkZW0gYW50ZXJpb3INCmluc3RhbmNpYXMgPC0gMA0KcmVwZWF0ew0KICB4IDwtIGFzLmludGVnZXIocnVuaWYoMSkqMTApDQogIGluc3RhbmNpYXMgPC0gaW5zdGFuY2lhcysxDQogIGlmKHg9PTUpIGJyZWFrKCkNCn0NCnByaW50KGluc3RhbmNpYXMpDQpgYGANCiogU2FsaWVuZG8gZGUgdW4gbG9vcDogKipicmVhayoqDQoNCkxhIGZ1bmNpw7NuICpicmVhayogc2FsZSBkZWwgbG9vcCB5IGVqZWN1dGEgbGEgaW5zdHJ1Y2Npw7NuIHNpZ3VpZW50ZSBhbCBtaXNtby4gRW4gZWwgY2FzbyBkZSBsb29wcyBhbmlkYWRvcywgKmJyZWFrKiBzb2xvIHNhbGUgZGVsIGxvb3AgaW50ZXJubw0KDQpgYGB7cn0NCiMgTWFrZSBhIGxvd2VyIHRyaWFuZ3VsYXIgbWF0cml4ICh6ZXJvZXMgaW4gdXBwZXIgcmlnaHQgY29ybmVyKQ0KbT0xMCANCm49MTANCg0KIyBBIGNvdW50ZXIgdG8gY291bnQgdGhlIGFzc2lnbm1lbnQNCmN0cj0wDQoNCiMgQ3JlYXRlIGEgMTAgeCAxMCBtYXRyaXggd2l0aCB6ZXJvZXMgDQpteW1hdCA9IG1hdHJpeCgwLG0sbikNCg0KZm9yKGkgaW4gMTptKSB7DQogIGZvcihqIGluIDE6bikgeyAgIA0KICAgIGlmKGk9PWopIHsgDQogICAgICBicmVhazsNCiAgICB9IGVsc2Ugew0KICAgICAgICMgeW91IGFzc2lnbiB0aGUgdmFsdWVzIG9ubHkgd2hlbiBpPD5qDQogICAgICBteW1hdFtpLGpdID0gaSpqDQogICAgICBjdHI9Y3RyKzENCiAgICAgIH0NCiAgfQ0KfQ0KDQojIFByaW50IGhvdyBtYW55IG1hdHJpeCBjZWxscyB3ZXJlIGFzc2lnbmVkDQpwcmludChteW1hdCkNCmBgYA0KDQojIEV2aXRhbmRvIGxvb3BzDQoNCkN1YW5kbyBlcyBwb3NpYmxlLCBjb252aWVuZSBldml0YXIgbG9zIGxvb3BzLiBBcXXDrSBhbGd1bmFzIGhlcnJhbWllbnRhcw0KDQpgYGB7cn0NCg0KIyBjb25zdHJ1aW1vcyB1bmEgbWF0cml6IGRlIDR4NSwgcXVlIGNvbnRpZW5lIG7Dum1lcm9zIGRlIDEgYSA1DQpteW1hdDwtbWF0cml4KHJlcChzZXEoNSksIDQpLCBuY29sID0gNSkNCg0KIyBhcGxpY2Ftb3MgbGEgZnVuY2nDs24gYHN1bWAgYSBsYXMgZmlsYXMgZGUgYG15bWF0YCAob2JzZXJ2YXIgZWwgJzEnKSANCmFwcGx5KG15bWF0LCAxLCBzdW0pDQoNCiMgYXBsaWNhbW9zIGxhIGZ1bmNpw7NuIGBzdW1gIGEgbGFzIGNvbHVtbmFzIGRlIGBteW1hdGAgKG9ic2VydmFyIGVsICcyJykNCmFwcGx5KG15bWF0LCAyLCBzdW0pDQoNCiMgUG9kZW1vcyBhcGxpY2FyIHVuYSBmdW5jacOzbiBkZWZpbmlkYSBwb3IgZWwgdXN1YXJpbyBhbGFzIGZpbGFzIG8gY29sdW1uYXMuIGVuIGVzdGUgY2FzbyBzdW1hbW9zIGB5PTQuNWAgYSBsYSAnc3VtJyBkZSBjYWRhIGZpbGEgZGUgYG15bWF0YCANCmFwcGx5KG15bWF0LCAxLCBmdW5jdGlvbih4LCB5KSBzdW0oeCkgKyB5LCB5PTQuNSkNCg0KIyBBcGxpY2Ftb3MgbGEgZnVuY2nDs24gYHN1bW1hcnlgIGEgY2FkYSBjb2x1bW5hDQphcHBseShteW1hdCwgMiwgZnVuY3Rpb24oeCkgc3VtbWFyeSh4KSkNCmBgYA0KDQojIyBHcsOhZmljb3Mgc2VuY2lsbG9zIGNvbiBQbG90DQoNCiMjIyBQbG90DQoNCmBgYHtyfQ0KeCA8LSBzZXEoLXBpLHBpLDAuMSkNCnBsb3QoeCwgc2luKHgpKQ0KYGBgDQojIyMgQWdyZWdhbmRvIHTDrXR1bG8geSBub21icmUgYSBsb3MgZWplcw0KDQpgYGB7cn0NCnBsb3QoeCwgc2luKHgpLA0KbWFpbj0iTGEgZnVuY2nDs24gc2VubyIsDQp5bGFiPSJzaW4oeCkiKQ0KYGBgDQoNCiMjIyBDYW1iaWFuZG8gZWwgY29sb3IgeSB0aXBvDQoNCkVsIGNvbG9yIHNlIHB1ZWRlIGNhbWJpYXIgY29uIGVsIHBhcsOhbWV0cm8gYGNvbGAsIGNvbW8gYGNvbD0iYmx1ZSJgLCB5IGVsIHRpcG8gY29uIGVsIHBhcsOhbWV0cm8gYHR5cGVgLCBxdWUgYWNlcHRhIGxvcyBzaWd1aWVudGVzIHZhbG9yZXMNCg0KKiAicCIgLSBwb2ludHMNCiogImwiIC0gbGluZXMNCiogImIiIC0gYm90aCBwb2ludHMgYW5kIGxpbmVzDQoqICJjIiAtIGVtcHR5IHBvaW50cyBqb2luZWQgYnkgbGluZXMNCiogIm8iIC0gb3ZlcnBsb3R0ZWQgcG9pbnRzIGFuZCBsaW5lcw0KKiAicyIgYW5kICJTIiAtIHN0YWlyIHN0ZXBzDQoqICJoIiAtIGhpc3RvZ3JhbS1saWtlIHZlcnRpY2FsIGxpbmVzDQoqICJuIiAtIGRvZXMgbm90IHByb2R1Y2UgYW55IHBvaW50cyBvciBsaW5lcw0KDQpgYGB7cn0NCnBsb3QoeCwgc2luKHgpLA0KbWFpbj0iVGhlIFNpbmUgRnVuY3Rpb24iLA0KeWxhYj0ic2luKHgpIiwNCnR5cGU9ImwiLA0KY29sPSJibHVlIikNCmBgYA0KDQojIyMgU3VwZXJwb25pZW5kbyBncsOhZmljb3MgeSBsYSBmdW5jacOzbiBgbGVnZW5kKClgIA0KYGBge3J9DQpwbG90KHgsIHNpbih4KSwNCm1haW49Ik92ZXJsYXlpbmcgR3JhcGhzIiwNCnlsYWI9IiIsDQp0eXBlPSJsIiwNCmNvbD0iYmx1ZSIpDQpsaW5lcyh4LGNvcyh4KSwgY29sPSJyZWQiKQ0KbGVnZW5kKCJ0b3BsZWZ0IiwNCmMoInNpbih4KSIsImNvcyh4KSIpLA0KZmlsbD1jKCJibHVlIiwicmVkIikNCikNCmBgYA0KDQojIyMgTcO6bHRpcGxlcyBwbG90cw0KDQpgYGB7cn0NCnBhcihtZnJvdz1jKDEsMikpDQpwbG90KHgsIHNpbih4KSwNCiAgICAgbWFpbj0iRnVuY2nDs24gc2VubyIsDQogICAgIHlsYWI9IiIsDQogICAgIHR5cGU9ImwiLA0KICAgICBjb2w9ImJsdWUiKQ0KcGxvdCh4LGNvcyh4KSwgDQogICAgIG1haW49IkZ1bmNpw7NuIGNvc2VubyIsDQogICAgIHlsYWI9IiIsDQogICAgIHR5cGU9ImwiLA0KICAgICBjb2w9InJlZCIpDQpgYGANCg0KIyBSZXNvbHZpZW5kbyBwcm9ibGVtYXMgZGUgdmFsb3JlcyBpbmljaWFsZXMNCg0KVG9tYWRvIGRlIFtQYWNrYWdlIGRlU29sdmU6IFNvbHZpbmcgSW5pdGlhbCBWYWx1ZSBEaWZmZXJlbnRpYWwNCkVxdWF0aW9ucyBpbiBSXShodHRwczovL2NyYW4uci1wcm9qZWN0Lm9yZy93ZWIvcGFja2FnZXMvZGVTb2x2ZS92aWduZXR0ZXMvZGVTb2x2ZS5wZGYpIHBvciBLLiBTb2V0YWVydCwgVC4gUGV0em9sZHQgeSBSLiBXb29kcm93IFNldHplci4NCg0KQ29uc2lkZXJlbW9zIGxhcyBlY3VhY2lvbmVzIGRlIExvcmVueiBxdWUgcmVwcmVzZW50YW4gdW4gY29tcG9ydGFtaWVudG8gaWRlYWxpemFkbyBkZSBsYSBhdG3Ds3NmZXJhIHRlcnJlc3RyZS4gRXN0ZSBtb2RlbG8gZGVzY3JpYmUgbGEgZGluw6FtaWNhIGRlIHRyZXMgdmFyaWFibGVzIGRlIGVzdGFkbywgJFgkLCAkWSQgeSAkWiQ6DQoNClxbXGJlZ2lue2VxbmFycmF5Kn0NClxmcmFje2RYfXtkdH0mPSZhWCtZWlxcDQpcZnJhY3tkWX17ZHR9Jj0mYihZLVopXFwNClxmcmFje2RafXtkdH0mPSYtWFkrY1ktWg0KXGVuZHtlcW5hcnJheSp9XF0NCmNvbiBjb25kaWNpb25lcyBpbmljaWFsZXMNClxbDQpYKDApPVkoMCk9WigwKT0xDQpcXQ0KeSBjb24gJGEsYiQgeSAkYyQgcXVlIHNvbiBwYXLDoW1ldHJvcyBjb24gdmFsb3JlcyAkLVxmcmFjODMsIC0xMCQgeSAkMjgkIHJlc3BlY3RpdmFtZW50ZS4NCg0KTGEgaW1wbGVtZW50YWNpw7NuIGRlIGVzdGUgcHJvYmxlbWFzIGRlIHZhbG9yZXMgaW5pY2lhbGVzIHB1ZWRlIGVuIGRvcyBwYXJ0ZXM6IGxhICplc3BlY2lmaWNhY2nDs24gZGVsIG1vZGVsbyogeSBsYSAqYXBsaWNhY2nDs24gZGVsIG1vZGVsbyouDQoNCiogKmVzcGVjaWZpY2FjacOzbiBkZWwgbW9kZWxvKjoNCiAgKyBEZWZpbmlyIGxvcyBwYXLDoW1ldHJvcyB5IHN1cyB2YWxvcmVzDQogICsgRGVmaW5pciBsYXMgdmFyaWFibGVzIGRlIGVzdGFkbyB5IHN1cyBjb25kaWNpb25lcyBpbmljaWFsZXMNCiAgKyBJbXBsZW1lbnRhciBsYXMgZWN1YWNpb25lcyBkZWwgbW9kZWxvIHkgY2FsY3VsYXIgbGFzIHJhem9uZXMgZGUgY2FtYmlvIGRlIGxhcyB2YXJpYWJsZXMgZGUgZXN0YWRvDQoNCiogKmFwbGljYWNpw7NuIGRlbCBtb2RlbG8qOg0KICArIEVzcGVjaWZpY2FyIGVsIHJhbmdvIGRlIHRpZW1wbyBlbiBxdWUgc2UgZGVzZWFyIHJlc29sdmVyIGxhcyBlY3VhY2lvbmVzDQogICsgSW50ZWdyYXIgZWwgbW9kZWxvIA0KICArIEdyYWZpY2FyIGxvcyByZXN1bHRhZG9zDQoNCiMjIEVzcGVjaWZpY2FjacOzbiBkZWwgbW9kZWxvDQoNCkRlZmluaW1vcyBkb3MgdmVjdG9yZXMgY29udGVuaWVuZG8gbG9zIHBhcsOhbWV0cm9zIGRlbCBtb2RlbG8geSBsYXMgdmFyaWFibGVzIGRlIGVzdGFkbyBjb24gbGFzIGNvbmRpY2lvbmVzIGluaWNpYWxlcw0KDQojIyMgUGFyw6FtZXRyb3MNCmBgYHtyfQ0KcGFyYW1ldGVycyA8LSBjKGEgPSAtOC8zLCBiID0gLTEwLCBjID0gMjgpDQpgYGANCiMjIyBWYXJpYWJsZXMgZGUgZXN0YWRvDQpgYGB7cn0NCnN0YXRlIDwtIGMoWCA9IDEsIFkgPSAxLCBaID0gMSkNCmBgYA0KIyMjIEVjdWFjaW9uZXMNCkxhcyBlY3VhY2lvbmVzIHNlIGVzcGVjaWZpY2FuIHBvciB1bmEgZnVuY2nDs24gYExvcmVuemAgcXVlLCBkYWRvcyBsb3MgcGFyw6FtZXRyb3MgeSB2YXJpYWJsZXMgZGUgZXN0YWRvLCBkZXZ1ZWx2ZSBsYXMgZGVyaXZhZGFzIGRlIGxhcyBtaXNtYXMuDQoNCmBgYHtyfQ0KTG9yZW56IDwtIGZ1bmN0aW9uKHQsIHN0YXRlLCBwYXJhbWV0ZXJzKSB7DQogICMgcGFyYSBwb2RlciB1c2FyIGxvcyBub21icmVzIGEsYixjLFgsWSxaDQogIHdpdGgoYXMubGlzdChjKHN0YXRlLCBwYXJhbWV0ZXJzKSksew0KICAjIHJhdGUgb2YgY2hhbmdlDQogIGRYIDwtIGEqWCArIFkqWg0KICBkWSA8LSBiICogKFktWikNCiAgZFogPC0gLVgqWSArIGMqWSAtIFoNCiANCiAgIyBkZXZ1ZWx2ZSBsYXMgZGVyaXZhZGFzIGRYLGRZLGRaIGNvbW8gdmVjdG9yDQogIGxpc3QoYyhkWCwgZFksIGRaKSkNCiAgfSkgIyBlbmQgd2l0aChhcy5saXN0IC4uLg0KICB9DQpgYGANCg0KIyMgQXBsaWNhY2nDs24gZGVsIG1vZGVsbw0KIyMjIFJhbmdvIGRlIHRpZW1wbw0KYGBge3J9DQp0aW1lcyA8LSBzZXEoMCwgMjAsIGJ5ID0gMC4wMSkNCmBgYA0KIyMjIEludGVncmFjacOzbg0KVXNhcmVtb3MgbGEgZnVuY2nDs24gYG9kZWAgZGVsIHBhcXVldGUgKipkZVNvbHZlKiogcXVlIG5vIGVzdMOhIHByZXNlbnRlIGVuIGxhIGRpc3RyaWJ1Y2nDs24gYsOhc2ljYSwgcG9yIGVzbyBhZ3JlZ2Ftb3MgZXN0ZSBwYXF1ZXRlIHkgbHVlZ28gYXBsaWNhbW9zIGxhIGZ1bmNpw7NuDQpgYGB7cn0NCmxpYnJhcnkoZGVTb2x2ZSkNCm91dCA8LSBvZGUoeSA9IHN0YXRlLCB0aW1lcyA9IHRpbWVzLCBmdW5jID0gTG9yZW56LCBwYXJtcyA9IHBhcmFtZXRlcnMpDQpoZWFkKG91dCkNCmNsYXNzKG91dCkNCmBgYA0KYG9kZWAgZGV2dWVsdmUgdW4gb2JqZXRvIGRlIGxhIGNsYXNlICoqZGVTb2x2ZSoqIGNvbiB1bmEgbWF0cml6IHF1ZSBjb250aWVuZSB1bmEgY29sdW1uYSBwYXJhIGVsIHRpZW1wbyB5IHVuYSBjb2x1bW5hIHBvciBjYWRhIHZhcmlhYmxlIGRlIGVzdGFkby4gQ2FkYSBmaWxhIGRlIGxhIG1hdHJpeiBjb250aWVuZSBsb3MgdmFsb3JlcyBkZSBsYXMgdmFyaWFibGVzIGVuIGVsIHRpZW1wbyBjb3JyZXNwb25kaWVudGUuDQoNCiMjIyBHcsOhZmljb3MNCg0KYGBge3J9DQpwYXIob21hID0gYygwLCAwLCAxLCAwKSkNCnBsb3Qob3V0LCB4bGFiID0gInRpbWUiLCB5bGFiID0gIi0iKQ0KcGxvdChvdXRbLCAiWCJdLCBvdXRbLCAiWiJdLCBwY2ggPSAiLiIpDQptdGV4dChvdXRlciA9IFRSVUUsIHNpZGUgPSAzLCAiTG9yZW56IG1vZGVsIiwgY2V4ID0gMS4wKQ0KDQpgYGANCg0KTGEgZnVuY2nDs24gYG9kZWAgaW1wbGVtZW50YSB1biBtw6l0b2RvIHBvciBkZWZhdWx0LiBTaW4gZW1iYXJnbyBlcyBwb3NpYmxlIHF1ZSBlbCBtw6l0b2RvIHNlYSBlbGVnaWRvIHBvciBlbCB1c3VhcmlvIHVzYW5kbyBlbCBwYXLDoW1ldHJvIGBtZXRob2RgLiBQb3IgZWplbXBsbywgc2kgc2UgcXVpZXJlIHVzYXIgZWwgbcOpdG9kbyBkZSBSdW5nZS1LdXR0YSBkZSBjdWFydG8gb3JkZW4sIHBvZGVtb3MgZXNjcmliaXINCmBgYHtyfQ0Kb3V0IDwtIG9kZSh5ID0gc3RhdGUsIHRpbWVzID0gdGltZXMsIGZ1bmMgPSBMb3JlbnosIHBhcm1zID0gcGFyYW1ldGVycywgbWV0aG9kID0gInJrNCIpDQpgYGANCkVzIHBvc2libGUgdXNhciBsb3Mgc2lndWllbnRlcyBtw6l0b2RvczoNCuKAnGxzb2Rh4oCdIChkZWZhdWx0KSwg4oCcbHNvZGXigJ0sIOKAnGxzb2Rlc+KAnSwg4oCcbHNvZGFy4oCdLCDigJx2b2Rl4oCdLCDigJxkYXNwa+KAnSwg4oCcZXVsZXLigJ0sIOKAnHJrNOKAnSwg4oCcb2RlMjPigJ0sIOKAnG9kZTQ14oCdLCDigJxyYWRhdeKAnSwg4oCcYmRm4oCdLCDigJxiZGZfZOKAnSwg4oCcYWRhbXPigJ0sIOKAnGltcEFkYW1z4oCdLCDigJxpbXBBZGFtc19k4oCdLCDigJxpdGVyYXRpb27igJ0NCg0KUGFyYSB2ZXIgbGFzIGNhcmFjdGVyw61zdGljYXMgZGUgdW4gbcOpdG9kbyBkZSBSdW5nZS1LdXR0YSB1dGlsaXphciBsYSBmdW5jacOzbiBgcmtNZXRob2QoKWANCmBgYHtyfQ0KcmtNZXRob2QoInJrNCIpDQpgYGANCkxvcyBtw6l0b2RvcyBgZXVsZXJgIHkgYHJrNGAgdXRpbGl6YW4gdW4gcGFzbyBlbiBlbCB0aWVtcG8gZmlqby4gUHVlZGVuIGltcGxlbWVudGFyc2UgY29tbyB1biBtw6l0b2RvIGRlIFJ1bmdlLUt1dHRhIGdlbmVyYWwsIGNvbiBsYSBmdW5jacOzbiBgb2RlYCBlc3BlY2lmaWNhbmRvIHVuIHBhc28gZGUgdGllbXBvIGluZGVwZW5kaWVudGUgZGVsIGFyZ3VubWVudG8gYHRpbWVzYCB1dGlsaXphbmRvIGVsIGFyZ3VubWVudG8gYGhpbmlgLiBUYW1iacOpbiBwdWVkZW4gaW1wbGVtZW50YXJzZSBjb24gbGFzIGZ1bmNpb25lcyBzaW1wbGlmaWNhZGFzIGBldWxlcmAgbyBgcms0YCwgZW4gbGFzIHF1ZSBlbCBwYXNvIGVuIGVsIHRpZW1wbyBlc3TDoSBkZXRlcm1pbmFkYSBwb3IgZWwgKnRpbWVzIHNwYW4qIHZlY3RvciBgdGltZXNgLiBMb3MgZGVtw6FzIG3DqXRvZG9zIHV0aWxpemFuIHVuIHBhc28gZGUgdGllbXBvIHZhcmlhYmxlIChhZGFwdGF0aXZvKS4gDQoNCiMgT3BlcmFjaW9uZXMgY29uIE1hdHJpY2VzDQoNClN1cG9uaWVuZG8gcXVlICRBJCB5ICRCJCBzb24gbWF0cmljZXMgeSAkeCQgeSAkYiQgdmVjdG9yZXMsIGVzdGFzIHNvbiBhbGd1bmFzIG9wZXJhY2lvbmVzIHF1ZSBwb2RlbW9zIHJlYWxpemFyLg0KDQoqIGBBKkJgICAgTXVsdGlwbGljYSBlbGVtZW50byBwb3IgZWxlbWVudG8gJChBOjpCKSQNCiogYEElKiVCYCAgIE11bHRpcGxpY2EgbGFzIG1hdHJpY2VzICRBJCB5ICRCJA0KKiBgdChBKWAgICAgVHJhc3B1ZXN0YSBkZSAkQSQNCiogYGRpYWcoeClgIENyZWEgdW5hIG1hdHJpeiBkaWFnb25hbCwgY29uIGxvcyBlbGVtZW50b3MgZGUgJHgkIGVuIGxhIGRpYWdvbmFsIHByaW5jaXBhbA0KKiBgZGlhZyhBKWAgRGV2dWVsdmUgZWwgdmVjdG9yIGNvbnRlbmllbmRvIGxvcyBlbGVtZW50b3MgZGUgbGEgZGlhZ29uYWwgcHJpbmNpcGFsIGRlICRBJA0KKiBgZGlhZyhrKWAgQ3JlYSB1bmEgbWF0cml6IGNvbiAkayQgZW4gbGEgZGlhZ29uYWwsIHNpICRrJCBlcyB1biBlc2NhbGFyDQoqIGBzb2x2ZShBLGIpYCBDYWxjdWxhIGxhIHNvbHVjacOzbiAkeCQgZGUgJEF4PWIkDQoqIGBzb2x2ZShBKWAgQ2FsY3VsYSBsYSBpbnZlcnNhIGRlICRBJA0KKiBgeSA8LSBlaWdlbihBKWAgQ2FsY3VsYSBhdXRvdmFsb3JlcyB5IGF1dG92ZWN0b3JlcyBkZSAkQSQsIGB5JHZhbHVlc2AgY29udGllbmUgbG9zIGF1dG92YWxvcmVzLCBgeSR2ZWN0b3JzYCBjb250aWVuZSBsb3MgYXV0b3ZlY3RvcmVzDQoNCg0KDQoNCg0KDQoNCg==