Visualisation des
fonction \(k\), \(a\) et \(s\) selon le phénotype
Tout d’abord regardons l’allure des fonctions \(k\), la capacité de charge pour chaque
phénotype, et la fonction de compétition \(a\) pour différentes valeurs de \(\sigma^2\).
a <- function(x1,x2, sigmaA){
return(exp(-.5*(x1-x2)**2/sigmaA))
}
K <- function(x){
return(K0 - lambda*(x - x0)**2)
}
x0=1
K0=1
lambda = 1
x = seq(0.1,1.9,.1)
sigma2 = c(0.1, 0.5, 1, 10)
plot_list = list()
for (i in 1:length(sigma2)){
sigmaA = sigma2[i]
data1 = data.frame(X=x, val_K = K(x),
A_x5=a(x[5],x, sigmaA),
A_x9=a(x[9],x, sigmaA),
A_x10=a(x[10],x, sigmaA),
A_x11=a(x[11],x, sigmaA),
A_x15=a(x[15],x, sigmaA)) %>%
pivot_longer(-X, names_to="func",values_to = "value")
plot_list[[i]] = ggplot(data=data1, aes(x=X, y=value, col=func))+
geom_line() +
xlab("Phénotype")+
ggtitle(paste0("Allure des fonctions pour sigma²=", sigmaA))+
theme_bw()+
theme(line = element_blank(),
axis.line = element_line(colour = "black"),
panel.border = element_blank(),
axis.ticks = element_line(colour = "black"),
axis.text.x = element_text(colour = "black", size=10),
axis.text.y = element_text(colour = "black", size=10),
axis.title=element_text(size=10),
legend.text = element_text(size = 7),
legend.title = element_text(size = 7)) +
scale_color_manual(
name = "Fonction",
values = c("A_x10" = "red","A_x9" = "orange","A_x11" = "magenta", "A_x5" = "blue", "A_x15" = "green", "val_K" = "black"),
labels = c("A(1,x)","A(0.9,x)","A(1.1,x)", "A(0.5,x)", "A(1.5,x)", "K(x)"))
}
plot_grid(plotlist = plot_list)

Lorsque \(\sigma^2\) est faible la
courbure pour la fonction de compétition est assez faible et plusieurs
phénotypes semblent pouvoir cohabiter à des valeurs de K non maximales
(en quelque sorte rentrer sous K), plus \(\sigma^2\) augmente, plus la fonction de
compétition s’applatit et la coexistence devrait devenir plus
compliquée. On s’attend donc à une relation négative entre \(\sigma^2\) et le nombre de phénotype
pouvant coexister.
On va maintenant faire une représentation en 3D pour la fitness
d’invasion, qui s’exprime : \(s(x_{1},x_{2})=r.(1-a(x_{1},x_{2}).\frac{K(x_{2})}{K(x_{1})})\)
a <- function(x1,x2, sigmaA){
return()
}
S <- function(x1,x2){
r=1
a = exp(-.5*(x1-x2)**2/sigmaA)
return(r*(1-a*K(x2)/K(x1)))
}
x0=1
K0=1
lambda = 1
sigmaA = 1
x1 <- seq(0.1,1.9,.1)
x2 <- seq(0.1,1.9,.1)
#create z-values
z = outer(x1, x2, S)
#create 3D plot
persp(x1, x2, z, xlab='x', ylab='y', zlab='s(x,y)',
main='Invasion fitness', col='lightblue', shade=.4, theta = 60, phi = 25, ticktype='simple')

Simulations de la
dynamique adaptative
Définition des
fonctions utilisées dans la suite
- Créations des fonctions de base pour la simulation
# Fonction pour calculer K
calculeK <- function(X,K0,x0, lambda){
return(max(K0 - lambda*(X - x0)**2, 10**(-9)))
}
# Fonction pour obtenir les valeurs de la matrice A
calculeA <- function(X, nbSpecies, sigmaA_squared){
D = matrix(data=X,ncol=nbSpecies,nrow=nbSpecies) # matrice
C = t(D) - D
return(exp(-C**2/(2*sigmaA_squared)))
}
# Fonction pour calculer la matrice B
calculeB <- function(X, nbSpecies, K0, x0, lambda){
vectK = sapply(X, FUN= function(x) { calculeK(x, K0, x0, lambda) })
B = matrix(data = 1/vectK, nrow=nbSpecies, ncol=nbSpecies, byrow=TRUE)
return(B)
}
# Fonction par calucle la matrice M qui est le produit de A et B
calculeM = function(X, nbSpecies, sigmaA_squared, K0=1, lambda=1){
x0 = min(X) + (max(X) - min(X)) / 2
A = calculeA(X, nbSpecies, sigmaA_squared) # matrice A
B = calculeB(X, nbSpecies, K0, x0, lambda) # matrice B
M=A*B # produit terme a terme de A avec B
return(M)
}
# Fonctions pour définition l'equation differentielle
calculeGradientLV = function(t, Ni, params){
with(as.list(params), {
dNi = r*Ni*(1 - Ni%*%M)
return(list(dNi))
})
}
# Fonctions pour resoudre l'equation differentielle avec ode du package deSolve
resoutEqd <- function(parameters, densite_t0, vect_time, vect_phenotypes){
df_densite = ode(y = densite_t0, times = vect_time, func = calculeGradientLV, parameters) %>%
as.data.frame() %>%
pivot_longer(-time, values_to = "density", names_to = "species")
df_densite$X = vect_phenotypes[as.numeric(df_densite$species)]
return(df_densite)# retourne sortie modifier de ode
}
# Définition de la fonction pour simuler le dynamique de la poppulation
simuleDynamique <- function(nbSpecies, sigmaA_squared, vect_temps, K0=1, r=1){
n0 = rep(K0/nbSpecies, nbSpecies)
vect_phenotypes = seq(0, 1, length.out = nbSpecies)
M = calculeM(vect_phenotypes, nbSpecies, sigmaA_squared, K0)
params = list(r = r, M = M)
df_densite = resoutEqd(params, n0, vect_temps, vect_phenotypes)
return(df_densite) # la densité de la population obtenu avec ode
}
- Les fonctions pour les représentations graphiques
# définition des thèmes
main_theme = theme_bw()+
theme(line = element_blank(),
axis.line = element_line(colour = "black"),
panel.border = element_blank(),
axis.ticks = element_line(colour = "black"),
axis.text.x = element_text(colour = "black", size=10),
axis.text.y = element_text(colour = "black", size=10),
legend.position="none",
axis.title=element_text(size=10))
# fonction pour illustrer le dynamique de la population selon le temps
plotDynamique <- function(df_density, plot_theme=main_theme, title= ""){
plot = ggplot(df_density)+
geom_line(aes(time, density, col = species)) +
ggtitle(label = title) +
xlab("Temps") +
ylab("Phénotypes") +
plot_theme
return(plot)
}
# fonction pour illustrer l'évolution de la dynamique selon les phénotypes
plotEvolutiontrait <- function(df_density, plot_theme=main_theme){
plot = ggplot(data = df_density) +
geom_tile(aes(x = X, y = time, fill = density))+
scale_fill_gradient2(low = "white" , high = "red")+
xlab("Phénotypes") +
ylab("Temps") +
plot_theme
return(plot)
}
Simulation sans
mutation
vect_temps = 0:2000 # temps de simulation
K0 = 1 # capacite de charge du milieu tout phenotype confondu
sigmaA_squared = 1 # varie entre 0.1 et 10
r=1 # taux de croissance, le même pour toutes les espèces
# Simulation de la dynamique avec 10 phenotypes
nbSpecies = 10 # nombre d'especes
df_densite10 = simuleDynamique(nbSpecies, sigmaA_squared, vect_temps)
plot1 = plotDynamique(df_densite10, title="Evolution des phénotypes (N = 10)" )
plot2 = plotEvolutiontrait(df_densite10)
# Simulation de la dynamique avec 100 phenotypes
nbSpecies = 100 # nombre d'especes
df_densite100 = simuleDynamique(nbSpecies, sigmaA_squared, vect_temps)
plot3 = plotDynamique(df_densite100, title="Evolution des phénotypes (N = 100)")
plot4 = plotEvolutiontrait(df_densite100)
plot_grid(labels = "auto",plotlist=list(plot1,plot2,plot3,plot4))

Avec \(\sigma^2 = 1\), une valeur
intermédiaire, on observe que toutes les espèces s’éloignant trop de
l’optimum sont éliminées. La tendance semble donc plutôt être
l’élimination par compétition que la coexistence de phénotype.
Coexistence des
phénotypes et valeurs de sigma
On peut se poser la question suivante : Combien d’espèces
persisteront dans le temps selon la valeur de sigma ?
vect_temps = 0:2000 # temps de simulation
sigmaA_squared = c(0.1, 0.5, 1, 10) # Les differents valeurs de sigma
plot_list = list()
nbSpecies = 100
for (i in 1:length(sigmaA_squared)){
df_densite = simuleDynamique(nbSpecies, sigmaA_squared[i], vect_temps)
plot_list[[i]] = plotDynamique(df_densite, title=paste("Cas N= 100, sigma² = ", sigmaA_squared[i]))
#plot_list[[i]] = plotEvolutiontrait(df_densite))
}
plot_grid(labels = "auto",plotlist = plot_list)

Avec \(N = 100\) On constate qu’à
mesure que \(\sigma^2\) augmente, le
nombre de phénotypes qui persistent à long terme augmente entre 0.1 et
0.5. Lorsque \(\sigma^2\) est supérieur
à \(1\), certains phénotypes subsistent
initialement mais finissent rapidement par s’éteindre, et au bout de
\(2000\) pas de temps de simulation, un
seul phénotype subsiste.
Regardons maintenant le nombre de phénotypes qui persistent selon
plusieurs valeurs de \(sigma²\).
density_min = 0.005 # en dessous de cette densité le phénotype est considéré disparu
vect_temps = 0:2000 # temps de simulation
sigmaA_squared = c(seq(0.1,1, by=0.1), seq(2,10)) # les valeurs de sigma² a tester
df_persitance = data.frame()
nbSpecies = 100 # Nombre d'espèces
for (i in 1:length(sigmaA_squared)){
df_densite = simuleDynamique(nbSpecies, sigmaA_squared[i], vect_temps)
nb_persistant = df_densite[df_densite$time == max(df_densite$time),] %>%
filter(density>density_min) %>%
nrow()
df_persitance = rbind(df_persitance,c(sigmaA_squared[i],nb_persistant))
}
colnames(df_persitance) = c("sigma_square", "nb_persistant")
ggplot(data = df_persitance, aes(x = sigma_square, y = nb_persistant)) +
geom_point() +
geom_line() +
labs(title = "Nombre de phénotypes persistants selon sigma²",
subtitle = "Cas N = 100",
x = "Sigma²",
y = "Nombre de phénotypes"
) +
main_theme

Comme on s’y attendait le nombre d’espèces persistantes diminue
lorsque \(\sigma^2\) augmente.
Y a-t-il une limite au nombre d’espèces qui peuvent coexister dans ce
modèle ? A priori il n’y pas de limite car on prend une fonction
gaussienne, mais une limite pourrait apparaitre si on prenais d’autres
distributions.
Simulation avec ajout
de mutations dans la dynamique
Définition des fonction pour simuler la mutation.
Pour simuler des mutations, le principe va être le suivant :
- On tire les temps auxquels les mutations vont avoir lieu de manière
aléatoire
2.a On simule la dynamique entre t0 et le premier temps de
mutation
2.b Au temps de mutation, on récupère les densités finales puis on
applique la mutation, qui consiste à “décaler” les phénotypes vers la
droite ou la gauche en déplaçant une certaines proportion de la densité
dans le phénotype juste à droite ou juste à gauche.
2.c On retourne à l’étape 2.a et on simule la dynamique entre le
temps de mutation et le suivant en prenant comme condition initiale le
vecteur des densités mutées.
Définition des fonctions utiles :
# fonction pour obtenir les temps auxquels il y aura une mutation
getMutationTime <- function(vect_temps, taux_mutation){
SimulationDuration = max(vect_temps)
# Determination des instants auxquels il y aura une mutation
nb_mutations_moy = SimulationDuration * taux_mutation
nb_mutations_sd = nb_mutations_moy / 4
nb_mutations = max(1,floor(rnorm(1, mean=nb_mutations_moy, sd=nb_mutations_sd)))
temps_mutation = c(0,sort(sample(x = vect_temps, size = nb_mutations)),SimulationDuration)
return(temps_mutation)
}
# fonction pour le décalage (droite ou gauche) de la mutation
mutationParDecalage <- function(df_densite, proportionMutant) {
n0 = df_densite$density[df_densite$time==max(df_densite$time)]
nbSpecies = length(n0)
# mutation : decalage d'une partie des effectifs
p = runif(1) # tirage aléatoire entre 0 et 1
if (p < .7){
# decalage à droite
effectifMutants = n0[1:(nbSpecies-1)]*proportionMutant
n0[1:(nbSpecies-1)] = n0[1:(nbSpecies-1)] - effectifMutants
n0[2:nbSpecies] = n0[2:nbSpecies] + effectifMutants
}
else{
# decalage a gauche
effectifMutants = n0[2:nbSpecies]*proportionMutant
n0[1:(nbSpecies-1)] = n0[1:(nbSpecies-1)] + effectifMutants
n0[2:nbSpecies] = n0[2:nbSpecies] - effectifMutants
}
return(n0)
}
# fonction pour la simulation de la mutation
simuleDynamiqueMutation <- function(nbSpecies, sigmaA_squared, temps_mutation, K0=1, r=1){
# Calcule des phenotypes et de M
vect_phenotypes = seq(0, 1, length.out = nbSpecies)
M = calculeM(vect_phenotypes, nbSpecies, sigmaA_squared, K0)
params = list(r = r, M = M)
# Initialisation de n0 et de df_densite
n0 = c(K0, rep(0, nbSpecies-1))
df_densite = data.frame()
# Simulation de la dynamique pour tout les temps de mutation
for (i in 2:length(temps_mutation)){
if (temps_mutation[i]-temps_mutation[i-1]>1){
# Entre 2 mutation on simule en resolvant l'equation differentielle
sample_vect_temps = seq(temps_mutation[i-1],temps_mutation[i]-1)
df_densite = rbind(df_densite,resoutEqd(params, n0, sample_vect_temps, vect_phenotypes))
# A chaque temps contenu dans temps_mutation une mutation a lieu
n0 = mutationParDecalage(df_densite, proportionMutant) # no devient le no avec mutation
}
}
return(df_densite) # pop tot avec mutation
}
Visualisation de la dynamique avec 10 et 100 espèces, et un
taux de mutation de 1/100, soit en moyenne une mutations tous les 100
pas de temps.
vect_temps = 0:2000 # Temps de simulation
sigmaA_squared = 0.4 # simga²
proportionMutant = 0.1 # Proportion du nombre de phénotype mutant
taux_mutation = 1/100 # taux de mutation
# Simulation de la dynamique avec 10 phenotypes
nbSpecies = 10
temps_mutation = getMutationTime(vect_temps, taux_mutation)
df_densite = simuleDynamiqueMutation(nbSpecies, sigmaA_squared, temps_mutation)
plot1 = plotDynamique(df_densite, title="Cas N = 10")
plot1 = plot1 + geom_vline(xintercept=temps_mutation, linetype='18', color='red')
plot2 = plotEvolutiontrait(df_densite)
# Simulation de la dynamique avec 100 phenotypes
nbSpecies = 100
temps_mutation = getMutationTime(vect_temps, taux_mutation)
df_densite100 = simuleDynamiqueMutation(nbSpecies, sigmaA_squared, temps_mutation)
plot3 = plotDynamique(df_densite100, title="Cas N = 100")
plot3 = plot3 + geom_vline(xintercept=temps_mutation, linetype='18', color='red')
plot4 = plotEvolutiontrait(df_densite100)
plot_grid(plotlist=list(plot1,plot2,plot3,plot4))

Lorsque l’on fait une simulation avec des mutation, on s’attend à ce
que les mutations ramènent progressivement les valeurs de phénotypes
vers l’optimum. Dans certains cas, en dynamique adaptative on peut
observer des points de branchement. Pour cela il faut qu’un point
singulier de la dynamique soit attracteur mais instable, et qu’il puisse
être envahi par des phénotype de valeur inférieure et supérieure à sa
valeur.
On a l’impression d’oberver un point de branchement avec nos 10
phénotypes. En revanche, on observe que avec 100 phénotypes, on n’a pas
le temps de rejoindre l’optimum. On va refaire une simulation avec 100
phénotypes en prenant 2 taux de mutations différents.
vect_temps = 0:10000
sigmaA_squared = 0.4
proportionMutant = 0.1
# Simulation de la dynamique avec 50 phenotypes et taux_mutation = 1/200
nbSpecies = 50
taux_mutation = 1/200
temps_mutation = getMutationTime(vect_temps, taux_mutation)
df_densite = simuleDynamiqueMutation(nbSpecies, sigmaA_squared, temps_mutation)
plot1 = plotDynamique(df_densite, title="Taux de mutation = 0.005")
plot1 = plot1
plot2 = plotEvolutiontrait(df_densite)
# Simulation de la dynamique avec 100 phenotypes et taux_mutation = 1/50
nbSpecies = 50
taux_mutation = 1/50
temps_mutation = getMutationTime(vect_temps, taux_mutation)
df_densite100 = simuleDynamiqueMutation(nbSpecies, sigmaA_squared, temps_mutation)
plot3 = plotDynamique(df_densite100, title="Taux de mutation = 0.02")
plot3 = plot3
plot4 = plotEvolutiontrait(df_densite100)
plot_grid(plotlist=list(plot1,plot2,plot3,plot4))

Avec un taux de mutation faible, la densité est toujours très
resserrée autour de peu de phénotype dominant (le trait est bien rouge
et pas étalé) et on se déplace très lentement vers l’optimum. Si le taux
de mutation est plus élevé en revanche, on observe un point de
branchement et plus de phénotypes qui coexistent en plus faible
densité.
LS0tDQp0aXRsZTogJ0NvbXB0ZSByZW5kdSBkZXMgVFAgZGUgbCcnVUUgTURFVDogRHluYW1pcXVlIGFkYXB0YXRpdmUnDQphdXRob3I6ICJMb3VpcyBTY2hyb2xsICYgQWJkb3VyYWhtYW5lIERpYWxsbyINCmRhdGU6ICIyMDIzLTExLTA1Ig0Kb3V0cHV0Og0KICBodG1sX2RvY3VtZW50Og0KICAgIGNvZGVfZG93bmxvYWQ6IHllcw0KICAgIHRvYzogeWVzDQogICAgdG9jX2Zsb2F0OiB5ZXMNCiAgICBjb2RlX2ZvbGQ6IGhpZGUNCiAgICB3YXJuaW5nOiBubw0KICAgIG1lc3NhZ2U6IG5vDQogICAgdGhlbWU6IHVuaXRlZA0KICAgIG51bWJlcl9zZWN0aW9uczogeWVzDQogICAgc2VjX251bWJlcl9zdWZmaXg6ICcuICAgJw0KICBwZGZfZG9jdW1lbnQ6DQogICAgdG9jOiB5ZXMNCi0tLQ0KDQpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0NCmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvID0gVFJVRSwgd2FybmluZyA9IEZBTFNFLCBtZXNzYWdlID0gRkFMU0UsIHJlc3VsdHMgPSAiaGlkZSIpDQpgYGANCg0KDQojIEltcG9ydGF0aW9uIGRlcyBwYWNrYWdlcyB1dGlsZXMgIHsudW5udW1iZXJlZH0NCg0KYGBge3IgUGFja2FnZXMsIHdhcm5pbmc9RkFMU0UsIG1lc3NhZ2U9RkFMU0V9DQoNCnJtKGxpc3Q9bHMoKSkgIyBOZXR0b3lhZ2UgZGUgbCdlbnZpcm9ubmVtZW50DQojIExpc3RlIGRlcyBwYWNrYWdlcyByZXF1aXMNCnBhY2thZ2VzIDwtIGMoImRlU29sdmUiLCAidGlkeXZlcnNlIiwgInJnbCIsICJjb3dwbG90IiwgInRpZHlyIikNCg0KIyBWw6lyaWZpY2F0aW9uIGRlcyBwYWNrYWdlcyBpbnN0YWxsw6lzDQpmb3IgKGkgaW4gcGFja2FnZXMpIHsNCiAgaWYgKCFyZXF1aXJlTmFtZXNwYWNlKGksIHF1aWV0bHkgPSBUUlVFKSkgew0KICAgICMgSW5zdGFsbGF0aW9uIGR1IHBhY2thZ2UgcydpbCBuJ2VzdCBwYXMgaW5zdGFsbMOpDQogICAgaW5zdGFsbC5wYWNrYWdlcyhpKQ0KICB9DQp9DQoNCiMgQ2hhcmdlbWVudCBkZXMgcGFja2FnZXMNCmxpYnJhcnkoZGVTb2x2ZSkgIyBwYWNrYWdlIGRlIGxhIGZvbmN0aW9uIG9kZSBwb3VyIGxlcyBlcXVhdGlvbiBkaWZmw6lyZW50aWVsbGVzIG9yZGluYWlyZXMNCmxpYnJhcnkodGlkeXZlcnNlKSAjIHBvdXIgbGVzIG1hbnVwaWxhdGlvbiBldCBsZXMgZ3JhcGhpcXVlcw0KbGlicmFyeShyZ2wpICMgcG91ciBsZXMgZ3JhcGhpcXVlcyBlbiAzRA0KbGlicmFyeShjb3dwbG90KQ0KbGlicmFyeSh0aWR5cikNCmBgYA0KDQoNCg0KIyBWaXN1YWxpc2F0aW9uIGRlcyBmb25jdGlvbiAkayQsICRhJCBldCAkcyQgc2Vsb24gbGUgcGjDqW5vdHlwZQ0KDQpUb3V0IGQnYWJvcmQgcmVnYXJkb25zIGwnYWxsdXJlIGRlcyBmb25jdGlvbnMgJGskLCBsYSBjYXBhY2l0w6kgZGUgY2hhcmdlIHBvdXIgY2hhcXVlIHBow6lub3R5cGUsIGV0IGxhIGZvbmN0aW9uIGRlIGNvbXDDqXRpdGlvbiAkYSQgcG91ciBkaWZmw6lyZW50ZXMgdmFsZXVycyBkZSAkXHNpZ21hXjIkLg0KDQpgYGB7ciwgZmlnLmFsaWduPSdjZW50ZXInfQ0KYSA8LSBmdW5jdGlvbih4MSx4Miwgc2lnbWFBKXsNCiAgcmV0dXJuKGV4cCgtLjUqKHgxLXgyKSoqMi9zaWdtYUEpKQ0KfQ0KDQpLIDwtIGZ1bmN0aW9uKHgpew0KICByZXR1cm4oSzAgLSBsYW1iZGEqKHggLSB4MCkqKjIpDQp9DQoNCngwPTENCkswPTENCmxhbWJkYSA9IDENCg0KeCA9IHNlcSgwLjEsMS45LC4xKQ0KDQpzaWdtYTIgPSBjKDAuMSwgMC41LCAxLCAxMCkNCnBsb3RfbGlzdCA9IGxpc3QoKQ0KZm9yIChpIGluIDE6bGVuZ3RoKHNpZ21hMikpew0KICBzaWdtYUEgPSBzaWdtYTJbaV0NCiAgZGF0YTEgPSBkYXRhLmZyYW1lKFg9eCwgdmFsX0sgPSBLKHgpLCANCiAgICAgICAgICAgICAgICAgICAgIEFfeDU9YSh4WzVdLHgsIHNpZ21hQSksDQogICAgICAgICAgICAgICAgICAgICBBX3g5PWEoeFs5XSx4LCBzaWdtYUEpLA0KICAgICAgICAgICAgICAgICAgICAgQV94MTA9YSh4WzEwXSx4LCBzaWdtYUEpLCANCiAgICAgICAgICAgICAgICAgICAgIEFfeDExPWEoeFsxMV0seCwgc2lnbWFBKSwNCiAgICAgICAgICAgICAgICAgICAgIEFfeDE1PWEoeFsxNV0seCwgc2lnbWFBKSkgJT4lDQogICAgcGl2b3RfbG9uZ2VyKC1YLCBuYW1lc190bz0iZnVuYyIsdmFsdWVzX3RvID0gInZhbHVlIikNCg0KICBwbG90X2xpc3RbW2ldXSA9IGdncGxvdChkYXRhPWRhdGExLCBhZXMoeD1YLCB5PXZhbHVlLCBjb2w9ZnVuYykpKw0KICAgIGdlb21fbGluZSgpICsNCiAgICB4bGFiKCJQaMOpbm90eXBlIikrDQogICAgZ2d0aXRsZShwYXN0ZTAoIkFsbHVyZSBkZXMgZm9uY3Rpb25zIHBvdXIgc2lnbWHCsj0iLCBzaWdtYUEpKSsNCiAgICB0aGVtZV9idygpKw0KICAgIHRoZW1lKGxpbmUgPSBlbGVtZW50X2JsYW5rKCksIA0KICAgICAgICBheGlzLmxpbmUgPSBlbGVtZW50X2xpbmUoY29sb3VyID0gImJsYWNrIiksDQogICAgICAgIHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfYmxhbmsoKSwNCiAgICAgICAgYXhpcy50aWNrcyA9ICBlbGVtZW50X2xpbmUoY29sb3VyID0gImJsYWNrIiksDQogICAgICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGNvbG91ciA9ICJibGFjayIsIHNpemU9MTApLA0KICAgICAgICBheGlzLnRleHQueSA9IGVsZW1lbnRfdGV4dChjb2xvdXIgPSAiYmxhY2siLCBzaXplPTEwKSwNCiAgICAgICAgYXhpcy50aXRsZT1lbGVtZW50X3RleHQoc2l6ZT0xMCksDQogICAgICAgIGxlZ2VuZC50ZXh0ID0gZWxlbWVudF90ZXh0KHNpemUgPSA3KSwgIA0KICAgICAgICBsZWdlbmQudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDcpKSArDQogICAgc2NhbGVfY29sb3JfbWFudWFsKA0KICAgICAgbmFtZSA9ICJGb25jdGlvbiIsDQogICAgICB2YWx1ZXMgPSBjKCJBX3gxMCIgPSAicmVkIiwiQV94OSIgPSAib3JhbmdlIiwiQV94MTEiID0gIm1hZ2VudGEiLCAiQV94NSIgPSAiYmx1ZSIsICJBX3gxNSIgPSAiZ3JlZW4iLCAidmFsX0siID0gImJsYWNrIiksDQogICAgICBsYWJlbHMgPSBjKCJBKDEseCkiLCJBKDAuOSx4KSIsIkEoMS4xLHgpIiwgIkEoMC41LHgpIiwgIkEoMS41LHgpIiwgIksoeCkiKSkNCiAgICANCiAgDQp9DQoNCnBsb3RfZ3JpZChwbG90bGlzdCA9IHBsb3RfbGlzdCkNCg0KYGBgDQoNCkxvcnNxdWUgJFxzaWdtYV4yJCBlc3QgZmFpYmxlIGxhIGNvdXJidXJlIHBvdXIgbGEgZm9uY3Rpb24gZGUgY29tcMOpdGl0aW9uIGVzdCBhc3NleiBmYWlibGUgZXQgcGx1c2lldXJzIHBow6lub3R5cGVzIHNlbWJsZW50IHBvdXZvaXIgY29oYWJpdGVyIMOgIGRlcyB2YWxldXJzIGRlIEsgbm9uIG1heGltYWxlcyAoZW4gcXVlbHF1ZSBzb3J0ZSByZW50cmVyIHNvdXMgSyksIHBsdXMgJFxzaWdtYV4yJCBhdWdtZW50ZSwgcGx1cyBsYSBmb25jdGlvbiBkZSBjb21ww6l0aXRpb24gcydhcHBsYXRpdCBldCBsYSBjb2V4aXN0ZW5jZSBkZXZyYWl0IGRldmVuaXIgcGx1cyBjb21wbGlxdcOpZS4gT24gcydhdHRlbmQgZG9uYyDDoCB1bmUgcmVsYXRpb24gbsOpZ2F0aXZlIGVudHJlICRcc2lnbWFeMiQgZXQgbGUgbm9tYnJlIGRlIHBow6lub3R5cGUgcG91dmFudCBjb2V4aXN0ZXIuIA0KDQpPbiB2YSBtYWludGVuYW50IGZhaXJlIHVuZSByZXByw6lzZW50YXRpb24gZW4gM0QgcG91ciBsYSBmaXRuZXNzIGQnaW52YXNpb24sIHF1aSBzJ2V4cHJpbWUgOiAkcyh4X3sxfSx4X3syfSk9ci4oMS1hKHhfezF9LHhfezJ9KS5cZnJhY3tLKHhfezJ9KX17Syh4X3sxfSl9KSQNCg0KYGBge3J9DQphIDwtIGZ1bmN0aW9uKHgxLHgyLCBzaWdtYUEpew0KICByZXR1cm4oKQ0KfQ0KDQpTIDwtIGZ1bmN0aW9uKHgxLHgyKXsNCiAgcj0xDQogIGEgPSBleHAoLS41Kih4MS14MikqKjIvc2lnbWFBKQ0KICByZXR1cm4ociooMS1hKksoeDIpL0soeDEpKSkNCn0NCngwPTENCkswPTENCmxhbWJkYSA9IDENCnNpZ21hQSA9IDENCg0KeDEgPC0gc2VxKDAuMSwxLjksLjEpDQp4MiA8LSBzZXEoMC4xLDEuOSwuMSkNCiNjcmVhdGUgei12YWx1ZXMNCnogPSBvdXRlcih4MSwgeDIsIFMpDQoNCiNjcmVhdGUgM0QgcGxvdA0KcGVyc3AoeDEsIHgyLCB6LCB4bGFiPSd4JywgeWxhYj0neScsIHpsYWI9J3MoeCx5KScsDQogICAgICBtYWluPSdJbnZhc2lvbiBmaXRuZXNzJywgY29sPSdsaWdodGJsdWUnLCBzaGFkZT0uNCwgdGhldGEgPSA2MCwgcGhpID0gMjUsIHRpY2t0eXBlPSdzaW1wbGUnKQ0KDQpgYGANCg0KDQojIFNpbXVsYXRpb25zIGRlIGxhIGR5bmFtaXF1ZSBhZGFwdGF0aXZlDQoNCiMjIETDqWZpbml0aW9uIGRlcyBmb25jdGlvbnMgdXRpbGlzw6llcyBkYW5zIGxhIHN1aXRlDQoNCiogQ3LDqWF0aW9ucyBkZXMgZm9uY3Rpb25zIGRlIGJhc2UgcG91ciBsYSBzaW11bGF0aW9uDQpgYGB7ciB9DQojIEZvbmN0aW9uIHBvdXIgY2FsY3VsZXIgSw0KY2FsY3VsZUsgPC0gZnVuY3Rpb24oWCxLMCx4MCwgbGFtYmRhKXsNCiAgcmV0dXJuKG1heChLMCAtIGxhbWJkYSooWCAtIHgwKSoqMiwgMTAqKigtOSkpKQ0KfQ0KDQoNCiMgRm9uY3Rpb24gcG91ciBvYnRlbmlyIGxlcyB2YWxldXJzIGRlIGxhIG1hdHJpY2UgQQ0KY2FsY3VsZUEgPC0gZnVuY3Rpb24oWCwgbmJTcGVjaWVzLCBzaWdtYUFfc3F1YXJlZCl7DQogIEQgPSBtYXRyaXgoZGF0YT1YLG5jb2w9bmJTcGVjaWVzLG5yb3c9bmJTcGVjaWVzKSAjIG1hdHJpY2UNCiAgQyA9IHQoRCkgLSBEDQogIHJldHVybihleHAoLUMqKjIvKDIqc2lnbWFBX3NxdWFyZWQpKSkNCn0NCg0KDQojIEZvbmN0aW9uIHBvdXIgY2FsY3VsZXIgbGEgbWF0cmljZSBCDQpjYWxjdWxlQiA8LSBmdW5jdGlvbihYLCBuYlNwZWNpZXMsIEswLCB4MCwgbGFtYmRhKXsNCiAgdmVjdEsgPSBzYXBwbHkoWCwgRlVOPSBmdW5jdGlvbih4KSB7IGNhbGN1bGVLKHgsIEswLCB4MCwgbGFtYmRhKSB9KQ0KICBCID0gbWF0cml4KGRhdGEgPSAxL3ZlY3RLLCBucm93PW5iU3BlY2llcywgbmNvbD1uYlNwZWNpZXMsIGJ5cm93PVRSVUUpDQogIHJldHVybihCKQ0KfQ0KDQoNCiMgRm9uY3Rpb24gcGFyIGNhbHVjbGUgbGEgbWF0cmljZSBNIHF1aSBlc3QgbGUgcHJvZHVpdCBkZSBBIGV0IEINCmNhbGN1bGVNID0gZnVuY3Rpb24oWCwgbmJTcGVjaWVzLCBzaWdtYUFfc3F1YXJlZCwgSzA9MSwgbGFtYmRhPTEpew0KICB4MCA9IG1pbihYKSArIChtYXgoWCkgLSBtaW4oWCkpIC8gMiAgDQogIA0KICBBID0gY2FsY3VsZUEoWCwgbmJTcGVjaWVzLCBzaWdtYUFfc3F1YXJlZCkgIyBtYXRyaWNlIEENCiAgQiA9IGNhbGN1bGVCKFgsIG5iU3BlY2llcywgSzAsIHgwLCBsYW1iZGEpICMgbWF0cmljZSBCDQogIA0KICBNPUEqQiAjIHByb2R1aXQgdGVybWUgYSB0ZXJtZSBkZSBBIGF2ZWMgQg0KICByZXR1cm4oTSkNCn0NCg0KDQojIEZvbmN0aW9ucyBwb3VyIGTDqWZpbml0aW9uIGwnZXF1YXRpb24gZGlmZmVyZW50aWVsbGUgDQpjYWxjdWxlR3JhZGllbnRMViA9IGZ1bmN0aW9uKHQsIE5pLCBwYXJhbXMpew0KICB3aXRoKGFzLmxpc3QocGFyYW1zKSwgew0KICAgIA0KICAgIGROaSA9IHIqTmkqKDEgLSBOaSUqJU0pDQoNCiAgICByZXR1cm4obGlzdChkTmkpKQ0KICB9KQ0KfQ0KDQoNCiMgRm9uY3Rpb25zIHBvdXIgcmVzb3VkcmUgbCdlcXVhdGlvbiBkaWZmZXJlbnRpZWxsZSBhdmVjIG9kZSBkdSBwYWNrYWdlIGRlU29sdmUNCnJlc291dEVxZCA8LSBmdW5jdGlvbihwYXJhbWV0ZXJzLCBkZW5zaXRlX3QwLCB2ZWN0X3RpbWUsIHZlY3RfcGhlbm90eXBlcyl7DQogIGRmX2RlbnNpdGUgPSBvZGUoeSA9IGRlbnNpdGVfdDAsIHRpbWVzID0gdmVjdF90aW1lLCBmdW5jID0gY2FsY3VsZUdyYWRpZW50TFYsIHBhcmFtZXRlcnMpICU+JQ0KICAgIGFzLmRhdGEuZnJhbWUoKSAlPiUNCiAgICBwaXZvdF9sb25nZXIoLXRpbWUsIHZhbHVlc190byA9ICJkZW5zaXR5IiwgbmFtZXNfdG8gPSAic3BlY2llcyIpDQoNCiAgZGZfZGVuc2l0ZSRYID0gdmVjdF9waGVub3R5cGVzW2FzLm51bWVyaWMoZGZfZGVuc2l0ZSRzcGVjaWVzKV0NCiAgICANCiAgcmV0dXJuKGRmX2RlbnNpdGUpIyByZXRvdXJuZSBzb3J0aWUgbW9kaWZpZXIgZGUgb2RlDQp9DQoNCg0KIyBEw6lmaW5pdGlvbiBkZSBsYSBmb25jdGlvbiBwb3VyIHNpbXVsZXIgbGUgZHluYW1pcXVlIGRlIGxhIHBvcHB1bGF0aW9uDQpzaW11bGVEeW5hbWlxdWUgPC0gZnVuY3Rpb24obmJTcGVjaWVzLCBzaWdtYUFfc3F1YXJlZCwgdmVjdF90ZW1wcywgSzA9MSwgcj0xKXsNCiAgbjAgPSByZXAoSzAvbmJTcGVjaWVzLCBuYlNwZWNpZXMpIA0KICB2ZWN0X3BoZW5vdHlwZXMgPSBzZXEoMCwgMSwgbGVuZ3RoLm91dCA9IG5iU3BlY2llcykNCg0KICBNID0gY2FsY3VsZU0odmVjdF9waGVub3R5cGVzLCBuYlNwZWNpZXMsIHNpZ21hQV9zcXVhcmVkLCBLMCkNCiAgcGFyYW1zID0gbGlzdChyID0gciwgTSA9IE0pDQogIGRmX2RlbnNpdGUgPSByZXNvdXRFcWQocGFyYW1zLCBuMCwgdmVjdF90ZW1wcywgdmVjdF9waGVub3R5cGVzKQ0KICANCiAgcmV0dXJuKGRmX2RlbnNpdGUpICMgbGEgZGVuc2l0w6kgZGUgbGEgcG9wdWxhdGlvbiBvYnRlbnUgYXZlYyBvZGUNCn0NCg0KYGBgDQoNCg0KDQoqIExlcyBmb25jdGlvbnMgcG91ciBsZXMgcmVwcsOpc2VudGF0aW9ucyBncmFwaGlxdWVzDQpgYGB7ciB9DQoNCiMgZMOpZmluaXRpb24gZGVzIHRow6htZXMNCm1haW5fdGhlbWUgPSB0aGVtZV9idygpKw0KICB0aGVtZShsaW5lID0gZWxlbWVudF9ibGFuaygpLCANCiAgICAgICAgYXhpcy5saW5lID0gZWxlbWVudF9saW5lKGNvbG91ciA9ICJibGFjayIpLA0KICAgICAgICBwYW5lbC5ib3JkZXIgPSBlbGVtZW50X2JsYW5rKCksDQogICAgICAgIGF4aXMudGlja3MgPSAgZWxlbWVudF9saW5lKGNvbG91ciA9ICJibGFjayIpLA0KICAgICAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChjb2xvdXIgPSAiYmxhY2siLCBzaXplPTEwKSwNCiAgICAgICAgYXhpcy50ZXh0LnkgPSBlbGVtZW50X3RleHQoY29sb3VyID0gImJsYWNrIiwgc2l6ZT0xMCksDQogICAgICAgIGxlZ2VuZC5wb3NpdGlvbj0ibm9uZSIsDQogICAgICAgIGF4aXMudGl0bGU9ZWxlbWVudF90ZXh0KHNpemU9MTApKQ0KDQojIGZvbmN0aW9uIHBvdXIgaWxsdXN0cmVyIGxlIGR5bmFtaXF1ZSBkZSBsYSBwb3B1bGF0aW9uIHNlbG9uIGxlIHRlbXBzDQpwbG90RHluYW1pcXVlIDwtIGZ1bmN0aW9uKGRmX2RlbnNpdHksIHBsb3RfdGhlbWU9bWFpbl90aGVtZSwgdGl0bGU9ICIiKXsNCiAgcGxvdCA9IGdncGxvdChkZl9kZW5zaXR5KSsNCiAgICBnZW9tX2xpbmUoYWVzKHRpbWUsIGRlbnNpdHksIGNvbCA9IHNwZWNpZXMpKSArDQogICAgZ2d0aXRsZShsYWJlbCA9IHRpdGxlKSArDQogICAgeGxhYigiVGVtcHMiKSArDQogICAgeWxhYigiUGjDqW5vdHlwZXMiKSArDQogICAgcGxvdF90aGVtZQ0KICByZXR1cm4ocGxvdCkNCn0NCg0KIyBmb25jdGlvbiBwb3VyIGlsbHVzdHJlciBsJ8Opdm9sdXRpb24gZGUgbGEgZHluYW1pcXVlIHNlbG9uIGxlcyBwaMOpbm90eXBlcw0KcGxvdEV2b2x1dGlvbnRyYWl0IDwtIGZ1bmN0aW9uKGRmX2RlbnNpdHksIHBsb3RfdGhlbWU9bWFpbl90aGVtZSl7DQogIHBsb3QgPSBnZ3Bsb3QoZGF0YSA9IGRmX2RlbnNpdHkpICsNCiAgICBnZW9tX3RpbGUoYWVzKHggPSBYLCB5ID0gdGltZSwgZmlsbCA9IGRlbnNpdHkpKSsNCiAgICBzY2FsZV9maWxsX2dyYWRpZW50Mihsb3cgPSAid2hpdGUiICwgaGlnaCA9ICJyZWQiKSsNCiAgICB4bGFiKCJQaMOpbm90eXBlcyIpICsNCiAgICB5bGFiKCJUZW1wcyIpICsNCiAgICBwbG90X3RoZW1lDQogIA0KICByZXR1cm4ocGxvdCkNCn0NCg0KYGBgDQoNCiANCg0KIyMgU2ltdWxhdGlvbiBzYW5zIG11dGF0aW9uDQoNCmBgYHtyLCBmaWcuYWxpZ249J2NlbnRlcid9DQp2ZWN0X3RlbXBzID0gMDoyMDAwICMgdGVtcHMgZGUgc2ltdWxhdGlvbg0KSzAgPSAxICAgICAgICAgICAgICMgY2FwYWNpdGUgZGUgY2hhcmdlIGR1IG1pbGlldSB0b3V0IHBoZW5vdHlwZSBjb25mb25kdQ0Kc2lnbWFBX3NxdWFyZWQgPSAxICMgdmFyaWUgZW50cmUgMC4xIGV0IDEwDQpyPTEgICAgICAgICAgICAgICAgIyB0YXV4IGRlIGNyb2lzc2FuY2UsIGxlIG3Dqm1lIHBvdXIgdG91dGVzIGxlcyBlc3DDqGNlcw0KDQoNCiMgU2ltdWxhdGlvbiBkZSBsYSBkeW5hbWlxdWUgYXZlYyAxMCBwaGVub3R5cGVzDQpuYlNwZWNpZXMgPSAxMCAjIG5vbWJyZSBkJ2VzcGVjZXMNCmRmX2RlbnNpdGUxMCA9IHNpbXVsZUR5bmFtaXF1ZShuYlNwZWNpZXMsIHNpZ21hQV9zcXVhcmVkLCB2ZWN0X3RlbXBzKQ0KDQpwbG90MSA9IHBsb3REeW5hbWlxdWUoZGZfZGVuc2l0ZTEwLCB0aXRsZT0iRXZvbHV0aW9uIGRlcyBwaMOpbm90eXBlcyAoTiA9IDEwKSIgKQ0KcGxvdDIgPSBwbG90RXZvbHV0aW9udHJhaXQoZGZfZGVuc2l0ZTEwKQ0KDQoNCiMgU2ltdWxhdGlvbiBkZSBsYSBkeW5hbWlxdWUgYXZlYyAxMDAgcGhlbm90eXBlcw0KbmJTcGVjaWVzID0gMTAwICMgbm9tYnJlIGQnZXNwZWNlcw0KZGZfZGVuc2l0ZTEwMCA9IHNpbXVsZUR5bmFtaXF1ZShuYlNwZWNpZXMsIHNpZ21hQV9zcXVhcmVkLCB2ZWN0X3RlbXBzKQ0KDQpwbG90MyA9IHBsb3REeW5hbWlxdWUoZGZfZGVuc2l0ZTEwMCwgdGl0bGU9IkV2b2x1dGlvbiBkZXMgcGjDqW5vdHlwZXMgKE4gPSAxMDApIikNCnBsb3Q0ID0gcGxvdEV2b2x1dGlvbnRyYWl0KGRmX2RlbnNpdGUxMDApDQoNCnBsb3RfZ3JpZChsYWJlbHMgPSAiYXV0byIscGxvdGxpc3Q9bGlzdChwbG90MSxwbG90MixwbG90MyxwbG90NCkpDQoNCmBgYA0KDQpBdmVjICRcc2lnbWFeMiA9IDEkLCB1bmUgdmFsZXVyIGludGVybcOpZGlhaXJlLCBvbiBvYnNlcnZlIHF1ZSB0b3V0ZXMgbGVzIGVzcMOoY2VzIHMnw6lsb2lnbmFudCB0cm9wIGRlIGwnb3B0aW11bSBzb250IMOpbGltaW7DqWVzLiBMYSB0ZW5kYW5jZSBzZW1ibGUgZG9uYyBwbHV0w7R0IMOqdHJlIGwnw6lsaW1pbmF0aW9uIHBhciBjb21ww6l0aXRpb24gcXVlIGxhIGNvZXhpc3RlbmNlIGRlIHBow6lub3R5cGUuDQoNCg0KIyMgQ29leGlzdGVuY2UgZGVzIHBow6lub3R5cGVzIGV0IHZhbGV1cnMgZGUgc2lnbWENCg0KT24gcGV1dCBzZSBwb3NlciBsYSBxdWVzdGlvbiBzdWl2YW50ZSA6IENvbWJpZW4gZCdlc3DDqGNlcyBwZXJzaXN0ZXJvbnQgZGFucyBsZSB0ZW1wcyBzZWxvbiBsYSB2YWxldXIgZGUgc2lnbWEgPw0KDQpgYGB7ciwgZmlnLmFsaWduPSdjZW50ZXInfQ0KdmVjdF90ZW1wcyA9IDA6MjAwMCAjIHRlbXBzIGRlIHNpbXVsYXRpb24NCnNpZ21hQV9zcXVhcmVkID0gYygwLjEsIDAuNSwgMSwgMTApICMgTGVzIGRpZmZlcmVudHMgdmFsZXVycyBkZSBzaWdtYQ0KDQpwbG90X2xpc3QgPSBsaXN0KCkNCg0KbmJTcGVjaWVzID0gMTAwDQpmb3IgKGkgaW4gMTpsZW5ndGgoc2lnbWFBX3NxdWFyZWQpKXsNCiAgZGZfZGVuc2l0ZSA9IHNpbXVsZUR5bmFtaXF1ZShuYlNwZWNpZXMsIHNpZ21hQV9zcXVhcmVkW2ldLCB2ZWN0X3RlbXBzKQ0KICBwbG90X2xpc3RbW2ldXSA9IHBsb3REeW5hbWlxdWUoZGZfZGVuc2l0ZSwgdGl0bGU9cGFzdGUoIkNhcyBOPSAxMDAsIHNpZ21hwrIgPSAiLCBzaWdtYUFfc3F1YXJlZFtpXSkpDQogICNwbG90X2xpc3RbW2ldXSA9IHBsb3RFdm9sdXRpb250cmFpdChkZl9kZW5zaXRlKSkNCn0NCg0KcGxvdF9ncmlkKGxhYmVscyA9ICJhdXRvIixwbG90bGlzdCA9IHBsb3RfbGlzdCkNCmBgYA0KDQpBdmVjICROID0gMTAwJCBPbiBjb25zdGF0ZSBxdSfDoCBtZXN1cmUgcXVlICRcc2lnbWFeMiQgYXVnbWVudGUsIGxlIG5vbWJyZSBkZSBwaMOpbm90eXBlcyBxdWkgcGVyc2lzdGVudCDDoCBsb25nIHRlcm1lIGF1Z21lbnRlIGVudHJlIDAuMSBldCAwLjUuIExvcnNxdWUgJFxzaWdtYV4yJCBlc3Qgc3Vww6lyaWV1ciDDoCAkMSQsIGNlcnRhaW5zIHBow6lub3R5cGVzIHN1YnNpc3RlbnQgaW5pdGlhbGVtZW50IG1haXMgZmluaXNzZW50IHJhcGlkZW1lbnQgcGFyIHMnw6l0ZWluZHJlLCBldCBhdSBib3V0IGRlICQyMDAwJCBwYXMgZGUgdGVtcHMgZGUgc2ltdWxhdGlvbiwgdW4gc2V1bCBwaMOpbm90eXBlIHN1YnNpc3RlLg0KDQoNClJlZ2FyZG9ucyBtYWludGVuYW50IGxlIG5vbWJyZSBkZSBwaMOpbm90eXBlcyBxdWkgcGVyc2lzdGVudCBzZWxvbiBwbHVzaWV1cnMgdmFsZXVycyBkZSAkc2lnbWHCsiQuDQoNCmBgYHtyLCBmaWcuYWxpZ249J2NlbnRlcicsIHdhcm5pbmc9RkFMU0UsIG1lc3NhZ2U9RkFMU0V9DQpkZW5zaXR5X21pbiA9IDAuMDA1ICMgZW4gZGVzc291cyBkZSBjZXR0ZSBkZW5zaXTDqSBsZSBwaMOpbm90eXBlIGVzdCBjb25zaWTDqXLDqSBkaXNwYXJ1DQoNCnZlY3RfdGVtcHMgPSAwOjIwMDAgIyB0ZW1wcyBkZSBzaW11bGF0aW9uDQpzaWdtYUFfc3F1YXJlZCA9IGMoc2VxKDAuMSwxLCBieT0wLjEpLCBzZXEoMiwxMCkpICMgbGVzIHZhbGV1cnMgZGUgc2lnbWHCsiBhIHRlc3Rlcg0KDQpkZl9wZXJzaXRhbmNlID0gZGF0YS5mcmFtZSgpDQpuYlNwZWNpZXMgPSAxMDAgIyBOb21icmUgZCdlc3DDqGNlcw0KZm9yIChpIGluIDE6bGVuZ3RoKHNpZ21hQV9zcXVhcmVkKSl7DQogIGRmX2RlbnNpdGUgPSBzaW11bGVEeW5hbWlxdWUobmJTcGVjaWVzLCBzaWdtYUFfc3F1YXJlZFtpXSwgdmVjdF90ZW1wcykNCiAgbmJfcGVyc2lzdGFudCA9IGRmX2RlbnNpdGVbZGZfZGVuc2l0ZSR0aW1lID09IG1heChkZl9kZW5zaXRlJHRpbWUpLF0gJT4lDQogICAgZmlsdGVyKGRlbnNpdHk+ZGVuc2l0eV9taW4pICU+JQ0KICAgIG5yb3coKQ0KICANCiAgZGZfcGVyc2l0YW5jZSA9IHJiaW5kKGRmX3BlcnNpdGFuY2UsYyhzaWdtYUFfc3F1YXJlZFtpXSxuYl9wZXJzaXN0YW50KSkNCn0NCg0KY29sbmFtZXMoZGZfcGVyc2l0YW5jZSkgPSBjKCJzaWdtYV9zcXVhcmUiLCAibmJfcGVyc2lzdGFudCIpDQoNCmdncGxvdChkYXRhID0gZGZfcGVyc2l0YW5jZSwgYWVzKHggPSBzaWdtYV9zcXVhcmUsIHkgPSBuYl9wZXJzaXN0YW50KSkgKw0KICBnZW9tX3BvaW50KCkgKw0KICBnZW9tX2xpbmUoKSArDQogIGxhYnModGl0bGUgPSAiTm9tYnJlIGRlIHBow6lub3R5cGVzIHBlcnNpc3RhbnRzIHNlbG9uIHNpZ21hwrIiLA0KICAgICAgIHN1YnRpdGxlID0gIkNhcyBOID0gMTAwIiwNCiAgICB4ID0gIlNpZ21hwrIiLA0KICAgIHkgPSAiTm9tYnJlIGRlIHBow6lub3R5cGVzIg0KICApICsNCiAgbWFpbl90aGVtZQ0KDQoNCg0KYGBgDQoNCkNvbW1lIG9uIHMneSBhdHRlbmRhaXQgbGUgbm9tYnJlIGQnZXNww6hjZXMgcGVyc2lzdGFudGVzIGRpbWludWUgbG9yc3F1ZSAkXHNpZ21hXjIkIGF1Z21lbnRlLg0KDQpZIGEtdC1pbCB1bmUgbGltaXRlIGF1IG5vbWJyZSBkJ2VzcMOoY2VzIHF1aSBwZXV2ZW50IGNvZXhpc3RlciBkYW5zIGNlIG1vZMOobGUgPyBBIHByaW9yaSBpbCBuJ3kgcGFzIGRlIGxpbWl0ZSBjYXIgb24gcHJlbmQgdW5lIGZvbmN0aW9uIGdhdXNzaWVubmUsIG1haXMgdW5lIGxpbWl0ZSBwb3VycmFpdCBhcHBhcmFpdHJlIHNpIG9uIHByZW5haXMgZCdhdXRyZXMgZGlzdHJpYnV0aW9ucy4gDQoNCg0KIyMgU2ltdWxhdGlvbiBhdmVjIGFqb3V0IGRlIG11dGF0aW9ucyBkYW5zIGxhIGR5bmFtaXF1ZQ0KDQpEw6lmaW5pdGlvbiBkZXMgZm9uY3Rpb24gcG91ciBzaW11bGVyIGxhIG11dGF0aW9uLg0KDQpQb3VyIHNpbXVsZXIgZGVzIG11dGF0aW9ucywgbGUgcHJpbmNpcGUgdmEgw6p0cmUgbGUgc3VpdmFudCA6DQogIA0KICAxLiBPbiB0aXJlIGxlcyB0ZW1wcyBhdXhxdWVscyBsZXMgbXV0YXRpb25zIHZvbnQgYXZvaXIgbGlldSBkZSBtYW5pw6hyZSBhbMOpYXRvaXJlDQogIA0KICANCiAgMi5hIE9uIHNpbXVsZSBsYSBkeW5hbWlxdWUgZW50cmUgdDAgZXQgbGUgcHJlbWllciB0ZW1wcyBkZSBtdXRhdGlvbg0KICANCiAgDQogIDIuYiBBdSB0ZW1wcyBkZSBtdXRhdGlvbiwgb24gcsOpY3Vww6hyZSBsZXMgZGVuc2l0w6lzIGZpbmFsZXMgcHVpcyBvbiBhcHBsaXF1ZSBsYSBtdXRhdGlvbiwgcXVpIGNvbnNpc3RlIMOgICJkw6ljYWxlciIgbGVzIHBow6lub3R5cGVzIHZlcnMgbGEgZHJvaXRlIG91IGxhIGdhdWNoZSBlbiBkw6lwbGHDp2FudCB1bmUgY2VydGFpbmVzIHByb3BvcnRpb24gZGUgbGEgZGVuc2l0w6kgZGFucyBsZSBwaMOpbm90eXBlIGp1c3RlIMOgIGRyb2l0ZSBvdSBqdXN0ZSDDoCBnYXVjaGUuIA0KICANCiAgDQogIDIuYyBPbiByZXRvdXJuZSDDoCBsJ8OpdGFwZSAyLmEgZXQgb24gc2ltdWxlIGxhIGR5bmFtaXF1ZSBlbnRyZSBsZSB0ZW1wcyBkZSBtdXRhdGlvbiBldCBsZSBzdWl2YW50IGVuIHByZW5hbnQgY29tbWUgY29uZGl0aW9uIGluaXRpYWxlIGxlIHZlY3RldXIgZGVzIGRlbnNpdMOpcyBtdXTDqWVzLg0KICANCiAgDQogIA0KKipEw6lmaW5pdGlvbiBkZXMgZm9uY3Rpb25zIHV0aWxlcyA6KioNCg0KYGBge3IgfQ0KDQojIGZvbmN0aW9uIHBvdXIgb2J0ZW5pciBsZXMgdGVtcHMgYXV4cXVlbHMgaWwgeSBhdXJhIHVuZSBtdXRhdGlvbiANCmdldE11dGF0aW9uVGltZSA8LSBmdW5jdGlvbih2ZWN0X3RlbXBzLCB0YXV4X211dGF0aW9uKXsNCiAgU2ltdWxhdGlvbkR1cmF0aW9uID0gbWF4KHZlY3RfdGVtcHMpDQogICMgRGV0ZXJtaW5hdGlvbiBkZXMgaW5zdGFudHMgYXV4cXVlbHMgaWwgeSBhdXJhIHVuZSBtdXRhdGlvbg0KICBuYl9tdXRhdGlvbnNfbW95ID0gU2ltdWxhdGlvbkR1cmF0aW9uICogdGF1eF9tdXRhdGlvbiANCiAgbmJfbXV0YXRpb25zX3NkID0gbmJfbXV0YXRpb25zX21veSAvIDQNCiAgbmJfbXV0YXRpb25zID0gbWF4KDEsZmxvb3Iocm5vcm0oMSwgbWVhbj1uYl9tdXRhdGlvbnNfbW95LCBzZD1uYl9tdXRhdGlvbnNfc2QpKSkNCiAgDQogIHRlbXBzX211dGF0aW9uID0gYygwLHNvcnQoc2FtcGxlKHggPSB2ZWN0X3RlbXBzLCBzaXplID0gbmJfbXV0YXRpb25zKSksU2ltdWxhdGlvbkR1cmF0aW9uKQ0KICByZXR1cm4odGVtcHNfbXV0YXRpb24pDQp9DQoNCg0KIyBmb25jdGlvbiBwb3VyIGxlIGTDqWNhbGFnZSAoZHJvaXRlIG91IGdhdWNoZSkgZGUgbGEgbXV0YXRpb24NCm11dGF0aW9uUGFyRGVjYWxhZ2UgPC0gZnVuY3Rpb24oZGZfZGVuc2l0ZSwgcHJvcG9ydGlvbk11dGFudCkgew0KICANCiAgbjAgPSBkZl9kZW5zaXRlJGRlbnNpdHlbZGZfZGVuc2l0ZSR0aW1lPT1tYXgoZGZfZGVuc2l0ZSR0aW1lKV0gDQogIG5iU3BlY2llcyA9IGxlbmd0aChuMCkNCiAgIyBtdXRhdGlvbiA6IGRlY2FsYWdlIGQndW5lIHBhcnRpZSBkZXMgZWZmZWN0aWZzDQogIHAgPSBydW5pZigxKSAjIHRpcmFnZSBhbMOpYXRvaXJlIGVudHJlIDAgZXQgMQ0KDQogIGlmIChwIDwgLjcpew0KICAjIGRlY2FsYWdlIMOgIGRyb2l0ZQ0KICBlZmZlY3RpZk11dGFudHMgPSBuMFsxOihuYlNwZWNpZXMtMSldKnByb3BvcnRpb25NdXRhbnQNCiAgICANCiAgbjBbMToobmJTcGVjaWVzLTEpXSA9IG4wWzE6KG5iU3BlY2llcy0xKV0gLSBlZmZlY3RpZk11dGFudHMNCiAgbjBbMjpuYlNwZWNpZXNdID0gbjBbMjpuYlNwZWNpZXNdICsgZWZmZWN0aWZNdXRhbnRzDQogIH0NCiAgZWxzZXsNCiAgIyBkZWNhbGFnZSBhIGdhdWNoZQ0KICBlZmZlY3RpZk11dGFudHMgPSBuMFsyOm5iU3BlY2llc10qcHJvcG9ydGlvbk11dGFudA0KICAgIA0KICBuMFsxOihuYlNwZWNpZXMtMSldID0gbjBbMToobmJTcGVjaWVzLTEpXSArIGVmZmVjdGlmTXV0YW50cw0KICBuMFsyOm5iU3BlY2llc10gPSBuMFsyOm5iU3BlY2llc10gLSBlZmZlY3RpZk11dGFudHMNCiAgfQ0KICByZXR1cm4objApDQp9DQoNCg0KIyBmb25jdGlvbiBwb3VyIGxhIHNpbXVsYXRpb24gZGUgbGEgbXV0YXRpb24NCnNpbXVsZUR5bmFtaXF1ZU11dGF0aW9uIDwtIGZ1bmN0aW9uKG5iU3BlY2llcywgc2lnbWFBX3NxdWFyZWQsIHRlbXBzX211dGF0aW9uLCBLMD0xLCByPTEpew0KICANCiAgIyBDYWxjdWxlIGRlcyBwaGVub3R5cGVzIGV0IGRlIE0NCiAgdmVjdF9waGVub3R5cGVzID0gc2VxKDAsIDEsIGxlbmd0aC5vdXQgPSBuYlNwZWNpZXMpDQogIE0gPSBjYWxjdWxlTSh2ZWN0X3BoZW5vdHlwZXMsIG5iU3BlY2llcywgc2lnbWFBX3NxdWFyZWQsIEswKQ0KICBwYXJhbXMgPSBsaXN0KHIgPSByLCBNID0gTSkNCiAgDQogICMgSW5pdGlhbGlzYXRpb24gZGUgbjAgZXQgZGUgZGZfZGVuc2l0ZQ0KICBuMCA9IGMoSzAsIHJlcCgwLCBuYlNwZWNpZXMtMSkpDQogIGRmX2RlbnNpdGUgPSBkYXRhLmZyYW1lKCkNCg0KICAjIFNpbXVsYXRpb24gZGUgbGEgZHluYW1pcXVlIHBvdXIgdG91dCBsZXMgdGVtcHMgZGUgbXV0YXRpb24NCiAgZm9yIChpIGluIDI6bGVuZ3RoKHRlbXBzX211dGF0aW9uKSl7DQogICAgaWYgKHRlbXBzX211dGF0aW9uW2ldLXRlbXBzX211dGF0aW9uW2ktMV0+MSl7DQogICAgICAjIEVudHJlIDIgbXV0YXRpb24gb24gc2ltdWxlIGVuIHJlc29sdmFudCBsJ2VxdWF0aW9uIGRpZmZlcmVudGllbGxlDQogICAgICBzYW1wbGVfdmVjdF90ZW1wcyA9IHNlcSh0ZW1wc19tdXRhdGlvbltpLTFdLHRlbXBzX211dGF0aW9uW2ldLTEpDQogICAgICBkZl9kZW5zaXRlID0gcmJpbmQoZGZfZGVuc2l0ZSxyZXNvdXRFcWQocGFyYW1zLCBuMCwgc2FtcGxlX3ZlY3RfdGVtcHMsIHZlY3RfcGhlbm90eXBlcykpDQogICAgICAjIEEgY2hhcXVlIHRlbXBzIGNvbnRlbnUgZGFucyB0ZW1wc19tdXRhdGlvbiB1bmUgbXV0YXRpb24gYSBsaWV1IA0KICAgICAgbjAgPSBtdXRhdGlvblBhckRlY2FsYWdlKGRmX2RlbnNpdGUsIHByb3BvcnRpb25NdXRhbnQpICMgbm8gZGV2aWVudCBsZSBubyBhdmVjIG11dGF0aW9uDQogICAgfQ0KICAgIA0KICB9DQogIHJldHVybihkZl9kZW5zaXRlKSAjIHBvcCB0b3QgYXZlYyBtdXRhdGlvbg0KfQ0KDQoNCmBgYA0KDQoNCioqVmlzdWFsaXNhdGlvbiBkZSBsYSBkeW5hbWlxdWUgYXZlYyAxMCBldCAxMDAgZXNww6hjZXMsIGV0IHVuIHRhdXggZGUgbXV0YXRpb24gZGUgMS8xMDAsIHNvaXQgZW4gbW95ZW5uZSB1bmUgbXV0YXRpb25zIHRvdXMgbGVzIDEwMCBwYXMgZGUgdGVtcHMuKiogDQoNCmBgYHtyLCBmaWcuYWxpZ249J2NlbnRlcid9DQp2ZWN0X3RlbXBzID0gMDoyMDAwICMgVGVtcHMgZGUgc2ltdWxhdGlvbg0Kc2lnbWFBX3NxdWFyZWQgPSAwLjQgIyBzaW1nYcKyDQpwcm9wb3J0aW9uTXV0YW50ID0gMC4xICMgUHJvcG9ydGlvbiBkdSBub21icmUgZGUgcGjDqW5vdHlwZSBtdXRhbnQNCnRhdXhfbXV0YXRpb24gPSAxLzEwMCAjIHRhdXggZGUgbXV0YXRpb24NCg0KDQojIFNpbXVsYXRpb24gZGUgbGEgZHluYW1pcXVlIGF2ZWMgMTAgcGhlbm90eXBlcw0KbmJTcGVjaWVzID0gMTANCnRlbXBzX211dGF0aW9uID0gZ2V0TXV0YXRpb25UaW1lKHZlY3RfdGVtcHMsIHRhdXhfbXV0YXRpb24pDQpkZl9kZW5zaXRlID0gc2ltdWxlRHluYW1pcXVlTXV0YXRpb24obmJTcGVjaWVzLCBzaWdtYUFfc3F1YXJlZCwgdGVtcHNfbXV0YXRpb24pDQoNCnBsb3QxID0gcGxvdER5bmFtaXF1ZShkZl9kZW5zaXRlLCB0aXRsZT0iQ2FzIE4gPSAxMCIpIA0KcGxvdDEgPSBwbG90MSArIGdlb21fdmxpbmUoeGludGVyY2VwdD10ZW1wc19tdXRhdGlvbiwgbGluZXR5cGU9JzE4JywgY29sb3I9J3JlZCcpDQoNCnBsb3QyID0gcGxvdEV2b2x1dGlvbnRyYWl0KGRmX2RlbnNpdGUpDQoNCg0KDQojIFNpbXVsYXRpb24gZGUgbGEgZHluYW1pcXVlIGF2ZWMgMTAwIHBoZW5vdHlwZXMNCm5iU3BlY2llcyA9IDEwMA0KdGVtcHNfbXV0YXRpb24gPSBnZXRNdXRhdGlvblRpbWUodmVjdF90ZW1wcywgdGF1eF9tdXRhdGlvbikNCmRmX2RlbnNpdGUxMDAgPSBzaW11bGVEeW5hbWlxdWVNdXRhdGlvbihuYlNwZWNpZXMsIHNpZ21hQV9zcXVhcmVkLCB0ZW1wc19tdXRhdGlvbikNCiAgDQpwbG90MyA9IHBsb3REeW5hbWlxdWUoZGZfZGVuc2l0ZTEwMCwgdGl0bGU9IkNhcyBOID0gMTAwIikgDQpwbG90MyA9IHBsb3QzICsgZ2VvbV92bGluZSh4aW50ZXJjZXB0PXRlbXBzX211dGF0aW9uLCBsaW5ldHlwZT0nMTgnLCBjb2xvcj0ncmVkJykNCg0KcGxvdDQgPSBwbG90RXZvbHV0aW9udHJhaXQoZGZfZGVuc2l0ZTEwMCkNCg0KcGxvdF9ncmlkKHBsb3RsaXN0PWxpc3QocGxvdDEscGxvdDIscGxvdDMscGxvdDQpKQ0KDQpgYGANCg0KTG9yc3F1ZSBsJ29uIGZhaXQgdW5lIHNpbXVsYXRpb24gYXZlYyBkZXMgbXV0YXRpb24sIG9uIHMnYXR0ZW5kIMOgIGNlIHF1ZSBsZXMgbXV0YXRpb25zIHJhbcOobmVudCBwcm9ncmVzc2l2ZW1lbnQgbGVzIHZhbGV1cnMgZGUgcGjDqW5vdHlwZXMgdmVycyBsJ29wdGltdW0uIERhbnMgY2VydGFpbnMgY2FzLCBlbiBkeW5hbWlxdWUgYWRhcHRhdGl2ZSBvbiBwZXV0IG9ic2VydmVyIGRlcyBwb2ludHMgZGUgYnJhbmNoZW1lbnQuIFBvdXIgY2VsYSBpbCBmYXV0IHF1J3VuIHBvaW50IHNpbmd1bGllciBkZSBsYSBkeW5hbWlxdWUgc29pdCBhdHRyYWN0ZXVyIG1haXMgaW5zdGFibGUsIGV0IHF1J2lsIHB1aXNzZSDDqnRyZSBlbnZhaGkgcGFyIGRlcyBwaMOpbm90eXBlIGRlIHZhbGV1ciBpbmbDqXJpZXVyZSBldCBzdXDDqXJpZXVyZSDDoCBzYSB2YWxldXIuIA0KDQpPbiBhIGwnaW1wcmVzc2lvbiBkJ29iZXJ2ZXIgdW4gcG9pbnQgZGUgYnJhbmNoZW1lbnQgYXZlYyBub3MgMTAgcGjDqW5vdHlwZXMuIEVuIHJldmFuY2hlLCBvbiBvYnNlcnZlIHF1ZSBhdmVjIDEwMCBwaMOpbm90eXBlcywgb24gbidhIHBhcyBsZSB0ZW1wcyBkZSByZWpvaW5kcmUgbCdvcHRpbXVtLiBPbiB2YSByZWZhaXJlIHVuZSBzaW11bGF0aW9uIGF2ZWMgMTAwIHBow6lub3R5cGVzIGVuIHByZW5hbnQgMiB0YXV4IGRlIG11dGF0aW9ucyBkaWZmw6lyZW50cy4NCg0KYGBge3IsIGZpZy5hbGlnbj0nY2VudGVyJ30NCnZlY3RfdGVtcHMgPSAwOjEwMDAwDQpzaWdtYUFfc3F1YXJlZCA9IDAuNA0KcHJvcG9ydGlvbk11dGFudCA9IDAuMQ0KDQoNCiMgU2ltdWxhdGlvbiBkZSBsYSBkeW5hbWlxdWUgYXZlYyA1MCBwaGVub3R5cGVzIGV0IHRhdXhfbXV0YXRpb24gPSAxLzIwMA0KbmJTcGVjaWVzID0gNTANCnRhdXhfbXV0YXRpb24gPSAxLzIwMA0KdGVtcHNfbXV0YXRpb24gPSBnZXRNdXRhdGlvblRpbWUodmVjdF90ZW1wcywgdGF1eF9tdXRhdGlvbikNCmRmX2RlbnNpdGUgPSBzaW11bGVEeW5hbWlxdWVNdXRhdGlvbihuYlNwZWNpZXMsIHNpZ21hQV9zcXVhcmVkLCB0ZW1wc19tdXRhdGlvbikNCg0KcGxvdDEgPSBwbG90RHluYW1pcXVlKGRmX2RlbnNpdGUsIHRpdGxlPSJUYXV4IGRlIG11dGF0aW9uID0gMC4wMDUiKSANCnBsb3QxID0gcGxvdDENCg0KcGxvdDIgPSBwbG90RXZvbHV0aW9udHJhaXQoZGZfZGVuc2l0ZSkNCg0KDQoNCiMgU2ltdWxhdGlvbiBkZSBsYSBkeW5hbWlxdWUgYXZlYyAxMDAgcGhlbm90eXBlcyBldCB0YXV4X211dGF0aW9uID0gMS81MA0KbmJTcGVjaWVzID0gNTANCnRhdXhfbXV0YXRpb24gPSAxLzUwDQp0ZW1wc19tdXRhdGlvbiA9IGdldE11dGF0aW9uVGltZSh2ZWN0X3RlbXBzLCB0YXV4X211dGF0aW9uKQ0KZGZfZGVuc2l0ZTEwMCA9IHNpbXVsZUR5bmFtaXF1ZU11dGF0aW9uKG5iU3BlY2llcywgc2lnbWFBX3NxdWFyZWQsIHRlbXBzX211dGF0aW9uKQ0KICANCnBsb3QzID0gcGxvdER5bmFtaXF1ZShkZl9kZW5zaXRlMTAwLCB0aXRsZT0iVGF1eCBkZSBtdXRhdGlvbiA9IDAuMDIiKSANCnBsb3QzID0gcGxvdDMNCg0KcGxvdDQgPSBwbG90RXZvbHV0aW9udHJhaXQoZGZfZGVuc2l0ZTEwMCkNCg0KcGxvdF9ncmlkKHBsb3RsaXN0PWxpc3QocGxvdDEscGxvdDIscGxvdDMscGxvdDQpKQ0KDQpgYGANCg0KQXZlYyB1biB0YXV4IGRlIG11dGF0aW9uIGZhaWJsZSwgbGEgZGVuc2l0w6kgZXN0IHRvdWpvdXJzIHRyw6hzIHJlc3NlcnLDqWUgYXV0b3VyIGRlIHBldSBkZSBwaMOpbm90eXBlIGRvbWluYW50IChsZSB0cmFpdCBlc3QgYmllbiByb3VnZSBldCBwYXMgw6l0YWzDqSkgZXQgb24gc2UgZMOpcGxhY2UgdHLDqHMgbGVudGVtZW50IHZlcnMgbCdvcHRpbXVtLiBTaSBsZSB0YXV4IGRlIG11dGF0aW9uIGVzdCBwbHVzIMOpbGV2w6kgZW4gcmV2YW5jaGUsIG9uIG9ic2VydmUgdW4gcG9pbnQgZGUgYnJhbmNoZW1lbnQgZXQgcGx1cyBkZSBwaMOpbm90eXBlcyBxdWkgY29leGlzdGVudCBlbiBwbHVzIGZhaWJsZSBkZW5zaXTDqS4gDQoNCg==