#############################################################

# Data prep

library("rio")
x <- import("https://docs.google.com/spreadsheets/d/1h7XhLd2Byp4OcXSdtxHly9iS7RA673RJ/export?format=csv&gid=1083477596")
x$commentCount <- as.integer(x$commentCount)
x$viewsCount <- as.numeric(x$viewsCount)
x$acousticness <- as.numeric(sub(",", ".", x$acousticness, fixed = TRUE))
x$danceability <- as.numeric(sub(",", ".", x$danceability, fixed = TRUE))
x$energy <- as.numeric(sub(",", ".", x$energy, fixed = TRUE))
x$instrumentalness <- as.numeric(sub(",", ".", x$instrumentalness, fixed = TRUE))
x$liveness <- as.numeric(sub(",", ".", x$liveness, fixed = TRUE))
x$loudness <- as.numeric(sub(",", ".", x$loudness, fixed = TRUE))
x$speechiness <- as.numeric(sub(",", ".", x$speechiness, fixed = TRUE))
x$tempo <- as.numeric(sub(",", ".", x$tempo, fixed = TRUE))
x$valence <- as.numeric(sub(",", ".", x$valence, fixed = TRUE))
x$key <- as.factor(x$key)
x$time_signature <- as.factor(x$time_signature)
min_max_normalize <- function(x)
{
  return( (1000-10)*((x- min(x)) /(max(x)-min(x))) + 10)
}
x$acousticness <- min_max_normalize(x$acousticness)
x$danceability <- min_max_normalize(x$danceability)
x$energy <- min_max_normalize(x$energy)
x$instrumentalness <- min_max_normalize(x$instrumentalness)
x$liveness <- min_max_normalize(x$liveness)
x$loudness <- min_max_normalize(x$loudness)
x$speechiness <- min_max_normalize(x$speechiness)
x$tempo <- min_max_normalize(x$tempo)
x$valence <- min_max_normalize(x$valence)
x$Genre <- as.factor(x$Genre)
x$key <- as.factor(x$key)
x$mode <- as.factor(x$mode)
x$Charts <- as.factor(x$Charts)

library("dplyr")

x <- dplyr::select(x, -one_of('Streams'))
x <- x[complete.cases(x), ]

var_list <- c('Artist_Albums_Number', 'Artist_Albums_Tracks_Number', 'Artist_Appearances_Number', 'Artist_Appearances_Tracks_Number', 'Artist_Compilations_Number', 'Artist_Compilations_Tracks_Number', 'Artist_Follower', 'Artist_Popularity', 'Artist_Singles_Number', 'Artist_Singles_Tracks_Number', 'Track_Duration_ms', 'Track_Popularity', 'acousticness', 'danceability', 'energy', 'instrumentalness', 'liveness', 'loudness', 'speechiness', 'tempo', 'valence', 'commentCount', 'dislikeCount', 'likeCount', 'viewsCount')

par(mar = rep(2, 4))

par(mfrow=c(5,5))

for (i in 1:length(var_list)){
  
  hist(x[, var_list[i]], probability = TRUE, col = "gray", main = var_list[i], xlab = "")
  lines(density(x[, var_list[i]]), col = "red")

}

NA
NA
NA
NA
par(mar = rep(2, 4))

par(mfrow=c(5,5))

for(i in 1:length(var_list)){
  
  df <- data.frame(x[,var_list[i]])
  names(df)[1] <- var_list[i]
  
  #ggplot(df, aes(x="",y=Artist_Albums_Number)) + stat_boxplot(geom ='errorbar') + geom_boxplot(outlier.colour="black", outlier.shape=16, #outlier.size=2, notch=FALSE)
  
  boxplot(x = df[,1], main = var_list[i], notch = FALSE)

}

NA
NA
NA
NA

Normality testing


#strictly_positive_variables <- c('Artist_Follower', 'Artist_Popularity', 'Track_Duration_ms', 'Track_Popularity', 'viewsCount', #'acousticness', 'danceability', 'energy', 'liveness', 'loudness', 'speechiness', 'tempo', 'valence')

for (i in 1:length(var_list)){
  
  column_name <- var_list[i]
  
  message(column_name)
  
  sub_df <- as.numeric(x[,column_name])
  
  # sub_df <- sub_df[complete.cases(sub_df), ]
  
  #sub_df <- as.numeric(as.character(unlist(sub_df[[1]])))
  
  test_statistic <- ks.test(sub_df, "pnorm", mean=mean(sub_df), sd=sd(sub_df))$statistic
  critical_value <- 1.3581 / sqrt (length(sub_df))
  
  if (test_statistic > critical_value) {
message(column_name)
} else {
message(paste(" ", column_name , " is approximately normally distributed!", test_statistic, critical_value))  
}}
Artist_Albums_Number
f昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinArtist_Albums_Number
Artist_Albums_Tracks_Number
f昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinArtist_Albums_Tracks_Number
Artist_Appearances_Number
f昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinArtist_Appearances_Number
Artist_Appearances_Tracks_Number
f昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinArtist_Appearances_Tracks_Number
Artist_Compilations_Number
f昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinArtist_Compilations_Number
Artist_Compilations_Tracks_Number
f昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinArtist_Compilations_Tracks_Number
Artist_Follower
Artist_Follower
Artist_Popularity
f昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden sein  Artist_Popularity  is approximately normally distributed! 0.0579112990955349 0.0970071428571429
Artist_Singles_Number
f昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinArtist_Singles_Number
Artist_Singles_Tracks_Number
f昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinArtist_Singles_Tracks_Number
Track_Duration_ms
f昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinTrack_Duration_ms
Track_Popularity
f昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinTrack_Popularity
acousticness
f昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinacousticness
danceability
f昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seindanceability
energy
f昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinenergy
instrumentalness
f昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seininstrumentalness
liveness
f昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinliveness
loudness
f昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinloudness
speechiness
f昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinspeechiness
tempo
f昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seintempo
valence
f昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinvalence
commentCount
f昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seincommentCount
dislikeCount
f昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seindislikeCount
likeCount
f昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinlikeCount
viewsCount
viewsCount

x_scaled <- as.data.frame(scale(x[, var_list]))

par(mar = rep(2, 4))

par(mfrow=c(5,5))

for (i in 1:length(var_list)){
  
  hist(x_scaled[, var_list[i]], probability = TRUE, col = "gray", main = var_list[i], xlab = "")
  lines(density(x_scaled[, var_list[i]]), col = "red")

}

NA
NA

par(mar = rep(2, 4))

par(mfrow=c(5,5))

for(i in 1:length(var_list)){
  
  df <- data.frame(x_scaled[,var_list[i]])
  names(df)[1] <- var_list[i]
  
  #ggplot(df, aes(x="",y=Artist_Albums_Number)) + stat_boxplot(geom ='errorbar') + geom_boxplot(outlier.colour="black", outlier.shape=16, #outlier.size=2, notch=FALSE)
  
  boxplot(x = df[,1], main = var_list[i], notch = FALSE)

}


for (i in 1:length(var_list)){
  
  column_name <- var_list[i]
  
  message(column_name)
  
  sub_df <- as.numeric(x_scaled[,column_name])
  
  # sub_df <- sub_df[complete.cases(sub_df), ]
  
  #sub_df <- as.numeric(as.character(unlist(sub_df[[1]])))
  
  test_statistic <- ks.test(sub_df, "pnorm", mean=mean(sub_df), sd=sd(sub_df))$statistic
  critical_value <- 1.3581 / sqrt (length(sub_df))
  
  if (test_statistic > critical_value) {
message(column_name)
} else {
message(paste(" ", column_name , " is approximately normally distributed!", test_statistic, critical_value))  
}}
Artist_Albums_Number
f昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinArtist_Albums_Number
Artist_Albums_Tracks_Number
f昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinArtist_Albums_Tracks_Number
Artist_Appearances_Number
f昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinArtist_Appearances_Number
Artist_Appearances_Tracks_Number
f昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinArtist_Appearances_Tracks_Number
Artist_Compilations_Number
f昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinArtist_Compilations_Number
Artist_Compilations_Tracks_Number
f昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinArtist_Compilations_Tracks_Number
Artist_Follower
Artist_Follower
Artist_Popularity
f昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden sein  Artist_Popularity  is approximately normally distributed! 0.0579112990955349 0.0970071428571429
Artist_Singles_Number
f昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinArtist_Singles_Number
Artist_Singles_Tracks_Number
f昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinArtist_Singles_Tracks_Number
Track_Duration_ms
f昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinTrack_Duration_ms
Track_Popularity
f昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinTrack_Popularity
acousticness
f昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinacousticness
danceability
f昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seindanceability
energy
f昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinenergy
instrumentalness
f昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seininstrumentalness
liveness
f昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinliveness
loudness
f昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinloudness
speechiness
f昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinspeechiness
tempo
f昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seintempo
valence
f昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinvalence
commentCount
f昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seincommentCount
dislikeCount
f昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seindislikeCount
likeCount
f昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinlikeCount
viewsCount
viewsCount

strictly_positive_variables <- c('Artist_Follower', 'Artist_Popularity', 'Artist_Singles_Number', 'Artist_Singles_Tracks_Number', 'Track_Duration_ms', 'Track_Popularity', 'acousticness', 'danceability', 'energy', 'instrumentalness', 'liveness', 'loudness', 'speechiness', 'tempo', 'valence', 'likeCount', 'viewsCount')

library("psych")
library("car")

ksD <- function (p, x) {
  y <- bcPower(x, p)
  ks.test(y, "pnorm", mean=mean(y), sd=sd(y))$statistic
}

oldw <- getOption("warn")
options(warn = -1)

min_values <- c()

for (column_index in 1:length(strictly_positive_variables)){
  
  column_name <- strictly_positive_variables[column_index]
  
  x_sub <- x[[paste(column_name)]]
  
  result <- optimize(ksD, c(-5,5), x=x_sub)
  
  min_values[column_index] <- result$minimum
  
  message(paste(column_index, ', minimum value is: ', result$minimum))
  
}
f昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden sein1 , minimum value is:  -0.0049316395173003
f昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden sein2 , minimum value is:  1.40215894049501
f昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden sein3 , minimum value is:  0.240525077020065
f昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden sein4 , minimum value is:  0.262671909258923
f昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden sein5 , minimum value is:  -0.699387491055179
f昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden sein6 , minimum value is:  0.949365177476973
f昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden sein7 , minimum value is:  0.130600084313768
f昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden sein8 , minimum value is:  3.51730755217668
f昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden sein9 , minimum value is:  3.07785781966221
f昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden sein10 , minimum value is:  -0.795345146822112
f昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden sein11 , minimum value is:  -0.303366878680729
f昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden sein12 , minimum value is:  4.64767393430379
f昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden sein13 , minimum value is:  -0.403363137393642
f昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden sein14 , minimum value is:  1.16379372652619
f昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden sein15 , minimum value is:  0.453314295028104
f昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden seinf昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden sein16 , minimum value is:  0.05466614868127
f昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden sein17 , minimum value is:  0.0345455532264414
options(warn = oldw)

column_index <- 1
column_name <- strictly_positive_variables[column_index]
x_sub <- x[[paste(column_name)]]
Artist_Follower_trans <- bcPower(x_sub, min_values[column_index])

column_index <- 2
column_name <- strictly_positive_variables[column_index]
x_sub <- x[[paste(column_name)]]
Artist_Popularity_trans <- bcPower(x_sub, min_values[column_index])

column_index <- 3
column_name <- strictly_positive_variables[column_index]
x_sub <- x[[paste(column_name)]]
Artist_Singles_Number_trans <- bcPower(x_sub, min_values[column_index])

column_index <- 4
column_name <- strictly_positive_variables[column_index]
x_sub <- x[[paste(column_name)]]
Artist_Singles_Tracks_Number_trans <- bcPower(x_sub, min_values[column_index])

column_index <- 5
column_name <- strictly_positive_variables[column_index]
x_sub <- x[[paste(column_name)]]
Track_Duration_ms_trans <- bcPower(x_sub, min_values[column_index])

column_index <- 6
column_name <- strictly_positive_variables[column_index]
x_sub <- x[[paste(column_name)]]
Track_Popularity_trans <- bcPower(x_sub, min_values[column_index])

column_index <- 7
column_name <- strictly_positive_variables[column_index]
x_sub <- x[[paste(column_name)]]
acousticness_trans <- bcPower(x_sub, min_values[column_index])

column_index <- 8
column_name <- strictly_positive_variables[column_index]
x_sub <- x[[paste(column_name)]]
danceability_trans <- bcPower(x_sub, min_values[column_index])

column_index <- 9
column_name <- strictly_positive_variables[column_index]
x_sub <- x[[paste(column_name)]]
energy_trans <- bcPower(x_sub, min_values[column_index])

column_index <- 10
column_name <- strictly_positive_variables[column_index]
x_sub <- x[[paste(column_name)]]
instrumentalness_trans <- bcPower(x_sub, min_values[column_index])

column_index <- 11
column_name <- strictly_positive_variables[column_index]
x_sub <- x[[paste(column_name)]]
liveness_trans <- bcPower(x_sub, min_values[column_index])

column_index <- 12
column_name <- strictly_positive_variables[column_index]
x_sub <- x[[paste(column_name)]]
loudness_trans <- bcPower(x_sub, min_values[column_index])

column_index <- 13
column_name <- strictly_positive_variables[column_index]
x_sub <- x[[paste(column_name)]]
speechiness_trans <- bcPower(x_sub, min_values[column_index])

column_index <- 14
column_name <- strictly_positive_variables[column_index]
x_sub <- x[[paste(column_name)]]
tempo_trans <- bcPower(x_sub, min_values[column_index])

column_index <- 15
column_name <- strictly_positive_variables[column_index]
x_sub <- x[[paste(column_name)]]
valence_trans <- bcPower(x_sub, min_values[column_index])

column_index <- 16
column_name <- strictly_positive_variables[column_index]
x_sub <- x[[paste(column_name)]]
likeCount_trans <- bcPower(x_sub, min_values[column_index])

column_index <- 17
column_name <- strictly_positive_variables[column_index]
x_sub <- x[[paste(column_name)]]
viewsCount_trans <- bcPower(x_sub, min_values[column_index])

hist_trans_list <- list(Artist_Follower_trans, 
                        Artist_Popularity_trans,
                        Artist_Singles_Number_trans,
                        Artist_Singles_Tracks_Number_trans,
                        Track_Duration_ms_trans, 
                        Track_Popularity_trans,
                     acousticness_trans, 
                     danceability_trans, 
                     energy_trans, 
                     instrumentalness_trans,
                     liveness_trans, 
                     loudness_trans, 
                     speechiness_trans, 
                     tempo_trans, 
                     valence_trans,
                     likeCount_trans,
                     viewsCount_trans)

par(mar = rep(2, 4))
par(mfrow=c(4,5))

for (trans_index in 1:length(hist_trans_list)){
  
  column_name <- strictly_positive_variables[trans_index]
  
  selected_trans <- hist_trans_list[trans_index]
  selected_trans <- as.numeric(as.character(unlist(selected_trans[[1]])))
  
  hist(selected_trans, col = "gray", probability = TRUE, main = column_name, xlab = "")
  points(seq(min(selected_trans), max(selected_trans), length.out = 500),
       dnorm(seq(min(selected_trans), max(selected_trans), length.out = 500),
             mean(selected_trans),sd(selected_trans)), type = "l", col = "red")
  
  test_statistic <- ks.test(selected_trans, "pnorm", mean=mean(selected_trans), sd=sd(selected_trans))$statistic
  critical_value <- 1.3581 / sqrt (length(selected_trans))
  
  if (test_statistic > critical_value) {
message(paste("Transformed ", column_name , " is not approximately normally distributed.", test_statistic, critical_value))
} else {
message(paste("Transformed ", column_name , " is approximately normally distributed!", test_statistic, critical_value))  
}}

NA
NA
NA

par(mar = rep(2, 4))

par(mfrow=c(4,5))

for(trans_index in 1:length(strictly_positive_variables)){
  
  
  column_name <- strictly_positive_variables[trans_index]
  
  selected_trans <- hist_trans_list[trans_index]
  selected_trans <- as.numeric(as.character(unlist(selected_trans[[1]])))
  
  df <- data.frame(selected_trans)
  names(df)[1] <- strictly_positive_variables[trans_index]
  
  boxplot(x = df[,1], main = strictly_positive_variables[trans_index], notch = FALSE)
  
}

NA
NA

Correlation



library(corrplot)

method <- "pearson"

clean_cor <- cor(x[,var_list], method = method)

cor.mtest <- function(mat, ...) {
  mat <- as.matrix(mat)
  n <- ncol(mat)
  p.mat<- matrix(NA, n, n)
  diag(p.mat) <- 0
  for (i in 1:(n - 1)) {
    for (j in (i + 1):n) {
      tmp <- cor.test(mat[, i], mat[, j], ..., method = method)
      p.mat[i, j] <- p.mat[j, i] <- tmp$p.value
    }
  }
  colnames(p.mat) <- rownames(p.mat) <- colnames(mat)
  p.mat
}

# matrix of the p-value of the correlation
p.mat <- cor.mtest(clean_cor)

col <- colorRampPalette(c("#BB4444", "#EE9988", "#FFFFFF", "#77AADD", "#4477AA"))
significance_level <- 0.05

corrplot(clean_cor, method="color", col=col(200),
         type="upper", order="alphabet", 
         addCoef.col = "black", # Add coefficient of correlation
         tl.col="black", tl.srt=90, #Text label color and rotation
         # Combine with significance
         p.mat = p.mat, sig.level = significance_level, insig = "blank", 
         # hide correlation coefficient on the principal diagonal
         diag=FALSE)

NA
NA
NA
NA

Steiger’s Z test


#

lm1 <- lm(x$Artist_Popularity ~ x$Artist_Follower)
summary(lm1)

Call:
lm(formula = x$Artist_Popularity ~ x$Artist_Follower)

Residuals:
    Min      1Q  Median      3Q     Max 
-39.796  -9.761   2.344  10.590  27.413 

Coefficients:
                   Estimate Std. Error t value Pr(>|t|)    
(Intercept)       6.268e+01  1.029e+00  60.900  < 2e-16 ***
x$Artist_Follower 1.216e-06  1.491e-07   8.154  4.3e-14 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 13.72 on 194 degrees of freedom
Multiple R-squared:  0.2552,    Adjusted R-squared:  0.2514 
F-statistic: 66.49 on 1 and 194 DF,  p-value: 4.298e-14
lm2 <- lm(Artist_Popularity_trans ~ Artist_Follower_trans)
summary(lm2)

Call:
lm(formula = Artist_Popularity_trans ~ Artist_Follower_trans)

Residuals:
    Min      1Q  Median      3Q     Max 
-92.767 -23.106  -7.232  23.326 121.213 

Coefficients:
                      Estimate Std. Error t value Pr(>|t|)    
(Intercept)           -108.514     12.039  -9.014   <2e-16 ***
Artist_Follower_trans   31.226      1.017  30.713   <2e-16 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 34.89 on 194 degrees of freedom
Multiple R-squared:  0.8294,    Adjusted R-squared:  0.8285 
F-statistic: 943.3 on 1 and 194 DF,  p-value: < 2.2e-16
par(mfrow=c(1,2))
plot(x$Artist_Follower, x$Artist_Popularity, main = paste("Untransformed, R^2=", round(summary(lm1)$r.squared, digits =2)), xlab = "Artist_Follower", ylab = "Artist_Popularity")
plot(Artist_Follower_trans, Artist_Popularity_trans, main = paste("Box-Cox transformed, R^2=", round(summary(lm2)$r.squared, digits =2)), xlab = "Artist_Follower", ylab = "Artist_Popularity")


bivariate_df_base <- data.frame(x$Artist_Follower, x$Artist_Popularity)
bivariate_df_bc <- data.frame(Artist_Follower_trans, Artist_Popularity_trans)

library("MVN")
MVN::mvn(bivariate_df_base, mvnTest = "mardia")$multivariateNormality # Not jointly normal
MVN::mvn(bivariate_df_base, mvnTest = "hz")$multivariateNormality # Not jointly normal
MVN::mvn(bivariate_df_base, mvnTest = "royston")$multivariateNormality # Not jointly normal
MVN::mvn(bivariate_df_base, mvnTest = "energy")$multivariateNormality # Jointly normal

MVN::mvn(bivariate_df_bc, mvnTest = "mardia")$multivariateNormality # Not jointly normal
MVN::mvn(bivariate_df_bc, mvnTest = "hz")$multivariateNormality # Not jointly normal
MVN::mvn(bivariate_df_bc, mvnTest = "royston")$multivariateNormality # Not jointly normal
MVN::mvn(bivariate_df_bc, mvnTest = "energy")$multivariateNormality # Jointly normal
NA

boxcox_df <- data.frame(matrix(vector(), nrow = 196, ncol = 17))
boxcox_df$Artist_Follower <- Artist_Follower_trans
boxcox_df$Artist_Popularity <- Artist_Popularity_trans
boxcox_df$Artist_Singles_Number <- Artist_Singles_Number_trans
boxcox_df$Artist_Singles_Tracks_Number <- Artist_Singles_Tracks_Number_trans
boxcox_df$Track_Duration_ms <- Track_Duration_ms_trans
boxcox_df$Track_Popularity <- Track_Popularity_trans
boxcox_df$acousticness <- acousticness_trans
boxcox_df$danceability <- danceability_trans
boxcox_df$energy <- energy_trans
boxcox_df$instrumentalness <- instrumentalness_trans
boxcox_df$liveness <- liveness_trans
boxcox_df$loudness <- loudness_trans
boxcox_df$speechiness <- speechiness_trans
boxcox_df$tempo <- tempo_trans
boxcox_df$valence <- valence_trans
boxcox_df$likeCount <- likeCount_trans
boxcox_df$viewsCount <- viewsCount_trans

drop.cols <- c('X1', 'X2', 'X3', 'X4', 'X5', 'X6', 'X7', 'X8', 'X9', 'X10', 'X11', 'X12', 'X13', 'X14', 'X15', 'X16', 'X17')

boxcox_df <- dplyr::select(boxcox_df, -one_of(drop.cols))

par(mfrow=c(1,1))

palette <- c('red', 'blue', 'black', 'purple')
# palette <- rainbow(length(levels(as.factor(x$Genre))))
my_colors <- palette[as.numeric(x$Genre)]

plot(Artist_Follower_trans, Artist_Popularity_trans, main = paste("Box-Cox transformed, R^2=", round(summary(lm2)$r.squared, digits =2)), xlab = "Artist_Follower", ylab = "Artist_Popularity", col = my_colors, pch = 16)
legend("bottomright", legend = levels(as.factor(x$Genre)), col = palette, pch = 16)


library("lattice")
xyplot(Artist_Popularity_trans~Artist_Follower_trans|x$Genre, pch=19, xlab = 'viewsCount (transformed)', ylab = 'Artist_Popularity (transformed)', main = "Biplot")


# pairs(boxcox_df)

sunflower_Artist_pop <- 2*round(boxcox_df$Artist_Popularity/2)
sunflower_viewsCount  <- 2*round(boxcox_df$viewsCount/2)
sunflowerplot(sunflower_Artist_pop~sunflower_viewsCount, xlab = 'viewsCount (transformed)', ylab = 'Artist_Popularity (transformed)', main = 'Sunflower plot')


require("MASS")
parcoord(boxcox_df, var.label = FALSE)



#library("lattice")
#parallelplot(boxcox_df, horizontal.axis=T)

#library("ggplot2")
#library("GGally")
#ggparcoord(boxcox_df) + geom_line()

library("andrews")
andrews(boxcox_df, ymax=7)


audio_features <- c('acousticness' , 'danceability' ,'energy', 'instrumentalness', 'liveness', 'loudness', 'speechiness', 'tempo', 'valence')

selected_columns <- dplyr::select(boxcox_df, audio_features)

parcoord(selected_columns , col= my_colors, var.label = FALSE, ... = par(las=2, mar = c(8,1,3,1), xpd=TRUE))
# title("Parallel coordinates", line = 0.6, adj = 0.1)
legend("topright", inset=c(0.035,-0.08),legend = c('Classic', 'Techno', 'Pop', 'Hip Hop'), col = unique(my_colors),
  bty = 'o', horiz = TRUE, pch = 15)


ax <- selected_columns
ax$clr <- x$Genre
andrews(ax, ymax=4, clr=10)
legend("topright",legend=levels(ax$clr), fill=rainbow(length(levels(as.factor(ax$clr)))))

library("scagnostics")

sub_df <- as.data.frame(x[, var_list])

s <- scagnostics(sub_df)
o <- scagnosticsOutliers(s)
g <- scagnosticsGrid(s)

go <- g[o,]

# Outliers: strongly different from the other plots

par(mfrow=c(1,1))


for (i in 1:nrow(go)){
plot(sub_df[[go[i,1]]], sub_df[[go[i,2]]], pch=19, xlab=names(sub_df)[go[i,1]], ylab=names(sub_df)[go[i,2]])
print(paste("Outlying pair: ", "y=", names(sub_df)[go[i,2]], "x=", names(sub_df)[go[i,1]]))}
[1] "Outlying pair:  y= Artist_Compilations_Number x= Artist_Albums_Number"

[1] "Outlying pair:  y= Artist_Compilations_Number x= Artist_Albums_Tracks_Number"

[1] "Outlying pair:  y= Artist_Compilations_Tracks_Number x= Artist_Albums_Number"

[1] "Outlying pair:  y= Artist_Compilations_Tracks_Number x= Artist_Compilations_Number"

[1] "Outlying pair:  y= Artist_Popularity x= Artist_Follower"

[1] "Outlying pair:  y= Artist_Singles_Number x= Artist_Albums_Number"

[1] "Outlying pair:  y= Artist_Singles_Number x= Artist_Compilations_Tracks_Number"

[1] "Outlying pair:  y= Artist_Singles_Tracks_Number x= Artist_Compilations_Tracks_Number"

[1] "Outlying pair:  y= Artist_Singles_Tracks_Number x= Artist_Singles_Number"

[1] "Outlying pair:  y= instrumentalness x= Track_Duration_ms"

[1] "Outlying pair:  y= loudness x= Artist_Albums_Tracks_Number"

[1] "Outlying pair:  y= valence x= instrumentalness"

[1] "Outlying pair:  y= dislikeCount x= Artist_Follower"

[1] "Outlying pair:  y= likeCount x= Artist_Follower"

[1] "Outlying pair:  y= likeCount x= commentCount"
# Highlights:

par(mfrow=c(1,2))

plot(x$Track_Duration_ms, x$instrumentalness, col = my_colors, pch = 16, xlab = 'Track_Duration_ms', ylab = 'instrumentalness')
plot(x$instrumentalness, x$valence, col = my_colors, pch = 16,  xlab = 'instrumentalness', ylab = 'valence')
legend(x=-1300, y=1250, legend = levels(as.factor(x$Genre)), col = palette, pch = 16, horiz = TRUE, xpd = NA)



# Exemplars: group of similar plots

e <- scagnosticsExemplars(s)

ge <- g[e,]

par(mfrow = c(2,2))


for (i in 1:dim(ge)[1]){
  plot(sub_df[[ge$x[i]]], sub_df[[ge$y[i]]], pch=19,
  xlab=names(sub_df)[ge$x[i]], ylab=names(sub_df)[ge$y[i]])
  print(paste("Exemplary pair: ", "y=", names(sub_df)[ge$y[i]], "x=", names(sub_df)[ge$x[i]]))
}
[1] "Exemplary pair:  y= Track_Popularity x= Artist_Appearances_Tracks_Number"
[1] "Exemplary pair:  y= energy x= Artist_Albums_Tracks_Number"
[1] "Exemplary pair:  y= likeCount x= Artist_Appearances_Number"
[1] "Exemplary pair:  y= viewsCount x= tempo"


table(x$Genre)

Classic Hip Hop     Pop  Techno 
     49      50      50      47 
x$Artist_Popularity_quantile <- 0

Artist_Popularity_quantiles <- quantile(x$Artist_Popularity, probs = c(0.25, 0.5, 0.75))

Artist_Pop_q_25 <- Artist_Popularity_quantiles[1]
Artist_Pop_median <- Artist_Popularity_quantiles[2]
Artist_Pop_q_75 <- Artist_Popularity_quantiles[3]

x$Artist_Popularity_quantile <- ifelse(x$Artist_Popularity < Artist_Pop_q_25, 1, x$Artist_Popularity_quantile + 0)

x$Artist_Popularity_quantile <- ifelse(((x$Artist_Popularity >= Artist_Pop_q_25) & (x$Artist_Popularity < Artist_Pop_median)), 2, x$Artist_Popularity_quantile + 0)

x$Artist_Popularity_quantile <- ifelse(((x$Artist_Popularity >= Artist_Pop_median) & (x$Artist_Popularity < Artist_Pop_q_75)), 3, x$Artist_Popularity_quantile + 0)

x$Artist_Popularity_quantile <- ifelse((x$Artist_Popularity >= Artist_Pop_q_75), 4, x$Artist_Popularity_quantile + 0)
x$Artist_Popularity_quantile <- as.factor(x$Artist_Popularity_quantile)

tab<-table(x$Genre, x$Artist_Popularity_quantile)
tab
         
           1  2  3  4
  Classic  9 21 13  6
  Hip Hop  0  8 25 17
  Pop      1  8 10 31
  Techno  37 10  0  0
# r = 4, c = 4
#df = (r-1)*(c-1) = 3+3 = 9
# critical value: 16.919


chisq.test(tab)

    Pearson's Chi-squared test

data:  tab
X-squared = 156.23, df = 9, p-value < 2.2e-16
chisq.test(tab, simulate.p.value = TRUE)

    Pearson's Chi-squared test with simulated p-value (based on 2000 replicates)

data:  tab
X-squared = 156.23, df = NA, p-value = 0.0004998
library("vcd")
assocstats(tab)
                    X^2 df P(> X^2)
Likelihood Ratio 168.67  9        0
Pearson          156.23  9        0

Phi-Coefficient   : NA 
Contingency Coeff.: 0.666 
Cramer's V        : 0.515 
#library("openintro")

#contTable(tab)

x$viewsCount_quantile <- 0

viewsCount_quantiles <- quantile(x$viewsCount, probs = c(0.25, 0.5, 0.75))

viewsCount_q_25 <- viewsCount_quantiles[1]
viewsCount_median <- viewsCount_quantiles[2]
viewsCount_q_75 <- viewsCount_quantiles[3]

x$viewsCount_quantile <- ifelse(x$viewsCount < viewsCount_q_25, 1, x$viewsCount_quantile + 0)

x$viewsCount_quantile <- ifelse(((x$viewsCount >= viewsCount_q_25) & (x$viewsCount < viewsCount_median)), 2, x$viewsCount_quantile + 0)

x$viewsCount_quantile <- ifelse(((x$viewsCount >= viewsCount_median) & (x$viewsCount < viewsCount_q_75)), 3, x$viewsCount_quantile + 0)

x$viewsCount_quantile <- ifelse((x$viewsCount >= viewsCount_q_75), 4, x$viewsCount_quantile + 0)
x$viewsCount_quantile <- as.factor(x$viewsCount_quantile)


tab<-table(x$viewsCount_quantile, x$Artist_Popularity_quantile)
tab
   
     1  2  3  4
  1 36  6  4  3
  2 10 19 12  8
  3  1 18 19 11
  4  0  4 13 32
chisq.test(tab)

    Pearson's Chi-squared test

data:  tab
X-squared = 133.34, df = 9, p-value < 2.2e-16
chisq.test(tab, simulate.p.value = TRUE)

    Pearson's Chi-squared test with simulated p-value (based on 2000 replicates)

data:  tab
X-squared = 133.34, df = NA, p-value = 0.0004998
library("vcd")
assocstats(tab)
                    X^2 df P(> X^2)
Likelihood Ratio 133.48  9        0
Pearson          133.34  9        0

Phi-Coefficient   : NA 
Contingency Coeff.: 0.636 
Cramer's V        : 0.476 
#contTable(tab)

ab2 <- na.omit(cbind(x$Artist_Popularity_quantile, x$viewsCount_quantile))
nrow(ab2)*(nrow(ab2)-1)/2
[1] 19110
#
ind <- order(ab2[,1], ab2[,2])
ab2 <- ab2[ind,]
#b
C <- D <- Tx <- Ty <- Txy <- 0
for (i in 1:(nrow(ab2)-1)) {
  if (i%%100==0) cat(i, "\n")
  for(j in (i+1):nrow(ab2)) {
    if (ab2[i,1]==ab2[j,1]) {
      if (ab2[i,2]==ab2[j,2]) {
        Txy <- Txy+1
      } else {
        Tx <- Tx+(ab2[i,2]<ab2[j,2])
      }
    } else {   
      if (ab2[i,2]==ab2[j,2]) Ty <- Ty+1
      if (ab2[i,2]<ab2[j,2]) C <- C+1
      if (ab2[i,2]>ab2[j,2]) D <- D+1
    }
  }
}
100 
c(C=C, D=D, Tx=Tx, Ty=Ty, Txy=Txy)
    C     D    Tx    Ty   Txy 
10048  1560  2798  2781  1923 
k_t <- (C - D)/(nrow(ab2)*(nrow(ab2)-1)/2)
k_t # (without ties)
[1] 0.4441654
library("ryouready")
ord.tau(table(ab2[,1], ab2[,2]))
Kendall's (and Stuart's) Tau statistics
    Tau-b: 0.590
    Tau-c: 0.589
cor(as.numeric(x$Artist_Popularity_quantile), as.numeric(x$viewsCount_quantile), method = "kendall") # identical result
[1] 0.5895469
cor.test(as.numeric(x$Artist_Popularity_quantile), as.numeric(x$viewsCount_quantile), method="kendall")

    Kendall's rank correlation tau

data:  as.numeric(x$Artist_Popularity_quantile) and as.numeric(x$viewsCount_quantile)
z = 9.8748, p-value < 2.2e-16
alternative hypothesis: true tau is not equal to 0
sample estimates:
      tau 
0.5895469 

modetab <- function(x, margin=0) { 
  if (margin>0) apply(x, margin, max) else max(x) 
}

tab2 <- table(x$Artist_Popularity_quantile, 
              x$Genre, dnn = c("Artist Popularity quantile", "Genre"))

tab2
                          Genre
Artist Popularity quantile Classic Hip Hop Pop Techno
                         1       9       0   1     37
                         2      21       8   8     10
                         3      13      25  10      0
                         4       6      17  31      0
contTable(tab2)
\begin{table}
\begin{center}
\begin{tabular}{lrrrrr}
\hline
 & Classic & Hip Hop & Pop & Techno & Total \\
\hline
1 &   9 &   0 &   1 &  37 &  47 \\
2 &  21 &   8 &   8 &  10 &  47 \\
3 &  13 &  25 &  10 &   0 &  48 \\
4 &   6 &  17 &  31 &   0 &  54 \\
\hline
Total &  49 &  50 &  50 &  47 & 196 \\
\hline
\end{tabular}
\end{center}
\caption{Contingency table for }
\label{}
\end{table}
tab <- rowSums(tab2)

message(paste("The mode across all four quantiles of stream distribution is", modetab(tab), " and is in the 4th quantile. So the best prediction for a new artist without further knowledge is that she will fall in the top quantile of the popularity distribution."))
The mode across all four quantiles of stream distribution is 54  and is in the 4th quantile. So the best prediction for a new artist without further knowledge is that she will fall in the top quantile of the popularity distribution.
e1_streams <- sum(tab) - modetab(tab)

message(paste("Without any knowledge about an additional feature other than artist popularity quantile itself and using the mode across classes as best predictor for class assignment for a new observation, the number of falsely predicted cases would be: ", e1_streams, " or ", e1_streams/sum(tab)*100, "%."))
Without any knowledge about an additional feature other than artist popularity quantile itself and using the mode across classes as best predictor for class assignment for a new observation, the number of falsely predicted cases would be:  142  or  72.4489795918367 %.
e2_streams <- sum(tab2) - sum(modetab(tab2, 2))

message(paste("Now having knowledge about an association between artist popularity and its genre and using class-internal modes for the additional feature, the number of falsely predicted cases would be: ", e2_streams, " or ", e2_streams/sum(tab)*100, "%, which is already much lower compared to the error rate from predicting without any knowledge about an association. On the other hand, ", sum(tab2)-e2_streams, " or ", (sum(tab2)-e2_streams)/sum(tab2)*100, "% would have been predicted correctly (compared to ", (1-e1_streams/sum(tab))*100, "% if there was no knowledge about genre.)"))
Now having knowledge about an association between artist popularity and its genre and using class-internal modes for the additional feature, the number of falsely predicted cases would be:  82  or  41.8367346938776 %, which is already much lower compared to the error rate from predicting without any knowledge about an association. On the other hand,  114  or  58.1632653061224 % would have been predicted correctly (compared to  27.5510204081633 % if there was no knowledge about genre.)
lambda <- (e1_streams - e2_streams)/e1_streams

message(paste("Therefore, Goodmann and Kruskals Lambda is: ", lambda))
Therefore, Goodmann and Kruskals Lambda is:  0.422535211267606
library("ryouready")

nom.lambda(tab2)
Lambda:
    Columns dependent: 0.438 
    Rows dependent: 0.423 
    Symmetric: 0.431 


modetab <- function(x, margin=0) { 
  if (margin>0) apply(x, margin, max) else max(x) 
}

tab2 <- table(x$Artist_Popularity_quantile, 
              x$viewsCount_quantile, dnn = c("Artist Popularity quantile", "viewsCount quantile"))

tab2
                          viewsCount quantile
Artist Popularity quantile  1  2  3  4
                         1 36 10  1  0
                         2  6 19 18  4
                         3  4 12 19 13
                         4  3  8 11 32
tab <- rowSums(tab2)

message(paste("The mode across all four quantiles of stream distribution is", modetab(tab), " and is in the 4th quantile. So the best prediction for a new artist without further knowledge is that she will fall in the top quantile of the popularity distribution."))
The mode across all four quantiles of stream distribution is 54  and is in the 4th quantile. So the best prediction for a new artist without further knowledge is that she will fall in the top quantile of the popularity distribution.
e1_streams <- sum(tab) - modetab(tab)

message(paste("Without any knowledge about an additional feature other than artist popularity quantile itself and using the mode across classes as best predictor for class assignment for a new observation, the number of falsely predicted cases would be: ", e1_streams, " or ", e1_streams/sum(tab)*100, "%."))
Without any knowledge about an additional feature other than artist popularity quantile itself and using the mode across classes as best predictor for class assignment for a new observation, the number of falsely predicted cases would be:  142  or  72.4489795918367 %.
e2_streams <- sum(tab2) - sum(modetab(tab2, 2))

message(paste("Now having knowledge about an association between artist popularity and its viewsCount quantile and using class-internal modes for the additional feature, the number of falsely predicted cases would be: ", e2_streams, " or ", e2_streams/sum(tab)*100, "%, which is already much lower compared to the error rate from predicting without any knowledge about an association. On the other hand, ", sum(tab2)-e2_streams, " or ", (sum(tab2)-e2_streams)/sum(tab2)*100, "% would have been predicted correctly (compared to ", (1-e1_streams/sum(tab))*100, "% if there was no knowledge about viewsCount quantile.)"))
Now having knowledge about an association between artist popularity and its viewsCount quantile and using class-internal modes for the additional feature, the number of falsely predicted cases would be:  90  or  45.9183673469388 %, which is already much lower compared to the error rate from predicting without any knowledge about an association. On the other hand,  106  or  54.0816326530612 % would have been predicted correctly (compared to  27.5510204081633 % if there was no knowledge about viewsCount quantile.)
lambda <- (e1_streams - e2_streams)/e1_streams

message(paste("Therefore, Goodmann and Kruskals Lambda is: ", lambda))
Therefore, Goodmann and Kruskals Lambda is:  0.366197183098592
library("ryouready")

nom.lambda(tab2)
Lambda:
    Columns dependent: 0.388 
    Rows dependent: 0.366 
    Symmetric: 0.377 

ANOVA


fit <- aov(Artist_Popularity~Genre , data =x)
summary(fit)
             Df Sum Sq Mean Sq F value Pr(>F)    
Genre         3  31032   10344   110.3 <2e-16 ***
Residuals   192  18006      94                   
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
ks.test(x$Artist_Popularity[x$Genre == 'Classic'], "pnorm", mean = mean (x$Artist_Popularity[x$Genre == 'Classic']) , sd=sd(x$Artist_Popularity[x$Genre == 'Classic']))$statistic
f昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden sein
         D 
0.06209392 
1.3581 / sqrt (length(x$Artist_Popularity[x$Genre == 'Classic']))
[1] 0.1940143
ks.test(x$Artist_Popularity[x$Genre == 'Techno'], "pnorm", mean = mean (x$Artist_Popularity[x$Genre == 'Techno']) , sd=sd(x$Artist_Popularity[x$Genre == 'Techno']))$statistic
f昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden sein
        D 
0.1108916 
1.3581 / sqrt (length(x$Artist_Popularity[x$Genre == 'Techno']))
[1] 0.1980992
ks.test(x$Artist_Popularity[x$Genre == 'Hip Hop'], "pnorm", mean = mean (x$Artist_Popularity[x$Genre == 'Hip Hop']) , sd=sd(x$Artist_Popularity[x$Genre == 'Hip Hop']))$statistic
f昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden sein
         D 
0.09990045 
1.3581 / sqrt (length(x$Artist_Popularity[x$Genre == 'Hip Hop']))
[1] 0.1920643
ks.test(x$Artist_Popularity[x$Genre == 'Pop'], "pnorm", mean = mean (x$Artist_Popularity[x$Genre == 'Pop']) , sd=sd(x$Artist_Popularity[x$Genre == 'Pop']))$statistic
f昼㹣r den Komogorov-Smirnov-Test sollten keine Bindungen vorhanden sein
        D 
0.1191381 
1.3581 / sqrt (length(x$Artist_Popularity[x$Genre == 'Pop']))
[1] 0.1920643
leveneTest (Artist_Popularity~Genre, data =x)
Levene's Test for Homogeneity of Variance (center = median)
       Df F value   Pr(>F)   
group   3  4.2258 0.006399 **
      192                    
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
varq <- tapply(x$Artist_Popularity, x$Genre, var, na.rm= TRUE)
varq/min(varq)
 Classic  Hip Hop      Pop   Techno 
1.522737 1.000000 2.293584 1.117978 

PCA


audio_features <- c('acousticness' , 'danceability' ,'energy', 'instrumentalness', 'liveness', 'loudness', 'speechiness', 'tempo', 'valence')
x_features <- dplyr::select(x, audio_features)
z <- scale(x_features)
eigen_cor <- eigen(cov(z))
E <- eigen_cor$vectors
pc_cor <- prcomp(z, center = F, scale = F)
plot(pc_cor, main="Scree plot as bar chart (cor)")

plot(pc_cor$sdev^2, type="b", main="Scree plot (cor)")


pc_index <- c("PC1", "PC2", "PC3", "PC4", "PC5", "PC6", "PC7", "PC8", "PC9")
eigen_values <- round(eigen_cor$values, digits = 2)
variance_explained <- round(eigen_cor$values/length(audio_features), digits = 2)
cum_variance <- round(cumsum(eigen_cor$values)/length(audio_features), digits = 2)
eigen_df <- data.frame(pc_index, eigen_values, variance_explained, cum_variance)
colnames(eigen_df) <- c("PC", "Eigen value", "Variance explained", "Cumulative variance explained")




as.data.frame(E)
E[,1] <- E[,1]*-1
E[,3] <- E[,3]*-1
E[,4] <- E[,4]*-1
E[,5] <- E[,5]*-1
E[,6] <- E[,6]*-1
as.data.frame(E)

scores <- z %*% E

library(corrplot)

col <- colorRampPalette(c("#BB4444", "#EE9988", "#FFFFFF", "#77AADD", "#4477AA"))

#par(mfrow=c(1,1))
#plot((pc_cor$sdev^2)/round(sum(pc_cor$sdev^2))*100, type="b", xlab = "Number of components", ylab = "Percentage of variance explained")

corrplot(pc_cor$rotation, method="color", col=col(200), 
        addCoef.col = "black", # Add coefficient of correlation
        tl.col="black", tl.srt=90, #Text label color and rotation
         diag=TRUE, mar = c(0,0,5,5), title = 'PCA - loadings matrix')





plot(scores[,1], scores[,2])

plot(pc_cor$x[,1], pc_cor$x[,2]) # both plots are identical


library("psych")
psych_pca <- psych::principal(z, nfactors = 9, rotate = "none")
scree(z)

# psych_pca$scores[,1]*sqrt(psych_pca$values[1]) # == scores[,1]

# transform loadings of psych::principal to unit-length eigenvectors according to 1/sqrt(a^2 + b^2) * (a,b)
# 1/sqrt(sum(psych_pca$loadings[,1]^2))*psych_pca$loadings[,1] # is identical to pc_cor$rotation[,1] and E[,1]

# transforming unit-length eigenvectors to loadings like in psych::principal:

manual_loadings <- matrix(nrow = dim(z)[2], ncol = dim(z)[2])

for (i in 1:dim(z)[2]){
  
  manual_loadings[,i] <- E[,i] * sqrt(eigen_cor$values[i])
  
}

# Still signs are interchanged, affected components are 3 and 9, change signs manually

manual_loadings[,3] <- manual_loadings[,3]*-1
manual_loadings[,9] <- manual_loadings[,9]*-1

# Scores in psych are computed according to 

(z %*% E[,1]) / sqrt(eigen_cor$values[1]) #  == psych_pca$scores[,1]
           [,1]
1   -1.59390372
2   -1.89244014
3   -1.56727583
4   -2.02403816
5   -1.34328173
6   -1.98652322
7   -1.65063718
8    0.23454238
9    0.33189660
10  -0.50527012
11   0.48474383
12   0.35548126
13   0.40438968
14   0.41389912
15   0.88932839
16   0.29545887
17   0.74966760
18  -0.33006285
19   0.42233800
20   0.60508283
21   0.35531678
22   1.08286743
23   0.33772362
24   0.39438136
25   0.38074682
26   0.53275627
27   0.35164160
28   0.89806447
29   0.34945558
30   0.79905861
31   0.64973543
32   0.79940254
33  -1.28729950
34   0.32285315
35   0.28938500
36   0.59057212
37  -0.91856390
38   0.54122847
39   0.77222028
40  -0.08719343
41   0.53776316
42   0.48802436
43   0.92409950
44   0.32211784
45   0.35080523
46   0.50370165
47   0.43417619
48   0.65562461
49   0.82640247
50   0.22221084
51   1.23872589
52  -0.27480596
53  -0.60837106
54   1.09983941
55   1.07217376
56   1.01232351
57   0.62184771
58   0.84979050
59   0.80091618
60   0.85471565
61   0.88031931
62   1.25090015
63   0.89078171
64   0.76487318
65   0.76159508
66   0.30109461
67   0.68030322
68   0.80407226
69   0.72002543
70   1.15107907
71   0.40045381
72   1.02252137
73   0.48945386
74   0.89961735
75   0.58542734
76   0.99733632
77   0.79627998
78   0.98885771
79   1.28630203
80   0.46651096
81   1.33907715
82   0.64998223
83   0.55201140
84   0.75942752
85   0.53261883
86   0.74383810
87   0.74165546
88   0.74866137
89   0.96277142
90   0.62715735
91   0.64793200
92   0.95334623
93   0.44942747
94   0.69025775
95   0.97104873
96   1.18362619
97   0.59019519
98  -0.39117191
99   0.40327243
100  0.99054825
101  0.86270002
102  0.67556803
103  1.01390165
104  1.36233335
105  0.81443559
106  1.12486562
107  0.70258335
108  0.10632817
109  0.99193014
110  0.75504621
111  0.65676336
112  0.33047902
113  0.95870755
114  0.75603911
115 -2.11696526
116 -1.74364531
117 -1.53213316
118 -1.94291687
119 -1.72239513
120 -1.36619922
121 -1.66305915
122 -2.06411957
123 -1.97969420
124 -1.73865879
125 -1.59092573
126 -1.75704042
127 -1.65120933
128 -0.90738230
129 -1.57915022
130 -1.79710843
131 -0.72000536
132 -1.11982385
133 -1.16126131
134 -1.26343254
135 -1.59056965
136 -1.19417247
137 -1.92762287
138 -1.75542132
139 -2.11166874
140 -1.75851617
141 -1.30171171
142 -1.64499033
143 -0.70384029
145 -2.22479894
146 -0.43936157
147 -1.98892924
148 -2.10319657
149  0.35090994
150  0.35960014
152  0.27783403
153  0.23520099
154  0.42816581
155  0.29415102
156  0.28883891
157  0.28901016
158  0.32561591
159  0.33618271
160  0.53796886
161  0.15721183
162  0.48276945
163  0.18837789
165  0.38861944
166  0.15693865
167  0.34641160
168  0.46655158
169  0.33666927
170  0.23963328
171  0.37607131
172  0.51586048
173 -0.01126555
174  0.40426506
175  0.23144730
176  0.40281787
177  0.20758467
178  0.24328905
179  0.06504826
180  0.25132125
181  0.18718854
183  0.39566848
184  0.49874147
185  0.39761257
186  0.59112779
187  0.52223745
188  0.34826820
189  0.17079406
190  0.22460558
191  0.18252664
192 -1.69078006
193 -1.32535523
194 -1.35844177
195 -1.97935120
196 -1.43855432
197 -1.63893155
198 -0.83876042
199 -1.32071392
200 -1.71405630
# i.e. using the unit-length eigenvectors and dividing them by the square root of the corresponding eigenvalue

par(mfrow=c(1,2))

biplot(pc_cor) # loadings plot with loadings scaled to fit in same graph with scores
plot(pc_cor$rotation[, 1:2]) # loadings in original measures
text(pc_cor$rotation[, 1:2], as.character(colnames(z)), pos = 1, cex = 0.7, offset = 0.1)

#palette <- rainbow(length(levels(as.factor(x$Genre))))
#my_colors <- palette[as.numeric(x$Genre)]

palette <- c('red', 'blue', 'black', 'purple')
# palette <- rainbow(length(levels(as.factor(x$Genre))))
my_colors <- palette[as.numeric(x$Genre)]

par(mfrow=c(1,1))

plot(scores[, 1:2], col = my_colors, pch = 19, xlab = "Scores PC1 (47.84% expl. variance)", ylab = "Scores PC2 (16.25% expl. variance)")
legend("topleft", legend = levels(as.factor(x$Genre)), col = palette, pch = 16, horiz = T)
#plot(pc_cor$rotation[, 1:2])
par(mfrow=c(1,1))

plot(E[, 1:2])
text(E[, 1:2], as.character(colnames(z)), pos = 1, cex = 0.7, offset = 0.1)

cumsum(eigen_cor$values)/length(audio_features)
[1] 0.4783993 0.6408610 0.7496500 0.8399854 0.9171536 0.9507038 0.9754113 0.9910233 1.0000000
# According to the 90% criterion there would be 5 components needed to appropriately retain the variance in the data by reducing the feature space from nine to five.

RMSE = function(m, o){
  
  sqrt(mean((m - o)^2))
  
}

# All expressions for the scores are identical

scores <- z %*% E
scores_from_psych <- (z %*% (1/sqrt(sum(psych_pca$loadings[,1]^2))*psych_pca$loadings[,1])) / sqrt(psych_pca$values[1]) # == psych_pca$scores[,1]
scores_from_princomp <- (z %*% pc_cor$rotation[,1]) / sqrt(eigen_cor$values[1])

fits <- matrix(nrow = dim(z)[2], ncol = 3)

for (i in 1:dim(z)[2]){
  
  num_comp <- i
  fit <- scores[, 1:num_comp] %*% t(E[, 1:num_comp])
  residuals <- z - fit
  rmse <- RMSE(z, fit)
  r2 <- 1-(sum(diag(var(residuals)))/ sum(diag(var(z))))
  
  fits[i,1] <- num_comp
  fits[i,2] <- rmse
  fits[i,3] <- r2
  
}

par(mfrow=c(1,1))

plot(fits[,1], fits[,2], type = "l", ylab = 'RMSE', xlab = 'Number of components')
par(new = TRUE)
plot(fits[,1], fits[,3], type = "l", axes = FALSE, bty = "n", xlab = "", ylab = "")
axis(side=4, at = pretty(range(fits[,3])))
mtext("R^2", side = 4, line = -1)
abline(v = 2, lty = 2, col = 'green')
mtext("Kaiser criterion", side = 1, line = 2, adj = 0.1)
abline(v=min(which(fits[,3]>=0.9)), lty = 2, col = 'red')
mtext("90% rule", side = 1, line = 1, adj = 0.5)

# Squared prediction errors / Q-residuals

par(mfrow=c(1,1))


a <- 1
Xhat <- pc_cor$x[,seq(1,a)] %*% t(pc_cor$rotation[,seq(1,a)])
RMSE(z, Xhat)
[1] 0.7203745
res <- z - Xhat
E2 <- res * res
SPE_1 <- apply(E2, 1, sum) # not using sqrt(apply(E2, 1, sum))!

# Box (1954) method: weighted Chi-squared

v <- var(SPE_1)
m <- mean(SPE_1)
h <- (2*m^2)/v
g <- v / (2*m)
lim_k1_95 <- g*qchisq(.95, df=h) 
plot(SPE_1, ylab = paste('SPE after using', a, 'component'), xlab = 'index')
abline(h = lim_k1_95, lty = 2, col = 'darkgreen')
mtext(paste('RMSE =', round(sqrt(mean(SPE_1)), digits = 4)), side=1, line=3.5, at=9)

sqrt(mean(SPE_1))
[1] 2.161124
a <- 2
Xhat <- pc_cor$x[,seq(1,a)] %*% t(pc_cor$rotation[,seq(1,a)])
RMSE(z, Xhat)
[1] 0.5977514
res <- z - Xhat
E2 <- res * res
#SPE_2 <- sqrt(apply(E2, 1, sum))
SPE_2 <- apply(E2, 1, sum)
# pca(z, ncomp = a, center = FALSE, scale = FALSE, alpha = 0.05)$Qlim[1]

v <- var(SPE_2)
m <- mean(SPE_2)
h <- (2*m^2)/v
g <- v / (2*m)
lim_k2_95 <- g*qchisq(.95, df=h) 
plot(SPE_2, ylab = paste('SPE after using', a, 'components'), xlab = 'index')
abline(h = lim_k2_95, lty = 2, col = 'darkgreen')
mtext(paste('RMSE =', round(sqrt(mean(SPE_2)), digits = 4)), side=1, line=3.5, at=9)

sqrt(mean(SPE_2))
[1] 1.793254
a <- 9
Xhat <- pc_cor$x[,seq(1,a)] %*% t(pc_cor$rotation[,seq(1,a)])
RMSE(z, Xhat)
[1] 9.521473e-16
res <- z - Xhat
E2 <- res * res
# SPE_9 <- sqrt(apply(E2, 1, sum))
SPE_9 <- apply(E2, 1, sum)
# pca(z, ncomp = a, center = FALSE, scale = FALSE, alpha = 0.05)$Qlim[1]

v <- var(SPE_9)
m <- mean(SPE_9)
h <- (2*m^2)/v
g <- v / (2*m)
lim_k9_95 <- g*qchisq(.95, df=h) 
plot(SPE_9, ylab = paste('SPE after using', a, 'components'), xlab = 'index')
abline(h = lim_k9_95, lty = 2, col = 'darkgreen')
mtext(paste('RMSE =', round(sqrt(mean(SPE_9)), digits = 4)), side=1, line=3.5, at=9)
sqrt(mean(SPE_9))
[1] 2.856442e-15
par(mfrow=c(3,1))


plot(SPE_1, type = 'l', col = 'darkgreen', ylab = "SPE for PC1")
abline(h = lim_k1_95, lty = 2, col = 'darkgreen')
plot(SPE_2, type = 'l', col = 'red', ylab = "SPE for PC1 + PC2")
abline(h = lim_k2_95, lty = 2, col = 'darkgreen')
plot(SPE_9, type = 'l', col = 'blue', ylab = "SPE for all PCs")
abline(h = lim_k9_95, lty = 2, col = 'darkgreen')


sum(SPE_1 > lim_k1_95)
[1] 6
sum(SPE_1 > lim_k1_95)/length(SPE_1)
[1] 0.03061224
sum(SPE_2 > lim_k2_95)
[1] 7
sum(SPE_2 > lim_k2_95)/length(SPE_2)
[1] 0.03571429
sum(SPE_9 > lim_k9_95)
[1] 10
sum(SPE_9 > lim_k9_95)/length(SPE_9)
[1] 0.05102041

Hotelling’s T^2



num_comp <- 2

inverse_cov <- diag(eigen_cor$values[1:num_comp], nrow = num_comp, ncol = num_comp)^-1
inverse_cov[is.infinite(inverse_cov)] <- 0

tsquared <- diag(z %*% E[,1:num_comp] %*% inverse_cov %*% t(z %*% E[,1:num_comp]))

# which is equivalent to the Mahalanobis distance: (for k > 1)

tsquared_mahal <- mahalanobis(scores[,1:num_comp], center = FALSE, cov(cbind(scores[,1], scores[,num_comp])), inverted = FALSE)
tsquared_lim95 <- (((dim(z)[1] - 1) * (dim(z)[1] + 1) * num_comp) / (dim(z)[1] * (dim(z)[1] - num_comp))) * qf(p =.95, df1 = num_comp, df2 = dim(z)[1] - num_comp)
tsquared_lim99 <- (((dim(z)[1] - 1) * (dim(z)[1] + 1) * num_comp) / (dim(z)[1] * (dim(z)[1] - num_comp))) * qf(p =.99, df1 = num_comp, df2 = dim(z)[1] - num_comp)

par(mfrow=c(1,1))

plot(tsquared, type = 'l', ylim = c(0,tsquared_lim99*1.1), ylab = "Hotelling's T2")
abline(h=tsquared_lim99, col = 'red', lty = 2)
text(190,10, "99% limit", col = "red")
abline(h=tsquared_lim95, col = 'darkgreen', lty = 2)
text(190,6.6, "95% limit", col = "darkgreen")


# source: https://github.com/hredestig/pcaMethods/blob/master/R/pca.R

simpleEllipse <- function(x, y, alfa=0.95, len=200) {
  N <- length(x)
  A <- 2
  mypi <- seq(0, 2 * pi, length=len)
  r1 <- sqrt(var(x) * qf(alfa, 2, N - 2) * (2*(N^2 - 1)/(N * (N - 2))))
  r2 <- sqrt(var(y) * qf(alfa, 2, N - 2) * (2*(N^2 - 1)/(N * (N - 2))))
  cbind(r1 * cos(mypi) + mean(x), r2 * sin(mypi) + mean(y))
}


confidence_ellipse95 <- simpleEllipse(scores[,1], scores[,2], alfa = 0.95, len=500)
confidence_ellipse99 <- simpleEllipse(scores[,1], scores[,2], alfa = 0.99, len=500)

plot(scores[,1], scores[,2], xlim = c(min(confidence_ellipse99[,1]), max(confidence_ellipse99[,1])), 
     ylim = c(min(confidence_ellipse99[,2]), max(confidence_ellipse99[,2])), ylab = 'Scores PC2', xlab = 'Scores PC1')
abline(h = 0, v = 0)
points(confidence_ellipse95, type = 'l', lty = 2, col = 'darkgreen')
points(confidence_ellipse99, type = 'l', lty = 2, col = 'red')


x[as.numeric(names(tsquared[tsquared > tsquared_lim95])),c('Track_Title', 'Track_Artist', 'Genre')]

outlier_tracks <- x[as.numeric(names(tsquared[tsquared > tsquared_lim95])),c('Track_Title', 'Track_Artist', 'Genre')]
round(z[as.numeric(names(tsquared[tsquared > tsquared_lim95])),], digits = 2)
    acousticness danceability energy instrumentalness liveness loudness speechiness tempo valence
21          0.88         0.21  -0.51            -0.90    -0.23    -0.04        4.30 -0.95    1.08
146         0.35         0.49  -0.79             1.26     2.13    -0.20       -0.61 -0.41   -0.68
audio_z <- as.data.frame(z[as.numeric(names(tsquared[tsquared > tsquared_lim95])),])
outlier_tracks$acousticness <- audio_z$acousticness
outlier_tracks$danceability <- audio_z$danceability
outlier_tracks$energy <- audio_z$energy
outlier_tracks$instrumentalness <- audio_z$instrumentalness
outlier_tracks$liveness <- audio_z$liveness
outlier_tracks$loudness <- audio_z$loudness
outlier_tracks$speechiness <- audio_z$speechiness
outlier_tracks$tempo <- audio_z$tempo
outlier_tracks$valence <- audio_z$valence

# Finally, combining residuals and scores (i.e. Hotelling's T^2)

# cumsum(pc_cor$sdev^2 / sum(pc_cor$sdev^2))[num_comp]

plot(tsquared, SPE_2, 
     xlab = paste("Hotelling's T2 (", round(cumsum(pc_cor$sdev^2 / sum(pc_cor$sdev^2))[num_comp]*100, digits = 2), "%)"),
     ylab = paste("Q-residuals (", round((1-cumsum(pc_cor$sdev^2 / sum(pc_cor$sdev^2))[num_comp])*100, digits = 2), "%)"))
abline(h = lim_k2_95, lty = 2, col = 'darkgreen') # seven residual outliers
abline(v = tsquared_lim95, lty = 2, col = 'darkgreen') #  two score outliers


library("robustbase")
out <- adjOutlyingness(z, ndir=5000, clower=0, cupper=0)
hist(out$adjout)
rug(out$adjout)

z[!out$nonOut,]#  as we can confirm, the outlying cases differ strongly in their characteristics from their respective                         centers, that is especially liveness and energy are inconsistent with the model
    acousticness danceability     energy instrumentalness   liveness   loudness speechiness       tempo    valence
15  -0.094467802  0.305088118  0.4781728       -0.9049362  4.3284375  0.5324131  -0.3911434  1.11569104  1.3435281
21   0.882322256  0.212927329 -0.5076300       -0.9049323 -0.2300724 -0.0352499   4.3016000 -0.94770231  1.0815371
51  -0.725356105  0.292520737  1.1679094       -0.9049362  7.1823764  0.8712256  -0.5411446 -0.42960905  1.6680842
66  -0.067632910 -0.172472333 -0.1725221       -0.9049362 -0.4141660  0.5886878   3.4161767 -0.96530254 -0.5998981
68   0.436863054 -0.440576446  0.5399888       -0.9049362 -0.3985814  0.7324146   3.4578437  2.23014470  0.3190258
88  -0.008596148  0.439140174 -0.1725221       -0.9047249 -0.3469572  0.2811821   3.4995106  0.75786761  0.7335191
96  -0.561394917  1.507367499  0.8197876       -0.9042543  4.8251982  0.5040817   1.4994957 -0.06945090  0.1156894
105 -0.448688372 -0.000718136  0.3968359       -0.9049362 -0.1813703  0.7114572   3.5515944  0.02013067  0.2290886
115  1.427070557 -2.111619112 -1.8571713       -0.1679945 -0.7385215 -2.8377300  -0.5098943 -1.52344531 -1.1692398
140  1.588079907 -1.818799151 -1.6889666        0.5378426  3.1693292 -1.7704512  -0.5848949 -1.98956402 -1.0077438
145  1.419020090 -1.709881855 -1.8773428       -0.9048587 -0.3469572 -4.0775846  -0.6432287 -1.63411123 -1.1700219
177 -0.633849124  0.845485470  0.2634435        1.2819623  2.5069816 -0.2098955   0.2494863  0.64759679 -0.9600380
x[rownames(z[!out$nonOut,]),c("Track_Title", "Track_Artist", "Genre", "Track_Popularity")]

library("paran")
paran(z, centile=95, all=T, graph=T)

Using eigendecomposition of correlation matrix.
Computing: 10%  20%  30%  40%  50%  60%  70%  80%  90%  100%


Results of Horn's Parallel Analysis for component retention
270 iterations, using the 95 centile estimate

-------------------------------------------------- 
Component   Adjusted    Unadjusted    Estimated 
            Eigenvalue  Eigenvalue    Bias 
-------------------------------------------------- 
1           3.869575    4.305594      0.436018
2           1.167162    1.462154      0.294991
3           0.784392    0.979100      0.194708
4           0.696123    0.813018      0.116894
5           0.656627    0.694514      0.037886
6           0.333946    0.301951     -0.03199
7           0.320340    0.222367     -0.09797
8           0.303919    0.140507     -0.16341
9           0.319357    0.080790     -0.23856
-------------------------------------------------- 

Adjusted eigenvalues > 1 indicate dimensions to retain.
(2 components retained)

x$pc_1 <- pc_cor$x[,1]
x$pc_2 <- pc_cor$x[,2]

x$Release_Date <- as.Date(paste(x$Release_Date, 1, 1, sep = "-"))
x$days_release_orig <- as.integer(round(difftime('2020-03-01', x$Release_Date, units = "days"), digits = 0))
x$Release_Date <- NULL
x$days_release <- as.numeric(scale(x$days_release_orig))


drop.cols <- c('Artist_ID', 'Genre', 'Track_Artist','Track_ID', 'Track_Title', 'key',
               'mode', 'time_signature', 'video_ID', 'Charts', 'acousticness', 'danceability', 'energy', 'instrumentalness',
               'liveness', 'loudness', 'speechiness', 'tempo', 'valence', 'Artist_Popularity', 'Track_Popularity', 'Artist_Popularity_quantile', 'viewsCount_quantile', 'days_release_orig', 'pc_1', 'pc_2')

x_sel <- dplyr::select(x, -one_of(drop.cols))
complete_index <- as.numeric(rownames(x_sel))

z <- scale(x_sel)

# inverse and partial correlations
p  <- solve(cor(z, use="complete.obs"))

# if the model holds then the non-diagonal elements of Rô€€€1
# must be close to zero (relative to the diagonal element)

levelplot(p, scales=list(x=list(rot=90)))


# Partial correlations
pr <- -p/sqrt(outer(diag(p), diag(p)))
levelplot(pr, scales=list(x=list(rot=90)))


KMO(z)
Kaiser-Meyer-Olkin factor adequacy
Call: KMO(r = z)
Overall MSA =  0.74
MSA for each item = 
             Artist_Albums_Number       Artist_Albums_Tracks_Number         Artist_Appearances_Number  Artist_Appearances_Tracks_Number 
                             0.81                              0.70                              0.56                              0.53 
       Artist_Compilations_Number Artist_Compilations_Tracks_Number                   Artist_Follower             Artist_Singles_Number 
                             0.71                              0.69                              0.71                              0.78 
     Artist_Singles_Tracks_Number                 Track_Duration_ms                      commentCount                      dislikeCount 
                             0.60                              0.70                              0.85                              0.84 
                        likeCount                        viewsCount                      days_release 
                             0.80                              0.85                              0.65 
# result: 0.74 overall: middling, every individual MSA value is as least as high as 0.53 (middling)

cortest.bartlett(z) # result: correlation matrix is NOT an identity matrix, so proceed with factor analysis
R was not square, finding R from data
$chisq
[1] 3167.088

$p.value
[1] 0

$df
[1] 105
scree(z)


library("paran")
paran(z, centile=95, all=T, graph=T) # four factors are appropriate

Using eigendecomposition of correlation matrix.
Computing: 10%  20%  30%  40%  50%  60%  70%  80%  90%  100%


Results of Horn's Parallel Analysis for component retention
450 iterations, using the 95 centile estimate

-------------------------------------------------- 
Component   Adjusted    Unadjusted    Estimated 
            Eigenvalue  Eigenvalue    Bias 
-------------------------------------------------- 
1           3.974762    4.590215      0.615452
2           3.295978    3.761880      0.465901
3           1.658319    2.026943      0.368624
4           1.057991    1.333118      0.275126
5           0.747975    0.955038      0.207062
6           0.506347    0.649312      0.142965
7           0.520244    0.598370      0.078126
8           0.357187    0.377677      0.020490
9           0.271482    0.245019     -0.02646
10          0.261008    0.177595     -0.08341
11          0.238168    0.106222     -0.13194
12          0.285147    0.096959     -0.18818
13          0.277766    0.036315     -0.24145
14          0.321604    0.025889     -0.29571
15          0.382354    0.019441     -0.36291
-------------------------------------------------- 

Adjusted eigenvalues > 1 indicate dimensions to retain.
(4 components retained)

Loadings


pca <- principal(z, nfactors=4, rotate="none") # h2 are "communalities"
pa <- fa(z, nfactors=4, rotate="none", fm="pa") # principal axis extraction
uls <- fa(z, nfactors=4, rotate="none")
ml <- fa(z, nfactors=4, rotate="none", fm="ml")
library("xtable")
# xtable(unclass(ml$loadings))
print(ml$loadings, sort = T)

Loadings:
                                  ML2    ML3    ML1    ML4   
commentCount                       0.965        -0.144       
dislikeCount                       0.968        -0.161       
likeCount                          0.980        -0.145       
viewsCount                         0.943        -0.108       
Artist_Albums_Number                      0.750  0.503       
Artist_Albums_Tracks_Number               0.638  0.232  0.587
Artist_Compilations_Number                0.876  0.365       
Artist_Compilations_Tracks_Number         0.896  0.408       
Artist_Singles_Number                     0.358  0.685 -0.206
Artist_Singles_Tracks_Number             -0.105  0.988       
Artist_Appearances_Number                        0.212  0.870
Artist_Appearances_Tracks_Number                 0.231  0.551
Artist_Follower                    0.335                     
Track_Duration_ms                                0.268  0.375
days_release                              0.384  0.106  0.392

                 ML2   ML3   ML1   ML4
SS loadings    3.879 2.849 2.315 1.756
Proportion Var 0.259 0.190 0.154 0.117
Cumulative Var 0.259 0.449 0.603 0.720
print(ml$loadings, cutoff=.4, sort = T)

Loadings:
                                  ML2    ML3    ML1    ML4   
commentCount                       0.965                     
dislikeCount                       0.968                     
likeCount                          0.980                     
viewsCount                         0.943                     
Artist_Albums_Number                      0.750  0.503       
Artist_Albums_Tracks_Number               0.638         0.587
Artist_Compilations_Number                0.876              
Artist_Compilations_Tracks_Number         0.896  0.408       
Artist_Singles_Number                            0.685       
Artist_Singles_Tracks_Number                     0.988       
Artist_Appearances_Number                               0.870
Artist_Appearances_Tracks_Number                        0.551
Artist_Follower                                              
Track_Duration_ms                                            
days_release                                                 

                 ML2   ML3   ML1   ML4
SS loadings    3.879 2.849 2.315 1.756
Proportion Var 0.259 0.190 0.154 0.117
Cumulative Var 0.259 0.449 0.603 0.720
set.seed(42)
ml.varimax <- fa(z, nfactors=4, rotate="varimax", fm="ml") 
print(ml.varimax$loadings, cutoff=.4, sort = T)

Loadings:
                                  ML2    ML3    ML4    ML1   
commentCount                       0.974                     
dislikeCount                       0.979                     
likeCount                          0.989                     
viewsCount                         0.950                     
Artist_Albums_Number                      0.857              
Artist_Compilations_Number                0.948              
Artist_Compilations_Tracks_Number         0.976              
Artist_Albums_Tracks_Number               0.619  0.643       
Artist_Appearances_Number                        0.898       
Artist_Appearances_Tracks_Number                 0.587       
Artist_Singles_Number                     0.555         0.579
Artist_Singles_Tracks_Number                            0.955
Artist_Follower                                              
Track_Duration_ms                                0.430       
days_release                                     0.417       

                 ML2   ML3   ML4   ML1
SS loadings    3.922 3.457 2.015 1.404
Proportion Var 0.261 0.230 0.134 0.094
Cumulative Var 0.261 0.492 0.626 0.720
# xtable(unclass(ml.varimax$loadings))

fa.congruence(ml, ml.varimax)
      ML2   ML3   ML4   ML1
ML2  1.00 -0.07 -0.09 -0.01
ML3 -0.01  0.98  0.33  0.17
ML1 -0.20  0.69  0.45  0.89
ML4 -0.03  0.14  0.96 -0.09
par(mfrow=c(1,1))

threshold <- 0.4
plot(ml.varimax$loadings[,1], ml.varimax$loadings[,2], xlim = c(-1, 1), ylim = c(-1,1.1), xlab = 'ML2', ylab = 'ML3')
palette <- c('red', 'blue')
col_index <- ifelse((abs(ml.varimax$loadings[,1]) > threshold) | (abs(ml.varimax$loadings[,2]) > threshold), 1, 0)
cols <- palette[as.numeric(as.factor(col_index))]
points(ml.varimax$loadings[,1], ml.varimax$loadings[,2], pch=19, col=cols)
abline(h = 0, v= 0)
text(ml.varimax$loadings[,1:2], as.character(rownames(ml.varimax$loadings)), pos = 1, cex = 0.6, offset = 0.5)
rect(xleft = -threshold, ybottom = -threshold, xright = threshold, ytop = threshold)
# fa.diagram(ml.varimax, simple=TRUE, cut=.2, digits=2)
set.seed(42)
ml.promax <- fa(z, nfactors=4, rotate="promax", fm="ml")
fa.congruence(ml.promax, ml.varimax)
      ML2   ML3   ML4   ML1
ML2  1.00 -0.01 -0.03 -0.08
ML3 -0.01  0.99  0.19  0.29
ML4  0.00  0.09  0.97  0.10
ML1 -0.09  0.24  0.12  0.99
ml.promax$Phi
            ML2        ML3        ML4         ML1
ML2  1.00000000 -0.1234826 -0.1397866  0.07121945
ML3 -0.12348257  1.0000000  0.3651684  0.16587388
ML4 -0.13978664  0.3651684  1.0000000 -0.00887250
ML1  0.07121945  0.1658739 -0.0088725  1.00000000
print(ml.promax$loadings, cutoff=.4, sort = T)

Loadings:
                                  ML2    ML3    ML4    ML1   
commentCount                       0.984                     
dislikeCount                       0.990                     
likeCount                          0.999                     
viewsCount                         0.962                     
Artist_Albums_Number                      0.860              
Artist_Albums_Tracks_Number               0.552  0.545       
Artist_Compilations_Number                0.999              
Artist_Compilations_Tracks_Number         1.018              
Artist_Appearances_Number                        0.940       
Artist_Appearances_Tracks_Number                 0.643       
Artist_Singles_Number                     0.535         0.541
Artist_Singles_Tracks_Number                            0.956
Artist_Follower                                              
Track_Duration_ms                                0.436       
days_release                                                 

                 ML2   ML3   ML4   ML1
SS loadings    3.989 3.519 2.033 1.346
Proportion Var 0.266 0.235 0.136 0.090
Cumulative Var 0.266 0.501 0.636 0.726
#xtable(unclass(ml.promax$loadings))

print(ml.promax$Structure, cutoff = 0.4)

Loadings:
                                  ML2    ML3    ML4    ML1   
Artist_Albums_Number                      0.889              
Artist_Albums_Tracks_Number               0.721  0.744       
Artist_Appearances_Number                        0.891       
Artist_Appearances_Tracks_Number                 0.565       
Artist_Compilations_Number                0.941              
Artist_Compilations_Tracks_Number         0.980              
Artist_Follower                                              
Artist_Singles_Number                     0.582         0.632
Artist_Singles_Tracks_Number                            0.958
Track_Duration_ms                                0.449       
commentCount                       0.974                     
dislikeCount                       0.979                     
likeCount                          0.989                     
viewsCount                         0.947                     
days_release                              0.426  0.477       

                 ML2   ML3   ML4   ML1
SS loadings    3.969 3.890 2.427 1.548
Proportion Var 0.265 0.259 0.162 0.103
Cumulative Var 0.265 0.524 0.686 0.789
# For orthoghonal rotations: cor(item, factor) = loadings ("pattern") matrix = structure matrix, 
# since structure matrix = loadings matrix *%* factor intercorrelation matrix (which is identity for orthogonal rotations)
# example: ml.promax$loadings %*% ml.promax$Phi = structure matrix


par(mfrow=c(1,1))


threshold <- 0.5
plot(ml.promax$Structure[,1], ml.promax$Structure[,2], xlim = c(-1, 1), ylim = c(-1,1.1), xlab = 'ML2', ylab = 'ML3')
palette <- c('red', 'blue')
col_index <- ifelse((abs(ml.promax$Structure[,1]) > threshold) | (abs(ml.promax$Structure[,2]) > threshold), 1, 0)
cols <- palette[as.numeric(as.factor(col_index))]
points(ml.promax$Structure[,1], ml.promax$Structure[,2], pch=19, col=cols)
abline(h = 0, v= 0)
text(ml.promax$Structure[,1:2], as.character(rownames(ml.promax$Structure)), pos = 1, cex = 0.6, offset = 0.5)
rect(xleft = -threshold, ybottom = -threshold, xright = threshold, ytop = threshold)




fa1 <-factanal(z, factors=4, scores = 'regression', lower = 0.1) # ML with Kaiser normalization 
head(fa1$scores)
      Factor1   Factor2    Factor3    Factor4
1 -0.09032514 4.7229399 -1.1982826  1.1913023
2 -0.06532926 1.9592530  0.6796187  1.9294387
3 -0.17155114 1.2161697  2.5043243 -0.6176557
4 -0.14220859 4.8213406 -1.0227099  0.2499034
5 -0.15666866 6.0158376 -1.7644434 -0.1556914
6 -0.16905143 0.1190256  2.0571621 -0.1523273
fa2 <- fa(z, nfactors=4) # oblimin rotation without Kaiser normalization
The estimated weights for the factor scores are probably incorrect.  Try a different factor score estimation method.
head(fa2$scores)
         MR2       MR1        MR3        MR4
1 -0.1257348 4.9076402 -1.4064326  3.2185555
2 -0.2186062 2.3024182  0.7123318  2.1407078
3 -0.2078290 1.3247000  2.3948606 -0.3356255
4 -0.1849004 4.2448612  0.0405285  1.8486009
5 -0.2345250 5.4651211 -0.7047564  1.0788027
6 -0.2682063 0.3496226  1.8536951  0.1795223
cor(fa1$scores, fa2$scores)
                MR2         MR1         MR3        MR4
Factor1  0.99066727 -0.04263156 -0.03183455 0.02879820
Factor2 -0.02300522  0.98601395  0.12472840 0.27124108
Factor3 -0.03546517  0.11316250  0.97914604 0.09737382
Factor4 -0.05458080  0.13298949 -0.03618965 0.93878256
cor(fa1$scores, ml.promax$scores)
                 ML2         ML3         ML4         ML1
Factor1  0.995994444 -0.06746742 -0.08311111 0.060236052
Factor2 -0.051288886  0.97618626  0.22252324 0.086347208
Factor3 -0.056169205  0.17796182  0.97556026 0.002189822
Factor4  0.007625069  0.11489683  0.02445173 0.989366259
cor(ml.promax$scores, ml.varimax$scores)
            ML2         ML3          ML4        ML1
ML2  0.99714527 -0.05043569 -0.058887859 0.01154380
ML3 -0.06617328  0.97785070  0.193263920 0.08735674
ML4 -0.08094263  0.19810346  0.979273972 0.03916156
ML1  0.06253243  0.10010835 -0.004175889 0.99271103
fa_bartlett_scores <- fa(z, nfactors=4, rotate="varimax", fm="ml", scores = 'Bartlett')
cor(ml.varimax$scores, fa_bartlett_scores$scores)
              ML2           ML3          ML4           ML1
ML2  9.999945e-01 -1.574145e-18 8.383016e-17 -1.545713e-17
ML3  4.392591e-17  9.999300e-01 7.699237e-16 -1.960363e-16
ML4  3.900769e-18  7.222804e-19 9.995550e-01  3.371781e-17
ML1 -5.920793e-18 -3.169430e-16 5.198842e-16  9.996269e-01
cor(fa_bartlett_scores$scores)
              ML2           ML3          ML4           ML1
ML2  1.0000000000  0.0003036624  0.003257071 -0.0006236104
ML3  0.0003036624  1.0000000000 -0.011669369 -0.0016210978
ML4  0.0032570710 -0.0116693685  1.000000000 -0.0272409055
ML1 -0.0006236104 -0.0016210978 -0.027240905  1.0000000000

# sum scores

threshold <- 0.5

vars_1 <- abs(ml.promax$Structure[,1])>threshold
vars_2 <- abs(ml.promax$Structure[,2])>threshold
vars_3 <- abs(ml.promax$Structure[,3])>threshold
vars_4 <- abs(ml.promax$Structure[,4])>threshold

key.list <- list(one = as.numeric(which(vars_1==1)), 
                two = as.numeric(which(vars_2==1)), 
                three = as.numeric(which(vars_3==1)), 
                four = as.numeric(which(vars_4==1)))

sign.mat <- cbind(sign(ml.promax$Structure[,1]), 
                      sign(ml.promax$Structure[,2]),
                      sign(ml.promax$Structure[,3]),
                      sign(ml.promax$Structure[,4]))

keys <- make.keys(z, key.list,item.labels = colnames(z))

si <- scoreItems(keys * sign.mat, z) # these are the scores I finally use

pairs(cbind(si$scores[,1], si$scores[,2], si$scores[,3], si$scores[,4]))

cor(cbind(si$scores[,1], si$scores[,2], si$scores[,3], si$scores[,4]))
            [,1]        [,2]        [,3]        [,4]
[1,]  1.00000000 -0.07190494 -0.04542303 -0.04038714
[2,] -0.07190494  1.00000000  0.42361028  0.62898318
[3,] -0.04542303  0.42361028  1.00000000  0.19564730
[4,] -0.04038714  0.62898318  0.19564730  1.00000000
print(ml.promax$loadings, cutoff = 0.5, sort = T)

Loadings:
                                  ML2    ML3    ML4    ML1   
commentCount                       0.984                     
dislikeCount                       0.990                     
likeCount                          0.999                     
viewsCount                         0.962                     
Artist_Albums_Number                      0.860              
Artist_Albums_Tracks_Number               0.552  0.545       
Artist_Compilations_Number                0.999              
Artist_Compilations_Tracks_Number         1.018              
Artist_Appearances_Number                        0.940       
Artist_Appearances_Tracks_Number                 0.643       
Artist_Singles_Number                     0.535         0.541
Artist_Singles_Tracks_Number                            0.956
Artist_Follower                                              
Track_Duration_ms                                            
days_release                                                 

                 ML2   ML3   ML4   ML1
SS loadings    3.989 3.519 2.033 1.346
Proportion Var 0.266 0.235 0.136 0.090
Cumulative Var 0.266 0.501 0.636 0.726
# I apply a threshold of 0.5 for manual computation of sum scores

f2 <- (z[,11] + z[,12] + z[,13] + z[,14])/4 # commentCount, dislikeCount, likeCount, viewsCount
f3 <- (z[,1] + z[,2] + z[,5])/3 # Artist_Albums_Number, Artist_Compilations_Number, Artist_Compilations_Tracks_Number
f4 <- (z[,2] + z[,3] + z[,4])/3 # Artist_Albums_Tracks_Number, Artist_Appearances_Number, Artist_Appearances_Tracks_Number
f1 <- (z[,8] + z[,9])/2 # Artist_Singles_Number, Artist_Singles_Tracks_Number

pairs(cbind(f2, f3, f4, f1))
cor(cbind(f2, f3, f4, f1))
            f2          f3          f4          f1
f2  1.00000000 -0.08246012 -0.04542303 -0.04038714
f3 -0.08246012  1.00000000  0.51683413  0.47961885
f4 -0.04542303  0.51683413  1.00000000  0.19564730
f1 -0.04038714  0.47961885  0.19564730  1.00000000
psych::alpha(cor(z[,vars_1]), check.keys = T) # commentCount, dislikeCount, likeCount, viewsCount

Reliability analysis   
Call: psych::alpha(x = cor(z[, vars_1]), check.keys = T)

 

 Reliability if an item is dropped:

 Item statistics 
psych::alpha(cor(z[,vars_2]), check.keys = T) # Artist_Albums_Number, Artist_Albums_Tracks_Number, Artist_Compilations_Number, Artist_Compilations_Tracks_Number, Artist_Singles_Number

Reliability analysis   
Call: psych::alpha(x = cor(z[, vars_2]), check.keys = T)

 

 Reliability if an item is dropped:

 Item statistics 
psych::alpha(cor(z[,vars_3]), check.keys = T) # Artist_Albums_Tracks_Number, Artist_Appearances_Number, Artist_Appearances_Tracks_Number, Track_Duration_ms 

Reliability analysis   
Call: psych::alpha(x = cor(z[, vars_3]), check.keys = T)

 

 Reliability if an item is dropped:

 Item statistics 
psych::alpha(cor(z[,vars_4]), check.keys = T) # Artist_Singles_Number, Artist_Singles_Tracks_Number
Datenl攼㸴nge [16] ist kein Teiler oder Vielfaches der Anzahl der Spalten [10]

Reliability analysis   
Call: psych::alpha(x = cor(z[, vars_4]), check.keys = T)

 

 Reliability if an item is dropped:

 Item statistics 
items_1 <- reverse.code((keys[,1] * sign.mat[,1])[(keys[,1] * sign.mat[,1]) != 0], z[, vars_1])
items_2 <- reverse.code((keys[,2] * sign.mat[,2])[(keys[,2] * sign.mat[,2]) != 0], z[, vars_2])
items_3 <- reverse.code((keys[,3] * sign.mat[,3])[(keys[,3] * sign.mat[,3]) != 0], z[, vars_3])
items_4 <- reverse.code((keys[,4] * sign.mat[,4])[(keys[,4] * sign.mat[,4]) != 0], z[, vars_4])

library("additivityTests")

tukey.test(items_1) # result: H0 rejected, i.e. sum score is insufficient to represent the variables (factor 2)

Tukey test on 5% alpha-level:

Test statistic: 16.19 
Critival value: 3.857 
The additivity hypothesis was rejected.
tukey.test(items_2) # result: H0 rejected (factor 3)

Tukey test on 5% alpha-level:

Test statistic: 9.276 
Critival value: 3.853 
The additivity hypothesis was rejected.
tukey.test(items_3) # result: H0 cannot be rejected (factor 4)

Tukey test on 5% alpha-level:

Test statistic: 0.3105 
Critival value: 3.865 
The additivity hypothesis cannot be rejected.
tukey.test(items_4) # result: H0 cannot be rejected (factor 1)

Tukey test on 5% alpha-level:

Test statistic: 0.004627 
Critival value: 3.89 
The additivity hypothesis cannot be rejected.
cor(ml.promax$scores, si$scores)
            one         two        three         four
ML2  0.99592621 -0.09140514 -0.079332072 -0.002099964
ML3 -0.09594147  0.97336006  0.416099225  0.487503635
ML4 -0.11258045  0.39562748  0.960537575  0.200024816
ML1  0.01023158  0.29765356 -0.008730584  0.882141227
fa_bartlett_scores <- fa(z, nfactors=4, rotate="promax", fm="ml", scores = 'Bartlett')
cor(ml.promax$scores, fa_bartlett_scores$scores)
            ML2        ML3          ML4          ML1
ML2  0.99999331 -0.1228703 -0.132348818  0.071013967
ML3 -0.12409086  0.9999497  0.347443877  0.166211182
ML4 -0.14750946  0.3834347  0.999105855 -0.009335731
ML1  0.07138876  0.1654449 -0.008420436  0.999492034
cor(si$scores, fa_bartlett_scores$scores)
               ML2         ML3         ML4         ML1
one    0.995760702 -0.09422492 -0.09953451  0.01023589
two   -0.091504358  0.97260263  0.36373011  0.29006661
three -0.082455588  0.42220226  0.95965169 -0.03857534
four  -0.001308527  0.48171167  0.16518758  0.87773348
cor(cbind(f2, f3, f4, f1), si$scores) # are almost identical now
           one         two       three        four
f2  1.00000000 -0.07190494 -0.04542303 -0.04038714
f3 -0.08246012  0.97220279  0.51683413  0.47961885
f4 -0.04542303  0.42361028  1.00000000  0.19564730
f1 -0.04038714  0.62898318  0.19564730  1.00000000
cor(cbind(f2, f3, f4, f1), fa_bartlett_scores$scores)
            ML2         ML3         ML4         ML1
f2  0.995760702 -0.09422492 -0.09953451  0.01023589
f3 -0.113694191  0.96801245  0.47604146  0.14747026
f4 -0.082455588  0.42220226  0.95965169 -0.03857534
f1 -0.001308527  0.48171167  0.16518758  0.87773348
cor(si$scores, fa_bartlett_scores$scores)
               ML2         ML3         ML4         ML1
one    0.995760702 -0.09422492 -0.09953451  0.01023589
two   -0.091504358  0.97260263  0.36373011  0.29006661
three -0.082455588  0.42220226  0.95965169 -0.03857534
four  -0.001308527  0.48171167  0.16518758  0.87773348
# I choose the manually computed scoreItems and sum scores omitting <= 0.5 structure loadings and assigning the item with the larger loading to a single factor in case of ambiguity

par(mfrow=c(1,2))

hist(ml.promax$scores[,1], main = 'Youtube popularity factor', xlab = 'Score corresponding to factor 2 (promax)')
rug(ml.promax$scores[ifelse(x[complete_index, 'Charts'] == 1, TRUE, FALSE), 1], col="blue")
rug(ml.promax$scores[ifelse(x[complete_index, 'Charts'] != 1, TRUE, FALSE), 1], col="red")

hist(ml.promax$scores[,2], main = 'Music supply factor', xlab = 'Score corresponding to factor 3 (promax)')
rug(ml.promax$scores[ifelse(x[complete_index, 'Charts'] == 1, TRUE, FALSE), 2], col="blue")
rug(ml.promax$scores[ifelse(x[complete_index, 'Charts'] != 1, TRUE, FALSE), 2], col="red")

par(mfrow=c(1,2))

hist(si$scores[,1], main = 'Youtube popularity factor', xlab = 'Score corresponding to factor 2 (promax)')
rug(si$scores[ifelse(x[complete_index, 'Charts'] == 1, TRUE, FALSE), 1], col="blue")
rug(si$scores[ifelse(x[complete_index, 'Charts'] != 1, TRUE, FALSE), 1], col="red")

hist(si$scores[,2], main = 'Music supply factor', xlab = 'Score corresponding to factor 3 (promax)')
rug(si$scores[ifelse(x[complete_index, 'Charts'] == 1, TRUE, FALSE), 2], col="blue")
rug(si$scores[ifelse(x[complete_index, 'Charts'] != 1, TRUE, FALSE), 2], col="red")

par(mfrow=c(1,2))

hist(f2, main = 'Youtube popularity factor', xlab = 'Score corresponding to factor 2 (promax)')
rug(f2[ifelse(x[complete_index, 'Charts'] == 1, TRUE, FALSE)], col="blue")
rug(f2[ifelse(x[complete_index, 'Charts'] != 1, TRUE, FALSE)], col="red")

hist(f3, main = 'Music supply factor', xlab = 'Score corresponding to factor 3 (promax)')
rug(f3[ifelse(x[complete_index, 'Charts'] == 1, TRUE, FALSE)], col="blue")
rug(f3[ifelse(x[complete_index, 'Charts'] != 1, TRUE, FALSE)], col="red")


x$factor_2 <- si$scores[,1]
x$factor_3 <- si$scores[,2]
x$factor_4 <- si$scores[,3]
x$factor_1 <- si$scores[,4]

x$factor_2man <- f2
x$factor_3man <- f3
x$factor_4man <- f4
x$factor_1man <- f1

cor(cbind(si$scores[,1], si$scores[,2], si$scores[,3], si$scores[,4]), cbind(f2, f3, f4, f1))
              f2          f3          f4          f1
[1,]  1.00000000 -0.08246012 -0.04542303 -0.04038714
[2,] -0.07190494  0.97220279  0.42361028  0.62898318
[3,] -0.04542303  0.51683413  1.00000000  0.19564730
[4,] -0.04038714  0.47961885  0.19564730  1.00000000
cor(cbind(si$scores[,1], si$scores[,2], si$scores[,3], si$scores[,4]), fa_bartlett_scores$scores)
              ML2         ML3         ML4         ML1
[1,]  0.995760702 -0.09422492 -0.09953451  0.01023589
[2,] -0.091504358  0.97260263  0.36373011  0.29006661
[3,] -0.082455588  0.42220226  0.95965169 -0.03857534
[4,] -0.001308527  0.48171167  0.16518758  0.87773348
cor(cbind(si$scores[,1], si$scores[,2], si$scores[,3], si$scores[,4]), ml.promax$scores)
              ML2         ML3        ML4          ML1
[1,]  0.995926214 -0.09594147 -0.1125804  0.010231584
[2,] -0.091405140  0.97336006  0.3956275  0.297653556
[3,] -0.079332072  0.41609922  0.9605376 -0.008730584
[4,] -0.002099964  0.48750364  0.2000248  0.882141227
x$factor_2bartlett <- fa_bartlett_scores$scores[,1]
x$factor_3bartlett <- fa_bartlett_scores$scores[,2]
x$factor_4bartlett <- fa_bartlett_scores$scores[,3]
x$factor_1bartlett <- fa_bartlett_scores$scores[,4]

z <- scale(x_features)

library(factoextra)

# Elbow method
fviz_nbclust(z, kmeans, method = "wss") +
  geom_vline(xintercept = 4, linetype = 2) + labs(title = "")

set.seed(42)
kz <- kmeans(z, c=4)

k_techno <- names(kz$cluster[kz$cluster == 1])
k_hiphop <- names(kz$cluster[kz$cluster == 2])
k_pop <- names(kz$cluster[kz$cluster == 3])
k_classic <- names(kz$cluster[kz$cluster == 4])

kz$cluster[k_techno] <- 4
kz$cluster[k_pop] <- 3
kz$cluster[k_hiphop] <- 2
kz$cluster[k_classic] <- 1

par(mfcol=c(1,1))

plot(pc_cor$x[,1], pc_cor$x[,2], col=kz$cluster, pch=19, xlab = 'PC1', ylab = 'PC2')

# project cluster centers to PCA feature space:

cluster_centers_pca <- kz$centers %*% pc_cor$rotation[, 1:2]
points(cluster_centers_pca[,1], cluster_centers_pca[,2], col = 'magenta', pch = 10, cex = 3)


plot(pc_cor$x[,1], pc_cor$x[,2], col=as.numeric(x$Genre), pch=19, xlab = 'PC1', ylab = 'PC2')


library("cluster")

sk_4 <- silhouette(kz$cluster, dist(z))
plot(sk_4, col=1:4, border=NA, ann = T, main = "")


library("caret")

confusionMatrix(as.factor(as.numeric(kz$cluster)), as.factor(as.numeric(x$Genre)))
Confusion Matrix and Statistics

          Reference
Prediction  1  2  3  4
         1 48  0  3  0
         2  1  1  0 47
         3  0 18  8  0
         4  0 31 39  0

Overall Statistics
                                          
               Accuracy : 0.2908          
                 95% CI : (0.2283, 0.3598)
    No Information Rate : 0.2551          
    P-Value [Acc > NIR] : 0.1437          
                                          
                  Kappa : 0.0566          
                                          
 Mcnemar's Test P-Value : NA              

Statistics by Class:

                     Class: 1 Class: 2 Class: 3 Class: 4
Sensitivity            0.9796 0.020000  0.16000   0.0000
Specificity            0.9796 0.671233  0.87671   0.5302
Pos Pred Value         0.9412 0.020408  0.30769   0.0000
Neg Pred Value         0.9931 0.666667  0.75294   0.6270
Prevalence             0.2500 0.255102  0.25510   0.2398
Detection Rate         0.2449 0.005102  0.04082   0.0000
Detection Prevalence   0.2602 0.250000  0.13265   0.3571
Balanced Accuracy      0.9796 0.345616  0.51836   0.2651
# Although k-means is not a classification algorithm, we can compute the proportions of genres per cluster to assign
# an interpretation for each cluster.

tab_kmeans <- table(kz$cluster, x$Genre)
#xtable(round(tab_kmeans/colSums(tab_kmeans), digits = 2)*100)
round(tab_kmeans/colSums(tab_kmeans), digits = 2)*100
   
    Classic Hip Hop Pop Techno
  1      98       0   6      0
  2       2       2   0     94
  3       0      36  16      0
  4       0      66  83      0
# It seems that Classic music and Techno music can be accurately distinguished but between Hip Hop and Pop there are 
# many false negatives, e.g. 62% of the tracks were predicted to be of the genre Pop although the truth was that they were
# of class Hip Hop, underlining the ambiguity between those genres in terms of music theoretical characteristics nowadays.
# Vice versa, 16% of the tracks predicted to be Hip Hop music were actually Pop music.
set.seed(42)
cl2 <- pam(z, 4)
plot(pc_cor$x[,1], pc_cor$x[,2], col=cl2$clustering)


k_classic <- names(cl2$clustering[cl2$clustering == 1])
k_techno <- names(cl2$clustering[cl2$clustering == 2])
k_pop <- names(cl2$clustering[cl2$clustering == 3])
k_hiphop <- names(cl2$clustering[cl2$clustering == 4])

cl2$clustering[k_techno] <- 4
cl2$clustering[k_pop] <- 3
cl2$clustering[k_hiphop] <- 2
cl2$clustering[k_classic] <- 1


par(mfcol=c(1,1))

plot(pc_cor$x[,1], pc_cor$x[,2], col=cl2$clustering, pch=19, xlab = 'PC1', ylab = 'PC2')
points(pc_cor$x[rownames(cl2$medoids),1], pc_cor$x[rownames(cl2$medoids),2], col = 'magenta', pch = 10, cex = 3)


plot(pc_cor$x[,1], pc_cor$x[,2], col=as.numeric(x$Genre), pch=19, xlab = 'PC1', ylab = 'PC2')


# xtable(x[rownames(cl2$medoids), c('Track_Title', 'Track_Artist', 'Genre')])

x[rownames(cl2$medoids), c('Track_Title', 'Track_Artist', 'Genre')]

sm_4 <- silhouette(cl2$clustering, dist(z))
plot(sm_4, col=1:4, border=NA, main = "")


confusionMatrix(as.factor(as.numeric(cl2$clustering)), as.factor(as.numeric(x$Genre)))
Confusion Matrix and Statistics

          Reference
Prediction  1  2  3  4
         1 48  0  2  0
         2  0 28 13  0
         3  0 21 35  0
         4  1  1  0 47

Overall Statistics
                                         
               Accuracy : 0.8061         
                 95% CI : (0.7437, 0.859)
    No Information Rate : 0.2551         
    P-Value [Acc > NIR] : < 2.2e-16      
                                         
                  Kappa : 0.7415         
                                         
 Mcnemar's Test P-Value : NA             

Statistics by Class:

                     Class: 1 Class: 2 Class: 3 Class: 4
Sensitivity            0.9796   0.5600   0.7000   1.0000
Specificity            0.9864   0.9110   0.8562   0.9866
Pos Pred Value         0.9600   0.6829   0.6250   0.9592
Neg Pred Value         0.9932   0.8581   0.8929   1.0000
Prevalence             0.2500   0.2551   0.2551   0.2398
Detection Rate         0.2449   0.1429   0.1786   0.2398
Detection Prevalence   0.2551   0.2092   0.2857   0.2500
Balanced Accuracy      0.9830   0.7355   0.7781   0.9933
tab_kmedoids <- table(cl2$clustering, x$Genre)
# xtable((round(tab_kmedoids/colSums(tab_kmedoids), digits = 2))*100)
round(tab_kmedoids/colSums(tab_kmedoids), digits = 2)*100
   
    Classic Hip Hop Pop Techno
  1      98       0   4      0
  2       0      56  26      0
  3       0      42  70      0
  4       2       2   0    100

# Factor analysis + k-means on scores

library("psych")
KMO(z)
Kaiser-Meyer-Olkin factor adequacy
Call: KMO(r = z)
Overall MSA =  0.83
MSA for each item = 
    acousticness     danceability           energy instrumentalness         liveness         loudness      speechiness            tempo 
            0.83             0.92             0.80             0.67             0.81             0.87             0.84             0.82 
         valence 
            0.80 
scree(z)

library("paran")
paran(z, centile=95, all=T, graph=T) # two factors are appropriate

Using eigendecomposition of correlation matrix.
Computing: 10%  20%  30%  40%  50%  60%  70%  80%  90%  100%


Results of Horn's Parallel Analysis for component retention
270 iterations, using the 95 centile estimate

-------------------------------------------------- 
Component   Adjusted    Unadjusted    Estimated 
            Eigenvalue  Eigenvalue    Bias 
-------------------------------------------------- 
1           3.857353    4.305594      0.448240
2           1.162793    1.462154      0.299361
3           0.783622    0.979100      0.195478
4           0.709253    0.813018      0.103765
5           0.659769    0.694514      0.034744
6           0.336983    0.301951     -0.03503
7           0.316533    0.222367     -0.09416
8           0.298139    0.140507     -0.15763
9           0.313580    0.080790     -0.23278
-------------------------------------------------- 

Adjusted eigenvalues > 1 indicate dimensions to retain.
(2 components retained)

fa <- fa(z, nfactors=2, rotate="varimax")
library("plot.matrix")
Registered S3 method overwritten by 'plot.matrix':
  method        from
  plot.loadings pls 
plot(loadings(fa), las=1)

al1 <- psych::alpha(z[,c("acousticness", "danceability", "energy", "loudness")], check.keys = TRUE)
Some items were negatively correlated with total scale and were automatically reversed.
 This is indicated by a negative sign for the variable name.
al1

Reliability analysis   
Call: psych::alpha(x = z[, c("acousticness", "danceability", 
    "energy", "loudness")], check.keys = TRUE)

 

 lower alpha upper     95% confidence boundaries
0.94 0.95 0.96 

 Reliability if an item is dropped:

 Item statistics 
al2 <- psych::alpha(z[,c("instrumentalness", "valence")], check.keys = TRUE)
Some items were negatively correlated with total scale and were automatically reversed.
 This is indicated by a negative sign for the variable name.Datenl攼㸴nge [16] ist kein Teiler oder Vielfaches der Anzahl der Spalten [10]
al2

Reliability analysis   
Call: psych::alpha(x = z[, c("instrumentalness", "valence")], 
    check.keys = TRUE)

 

 lower alpha upper     95% confidence boundaries
0.73 0.79 0.85 

 Reliability if an item is dropped:

 Item statistics 
scale <- cbind(al1$scores, al2$scores)

fviz_nbclust(scale, kmeans, method = "wss") +
  geom_vline(xintercept = 4, linetype = 2)+
  labs(subtitle = "Elbow method")

reorder <- function(x, ...) {
  dr <- dist(x)
  hr <- hclust(dr)
  dc <- dist(t(x))
  hc <- hclust(dc)
  x[hr$order, hc$order]
}

set.seed(42)
cl1 <- kmeans(z, 4)
set.seed(42)
cl2 <- kmeans(scale, 4)

print(reorder(table(cl1$cluster,cl2$cluster)))
   
     4  2  1  3
  4 51  0  0  0
  2  0 49  0  0
  1  0  0 39 31
  3  0  0 17  9
par(mfcol=c(1,3))


plot(pc_cor$x[,1], pc_cor$x[,2], col=cl1$cluster, pch=19)
plot(pc_cor$x[,1], pc_cor$x[,2], col=cl2$cluster, pch=19)
plot(pc_cor$x[,1], pc_cor$x[,2], col=as.numeric(x$Genre), pch=19)

par(mfcol=c(1,2))

library("e1071")
set.seed(42)
cl1 <- cmeans(z, 4)
mcolor <- colorRamp(c("black", "green", "blue", "red"))
col <- rgb(mcolor(cl1$membership[,1]), max=255)
plot(pc_cor$x[,1], pc_cor$x[,2], pch=19, col=col, main = 'Soft-clustering (fuzzy c-means, k = 4)')


cl  <- diana(z)
hcl <- cutree(as.hclust(cl), k = 4)
par(mfrow=c(1,1))
plot(pc_cor$x[,1], pc_cor$x[,2], col=hcl, 
     pch=19, cex=0.5)

NA
NA
NA
NA

library("proxy")

Attache Paket: 㤼㸱proxy㤼㸲

The following objects are masked from 㤼㸱package:stats㤼㸲:

    as.dist, dist

The following object is masked from 㤼㸱package:base㤼㸲:

    as.matrix
d <- as.matrix(dist(z))
heatmap(d)

s <- pr_dist2simil(d) # the darker, the more similar
heatmap(s)


par(mfcol=c(1,1))

d <- dist(z)
# hclust
cl1 <- hclust(d, method = 'ward.D2')
memb <- cutree(cl1, 4)
plot(pc_cor$x[,1], pc_cor$x[,2], col=memb)

plot(cl1, ann = F)
title(xlab = "Ward.D2", ylab = "Height")


library("ggplot2")
library("tibble")

ggplot(cl1$height %>%
as_tibble() %>%
add_column(groups = length(cl1$height):1) %>%
rename(height=value),
aes(x=groups, y=height)) +
geom_point() +
geom_line()
Calling `as_tibble()` on a vector is discouraged, because the behavior is likely to change in the future. Use `tibble::enframe(name = NULL)` instead.
This warning is displayed once per session.


library("mclust")
    __  ___________    __  _____________
   /  |/  / ____/ /   / / / / ___/_  __/
  / /|_/ / /   / /   / / / /\__ \ / /   
 / /  / / /___/ /___/ /_/ /___/ // /    
/_/  /_/\____/_____/\____//____//_/    version 5.4.5
Type 'citation("mclust")' for citing this R package in publications.

Attache Paket: 㤼㸱mclust㤼㸲

The following object is masked from 㤼㸱package:MVN㤼㸲:

    mvn

The following object is masked from 㤼㸱package:psych㤼㸲:

    sim
set.seed(42)
cl <- Mclust(z)
fitting ...

  |                                                                                                                                            
  |                                                                                                                                      |   0%
  |                                                                                                                                            
  |=                                                                                                                                     |   1%
  |                                                                                                                                            
  |==                                                                                                                                    |   2%
  |                                                                                                                                            
  |===                                                                                                                                   |   2%
  |                                                                                                                                            
  |====                                                                                                                                  |   3%
  |                                                                                                                                            
  |=====                                                                                                                                 |   4%
  |                                                                                                                                            
  |======                                                                                                                                |   5%
  |                                                                                                                                            
  |=======                                                                                                                               |   6%
  |                                                                                                                                            
  |========                                                                                                                              |   6%
  |                                                                                                                                            
  |=========                                                                                                                             |   7%
  |                                                                                                                                            
  |===========                                                                                                                           |   8%
  |                                                                                                                                            
  |============                                                                                                                          |   9%
  |                                                                                                                                            
  |=============                                                                                                                         |   9%
  |                                                                                                                                            
  |==============                                                                                                                        |  10%
  |                                                                                                                                            
  |===============                                                                                                                       |  11%
  |                                                                                                                                            
  |================                                                                                                                      |  12%
  |                                                                                                                                            
  |=================                                                                                                                     |  13%
  |                                                                                                                                            
  |==================                                                                                                                    |  13%
  |                                                                                                                                            
  |===================                                                                                                                   |  14%
  |                                                                                                                                            
  |====================                                                                                                                  |  15%
  |                                                                                                                                            
  |=====================                                                                                                                 |  16%
  |                                                                                                                                            
  |======================                                                                                                                |  17%
  |                                                                                                                                            
  |=======================                                                                                                               |  17%
  |                                                                                                                                            
  |========================                                                                                                              |  18%
  |                                                                                                                                            
  |=========================                                                                                                             |  19%
  |                                                                                                                                            
  |==========================                                                                                                            |  20%
  |                                                                                                                                            
  |===========================                                                                                                           |  20%
  |                                                                                                                                            
  |============================                                                                                                          |  21%
  |                                                                                                                                            
  |==============================                                                                                                        |  22%
  |                                                                                                                                            
  |===============================                                                                                                       |  23%
  |                                                                                                                                            
  |================================                                                                                                      |  24%
  |                                                                                                                                            
  |=================================                                                                                                     |  24%
  |                                                                                                                                            
  |==================================                                                                                                    |  25%
  |                                                                                                                                            
  |===================================                                                                                                   |  26%
  |                                                                                                                                            
  |====================================                                                                                                  |  27%
  |                                                                                                                                            
  |=====================================                                                                                                 |  28%
  |                                                                                                                                            
  |======================================                                                                                                |  28%
  |                                                                                                                                            
  |=======================================                                                                                               |  29%
  |                                                                                                                                            
  |========================================                                                                                              |  30%
  |                                                                                                                                            
  |=========================================                                                                                             |  31%
  |                                                                                                                                            
  |==========================================                                                                                            |  31%
  |                                                                                                                                            
  |===========================================                                                                                           |  32%
  |                                                                                                                                            
  |============================================                                                                                          |  33%
  |                                                                                                                                            
  |=============================================                                                                                         |  34%
  |                                                                                                                                            
  |==============================================                                                                                        |  35%
  |                                                                                                                                            
  |===============================================                                                                                       |  35%
  |                                                                                                                                            
  |=================================================                                                                                     |  36%
  |                                                                                                                                            
  |==================================================                                                                                    |  37%
  |                                                                                                                                            
  |===================================================                                                                                   |  38%
  |                                                                                                                                            
  |====================================================                                                                                  |  39%
  |                                                                                                                                            
  |=====================================================                                                                                 |  39%
  |                                                                                                                                            
  |======================================================                                                                                |  40%
  |                                                                                                                                            
  |=======================================================                                                                               |  41%
  |                                                                                                                                            
  |========================================================                                                                              |  42%
  |                                                                                                                                            
  |=========================================================                                                                             |  43%
  |                                                                                                                                            
  |==========================================================                                                                            |  43%
  |                                                                                                                                            
  |===========================================================                                                                           |  44%
  |                                                                                                                                            
  |============================================================                                                                          |  45%
  |                                                                                                                                            
  |=============================================================                                                                         |  46%
  |                                                                                                                                            
  |==============================================================                                                                        |  46%
  |                                                                                                                                            
  |===============================================================                                                                       |  47%
  |                                                                                                                                            
  |================================================================                                                                      |  48%
  |                                                                                                                                            
  |=================================================================                                                                     |  49%
  |                                                                                                                                            
  |==================================================================                                                                    |  50%
  |                                                                                                                                            
  |====================================================================                                                                  |  50%
  |                                                                                                                                            
  |=====================================================================                                                                 |  51%
  |                                                                                                                                            
  |======================================================================                                                                |  52%
  |                                                                                                                                            
  |=======================================================================                                                               |  53%
  |                                                                                                                                            
  |========================================================================                                                              |  54%
  |                                                                                                                                            
  |=========================================================================                                                             |  54%
  |                                                                                                                                            
  |==========================================================================                                                            |  55%
  |                                                                                                                                            
  |===========================================================================                                                           |  56%
  |                                                                                                                                            
  |============================================================================                                                          |  57%
  |                                                                                                                                            
  |=============================================================================                                                         |  57%
  |                                                                                                                                            
  |==============================================================================                                                        |  58%
  |                                                                                                                                            
  |===============================================================================                                                       |  59%
  |                                                                                                                                            
  |================================================================================                                                      |  60%
  |                                                                                                                                            
  |=================================================================================                                                     |  61%
  |                                                                                                                                            
  |==================================================================================                                                    |  61%
  |                                                                                                                                            
  |===================================================================================                                                   |  62%
  |                                                                                                                                            
  |====================================================================================                                                  |  63%
  |                                                                                                                                            
  |=====================================================================================                                                 |  64%
  |                                                                                                                                            
  |=======================================================================================                                               |  65%
  |                                                                                                                                            
  |========================================================================================                                              |  65%
  |                                                                                                                                            
  |=========================================================================================                                             |  66%
  |                                                                                                                                            
  |==========================================================================================                                            |  67%
  |                                                                                                                                            
  |===========================================================================================                                           |  68%
  |                                                                                                                                            
  |============================================================================================                                          |  69%
  |                                                                                                                                            
  |=============================================================================================                                         |  69%
  |                                                                                                                                            
  |==============================================================================================                                        |  70%
  |                                                                                                                                            
  |===============================================================================================                                       |  71%
  |                                                                                                                                            
  |================================================================================================                                      |  72%
  |                                                                                                                                            
  |=================================================================================================                                     |  72%
  |                                                                                                                                            
  |==================================================================================================                                    |  73%
  |                                                                                                                                            
  |===================================================================================================                                   |  74%
  |                                                                                                                                            
  |====================================================================================================                                  |  75%
  |                                                                                                                                            
  |=====================================================================================================                                 |  76%
  |                                                                                                                                            
  |======================================================================================================                                |  76%
  |                                                                                                                                            
  |=======================================================================================================                               |  77%
  |                                                                                                                                            
  |========================================================================================================                              |  78%
  |                                                                                                                                            
  |==========================================================================================================                            |  79%
  |                                                                                                                                            
  |===========================================================================================================                           |  80%
  |                                                                                                                                            
  |============================================================================================================                          |  80%
  |                                                                                                                                            
  |=============================================================================================================                         |  81%
  |                                                                                                                                            
  |==============================================================================================================                        |  82%
  |                                                                                                                                            
  |===============================================================================================================                       |  83%
  |                                                                                                                                            
  |================================================================================================================                      |  83%
  |                                                                                                                                            
  |=================================================================================================================                     |  84%
  |                                                                                                                                            
  |==================================================================================================================                    |  85%
  |                                                                                                                                            
  |===================================================================================================================                   |  86%
  |                                                                                                                                            
  |====================================================================================================================                  |  87%
  |                                                                                                                                            
  |=====================================================================================================================                 |  87%
  |                                                                                                                                            
  |======================================================================================================================                |  88%
  |                                                                                                                                            
  |=======================================================================================================================               |  89%
  |                                                                                                                                            
  |========================================================================================================================              |  90%
  |                                                                                                                                            
  |=========================================================================================================================             |  91%
  |                                                                                                                                            
  |==========================================================================================================================            |  91%
  |                                                                                                                                            
  |===========================================================================================================================           |  92%
  |                                                                                                                                            
  |=============================================================================================================================         |  93%
  |                                                                                                                                            
  |==============================================================================================================================        |  94%
  |                                                                                                                                            
  |===============================================================================================================================       |  94%
  |                                                                                                                                            
  |================================================================================================================================      |  95%
  |                                                                                                                                            
  |=================================================================================================================================     |  96%
  |                                                                                                                                            
  |==================================================================================================================================    |  97%
  |                                                                                                                                            
  |===================================================================================================================================   |  98%
  |                                                                                                                                            
  |====================================================================================================================================  |  98%
  |                                                                                                                                            
  |===================================================================================================================================== |  99%
  |                                                                                                                                            
  |======================================================================================================================================| 100%
summary(cl)
---------------------------------------------------- 
Gaussian finite mixture model fitted by EM algorithm 
---------------------------------------------------- 

Mclust VVI (diagonal, varying volume and shape) model with 9 components: 

Clustering table:
 1  2  3  4  5  6  7  8  9 
 9 23  7 35  7 65 24  8 18 
par(mfrow=c(2,2))
plot(cl, "BIC")
plot(pc_cor$x[,1], pc_cor$x[,2], col = cl$classification)

EM_density_plot <- plot(cl, "density")

NA
NA

par(mfcol=c(2,2))

library("fpc")

Attache Paket: 㤼㸱fpc㤼㸲

The following object is masked from 㤼㸱package:xtable㤼㸲:

    xtable
set.seed(42)
cl <- dbscan(z, 0.5, scale=F, MinPts = 5)
col <- c('grey', rainbow(max(cl$cluster)))
plot(pc_cor$x[,1], pc_cor$x[,2], pch=19, col=col[1+cl$cluster])

set.seed(42)
cl <- dbscan(z, 1, scale=F, MinPts = 5)
col <- c('grey', rainbow(max(cl$cluster)))
plot(pc_cor$x[,1], pc_cor$x[,2], pch=19, col=col[1+cl$cluster])

set.seed(42)
cl <- dbscan(z, 1.5, scale=F, MinPts = 5)
col <- c('grey', rainbow(max(cl$cluster)))
plot(pc_cor$x[,1], pc_cor$x[,2], pch=19, col=col[1+cl$cluster])

set.seed(42)
cl <- dbscan(z, 2, scale=F, MinPts = 5)
col <- c('grey', rainbow(max(cl$cluster)))
plot(pc_cor$x[,1], pc_cor$x[,2], pch=19, col=col[1+cl$cluster])


# Mixed clustering


# hclust
d    <- dist(z)
set.seed(42)
cl1  <- hclust(d, method="ward.D2")
memb <- cutree(cl1, 4)
# kmeans
groupm  <- aggregate(z, list(memb), mean)
centers <- cbind(groupm$acousticness, 
                 groupm$danceability, 
                 groupm$energy, 
                 groupm$instrumentalness,
                 groupm$liveness,
                 groupm$loudness,
                 groupm$speechiness,
                 groupm$tempo,
                 groupm$valence)
set.seed(42)
cl2 <- kmeans(z, centers = centers)
# compare results (confusion matrix)
table(memb, cl2$cluster) # result: k-means has classified 4 observations differently than hclust
    
memb  1  2  3  4
   1 51  0  0  1
   2  0 49  0  0
   3  0  0  7  0
   4  0  0  3 85
par(mfcol=c(1,2))
plot(pc_cor$x[,1], pc_cor$x[,2], col=cl2$cluster,
     main="Cluster predictions, mixed clustering", pch=19, xlab = 'PC1', ylab = 'PC2')

# project cluster centers to PCA feature space:

cluster_centers_pca <- cl2$centers %*% pc_cor$rotation[, 1:2]
points(cluster_centers_pca[,1], cluster_centers_pca[,2], col = 'magenta', pch = 10, cex = 3)

plot(pc_cor$x[,1], pc_cor$x[,2], col=as.numeric(x$Genre),
     main="Truth", pch=19, xlab = 'PC1', ylab = 'PC2')


library("NbClust")

# Total variance explained
tve <- rep(NA, 15)
for (k in 2:15) {
  clk <- kmeans(z, k)
  tve[k] <- 1-clk$tot.withinss/clk$totss
}
plot(tve, type="b")


set.seed(42)
NbClust(z, method="ward.D2", index="ch") # result: k*=2
$All.index
       2        3        4        5        6        7        8        9       10       11       12       13       14       15 
139.7710 120.4259  98.3300  88.7335  82.5706  74.8123  69.3249  65.2113  61.9272  59.6528  58.1524  57.0914  56.0043  55.3023 

$Best.nc
Number_clusters     Value_Index 
          2.000         139.771 

$Best.partition
  1   2   3   4   5   6   7   8   9  10  11  12  13  14  15  16  17  18  19  20  21  22  23  24  25  26  27  28  29  30  31  32  33  34  35  36 
  1   1   1   1   1   1   1   2   2   2   2   2   2   2   2   2   2   1   2   2   2   2   2   2   2   2   2   2   2   2   2   2   1   2   2   2 
 37  38  39  40  41  42  43  44  45  46  47  48  49  50  51  52  53  54  55  56  57  58  59  60  61  62  63  64  65  66  67  68  69  70  71  72 
  1   2   2   2   2   2   2   2   2   2   2   2   2   2   2   2   1   2   2   2   2   2   2   2   2   2   2   2   2   2   2   2   2   2   2   2 
 73  74  75  76  77  78  79  80  81  82  83  84  85  86  87  88  89  90  91  92  93  94  95  96  97  98  99 100 101 102 103 104 105 106 107 108 
  2   2   2   2   2   2   2   2   2   2   2   2   2   2   2   2   2   2   2   2   2   2   2   2   2   2   2   2   2   2   2   2   2   2   2   2 
109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 145 
  2   2   2   2   2   2   1   1   1   1   1   1   1   1   1   1   1   1   1   1   1   1   1   1   1   1   1   1   1   1   1   1   1   1   1   1 
146 147 148 149 150 152 153 154 155 156 157 158 159 160 161 162 163 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 183 184 
  2   1   1   2   2   2   2   2   2   2   2   2   2   2   2   2   2   2   2   2   2   2   2   2   2   2   2   2   2   2   2   2   2   2   2   2 
185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 
  2   2   2   2   2   2   2   1   1   1   1   1   1   1   1   1 
set.seed(42)
NbClust(z, method="ward.D2") # result: majority vote (12) for k*=3.
*** : The Hubert index is a graphical method of determining the number of clusters.
                In the plot of Hubert index, we seek a significant knee that corresponds to a 
                significant increase of the value of the measure i.e the significant peak in Hubert
                index second differences plot. 
 

*** : The D index is a graphical method of determining the number of clusters. 
                In the plot of D index, we seek a significant knee (the significant peak in Dindex
                second differences plot) that corresponds to a significant increase of the value of
                the measure. 
 
******************************************************************* 
* Among all indices:                                                
* 8 proposed 2 as the best number of clusters 
* 12 proposed 3 as the best number of clusters 
* 1 proposed 4 as the best number of clusters 
* 1 proposed 14 as the best number of clusters 
* 2 proposed 15 as the best number of clusters 

                   ***** Conclusion *****                            
 
* According to the majority rule, the best number of clusters is  3 
 
 
******************************************************************* 
$All.index
       KL       CH Hartigan     CCC     Scott      Marriot    TrCovW    TraceW Friedman  Rubin Cindex     DB Silhouette   Duda Pseudot2   Beale
2  2.9225 139.7710  59.4773  3.0748  463.1206 4.072864e+17 21596.043 1020.0706  21.7400 1.7205 0.3120 0.9783     0.4265 0.7137  56.9638  2.3921
3  3.5131 120.4259  24.7670  6.5366  826.6680 1.433964e+17 11111.650  780.7158  28.4878 2.2479 0.2692 1.1655     0.3482 0.8329  18.6544  1.1917
4  0.8659  98.3300  24.3660  7.3358  952.5966 1.340864e+17  8423.499  691.9237  29.5447 2.5364 0.3313 1.1514     0.3628 0.8040  20.9664  1.4472
5  1.1693  88.7335  21.0237  9.1153 1104.3665 9.658613e+16  6515.744  614.0028  31.1993 2.8583 0.2878 1.4486     0.3458 0.6822  16.3044  2.7196
6  2.4339  82.5706  12.1013 11.0072 1213.6275 7.964860e+16  4756.855  553.1201  32.3856 3.1729 0.3217 1.2612     0.3517 0.8201  10.9717  1.2919
7  0.9542  74.8123  11.5500 10.5538 1366.8443 4.961074e+16  4347.806  520.0008  37.4071 3.3750 0.3024 1.4710     0.2876 0.7661  14.9600  1.7967
8  1.0040  69.3249  10.9477 10.3915 1447.6622 4.290265e+16  3883.982  490.0530  39.0800 3.5812 0.2885 1.5297     0.2588 0.6165   8.0877  3.4690
9  1.0561  65.2113  10.1985 10.3909 1533.7264 3.500170e+16  3428.487  463.0863  40.7503 3.7898 0.2840 1.4543     0.2668 0.6327  27.2817  3.4130
10 0.8937  61.9272  10.6113 10.4543 1598.4194 3.106406e+16  2948.095  439.1369  42.0453 3.9965 0.2731 1.4046     0.2679 0.6075  12.9223  3.6951
11 0.9214  59.6528  11.0369 10.7463 1689.2379 2.364879e+16  2610.542  415.4363  44.1287 4.2245 0.2665 1.3700     0.2704 0.8028   9.5795  1.4381
12 0.9994  58.1524  10.9827 11.8133 1812.3952 1.501397e+16  2480.136  392.0472  48.7617 4.4765 0.2489 1.3929     0.2545 0.7169  11.0589  2.2899
13 1.1530  57.0914   9.8993 12.5013 1911.2783 1.063937e+16  2136.940  369.9646  52.5702 4.7437 0.2390 1.3446     0.2621 0.6343  12.6834  3.3114
14 0.9733  56.0043  10.0901 13.0538 1965.2543 9.368841e+15  1826.549  350.9785  53.4624 5.0003 0.2303 1.2754     0.2686 1.7043  -2.0662 -2.0679
15 1.3913  55.3023   7.9632 13.7026 2032.3638 7.636806e+15  1567.078  332.5424  54.9323 5.2775 0.2808 1.2230     0.2738 0.6088   6.4256  3.5078
   Ratkowsky     Ball Ptbiserial    Frey McClain   Dunn Hubert SDindex Dindex   SDbw
2     0.4113 510.0353     0.6596  1.5894  0.3791 0.1672 0.0014  1.3874 2.1143 0.6318
3     0.4042 260.2386     0.5824 -0.0150  1.0162 0.1346 0.0014  1.4240 1.7562 0.5419
4     0.3785 172.9809     0.6279  0.6392  1.0868 0.1782 0.0015  1.6964 1.6896 0.6632
5     0.3548 122.8006     0.5917  0.1784  1.6348 0.1280 0.0016  1.9833 1.5926 0.7320
6     0.3361  92.1867     0.5988  0.3878  1.7244 0.1504 0.0017  1.7666 1.5282 0.5752
7     0.3153  74.2858     0.5900  0.9796  1.9096 0.1504 0.0018  1.8074 1.4831 0.5428
8     0.2988  61.2566     0.5491  0.0245  2.3354 0.1264 0.0018  2.0264 1.4334 0.4935
9     0.2849  51.4540     0.5521  0.2609  2.3378 0.1264 0.0018  1.9033 1.4024 0.4559
10    0.2731  43.9137     0.5485  0.1750  2.4413 0.1264 0.0018  1.8289 1.3667 0.4188
11    0.2628  37.7669     0.5484  0.3502  2.4821 0.1264 0.0019  1.7465 1.3323 0.3863
12    0.2538  32.6706     0.5333  0.2567  2.7392 0.1264 0.0020  1.7621 1.2967 0.3768
13    0.2459  28.4588     0.5279  0.1186  2.8500 0.1264 0.0021  1.6812 1.2591 0.3519
14    0.2387  25.0699     0.5285 -0.0290  2.8823 0.1264 0.0021  1.6455 1.2326 0.3438
15    0.2322  22.1695     0.5298  0.1067  2.8742 0.1535 0.0022  1.8933 1.2092 0.2842

$All.CriticalValues
   CritValue_Duda CritValue_PseudoT2 Fvalue_Beale
2          0.8094            33.4492       0.0110
3          0.7816            25.9821       0.2967
4          0.7759            24.8432       0.1638
5          0.6927            15.5269       0.0046
6          0.7297            18.5198       0.2388
7          0.7278            18.3290       0.0668
8          0.5577            10.3089       0.0008
9          0.7237            17.9442       0.0005
10         0.6225            12.1297       0.0003
11         0.7045            16.3556       0.1702
12         0.6665            14.0075       0.0174
13         0.6355            12.6164       0.0009
14         0.3854             7.9739       1.0000
15         0.5139             9.4601       0.0009

$Best.nc
                    KL      CH Hartigan     CCC    Scott    Marriot   TrCovW   TraceW Friedman  Rubin  Cindex     DB Silhouette   Duda PseudoT2
Number_clusters 3.0000   2.000   3.0000 15.0000   3.0000 3.0000e+00     3.00   3.0000   3.0000  3.000 14.0000 2.0000     2.0000 3.0000   3.0000
Value_Index     3.5131 139.771  34.7103 13.7026 363.5475 2.5458e+17 10484.39 150.5628   6.7478 -0.239  0.2303 0.9783     0.4265 0.8329  18.6544
                 Beale Ratkowsky     Ball PtBiserial   Frey McClain   Dunn Hubert SDindex Dindex    SDbw
Number_clusters 3.0000    2.0000   3.0000     2.0000 2.0000  2.0000 4.0000      0  2.0000      0 15.0000
Value_Index     1.1917    0.4113 249.7967     0.6596 1.5894  0.3791 0.1782      0  1.3874      0  0.2842

$Best.partition
  1   2   3   4   5   6   7   8   9  10  11  12  13  14  15  16  17  18  19  20  21  22  23  24  25  26  27  28  29  30  31  32  33  34  35  36 
  1   1   1   1   1   1   1   2   2   2   2   2   2   2   3   3   3   1   3   3   3   3   3   3   3   3   3   3   3   3   3   3   1   3   3   3 
 37  38  39  40  41  42  43  44  45  46  47  48  49  50  51  52  53  54  55  56  57  58  59  60  61  62  63  64  65  66  67  68  69  70  71  72 
  1   3   3   3   3   3   3   3   3   3   3   3   3   3   3   3   1   3   3   3   3   3   3   3   3   3   3   3   3   3   3   3   3   3   2   3 
 73  74  75  76  77  78  79  80  81  82  83  84  85  86  87  88  89  90  91  92  93  94  95  96  97  98  99 100 101 102 103 104 105 106 107 108 
  3   3   3   3   3   3   3   3   3   3   3   3   3   3   3   3   3   3   3   3   3   3   3   3   3   3   3   3   3   3   3   3   3   3   3   3 
109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 145 
  3   3   3   3   3   3   1   1   1   1   1   1   1   1   1   1   1   1   1   1   1   1   1   1   1   1   1   1   1   1   1   1   1   1   1   1 
146 147 148 149 150 152 153 154 155 156 157 158 159 160 161 162 163 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 183 184 
  2   1   1   2   2   2   2   2   2   2   2   2   2   2   2   2   2   2   2   2   2   2   2   2   2   2   2   2   2   2   2   2   2   2   2   2 
185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 
  2   2   2   2   2   2   2   1   1   1   1   1   1   1   1   1 


# Silhouette method
set.seed(42)
fviz_nbclust(z, kmeans, method = "silhouette")+
  labs(subtitle = "Silhouette method")

d <- dist(z)
cl1 <- hclust(d, method="ward.D2")
memb2 <- cutree(cl1, 2)
memb3 <- cutree(cl1, 3)
memb4 <- cutree(cl1, 4)

library("cluster")
par(mfcol=c(2,2))

plot(pc_cor$x[,1], pc_cor$x[,2], col=memb2)
s2 <- silhouette(memb2, d)
plot(s2, col=1:2, border=NA)
plot(pc_cor$x[,1], pc_cor$x[,2], col=memb3)
s3 <- silhouette(memb3, d)
plot(s3, col=1:3, border=NA)

par(mfcol=c(1,3))

clusplot(z, memb2, col.p=memb2)
clusplot(z, memb3, col.p=memb3)
clusplot(z, memb4, col.p=memb4)

NA
NA

library("cluster")
set.seed(42)
cl2 <- pam(z, 4)
k_classic <- names(cl2$clustering[cl2$clustering == 1])
k_techno <- names(cl2$clustering[cl2$clustering == 2])
k_pop <- names(cl2$clustering[cl2$clustering == 3])
k_hiphop <- names(cl2$clustering[cl2$clustering == 4])

cl2$clustering[k_techno] <- 4
cl2$clustering[k_pop] <- 3
cl2$clustering[k_hiphop] <- 2
cl2$clustering[k_classic] <- 1

x$cluster <- as.factor(cl2$clustering)

x$Artist_Follower <- x$Artist_Follower/1000000

x$fa_1 <- x$factor_1
x$fa_2 <- x$factor_2
x$fa_3 <- x$factor_3
x$fa_4 <- x$factor_4


par(mfrow=c(3,3))
nx <- c("fa_1", "fa_2", "fa_3", "fa_4", "pc_1", "pc_2", "Artist_Follower", "days_release_orig", "Track_Popularity")
for (i in 1:length(nx)) {
  lmi <- lm(as.formula(paste('Artist_Popularity', nx[i], sep="~")), data=x)
  summary(lmi)
  plot(x[,nx[i]], x[,'Artist_Popularity'], xlab=nx[i], ylab='Artist Popularity', main=sprintf("R^2=%.2f", summary(lmi)$r.squared))
  abline(lmi, col="red")
}

NA
NA
NA

model1 <- lm(Artist_Popularity ~ fa_1 + fa_2 + fa_3 + fa_4 + pc_1 + pc_2 + Artist_Follower + days_release_orig, data = x)

summary(model1)

Call:
lm(formula = Artist_Popularity ~ fa_1 + fa_2 + fa_3 + fa_4 + 
    pc_1 + pc_2 + Artist_Follower + days_release_orig, data = x)

Residuals:
     Min       1Q   Median       3Q      Max 
-28.4051  -6.6722   0.3102   7.0904  21.0544 

Coefficients:
                    Estimate Std. Error t value Pr(>|t|)    
(Intercept)       64.5143111  0.8690218  74.238  < 2e-16 ***
fa_1              -0.0816610  1.1386538  -0.072 0.942904    
fa_2               1.8480927  0.7571880   2.441 0.015589 *  
fa_3               5.4094057  1.3919701   3.886 0.000141 ***
fa_4              -1.2986520  1.2829029  -1.012 0.312714    
pc_1               1.4023625  0.5912068   2.372 0.018706 *  
pc_2               7.4542956  0.6570917  11.344  < 2e-16 ***
Artist_Follower    0.8090864  0.1199206   6.747 1.84e-10 ***
days_release_orig -0.0004646  0.0002093  -2.220 0.027631 *  
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 9.765 on 187 degrees of freedom
Multiple R-squared:  0.6364,    Adjusted R-squared:  0.6208 
F-statistic: 40.91 on 8 and 187 DF,  p-value: < 2.2e-16
# fa_2: "Youtube"
# fa_3: "Artist content supply"
# fa_4: "Artist long-term activity"
# fa_1: "One hit wonder"

# pc_1 (between classic and Pop, Hip Hop, Techno): strong negative loadings: acousticness and instrumentalness, strong positive loadings: danceability, energy and loudness
# pc_2 (between Techno and Pop, Hip Hop): strong negative loadings: instrumentalness and tempo, strong positive loadings: speechiness and valence

vif(model1) # fa_1, fa_3, fa_4, pc_1 mild collinearity
             fa_1              fa_2              fa_3              fa_4              pc_1              pc_2   Artist_Follower days_release_orig 
         2.181823          1.127092          2.881682          2.202112          3.077405          1.290976          1.276809          1.607455 
library("perturb") # condition index
colldiag(model1)
Condition
Index   Variance Decomposition Proportions
         intercept fa_1  fa_2  fa_3  fa_4  pc_1  pc_2  Artist_Follower days_release_orig
1  1.000 0.002     0.017 0.003 0.030 0.035 0.031 0.000 0.000           0.029            
2  1.314 0.106     0.011 0.066 0.002 0.000 0.002 0.071 0.129           0.036            
3  1.462 0.003     0.135 0.054 0.013 0.014 0.008 0.093 0.103           0.023            
4  1.562 0.203     0.000 0.220 0.006 0.015 0.004 0.141 0.006           0.030            
5  1.914 0.002     0.036 0.381 0.041 0.049 0.017 0.373 0.025           0.005            
6  2.154 0.035     0.007 0.265 0.055 0.194 0.002 0.031 0.405           0.048            
7  2.926 0.635     0.006 0.006 0.000 0.069 0.042 0.099 0.121           0.739            
8  3.154 0.000     0.412 0.007 0.169 0.323 0.301 0.085 0.204           0.069            
9  3.912 0.015     0.376 0.000 0.683 0.301 0.593 0.107 0.007           0.022            
# I remove some variables according to insignificance (fa_1, fa_4)

model2 <- lm(Artist_Popularity ~ fa_2 + fa_3 + pc_1 + pc_2 + Artist_Follower + days_release_orig, data = x)
# summary(model2)
vif(model2)
             fa_2              fa_3              pc_1              pc_2   Artist_Follower days_release_orig 
         1.126103          1.674037          2.021619          1.082232          1.177174          1.510313 
select.cols <- c("Artist_Popularity", "fa_2", "fa_3", "pc_1", "pc_2", "Artist_Follower", "days_release_orig")

z <- as.data.frame(scale(dplyr::select(x, select.cols)))
Note: Using an external vector in selections is ambiguous.
i Use `all_of(select.cols)` instead of `select.cols` to silence this message.
i See <https://tidyselect.r-lib.org/reference/faq-external-vector.html>.
This message is displayed once per session.
model2_z <- lm(Artist_Popularity ~ fa_2 + fa_3 + pc_1 + pc_2 + Artist_Follower + days_release_orig, data = z)
# summary(model2_z)
vif(model2_z)
             fa_2              fa_3              pc_1              pc_2   Artist_Follower days_release_orig 
         1.126103          1.674037          2.021619          1.082232          1.177174          1.510313 
model2_z$coefficients^2
      (Intercept)              fa_2              fa_3              pc_1              pc_2   Artist_Follower days_release_orig 
     8.251688e-33      1.318360e-02      8.530193e-02      5.212851e-02      3.226773e-01      1.063958e-01      1.819168e-02 
sum(model2_z$coefficients^2) # 60% is the proportion in the variance of y explained by X and 40% of the variance of y is due to the variance of the residuals outside of the model IFF there was zero multicollinearity. In terms of relevance, 32% of variance in y is explained by pc_2, 10% by Artist_Follower, 8% by fa_3
[1] 0.5978788
sort(round((model2_z$coefficients)^2, digits = 4), decreasing = T)
             pc_2   Artist_Follower              fa_3              pc_1 days_release_orig              fa_2       (Intercept) 
           0.3227            0.1064            0.0853            0.0521            0.0182            0.0132            0.0000 
# Quadratic specification seems appropriate:

# variables without and with quadratic terms (fa_2, pc_2, Artist_Follower)

par(mfcol=c(2,3))

lm_fa2 <- lm(Artist_Popularity ~ fa_2, data=x)
plot(x[,'fa_2'], x[,'Artist_Popularity'], xlab='fa_2', main=sprintf("R^2=%.2f", summary(lm_fa2)$r.squared),
     ylim = c(min(min(fitted(lm_fa2)), min(x$Artist_Popularity)), max(max(fitted(lm_fa2)), max(x$Artist_Popularity))), ylab = 'Artist_Popularity')
abline(lm_fa2, col="red")

lmq_fa2 <- lm(Artist_Popularity ~ poly(fa_2, 2), data=x)
o <- order(x$fa_2)
plot(x[,'fa_2'], x[,'Artist_Popularity'], xlab= 'fa_2 + fa_2^2', main=sprintf("R^2=%.2f", summary(lmq_fa2)$r.squared),
     ylim = c(min(min(fitted(lmq_fa2)), min(x$Artist_Popularity)), max(max(fitted(lmq_fa2)), max(x$Artist_Popularity))), ylab = 'Artist_Popularity')
lines(x$fa_2[o], fitted(lmq_fa2)[o], col="red")

lm_follower <- lm(Artist_Popularity ~ Artist_Follower, data=x)
plot(x[,'Artist_Follower'], x[,'Artist_Popularity'], xlab='Artist_Follower', main=sprintf("R^2=%.2f", summary(lm_follower)$r.squared),
     ylim = c(min(min(fitted(lm_follower)), min(x$Artist_Popularity)), max(max(fitted(lm_follower)), max(x$Artist_Popularity))), ylab = 'Artist_Popularity')
abline(lm_follower, col="red")

lmq_follower <- lm(Artist_Popularity ~ poly(Artist_Follower, 2), data=x)
o <- order(x$Artist_Follower)
plot(x[,'Artist_Follower'], x[,'Artist_Popularity'], xlab= 'Artist_Follower + Artist_Follower^2', main=sprintf("R^2=%.2f", summary(lmq_follower)$r.squared),
     ylim = c(min(min(fitted(lmq_follower)), min(x$Artist_Popularity)), max(max(fitted(lmq_follower)), max(x$Artist_Popularity))), ylab = 'Artist_Popularity')
lines(x$Artist_Follower[o], fitted(lmq_follower)[o], col="red")

lm_pc2 <- lm(Artist_Popularity ~ pc_2, data=x)
plot(x[,'pc_2'], x[,'Artist_Popularity'], xlab='pc_2', main=sprintf("R^2=%.2f", summary(lm_pc2)$r.squared),
     ylim = c(min(min(fitted(lm_pc2)), min(x$Artist_Popularity)), max(max(fitted(lm_pc2)), max(x$Artist_Popularity))), ylab = 'Artist_Popularity')
abline(lm_pc2, col="red")

lmq_pc2 <- lm(Artist_Popularity ~ poly(pc_2, 2), data=x)
o <- order(x$pc_2)
plot(x[,'pc_2'], x[,'Artist_Popularity'], xlab= 'pc_2 + pc_2^2', main=sprintf("R^2=%.2f", summary(lmq_pc2)$r.squared),
     ylim = c(min(min(fitted(lmq_pc2)), min(x$Artist_Popularity)), max(max(fitted(lmq_pc2)), max(x$Artist_Popularity))), ylab = 'Artist_Popularity')
lines(x$pc_2[o], fitted(lmq_pc2)[o], col="red")

NA
NA

# Residuals normality

ks.test(model1$residuals, "pnorm", mean = mean(model1$residuals), sd=sd(model1$residuals))$statistic
         D 
0.04757442 
1.3581 / sqrt(length(model1$residuals))
[1] 0.09700714
ks.test(model2$residuals, "pnorm", mean = mean(model2$residuals), sd=sd(model2$residuals))$statistic
         D 
0.05823339 
1.3581 / sqrt(length(model2$residuals))
[1] 0.09700714
par(mfcol=c(2,2))
plot(model2)

par(mfcol=c(2,3))

nx <- names(model2$model)
for (i in 2:length(model2$model)) {
  plot(model2$model[,i], residuals(model2), xlab=nx[i])
  lines(lowess(model2$model[,i], residuals(model2)), col = "red")
  abline(h = 0, col = "black", lty = 2)
}

par(mfcol=c(1,1))

plot(model2$fitted.values, residuals(model2))
lines(lowess(model2$fitted.values, residuals(model2)), col = "red")
abline(h = 0, col = "black", lty = 2)


# residuals vs. predictors and fitted values suggests nonlinearity of the regression function

par(mfcol=c(2,3))
nx <- names(model2$model)
for (i in 2:length(model2$model)) {
  plot(model2$model[,i], rstandard(model2), xlab=nx[i])
  lines(lowess(model2$model[,i], rstandard(model2)), col = "red")
  # wenn Linie von 0 abweicht: Residuen sind biased, sprich deren Mittelwert nicht 0
  abline(h = 0, col = "black", lty = 2)
}

# standardized residuals vs. fitted values

par(mfcol=c(1,1))

plot(model2$fitted.values, rstandard(model2), main = 'Standardized residuals vs. fitted values')
lines(lowess(model2$fitted.values, rstandard(model2)), col = "red")
abline(h = 0, col = "black", lty = 2)

NA
NA

# Breusch-Pagan Test + Durbin-Watson Test / spherical errors (i.e. iid)
# model2 uses unscaled data
auxiliary_reg <- lm(model2$residuals^2 ~ fa_2 + fa_3 + pc_1 + pc_2 + Artist_Follower + days_release_orig, data = x)
summary(auxiliary_reg)

Call:
lm(formula = model2$residuals^2 ~ fa_2 + fa_3 + pc_1 + pc_2 + 
    Artist_Follower + days_release_orig, data = x)

Residuals:
    Min      1Q  Median      3Q     Max 
-209.99  -69.77  -26.75   39.15  712.88 

Coefficients:
                    Estimate Std. Error t value Pr(>|t|)    
(Intercept)        92.093388   9.620134   9.573  < 2e-16 ***
fa_2                0.973183   8.480588   0.115  0.90876    
fa_3              -29.748317  11.887822  -2.502  0.01318 *  
pc_1               -8.915701   5.369201  -1.661  0.09847 .  
pc_2                4.904291   6.741245   0.728  0.46782    
Artist_Follower     3.868671   1.290221   2.998  0.00308 ** 
days_release_orig  -0.004154   0.002273  -1.828  0.06918 .  
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 109.4 on 189 degrees of freedom
Multiple R-squared:  0.1067,    Adjusted R-squared:  0.0783 
F-statistic: 3.761 on 6 and 189 DF,  p-value: 0.001467
# R^2: 0.1067
# N = 196, N = nrow(x) 
# test stat: 
summary(auxiliary_reg)$r.square * nrow(x)
[1] 20.90497
# test stat: 20.90497
p <- ncol(model2$model)-1
qchisq(.95, df = p) # df = number of parameters p (without the constant!), acc. to Klinke: q = 5*(5+3)/2 WTF?
[1] 12.59159
# p_value = P(z > 12.59159) = 1-pchisq(20.90497, p=6) = 0.001908146 < 0.05 ,  reject H0 of homoskedasticity and conclude there is heteroskedasticity
1-pchisq(20.90497, p)
[1] 0.001908146
library("lmtest")
Lade n昼㸶tiges Paket: zoo

Attache Paket: 㤼㸱zoo㤼㸲

The following objects are masked from 㤼㸱package:base㤼㸲:

    as.Date, as.Date.numeric
bptest(model2)

    studentized Breusch-Pagan test

data:  model2
BP = 20.905, df = 6, p-value = 0.001908
# test statistic n*R^2_aux > X^2_crit = qchisq(.95, df = 5), hence reject H0 and conclude that there's
# heteroskedasticity and constancy of error variance does not hold --> inference invalidated

dwtest(model2)

    Durbin-Watson test

data:  model2
DW = 1.9149, p-value = 0.2323
alternative hypothesis: true autocorrelation is greater than 0
# Durbin Watson test confirms that the error variance-covariance is diagonal, i.e. var(eps | X) =  \Omega * I
# fail to reject H0: rho = 0 (alternative hypothesis is that there is autocorrelation in the errors)
# outlier detection / leverage

par(mfcol=c(1,2))
plot(hatvalues(model2), pch=19, main="Leverage", cex=0.5)
n <- nrow(model2$model)
p <- ncol(model2$model)-1
abline(h=(1:3)*(p+1)/n, col=c("black", "darkred", "red"))

plot(cooks.distance(model2), pch=19, main="Cook's distances",
     cex=0.5)
n <- nrow(model2$model)
p <- ncol(model2$model)-1
abline(h=4/n, col="red")


outlier_index_leverage <- as.numeric(rownames(x[hatvalues(model2) > 3*(p+1)/n, ]))
outlier_index_final <- outlier_index_leverage
length(outlier_index_leverage)
[1] 9
# There are 10 outliers that might be influential on the regression
x[outlier_index_leverage, c("Track_Title","Track_Artist", "Artist_Popularity", "Artist_Follower", "viewsCount", "pc_1",
                            "pc_2")]

# Rule of thumb Cook's distance
# movement of regression coefficients all together if ith observation is excluded
outlier_index_cooksd <- as.numeric(rownames(x[cooks.distance(model2) > 3*(p+1)/n, ]))
length(outlier_index_cooksd)
[1] 2
x[outlier_index_cooksd, c("Track_Title","Track_Artist", "Artist_Popularity", "Artist_Follower", "viewsCount", "pc_1",
                            "pc_2")]
NA

# differences

# SDBETA

SDBETA <- dfbetas(model2)
n <- nrow(model2$model)
par(mfcol=c(2,3))
plot(SDBETA[, 'fa_2'], main="fa_2", pch=19)
abline(h=c(-2,2)/sqrt(n), col="red")
plot(SDBETA[, 'fa_3'], main="fa_3", pch=19)
abline(h=c(-2,2)/sqrt(n), col="red")
plot(SDBETA[, 'pc_1'], main="pc_1", pch=19)
abline(h=c(-2,2)/sqrt(n), col="red")
plot(SDBETA[, 'pc_2'], main="pc_2", pch=19)
abline(h=c(-2,2)/sqrt(n), col="red")
plot(SDBETA[, 'Artist_Follower'], main="Artist_Follower", pch=19)
abline(h=c(-2,2)/sqrt(n), col="red")
plot(SDBETA[, 'days_release_orig'], main="days_release_orig", pch=19)
abline(h=c(-2,2)/sqrt(n), col="red")

# SDFITS
par(mfcol=c(1,1))

SDFITS <- dffits(model2)
plot(SDFITS, pch=19)
abline(h=c(-1,1), col="red")
n <- nrow(model2$model)
p <- ncol(model2$model)-1
abline(h=c(-2,2)*sqrt(p/n), col="darkred")


# As this is a small data set, it is sufficient to analyze |\delta*y_i| > 1, in this case two observations are identified
# of exceeding this limit. They are

outlier_index_sdfits <- as.numeric(names(which(abs(SDFITS) > 1))) 

x[outlier_index_sdfits, c("Track_Title","Track_Artist", "Artist_Popularity", "Artist_Follower", "viewsCount", "pc_1",
                          "pc_2")]
NA
# Add quadratic terms for fa_2, Artist_Follower and pc_2 first before excluding outliers!

x$fa_2sq <- x$fa_2^2
x$pc_2sq <- x$pc_2^2
x$Artist_Followersq <- x$Artist_Follower^2
# model2: lm(Artist_Popularity ~ fa_2 + fa_3 + pc_1 + pc_2 + Artist_Follower + days_release_orig, data = x)


model3 <- lm(Artist_Popularity ~ fa_2 + fa_2sq + fa_3 + pc_1 + pc_2 + pc_2sq + Artist_Follower + Artist_Followersq + days_release_orig, data = x)
summary(model3)

Call:
lm(formula = Artist_Popularity ~ fa_2 + fa_2sq + fa_3 + pc_1 + 
    pc_2 + pc_2sq + Artist_Follower + Artist_Followersq + days_release_orig, 
    data = x)

Residuals:
     Min       1Q   Median       3Q      Max 
-28.3742  -5.6832   0.3771   4.9769  19.6356 

Coefficients:
                    Estimate Std. Error t value Pr(>|t|)    
(Intercept)       67.5611323  0.9877844  68.397  < 2e-16 ***
fa_2               6.0858820  1.7276487   3.523 0.000538 ***
fa_2sq            -0.5504304  0.1615332  -3.408 0.000803 ***
fa_3               4.4969138  0.9010981   4.990 1.38e-06 ***
pc_1               1.8133635  0.4088899   4.435 1.57e-05 ***
pc_2               5.9511646  0.5374732  11.072  < 2e-16 ***
pc_2sq            -2.1818759  0.3886134  -5.615 7.08e-08 ***
Artist_Follower    1.6608731  0.2291803   7.247 1.10e-11 ***
Artist_Followersq -0.0233799  0.0049095  -4.762 3.84e-06 ***
days_release_orig -0.0004777  0.0001710  -2.794 0.005745 ** 
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 8.206 on 186 degrees of freedom
Multiple R-squared:  0.7446,    Adjusted R-squared:  0.7322 
F-statistic: 60.25 on 9 and 186 DF,  p-value: < 2.2e-16
vif(model3)
             fa_2            fa_2sq              fa_3              pc_1              pc_2            pc_2sq   Artist_Follower Artist_Followersq 
         8.309381          7.584394          1.710158          2.084608          1.223166          1.286026          6.603869          5.824194 
days_release_orig 
         1.519144 

ks.test(model3$residuals, "pnorm", mean = mean(model3$residuals), sd=sd(model3$residuals))$statistic
        D 
0.0367905 
1.3581 / sqrt(length(model3$residuals))
[1] 0.09700714
par(mfcol=c(2,2))
plot(model2)

par(mfcol=c(3,3))

nx <- names(model3$model)
for (i in 2:length(model3$model)) {
  plot(model3$model[,i], residuals(model3), xlab=nx[i])
  lines(lowess(model3$model[,i], residuals(model3)), col = "red")
  abline(h = 0, col = "black", lty = 2)
}

par(mfcol=c(1,1))

plot(model3$fitted.values, residuals(model3))
lines(lowess(model3$fitted.values, residuals(model3)), col = "red")
abline(h = 0, col = "black", lty = 2)

# residuals vs. predictors and fitted values suggests accounting for nonlinearity of the regression function has improved

par(mfcol=c(3,3))

nx <- names(model3$model)
for (i in 2:length(model3$model)) {
  plot(model3$model[,i], rstandard(model3), xlab=nx[i])
  lines(lowess(model3$model[,i], rstandard(model3)), col = "red")
  # wenn Linie von 0 abweicht: Residuen sind biased, sprich deren Mittelwert nicht 0
  abline(h = 0, col = "black", lty = 2)
}

# standardized residuals vs. fitted values

par(mfcol=c(1,1))

plot(model3$fitted.values, rstandard(model3), main = 'Standardized residuals vs. fitted values')
lines(lowess(model3$fitted.values, rstandard(model3)), col = "red")
abline(h = 0, col = "black", lty = 2)


# Breusch-Pagan Test + Durbin-Watson Test / spherical errors (i.e. iid)
# model2 uses unscaled data
auxiliary_reg <- lm(model3$residuals^2 ~ fa_2 + fa_2sq + fa_3 + pc_1 + pc_2 + pc_2sq + Artist_Follower + Artist_Followersq + days_release_orig, data = x)
summary(auxiliary_reg)

Call:
lm(formula = model3$residuals^2 ~ fa_2 + fa_2sq + fa_3 + pc_1 + 
    pc_2 + pc_2sq + Artist_Follower + Artist_Followersq + days_release_orig, 
    data = x)

Residuals:
   Min     1Q Median     3Q    Max 
-98.32 -52.32 -23.00  18.34 719.43 

Coefficients:
                    Estimate Std. Error t value Pr(>|t|)    
(Intercept)        78.727326  11.223118   7.015 4.15e-11 ***
fa_2                2.535328  19.629390   0.129   0.8974    
fa_2sq             -0.474656   1.835326  -0.259   0.7962    
fa_3              -24.009503  10.238196  -2.345   0.0201 *  
pc_1               -6.105726   4.645771  -1.314   0.1904    
pc_2               -9.200191   6.106722  -1.507   0.1336    
pc_2sq             -4.239332   4.415390  -0.960   0.3382    
Artist_Follower    -1.901275   2.603926  -0.730   0.4662    
Artist_Followersq   0.021242   0.055781   0.381   0.7038    
days_release_orig  -0.002471   0.001942  -1.272   0.2050    
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 93.24 on 186 degrees of freedom
Multiple R-squared:  0.06758,   Adjusted R-squared:  0.02246 
F-statistic: 1.498 on 9 and 186 DF,  p-value: 0.1513
# R^2: 0.06758
# N = 196, N = nrow(x) 
# test stat: 
summary(auxiliary_reg)$r.square * nrow(x)
[1] 13.24499
# test stat: 13.24499
p <- ncol(model3$model)-1
qchisq(.95, df = p) # df = number of parameters p (without the constant!), acc. to Klinke: q = 5*(5+3)/2 WTF?
[1] 16.91898
# p_value = P(z > 16.91898) = 1-pchisq(13.24499, p=9) = 0.1518304 > 0.05 ,  fail to reject H0 of homoskedasticity, i.e. constant variance of error term
1-pchisq(13.24499, p)
[1] 0.1518304
library("lmtest")
bptest(model3)

    studentized Breusch-Pagan test

data:  model3
BP = 13.245, df = 9, p-value = 0.1518
# test statistic n*R^2_aux > X^2_crit = qchisq(.95, df = 5), hence fail to reject H0 and conclude that there's
# no heteroskedasticity --> valid inference

dwtest(model3)

    Durbin-Watson test

data:  model3
DW = 2.0528, p-value = 0.5856
alternative hypothesis: true autocorrelation is greater than 0
# Durbin Watson test confirms that the error variance-covariance is diagonal, i.e. var(eps | X) =  \Omega * I
# fail to reject H0: rho = 0 (alternative hypothesis is that there is autocorrelation in the errors)
# outlier detection / leverage

par(mfcol=c(1,1))
plot(hatvalues(model3), pch=19, cex=0.5)
n <- nrow(model3$model)
p <- ncol(model3$model)-1
abline(h=(1:3)*(p+1)/n, col=c("black", "darkred", "red"))


plot(cooks.distance(model3), pch=19,
     cex=0.5)
n <- nrow(model3$model)
p <- ncol(model3$model)-1
abline(h=4/n, col="red")


outlier_index_leverage <- as.numeric(rownames(x[hatvalues(model3) > 3*(p+1)/n, ]))
outlier_index_final <- outlier_index_leverage
length(outlier_index_leverage)
[1] 10
# There are 10 outliers that might be influential on the regression
x[outlier_index_leverage, c("Track_Title","Track_Artist", "Genre", "Artist_Popularity", "Artist_Follower", "viewsCount")]




# Rule of thumb Cook's distance
# movement of regression coefficients all together if ith observation is excluded
outlier_index_cooksd <- as.numeric(rownames(x[cooks.distance(model3) > 3*(p+1)/n, ]))
length(outlier_index_cooksd)
[1] 2
x[outlier_index_cooksd, c("Track_Title","Track_Artist", "Artist_Popularity", "Artist_Follower", "viewsCount", "pc_1",
                            "pc_2")]

# same outliers detected as in model2 even after modification of the model

# differences

# SDBETA

SDBETA <- dfbetas(model3)
n <- nrow(model3$model)
par(mfcol=c(3,3))
plot(SDBETA[, 'fa_2'], main="fa_2", pch=19, ylab = "fa_2")
abline(h=c(-2,2)/sqrt(n), col="red")
plot(SDBETA[, 'fa_2sq'], main="fa_2sq", pch=19, ylab = "fa_2sq")
abline(h=c(-2,2)/sqrt(n), col="red")
plot(SDBETA[, 'fa_3'], main="fa_3", pch=19, ylab = "fa_3")
abline(h=c(-2,2)/sqrt(n), col="red")
plot(SDBETA[, 'pc_1'], main="pc_1", pch=19, ylab = "pc_1")
abline(h=c(-2,2)/sqrt(n), col="red")
plot(SDBETA[, 'pc_2'], main="pc_2", pch=19, ylab = "pc_2")
abline(h=c(-2,2)/sqrt(n), col="red")
plot(SDBETA[, 'pc_2sq'], main="pc_2sq", pch=19, ylab = "pc_2sq")
abline(h=c(-2,2)/sqrt(n), col="red")
plot(SDBETA[, 'Artist_Follower'], main="Artist_Follower", pch=19, ylab = "Artist_Follower")
abline(h=c(-2,2)/sqrt(n), col="red")
plot(SDBETA[, 'Artist_Followersq'], main="Artist_Followersq", pch=19, ylab = 'Artist_Followersq')
abline(h=c(-2,2)/sqrt(n), col="red")
plot(SDBETA[, 'days_release_orig'], main="days_release_orig", pch=19, ylab = 'days_release')
abline(h=c(-2,2)/sqrt(n), col="red")

# SDFITS
par(mfcol=c(1,1))

SDFITS <- dffits(model3)
plot(SDFITS, pch=19)
abline(h=c(-1,1), col="red")
n <- nrow(model3$model)
p <- ncol(model3$model)-1
abline(h=c(-2,2)*sqrt(p/n), col="darkred")


# As this is a small data set, it is sufficient to analyze |\delta*y_i| > 1, in this case two observations are identified
# of exceeding this limit. They are

outlier_index_sdfits <- as.numeric(names(which(abs(SDFITS) > 1))) 

x[outlier_index_sdfits, c("Track_Title","Track_Artist", "Artist_Popularity", "Artist_Follower", "viewsCount", "pc_1",
                          "pc_2")]
NA
NA
# Now exclude the outliers from leverage analysis

x_noout <- x[-outlier_index_final, ]

lm_final <- lm(Artist_Popularity ~ fa_2 + fa_2sq + fa_3 + pc_1 + pc_2 + pc_2sq + Artist_Follower + Artist_Followersq + days_release_orig, data = x_noout)
summary(lm_final) #  result: R^2 decreased a little bit

Call:
lm(formula = Artist_Popularity ~ fa_2 + fa_2sq + fa_3 + pc_1 + 
    pc_2 + pc_2sq + Artist_Follower + Artist_Followersq + days_release_orig, 
    data = x_noout)

Residuals:
     Min       1Q   Median       3Q      Max 
-27.4824  -5.2041  -0.3971   4.9615  19.4089 

Coefficients:
                    Estimate Std. Error t value Pr(>|t|)    
(Intercept)       67.5778523  1.1556264  58.477  < 2e-16 ***
fa_2              14.0294272  3.7773779   3.714 0.000274 ***
fa_2sq            -7.7026797  2.3813018  -3.235 0.001455 ** 
fa_3               4.1812469  1.1144664   3.752 0.000238 ***
pc_1               1.5664575  0.4198679   3.731 0.000257 ***
pc_2               5.2986744  0.5665857   9.352  < 2e-16 ***
pc_2sq            -1.9828773  0.4235979  -4.681 5.68e-06 ***
Artist_Follower    4.0311406  0.6231803   6.469 9.45e-10 ***
Artist_Followersq -0.1626515  0.0347541  -4.680 5.70e-06 ***
days_release_orig -0.0004396  0.0001751  -2.510 0.012974 *  
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 7.855 on 176 degrees of freedom
Multiple R-squared:  0.7448,    Adjusted R-squared:  0.7317 
F-statistic: 57.06 on 9 and 176 DF,  p-value: < 2.2e-16
vif(lm_final)
             fa_2            fa_2sq              fa_3              pc_1              pc_2            pc_2sq   Artist_Follower Artist_Followersq 
         3.847406          3.207554          1.750377          2.249691          1.401325          1.436766         13.792858         12.676491 
days_release_orig 
         1.497164 
# library("stargazer")
# stargazer::stargazer(model1, model2, model3, lm_final)

library("boot")

Attache Paket: 㤼㸱boot㤼㸲

The following object is masked from 㤼㸱package:robustbase㤼㸲:

    salinity

The following object is masked from 㤼㸱package:lattice㤼㸲:

    melanoma

The following object is masked from 㤼㸱package:car㤼㸲:

    logit

The following object is masked from 㤼㸱package:psych㤼㸲:

    logit
ci <- confint(lm_final, level = 0.95)

plot.confint <- function (b, b.boot, ci, main) {
  hist(b.boot, main=main); rug(b.boot)
  abline(v=b, col="red"); abline(v=ci, col="blue")
  abline(v=quantile(b.boot, c(0.025, 0.975)), col="green")
}

betahat <- boot(lm_final$model, R=999, 
                function(x, index) { lm(Artist_Popularity ~ fa_2 + fa_2sq + fa_3 + pc_1 + pc_2 + pc_2sq + Artist_Follower + Artist_Followersq + days_release_orig, data = x_noout, subset=index)$coefficients })

par(mfcol=c(2,3))

plot.confint(lm_final$coefficients[1], betahat$t[,1], ci[1,], "Intercept")
plot.confint(lm_final$coefficients[2], betahat$t[,2], ci[2,], "fa_2")
plot.confint(lm_final$coefficients[3], betahat$t[,3], ci[3,], "fa_2sq")
plot.confint(lm_final$coefficients[4], betahat$t[,4], ci[4,], "fa_3")
plot.confint(lm_final$coefficients[5], betahat$t[,5], ci[5,], "pc_1")
plot.confint(lm_final$coefficients[6], betahat$t[,6], ci[6,], "pc_2")

par(mfcol=c(2,2))


plot.confint(lm_final$coefficients[7], betahat$t[,7], ci[7,], "pc_2sq")
plot.confint(lm_final$coefficients[8], betahat$t[,8], ci[8,], "Artist_Follower")
plot.confint(lm_final$coefficients[9], betahat$t[,9], ci[9,], "Artist_Followersq")
plot.confint(lm_final$coefficients[10], betahat$t[,10], ci[10,], "days_release")

NA
NA

model5 <- lm(Artist_Popularity ~ fa_2 + fa_2sq, data = x)
summary(model5)

Call:
lm(formula = Artist_Popularity ~ fa_2 + fa_2sq, data = x)

Residuals:
    Min      1Q  Median      3Q     Max 
-37.506 -10.505   1.289  10.461  36.224 

Coefficients:
            Estimate Std. Error t value Pr(>|t|)    
(Intercept)  66.5658     1.0458  63.649  < 2e-16 ***
fa_2         18.0348     2.7106   6.654 2.88e-10 ***
fa_2sq       -1.3864     0.2653  -5.226 4.47e-07 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 14.2 on 193 degrees of freedom
Multiple R-squared:  0.2059,    Adjusted R-squared:  0.1977 
F-statistic: 25.03 on 2 and 193 DF,  p-value: 2.167e-10
vertex <- -model5$coefficients[2] / (2*model5$coefficients[3])
vertex
    fa_2 
6.504143 
(vertex <= max(x$fa_2)) & (vertex >= min(x$fa_2)) # so vertex is inside of range of Artist_Follower and there exists a quadratic component
fa_2 
TRUE 
# https://stats.idre.ucla.edu/r/faq/how-can-i-estimate-the-standard-error-of-transformed-regression-parameters-in-r-using-the-delta-method/
# https://www.statalist.org/forums/forum/general-stata-discussion/general/1376936-testing-whether-to-include-a-squared-term
# https://www.statalist.org/forums/forum/general-stata-discussion/general/1344089-how-to-find-the-turning-point-for-a-quadratic-function
# https://www.statalist.org/forums/forum/general-stata-discussion/general/1408413-significance-level-of-quadratic-term
# https://psych.unl.edu/psycrs/statpage/nonlin_eg.pdf ("true" quadratic component vs. skewed predictor)


library("msm") # equivalent to the nlcom function in Stata

# z-value according to the Delta method

vertex / msm::deltamethod(~ -x2 / (2 * x3), coef(model5), vcov(model5)) # = 12.8421
   fa_2 
12.8421 
# CI according to Stata

vertex - qt(0.975, df = 10000000) * msm::deltamethod(~ -x2 / (2 * x3), coef(model5), vcov(model5))
    fa_2 
5.511479 
vertex + qt(0.975, df = 10000000) * msm::deltamethod(~ -x2 / (2 * x3), coef(model5), vcov(model5))
    fa_2 
7.496808 
n <- nrow(model5$model)
d <- length(model5$coefficients)
df <- n - d

# http://sia.webpopix.org/nonlinearRegression.html

# p_value = P(z > 1.959964) = 1-pt(z, df=df) = 3.270659e-10 < 0.05 <-- reject H0 that vertex is equal to zero

# vertex for fa_2

vertex <- -model3$coefficients[2] / (2*model3$coefficients[3])
vertex
    fa_2 
5.528294 
(vertex <= max(x$fa_2)) & (vertex >= min(x$fa_2)) 
fa_2 
TRUE 
z <- vertex / msm::deltamethod(~ -x2 / (2 * x3), coef(model3), vcov(model3))
z
    fa_2 
8.814632 
lower_CI <- vertex - qt(0.975, df = 10000000) * msm::deltamethod(~ -x2 / (2 * x3), coef(model3), vcov(model3))
upper_CI <- vertex + qt(0.975, df = 10000000) * msm::deltamethod(~ -x2 / (2 * x3), coef(model3), vcov(model3))
n <- nrow(model3$model)
d <- length(model3$coefficients)
df <- n - d
1-pt(z, df=df)
        fa_2 
4.440892e-16 
# p_value = P(z > 1.959964) = 1-pt(z, df=df) = 4.440892e-16 < 0.05 <-- reject H0 that vertex is equal to zero

# vertex for pc_2

vertex <- -model3$coefficients[6] / (2*model3$coefficients[7])
vertex
    pc_2 
1.363773 
(vertex <= max(x$pc_2)) & (vertex >= min(x$pc_2)) 
pc_2 
TRUE 
z <- vertex / msm::deltamethod(~ -x6 / (2 * x7), coef(model3), vcov(model3))
z
    pc_2 
4.536158 
lower_CI <- vertex - qt(0.975, df = 10000000) * msm::deltamethod(~ -x6 / (2 * x7), coef(model3), vcov(model3))
upper_CI <- vertex + qt(0.975, df = 10000000) * msm::deltamethod(~ -x6 / (2 * x7), coef(model3), vcov(model3))
n <- nrow(model3$model)
d <- length(model3$coefficients)
df <- n - d
1-pt(z, df=df)
        pc_2 
5.125243e-06 
# p_value = P(z > 1.959964) = 1-pt(z, df=df) = 5.125243e-06 < 0.05 <-- reject H0 that vertex is equal to zero

# vertex for Artist_Follower

vertex <- -model3$coefficients[8] / (2*model3$coefficients[9])
vertex
Artist_Follower 
       35.51927 
(vertex <= max(x$Artist_Follower)) & (vertex >= min(x$Artist_Follower)) 
Artist_Follower 
           TRUE 
z <- vertex / msm::deltamethod(~ -x8 / (2 * x9), coef(model3), vcov(model3))
z
Artist_Follower 
       9.652667 
lower_CI <- vertex - qt(0.975, df = 10000000) * msm::deltamethod(~ -x8 / (2 * x9), coef(model3), vcov(model3))
upper_CI <- vertex + qt(0.975, df = 10000000) * msm::deltamethod(~ -x8 / (2 * x9), coef(model3), vcov(model3))
n <- nrow(model3$model)
d <- length(model3$coefficients)
df <- n - d
1-pt(z, df=df)
Artist_Follower 
              0 
#plot(Artist_Follower_trans, x$Artist_Popularity)

#min_max_normalize <- function(x)
#{
#  return( (10-1)*((x- min(x)) /(max(x)-min(x))) + 1)
#}

#min_fa_2 <- optimize(ksD, c(-10,10), x=min_max_normalize(x$pc_2))$minimum
#fa_2bc <- bcPower(min_max_normalize(x$pc_2), min_fa_2)

#fa_2bcsqrr <- fa_2bc ** 0.5
#fa_2bcsqrrcenter <- fa_2bcsqrr - mean(fa_2bcsqrr)
#fa_2bcsqrrcentersq <- fa_2bcsqrrcenter ** 2

#cor(cbind(x$Artist_Popularity, fa_2bcsqrrcenter, fa_2bcsqrrcentersq))





#plot(x$fa_2, x$Artist_Popularity)
#plot(x$pc_2, x$Artist_Popularity)

# baseline model:
# forward

lmi <- lm(Artist_Popularity~1, data=lm_final$model)
lmf <- MASS::stepAIC(lmi, scope=~
                fa_2 
               + fa_2sq
               + fa_3 
               + x_noout[,'fa_4'] 
               + pc_1
               + pc_2
               + pc_2sq
               + Artist_Follower
               + Artist_Followersq
               + days_release_orig
               + x_noout[, 'fa_1']
               + x_noout[, 'Track_Duration_ms']
               + x_noout[, 'Track_Popularity']
               ,direction = "forward")
Start:  AIC=1012.44
Artist_Popularity ~ 1

                                 Df Sum of Sq   RSS     AIC
+ pc_2                            1   17048.4 25493  919.20
+ x_noout[, "Track_Duration_ms"]  1   12472.7 30069  949.90
+ Artist_Follower                 1   11518.9 31023  955.71
+ pc_2sq                          1   10312.7 32229  962.81
+ fa_2                            1    8694.3 33848  971.92
+ x_noout[, "Track_Popularity"]   1    7421.3 35120  978.79
+ Artist_Followersq               1    6625.6 35916  982.95
+ pc_1                            1    1481.0 41061 1007.85
+ fa_2sq                          1    1418.9 41123 1008.13
<none>                                        42542 1012.44
+ days_release_orig               1     414.5 42127 1012.62
+ x_noout[, "fa_4"]               1     398.2 42144 1012.69
+ x_noout[, "fa_1"]               1     176.7 42365 1013.67
+ fa_3                            1     133.5 42408 1013.86

Step:  AIC=919.2
Artist_Popularity ~ pc_2

                                 Df Sum of Sq   RSS    AIC
+ Artist_Follower                 1    7669.0 17824 854.64
+ Artist_Followersq               1    4882.1 20611 881.66
+ x_noout[, "Track_Duration_ms"]  1    4480.6 21013 885.25
+ fa_2                            1    4209.3 21284 887.63
+ x_noout[, "Track_Popularity"]   1    3579.8 21914 893.06
+ pc_2sq                          1    2528.4 22965 901.77
+ days_release_orig               1    2251.5 23242 904.00
+ pc_1                            1    1731.7 23762 908.12
+ x_noout[, "fa_4"]               1    1088.3 24405 913.09
+ fa_2sq                          1     856.0 24637 914.85
+ x_noout[, "fa_1"]               1     736.0 24757 915.75
<none>                                        25493 919.20
+ fa_3                            1      25.1 25468 921.02

Step:  AIC=854.64
Artist_Popularity ~ pc_2 + Artist_Follower

                                 Df Sum of Sq   RSS    AIC
+ x_noout[, "Track_Popularity"]   1    3198.4 14626 819.85
+ x_noout[, "Track_Duration_ms"]  1    2853.7 14971 824.19
+ Artist_Followersq               1    2136.9 15688 832.89
+ pc_2sq                          1    1360.1 16464 841.88
+ days_release_orig               1    1225.0 16599 843.40
+ fa_2                            1    1157.3 16667 844.15
+ x_noout[, "fa_4"]               1    1050.6 16774 845.34
+ pc_1                            1     843.0 16981 847.63
<none>                                        17824 854.64
+ fa_2sq                          1      96.1 17728 855.63
+ fa_3                            1      71.3 17753 855.89
+ x_noout[, "fa_1"]               1      19.1 17805 856.44

Step:  AIC=819.85
Artist_Popularity ~ pc_2 + Artist_Follower + x_noout[, "Track_Popularity"]

                                 Df Sum of Sq   RSS    AIC
+ x_noout[, "Track_Duration_ms"]  1   1841.18 12785 796.83
+ Artist_Followersq               1   1806.26 12820 797.34
+ pc_2sq                          1   1150.74 13475 806.61
+ days_release_orig               1    501.06 14125 815.37
+ pc_1                            1    417.59 14208 816.47
+ x_noout[, "fa_4"]               1    282.55 14343 818.23
+ fa_3                            1    198.73 14427 819.31
<none>                                        14626 819.85
+ fa_2                            1    142.21 14484 820.04
+ x_noout[, "fa_1"]               1     23.05 14603 821.56
+ fa_2sq                          1      7.91 14618 821.75

Step:  AIC=796.83
Artist_Popularity ~ pc_2 + Artist_Follower + x_noout[, "Track_Popularity"] + 
    x_noout[, "Track_Duration_ms"]

                    Df Sum of Sq   RSS    AIC
+ Artist_Followersq  1   1508.10 11277 775.48
+ pc_2sq             1   1241.24 11544 779.83
+ fa_3               1    664.00 12121 788.91
+ x_noout[, "fa_1"]  1    147.88 12637 796.67
<none>                           12785 796.83
+ fa_2               1     83.00 12702 797.62
+ days_release_orig  1     71.00 12714 797.79
+ pc_1               1      6.16 12779 798.74
+ x_noout[, "fa_4"]  1      5.55 12779 798.75
+ fa_2sq             1      4.04 12781 798.77

Step:  AIC=775.48
Artist_Popularity ~ pc_2 + Artist_Follower + x_noout[, "Track_Popularity"] + 
    x_noout[, "Track_Duration_ms"] + Artist_Followersq

                    Df Sum of Sq   RSS    AIC
+ pc_2sq             1   1076.83 10200 758.82
+ fa_3               1    598.01 10679 767.35
+ fa_2sq             1    185.08 11092 774.40
<none>                           11277 775.48
+ x_noout[, "fa_1"]  1     84.76 11192 776.08
+ days_release_orig  1     36.02 11241 776.89
+ x_noout[, "fa_4"]  1      5.24 11272 777.40
+ pc_1               1      3.80 11273 777.42
+ fa_2               1      0.27 11276 777.48

Step:  AIC=758.82
Artist_Popularity ~ pc_2 + Artist_Follower + x_noout[, "Track_Popularity"] + 
    x_noout[, "Track_Duration_ms"] + Artist_Followersq + pc_2sq

                    Df Sum of Sq     RSS    AIC
+ fa_3               1   250.781  9949.1 756.19
+ fa_2sq             1   177.173 10022.7 757.56
+ days_release_orig  1   167.813 10032.1 757.73
<none>                           10199.9 758.82
+ pc_1               1    98.887 10101.0 759.00
+ x_noout[, "fa_1"]  1    81.693 10118.2 759.32
+ x_noout[, "fa_4"]  1    28.307 10171.6 760.30
+ fa_2               1     0.969 10198.9 760.80

Step:  AIC=756.19
Artist_Popularity ~ pc_2 + Artist_Follower + x_noout[, "Track_Popularity"] + 
    x_noout[, "Track_Duration_ms"] + Artist_Followersq + pc_2sq + 
    fa_3

                    Df Sum of Sq    RSS    AIC
+ pc_1               1    546.47 9402.6 747.68
+ days_release_orig  1    302.11 9647.0 752.45
+ x_noout[, "fa_4"]  1    190.84 9758.3 754.58
+ fa_2sq             1    159.29 9789.8 755.18
<none>                           9949.1 756.19
+ x_noout[, "fa_1"]  1      1.79 9947.3 758.15
+ fa_2               1      0.74 9948.4 758.17

Step:  AIC=747.68
Artist_Popularity ~ pc_2 + Artist_Follower + x_noout[, "Track_Popularity"] + 
    x_noout[, "Track_Duration_ms"] + Artist_Followersq + pc_2sq + 
    fa_3 + pc_1

                    Df Sum of Sq    RSS    AIC
+ fa_2sq             1   156.732 9245.9 746.55
+ days_release_orig  1   117.363 9285.3 747.34
<none>                           9402.6 747.68
+ x_noout[, "fa_1"]  1    25.394 9377.2 749.17
+ x_noout[, "fa_4"]  1     4.049 9398.6 749.60
+ fa_2               1     2.462 9400.2 749.63

Step:  AIC=746.55
Artist_Popularity ~ pc_2 + Artist_Follower + x_noout[, "Track_Popularity"] + 
    x_noout[, "Track_Duration_ms"] + Artist_Followersq + pc_2sq + 
    fa_3 + pc_1 + fa_2sq

                    Df Sum of Sq    RSS    AIC
+ fa_2               1   191.308 9054.6 744.66
+ days_release_orig  1   105.159 9140.7 746.42
<none>                           9245.9 746.55
+ x_noout[, "fa_1"]  1    37.651 9208.2 747.79
+ x_noout[, "fa_4"]  1     0.048 9245.8 748.55

Step:  AIC=744.66
Artist_Popularity ~ pc_2 + Artist_Follower + x_noout[, "Track_Popularity"] + 
    x_noout[, "Track_Duration_ms"] + Artist_Followersq + pc_2sq + 
    fa_3 + pc_1 + fa_2sq + fa_2

                    Df Sum of Sq    RSS    AIC
+ days_release_orig  1   109.025 8945.6 744.41
<none>                           9054.6 744.66
+ x_noout[, "fa_1"]  1    36.249 9018.3 745.92
+ x_noout[, "fa_4"]  1     0.008 9054.6 746.66

Step:  AIC=744.41
Artist_Popularity ~ pc_2 + Artist_Follower + x_noout[, "Track_Popularity"] + 
    x_noout[, "Track_Duration_ms"] + Artist_Followersq + pc_2sq + 
    fa_3 + pc_1 + fa_2sq + fa_2 + days_release_orig

                    Df Sum of Sq    RSS    AIC
<none>                           8945.6 744.41
+ x_noout[, "fa_1"]  1    49.184 8896.4 745.38
+ x_noout[, "fa_4"]  1     6.072 8939.5 746.28
summary(lmf)

Call:
lm(formula = Artist_Popularity ~ pc_2 + Artist_Follower + x_noout[, 
    "Track_Popularity"] + x_noout[, "Track_Duration_ms"] + Artist_Followersq + 
    pc_2sq + fa_3 + pc_1 + fa_2sq + fa_2 + days_release_orig, 
    data = lm_final$model)

Residuals:
     Min       1Q   Median       3Q      Max 
-23.0380  -5.2954   0.3417   4.5413  17.3011 

Coefficients:
                                 Estimate Std. Error t value Pr(>|t|)    
(Intercept)                     5.987e+01  3.045e+00  19.664  < 2e-16 ***
pc_2                            4.357e+00  5.531e-01   7.877 3.48e-13 ***
Artist_Follower                 4.143e+00  5.731e-01   7.229 1.48e-11 ***
x_noout[, "Track_Popularity"]   1.936e-01  4.086e-02   4.737 4.48e-06 ***
x_noout[, "Track_Duration_ms"] -1.528e-05  4.591e-06  -3.329 0.001065 ** 
Artist_Followersq              -1.614e-01  3.182e-02  -5.071 1.01e-06 ***
pc_2sq                         -1.667e+00  3.918e-01  -4.255 3.41e-05 ***
fa_3                            3.769e+00  1.024e+00   3.682 0.000308 ***
pc_1                            9.810e-01  4.271e-01   2.297 0.022806 *  
fa_2sq                         -5.659e+00  2.205e+00  -2.566 0.011127 *  
fa_2                            7.210e+00  3.700e+00   1.948 0.052974 .  
days_release_orig              -2.378e-04  1.633e-04  -1.456 0.147127    
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 7.17 on 174 degrees of freedom
Multiple R-squared:  0.7897,    Adjusted R-squared:  0.7764 
F-statistic: 59.41 on 11 and 174 DF,  p-value: < 2.2e-16
# backward:
lma <- lm(Artist_Popularity ~ fa_2 + fa_2sq + fa_3 + fa_4 + pc_1 + pc_2 + pc_2sq + Artist_Follower 
          + Artist_Followersq + fa_1 + Track_Duration_ms
          + days_release_orig + Track_Popularity, 
          data= x_noout)

lmb <- MASS::stepAIC(lma)
Start:  AIC=747.24
Artist_Popularity ~ fa_2 + fa_2sq + fa_3 + fa_4 + pc_1 + pc_2 + 
    pc_2sq + Artist_Follower + Artist_Followersq + fa_1 + Track_Duration_ms + 
    days_release_orig + Track_Popularity

                    Df Sum of Sq     RSS    AIC
- fa_4               1      6.65  8896.4 745.38
- fa_1               1     49.76  8939.5 746.28
<none>                            8889.7 747.24
- days_release_orig  1    128.61  9018.3 747.92
- fa_2               1    195.49  9085.2 749.29
- pc_1               1    271.47  9161.2 750.84
- fa_2sq             1    355.61  9245.3 752.54
- Track_Duration_ms  1    536.66  9426.4 756.15
- fa_3               1    615.81  9505.5 757.70
- pc_2sq             1    830.66  9720.4 761.86
- Track_Popularity   1   1166.70 10056.4 768.18
- Artist_Followersq  1   1363.69 10253.4 771.79
- pc_2               1   2572.29 11462.0 792.51
- Artist_Follower    1   2719.78 11609.5 794.89

Step:  AIC=745.38
Artist_Popularity ~ fa_2 + fa_2sq + fa_3 + pc_1 + pc_2 + pc_2sq + 
    Artist_Follower + Artist_Followersq + fa_1 + Track_Duration_ms + 
    days_release_orig + Track_Popularity

                    Df Sum of Sq     RSS    AIC
- fa_1               1     49.18  8945.6 744.41
<none>                            8896.4 745.38
- days_release_orig  1    121.96  9018.3 745.92
- fa_2               1    193.77  9090.1 747.39
- pc_1               1    295.87  9192.2 749.47
- fa_2sq             1    349.34  9245.7 750.55
- Track_Duration_ms  1    530.56  9426.9 754.16
- fa_3               1    632.49  9528.9 756.16
- pc_2sq             1    828.02  9724.4 759.94
- Track_Popularity   1   1183.07 10079.4 766.61
- Artist_Followersq  1   1358.68 10255.0 769.82
- pc_2               1   2647.56 11543.9 791.84
- Artist_Follower    1   2723.31 11619.7 793.06

Step:  AIC=744.41
Artist_Popularity ~ fa_2 + fa_2sq + fa_3 + pc_1 + pc_2 + pc_2sq + 
    Artist_Follower + Artist_Followersq + Track_Duration_ms + 
    days_release_orig + Track_Popularity

                    Df Sum of Sq     RSS    AIC
<none>                            8945.6 744.41
- days_release_orig  1     109.0  9054.6 744.66
- fa_2               1     195.2  9140.7 746.42
- pc_1               1     271.3  9216.8 747.97
- fa_2sq             1     338.5  9284.1 749.32
- Track_Duration_ms  1     569.7  9515.2 753.89
- fa_3               1     697.0  9642.6 756.37
- pc_2sq             1     930.7  9876.3 760.82
- Track_Popularity   1    1153.8 10099.4 764.97
- Artist_Followersq  1    1321.8 10267.4 768.04
- Artist_Follower    1    2686.6 11632.1 791.26
- pc_2               1    3190.0 12135.5 799.13
summary(lmb)

Call:
lm(formula = Artist_Popularity ~ fa_2 + fa_2sq + fa_3 + pc_1 + 
    pc_2 + pc_2sq + Artist_Follower + Artist_Followersq + Track_Duration_ms + 
    days_release_orig + Track_Popularity, data = x_noout)

Residuals:
     Min       1Q   Median       3Q      Max 
-23.0380  -5.2954   0.3417   4.5413  17.3011 

Coefficients:
                    Estimate Std. Error t value Pr(>|t|)    
(Intercept)        5.987e+01  3.045e+00  19.664  < 2e-16 ***
fa_2               7.210e+00  3.700e+00   1.948 0.052974 .  
fa_2sq            -5.659e+00  2.205e+00  -2.566 0.011127 *  
fa_3               3.769e+00  1.024e+00   3.682 0.000308 ***
pc_1               9.810e-01  4.271e-01   2.297 0.022806 *  
pc_2               4.357e+00  5.531e-01   7.877 3.48e-13 ***
pc_2sq            -1.667e+00  3.918e-01  -4.255 3.41e-05 ***
Artist_Follower    4.143e+00  5.731e-01   7.229 1.48e-11 ***
Artist_Followersq -1.614e-01  3.182e-02  -5.071 1.01e-06 ***
Track_Duration_ms -1.528e-05  4.591e-06  -3.329 0.001065 ** 
days_release_orig -2.378e-04  1.633e-04  -1.456 0.147127    
Track_Popularity   1.936e-01  4.086e-02   4.737 4.48e-06 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 7.17 on 174 degrees of freedom
Multiple R-squared:  0.7897,    Adjusted R-squared:  0.7764 
F-statistic: 59.41 on 11 and 174 DF,  p-value: < 2.2e-16

# Regularization

sel.var <- c("fa_1", "fa_2", "fa_2sq", "fa_3", "fa_4", "pc_1", "pc_2", "pc_2sq", "Artist_Follower", "Artist_Followersq", "Artist_Popularity", "days_release_orig", "Track_Popularity", "Track_Duration_ms") 

df <- dplyr::select(x_noout, sel.var)
Note: Using an external vector in selections is ambiguous.
i Use `all_of(sel.var)` instead of `sel.var` to silence this message.
i See <https://tidyselect.r-lib.org/reference/faq-external-vector.html>.
This message is displayed once per session.
lmfull <- lm(Artist_Popularity~., data=df)
lmback <- step(lmfull)
Start:  AIC=747.24
Artist_Popularity ~ fa_1 + fa_2 + fa_2sq + fa_3 + fa_4 + pc_1 + 
    pc_2 + pc_2sq + Artist_Follower + Artist_Followersq + days_release_orig + 
    Track_Popularity + Track_Duration_ms

                    Df Sum of Sq     RSS    AIC
- fa_4               1      6.65  8896.4 745.38
- fa_1               1     49.76  8939.5 746.28
<none>                            8889.7 747.24
- days_release_orig  1    128.61  9018.3 747.92
- fa_2               1    195.49  9085.2 749.29
- pc_1               1    271.47  9161.2 750.84
- fa_2sq             1    355.61  9245.3 752.54
- Track_Duration_ms  1    536.66  9426.4 756.15
- fa_3               1    615.81  9505.5 757.70
- pc_2sq             1    830.66  9720.4 761.86
- Track_Popularity   1   1166.70 10056.4 768.18
- Artist_Followersq  1   1363.69 10253.4 771.79
- pc_2               1   2572.29 11462.0 792.51
- Artist_Follower    1   2719.78 11609.5 794.89

Step:  AIC=745.38
Artist_Popularity ~ fa_1 + fa_2 + fa_2sq + fa_3 + pc_1 + pc_2 + 
    pc_2sq + Artist_Follower + Artist_Followersq + days_release_orig + 
    Track_Popularity + Track_Duration_ms

                    Df Sum of Sq     RSS    AIC
- fa_1               1     49.18  8945.6 744.41
<none>                            8896.4 745.38
- days_release_orig  1    121.96  9018.3 745.92
- fa_2               1    193.77  9090.1 747.39
- pc_1               1    295.87  9192.2 749.47
- fa_2sq             1    349.34  9245.7 750.55
- Track_Duration_ms  1    530.56  9426.9 754.16
- fa_3               1    632.49  9528.9 756.16
- pc_2sq             1    828.02  9724.4 759.94
- Track_Popularity   1   1183.07 10079.4 766.61
- Artist_Followersq  1   1358.68 10255.0 769.82
- pc_2               1   2647.56 11543.9 791.84
- Artist_Follower    1   2723.31 11619.7 793.06

Step:  AIC=744.41
Artist_Popularity ~ fa_2 + fa_2sq + fa_3 + pc_1 + pc_2 + pc_2sq + 
    Artist_Follower + Artist_Followersq + days_release_orig + 
    Track_Popularity + Track_Duration_ms

                    Df Sum of Sq     RSS    AIC
<none>                            8945.6 744.41
- days_release_orig  1     109.0  9054.6 744.66
- fa_2               1     195.2  9140.7 746.42
- pc_1               1     271.3  9216.8 747.97
- fa_2sq             1     338.5  9284.1 749.32
- Track_Duration_ms  1     569.7  9515.2 753.89
- fa_3               1     697.0  9642.6 756.37
- pc_2sq             1     930.7  9876.3 760.82
- Track_Popularity   1    1153.8 10099.4 764.97
- Artist_Followersq  1    1321.8 10267.4 768.04
- Artist_Follower    1    2686.6 11632.1 791.26
- pc_2               1    3190.0 12135.5 799.13
library("glmnet")
Lade n昼㸶tiges Paket: Matrix
Loaded glmnet 3.0-2
xl <- as.matrix(df[,-11])
yl <- df[,11]
set.seed(42)
lmlasso <- cv.glmnet(xl, yl, alpha =1) # cross-validation finds the value of lambda which minimizes MSE, alpha = 1 for LASSO
plot(lmlasso) # displays the bias-variance trade-off (MSE = variance + bias^2)

best.lambda <- lmlasso$lambda.min
lmlasso$lambda.1se
[1] 0.3361553
cl <- list(coef(lmlasso, s="lambda.min")[,1], coef(lmlasso, s="lambda.1se")[,1], coef(lmback), coef(lmfull))
cf <- matrix(NA, nrow=dim(xl)[2]+1, ncol=length(cl))
row.names(cf) <- names(lmfull$coefficients)
i <- 1
for (ci in cl) {
  cf[names(ci),i] <- ci[names(ci)]
  i <- i+1
}
colnames(cf) <- c("min", "1se", "back", "full")
cf[cf==0] <- NA
cf
                            min           1se          back          full
(Intercept)        6.068034e+01  6.114886e+01  5.987096e+01  5.933345e+01
fa_1                         NA            NA            NA -8.893245e-01
fa_2               4.332808e+00  2.626698e+00  7.209531e+00  7.217918e+00
fa_2sq            -2.462341e+00 -5.763024e-01 -5.658801e+00 -5.837164e+00
fa_3               3.059559e+00  2.638256e+00  3.769373e+00  4.505508e+00
fa_4                         NA            NA            NA  3.733388e-01
pc_1               7.606562e-01  6.300647e-01  9.809889e-01  1.111732e+00
pc_2               4.498899e+00  4.581303e+00  4.356624e+00  4.151896e+00
pc_2sq            -1.670583e+00 -1.671870e+00 -1.667241e+00 -1.600879e+00
Artist_Follower    2.622179e+00  1.731344e+00  4.143098e+00  4.255461e+00
Artist_Followersq -7.241574e-02 -2.026752e-02 -1.613611e-01 -1.647535e-01
days_release_orig -2.304611e-04 -2.257810e-04 -2.377630e-04 -2.659810e-04
Track_Popularity   1.893293e-01  1.868568e-01  1.935870e-01  1.997832e-01
Track_Duration_ms -1.707089e-05 -1.812532e-05 -1.528159e-05 -1.495624e-05
# variable importance
glmmod <- glmnet(xl, yl, lambda = best.lambda)
coefs = coef(glmmod)[,1]
coefs = sort(abs(coefs), decreasing = T)
coefs
      (Intercept)              pc_2              fa_2              fa_3   Artist_Follower            fa_2sq            pc_2sq              pc_1 
     6.068265e+01      4.498154e+00      4.340450e+00      3.059708e+00      2.623252e+00      2.466486e+00      1.670685e+00      7.606794e-01 
 Track_Popularity Artist_Followersq days_release_orig Track_Duration_ms              fa_1              fa_4 
     1.892949e-01      7.248197e-02      2.303984e-04      1.707017e-05      0.000000e+00      0.000000e+00 
coef(lmlasso, s="lambda.1se")
14 x 1 sparse Matrix of class "dgCMatrix"
                              1
(Intercept)        6.114886e+01
fa_1               .           
fa_2               2.626698e+00
fa_2sq            -5.763024e-01
fa_3               2.638256e+00
fa_4               .           
pc_1               6.300647e-01
pc_2               4.581303e+00
pc_2sq            -1.671870e+00
Artist_Follower    1.731344e+00
Artist_Followersq -2.026752e-02
days_release_orig -2.257810e-04
Track_Popularity   1.868568e-01
Track_Duration_ms -1.812532e-05
######################
# Performance metrics - full data

performace_metrics <- matrix(nrow = 10, ncol = 6)

RMSE(predict(lmfull, newdata = df), df$Artist_Popularity)
[1] 6.913336
R2(predict(lmfull, newdata = df), df$Artist_Popularity)
[1] 0.7910356
performace_metrics[1,1] <- RMSE(predict(lmfull, newdata = df), df$Artist_Popularity)
performace_metrics[1,2] <- R2(predict(lmfull, newdata = df), df$Artist_Popularity)


# x_model <- model.matrix(Artist_Popularity~., x_noout)

library("glmnet")
"Ridge regression"
[1] "Ridge regression"
set.seed(42)
best.lambda <- cv.glmnet(x = as.matrix(df[,-11]), y = as.matrix(df[,11]), alpha = 0)$lambda.min
ridge.cv <- glmnet(x = as.matrix(df[,-11]), y = as.matrix(df[,11]), alpha = 0, lambda = best.lambda)
# as.numeric(coef(ridge.cv)[,1]) %*% as.numeric(x_model[1,]) equivalent
# predict(ridge.cv, newx = as.matrix(x_noout[1,-8])) equivalent
RMSE(predict(ridge.cv, newx = as.matrix(df[,-11])), as.matrix(df[,11]))
[1] 7.262922
R2(predict(ridge.cv, newx = as.matrix(df[,-11])), as.matrix(df[,11]))
            s0
[1,] 0.7710931
performace_metrics[2,1] <- RMSE(predict(ridge.cv, newx = as.matrix(df[,-11])), as.matrix(df[,11]))
performace_metrics[2,2] <- R2(predict(ridge.cv, newx = as.matrix(df[,-11])), as.matrix(df[,11]))

"LASSO regression"
[1] "LASSO regression"
set.seed(42)
best.lambda <- cv.glmnet(x = as.matrix(df[,-11]), y = as.matrix(df[,11]), alpha = 1)$lambda.min
lasso.cv <- glmnet(x = as.matrix(df[,-11]), y = as.matrix(df[,11]), alpha = 1, lambda = best.lambda)
RMSE(predict(lasso.cv, newx = as.matrix(df[,-11])), as.matrix(df[,11]))
[1] 7.135634
R2(predict(lasso.cv, newx = as.matrix(df[,-11])), as.matrix(df[,11]))
          s0
[1,] 0.77876
performace_metrics[3,1] <- RMSE(predict(lasso.cv, newx = as.matrix(df[,-11])), as.matrix(df[,11]))
performace_metrics[3,2] <- R2(predict(lasso.cv, newx = as.matrix(df[,-11])), as.matrix(df[,11]))

"Elasticnet regression"
[1] "Elasticnet regression"
set.seed(42)
best.lambda <- cv.glmnet(x = as.matrix(df[,-11]), y = as.matrix(df[,11]), alpha = 0.5)$lambda.min
elasticnet.cv <- glmnet(x = as.matrix(df[,-11]), y = as.matrix(df[,11]), alpha = 0.5, lambda = best.lambda)
RMSE(predict(elasticnet.cv, newx = as.matrix(df[,-11])), as.matrix(df[,11]))
[1] 7.17704
R2(predict(elasticnet.cv, newx = as.matrix(df[,-11])), as.matrix(df[,11]))
            s0
[1,] 0.7762593
performace_metrics[4,1] <- RMSE(predict(elasticnet.cv, newx = as.matrix(df[,-11])), as.matrix(df[,11]))
performace_metrics[4,2] <- R2(predict(elasticnet.cv, newx = as.matrix(df[,-11])), as.matrix(df[,11]))

x_full <- model.matrix(Artist_Popularity~., df)[,-1]

ridge_res2 <- as.numeric(df$Artist_Popularity - predict(ridge.cv, x_full))^2
lasso_res2 <- as.numeric(df$Artist_Popularity - predict(lasso.cv, x_full))^2
elasticnet_res2 <- as.numeric(df$Artist_Popularity - predict(elasticnet.cv, x_full))^2

par(mfcol=c(2,2))
barplot((df$Artist_Popularity - predict(lmfull, df))^2)
Axis(side=2)
title(main = paste("LM (full sample): RMSE =",round(RMSE(predict(lmfull, df), df$Artist_Popularity), digits = 2), ", R^2:", round(R2(predict(lmfull, df), df$Artist_Popularity), digits = 2)))
barplot(ridge_res2, main = paste("Ridge (full sample): RMSE =",round(RMSE(predict(ridge.cv, x_full), df$Artist_Popularity), digits = 2), ", R^2:", round(R2(predict(ridge.cv, x_full), df$Artist_Popularity), digits = 2)))
barplot(lasso_res2, main = paste("LASSO (full sample): RMSE =",round(RMSE(predict(lasso.cv, x_full), df$Artist_Popularity), digits = 2), ", R^2:", round(R2(predict(lasso.cv, x_full), df$Artist_Popularity), digits = 2)))
barplot(elasticnet_res2, main = paste("Elasticnet (full sample): RMSE =",round(RMSE(predict(elasticnet.cv, x_full), df$Artist_Popularity), digits = 2), ", R^2:", round(R2(predict(elasticnet.cv, x_full), df$Artist_Popularity), digits = 2)))

NA
NA
NA
NA
NA

library("caret")

# Splitting in train and test data

set.seed(42)
idx.train <- createDataPartition(y = df$Artist_Popularity, p = 0.75, list = FALSE)
train <- df[idx.train, ]
test <- df[-idx.train,]

lm_train <- lm(Artist_Popularity~., data=train)
RMSE(predict(lm_train, newdata = train), train$Artist_Popularity)
[1] 6.914704
R2(predict(lm_train, newdata = train), train$Artist_Popularity)
[1] 0.799549
performace_metrics[1,3] <- RMSE(predict(lm_train, newdata = train), train$Artist_Popularity)
performace_metrics[1,4] <- R2(predict(lm_train, newdata = train), train$Artist_Popularity)

RMSE(predict(lm_train, newdata = test), test$Artist_Popularity)
[1] 7.138894
R2(predict(lm_train, newdata = test), test$Artist_Popularity)
[1] 0.7464997
performace_metrics[1,5] <- RMSE(predict(lm_train, newdata = test), test$Artist_Popularity)
performace_metrics[1,6] <- R2(predict(lm_train, newdata = test), test$Artist_Popularity)

"Ridge regression"
[1] "Ridge regression"
set.seed(42)
best.lambda <- cv.glmnet(x = as.matrix(train[,-11]), y = as.matrix(train[,11]), alpha = 0)$lambda.min
ridge.cv <- glmnet(x = as.matrix(train[,-11]), y = as.matrix(train[,11]), alpha = 0, lambda = best.lambda)
# as.numeric(coef(ridge.cv)[,1]) %*% as.numeric(x_model[1,]) equivalent
# predict(ridge.cv, newx = as.matrix(x_noout[1,-8])) equivalent
RMSE(predict(ridge.cv, newx = as.matrix(train[,-11])), as.matrix(train[,11]))
[1] 7.387105
R2(predict(ridge.cv, newx = as.matrix(train[,-11])), as.matrix(train[,11]))
            s0
[1,] 0.7743463
performace_metrics[2,3] <- RMSE(predict(ridge.cv, newx = as.matrix(train[,-11])), as.matrix(train[,11]))
performace_metrics[2,4] <- R2(predict(ridge.cv, newx = as.matrix(train[,-11])), as.matrix(train[,11]))

RMSE(predict(ridge.cv, newx = as.matrix(test[,-11])), as.matrix(test[,11]))
[1] 8.151663
R2(predict(ridge.cv, newx = as.matrix(test[,-11])), as.matrix(test[,11]))
            s0
[1,] 0.6838742
performace_metrics[2,5] <- RMSE(predict(ridge.cv, newx = as.matrix(test[,-11])), as.matrix(test[,11])) 
performace_metrics[2,6] <- R2(predict(ridge.cv, newx = as.matrix(test[,-11])), as.matrix(test[,11]))


"LASSO regression"
[1] "LASSO regression"
set.seed(42)
best.lambda <- cv.glmnet(x = as.matrix(train[,-11]), y = as.matrix(train[,11]), alpha = 1)$lambda.min
lasso.cv <- glmnet(x = as.matrix(train[,-11]), y = as.matrix(train[,11]), alpha = 1, lambda = best.lambda)
RMSE(predict(lasso.cv, newx = as.matrix(train[,-11])), as.matrix(train[,11]))
[1] 7.055445
R2(predict(lasso.cv, newx = as.matrix(train[,-11])), as.matrix(train[,11]))
            s0
[1,] 0.7920884
performace_metrics[3,3] <- RMSE(predict(lasso.cv, newx = as.matrix(train[,-11])), as.matrix(train[,11]))
performace_metrics[3,4] <- R2(predict(lasso.cv, newx = as.matrix(train[,-11])), as.matrix(train[,11]))

RMSE(predict(lasso.cv, newx = as.matrix(test[,-11])), as.matrix(test[,11]))
[1] 7.569551
R2(predict(lasso.cv, newx = as.matrix(test[,-11])), as.matrix(test[,11]))
            s0
[1,] 0.7215951
performace_metrics[3,5] <- RMSE(predict(lasso.cv, newx = as.matrix(test[,-11])), as.matrix(test[,11]))
performace_metrics[3,6] <- R2(predict(lasso.cv, newx = as.matrix(test[,-11])), as.matrix(test[,11]))
  
"Elasticnet regression"
[1] "Elasticnet regression"
set.seed(42)
best.lambda <- cv.glmnet(x = as.matrix(train[,-11]), y = as.matrix(train[,11]), alpha = 0.5)$lambda.min
elasticnet.cv <- glmnet(x = as.matrix(train[,-11]), y = as.matrix(train[,11]), alpha = 0.5, lambda = best.lambda)
RMSE(predict(elasticnet.cv, newx = as.matrix(train[,-11])), as.matrix(train[,11]))
[1] 7.093421
R2(predict(elasticnet.cv, newx = as.matrix(train[,-11])), as.matrix(train[,11]))
            s0
[1,] 0.7898949
performace_metrics[4,3] <- RMSE(predict(elasticnet.cv, newx = as.matrix(train[,-11])), as.matrix(train[,11]))
performace_metrics[4,4] <- R2(predict(elasticnet.cv, newx = as.matrix(train[,-11])), as.matrix(train[,11]))

RMSE(predict(elasticnet.cv, newx = as.matrix(test[,-11])), as.matrix(test[,11]))
[1] 7.629058
R2(predict(elasticnet.cv, newx = as.matrix(test[,-11])), as.matrix(test[,11]))
            s0
[1,] 0.7179827
performace_metrics[4,5] <- RMSE(predict(elasticnet.cv, newx = as.matrix(test[,-11])), as.matrix(test[,11]))
performace_metrics[4,6] <- R2(predict(elasticnet.cv, newx = as.matrix(test[,-11])), as.matrix(test[,11]))
  
# Graphics

# Training data

x_train <- model.matrix(Artist_Popularity~., train)[,-1]

ridge_res2 <- as.numeric(train$Artist_Popularity - predict(ridge.cv, x_train))^2
lasso_res2 <- as.numeric(train$Artist_Popularity - predict(lasso.cv, x_train))^2
elasticnet_res2 <- as.numeric(train$Artist_Popularity - predict(elasticnet.cv, x_train))^2

par(mfcol=c(2,2))
barplot((train$Artist_Popularity - predict(lm_train, train))^2, main = paste("LM (train): RMSE =",round(RMSE(predict(lm_train, train), train$Artist_Popularity), digits = 2), ", R^2:", round(R2(predict(lm_train, train), train$Artist_Popularity), digits = 2)))
barplot(ridge_res2, main = paste("Ridge (train): RMSE =",round(RMSE(predict(ridge.cv, x_train), train$Artist_Popularity), digits = 2), ", R^2:", round(R2(predict(ridge.cv, x_train), train$Artist_Popularity), digits = 2)))
barplot(lasso_res2, main = paste("LASSO (train): RMSE =",round(RMSE(predict(lasso.cv, x_train), train$Artist_Popularity), digits = 2), ", R^2:", round(R2(predict(lasso.cv, x_train), train$Artist_Popularity), digits = 2)))
barplot(elasticnet_res2, main = paste("Elasticnet (train): RMSE =",round(RMSE(predict(elasticnet.cv, x_train), train$Artist_Popularity), digits = 2), ", R^2:", round(R2(predict(elasticnet.cv, x_train), train$Artist_Popularity), digits = 2)))

# Test data

x_test <- model.matrix(Artist_Popularity~., test)[,-1]

ridge_res2 <- as.numeric(test$Artist_Popularity - predict(ridge.cv, x_test))^2
lasso_res2 <- as.numeric(test$Artist_Popularity - predict(lasso.cv, x_test))^2
elasticnet_res2 <- as.numeric(test$Artist_Popularity - predict(elasticnet.cv, x_test))^2

par(mfcol=c(2,2))

barplot((test$Artist_Popularity - predict(lm_train, test))^2, main = paste("LM (test): RMSE =",round(RMSE(predict(lm_train, test), test$Artist_Popularity), digits = 2), ", R^2:", round(R2(predict(lm_train, test), test$Artist_Popularity), digits = 2)))
barplot(ridge_res2, main = paste("Ridge (test): RMSE =",round(RMSE(predict(ridge.cv, x_test), test$Artist_Popularity), digits = 2), ", R^2:", round(R2(predict(ridge.cv, x_test), test$Artist_Popularity), digits = 2)))
barplot(lasso_res2, main = paste("LASSO (test): RMSE =",round(RMSE(predict(lasso.cv, x_test), test$Artist_Popularity), digits = 2), ", R^2:", round(R2(predict(lasso.cv, x_test), test$Artist_Popularity), digits = 2)))
barplot(elasticnet_res2, main = paste("Elasticnet (test): RMSE =",round(RMSE(predict(elasticnet.cv, x_test), test$Artist_Popularity), digits = 2), ", R^2:", round(R2(predict(elasticnet.cv, x_test), test$Artist_Popularity), digits = 2)))

NA
NA
NA
NA
NA


# Non-parametric and semiparametric regression

# Kernel density estimator

library("np")
bw   <- npudensbw(~Track_Duration_ms, data=df) # Track_Duration_ms bi-modal?

Multistart 1 of 1 |
Multistart 1 of 1 |
Multistart 1 of 1 |
Multistart 1 of 1 /
Multistart 1 of 1 |
Multistart 1 of 1 |
                   
fhat <- npudens(bw)
fhat

Density Data: 186 training points, in 1 variable(s)
              Track_Duration_ms
Bandwidth(s):          25427.78

Bandwidth Type: Fixed
Log Likelihood: -2404.132

Continuous Kernel Type: Second-Order Gaussian
No. Continuous Vars.: 1
plot(fhat, main=sprintf("%s with h=%.2f", fhat$pckertype, fhat$bw))
rug(df$Track_Duration_ms)

bw <- npudensbw(~Artist_Popularity+Track_Duration_ms, data=df, bwmethod="normal-reference")
fhat <- npudens(bw)
# plot(fhat) # bi-modal

par(mfcol=c(1,1))

#1) at Track_Duration_ms ~ 1 and Artist_Popularity ~ -1 (meaning longer tracks' artists are less popular)
plot(fhat, view="fixed", phi=10, theta=320, main = "")
par(mfcol=c(1,1))

# 2) at Track_Duration_ms ~ -0.5 and Artist_Popularity ~ 1
plot(fhat, view="fixed", phi=10, theta=155, main = "")

par(mfcol=c(1,1))
nw2 <- npreg(Artist_Popularity~pc_2+Track_Duration_ms, data=df)

Multistart 1 of 2 |
Multistart 1 of 2 |
Multistart 1 of 2 |
Multistart 1 of 2 /
Multistart 1 of 2 -
Multistart 1 of 2 |
Multistart 1 of 2 |
Multistart 2 of 2 |
Multistart 2 of 2 |
Multistart 2 of 2 /
Multistart 2 of 2 |
Multistart 2 of 2 |
                   
summary(nw2)

Regression Data: 186 training points, in 2 variable(s)
                   pc_2 Track_Duration_ms
Bandwidth(s): 0.6558287          64051.76

Kernel Regression Estimator: Local-Constant
Bandwidth Type: Fixed
Residual standard error: 9.136543
R-squared: 0.6351856

Continuous Kernel Type: Second-Order Gaussian
No. Continuous Explanatory Vars.: 2
# plot(nw2)

# Nadaraya-Watson estimator

# bw <- npregbw(Artist_Popularity~Track_Duration_ms, data=df)
# mhat <- npreg(bw)
# main <- sprintf("%s with h=%.2f", mhat$pckertype, mhat$bw)
# plot(df$Artist_Popularity, df$Track_Duration_ms, pch=19, cex=0.3, main=main)
# ind <- order(df$Track_Duration_ms)
# xs  <- cbind(df$Track_Duration_ms, fitted(mhat))[ind,]
# lines(xs[,1], xs[,2], col="red", lwd=2)
# rug(df$Track_Duration_ms)

# bw <- npregbw(Artist_Popularity~Track_Duration_ms+Track_Popularity, data=df)
# mhat <- npreg(bw)
# plot(mhat)


plotContour <- function (model, data, n=30) {
  mf <- model.frame(model$terms, data)
  mc <- lapply(as.list(mf[,-1]), pretty, n=n)
  zc <- predict(model, expand.grid(mc))
  dim(zc) <- sapply(mc, length)
  r2 <- 1-var(residuals(model))/var(mf[,1])
  contour(mc[[1]], mc[[2]], zc, xlab=names(mf)[2], ylab=names(mf)[3],
            main=sprintf("R^2=%.3f", r2))
  cc <- gray(0.75-0.75*(mf[,1]-min(mf[,1]))/(max(mf[,1])-min(mf[,1])))
  points(mf[,2], mf[,3], pch=19, cex=0.5, col=cc)
  }


model <- lm(Artist_Popularity~pc_1 + pc_2, data=df)
par(mfrow=c(1,1))

plotContour(model, df)


# Partial linear model (mixing linear and spline terms)

library("mgcv")
model <- gam(Artist_Popularity~s(Track_Popularity)+Track_Duration_ms, data=df)
# plotContour(model, df$Track_Popularity, df$Track_Duration_ms, df$Artist_Popularity)
# plot(model)

# Additive model

library("gam")
Lade n昼㸶tiges Paket: splines
Lade n昼㸶tiges Paket: foreach
Loaded gam 1.16.1


Attache Paket: 㤼㸱gam㤼㸲

The following objects are masked from 㤼㸱package:mgcv㤼㸲:

    gam, gam.control, gam.fit, s
am1 <- gam(Artist_Popularity ~ s(fa_1) + s(fa_2) + s(fa_3) + s(fa_4) + s(pc_1) + s(pc_2) + s(Artist_Follower) + s(days_release_orig) + s(Track_Popularity) + s(Track_Duration_ms) + s(fa_2sq) + s(pc_2sq) + s(Artist_Followersq), data=df)
non-list contrasts argument ignored
print(am1)
Call:
gam(formula = Artist_Popularity ~ s(fa_1) + s(fa_2) + s(fa_3) + 
    s(fa_4) + s(pc_1) + s(pc_2) + s(Artist_Follower) + s(days_release_orig) + 
    s(Track_Popularity) + s(Track_Duration_ms) + s(fa_2sq) + 
    s(pc_2sq) + s(Artist_Followersq), data = df)

Degrees of Freedom: 185 total; 76.48856 Residual
Residual Deviance: 2110.514 
par(mfrow=c(3,4))
plot(am1)

plot(fitted(am1), residuals(am1))
lines(lowess(fitted(am1), residuals(am1)), col="red", lwd=2)
n <- nrow(df)
1-sum(residuals(am1)^2)/((n-1)*var(df$Artist_Popularity))
[1] 0.9503897
performace_metrics[5,1] <- RMSE(predict(am1, newdata = df), df$Artist_Popularity)
performace_metrics[5,2] <- R2(predict(am1, newdata = df), df$Artist_Popularity)

am1_train <- gam(Artist_Popularity ~ s(fa_1) + s(fa_2) + s(fa_3) + s(fa_4) + s(pc_1) + s(pc_2) + s(Artist_Follower) + s(days_release_orig) + s(Track_Popularity) + s(Track_Duration_ms) + s(fa_2sq) + s(pc_2sq) + s(Artist_Followersq), data=train)
non-list contrasts argument ignored
performace_metrics[5,3] <- RMSE(predict(am1_train, newdata = train), train$Artist_Popularity)
performace_metrics[5,4] <- R2(predict(am1_train, newdata = train), train$Artist_Popularity)

performace_metrics[5,5] <- RMSE(predict(am1_train, newdata = test), test$Artist_Popularity)
performace_metrics[5,6] <- R2(predict(am1_train, newdata = test), test$Artist_Popularity)


# Single index model

sim <- npindex(Artist_Popularity ~ fa_1 + fa_2 + fa_3 + fa_4 + pc_1 + pc_2 + Artist_Follower + days_release_orig + Track_Popularity + Track_Duration_ms + fa_2sq + pc_2sq + Artist_Followersq, data=df)
Multistart 1 of 5...Multistart 2 of 5...Multistart 3 of 5...Multistart 4 of 5...Multistart 5 of 5...                    
summary(sim)

Single Index Model
Regression Data: 186 training points, in 13 variable(s)

      fa_1     fa_2       fa_3     fa_4      pc_1     pc_2 Artist_Follower days_release_orig Track_Popularity Track_Duration_ms    fa_2sq     pc_2sq
Beta:    1 7.140808 -0.9481839 1.320797 0.3963661 1.316466        10.62225     -4.818576e-05       0.08091355     -7.782318e-06 -7.679957 -0.8994251
      Artist_Followersq
Beta:        -0.1862698
Bandwidth: 0.8377536
Kernel Regression Estimator: Local-Constant

Residual standard error: 5.506267
R-squared: 0.8675706

Continuous Kernel Type: Second-Order Gaussian
No. Continuous Explanatory Vars.: 1
performace_metrics[6,1] <- RMSE(predict(sim, newdata = df), df$Artist_Popularity)
performace_metrics[6,2] <- R2(predict(sim, newdata = df), df$Artist_Popularity)

sim_train <- npindex(Artist_Popularity ~ fa_1 + fa_2 + fa_3 + fa_4 + pc_1 + pc_2 + Artist_Follower + days_release_orig + Track_Popularity + Track_Duration_ms + fa_2sq + pc_2sq + Artist_Followersq, data=train)
Multistart 1 of 5...Multistart 2 of 5...Multistart 3 of 5...Multistart 4 of 5...Multistart 5 of 5...                    
performace_metrics[6,3] <- RMSE(predict(sim_train, newdata = train), train$Artist_Popularity)
performace_metrics[6,4] <- R2(predict(sim_train, newdata = train), train$Artist_Popularity)

performace_metrics[6,5] <- RMSE(predict(sim_train, newdata = test), test$Artist_Popularity)
performace_metrics[6,6] <- R2(predict(sim_train, newdata = test), test$Artist_Popularity)

par(mfrow=c(3,4))

plot(sim)

plot(fitted(sim), residuals(sim))
lines(lowess(fitted(sim), residuals(sim)), col="red", lwd=2)
n <- nrow(df)
1-sum(residuals(sim)^2)/((n-1)*var(df$Artist_Popularity))
[1] 0.8674403
# Projection Pursuit Regression

ppr <- ppr(Artist_Popularity ~ fa_1 + fa_2 + fa_3 + fa_4 + pc_1 + pc_2 + Artist_Follower + days_release_orig + Track_Popularity + Track_Duration_ms + fa_2sq + pc_2sq + Artist_Followersq, data=df, nterm=5)

performace_metrics[7,1] <- RMSE(predict(ppr, newdata = df), df$Artist_Popularity)
performace_metrics[7,2] <- R2(predict(ppr, newdata = df), df$Artist_Popularity)

ppr_train <- ppr(Artist_Popularity ~ fa_1 + fa_2 + fa_3 + fa_4 + pc_1 + pc_2 + Artist_Follower + days_release_orig + Track_Popularity + Track_Duration_ms + fa_2sq + pc_2sq + Artist_Followersq, data=train, nterm=5)

performace_metrics[7,3] <- RMSE(predict(ppr_train, newdata = train), train$Artist_Popularity)
performace_metrics[7,4] <- R2(predict(ppr_train, newdata = train), train$Artist_Popularity)
performace_metrics[7,5] <- RMSE(predict(ppr_train, newdata = test), test$Artist_Popularity)
performace_metrics[7,6] <- R2(predict(ppr_train, newdata = test), test$Artist_Popularity)

summary(ppr)
Call:
ppr(formula = Artist_Popularity ~ fa_1 + fa_2 + fa_3 + fa_4 + 
    pc_1 + pc_2 + Artist_Follower + days_release_orig + Track_Popularity + 
    Track_Duration_ms + fa_2sq + pc_2sq + Artist_Followersq, 
    data = df, nterm = 5)

Goodness of fit:
 5 terms 
1741.435 

Projection direction vectors ('alpha'):
                  term 1        term 2        term 3        term 4        term 5       
fa_1              -6.325427e-02  2.081791e-01 -8.112105e-02  4.462995e-01 -6.211143e-01
fa_2               1.999522e-01  1.988317e-01  3.820633e-01 -1.225150e-01 -1.261187e-01
fa_3               1.776410e-01 -4.813375e-01  4.625303e-01 -2.615833e-01 -2.121784e-01
fa_4               9.095299e-02 -7.325872e-01  5.161008e-01 -2.094942e-01  4.149667e-01
pc_1               5.912856e-02 -1.634530e-01 -1.398765e-01 -2.846767e-02 -2.597611e-01
pc_2               7.261656e-02  1.218474e-01 -3.230672e-01 -5.558153e-01  4.152905e-01
Artist_Follower    9.472078e-01 -2.009118e-01  1.651405e-01  5.085105e-01 -1.891676e-01
days_release_orig  5.284337e-07 -2.107916e-05  5.900920e-05  2.729395e-05 -7.811224e-05
Track_Popularity   6.925853e-03  1.765052e-02 -6.223949e-02  2.066660e-03 -2.572436e-03
Track_Duration_ms -3.916723e-07  4.233016e-07  2.333658e-06 -2.253623e-06 -2.907082e-06
fa_2sq            -9.559306e-02 -2.453111e-01  5.941416e-02 -1.868207e-01 -1.176104e-01
pc_2sq            -7.315117e-03 -7.878193e-02  4.566070e-01 -2.623794e-01  3.024768e-01
Artist_Followersq -3.126859e-02  1.152058e-02 -7.044398e-03 -3.768289e-02  6.051800e-03

Coefficients of ridge terms ('beta'):
   term 1    term 2    term 3    term 4    term 5 
15.436585  3.228392  3.418258  2.647310  2.190618 
par(mfrow=c(1,1))

plot(ppr)

plot(fitted(ppr), residuals(ppr))
lines(lowess(fitted(ppr), residuals(ppr)), col="red", lwd=2)

n <- nrow(df)
1-sum(residuals(ppr)^2)/((n-1)*var(df$Artist_Popularity))
[1] 0.9590653

library("rpart")
set.seed(42)
tr1 <- rpart(Artist_Popularity~fa_1+fa_2+fa_3+fa_4+pc_1+pc_2+Artist_Follower+days_release_orig
             +Track_Popularity+Track_Duration_ms+fa_2sq+pc_2sq+Artist_Followersq, data=df, cp = 0)

tr1
n= 186 

node), split, n, deviance, yval
      * denotes terminal node

  1) root 186 42541.81000 64.03226  
    2) Artist_Follower< 0.069933 69  6966.63800 49.07246  
      4) fa_2sq>=0.04873442 44  1927.88600 43.65909  
        8) Artist_Follower< 0.0063065 24   493.33330 39.33333  
         16) Track_Popularity< 42.5 7   246.85710 36.14286 *
         17) Track_Popularity>=42.5 17   145.88240 40.64706 *
        9) Artist_Follower>=0.0063065 20   446.55000 48.85000  
         18) Artist_Follower< 0.0181555 7    27.42857 45.71429 *
         19) Artist_Follower>=0.0181555 13   313.23080 50.53846 *
      5) fa_2sq< 0.04873442 25  1480.00000 58.60000  
       10) Track_Duration_ms>=322237 11   247.63640 53.81818 *
       11) Track_Duration_ms< 322237 14   783.21430 62.35714 *
    3) Artist_Follower>=0.069933 117 11026.53000 72.85470  
      6) Artist_Follower< 0.538149 59  2850.67800 66.23729  
       12) Track_Duration_ms>=327871 16   283.00000 59.25000 *
       13) Track_Duration_ms< 327871 43  1495.86000 68.83721  
         26) Track_Popularity< 66 33   762.90910 66.81818  
           52) fa_1< -0.7142649 12   117.66670 64.16667 *
           53) fa_1>=-0.7142649 21   512.66670 68.33333  
            106) Track_Duration_ms>=208616 7    77.71429 65.42857 *
            107) Track_Duration_ms< 208616 14   346.35710 69.78571 *
         27) Track_Popularity>=66 10   154.50000 75.50000 *
      7) Artist_Follower>=0.538149 58  2964.06900 79.58621  
       14) Artist_Follower< 2.122275 41  1084.04900 76.26829  
         28) Track_Popularity< 59 25   470.96000 73.96000  
           56) Artist_Follower< 0.779791 11   144.54550 71.36364 *
           57) Artist_Follower>=0.779791 14   194.00000 76.00000 *
         29) Track_Popularity>=59 16   271.75000 79.87500 *
       15) Artist_Follower>=2.122275 17   340.11760 87.58824 *
par(mfrow=c(1,1))
plot(tr1)
text(tr1)

# summary(tr1)

library("rpart.plot")
rpart.plot(tr1, cex = 0.7)

n <- nrow(x_noout)
1-sum(residuals(tr1)^2)/((n-1)*var(df$Artist_Popularity))
[1] 0.9131701
RMSE(predict(tr1, newx = as.matrix(df[,-11])), as.matrix(df[,11]))
[1] 4.45642
R2(predict(tr1, newx = as.matrix(df[,-11])), as.matrix(df[,11]))
          [,1]
[1,] 0.9131701
performace_metrics[8,1] <- RMSE(predict(tr1, newx = as.matrix(df[,-11])), as.matrix(df[,11]))
performace_metrics[8,2] <- R2(predict(tr1, newx = as.matrix(df[,-11])), as.matrix(df[,11]))

par(mfrow=c(1,1))

printcp(tr1)

Regression tree:
rpart(formula = Artist_Popularity ~ fa_1 + fa_2 + fa_3 + fa_4 + 
    pc_1 + pc_2 + Artist_Follower + days_release_orig + Track_Popularity + 
    Track_Duration_ms + fa_2sq + pc_2sq + Artist_Followersq, 
    data = df, cp = 0)

Variables actually used in tree construction:
[1] Artist_Follower   fa_1              fa_2sq            Track_Duration_ms Track_Popularity 

Root node error: 42542/186 = 228.72

n= 186 

          CP nsplit rel error  xerror     xstd
1  0.5770474      0  1.000000 1.01051 0.083930
2  0.1225097      1  0.422953 0.52027 0.047831
3  0.0836530      2  0.300443 0.39602 0.043732
4  0.0361974      3  0.216790 0.34182 0.037375
5  0.0251945      4  0.180592 0.24520 0.032810
6  0.0232243      5  0.155398 0.24470 0.032927
7  0.0135972      6  0.132174 0.23754 0.032621
8  0.0105578      7  0.118577 0.22022 0.032283
9  0.0080236      8  0.108019 0.19763 0.026040
10 0.0031164      9  0.099995 0.17719 0.024187
11 0.0031126     10  0.096879 0.17391 0.023256
12 0.0024891     11  0.093766 0.17227 0.023182
13 0.0023646     12  0.091277 0.17570 0.023223
14 0.0020825     13  0.088912 0.17667 0.023198
15 0.0000000     14  0.086830 0.17584 0.022941
plotcp(tr1)

tr1$cptable
            CP nsplit  rel error    xerror       xstd
1  0.577047401      0 1.00000000 1.0105061 0.08393019
2  0.122509677      1 0.42295260 0.5202683 0.04783113
3  0.083653037      2 0.30044292 0.3960213 0.04373196
4  0.036197394      3 0.21678988 0.3418154 0.03737539
5  0.025194452      4 0.18059249 0.2451952 0.03281039
6  0.023224285      5 0.15539804 0.2447027 0.03292670
7  0.013597245      6 0.13217375 0.2375437 0.03262056
8  0.010557834      7 0.11857651 0.2202174 0.03228308
9  0.008023608      8 0.10801867 0.1976297 0.02603967
10 0.003116364      9 0.09999507 0.1771938 0.02418689
11 0.003112575     10 0.09687870 0.1739115 0.02325628
12 0.002489096     11 0.09376613 0.1722744 0.02318153
13 0.002364588     12 0.09127703 0.1757018 0.02322286
14 0.002082545     13 0.08891244 0.1766732 0.02319815
15 0.000000000     14 0.08682990 0.1758450 0.02294140
best.cp <- tr1$cptable[which.min(tr1$cptable[,"xerror"]),"CP"]
best.cp
[1] 0.002489096
dt_pruned <- prune(tr1, cp=best.cp)

1-sum(residuals(dt_pruned)^2)/((n-1)*var(df$Artist_Popularity))
[1] 0.9062339
RMSE(predict(dt_pruned, newx = as.matrix(df[,-11])), as.matrix(df[,11]))
[1] 4.630997
R2(predict(dt_pruned, newx = as.matrix(df[,-11])), as.matrix(df[,11])) # lower R^2 after pruning
          [,1]
[1,] 0.9062339

library("caret")

# DT CV on full data

caret.control <- trainControl(method = "repeatedcv",
                              number = 10,
                              repeats = 3)
set.seed(42)
rpart.cv <- caret::train(Artist_Popularity ~ ., 
                         data = df,
                         method = "rpart",
                         trControl = caret.control,
                         tuneLength = 15)
There were missing values in resampled performance measures.
rpart.cv
CART 

186 samples
 13 predictor

No pre-processing
Resampling: Cross-Validated (10 fold, repeated 3 times) 
Summary of sample sizes: 168, 167, 169, 167, 168, 166, ... 
Resampling results across tuning parameters:

  cp           RMSE       Rsquared   MAE      
  0.000000000   6.202692  0.8458133   4.909969
  0.002082545   6.162816  0.8483583   4.865791
  0.002364588   6.155920  0.8485051   4.852674
  0.002489096   6.156964  0.8487421   4.841797
  0.003112575   6.130607  0.8500124   4.814403
  0.003116364   6.130607  0.8500124   4.814403
  0.008023608   6.355019  0.8355349   5.050998
  0.010557834   6.551789  0.8232113   5.215937
  0.013597245   6.815278  0.8076098   5.468092
  0.023224285   7.373800  0.7741045   5.936121
  0.025194452   7.564302  0.7615957   6.066125
  0.036197394   7.905309  0.7373849   6.393810
  0.083653037   8.645345  0.6856792   7.103210
  0.122509677   9.809266  0.5978668   8.102628
  0.577047401  12.815514  0.4840569  10.658897

RMSE was used to select the optimal model using the smallest value.
The final value used for the model was cp = 0.003116364.
rpart.best.cv <- rpart.cv$finalModel
rpart.best.cv
n= 186 

node), split, n, deviance, yval
      * denotes terminal node

 1) root 186 42541.8100 64.03226  
   2) Artist_Follower< 0.069933 69  6966.6380 49.07246  
     4) fa_2sq>=0.04873442 44  1927.8860 43.65909  
       8) Artist_Follower< 0.0063065 24   493.3333 39.33333 *
       9) Artist_Follower>=0.0063065 20   446.5500 48.85000 *
     5) fa_2sq< 0.04873442 25  1480.0000 58.60000  
      10) Track_Duration_ms>=322237 11   247.6364 53.81818 *
      11) Track_Duration_ms< 322237 14   783.2143 62.35714 *
   3) Artist_Follower>=0.069933 117 11026.5300 72.85470  
     6) Artist_Follower< 0.538149 59  2850.6780 66.23729  
      12) Track_Duration_ms>=327871 16   283.0000 59.25000 *
      13) Track_Duration_ms< 327871 43  1495.8600 68.83721  
        26) Track_Popularity< 66 33   762.9091 66.81818 *
        27) Track_Popularity>=66 10   154.5000 75.50000 *
     7) Artist_Follower>=0.538149 58  2964.0690 79.58621  
      14) Artist_Follower< 2.122275 41  1084.0490 76.26829  
        28) Track_Popularity< 59 25   470.9600 73.96000 *
        29) Track_Popularity>=59 16   271.7500 79.87500 *
      15) Artist_Follower>=2.122275 17   340.1176 87.58824 *
rpart.plot(rpart.best.cv)


RMSE(predict(rpart.best.cv, newdata = df), df$Artist_Popularity) # corresponds exactly to the pruned tree solution
[1] 4.782344
R2(predict(rpart.best.cv, newdata = df), df$Artist_Popularity) # corresponds exactly to the pruned tree solution
[1] 0.9000049
# DT CV on split data

set.seed(42)
idx.train <- createDataPartition(y = df$Artist_Popularity, p = 0.75, list = FALSE)
train.data <- df[idx.train, ]
test.data <- df[-idx.train,]

caret.control <- trainControl(method = "repeatedcv",
                              number = 10,
                              repeats = 3)
set.seed(42)
rpart.cv <- caret::train(Artist_Popularity ~ ., 
                  data = train.data,
                  method = "rpart",
                  trControl = caret.control,
                  tuneLength = 15)
There were missing values in resampled performance measures.
rpart.cv
CART 

142 samples
 13 predictor

No pre-processing
Resampling: Cross-Validated (10 fold, repeated 3 times) 
Summary of sample sizes: 129, 127, 128, 128, 127, 128, ... 
Resampling results across tuning parameters:

  cp          RMSE       Rsquared   MAE      
  0.00000000   6.684278  0.8239615   5.436018
  0.04189278   7.224630  0.7877985   5.728523
  0.08378556   8.309005  0.7178983   6.710720
  0.12567835  10.113664  0.5860693   8.319214
  0.16757113  10.113664  0.5860693   8.319214
  0.20946391  10.215698  0.5771918   8.394308
  0.25135669  10.215698  0.5771918   8.394308
  0.29324948  10.215698  0.5771918   8.394308
  0.33514226  10.215698  0.5771918   8.394308
  0.37703504  10.215698  0.5771918   8.394308
  0.41892782  10.215698  0.5771918   8.394308
  0.46082061  10.215698  0.5771918   8.394308
  0.50271339  10.215698  0.5771918   8.394308
  0.54460617  10.215698  0.5771918   8.394308
  0.58649895  13.543120  0.4765201  11.241111

RMSE was used to select the optimal model using the smallest value.
The final value used for the model was cp = 0.
rpart.best.cv <- rpart.cv$finalModel
rpart.best.cv
n= 142 

node), split, n, deviance, yval
      * denotes terminal node

  1) root 142 33870.94000 63.97887  
    2) Artist_Follower< 0.069933 56  6172.21400 49.32143  
      4) Artist_Follower< 0.0085735 22   563.86360 39.77273  
        8) Artist_Follower< 0.003799 15   304.93330 37.73333 *
        9) Artist_Follower>=0.003799 7    62.85714 44.14286 *
      5) Artist_Follower>=0.0085735 34  2304.50000 55.50000  
       10) Track_Popularity< 53.5 23   719.30430 51.82609  
         20) fa_2< -0.2208195 14   276.00000 49.00000 *
         21) fa_2>=-0.2208195 9   157.55560 56.22222 *
       11) Track_Popularity>=53.5 11   625.63640 63.18182 *
    3) Artist_Follower>=0.069933 86  7833.45300 73.52326  
      6) Artist_Follower< 1.461101 68  3699.11800 70.20588  
       12) Artist_Follower< 0.2739165 27   821.18520 64.25926  
         24) fa_2sq>=0.02105125 20   286.55000 61.85000  
           48) Track_Duration_ms>=321893 10    75.60000 59.20000 *
           49) Track_Duration_ms< 321893 10    70.50000 64.50000 *
         25) fa_2sq< 0.02105125 7    86.85714 71.14286 *
       13) Artist_Follower>=0.2739165 41  1294.39000 74.12195  
         26) Track_Popularity< 68.5 33   816.54550 72.72727  
           52) days_release_orig>=160 26   538.50000 71.50000  
            104) fa_2< -0.08397494 19   290.00000 70.00000 *
            105) fa_2>=-0.08397494 7    89.71429 75.57143 *
           53) days_release_orig< 160 7    93.42857 77.28571 *
         27) Track_Popularity>=68.5 8   148.87500 79.87500 *
      7) Artist_Follower>=1.461101 18   558.94440 86.05556 *
rpart.plot(rpart.best.cv)




RMSE(predict(rpart.best.cv, newdata = train.data), train.data$Artist_Popularity)
[1] 4.472846
R2(predict(rpart.best.cv, newdata = train.data), train.data$Artist_Popularity)
[1] 0.9161257
performace_metrics[8,3] <- RMSE(predict(rpart.best.cv, newdata = train.data), train.data$Artist_Popularity)
performace_metrics[8,4] <- R2(predict(rpart.best.cv, newdata = train.data), train.data$Artist_Popularity)

RMSE(predict(rpart.best.cv, newdata = test.data), test.data$Artist_Popularity)
[1] 7.21592
R2(predict(rpart.best.cv, newdata = test.data), test.data$Artist_Popularity)
[1] 0.7520699
performace_metrics[8,5] <- RMSE(predict(rpart.best.cv, newdata = test.data), test.data$Artist_Popularity)
performace_metrics[8,6] <- R2(predict(rpart.best.cv, newdata = test.data), test.data$Artist_Popularity)

#########################################################################

library("randomForest")
set.seed(42)
rf <-randomForest(Artist_Popularity~fa_1+fa_2+fa_3+fa_4+pc_1+pc_2+Artist_Follower+days_release_orig
                  +Track_Popularity+Track_Duration_ms+fa_2sq+pc_2sq+Artist_Followersq, data=df, cp = 0, method = 'anova')
summary(rf)
                Length Class  Mode     
call              5    -none- call     
type              1    -none- character
predicted       186    -none- numeric  
mse             500    -none- numeric  
rsq             500    -none- numeric  
oob.times       186    -none- numeric  
importance       13    -none- numeric  
importanceSD      0    -none- NULL     
localImportance   0    -none- NULL     
proximity         0    -none- NULL     
ntree             1    -none- numeric  
mtry              1    -none- numeric  
forest           11    -none- list     
coefs             0    -none- NULL     
y               186    -none- numeric  
test              0    -none- NULL     
inbag             0    -none- NULL     
terms             3    terms  call     
imp <- importance(rf)
ind <- order(imp, decreasing=T)
imp[ind,]
  Artist_Follower Artist_Followersq Track_Duration_ms              fa_2              pc_2  Track_Popularity            fa_2sq days_release_orig 
       12033.2942        10915.4479         4639.3532         3775.8012         3435.4751         1707.2339         1180.2063          850.0604 
           pc_2sq              fa_3              pc_1              fa_4              fa_1 
         819.4110          763.9136          761.8407          700.4277          444.7691 
rf_pred <- predict(rf, df, type="class")
n <- nrow(df)
1-sum(residuals(rf)^2)/((n-1)*var(df$Artist_Popularity))
[1] 1
RMSE(predict(rf, newdata = df), df$Artist_Popularity)
[1] 2.114007
R2(predict(rf, newdata = df), df$Artist_Popularity)
[1] 0.9826053
performace_metrics[9,1] <- RMSE(predict(rf, newdata = df), df$Artist_Popularity)
performace_metrics[9,2] <- R2(predict(rf, newdata = df), df$Artist_Popularity)

# RMSE(predict(rf, newdata = train.data), train.data$Artist_Popularity)
# R2(predict(rf, newdata = train.data), train.data$Artist_Popularity)

# RMSE(predict(rf, newdata = test.data), test.data$Artist_Popularity)
# R2(predict(rf, newdata = test.data), test.data$Artist_Popularity)


#########################################################################
library("data.table")
library("mlr")
library("parallel")

rf_train <- as.data.table(train)
rf_test <- as.data.table(test)

task <- makeRegrTask(data = train, target = "Artist_Popularity")

rf_learner <- makeLearner("regr.randomForest", 
                          predict.type = "response")

rf.parms <- makeParamSet(
  # The recommendation for mtry by Breiman is squareroot number of columns
  makeDiscreteParam("mtry", values = c(2,3,4,5,6)), # Number of features selected at each node, smaller -> faster
  makeDiscreteParam("sampsize", values =  c(30, 50, 70)), # bootstrap sample size, smaller -> faster
  makeDiscreteParam("ntree", values = c(10,30,50,100, 500, 1000)) # Number of tree, smaller -> faster
) 

tuneControl <- makeTuneControlGrid(resolution = 3, tune.threshold = FALSE)

rdesc <- makeResampleDesc(method = "CV", iters = 5, stratify = FALSE)

library("parallelMap")

parallelStartSocket(4, level = "mlr.tuneParams")
Parallelization was not stopped, doing it now.Stopped parallelization. All cleaned up.
Starting parallelization in mode=socket with cpus=4.
#tuning <- tuneParams(rf_learner, task = task, resampling = rdesc,
#                     par.set = rf.parms, control = tuneControl, measures = mlr::rmse)

# https://stackoverflow.com/questions/51333410/mlr-why-does-reproducibility-of-hyperparameter-tuning-fail-using-parallelizatio

suppressMessages({

  set.seed(123456, "L'Ecuyer-CMRG")
  clusterSetRNGStream(iseed = 123456)
  tuning <- tuneParams(rf_learner, task = task, resampling = rdesc,
                     par.set = rf.parms, control = tuneControl, measures = mlr::rmse)
  
})

parallelStop()
Stopped parallelization. All cleaned up.
tuning$x
$mtry
[1] 5

$sampsize
[1] 70

$ntree
[1] 50
tuning_results <- generateHyperParsEffectData(tuning, partial.dep = TRUE)

tuning_results$data

tapply(tuning_results$data$rmse.test.rmse, INDEX = c(tuning_results$data$mtry), mean)
       2        3        4        5        6 
6.170433 5.843650 5.704894 5.651328 5.639517 
rf_tuned <- setHyperPars(rf_learner, par.vals = tuning$x)
rf_model <- mlr::train(rf_tuned, task = task)
rf_pred_train <- predict(rf_model, newdata = train, type = "response")
rf_pred_test <- predict(rf_model, newdata = test, type = "response")
# rf_pred_train$data$response
# rf_pred_test$data$response

RMSE(predict(rf_model, newdata = train, type = "response")$data$response, train$Artist_Popularity)
[1] 3.511922
R2(predict(rf_model, newdata = train, type = "response")$data$response, train$Artist_Popularity)
[1] 0.954947
performace_metrics[9,3] <- RMSE(predict(rf_model, newdata = train, type = "response")$data$response, train$Artist_Popularity)
performace_metrics[9,4] <- R2(predict(rf_model, newdata = train, type = "response")$data$response, train$Artist_Popularity)

RMSE(predict(rf_model, newdata = test, type = "class")$data$response, test$Artist_Popularity)
[1] 4.908244
R2(predict(rf_model, newdata = test, type = "class")$data$response, test$Artist_Popularity)
[1] 0.8911804
performace_metrics[9,5] <- RMSE(predict(rf_model, newdata = test, type = "response")$data$response, test$Artist_Popularity)
performace_metrics[9,6] <- R2(predict(rf_model, newdata = test, type = "response")$data$response, test$Artist_Popularity)

library("MASS")
library("nnet")

df_z <- scale(as.matrix(df))
train_z <- as.data.frame(df_z[idx.train, ])
test_z <- as.data.frame(df_z[-idx.train,])

mean_mat <- matrix(, nrow = dim(df)[1], ncol = dim(df)[2])
mean_mat[1, ] <- as.numeric(sapply(df, mean, na.rm = TRUE))

library("zoo")
mean_mat <- na.locf(mean_mat)

diag_sd <- diag(sapply(df, sd, na.rm = TRUE))

# Backtransformation scale to orginal

# as.matrix(df_z) %*% diag_sd + mean_mat

task <- makeRegrTask(data = train_z, target = "Artist_Popularity")
nnet <- makeLearner("regr.nnet", predict.type = "response", par.vals = list("trace" = FALSE, "maxit" = 1000,
                                                                           "MaxNWts" = 80000))

nn.parms <- makeParamSet(
  makeDiscreteParam("decay", values = c(0.0001,0.001, 0.01, 0.1)), 
  makeDiscreteParam("size", values = c(2,4,6,8,10,12,14)))

tuneControl <- makeTuneControlGrid(resolution = 3, tune.threshold = FALSE)

rdesc <- makeResampleDesc(method = "CV", iters = 5, stratify = FALSE)


parallelStartSocket(4, level = "mlr.tuneParams")
Parallelization was not stopped, doing it now.Stopped parallelization. All cleaned up.
Starting parallelization in mode=socket with cpus=4.
# tuning <- tuneParams(nnet, task = task, resampling = rdesc, par.set = nn.parms, control = tuneControl,
#                     measures = mlr::rmse)

suppressMessages({

  set.seed(123456, "L'Ecuyer-CMRG")
  clusterSetRNGStream(iseed = 123456)
  tuning <- tuneParams(nnet, task = task, resampling = rdesc, par.set = nn.parms, control = tuneControl,
                     measures = mlr::rmse)
})

parallelStop()
Stopped parallelization. All cleaned up.
tuning$x # result with full data set was decay = 1e-04, size = 2, rmse.test.rmse = 0.4336486
$decay
[1] 0.001

$size
[1] 2
tuning_results <- generateHyperParsEffectData(tuning, partial.dep = FALSE)

plotHyperParsEffect(tuning_results, x = "size", y = "rmse.test.rmse")

plotHyperParsEffect(tuning_results, x = "decay", y = "rmse.test.rmse")


nnet.tuned <- setHyperPars(nnet, par.vals = tuning$x)
nn_model <- mlr::train(nnet.tuned, task = task)

nn_pred_train <- predict(nn_model, newdata = train_z, type = "response")
nn_pred_test <- predict(nn_model, newdata = test_z, type = "response")

RMSE(predict(nn_model, newdata = train_z, type = "response")$data$response, train_z$Artist_Popularity)
[1] 0.2779096
R2(predict(nn_model, newdata = train_z, type = "response")$data$response, train_z$Artist_Popularity)
[1] 0.9255425
RMSE(predict(nn_model, newdata = test_z, type = "response")$data$response, test_z$Artist_Popularity)
[1] 0.4380883
R2(predict(nn_model, newdata = test_z, type = "response")$data$response, test_z$Artist_Popularity)
[1] 0.7980027
# Scale predictions back to original:

# as.matrix(train_z[, 11]) %*% diag_sd[11,11] + mean_mat[1,11] == train[,11]

RMSE(as.matrix(nn_pred_train$data$response) %*% diag_sd[11,11] + mean_mat[1,11], train$Artist_Popularity)
[1] 4.214302
R2(as.matrix(nn_pred_train$data$response) %*% diag_sd[11,11] + mean_mat[1,11], train$Artist_Popularity)
          [,1]
[1,] 0.9255425
performace_metrics[10,3] <- RMSE(as.matrix(nn_pred_train$data$response) %*% diag_sd[11,11] + mean_mat[1,11], train$Artist_Popularity)
performace_metrics[10,4] <- R2(as.matrix(nn_pred_train$data$response) %*% diag_sd[11,11] + mean_mat[1,11], train$Artist_Popularity)

RMSE(as.matrix(nn_pred_test$data$response) %*% diag_sd[11,11] + mean_mat[1,11], test$Artist_Popularity)
[1] 6.643298
R2(as.matrix(nn_pred_test$data$response) %*% diag_sd[11,11] + mean_mat[1,11], test$Artist_Popularity)
          [,1]
[1,] 0.7980027
performace_metrics[10,5] <- RMSE(as.matrix(nn_pred_test$data$response) %*% diag_sd[11,11] + mean_mat[1,11], test$Artist_Popularity)
performace_metrics[10,6] <- R2(as.matrix(nn_pred_test$data$response) %*% diag_sd[11,11] + mean_mat[1,11], test$Artist_Popularity)

round(performace_metrics, digits = 2)
      [,1] [,2] [,3] [,4] [,5] [,6]
 [1,] 6.91 0.79 6.91 0.80 7.14 0.75
 [2,] 7.26 0.77 7.39 0.77 8.15 0.68
 [3,] 7.14 0.78 7.06 0.79 7.57 0.72
 [4,] 7.18 0.78 7.09 0.79 7.63 0.72
 [5,] 3.37 0.95 2.92 0.96 6.94 0.80
 [6,] 5.51 0.87 5.91 0.86 5.87 0.84
 [7,] 3.06 0.96 3.46 0.95 7.17 0.76
 [8,] 4.46 0.91 4.47 0.92 7.22 0.75
 [9,] 2.11 0.98 3.51 0.95 4.91 0.89
[10,]   NA   NA 4.21 0.93 6.64 0.80
# Classification

df$Charts <- as.factor(x_noout$Charts)
set.seed(42)
rpart <- rpart(data = df,Charts~.,method = 'class')
rpart.plot(rpart)
rf_pred <- predict(rpart, newdata = df, type = "class")
confMat <- table(df$Charts,rf_pred)
confMat # 0: not in charts, 1: in charts
   rf_pred
     0  1
  0 93  0
  1  7 86
accuracy <- sum(diag(confMat))/sum(confMat)
accuracy
[1] 0.9623656
# 0.9623656

par(mfrow=c(1,1))

printcp(rpart)

Classification tree:
rpart(formula = Charts ~ ., data = df, method = "class")

Variables actually used in tree construction:
[1] fa_2              pc_1              Track_Duration_ms

Root node error: 93/186 = 0.5

n= 186 

        CP nsplit rel error  xerror     xstd
1 0.795699      0  1.000000 1.19355 0.071937
2 0.118280      1  0.204301 0.23656 0.047358
3 0.010753      2  0.086022 0.12903 0.036027
4 0.010000      3  0.075269 0.13978 0.037390
plotcp(rpart)

rpart$cptable
          CP nsplit  rel error    xerror       xstd
1 0.79569892      0 1.00000000 1.1935484 0.07193706
2 0.11827957      1 0.20430108 0.2365591 0.04735805
3 0.01075269      2 0.08602151 0.1290323 0.03602681
4 0.01000000      3 0.07526882 0.1397849 0.03738999
best.cp <- rpart$cptable[which.min(rpart$cptable[,"xerror"]),"CP"]
best.cp
[1] 0.01075269
set.seed(42)
rpart_pruned <- prune(rpart, cp=best.cp)
rpart.plot(rpart_pruned)

rf_pred <- predict(rpart_pruned, newdata = df, type = "class")
confMat <- table(df$Charts,rf_pred)
confMat # 0: not in charts, 1: in charts
   rf_pred
     0  1
  0 89  4
  1  4 89
accuracy <- sum(diag(confMat))/sum(confMat)
accuracy
[1] 0.9569892
# 0.9569892

library("caret")

train.data <- df[idx.train, ]
test.data <- df[-idx.train,]

caret.control <- trainControl(method = "repeatedcv",
                              number = 10,
                              repeats = 3)

set.seed(42)
rpart.cv <- caret::train(Charts ~ ., 
                         data = train.data,
                         method = "rpart",
                         trControl = caret.control,
                         tuneLength = 15)

rpart.cv
CART 

142 samples
 14 predictor
  2 classes: '0', '1' 

No pre-processing
Resampling: Cross-Validated (10 fold, repeated 3 times) 
Summary of sample sizes: 128, 127, 128, 128, 127, 127, ... 
Resampling results across tuning parameters:

  cp          Accuracy   Kappa    
  0.00000000  0.9124908  0.8241111
  0.05462185  0.9073626  0.8135420
  0.10924370  0.8863614  0.7724387
  0.16386555  0.8579487  0.7174184
  0.21848739  0.8579487  0.7174184
  0.27310924  0.8579487  0.7174184
  0.32773109  0.8579487  0.7174184
  0.38235294  0.8579487  0.7174184
  0.43697479  0.8579487  0.7174184
  0.49159664  0.8579487  0.7174184
  0.54621849  0.8579487  0.7174184
  0.60084034  0.8579487  0.7174184
  0.65546218  0.8579487  0.7174184
  0.71008403  0.8579487  0.7174184
  0.76470588  0.6828083  0.3512446

Accuracy was used to select the optimal model using the largest value.
The final value used for the model was cp = 0.
set.seed(42)
rpart.best.cv <- rpart.cv$finalModel
rpart.best.cv
n= 142 

node), split, n, loss, yval, (yprob)
      * denotes terminal node

1) root 142 68 0 (0.52112676 0.47887324)  
  2) Track_Duration_ms>=247464.5 62  2 0 (0.96774194 0.03225806) *
  3) Track_Duration_ms< 247464.5 80 14 1 (0.17500000 0.82500000)  
    6) pc_1< -0.8616754 13  2 0 (0.84615385 0.15384615) *
    7) pc_1>=-0.8616754 67  3 1 (0.04477612 0.95522388) *
rpart.plot(rpart.best.cv)


rpart_pred <- predict(rpart.best.cv, newdata = train.data, type = "class")
confMat <- table(train.data$Charts,rpart_pred)
confMat # 0: not in charts, 1: in charts
   rpart_pred
     0  1
  0 71  3
  1  4 64
accuracy <- sum(diag(confMat))/sum(confMat)
accuracy
[1] 0.9507042
# 0.9507042

rpart_pred <- predict(rpart.best.cv, newdata = test.data, type = "class")
confMat <- table(test.data$Charts,rpart_pred)
confMat # 0: not in charts, 1: in charts
   rpart_pred
     0  1
  0 19  0
  1  3 22
accuracy <- sum(diag(confMat))/sum(confMat)
accuracy
[1] 0.9318182
# 0.9318182

#################################################

# Random forest

rf_train <- train.data
rf_test <- test.data
task <- makeClassifTask(data = rf_train, target = "Charts", positive = "1")
rf_learner <- makeLearner("classif.randomForest", 
                          predict.type = "prob", # prediction type needs to be specified for the learner 
                          par.vals = list("replace" = TRUE, "importance" = FALSE))

rf.parms <- makeParamSet(
  # The recommendation for mtry by Breiman is squareroot number of columns
  makeDiscreteParam("mtry", values = c(2,3,4,5,6)), # Number of features selected at each node, smaller -> faster
  makeDiscreteParam("sampsize", values =  c(30, 50, 70)), # bootstrap sample size, smaller -> faster
  makeDiscreteParam("ntree", values = c(10,30,50,100, 500, 1000)) # Number of tree, smaller -> faster
) 

tuneControl <- makeTuneControlGrid(resolution = 3, tune.threshold = FALSE)

rdesc <- makeResampleDesc(method = "CV", iters = 5, stratify = TRUE)

parallelStartSocket(4, level = "mlr.tuneParams")
Starting parallelization in mode=socket with cpus=4.
suppressMessages({

  set.seed(123456, "L'Ecuyer-CMRG")
  clusterSetRNGStream(iseed = 123456)
  tuning <- tuneParams(rf_learner, task = task, resampling = rdesc,
                     par.set = rf.parms, control = tuneControl, measures = mlr::auc)
})


parallelStop()
Stopped parallelization. All cleaned up.
tuning$x
$mtry
[1] 4

$sampsize
[1] 70

$ntree
[1] 50
tuning_results <- generateHyperParsEffectData(tuning, partial.dep = TRUE)

tuning_results$data
tapply(tuning_results$data$auc.test.mean, INDEX = c(tuning_results$data$mtry), mean)
        2         3         4         5         6 
0.9953519 0.9952401 0.9955916 0.9944808 0.9951856 
rf_tuned <- setHyperPars(rf_learner, par.vals = tuning$x)
rf_model <- mlr::train(rf_tuned, task = task)
rf_pred <- predict(rf_model, newdata = train.data, type = "class")
# rf_pred$data$response
confMat <- table(train.data$Charts,rf_pred$data$response)
confMat # 0: not in charts, 1: in charts
   
     0  1
  0 74  0
  1  1 67
accuracy <- sum(diag(confMat))/sum(confMat)
accuracy
[1] 0.9929577
# 1

rf_pred <- predict(rf_model, newdata = test.data, type = "class")
# rf_pred$data$response
confMat <- table(test.data$Charts,rf_pred$data$response)
confMat # 0: not in charts, 1: in charts
   
     0  1
  0 19  0
  1  0 25
accuracy <- sum(diag(confMat))/sum(confMat)
accuracy
[1] 1
# 1

# Neural net classification

df$Charts <- x_noout$Charts
train_z$Charts <- df[idx.train, "Charts"]
test_z$Charts <- df[-idx.train, "Charts"]


task <- makeClassifTask(data = train_z, target = "Charts", positive = "1")
nnet <- makeLearner("classif.nnet", predict.type = "prob", par.vals = list("trace" = FALSE, "maxit" = 1000,
                                                                           "MaxNWts" = 80000))

nn.parms <- makeParamSet(
  makeDiscreteParam("decay", values = c(0.0001,0.001, 0.01, 0.1)), 
  makeDiscreteParam("size", values = c(2,4,6,8,10,12,14)))

tuneControl <- makeTuneControlGrid(resolution = 3, tune.threshold = FALSE)

rdesc <- makeResampleDesc(method = "CV", iters = 5, stratify = TRUE)


parallelStartSocket(4, level = "mlr.tuneParams")
Starting parallelization in mode=socket with cpus=4.
suppressMessages({

  set.seed(123456, "L'Ecuyer-CMRG")
  clusterSetRNGStream(iseed = 123456)
  tuning <- tuneParams(nnet, task = task, resampling = rdesc, par.set = nn.parms, control = tuneControl,
                     measures = mlr::auc)
})

parallelStop()
Stopped parallelization. All cleaned up.
tuning$x # result with full data set was decay = 1e-04, size = 2, rmse.test.rmse = 0.4336486
$decay
[1] 0.1

$size
[1] 2
tuning_results <- generateHyperParsEffectData(tuning, partial.dep = FALSE)

plotHyperParsEffect(tuning_results, x = "size", y = "auc.test.mean")

plotHyperParsEffect(tuning_results, x = "decay", y = "auc.test.mean")


nnet.tuned <- setHyperPars(nnet, par.vals = tuning$x)
nn_model <- mlr::train(nnet.tuned, task = task)

model <- mlr::train(nnet.tuned, task = task)

nn_pred_train <- predict(model, newdata = train_z, type = "class")
nn_pred_test <- predict(model, newdata = test_z, type = "class")

confMat <- table(train_z$Charts,nn_pred_train$data$response)
confMat # 0: not in charts, 1: in charts
   
     0  1
  0 74  0
  1  0 68
accuracy <- sum(diag(confMat))/sum(confMat)
accuracy
[1] 1
# 1

confMat <- table(test_z$Charts,nn_pred_test$data$response)
confMat # 0: not in charts, 1: in charts
   
     0  1
  0 19  0
  1  0 25
accuracy <- sum(diag(confMat))/sum(confMat)
accuracy
[1] 1
# 1

```

LS0tDQp0aXRsZTogIkRhdGVuYW5hbHlzZSAyIC0gQWNjb21wYW55aW5nIGNvZGUiDQpvdXRwdXQ6DQogIGh0bWxfbm90ZWJvb2s6IGRlZmF1bHQNCiAgcGRmX2RvY3VtZW50OiBkZWZhdWx0DQotLS0NCg0KDQoNCmBgYHtyfQ0KDQojIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjDQoNCiMgRGF0YSBwcmVwDQoNCmxpYnJhcnkoInJpbyIpDQp4IDwtIGltcG9ydCgiaHR0cHM6Ly9kb2NzLmdvb2dsZS5jb20vc3ByZWFkc2hlZXRzL2QvMWg3WGhMZDJCeXA0T2NYU2R0eEhseTlpUzdSQTY3M1JKL2V4cG9ydD9mb3JtYXQ9Y3N2JmdpZD0xMDgzNDc3NTk2IikNCngkY29tbWVudENvdW50IDwtIGFzLmludGVnZXIoeCRjb21tZW50Q291bnQpDQp4JHZpZXdzQ291bnQgPC0gYXMubnVtZXJpYyh4JHZpZXdzQ291bnQpDQp4JGFjb3VzdGljbmVzcyA8LSBhcy5udW1lcmljKHN1YigiLCIsICIuIiwgeCRhY291c3RpY25lc3MsIGZpeGVkID0gVFJVRSkpDQp4JGRhbmNlYWJpbGl0eSA8LSBhcy5udW1lcmljKHN1YigiLCIsICIuIiwgeCRkYW5jZWFiaWxpdHksIGZpeGVkID0gVFJVRSkpDQp4JGVuZXJneSA8LSBhcy5udW1lcmljKHN1YigiLCIsICIuIiwgeCRlbmVyZ3ksIGZpeGVkID0gVFJVRSkpDQp4JGluc3RydW1lbnRhbG5lc3MgPC0gYXMubnVtZXJpYyhzdWIoIiwiLCAiLiIsIHgkaW5zdHJ1bWVudGFsbmVzcywgZml4ZWQgPSBUUlVFKSkNCngkbGl2ZW5lc3MgPC0gYXMubnVtZXJpYyhzdWIoIiwiLCAiLiIsIHgkbGl2ZW5lc3MsIGZpeGVkID0gVFJVRSkpDQp4JGxvdWRuZXNzIDwtIGFzLm51bWVyaWMoc3ViKCIsIiwgIi4iLCB4JGxvdWRuZXNzLCBmaXhlZCA9IFRSVUUpKQ0KeCRzcGVlY2hpbmVzcyA8LSBhcy5udW1lcmljKHN1YigiLCIsICIuIiwgeCRzcGVlY2hpbmVzcywgZml4ZWQgPSBUUlVFKSkNCngkdGVtcG8gPC0gYXMubnVtZXJpYyhzdWIoIiwiLCAiLiIsIHgkdGVtcG8sIGZpeGVkID0gVFJVRSkpDQp4JHZhbGVuY2UgPC0gYXMubnVtZXJpYyhzdWIoIiwiLCAiLiIsIHgkdmFsZW5jZSwgZml4ZWQgPSBUUlVFKSkNCngka2V5IDwtIGFzLmZhY3Rvcih4JGtleSkNCngkdGltZV9zaWduYXR1cmUgPC0gYXMuZmFjdG9yKHgkdGltZV9zaWduYXR1cmUpDQptaW5fbWF4X25vcm1hbGl6ZSA8LSBmdW5jdGlvbih4KQ0Kew0KICByZXR1cm4oICgxMDAwLTEwKSooKHgtIG1pbih4KSkgLyhtYXgoeCktbWluKHgpKSkgKyAxMCkNCn0NCngkYWNvdXN0aWNuZXNzIDwtIG1pbl9tYXhfbm9ybWFsaXplKHgkYWNvdXN0aWNuZXNzKQ0KeCRkYW5jZWFiaWxpdHkgPC0gbWluX21heF9ub3JtYWxpemUoeCRkYW5jZWFiaWxpdHkpDQp4JGVuZXJneSA8LSBtaW5fbWF4X25vcm1hbGl6ZSh4JGVuZXJneSkNCngkaW5zdHJ1bWVudGFsbmVzcyA8LSBtaW5fbWF4X25vcm1hbGl6ZSh4JGluc3RydW1lbnRhbG5lc3MpDQp4JGxpdmVuZXNzIDwtIG1pbl9tYXhfbm9ybWFsaXplKHgkbGl2ZW5lc3MpDQp4JGxvdWRuZXNzIDwtIG1pbl9tYXhfbm9ybWFsaXplKHgkbG91ZG5lc3MpDQp4JHNwZWVjaGluZXNzIDwtIG1pbl9tYXhfbm9ybWFsaXplKHgkc3BlZWNoaW5lc3MpDQp4JHRlbXBvIDwtIG1pbl9tYXhfbm9ybWFsaXplKHgkdGVtcG8pDQp4JHZhbGVuY2UgPC0gbWluX21heF9ub3JtYWxpemUoeCR2YWxlbmNlKQ0KeCRHZW5yZSA8LSBhcy5mYWN0b3IoeCRHZW5yZSkNCngka2V5IDwtIGFzLmZhY3Rvcih4JGtleSkNCngkbW9kZSA8LSBhcy5mYWN0b3IoeCRtb2RlKQ0KeCRDaGFydHMgPC0gYXMuZmFjdG9yKHgkQ2hhcnRzKQ0KDQpsaWJyYXJ5KCJkcGx5ciIpDQoNCnggPC0gZHBseXI6OnNlbGVjdCh4LCAtb25lX29mKCdTdHJlYW1zJykpDQp4IDwtIHhbY29tcGxldGUuY2FzZXMoeCksIF0NCg0KDQpgYGANCg0KYGBge3J9DQoNCnZhcl9saXN0IDwtIGMoJ0FydGlzdF9BbGJ1bXNfTnVtYmVyJywgJ0FydGlzdF9BbGJ1bXNfVHJhY2tzX051bWJlcicsICdBcnRpc3RfQXBwZWFyYW5jZXNfTnVtYmVyJywgJ0FydGlzdF9BcHBlYXJhbmNlc19UcmFja3NfTnVtYmVyJywgJ0FydGlzdF9Db21waWxhdGlvbnNfTnVtYmVyJywgJ0FydGlzdF9Db21waWxhdGlvbnNfVHJhY2tzX051bWJlcicsICdBcnRpc3RfRm9sbG93ZXInLCAnQXJ0aXN0X1BvcHVsYXJpdHknLCAnQXJ0aXN0X1NpbmdsZXNfTnVtYmVyJywgJ0FydGlzdF9TaW5nbGVzX1RyYWNrc19OdW1iZXInLCAnVHJhY2tfRHVyYXRpb25fbXMnLCAnVHJhY2tfUG9wdWxhcml0eScsICdhY291c3RpY25lc3MnLCAnZGFuY2VhYmlsaXR5JywgJ2VuZXJneScsICdpbnN0cnVtZW50YWxuZXNzJywgJ2xpdmVuZXNzJywgJ2xvdWRuZXNzJywgJ3NwZWVjaGluZXNzJywgJ3RlbXBvJywgJ3ZhbGVuY2UnLCAnY29tbWVudENvdW50JywgJ2Rpc2xpa2VDb3VudCcsICdsaWtlQ291bnQnLCAndmlld3NDb3VudCcpDQoNCnBhcihtYXIgPSByZXAoMiwgNCkpDQoNCnBhcihtZnJvdz1jKDUsNSkpDQoNCmZvciAoaSBpbiAxOmxlbmd0aCh2YXJfbGlzdCkpew0KICANCiAgaGlzdCh4WywgdmFyX2xpc3RbaV1dLCBwcm9iYWJpbGl0eSA9IFRSVUUsIGNvbCA9ICJncmF5IiwgbWFpbiA9IHZhcl9saXN0W2ldLCB4bGFiID0gIiIpDQogIGxpbmVzKGRlbnNpdHkoeFssIHZhcl9saXN0W2ldXSksIGNvbCA9ICJyZWQiKQ0KDQp9DQoNCg0KDQoNCmBgYA0KDQoNCg0KYGBge3J9DQpwYXIobWFyID0gcmVwKDIsIDQpKQ0KDQpwYXIobWZyb3c9Yyg1LDUpKQ0KDQpmb3IoaSBpbiAxOmxlbmd0aCh2YXJfbGlzdCkpew0KICANCiAgZGYgPC0gZGF0YS5mcmFtZSh4Wyx2YXJfbGlzdFtpXV0pDQogIG5hbWVzKGRmKVsxXSA8LSB2YXJfbGlzdFtpXQ0KICANCiAgI2dncGxvdChkZiwgYWVzKHg9IiIseT1BcnRpc3RfQWxidW1zX051bWJlcikpICsgc3RhdF9ib3hwbG90KGdlb20gPSdlcnJvcmJhcicpICsgZ2VvbV9ib3hwbG90KG91dGxpZXIuY29sb3VyPSJibGFjayIsIG91dGxpZXIuc2hhcGU9MTYsICNvdXRsaWVyLnNpemU9Miwgbm90Y2g9RkFMU0UpDQogIA0KICBib3hwbG90KHggPSBkZlssMV0sIG1haW4gPSB2YXJfbGlzdFtpXSwgbm90Y2ggPSBGQUxTRSkNCg0KfQ0KDQoNCg0KDQpgYGANCg0KDQoNCg0KTm9ybWFsaXR5IHRlc3RpbmcNCg0KDQpgYGB7cn0NCg0KI3N0cmljdGx5X3Bvc2l0aXZlX3ZhcmlhYmxlcyA8LSBjKCdBcnRpc3RfRm9sbG93ZXInLCAnQXJ0aXN0X1BvcHVsYXJpdHknLCAnVHJhY2tfRHVyYXRpb25fbXMnLCAnVHJhY2tfUG9wdWxhcml0eScsICd2aWV3c0NvdW50JywgIydhY291c3RpY25lc3MnLCAnZGFuY2VhYmlsaXR5JywgJ2VuZXJneScsICdsaXZlbmVzcycsICdsb3VkbmVzcycsICdzcGVlY2hpbmVzcycsICd0ZW1wbycsICd2YWxlbmNlJykNCg0KZm9yIChpIGluIDE6bGVuZ3RoKHZhcl9saXN0KSl7DQogIA0KICBjb2x1bW5fbmFtZSA8LSB2YXJfbGlzdFtpXQ0KICANCiAgbWVzc2FnZShjb2x1bW5fbmFtZSkNCiAgDQogIHN1Yl9kZiA8LSBhcy5udW1lcmljKHhbLGNvbHVtbl9uYW1lXSkNCiAgDQogICMgc3ViX2RmIDwtIHN1Yl9kZltjb21wbGV0ZS5jYXNlcyhzdWJfZGYpLCBdDQogIA0KICAjc3ViX2RmIDwtIGFzLm51bWVyaWMoYXMuY2hhcmFjdGVyKHVubGlzdChzdWJfZGZbWzFdXSkpKQ0KICANCiAgdGVzdF9zdGF0aXN0aWMgPC0ga3MudGVzdChzdWJfZGYsICJwbm9ybSIsIG1lYW49bWVhbihzdWJfZGYpLCBzZD1zZChzdWJfZGYpKSRzdGF0aXN0aWMNCiAgY3JpdGljYWxfdmFsdWUgPC0gMS4zNTgxIC8gc3FydCAobGVuZ3RoKHN1Yl9kZikpDQogIA0KICBpZiAodGVzdF9zdGF0aXN0aWMgPiBjcml0aWNhbF92YWx1ZSkgew0KbWVzc2FnZShjb2x1bW5fbmFtZSkNCn0gZWxzZSB7DQptZXNzYWdlKHBhc3RlKCIgIiwgY29sdW1uX25hbWUgLCAiIGlzIGFwcHJveGltYXRlbHkgbm9ybWFsbHkgZGlzdHJpYnV0ZWQhIiwgdGVzdF9zdGF0aXN0aWMsIGNyaXRpY2FsX3ZhbHVlKSkgIA0KfX0NCg0KDQpgYGANCg0KYGBge3J9DQoNCnhfc2NhbGVkIDwtIGFzLmRhdGEuZnJhbWUoc2NhbGUoeFssIHZhcl9saXN0XSkpDQoNCnBhcihtYXIgPSByZXAoMiwgNCkpDQoNCnBhcihtZnJvdz1jKDUsNSkpDQoNCmZvciAoaSBpbiAxOmxlbmd0aCh2YXJfbGlzdCkpew0KICANCiAgaGlzdCh4X3NjYWxlZFssIHZhcl9saXN0W2ldXSwgcHJvYmFiaWxpdHkgPSBUUlVFLCBjb2wgPSAiZ3JheSIsIG1haW4gPSB2YXJfbGlzdFtpXSwgeGxhYiA9ICIiKQ0KICBsaW5lcyhkZW5zaXR5KHhfc2NhbGVkWywgdmFyX2xpc3RbaV1dKSwgY29sID0gInJlZCIpDQoNCn0NCg0KDQpgYGANCg0KDQpgYGB7cn0NCg0KcGFyKG1hciA9IHJlcCgyLCA0KSkNCg0KcGFyKG1mcm93PWMoNSw1KSkNCg0KZm9yKGkgaW4gMTpsZW5ndGgodmFyX2xpc3QpKXsNCiAgDQogIGRmIDwtIGRhdGEuZnJhbWUoeF9zY2FsZWRbLHZhcl9saXN0W2ldXSkNCiAgbmFtZXMoZGYpWzFdIDwtIHZhcl9saXN0W2ldDQogIA0KICAjZ2dwbG90KGRmLCBhZXMoeD0iIix5PUFydGlzdF9BbGJ1bXNfTnVtYmVyKSkgKyBzdGF0X2JveHBsb3QoZ2VvbSA9J2Vycm9yYmFyJykgKyBnZW9tX2JveHBsb3Qob3V0bGllci5jb2xvdXI9ImJsYWNrIiwgb3V0bGllci5zaGFwZT0xNiwgI291dGxpZXIuc2l6ZT0yLCBub3RjaD1GQUxTRSkNCiAgDQogIGJveHBsb3QoeCA9IGRmWywxXSwgbWFpbiA9IHZhcl9saXN0W2ldLCBub3RjaCA9IEZBTFNFKQ0KDQp9DQoNCmBgYA0KDQoNCg0KYGBge3J9DQoNCmZvciAoaSBpbiAxOmxlbmd0aCh2YXJfbGlzdCkpew0KICANCiAgY29sdW1uX25hbWUgPC0gdmFyX2xpc3RbaV0NCiAgDQogIG1lc3NhZ2UoY29sdW1uX25hbWUpDQogIA0KICBzdWJfZGYgPC0gYXMubnVtZXJpYyh4X3NjYWxlZFssY29sdW1uX25hbWVdKQ0KICANCiAgIyBzdWJfZGYgPC0gc3ViX2RmW2NvbXBsZXRlLmNhc2VzKHN1Yl9kZiksIF0NCiAgDQogICNzdWJfZGYgPC0gYXMubnVtZXJpYyhhcy5jaGFyYWN0ZXIodW5saXN0KHN1Yl9kZltbMV1dKSkpDQogIA0KICB0ZXN0X3N0YXRpc3RpYyA8LSBrcy50ZXN0KHN1Yl9kZiwgInBub3JtIiwgbWVhbj1tZWFuKHN1Yl9kZiksIHNkPXNkKHN1Yl9kZikpJHN0YXRpc3RpYw0KICBjcml0aWNhbF92YWx1ZSA8LSAxLjM1ODEgLyBzcXJ0IChsZW5ndGgoc3ViX2RmKSkNCiAgDQogIGlmICh0ZXN0X3N0YXRpc3RpYyA+IGNyaXRpY2FsX3ZhbHVlKSB7DQptZXNzYWdlKGNvbHVtbl9uYW1lKQ0KfSBlbHNlIHsNCm1lc3NhZ2UocGFzdGUoIiAiLCBjb2x1bW5fbmFtZSAsICIgaXMgYXBwcm94aW1hdGVseSBub3JtYWxseSBkaXN0cmlidXRlZCEiLCB0ZXN0X3N0YXRpc3RpYywgY3JpdGljYWxfdmFsdWUpKSAgDQp9fQ0KDQoNCg0KYGBgDQoNCmBgYHtyfQ0KDQpzdHJpY3RseV9wb3NpdGl2ZV92YXJpYWJsZXMgPC0gYygnQXJ0aXN0X0ZvbGxvd2VyJywgJ0FydGlzdF9Qb3B1bGFyaXR5JywgJ0FydGlzdF9TaW5nbGVzX051bWJlcicsICdBcnRpc3RfU2luZ2xlc19UcmFja3NfTnVtYmVyJywgJ1RyYWNrX0R1cmF0aW9uX21zJywgJ1RyYWNrX1BvcHVsYXJpdHknLCAnYWNvdXN0aWNuZXNzJywgJ2RhbmNlYWJpbGl0eScsICdlbmVyZ3knLCAnaW5zdHJ1bWVudGFsbmVzcycsICdsaXZlbmVzcycsICdsb3VkbmVzcycsICdzcGVlY2hpbmVzcycsICd0ZW1wbycsICd2YWxlbmNlJywgJ2xpa2VDb3VudCcsICd2aWV3c0NvdW50JykNCg0KbGlicmFyeSgicHN5Y2giKQ0KbGlicmFyeSgiY2FyIikNCg0Ka3NEIDwtIGZ1bmN0aW9uIChwLCB4KSB7DQogIHkgPC0gYmNQb3dlcih4LCBwKQ0KICBrcy50ZXN0KHksICJwbm9ybSIsIG1lYW49bWVhbih5KSwgc2Q9c2QoeSkpJHN0YXRpc3RpYw0KfQ0KDQpvbGR3IDwtIGdldE9wdGlvbigid2FybiIpDQpvcHRpb25zKHdhcm4gPSAtMSkNCg0KbWluX3ZhbHVlcyA8LSBjKCkNCg0KZm9yIChjb2x1bW5faW5kZXggaW4gMTpsZW5ndGgoc3RyaWN0bHlfcG9zaXRpdmVfdmFyaWFibGVzKSl7DQogIA0KICBjb2x1bW5fbmFtZSA8LSBzdHJpY3RseV9wb3NpdGl2ZV92YXJpYWJsZXNbY29sdW1uX2luZGV4XQ0KICANCiAgeF9zdWIgPC0geFtbcGFzdGUoY29sdW1uX25hbWUpXV0NCiAgDQogIHJlc3VsdCA8LSBvcHRpbWl6ZShrc0QsIGMoLTUsNSksIHg9eF9zdWIpDQogIA0KICBtaW5fdmFsdWVzW2NvbHVtbl9pbmRleF0gPC0gcmVzdWx0JG1pbmltdW0NCiAgDQogIG1lc3NhZ2UocGFzdGUoY29sdW1uX2luZGV4LCAnLCBtaW5pbXVtIHZhbHVlIGlzOiAnLCByZXN1bHQkbWluaW11bSkpDQogIA0KfQ0KDQpvcHRpb25zKHdhcm4gPSBvbGR3KQ0KDQpgYGANCg0KDQpgYGB7cn0NCg0KY29sdW1uX2luZGV4IDwtIDENCmNvbHVtbl9uYW1lIDwtIHN0cmljdGx5X3Bvc2l0aXZlX3ZhcmlhYmxlc1tjb2x1bW5faW5kZXhdDQp4X3N1YiA8LSB4W1twYXN0ZShjb2x1bW5fbmFtZSldXQ0KQXJ0aXN0X0ZvbGxvd2VyX3RyYW5zIDwtIGJjUG93ZXIoeF9zdWIsIG1pbl92YWx1ZXNbY29sdW1uX2luZGV4XSkNCg0KY29sdW1uX2luZGV4IDwtIDINCmNvbHVtbl9uYW1lIDwtIHN0cmljdGx5X3Bvc2l0aXZlX3ZhcmlhYmxlc1tjb2x1bW5faW5kZXhdDQp4X3N1YiA8LSB4W1twYXN0ZShjb2x1bW5fbmFtZSldXQ0KQXJ0aXN0X1BvcHVsYXJpdHlfdHJhbnMgPC0gYmNQb3dlcih4X3N1YiwgbWluX3ZhbHVlc1tjb2x1bW5faW5kZXhdKQ0KDQpjb2x1bW5faW5kZXggPC0gMw0KY29sdW1uX25hbWUgPC0gc3RyaWN0bHlfcG9zaXRpdmVfdmFyaWFibGVzW2NvbHVtbl9pbmRleF0NCnhfc3ViIDwtIHhbW3Bhc3RlKGNvbHVtbl9uYW1lKV1dDQpBcnRpc3RfU2luZ2xlc19OdW1iZXJfdHJhbnMgPC0gYmNQb3dlcih4X3N1YiwgbWluX3ZhbHVlc1tjb2x1bW5faW5kZXhdKQ0KDQpjb2x1bW5faW5kZXggPC0gNA0KY29sdW1uX25hbWUgPC0gc3RyaWN0bHlfcG9zaXRpdmVfdmFyaWFibGVzW2NvbHVtbl9pbmRleF0NCnhfc3ViIDwtIHhbW3Bhc3RlKGNvbHVtbl9uYW1lKV1dDQpBcnRpc3RfU2luZ2xlc19UcmFja3NfTnVtYmVyX3RyYW5zIDwtIGJjUG93ZXIoeF9zdWIsIG1pbl92YWx1ZXNbY29sdW1uX2luZGV4XSkNCg0KY29sdW1uX2luZGV4IDwtIDUNCmNvbHVtbl9uYW1lIDwtIHN0cmljdGx5X3Bvc2l0aXZlX3ZhcmlhYmxlc1tjb2x1bW5faW5kZXhdDQp4X3N1YiA8LSB4W1twYXN0ZShjb2x1bW5fbmFtZSldXQ0KVHJhY2tfRHVyYXRpb25fbXNfdHJhbnMgPC0gYmNQb3dlcih4X3N1YiwgbWluX3ZhbHVlc1tjb2x1bW5faW5kZXhdKQ0KDQpjb2x1bW5faW5kZXggPC0gNg0KY29sdW1uX25hbWUgPC0gc3RyaWN0bHlfcG9zaXRpdmVfdmFyaWFibGVzW2NvbHVtbl9pbmRleF0NCnhfc3ViIDwtIHhbW3Bhc3RlKGNvbHVtbl9uYW1lKV1dDQpUcmFja19Qb3B1bGFyaXR5X3RyYW5zIDwtIGJjUG93ZXIoeF9zdWIsIG1pbl92YWx1ZXNbY29sdW1uX2luZGV4XSkNCg0KY29sdW1uX2luZGV4IDwtIDcNCmNvbHVtbl9uYW1lIDwtIHN0cmljdGx5X3Bvc2l0aXZlX3ZhcmlhYmxlc1tjb2x1bW5faW5kZXhdDQp4X3N1YiA8LSB4W1twYXN0ZShjb2x1bW5fbmFtZSldXQ0KYWNvdXN0aWNuZXNzX3RyYW5zIDwtIGJjUG93ZXIoeF9zdWIsIG1pbl92YWx1ZXNbY29sdW1uX2luZGV4XSkNCg0KY29sdW1uX2luZGV4IDwtIDgNCmNvbHVtbl9uYW1lIDwtIHN0cmljdGx5X3Bvc2l0aXZlX3ZhcmlhYmxlc1tjb2x1bW5faW5kZXhdDQp4X3N1YiA8LSB4W1twYXN0ZShjb2x1bW5fbmFtZSldXQ0KZGFuY2VhYmlsaXR5X3RyYW5zIDwtIGJjUG93ZXIoeF9zdWIsIG1pbl92YWx1ZXNbY29sdW1uX2luZGV4XSkNCg0KY29sdW1uX2luZGV4IDwtIDkNCmNvbHVtbl9uYW1lIDwtIHN0cmljdGx5X3Bvc2l0aXZlX3ZhcmlhYmxlc1tjb2x1bW5faW5kZXhdDQp4X3N1YiA8LSB4W1twYXN0ZShjb2x1bW5fbmFtZSldXQ0KZW5lcmd5X3RyYW5zIDwtIGJjUG93ZXIoeF9zdWIsIG1pbl92YWx1ZXNbY29sdW1uX2luZGV4XSkNCg0KY29sdW1uX2luZGV4IDwtIDEwDQpjb2x1bW5fbmFtZSA8LSBzdHJpY3RseV9wb3NpdGl2ZV92YXJpYWJsZXNbY29sdW1uX2luZGV4XQ0KeF9zdWIgPC0geFtbcGFzdGUoY29sdW1uX25hbWUpXV0NCmluc3RydW1lbnRhbG5lc3NfdHJhbnMgPC0gYmNQb3dlcih4X3N1YiwgbWluX3ZhbHVlc1tjb2x1bW5faW5kZXhdKQ0KDQpjb2x1bW5faW5kZXggPC0gMTENCmNvbHVtbl9uYW1lIDwtIHN0cmljdGx5X3Bvc2l0aXZlX3ZhcmlhYmxlc1tjb2x1bW5faW5kZXhdDQp4X3N1YiA8LSB4W1twYXN0ZShjb2x1bW5fbmFtZSldXQ0KbGl2ZW5lc3NfdHJhbnMgPC0gYmNQb3dlcih4X3N1YiwgbWluX3ZhbHVlc1tjb2x1bW5faW5kZXhdKQ0KDQpjb2x1bW5faW5kZXggPC0gMTINCmNvbHVtbl9uYW1lIDwtIHN0cmljdGx5X3Bvc2l0aXZlX3ZhcmlhYmxlc1tjb2x1bW5faW5kZXhdDQp4X3N1YiA8LSB4W1twYXN0ZShjb2x1bW5fbmFtZSldXQ0KbG91ZG5lc3NfdHJhbnMgPC0gYmNQb3dlcih4X3N1YiwgbWluX3ZhbHVlc1tjb2x1bW5faW5kZXhdKQ0KDQpjb2x1bW5faW5kZXggPC0gMTMNCmNvbHVtbl9uYW1lIDwtIHN0cmljdGx5X3Bvc2l0aXZlX3ZhcmlhYmxlc1tjb2x1bW5faW5kZXhdDQp4X3N1YiA8LSB4W1twYXN0ZShjb2x1bW5fbmFtZSldXQ0Kc3BlZWNoaW5lc3NfdHJhbnMgPC0gYmNQb3dlcih4X3N1YiwgbWluX3ZhbHVlc1tjb2x1bW5faW5kZXhdKQ0KDQpjb2x1bW5faW5kZXggPC0gMTQNCmNvbHVtbl9uYW1lIDwtIHN0cmljdGx5X3Bvc2l0aXZlX3ZhcmlhYmxlc1tjb2x1bW5faW5kZXhdDQp4X3N1YiA8LSB4W1twYXN0ZShjb2x1bW5fbmFtZSldXQ0KdGVtcG9fdHJhbnMgPC0gYmNQb3dlcih4X3N1YiwgbWluX3ZhbHVlc1tjb2x1bW5faW5kZXhdKQ0KDQpjb2x1bW5faW5kZXggPC0gMTUNCmNvbHVtbl9uYW1lIDwtIHN0cmljdGx5X3Bvc2l0aXZlX3ZhcmlhYmxlc1tjb2x1bW5faW5kZXhdDQp4X3N1YiA8LSB4W1twYXN0ZShjb2x1bW5fbmFtZSldXQ0KdmFsZW5jZV90cmFucyA8LSBiY1Bvd2VyKHhfc3ViLCBtaW5fdmFsdWVzW2NvbHVtbl9pbmRleF0pDQoNCmNvbHVtbl9pbmRleCA8LSAxNg0KY29sdW1uX25hbWUgPC0gc3RyaWN0bHlfcG9zaXRpdmVfdmFyaWFibGVzW2NvbHVtbl9pbmRleF0NCnhfc3ViIDwtIHhbW3Bhc3RlKGNvbHVtbl9uYW1lKV1dDQpsaWtlQ291bnRfdHJhbnMgPC0gYmNQb3dlcih4X3N1YiwgbWluX3ZhbHVlc1tjb2x1bW5faW5kZXhdKQ0KDQpjb2x1bW5faW5kZXggPC0gMTcNCmNvbHVtbl9uYW1lIDwtIHN0cmljdGx5X3Bvc2l0aXZlX3ZhcmlhYmxlc1tjb2x1bW5faW5kZXhdDQp4X3N1YiA8LSB4W1twYXN0ZShjb2x1bW5fbmFtZSldXQ0Kdmlld3NDb3VudF90cmFucyA8LSBiY1Bvd2VyKHhfc3ViLCBtaW5fdmFsdWVzW2NvbHVtbl9pbmRleF0pDQoNCg0KYGBgDQoNCg0KYGBge3J9DQoNCmhpc3RfdHJhbnNfbGlzdCA8LSBsaXN0KEFydGlzdF9Gb2xsb3dlcl90cmFucywgDQogICAgICAgICAgICAgICAgICAgICAgICBBcnRpc3RfUG9wdWxhcml0eV90cmFucywNCiAgICAgICAgICAgICAgICAgICAgICAgIEFydGlzdF9TaW5nbGVzX051bWJlcl90cmFucywNCiAgICAgICAgICAgICAgICAgICAgICAgIEFydGlzdF9TaW5nbGVzX1RyYWNrc19OdW1iZXJfdHJhbnMsDQogICAgICAgICAgICAgICAgICAgICAgICBUcmFja19EdXJhdGlvbl9tc190cmFucywgDQogICAgICAgICAgICAgICAgICAgICAgICBUcmFja19Qb3B1bGFyaXR5X3RyYW5zLA0KICAgICAgICAgICAgICAgICAgICAgYWNvdXN0aWNuZXNzX3RyYW5zLCANCiAgICAgICAgICAgICAgICAgICAgIGRhbmNlYWJpbGl0eV90cmFucywgDQogICAgICAgICAgICAgICAgICAgICBlbmVyZ3lfdHJhbnMsIA0KICAgICAgICAgICAgICAgICAgICAgaW5zdHJ1bWVudGFsbmVzc190cmFucywNCiAgICAgICAgICAgICAgICAgICAgIGxpdmVuZXNzX3RyYW5zLCANCiAgICAgICAgICAgICAgICAgICAgIGxvdWRuZXNzX3RyYW5zLCANCiAgICAgICAgICAgICAgICAgICAgIHNwZWVjaGluZXNzX3RyYW5zLCANCiAgICAgICAgICAgICAgICAgICAgIHRlbXBvX3RyYW5zLCANCiAgICAgICAgICAgICAgICAgICAgIHZhbGVuY2VfdHJhbnMsDQogICAgICAgICAgICAgICAgICAgICBsaWtlQ291bnRfdHJhbnMsDQogICAgICAgICAgICAgICAgICAgICB2aWV3c0NvdW50X3RyYW5zKQ0KDQpwYXIobWFyID0gcmVwKDIsIDQpKQ0KcGFyKG1mcm93PWMoNCw1KSkNCg0KZm9yICh0cmFuc19pbmRleCBpbiAxOmxlbmd0aChoaXN0X3RyYW5zX2xpc3QpKXsNCiAgDQogIGNvbHVtbl9uYW1lIDwtIHN0cmljdGx5X3Bvc2l0aXZlX3ZhcmlhYmxlc1t0cmFuc19pbmRleF0NCiAgDQogIHNlbGVjdGVkX3RyYW5zIDwtIGhpc3RfdHJhbnNfbGlzdFt0cmFuc19pbmRleF0NCiAgc2VsZWN0ZWRfdHJhbnMgPC0gYXMubnVtZXJpYyhhcy5jaGFyYWN0ZXIodW5saXN0KHNlbGVjdGVkX3RyYW5zW1sxXV0pKSkNCiAgDQogIGhpc3Qoc2VsZWN0ZWRfdHJhbnMsIGNvbCA9ICJncmF5IiwgcHJvYmFiaWxpdHkgPSBUUlVFLCBtYWluID0gY29sdW1uX25hbWUsIHhsYWIgPSAiIikNCiAgcG9pbnRzKHNlcShtaW4oc2VsZWN0ZWRfdHJhbnMpLCBtYXgoc2VsZWN0ZWRfdHJhbnMpLCBsZW5ndGgub3V0ID0gNTAwKSwNCiAgICAgICBkbm9ybShzZXEobWluKHNlbGVjdGVkX3RyYW5zKSwgbWF4KHNlbGVjdGVkX3RyYW5zKSwgbGVuZ3RoLm91dCA9IDUwMCksDQogICAgICAgICAgICAgbWVhbihzZWxlY3RlZF90cmFucyksc2Qoc2VsZWN0ZWRfdHJhbnMpKSwgdHlwZSA9ICJsIiwgY29sID0gInJlZCIpDQogIA0KICB0ZXN0X3N0YXRpc3RpYyA8LSBrcy50ZXN0KHNlbGVjdGVkX3RyYW5zLCAicG5vcm0iLCBtZWFuPW1lYW4oc2VsZWN0ZWRfdHJhbnMpLCBzZD1zZChzZWxlY3RlZF90cmFucykpJHN0YXRpc3RpYw0KICBjcml0aWNhbF92YWx1ZSA8LSAxLjM1ODEgLyBzcXJ0IChsZW5ndGgoc2VsZWN0ZWRfdHJhbnMpKQ0KICANCiAgaWYgKHRlc3Rfc3RhdGlzdGljID4gY3JpdGljYWxfdmFsdWUpIHsNCm1lc3NhZ2UocGFzdGUoIlRyYW5zZm9ybWVkICIsIGNvbHVtbl9uYW1lICwgIiBpcyBub3QgYXBwcm94aW1hdGVseSBub3JtYWxseSBkaXN0cmlidXRlZC4iLCB0ZXN0X3N0YXRpc3RpYywgY3JpdGljYWxfdmFsdWUpKQ0KfSBlbHNlIHsNCm1lc3NhZ2UocGFzdGUoIlRyYW5zZm9ybWVkICIsIGNvbHVtbl9uYW1lICwgIiBpcyBhcHByb3hpbWF0ZWx5IG5vcm1hbGx5IGRpc3RyaWJ1dGVkISIsIHRlc3Rfc3RhdGlzdGljLCBjcml0aWNhbF92YWx1ZSkpICANCn19DQoNCg0KDQpgYGANCg0KYGBge3J9DQoNCnBhcihtYXIgPSByZXAoMiwgNCkpDQoNCnBhcihtZnJvdz1jKDQsNSkpDQoNCmZvcih0cmFuc19pbmRleCBpbiAxOmxlbmd0aChzdHJpY3RseV9wb3NpdGl2ZV92YXJpYWJsZXMpKXsNCiAgDQogIA0KICBjb2x1bW5fbmFtZSA8LSBzdHJpY3RseV9wb3NpdGl2ZV92YXJpYWJsZXNbdHJhbnNfaW5kZXhdDQogIA0KICBzZWxlY3RlZF90cmFucyA8LSBoaXN0X3RyYW5zX2xpc3RbdHJhbnNfaW5kZXhdDQogIHNlbGVjdGVkX3RyYW5zIDwtIGFzLm51bWVyaWMoYXMuY2hhcmFjdGVyKHVubGlzdChzZWxlY3RlZF90cmFuc1tbMV1dKSkpDQogIA0KICBkZiA8LSBkYXRhLmZyYW1lKHNlbGVjdGVkX3RyYW5zKQ0KICBuYW1lcyhkZilbMV0gPC0gc3RyaWN0bHlfcG9zaXRpdmVfdmFyaWFibGVzW3RyYW5zX2luZGV4XQ0KICANCiAgYm94cGxvdCh4ID0gZGZbLDFdLCBtYWluID0gc3RyaWN0bHlfcG9zaXRpdmVfdmFyaWFibGVzW3RyYW5zX2luZGV4XSwgbm90Y2ggPSBGQUxTRSkNCiAgDQp9DQoNCg0KYGBgDQoNCkNvcnJlbGF0aW9uDQoNCmBgYHtyfQ0KDQoNCmxpYnJhcnkoY29ycnBsb3QpDQoNCm1ldGhvZCA8LSAicGVhcnNvbiINCg0KY2xlYW5fY29yIDwtIGNvcih4Wyx2YXJfbGlzdF0sIG1ldGhvZCA9IG1ldGhvZCkNCg0KY29yLm10ZXN0IDwtIGZ1bmN0aW9uKG1hdCwgLi4uKSB7DQogIG1hdCA8LSBhcy5tYXRyaXgobWF0KQ0KICBuIDwtIG5jb2wobWF0KQ0KICBwLm1hdDwtIG1hdHJpeChOQSwgbiwgbikNCiAgZGlhZyhwLm1hdCkgPC0gMA0KICBmb3IgKGkgaW4gMToobiAtIDEpKSB7DQogICAgZm9yIChqIGluIChpICsgMSk6bikgew0KICAgICAgdG1wIDwtIGNvci50ZXN0KG1hdFssIGldLCBtYXRbLCBqXSwgLi4uLCBtZXRob2QgPSBtZXRob2QpDQogICAgICBwLm1hdFtpLCBqXSA8LSBwLm1hdFtqLCBpXSA8LSB0bXAkcC52YWx1ZQ0KICAgIH0NCiAgfQ0KICBjb2xuYW1lcyhwLm1hdCkgPC0gcm93bmFtZXMocC5tYXQpIDwtIGNvbG5hbWVzKG1hdCkNCiAgcC5tYXQNCn0NCg0KIyBtYXRyaXggb2YgdGhlIHAtdmFsdWUgb2YgdGhlIGNvcnJlbGF0aW9uDQpwLm1hdCA8LSBjb3IubXRlc3QoY2xlYW5fY29yKQ0KDQpjb2wgPC0gY29sb3JSYW1wUGFsZXR0ZShjKCIjQkI0NDQ0IiwgIiNFRTk5ODgiLCAiI0ZGRkZGRiIsICIjNzdBQUREIiwgIiM0NDc3QUEiKSkNCnNpZ25pZmljYW5jZV9sZXZlbCA8LSAwLjA1DQoNCmNvcnJwbG90KGNsZWFuX2NvciwgbWV0aG9kPSJjb2xvciIsIGNvbD1jb2woMjAwKSwNCiAgICAgICAgIHR5cGU9InVwcGVyIiwgb3JkZXI9ImFscGhhYmV0IiwgDQogICAgICAgICBhZGRDb2VmLmNvbCA9ICJibGFjayIsICMgQWRkIGNvZWZmaWNpZW50IG9mIGNvcnJlbGF0aW9uDQogICAgICAgICB0bC5jb2w9ImJsYWNrIiwgdGwuc3J0PTkwLCAjVGV4dCBsYWJlbCBjb2xvciBhbmQgcm90YXRpb24NCiAgICAgICAgICMgQ29tYmluZSB3aXRoIHNpZ25pZmljYW5jZQ0KICAgICAgICAgcC5tYXQgPSBwLm1hdCwgc2lnLmxldmVsID0gc2lnbmlmaWNhbmNlX2xldmVsLCBpbnNpZyA9ICJibGFuayIsIA0KICAgICAgICAgIyBoaWRlIGNvcnJlbGF0aW9uIGNvZWZmaWNpZW50IG9uIHRoZSBwcmluY2lwYWwgZGlhZ29uYWwNCiAgICAgICAgIGRpYWc9RkFMU0UpDQoNCg0KDQoNCmBgYA0KDQpTdGVpZ2VyJ3MgWiB0ZXN0DQoNCmBgYHtyfQ0KDQojDQoNCmxtMSA8LSBsbSh4JEFydGlzdF9Qb3B1bGFyaXR5IH4geCRBcnRpc3RfRm9sbG93ZXIpDQpzdW1tYXJ5KGxtMSkNCmxtMiA8LSBsbShBcnRpc3RfUG9wdWxhcml0eV90cmFucyB+IEFydGlzdF9Gb2xsb3dlcl90cmFucykNCnN1bW1hcnkobG0yKQ0KDQpwYXIobWZyb3c9YygxLDIpKQ0KcGxvdCh4JEFydGlzdF9Gb2xsb3dlciwgeCRBcnRpc3RfUG9wdWxhcml0eSwgbWFpbiA9IHBhc3RlKCJVbnRyYW5zZm9ybWVkLCBSXjI9Iiwgcm91bmQoc3VtbWFyeShsbTEpJHIuc3F1YXJlZCwgZGlnaXRzID0yKSksIHhsYWIgPSAiQXJ0aXN0X0ZvbGxvd2VyIiwgeWxhYiA9ICJBcnRpc3RfUG9wdWxhcml0eSIpDQpwbG90KEFydGlzdF9Gb2xsb3dlcl90cmFucywgQXJ0aXN0X1BvcHVsYXJpdHlfdHJhbnMsIG1haW4gPSBwYXN0ZSgiQm94LUNveCB0cmFuc2Zvcm1lZCwgUl4yPSIsIHJvdW5kKHN1bW1hcnkobG0yKSRyLnNxdWFyZWQsIGRpZ2l0cyA9MikpLCB4bGFiID0gIkFydGlzdF9Gb2xsb3dlciIsIHlsYWIgPSAiQXJ0aXN0X1BvcHVsYXJpdHkiKQ0KDQpiaXZhcmlhdGVfZGZfYmFzZSA8LSBkYXRhLmZyYW1lKHgkQXJ0aXN0X0ZvbGxvd2VyLCB4JEFydGlzdF9Qb3B1bGFyaXR5KQ0KYml2YXJpYXRlX2RmX2JjIDwtIGRhdGEuZnJhbWUoQXJ0aXN0X0ZvbGxvd2VyX3RyYW5zLCBBcnRpc3RfUG9wdWxhcml0eV90cmFucykNCg0KbGlicmFyeSgiTVZOIikNCk1WTjo6bXZuKGJpdmFyaWF0ZV9kZl9iYXNlLCBtdm5UZXN0ID0gIm1hcmRpYSIpJG11bHRpdmFyaWF0ZU5vcm1hbGl0eSAjIE5vdCBqb2ludGx5IG5vcm1hbA0KTVZOOjptdm4oYml2YXJpYXRlX2RmX2Jhc2UsIG12blRlc3QgPSAiaHoiKSRtdWx0aXZhcmlhdGVOb3JtYWxpdHkgIyBOb3Qgam9pbnRseSBub3JtYWwNCk1WTjo6bXZuKGJpdmFyaWF0ZV9kZl9iYXNlLCBtdm5UZXN0ID0gInJveXN0b24iKSRtdWx0aXZhcmlhdGVOb3JtYWxpdHkgIyBOb3Qgam9pbnRseSBub3JtYWwNCk1WTjo6bXZuKGJpdmFyaWF0ZV9kZl9iYXNlLCBtdm5UZXN0ID0gImVuZXJneSIpJG11bHRpdmFyaWF0ZU5vcm1hbGl0eSAjIEpvaW50bHkgbm9ybWFsDQoNCk1WTjo6bXZuKGJpdmFyaWF0ZV9kZl9iYywgbXZuVGVzdCA9ICJtYXJkaWEiKSRtdWx0aXZhcmlhdGVOb3JtYWxpdHkgIyBOb3Qgam9pbnRseSBub3JtYWwNCk1WTjo6bXZuKGJpdmFyaWF0ZV9kZl9iYywgbXZuVGVzdCA9ICJoeiIpJG11bHRpdmFyaWF0ZU5vcm1hbGl0eSAjIE5vdCBqb2ludGx5IG5vcm1hbA0KTVZOOjptdm4oYml2YXJpYXRlX2RmX2JjLCBtdm5UZXN0ID0gInJveXN0b24iKSRtdWx0aXZhcmlhdGVOb3JtYWxpdHkgIyBOb3Qgam9pbnRseSBub3JtYWwNCk1WTjo6bXZuKGJpdmFyaWF0ZV9kZl9iYywgbXZuVGVzdCA9ICJlbmVyZ3kiKSRtdWx0aXZhcmlhdGVOb3JtYWxpdHkgIyBKb2ludGx5IG5vcm1hbA0KDQpgYGANCg0KYGBge3J9DQoNCmJveGNveF9kZiA8LSBkYXRhLmZyYW1lKG1hdHJpeCh2ZWN0b3IoKSwgbnJvdyA9IDE5NiwgbmNvbCA9IDE3KSkNCmJveGNveF9kZiRBcnRpc3RfRm9sbG93ZXIgPC0gQXJ0aXN0X0ZvbGxvd2VyX3RyYW5zDQpib3hjb3hfZGYkQXJ0aXN0X1BvcHVsYXJpdHkgPC0gQXJ0aXN0X1BvcHVsYXJpdHlfdHJhbnMNCmJveGNveF9kZiRBcnRpc3RfU2luZ2xlc19OdW1iZXIgPC0gQXJ0aXN0X1NpbmdsZXNfTnVtYmVyX3RyYW5zDQpib3hjb3hfZGYkQXJ0aXN0X1NpbmdsZXNfVHJhY2tzX051bWJlciA8LSBBcnRpc3RfU2luZ2xlc19UcmFja3NfTnVtYmVyX3RyYW5zDQpib3hjb3hfZGYkVHJhY2tfRHVyYXRpb25fbXMgPC0gVHJhY2tfRHVyYXRpb25fbXNfdHJhbnMNCmJveGNveF9kZiRUcmFja19Qb3B1bGFyaXR5IDwtIFRyYWNrX1BvcHVsYXJpdHlfdHJhbnMNCmJveGNveF9kZiRhY291c3RpY25lc3MgPC0gYWNvdXN0aWNuZXNzX3RyYW5zDQpib3hjb3hfZGYkZGFuY2VhYmlsaXR5IDwtIGRhbmNlYWJpbGl0eV90cmFucw0KYm94Y294X2RmJGVuZXJneSA8LSBlbmVyZ3lfdHJhbnMNCmJveGNveF9kZiRpbnN0cnVtZW50YWxuZXNzIDwtIGluc3RydW1lbnRhbG5lc3NfdHJhbnMNCmJveGNveF9kZiRsaXZlbmVzcyA8LSBsaXZlbmVzc190cmFucw0KYm94Y294X2RmJGxvdWRuZXNzIDwtIGxvdWRuZXNzX3RyYW5zDQpib3hjb3hfZGYkc3BlZWNoaW5lc3MgPC0gc3BlZWNoaW5lc3NfdHJhbnMNCmJveGNveF9kZiR0ZW1wbyA8LSB0ZW1wb190cmFucw0KYm94Y294X2RmJHZhbGVuY2UgPC0gdmFsZW5jZV90cmFucw0KYm94Y294X2RmJGxpa2VDb3VudCA8LSBsaWtlQ291bnRfdHJhbnMNCmJveGNveF9kZiR2aWV3c0NvdW50IDwtIHZpZXdzQ291bnRfdHJhbnMNCg0KZHJvcC5jb2xzIDwtIGMoJ1gxJywgJ1gyJywgJ1gzJywgJ1g0JywgJ1g1JywgJ1g2JywgJ1g3JywgJ1g4JywgJ1g5JywgJ1gxMCcsICdYMTEnLCAnWDEyJywgJ1gxMycsICdYMTQnLCAnWDE1JywgJ1gxNicsICdYMTcnKQ0KDQpib3hjb3hfZGYgPC0gZHBseXI6OnNlbGVjdChib3hjb3hfZGYsIC1vbmVfb2YoZHJvcC5jb2xzKSkNCg0KcGFyKG1mcm93PWMoMSwxKSkNCg0KcGFsZXR0ZSA8LSBjKCdyZWQnLCAnYmx1ZScsICdibGFjaycsICdwdXJwbGUnKQ0KIyBwYWxldHRlIDwtIHJhaW5ib3cobGVuZ3RoKGxldmVscyhhcy5mYWN0b3IoeCRHZW5yZSkpKSkNCm15X2NvbG9ycyA8LSBwYWxldHRlW2FzLm51bWVyaWMoeCRHZW5yZSldDQoNCnBsb3QoQXJ0aXN0X0ZvbGxvd2VyX3RyYW5zLCBBcnRpc3RfUG9wdWxhcml0eV90cmFucywgbWFpbiA9IHBhc3RlKCJCb3gtQ294IHRyYW5zZm9ybWVkLCBSXjI9Iiwgcm91bmQoc3VtbWFyeShsbTIpJHIuc3F1YXJlZCwgZGlnaXRzID0yKSksIHhsYWIgPSAiQXJ0aXN0X0ZvbGxvd2VyIiwgeWxhYiA9ICJBcnRpc3RfUG9wdWxhcml0eSIsIGNvbCA9IG15X2NvbG9ycywgcGNoID0gMTYpDQpsZWdlbmQoImJvdHRvbXJpZ2h0IiwgbGVnZW5kID0gbGV2ZWxzKGFzLmZhY3Rvcih4JEdlbnJlKSksIGNvbCA9IHBhbGV0dGUsIHBjaCA9IDE2KQ0KDQpsaWJyYXJ5KCJsYXR0aWNlIikNCnh5cGxvdChBcnRpc3RfUG9wdWxhcml0eV90cmFuc35BcnRpc3RfRm9sbG93ZXJfdHJhbnN8eCRHZW5yZSwgcGNoPTE5LCB4bGFiID0gJ3ZpZXdzQ291bnQgKHRyYW5zZm9ybWVkKScsIHlsYWIgPSAnQXJ0aXN0X1BvcHVsYXJpdHkgKHRyYW5zZm9ybWVkKScsIG1haW4gPSAiQmlwbG90IikNCg0KIyBwYWlycyhib3hjb3hfZGYpDQoNCnN1bmZsb3dlcl9BcnRpc3RfcG9wIDwtIDIqcm91bmQoYm94Y294X2RmJEFydGlzdF9Qb3B1bGFyaXR5LzIpDQpzdW5mbG93ZXJfdmlld3NDb3VudCAgPC0gMipyb3VuZChib3hjb3hfZGYkdmlld3NDb3VudC8yKQ0Kc3VuZmxvd2VycGxvdChzdW5mbG93ZXJfQXJ0aXN0X3BvcH5zdW5mbG93ZXJfdmlld3NDb3VudCwgeGxhYiA9ICd2aWV3c0NvdW50ICh0cmFuc2Zvcm1lZCknLCB5bGFiID0gJ0FydGlzdF9Qb3B1bGFyaXR5ICh0cmFuc2Zvcm1lZCknLCBtYWluID0gJ1N1bmZsb3dlciBwbG90JykNCg0KcmVxdWlyZSgiTUFTUyIpDQpwYXJjb29yZChib3hjb3hfZGYsIHZhci5sYWJlbCA9IEZBTFNFKQ0KDQoNCiNsaWJyYXJ5KCJsYXR0aWNlIikNCiNwYXJhbGxlbHBsb3QoYm94Y294X2RmLCBob3Jpem9udGFsLmF4aXM9VCkNCg0KI2xpYnJhcnkoImdncGxvdDIiKQ0KI2xpYnJhcnkoIkdHYWxseSIpDQojZ2dwYXJjb29yZChib3hjb3hfZGYpICsgZ2VvbV9saW5lKCkNCg0KbGlicmFyeSgiYW5kcmV3cyIpDQphbmRyZXdzKGJveGNveF9kZiwgeW1heD03KQ0KDQphdWRpb19mZWF0dXJlcyA8LSBjKCdhY291c3RpY25lc3MnICwgJ2RhbmNlYWJpbGl0eScgLCdlbmVyZ3knLCAnaW5zdHJ1bWVudGFsbmVzcycsICdsaXZlbmVzcycsICdsb3VkbmVzcycsICdzcGVlY2hpbmVzcycsICd0ZW1wbycsICd2YWxlbmNlJykNCg0Kc2VsZWN0ZWRfY29sdW1ucyA8LSBkcGx5cjo6c2VsZWN0KGJveGNveF9kZiwgYXVkaW9fZmVhdHVyZXMpDQoNCnBhcmNvb3JkKHNlbGVjdGVkX2NvbHVtbnMgLCBjb2w9IG15X2NvbG9ycywgdmFyLmxhYmVsID0gRkFMU0UsIC4uLiA9IHBhcihsYXM9MiwgbWFyID0gYyg4LDEsMywxKSwgeHBkPVRSVUUpKQ0KIyB0aXRsZSgiUGFyYWxsZWwgY29vcmRpbmF0ZXMiLCBsaW5lID0gMC42LCBhZGogPSAwLjEpDQpsZWdlbmQoInRvcHJpZ2h0IiwgaW5zZXQ9YygwLjAzNSwtMC4wOCksbGVnZW5kID0gYygnQ2xhc3NpYycsICdUZWNobm8nLCAnUG9wJywgJ0hpcCBIb3AnKSwgY29sID0gdW5pcXVlKG15X2NvbG9ycyksDQogIGJ0eSA9ICdvJywgaG9yaXogPSBUUlVFLCBwY2ggPSAxNSkNCg0KYXggPC0gc2VsZWN0ZWRfY29sdW1ucw0KYXgkY2xyIDwtIHgkR2VucmUNCmFuZHJld3MoYXgsIHltYXg9NCwgY2xyPTEwKQ0KbGVnZW5kKCJ0b3ByaWdodCIsbGVnZW5kPWxldmVscyhheCRjbHIpLCBmaWxsPXJhaW5ib3cobGVuZ3RoKGxldmVscyhhcy5mYWN0b3IoYXgkY2xyKSkpKSkNCg0KbGlicmFyeSgic2NhZ25vc3RpY3MiKQ0KDQpzdWJfZGYgPC0gYXMuZGF0YS5mcmFtZSh4WywgdmFyX2xpc3RdKQ0KDQpzIDwtIHNjYWdub3N0aWNzKHN1Yl9kZikNCm8gPC0gc2NhZ25vc3RpY3NPdXRsaWVycyhzKQ0KZyA8LSBzY2Fnbm9zdGljc0dyaWQocykNCg0KZ28gPC0gZ1tvLF0NCg0KIyBPdXRsaWVyczogc3Ryb25nbHkgZGlmZmVyZW50IGZyb20gdGhlIG90aGVyIHBsb3RzDQoNCnBhcihtZnJvdz1jKDEsMSkpDQoNCmZvciAoaSBpbiAxOm5yb3coZ28pKXsNCnBsb3Qoc3ViX2RmW1tnb1tpLDFdXV0sIHN1Yl9kZltbZ29baSwyXV1dLCBwY2g9MTksIHhsYWI9bmFtZXMoc3ViX2RmKVtnb1tpLDFdXSwgeWxhYj1uYW1lcyhzdWJfZGYpW2dvW2ksMl1dKQ0KcHJpbnQocGFzdGUoIk91dGx5aW5nIHBhaXI6ICIsICJ5PSIsIG5hbWVzKHN1Yl9kZilbZ29baSwyXV0sICJ4PSIsIG5hbWVzKHN1Yl9kZilbZ29baSwxXV0pKX0NCg0KIyBIaWdobGlnaHRzOg0KDQpwYXIobWZyb3c9YygxLDIpKQ0KcGxvdCh4JFRyYWNrX0R1cmF0aW9uX21zLCB4JGluc3RydW1lbnRhbG5lc3MsIGNvbCA9IG15X2NvbG9ycywgcGNoID0gMTYsIHhsYWIgPSAnVHJhY2tfRHVyYXRpb25fbXMnLCB5bGFiID0gJ2luc3RydW1lbnRhbG5lc3MnKQ0KcGxvdCh4JGluc3RydW1lbnRhbG5lc3MsIHgkdmFsZW5jZSwgY29sID0gbXlfY29sb3JzLCBwY2ggPSAxNiwgIHhsYWIgPSAnaW5zdHJ1bWVudGFsbmVzcycsIHlsYWIgPSAndmFsZW5jZScpDQpsZWdlbmQoeD0tMTMwMCwgeT0xMjUwLCBsZWdlbmQgPSBsZXZlbHMoYXMuZmFjdG9yKHgkR2VucmUpKSwgY29sID0gcGFsZXR0ZSwgcGNoID0gMTYsIGhvcml6ID0gVFJVRSwgeHBkID0gTkEpDQoNCg0KDQojIEV4ZW1wbGFyczogZ3JvdXAgb2Ygc2ltaWxhciBwbG90cw0KDQplIDwtIHNjYWdub3N0aWNzRXhlbXBsYXJzKHMpDQoNCmdlIDwtIGdbZSxdDQoNCnBhcihtZnJvdyA9IGMoMiwyKSkNCg0KZm9yIChpIGluIDE6ZGltKGdlKVsxXSl7DQogIHBsb3Qoc3ViX2RmW1tnZSR4W2ldXV0sIHN1Yl9kZltbZ2UkeVtpXV1dLCBwY2g9MTksDQogIHhsYWI9bmFtZXMoc3ViX2RmKVtnZSR4W2ldXSwgeWxhYj1uYW1lcyhzdWJfZGYpW2dlJHlbaV1dKQ0KICBwcmludChwYXN0ZSgiRXhlbXBsYXJ5IHBhaXI6ICIsICJ5PSIsIG5hbWVzKHN1Yl9kZilbZ2UkeVtpXV0sICJ4PSIsIG5hbWVzKHN1Yl9kZilbZ2UkeFtpXV0pKQ0KfQ0KDQpgYGANCg0KYGBge3J9DQoNCnRhYmxlKHgkR2VucmUpDQoNCngkQXJ0aXN0X1BvcHVsYXJpdHlfcXVhbnRpbGUgPC0gMA0KDQpBcnRpc3RfUG9wdWxhcml0eV9xdWFudGlsZXMgPC0gcXVhbnRpbGUoeCRBcnRpc3RfUG9wdWxhcml0eSwgcHJvYnMgPSBjKDAuMjUsIDAuNSwgMC43NSkpDQoNCkFydGlzdF9Qb3BfcV8yNSA8LSBBcnRpc3RfUG9wdWxhcml0eV9xdWFudGlsZXNbMV0NCkFydGlzdF9Qb3BfbWVkaWFuIDwtIEFydGlzdF9Qb3B1bGFyaXR5X3F1YW50aWxlc1syXQ0KQXJ0aXN0X1BvcF9xXzc1IDwtIEFydGlzdF9Qb3B1bGFyaXR5X3F1YW50aWxlc1szXQ0KDQp4JEFydGlzdF9Qb3B1bGFyaXR5X3F1YW50aWxlIDwtIGlmZWxzZSh4JEFydGlzdF9Qb3B1bGFyaXR5IDwgQXJ0aXN0X1BvcF9xXzI1LCAxLCB4JEFydGlzdF9Qb3B1bGFyaXR5X3F1YW50aWxlICsgMCkNCg0KeCRBcnRpc3RfUG9wdWxhcml0eV9xdWFudGlsZSA8LSBpZmVsc2UoKCh4JEFydGlzdF9Qb3B1bGFyaXR5ID49IEFydGlzdF9Qb3BfcV8yNSkgJiAoeCRBcnRpc3RfUG9wdWxhcml0eSA8IEFydGlzdF9Qb3BfbWVkaWFuKSksIDIsIHgkQXJ0aXN0X1BvcHVsYXJpdHlfcXVhbnRpbGUgKyAwKQ0KDQp4JEFydGlzdF9Qb3B1bGFyaXR5X3F1YW50aWxlIDwtIGlmZWxzZSgoKHgkQXJ0aXN0X1BvcHVsYXJpdHkgPj0gQXJ0aXN0X1BvcF9tZWRpYW4pICYgKHgkQXJ0aXN0X1BvcHVsYXJpdHkgPCBBcnRpc3RfUG9wX3FfNzUpKSwgMywgeCRBcnRpc3RfUG9wdWxhcml0eV9xdWFudGlsZSArIDApDQoNCngkQXJ0aXN0X1BvcHVsYXJpdHlfcXVhbnRpbGUgPC0gaWZlbHNlKCh4JEFydGlzdF9Qb3B1bGFyaXR5ID49IEFydGlzdF9Qb3BfcV83NSksIDQsIHgkQXJ0aXN0X1BvcHVsYXJpdHlfcXVhbnRpbGUgKyAwKQ0KeCRBcnRpc3RfUG9wdWxhcml0eV9xdWFudGlsZSA8LSBhcy5mYWN0b3IoeCRBcnRpc3RfUG9wdWxhcml0eV9xdWFudGlsZSkNCg0KdGFiPC10YWJsZSh4JEdlbnJlLCB4JEFydGlzdF9Qb3B1bGFyaXR5X3F1YW50aWxlKQ0KdGFiDQoNCiMgciA9IDQsIGMgPSA0DQojZGYgPSAoci0xKSooYy0xKSA9IDMrMyA9IDkNCiMgY3JpdGljYWwgdmFsdWU6IDE2LjkxOQ0KDQoNCmNoaXNxLnRlc3QodGFiKQ0KY2hpc3EudGVzdCh0YWIsIHNpbXVsYXRlLnAudmFsdWUgPSBUUlVFKQ0KDQoNCg0KbGlicmFyeSgidmNkIikNCmFzc29jc3RhdHModGFiKQ0KDQojbGlicmFyeSgib3BlbmludHJvIikNCg0KI2NvbnRUYWJsZSh0YWIpDQoNCmBgYA0KDQpgYGB7cn0NCg0KeCR2aWV3c0NvdW50X3F1YW50aWxlIDwtIDANCg0Kdmlld3NDb3VudF9xdWFudGlsZXMgPC0gcXVhbnRpbGUoeCR2aWV3c0NvdW50LCBwcm9icyA9IGMoMC4yNSwgMC41LCAwLjc1KSkNCg0Kdmlld3NDb3VudF9xXzI1IDwtIHZpZXdzQ291bnRfcXVhbnRpbGVzWzFdDQp2aWV3c0NvdW50X21lZGlhbiA8LSB2aWV3c0NvdW50X3F1YW50aWxlc1syXQ0Kdmlld3NDb3VudF9xXzc1IDwtIHZpZXdzQ291bnRfcXVhbnRpbGVzWzNdDQoNCngkdmlld3NDb3VudF9xdWFudGlsZSA8LSBpZmVsc2UoeCR2aWV3c0NvdW50IDwgdmlld3NDb3VudF9xXzI1LCAxLCB4JHZpZXdzQ291bnRfcXVhbnRpbGUgKyAwKQ0KDQp4JHZpZXdzQ291bnRfcXVhbnRpbGUgPC0gaWZlbHNlKCgoeCR2aWV3c0NvdW50ID49IHZpZXdzQ291bnRfcV8yNSkgJiAoeCR2aWV3c0NvdW50IDwgdmlld3NDb3VudF9tZWRpYW4pKSwgMiwgeCR2aWV3c0NvdW50X3F1YW50aWxlICsgMCkNCg0KeCR2aWV3c0NvdW50X3F1YW50aWxlIDwtIGlmZWxzZSgoKHgkdmlld3NDb3VudCA+PSB2aWV3c0NvdW50X21lZGlhbikgJiAoeCR2aWV3c0NvdW50IDwgdmlld3NDb3VudF9xXzc1KSksIDMsIHgkdmlld3NDb3VudF9xdWFudGlsZSArIDApDQoNCngkdmlld3NDb3VudF9xdWFudGlsZSA8LSBpZmVsc2UoKHgkdmlld3NDb3VudCA+PSB2aWV3c0NvdW50X3FfNzUpLCA0LCB4JHZpZXdzQ291bnRfcXVhbnRpbGUgKyAwKQ0KeCR2aWV3c0NvdW50X3F1YW50aWxlIDwtIGFzLmZhY3Rvcih4JHZpZXdzQ291bnRfcXVhbnRpbGUpDQoNCg0KdGFiPC10YWJsZSh4JHZpZXdzQ291bnRfcXVhbnRpbGUsIHgkQXJ0aXN0X1BvcHVsYXJpdHlfcXVhbnRpbGUpDQp0YWINCg0KY2hpc3EudGVzdCh0YWIpDQpjaGlzcS50ZXN0KHRhYiwgc2ltdWxhdGUucC52YWx1ZSA9IFRSVUUpDQoNCmxpYnJhcnkoInZjZCIpDQphc3NvY3N0YXRzKHRhYikNCg0KI2NvbnRUYWJsZSh0YWIpDQoNCmBgYA0KDQpgYGB7cn0NCg0KYWIyIDwtIG5hLm9taXQoY2JpbmQoeCRBcnRpc3RfUG9wdWxhcml0eV9xdWFudGlsZSwgeCR2aWV3c0NvdW50X3F1YW50aWxlKSkNCm5yb3coYWIyKSoobnJvdyhhYjIpLTEpLzINCiMNCmluZCA8LSBvcmRlcihhYjJbLDFdLCBhYjJbLDJdKQ0KYWIyIDwtIGFiMltpbmQsXQ0KI2INCkMgPC0gRCA8LSBUeCA8LSBUeSA8LSBUeHkgPC0gMA0KZm9yIChpIGluIDE6KG5yb3coYWIyKS0xKSkgew0KICBpZiAoaSUlMTAwPT0wKSBjYXQoaSwgIlxuIikNCiAgZm9yKGogaW4gKGkrMSk6bnJvdyhhYjIpKSB7DQogICAgaWYgKGFiMltpLDFdPT1hYjJbaiwxXSkgew0KICAgICAgaWYgKGFiMltpLDJdPT1hYjJbaiwyXSkgew0KICAgICAgICBUeHkgPC0gVHh5KzENCiAgICAgIH0gZWxzZSB7DQogICAgICAgIFR4IDwtIFR4KyhhYjJbaSwyXTxhYjJbaiwyXSkNCiAgICAgIH0NCiAgICB9IGVsc2UgeyAgIA0KICAgICAgaWYgKGFiMltpLDJdPT1hYjJbaiwyXSkgVHkgPC0gVHkrMQ0KICAgICAgaWYgKGFiMltpLDJdPGFiMltqLDJdKSBDIDwtIEMrMQ0KICAgICAgaWYgKGFiMltpLDJdPmFiMltqLDJdKSBEIDwtIEQrMQ0KICAgIH0NCiAgfQ0KfQ0KDQpjKEM9QywgRD1ELCBUeD1UeCwgVHk9VHksIFR4eT1UeHkpDQoNCmtfdCA8LSAoQyAtIEQpLyhucm93KGFiMikqKG5yb3coYWIyKS0xKS8yKQ0Ka190ICMgKHdpdGhvdXQgdGllcykNCg0KbGlicmFyeSgicnlvdXJlYWR5IikNCm9yZC50YXUodGFibGUoYWIyWywxXSwgYWIyWywyXSkpDQoNCmNvcihhcy5udW1lcmljKHgkQXJ0aXN0X1BvcHVsYXJpdHlfcXVhbnRpbGUpLCBhcy5udW1lcmljKHgkdmlld3NDb3VudF9xdWFudGlsZSksIG1ldGhvZCA9ICJrZW5kYWxsIikgIyBpZGVudGljYWwgcmVzdWx0DQoNCmNvci50ZXN0KGFzLm51bWVyaWMoeCRBcnRpc3RfUG9wdWxhcml0eV9xdWFudGlsZSksIGFzLm51bWVyaWMoeCR2aWV3c0NvdW50X3F1YW50aWxlKSwgbWV0aG9kPSJrZW5kYWxsIikNCg0KDQoNCg0KDQoNCmBgYA0KDQpgYGB7cn0NCg0KbW9kZXRhYiA8LSBmdW5jdGlvbih4LCBtYXJnaW49MCkgeyANCiAgaWYgKG1hcmdpbj4wKSBhcHBseSh4LCBtYXJnaW4sIG1heCkgZWxzZSBtYXgoeCkgDQp9DQoNCnRhYjIgPC0gdGFibGUoeCRBcnRpc3RfUG9wdWxhcml0eV9xdWFudGlsZSwgDQogICAgICAgICAgICAgIHgkR2VucmUsIGRubiA9IGMoIkFydGlzdCBQb3B1bGFyaXR5IHF1YW50aWxlIiwgIkdlbnJlIikpDQoNCnRhYjINCg0KIyBjb250VGFibGUodGFiMikNCg0KDQp0YWIgPC0gcm93U3Vtcyh0YWIyKQ0KDQptZXNzYWdlKHBhc3RlKCJUaGUgbW9kZSBhY3Jvc3MgYWxsIGZvdXIgcXVhbnRpbGVzIG9mIHN0cmVhbSBkaXN0cmlidXRpb24gaXMiLCBtb2RldGFiKHRhYiksICIgYW5kIGlzIGluIHRoZSA0dGggcXVhbnRpbGUuIFNvIHRoZSBiZXN0IHByZWRpY3Rpb24gZm9yIGEgbmV3IGFydGlzdCB3aXRob3V0IGZ1cnRoZXIga25vd2xlZGdlIGlzIHRoYXQgc2hlIHdpbGwgZmFsbCBpbiB0aGUgdG9wIHF1YW50aWxlIG9mIHRoZSBwb3B1bGFyaXR5IGRpc3RyaWJ1dGlvbi4iKSkNCg0KZTFfc3RyZWFtcyA8LSBzdW0odGFiKSAtIG1vZGV0YWIodGFiKQ0KDQptZXNzYWdlKHBhc3RlKCJXaXRob3V0IGFueSBrbm93bGVkZ2UgYWJvdXQgYW4gYWRkaXRpb25hbCBmZWF0dXJlIG90aGVyIHRoYW4gYXJ0aXN0IHBvcHVsYXJpdHkgcXVhbnRpbGUgaXRzZWxmIGFuZCB1c2luZyB0aGUgbW9kZSBhY3Jvc3MgY2xhc3NlcyBhcyBiZXN0IHByZWRpY3RvciBmb3IgY2xhc3MgYXNzaWdubWVudCBmb3IgYSBuZXcgb2JzZXJ2YXRpb24sIHRoZSBudW1iZXIgb2YgZmFsc2VseSBwcmVkaWN0ZWQgY2FzZXMgd291bGQgYmU6ICIsIGUxX3N0cmVhbXMsICIgb3IgIiwgZTFfc3RyZWFtcy9zdW0odGFiKSoxMDAsICIlLiIpKQ0KDQplMl9zdHJlYW1zIDwtIHN1bSh0YWIyKSAtIHN1bShtb2RldGFiKHRhYjIsIDIpKQ0KDQptZXNzYWdlKHBhc3RlKCJOb3cgaGF2aW5nIGtub3dsZWRnZSBhYm91dCBhbiBhc3NvY2lhdGlvbiBiZXR3ZWVuIGFydGlzdCBwb3B1bGFyaXR5IGFuZCBpdHMgZ2VucmUgYW5kIHVzaW5nIGNsYXNzLWludGVybmFsIG1vZGVzIGZvciB0aGUgYWRkaXRpb25hbCBmZWF0dXJlLCB0aGUgbnVtYmVyIG9mIGZhbHNlbHkgcHJlZGljdGVkIGNhc2VzIHdvdWxkIGJlOiAiLCBlMl9zdHJlYW1zLCAiIG9yICIsIGUyX3N0cmVhbXMvc3VtKHRhYikqMTAwLCAiJSwgd2hpY2ggaXMgYWxyZWFkeSBtdWNoIGxvd2VyIGNvbXBhcmVkIHRvIHRoZSBlcnJvciByYXRlIGZyb20gcHJlZGljdGluZyB3aXRob3V0IGFueSBrbm93bGVkZ2UgYWJvdXQgYW4gYXNzb2NpYXRpb24uIE9uIHRoZSBvdGhlciBoYW5kLCAiLCBzdW0odGFiMiktZTJfc3RyZWFtcywgIiBvciAiLCAoc3VtKHRhYjIpLWUyX3N0cmVhbXMpL3N1bSh0YWIyKSoxMDAsICIlIHdvdWxkIGhhdmUgYmVlbiBwcmVkaWN0ZWQgY29ycmVjdGx5IChjb21wYXJlZCB0byAiLCAoMS1lMV9zdHJlYW1zL3N1bSh0YWIpKSoxMDAsICIlIGlmIHRoZXJlIHdhcyBubyBrbm93bGVkZ2UgYWJvdXQgZ2VucmUuKSIpKQ0KDQoNCmxhbWJkYSA8LSAoZTFfc3RyZWFtcyAtIGUyX3N0cmVhbXMpL2UxX3N0cmVhbXMNCg0KbWVzc2FnZShwYXN0ZSgiVGhlcmVmb3JlLCBHb29kbWFubiBhbmQgS3J1c2thbHMgTGFtYmRhIGlzOiAiLCBsYW1iZGEpKQ0KDQpsaWJyYXJ5KCJyeW91cmVhZHkiKQ0KDQpub20ubGFtYmRhKHRhYjIpDQoNCg0KYGBgDQoNCg0KDQpgYGB7cn0NCg0KDQptb2RldGFiIDwtIGZ1bmN0aW9uKHgsIG1hcmdpbj0wKSB7IA0KICBpZiAobWFyZ2luPjApIGFwcGx5KHgsIG1hcmdpbiwgbWF4KSBlbHNlIG1heCh4KSANCn0NCg0KdGFiMiA8LSB0YWJsZSh4JEFydGlzdF9Qb3B1bGFyaXR5X3F1YW50aWxlLCANCiAgICAgICAgICAgICAgeCR2aWV3c0NvdW50X3F1YW50aWxlLCBkbm4gPSBjKCJBcnRpc3QgUG9wdWxhcml0eSBxdWFudGlsZSIsICJ2aWV3c0NvdW50IHF1YW50aWxlIikpDQoNCnRhYjINCg0KdGFiIDwtIHJvd1N1bXModGFiMikNCg0KbWVzc2FnZShwYXN0ZSgiVGhlIG1vZGUgYWNyb3NzIGFsbCBmb3VyIHF1YW50aWxlcyBvZiBzdHJlYW0gZGlzdHJpYnV0aW9uIGlzIiwgbW9kZXRhYih0YWIpLCAiIGFuZCBpcyBpbiB0aGUgNHRoIHF1YW50aWxlLiBTbyB0aGUgYmVzdCBwcmVkaWN0aW9uIGZvciBhIG5ldyBhcnRpc3Qgd2l0aG91dCBmdXJ0aGVyIGtub3dsZWRnZSBpcyB0aGF0IHNoZSB3aWxsIGZhbGwgaW4gdGhlIHRvcCBxdWFudGlsZSBvZiB0aGUgcG9wdWxhcml0eSBkaXN0cmlidXRpb24uIikpDQoNCmUxX3N0cmVhbXMgPC0gc3VtKHRhYikgLSBtb2RldGFiKHRhYikNCg0KbWVzc2FnZShwYXN0ZSgiV2l0aG91dCBhbnkga25vd2xlZGdlIGFib3V0IGFuIGFkZGl0aW9uYWwgZmVhdHVyZSBvdGhlciB0aGFuIGFydGlzdCBwb3B1bGFyaXR5IHF1YW50aWxlIGl0c2VsZiBhbmQgdXNpbmcgdGhlIG1vZGUgYWNyb3NzIGNsYXNzZXMgYXMgYmVzdCBwcmVkaWN0b3IgZm9yIGNsYXNzIGFzc2lnbm1lbnQgZm9yIGEgbmV3IG9ic2VydmF0aW9uLCB0aGUgbnVtYmVyIG9mIGZhbHNlbHkgcHJlZGljdGVkIGNhc2VzIHdvdWxkIGJlOiAiLCBlMV9zdHJlYW1zLCAiIG9yICIsIGUxX3N0cmVhbXMvc3VtKHRhYikqMTAwLCAiJS4iKSkNCg0KZTJfc3RyZWFtcyA8LSBzdW0odGFiMikgLSBzdW0obW9kZXRhYih0YWIyLCAyKSkNCg0KbWVzc2FnZShwYXN0ZSgiTm93IGhhdmluZyBrbm93bGVkZ2UgYWJvdXQgYW4gYXNzb2NpYXRpb24gYmV0d2VlbiBhcnRpc3QgcG9wdWxhcml0eSBhbmQgaXRzIHZpZXdzQ291bnQgcXVhbnRpbGUgYW5kIHVzaW5nIGNsYXNzLWludGVybmFsIG1vZGVzIGZvciB0aGUgYWRkaXRpb25hbCBmZWF0dXJlLCB0aGUgbnVtYmVyIG9mIGZhbHNlbHkgcHJlZGljdGVkIGNhc2VzIHdvdWxkIGJlOiAiLCBlMl9zdHJlYW1zLCAiIG9yICIsIGUyX3N0cmVhbXMvc3VtKHRhYikqMTAwLCAiJSwgd2hpY2ggaXMgYWxyZWFkeSBtdWNoIGxvd2VyIGNvbXBhcmVkIHRvIHRoZSBlcnJvciByYXRlIGZyb20gcHJlZGljdGluZyB3aXRob3V0IGFueSBrbm93bGVkZ2UgYWJvdXQgYW4gYXNzb2NpYXRpb24uIE9uIHRoZSBvdGhlciBoYW5kLCAiLCBzdW0odGFiMiktZTJfc3RyZWFtcywgIiBvciAiLCAoc3VtKHRhYjIpLWUyX3N0cmVhbXMpL3N1bSh0YWIyKSoxMDAsICIlIHdvdWxkIGhhdmUgYmVlbiBwcmVkaWN0ZWQgY29ycmVjdGx5IChjb21wYXJlZCB0byAiLCAoMS1lMV9zdHJlYW1zL3N1bSh0YWIpKSoxMDAsICIlIGlmIHRoZXJlIHdhcyBubyBrbm93bGVkZ2UgYWJvdXQgdmlld3NDb3VudCBxdWFudGlsZS4pIikpDQoNCg0KbGFtYmRhIDwtIChlMV9zdHJlYW1zIC0gZTJfc3RyZWFtcykvZTFfc3RyZWFtcw0KDQptZXNzYWdlKHBhc3RlKCJUaGVyZWZvcmUsIEdvb2RtYW5uIGFuZCBLcnVza2FscyBMYW1iZGEgaXM6ICIsIGxhbWJkYSkpDQoNCmxpYnJhcnkoInJ5b3VyZWFkeSIpDQoNCm5vbS5sYW1iZGEodGFiMikNCg0KDQpgYGANCg0KQU5PVkENCg0KYGBge3J9DQoNCmZpdCA8LSBhb3YoQXJ0aXN0X1BvcHVsYXJpdHl+R2VucmUgLCBkYXRhID14KQ0Kc3VtbWFyeShmaXQpDQoNCg0Ka3MudGVzdCh4JEFydGlzdF9Qb3B1bGFyaXR5W3gkR2VucmUgPT0gJ0NsYXNzaWMnXSwgInBub3JtIiwgbWVhbiA9IG1lYW4gKHgkQXJ0aXN0X1BvcHVsYXJpdHlbeCRHZW5yZSA9PSAnQ2xhc3NpYyddKSAsIHNkPXNkKHgkQXJ0aXN0X1BvcHVsYXJpdHlbeCRHZW5yZSA9PSAnQ2xhc3NpYyddKSkkc3RhdGlzdGljDQoxLjM1ODEgLyBzcXJ0IChsZW5ndGgoeCRBcnRpc3RfUG9wdWxhcml0eVt4JEdlbnJlID09ICdDbGFzc2ljJ10pKQ0KDQprcy50ZXN0KHgkQXJ0aXN0X1BvcHVsYXJpdHlbeCRHZW5yZSA9PSAnVGVjaG5vJ10sICJwbm9ybSIsIG1lYW4gPSBtZWFuICh4JEFydGlzdF9Qb3B1bGFyaXR5W3gkR2VucmUgPT0gJ1RlY2hubyddKSAsIHNkPXNkKHgkQXJ0aXN0X1BvcHVsYXJpdHlbeCRHZW5yZSA9PSAnVGVjaG5vJ10pKSRzdGF0aXN0aWMNCjEuMzU4MSAvIHNxcnQgKGxlbmd0aCh4JEFydGlzdF9Qb3B1bGFyaXR5W3gkR2VucmUgPT0gJ1RlY2hubyddKSkNCg0Ka3MudGVzdCh4JEFydGlzdF9Qb3B1bGFyaXR5W3gkR2VucmUgPT0gJ0hpcCBIb3AnXSwgInBub3JtIiwgbWVhbiA9IG1lYW4gKHgkQXJ0aXN0X1BvcHVsYXJpdHlbeCRHZW5yZSA9PSAnSGlwIEhvcCddKSAsIHNkPXNkKHgkQXJ0aXN0X1BvcHVsYXJpdHlbeCRHZW5yZSA9PSAnSGlwIEhvcCddKSkkc3RhdGlzdGljDQoxLjM1ODEgLyBzcXJ0IChsZW5ndGgoeCRBcnRpc3RfUG9wdWxhcml0eVt4JEdlbnJlID09ICdIaXAgSG9wJ10pKQ0KDQprcy50ZXN0KHgkQXJ0aXN0X1BvcHVsYXJpdHlbeCRHZW5yZSA9PSAnUG9wJ10sICJwbm9ybSIsIG1lYW4gPSBtZWFuICh4JEFydGlzdF9Qb3B1bGFyaXR5W3gkR2VucmUgPT0gJ1BvcCddKSAsIHNkPXNkKHgkQXJ0aXN0X1BvcHVsYXJpdHlbeCRHZW5yZSA9PSAnUG9wJ10pKSRzdGF0aXN0aWMNCjEuMzU4MSAvIHNxcnQgKGxlbmd0aCh4JEFydGlzdF9Qb3B1bGFyaXR5W3gkR2VucmUgPT0gJ1BvcCddKSkNCg0KbGV2ZW5lVGVzdCAoQXJ0aXN0X1BvcHVsYXJpdHl+R2VucmUsIGRhdGEgPXgpDQp2YXJxIDwtIHRhcHBseSh4JEFydGlzdF9Qb3B1bGFyaXR5LCB4JEdlbnJlLCB2YXIsIG5hLnJtPSBUUlVFKQ0KdmFycS9taW4odmFycSkNCg0KDQpgYGANCg0KUENBDQoNCmBgYHtyfQ0KDQphdWRpb19mZWF0dXJlcyA8LSBjKCdhY291c3RpY25lc3MnICwgJ2RhbmNlYWJpbGl0eScgLCdlbmVyZ3knLCAnaW5zdHJ1bWVudGFsbmVzcycsICdsaXZlbmVzcycsICdsb3VkbmVzcycsICdzcGVlY2hpbmVzcycsICd0ZW1wbycsICd2YWxlbmNlJykNCnhfZmVhdHVyZXMgPC0gZHBseXI6OnNlbGVjdCh4LCBhdWRpb19mZWF0dXJlcykNCnogPC0gc2NhbGUoeF9mZWF0dXJlcykNCmVpZ2VuX2NvciA8LSBlaWdlbihjb3YoeikpDQpFIDwtIGVpZ2VuX2NvciR2ZWN0b3JzDQpwY19jb3IgPC0gcHJjb21wKHosIGNlbnRlciA9IEYsIHNjYWxlID0gRikNCnBsb3QocGNfY29yLCBtYWluPSJTY3JlZSBwbG90IGFzIGJhciBjaGFydCAoY29yKSIpDQpwbG90KHBjX2NvciRzZGV2XjIsIHR5cGU9ImIiLCBtYWluPSJTY3JlZSBwbG90IChjb3IpIikNCg0KcGNfaW5kZXggPC0gYygiUEMxIiwgIlBDMiIsICJQQzMiLCAiUEM0IiwgIlBDNSIsICJQQzYiLCAiUEM3IiwgIlBDOCIsICJQQzkiKQ0KZWlnZW5fdmFsdWVzIDwtIHJvdW5kKGVpZ2VuX2NvciR2YWx1ZXMsIGRpZ2l0cyA9IDIpDQp2YXJpYW5jZV9leHBsYWluZWQgPC0gcm91bmQoZWlnZW5fY29yJHZhbHVlcy9sZW5ndGgoYXVkaW9fZmVhdHVyZXMpLCBkaWdpdHMgPSAyKQ0KY3VtX3ZhcmlhbmNlIDwtIHJvdW5kKGN1bXN1bShlaWdlbl9jb3IkdmFsdWVzKS9sZW5ndGgoYXVkaW9fZmVhdHVyZXMpLCBkaWdpdHMgPSAyKQ0KZWlnZW5fZGYgPC0gZGF0YS5mcmFtZShwY19pbmRleCwgZWlnZW5fdmFsdWVzLCB2YXJpYW5jZV9leHBsYWluZWQsIGN1bV92YXJpYW5jZSkNCmNvbG5hbWVzKGVpZ2VuX2RmKSA8LSBjKCJQQyIsICJFaWdlbiB2YWx1ZSIsICJWYXJpYW5jZSBleHBsYWluZWQiLCAiQ3VtdWxhdGl2ZSB2YXJpYW5jZSBleHBsYWluZWQiKQ0KDQoNCg0KDQphcy5kYXRhLmZyYW1lKEUpDQpFWywxXSA8LSBFWywxXSotMQ0KRVssM10gPC0gRVssM10qLTENCkVbLDRdIDwtIEVbLDRdKi0xDQpFWyw1XSA8LSBFWyw1XSotMQ0KRVssNl0gPC0gRVssNl0qLTENCmFzLmRhdGEuZnJhbWUoRSkNCg0Kc2NvcmVzIDwtIHogJSolIEUNCg0KbGlicmFyeShjb3JycGxvdCkNCg0KY29sIDwtIGNvbG9yUmFtcFBhbGV0dGUoYygiI0JCNDQ0NCIsICIjRUU5OTg4IiwgIiNGRkZGRkYiLCAiIzc3QUFERCIsICIjNDQ3N0FBIikpDQoNCiNwYXIobWZyb3c9YygxLDEpKQ0KI3Bsb3QoKHBjX2NvciRzZGV2XjIpL3JvdW5kKHN1bShwY19jb3Ikc2Rldl4yKSkqMTAwLCB0eXBlPSJiIiwgeGxhYiA9ICJOdW1iZXIgb2YgY29tcG9uZW50cyIsIHlsYWIgPSAiUGVyY2VudGFnZSBvZiB2YXJpYW5jZSBleHBsYWluZWQiKQ0KDQpjb3JycGxvdChwY19jb3Ikcm90YXRpb24sIG1ldGhvZD0iY29sb3IiLCBjb2w9Y29sKDIwMCksIA0KICAgICAgICBhZGRDb2VmLmNvbCA9ICJibGFjayIsICMgQWRkIGNvZWZmaWNpZW50IG9mIGNvcnJlbGF0aW9uDQogICAgICAgIHRsLmNvbD0iYmxhY2siLCB0bC5zcnQ9OTAsICNUZXh0IGxhYmVsIGNvbG9yIGFuZCByb3RhdGlvbg0KICAgICAgICAgZGlhZz1UUlVFLCBtYXIgPSBjKDAsMCw1LDUpLCB0aXRsZSA9ICdQQ0EgLSBsb2FkaW5ncyBtYXRyaXgnKQ0KDQoNCg0KDQpwbG90KHNjb3Jlc1ssMV0sIHNjb3Jlc1ssMl0pDQpwbG90KHBjX2NvciR4WywxXSwgcGNfY29yJHhbLDJdKSAjIGJvdGggcGxvdHMgYXJlIGlkZW50aWNhbA0KDQpsaWJyYXJ5KCJwc3ljaCIpDQpwc3ljaF9wY2EgPC0gcHN5Y2g6OnByaW5jaXBhbCh6LCBuZmFjdG9ycyA9IDksIHJvdGF0ZSA9ICJub25lIikNCnNjcmVlKHopDQoNCiMgcHN5Y2hfcGNhJHNjb3Jlc1ssMV0qc3FydChwc3ljaF9wY2EkdmFsdWVzWzFdKSAjID09IHNjb3Jlc1ssMV0NCg0KIyB0cmFuc2Zvcm0gbG9hZGluZ3Mgb2YgcHN5Y2g6OnByaW5jaXBhbCB0byB1bml0LWxlbmd0aCBlaWdlbnZlY3RvcnMgYWNjb3JkaW5nIHRvIDEvc3FydChhXjIgKyBiXjIpICogKGEsYikNCiMgMS9zcXJ0KHN1bShwc3ljaF9wY2EkbG9hZGluZ3NbLDFdXjIpKSpwc3ljaF9wY2EkbG9hZGluZ3NbLDFdICMgaXMgaWRlbnRpY2FsIHRvIHBjX2NvciRyb3RhdGlvblssMV0gYW5kIEVbLDFdDQoNCiMgdHJhbnNmb3JtaW5nIHVuaXQtbGVuZ3RoIGVpZ2VudmVjdG9ycyB0byBsb2FkaW5ncyBsaWtlIGluIHBzeWNoOjpwcmluY2lwYWw6DQoNCm1hbnVhbF9sb2FkaW5ncyA8LSBtYXRyaXgobnJvdyA9IGRpbSh6KVsyXSwgbmNvbCA9IGRpbSh6KVsyXSkNCg0KZm9yIChpIGluIDE6ZGltKHopWzJdKXsNCiAgDQogIG1hbnVhbF9sb2FkaW5nc1ssaV0gPC0gRVssaV0gKiBzcXJ0KGVpZ2VuX2NvciR2YWx1ZXNbaV0pDQogIA0KfQ0KDQojIFN0aWxsIHNpZ25zIGFyZSBpbnRlcmNoYW5nZWQsIGFmZmVjdGVkIGNvbXBvbmVudHMgYXJlIDMgYW5kIDksIGNoYW5nZSBzaWducyBtYW51YWxseQ0KDQptYW51YWxfbG9hZGluZ3NbLDNdIDwtIG1hbnVhbF9sb2FkaW5nc1ssM10qLTENCm1hbnVhbF9sb2FkaW5nc1ssOV0gPC0gbWFudWFsX2xvYWRpbmdzWyw5XSotMQ0KDQojIFNjb3JlcyBpbiBwc3ljaCBhcmUgY29tcHV0ZWQgYWNjb3JkaW5nIHRvIA0KDQooeiAlKiUgRVssMV0pIC8gc3FydChlaWdlbl9jb3IkdmFsdWVzWzFdKSAjICA9PSBwc3ljaF9wY2Ekc2NvcmVzWywxXQ0KDQojIGkuZS4gdXNpbmcgdGhlIHVuaXQtbGVuZ3RoIGVpZ2VudmVjdG9ycyBhbmQgZGl2aWRpbmcgdGhlbSBieSB0aGUgc3F1YXJlIHJvb3Qgb2YgdGhlIGNvcnJlc3BvbmRpbmcgZWlnZW52YWx1ZQ0KDQpwYXIobWZyb3c9YygxLDIpKQ0KYmlwbG90KHBjX2NvcikgIyBsb2FkaW5ncyBwbG90IHdpdGggbG9hZGluZ3Mgc2NhbGVkIHRvIGZpdCBpbiBzYW1lIGdyYXBoIHdpdGggc2NvcmVzDQpwbG90KHBjX2NvciRyb3RhdGlvblssIDE6Ml0pICMgbG9hZGluZ3MgaW4gb3JpZ2luYWwgbWVhc3VyZXMNCnRleHQocGNfY29yJHJvdGF0aW9uWywgMToyXSwgYXMuY2hhcmFjdGVyKGNvbG5hbWVzKHopKSwgcG9zID0gMSwgY2V4ID0gMC43LCBvZmZzZXQgPSAwLjEpDQoNCiNwYWxldHRlIDwtIHJhaW5ib3cobGVuZ3RoKGxldmVscyhhcy5mYWN0b3IoeCRHZW5yZSkpKSkNCiNteV9jb2xvcnMgPC0gcGFsZXR0ZVthcy5udW1lcmljKHgkR2VucmUpXQ0KDQpwYWxldHRlIDwtIGMoJ3JlZCcsICdibHVlJywgJ2JsYWNrJywgJ3B1cnBsZScpDQojIHBhbGV0dGUgPC0gcmFpbmJvdyhsZW5ndGgobGV2ZWxzKGFzLmZhY3Rvcih4JEdlbnJlKSkpKQ0KbXlfY29sb3JzIDwtIHBhbGV0dGVbYXMubnVtZXJpYyh4JEdlbnJlKV0NCg0KcGFyKG1mcm93PWMoMSwxKSkNCnBsb3Qoc2NvcmVzWywgMToyXSwgY29sID0gbXlfY29sb3JzLCBwY2ggPSAxOSwgeGxhYiA9ICJTY29yZXMgUEMxICg0Ny44NCUgZXhwbC4gdmFyaWFuY2UpIiwgeWxhYiA9ICJTY29yZXMgUEMyICgxNi4yNSUgZXhwbC4gdmFyaWFuY2UpIikNCmxlZ2VuZCgidG9wbGVmdCIsIGxlZ2VuZCA9IGxldmVscyhhcy5mYWN0b3IoeCRHZW5yZSkpLCBjb2wgPSBwYWxldHRlLCBwY2ggPSAxNiwgaG9yaXogPSBUKQ0KI3Bsb3QocGNfY29yJHJvdGF0aW9uWywgMToyXSkNCnBhcihtZnJvdz1jKDEsMSkpDQpwbG90KEVbLCAxOjJdKQ0KdGV4dChFWywgMToyXSwgYXMuY2hhcmFjdGVyKGNvbG5hbWVzKHopKSwgcG9zID0gMSwgY2V4ID0gMC43LCBvZmZzZXQgPSAwLjEpDQoNCmN1bXN1bShlaWdlbl9jb3IkdmFsdWVzKS9sZW5ndGgoYXVkaW9fZmVhdHVyZXMpDQojIEFjY29yZGluZyB0byB0aGUgOTAlIGNyaXRlcmlvbiB0aGVyZSB3b3VsZCBiZSA1IGNvbXBvbmVudHMgbmVlZGVkIHRvIGFwcHJvcHJpYXRlbHkgcmV0YWluIHRoZSB2YXJpYW5jZSBpbiB0aGUgZGF0YSBieSByZWR1Y2luZyB0aGUgZmVhdHVyZSBzcGFjZSBmcm9tIG5pbmUgdG8gZml2ZS4NCg0KUk1TRSA9IGZ1bmN0aW9uKG0sIG8pew0KICANCiAgc3FydChtZWFuKChtIC0gbyleMikpDQogIA0KfQ0KDQojIEFsbCBleHByZXNzaW9ucyBmb3IgdGhlIHNjb3JlcyBhcmUgaWRlbnRpY2FsDQoNCnNjb3JlcyA8LSB6ICUqJSBFDQpzY29yZXNfZnJvbV9wc3ljaCA8LSAoeiAlKiUgKDEvc3FydChzdW0ocHN5Y2hfcGNhJGxvYWRpbmdzWywxXV4yKSkqcHN5Y2hfcGNhJGxvYWRpbmdzWywxXSkpIC8gc3FydChwc3ljaF9wY2EkdmFsdWVzWzFdKSAjID09IHBzeWNoX3BjYSRzY29yZXNbLDFdDQpzY29yZXNfZnJvbV9wcmluY29tcCA8LSAoeiAlKiUgcGNfY29yJHJvdGF0aW9uWywxXSkgLyBzcXJ0KGVpZ2VuX2NvciR2YWx1ZXNbMV0pDQoNCmZpdHMgPC0gbWF0cml4KG5yb3cgPSBkaW0oeilbMl0sIG5jb2wgPSAzKQ0KDQpmb3IgKGkgaW4gMTpkaW0oeilbMl0pew0KICANCiAgbnVtX2NvbXAgPC0gaQ0KICBmaXQgPC0gc2NvcmVzWywgMTpudW1fY29tcF0gJSolIHQoRVssIDE6bnVtX2NvbXBdKQ0KICByZXNpZHVhbHMgPC0geiAtIGZpdA0KICBybXNlIDwtIFJNU0UoeiwgZml0KQ0KICByMiA8LSAxLShzdW0oZGlhZyh2YXIocmVzaWR1YWxzKSkpLyBzdW0oZGlhZyh2YXIoeikpKSkNCiAgDQogIGZpdHNbaSwxXSA8LSBudW1fY29tcA0KICBmaXRzW2ksMl0gPC0gcm1zZQ0KICBmaXRzW2ksM10gPC0gcjINCiAgDQp9DQoNCnBhcihtZnJvdz1jKDEsMSkpDQpwbG90KGZpdHNbLDFdLCBmaXRzWywyXSwgdHlwZSA9ICJsIiwgeWxhYiA9ICdSTVNFJywgeGxhYiA9ICdOdW1iZXIgb2YgY29tcG9uZW50cycpDQpwYXIobmV3ID0gVFJVRSkNCnBsb3QoZml0c1ssMV0sIGZpdHNbLDNdLCB0eXBlID0gImwiLCBheGVzID0gRkFMU0UsIGJ0eSA9ICJuIiwgeGxhYiA9ICIiLCB5bGFiID0gIiIpDQpheGlzKHNpZGU9NCwgYXQgPSBwcmV0dHkocmFuZ2UoZml0c1ssM10pKSkNCm10ZXh0KCJSXjIiLCBzaWRlID0gNCwgbGluZSA9IC0xKQ0KYWJsaW5lKHYgPSAyLCBsdHkgPSAyLCBjb2wgPSAnZ3JlZW4nKQ0KbXRleHQoIkthaXNlciBjcml0ZXJpb24iLCBzaWRlID0gMSwgbGluZSA9IDIsIGFkaiA9IDAuMSkNCmFibGluZSh2PW1pbih3aGljaChmaXRzWywzXT49MC45KSksIGx0eSA9IDIsIGNvbCA9ICdyZWQnKQ0KbXRleHQoIjkwJSBydWxlIiwgc2lkZSA9IDEsIGxpbmUgPSAxLCBhZGogPSAwLjUpDQoNCiMgU3F1YXJlZCBwcmVkaWN0aW9uIGVycm9ycyAvIFEtcmVzaWR1YWxzDQoNCnBhcihtZnJvdz1jKDEsMSkpDQoNCmEgPC0gMQ0KWGhhdCA8LSBwY19jb3IkeFssc2VxKDEsYSldICUqJSB0KHBjX2NvciRyb3RhdGlvblssc2VxKDEsYSldKQ0KUk1TRSh6LCBYaGF0KQ0KcmVzIDwtIHogLSBYaGF0DQpFMiA8LSByZXMgKiByZXMNClNQRV8xIDwtIGFwcGx5KEUyLCAxLCBzdW0pICMgbm90IHVzaW5nIHNxcnQoYXBwbHkoRTIsIDEsIHN1bSkpIQ0KDQojIEJveCAoMTk1NCkgbWV0aG9kOiB3ZWlnaHRlZCBDaGktc3F1YXJlZA0KDQp2IDwtIHZhcihTUEVfMSkNCm0gPC0gbWVhbihTUEVfMSkNCmggPC0gKDIqbV4yKS92DQpnIDwtIHYgLyAoMiptKQ0KbGltX2sxXzk1IDwtIGcqcWNoaXNxKC45NSwgZGY9aCkgDQpwbG90KFNQRV8xLCB5bGFiID0gcGFzdGUoJ1NQRSBhZnRlciB1c2luZycsIGEsICdjb21wb25lbnQnKSwgeGxhYiA9ICdpbmRleCcpDQphYmxpbmUoaCA9IGxpbV9rMV85NSwgbHR5ID0gMiwgY29sID0gJ2RhcmtncmVlbicpDQptdGV4dChwYXN0ZSgnUk1TRSA9Jywgcm91bmQoc3FydChtZWFuKFNQRV8xKSksIGRpZ2l0cyA9IDQpKSwgc2lkZT0xLCBsaW5lPTMuNSwgYXQ9OSkNCnNxcnQobWVhbihTUEVfMSkpDQoNCg0KYSA8LSAyDQpYaGF0IDwtIHBjX2NvciR4WyxzZXEoMSxhKV0gJSolIHQocGNfY29yJHJvdGF0aW9uWyxzZXEoMSxhKV0pDQpSTVNFKHosIFhoYXQpDQpyZXMgPC0geiAtIFhoYXQNCkUyIDwtIHJlcyAqIHJlcw0KI1NQRV8yIDwtIHNxcnQoYXBwbHkoRTIsIDEsIHN1bSkpDQpTUEVfMiA8LSBhcHBseShFMiwgMSwgc3VtKQ0KIyBwY2EoeiwgbmNvbXAgPSBhLCBjZW50ZXIgPSBGQUxTRSwgc2NhbGUgPSBGQUxTRSwgYWxwaGEgPSAwLjA1KSRRbGltWzFdDQoNCnYgPC0gdmFyKFNQRV8yKQ0KbSA8LSBtZWFuKFNQRV8yKQ0KaCA8LSAoMiptXjIpL3YNCmcgPC0gdiAvICgyKm0pDQpsaW1fazJfOTUgPC0gZypxY2hpc3EoLjk1LCBkZj1oKSANCnBsb3QoU1BFXzIsIHlsYWIgPSBwYXN0ZSgnU1BFIGFmdGVyIHVzaW5nJywgYSwgJ2NvbXBvbmVudHMnKSwgeGxhYiA9ICdpbmRleCcpDQphYmxpbmUoaCA9IGxpbV9rMl85NSwgbHR5ID0gMiwgY29sID0gJ2RhcmtncmVlbicpDQptdGV4dChwYXN0ZSgnUk1TRSA9Jywgcm91bmQoc3FydChtZWFuKFNQRV8yKSksIGRpZ2l0cyA9IDQpKSwgc2lkZT0xLCBsaW5lPTMuNSwgYXQ9OSkNCnNxcnQobWVhbihTUEVfMikpDQoNCg0KYSA8LSA5DQpYaGF0IDwtIHBjX2NvciR4WyxzZXEoMSxhKV0gJSolIHQocGNfY29yJHJvdGF0aW9uWyxzZXEoMSxhKV0pDQpSTVNFKHosIFhoYXQpDQpyZXMgPC0geiAtIFhoYXQNCkUyIDwtIHJlcyAqIHJlcw0KIyBTUEVfOSA8LSBzcXJ0KGFwcGx5KEUyLCAxLCBzdW0pKQ0KU1BFXzkgPC0gYXBwbHkoRTIsIDEsIHN1bSkNCiMgcGNhKHosIG5jb21wID0gYSwgY2VudGVyID0gRkFMU0UsIHNjYWxlID0gRkFMU0UsIGFscGhhID0gMC4wNSkkUWxpbVsxXQ0KDQp2IDwtIHZhcihTUEVfOSkNCm0gPC0gbWVhbihTUEVfOSkNCmggPC0gKDIqbV4yKS92DQpnIDwtIHYgLyAoMiptKQ0KbGltX2s5Xzk1IDwtIGcqcWNoaXNxKC45NSwgZGY9aCkgDQpwbG90KFNQRV85LCB5bGFiID0gcGFzdGUoJ1NQRSBhZnRlciB1c2luZycsIGEsICdjb21wb25lbnRzJyksIHhsYWIgPSAnaW5kZXgnKQ0KYWJsaW5lKGggPSBsaW1fazlfOTUsIGx0eSA9IDIsIGNvbCA9ICdkYXJrZ3JlZW4nKQ0KbXRleHQocGFzdGUoJ1JNU0UgPScsIHJvdW5kKHNxcnQobWVhbihTUEVfOSkpLCBkaWdpdHMgPSA0KSksIHNpZGU9MSwgbGluZT0zLjUsIGF0PTkpDQpzcXJ0KG1lYW4oU1BFXzkpKQ0KDQoNCnBhcihtZnJvdz1jKDMsMSkpDQoNCnBsb3QoU1BFXzEsIHR5cGUgPSAnbCcsIGNvbCA9ICdkYXJrZ3JlZW4nLCB5bGFiID0gIlNQRSBmb3IgUEMxIikNCmFibGluZShoID0gbGltX2sxXzk1LCBsdHkgPSAyLCBjb2wgPSAnZGFya2dyZWVuJykNCnBsb3QoU1BFXzIsIHR5cGUgPSAnbCcsIGNvbCA9ICdyZWQnLCB5bGFiID0gIlNQRSBmb3IgUEMxICsgUEMyIikNCmFibGluZShoID0gbGltX2syXzk1LCBsdHkgPSAyLCBjb2wgPSAnZGFya2dyZWVuJykNCnBsb3QoU1BFXzksIHR5cGUgPSAnbCcsIGNvbCA9ICdibHVlJywgeWxhYiA9ICJTUEUgZm9yIGFsbCBQQ3MiKQ0KYWJsaW5lKGggPSBsaW1fazlfOTUsIGx0eSA9IDIsIGNvbCA9ICdkYXJrZ3JlZW4nKQ0KDQpzdW0oU1BFXzEgPiBsaW1fazFfOTUpDQpzdW0oU1BFXzEgPiBsaW1fazFfOTUpL2xlbmd0aChTUEVfMSkNCg0Kc3VtKFNQRV8yID4gbGltX2syXzk1KQ0Kc3VtKFNQRV8yID4gbGltX2syXzk1KS9sZW5ndGgoU1BFXzIpDQoNCnN1bShTUEVfOSA+IGxpbV9rOV85NSkNCnN1bShTUEVfOSA+IGxpbV9rOV85NSkvbGVuZ3RoKFNQRV85KQ0KDQpgYGANCg0KSG90ZWxsaW5nJ3MgVF4yDQoNCmBgYHtyfQ0KDQoNCm51bV9jb21wIDwtIDINCg0KaW52ZXJzZV9jb3YgPC0gZGlhZyhlaWdlbl9jb3IkdmFsdWVzWzE6bnVtX2NvbXBdLCBucm93ID0gbnVtX2NvbXAsIG5jb2wgPSBudW1fY29tcCleLTENCmludmVyc2VfY292W2lzLmluZmluaXRlKGludmVyc2VfY292KV0gPC0gMA0KDQp0c3F1YXJlZCA8LSBkaWFnKHogJSolIEVbLDE6bnVtX2NvbXBdICUqJSBpbnZlcnNlX2NvdiAlKiUgdCh6ICUqJSBFWywxOm51bV9jb21wXSkpDQoNCiMgd2hpY2ggaXMgZXF1aXZhbGVudCB0byB0aGUgTWFoYWxhbm9iaXMgZGlzdGFuY2U6IChmb3IgayA+IDEpDQoNCnRzcXVhcmVkX21haGFsIDwtIG1haGFsYW5vYmlzKHNjb3Jlc1ssMTpudW1fY29tcF0sIGNlbnRlciA9IEZBTFNFLCBjb3YoY2JpbmQoc2NvcmVzWywxXSwgc2NvcmVzWyxudW1fY29tcF0pKSwgaW52ZXJ0ZWQgPSBGQUxTRSkNCnRzcXVhcmVkX2xpbTk1IDwtICgoKGRpbSh6KVsxXSAtIDEpICogKGRpbSh6KVsxXSArIDEpICogbnVtX2NvbXApIC8gKGRpbSh6KVsxXSAqIChkaW0oeilbMV0gLSBudW1fY29tcCkpKSAqIHFmKHAgPS45NSwgZGYxID0gbnVtX2NvbXAsIGRmMiA9IGRpbSh6KVsxXSAtIG51bV9jb21wKQ0KdHNxdWFyZWRfbGltOTkgPC0gKCgoZGltKHopWzFdIC0gMSkgKiAoZGltKHopWzFdICsgMSkgKiBudW1fY29tcCkgLyAoZGltKHopWzFdICogKGRpbSh6KVsxXSAtIG51bV9jb21wKSkpICogcWYocCA9Ljk5LCBkZjEgPSBudW1fY29tcCwgZGYyID0gZGltKHopWzFdIC0gbnVtX2NvbXApDQoNCnBhcihtZnJvdz1jKDEsMSkpDQoNCnBsb3QodHNxdWFyZWQsIHR5cGUgPSAnbCcsIHlsaW0gPSBjKDAsdHNxdWFyZWRfbGltOTkqMS4xKSwgeWxhYiA9ICJIb3RlbGxpbmcncyBUMiIpDQphYmxpbmUoaD10c3F1YXJlZF9saW05OSwgY29sID0gJ3JlZCcsIGx0eSA9IDIpDQp0ZXh0KDE5MCwxMCwgIjk5JSBsaW1pdCIsIGNvbCA9ICJyZWQiKQ0KYWJsaW5lKGg9dHNxdWFyZWRfbGltOTUsIGNvbCA9ICdkYXJrZ3JlZW4nLCBsdHkgPSAyKQ0KdGV4dCgxOTAsNi42LCAiOTUlIGxpbWl0IiwgY29sID0gImRhcmtncmVlbiIpDQoNCiMgc291cmNlOiBodHRwczovL2dpdGh1Yi5jb20vaHJlZGVzdGlnL3BjYU1ldGhvZHMvYmxvYi9tYXN0ZXIvUi9wY2EuUg0KDQpzaW1wbGVFbGxpcHNlIDwtIGZ1bmN0aW9uKHgsIHksIGFsZmE9MC45NSwgbGVuPTIwMCkgew0KICBOIDwtIGxlbmd0aCh4KQ0KICBBIDwtIDINCiAgbXlwaSA8LSBzZXEoMCwgMiAqIHBpLCBsZW5ndGg9bGVuKQ0KICByMSA8LSBzcXJ0KHZhcih4KSAqIHFmKGFsZmEsIDIsIE4gLSAyKSAqICgyKihOXjIgLSAxKS8oTiAqIChOIC0gMikpKSkNCiAgcjIgPC0gc3FydCh2YXIoeSkgKiBxZihhbGZhLCAyLCBOIC0gMikgKiAoMiooTl4yIC0gMSkvKE4gKiAoTiAtIDIpKSkpDQogIGNiaW5kKHIxICogY29zKG15cGkpICsgbWVhbih4KSwgcjIgKiBzaW4obXlwaSkgKyBtZWFuKHkpKQ0KfQ0KDQoNCmNvbmZpZGVuY2VfZWxsaXBzZTk1IDwtIHNpbXBsZUVsbGlwc2Uoc2NvcmVzWywxXSwgc2NvcmVzWywyXSwgYWxmYSA9IDAuOTUsIGxlbj01MDApDQpjb25maWRlbmNlX2VsbGlwc2U5OSA8LSBzaW1wbGVFbGxpcHNlKHNjb3Jlc1ssMV0sIHNjb3Jlc1ssMl0sIGFsZmEgPSAwLjk5LCBsZW49NTAwKQ0KDQpwbG90KHNjb3Jlc1ssMV0sIHNjb3Jlc1ssMl0sIHhsaW0gPSBjKG1pbihjb25maWRlbmNlX2VsbGlwc2U5OVssMV0pLCBtYXgoY29uZmlkZW5jZV9lbGxpcHNlOTlbLDFdKSksIA0KICAgICB5bGltID0gYyhtaW4oY29uZmlkZW5jZV9lbGxpcHNlOTlbLDJdKSwgbWF4KGNvbmZpZGVuY2VfZWxsaXBzZTk5WywyXSkpLCB5bGFiID0gJ1Njb3JlcyBQQzInLCB4bGFiID0gJ1Njb3JlcyBQQzEnKQ0KYWJsaW5lKGggPSAwLCB2ID0gMCkNCnBvaW50cyhjb25maWRlbmNlX2VsbGlwc2U5NSwgdHlwZSA9ICdsJywgbHR5ID0gMiwgY29sID0gJ2RhcmtncmVlbicpDQpwb2ludHMoY29uZmlkZW5jZV9lbGxpcHNlOTksIHR5cGUgPSAnbCcsIGx0eSA9IDIsIGNvbCA9ICdyZWQnKQ0KDQp4W2FzLm51bWVyaWMobmFtZXModHNxdWFyZWRbdHNxdWFyZWQgPiB0c3F1YXJlZF9saW05NV0pKSxjKCdUcmFja19UaXRsZScsICdUcmFja19BcnRpc3QnLCAnR2VucmUnKV0NCg0Kb3V0bGllcl90cmFja3MgPC0geFthcy5udW1lcmljKG5hbWVzKHRzcXVhcmVkW3RzcXVhcmVkID4gdHNxdWFyZWRfbGltOTVdKSksYygnVHJhY2tfVGl0bGUnLCAnVHJhY2tfQXJ0aXN0JywgJ0dlbnJlJyldDQpyb3VuZCh6W2FzLm51bWVyaWMobmFtZXModHNxdWFyZWRbdHNxdWFyZWQgPiB0c3F1YXJlZF9saW05NV0pKSxdLCBkaWdpdHMgPSAyKQ0KYXVkaW9feiA8LSBhcy5kYXRhLmZyYW1lKHpbYXMubnVtZXJpYyhuYW1lcyh0c3F1YXJlZFt0c3F1YXJlZCA+IHRzcXVhcmVkX2xpbTk1XSkpLF0pDQpvdXRsaWVyX3RyYWNrcyRhY291c3RpY25lc3MgPC0gYXVkaW9feiRhY291c3RpY25lc3MNCm91dGxpZXJfdHJhY2tzJGRhbmNlYWJpbGl0eSA8LSBhdWRpb196JGRhbmNlYWJpbGl0eQ0Kb3V0bGllcl90cmFja3MkZW5lcmd5IDwtIGF1ZGlvX3okZW5lcmd5DQpvdXRsaWVyX3RyYWNrcyRpbnN0cnVtZW50YWxuZXNzIDwtIGF1ZGlvX3okaW5zdHJ1bWVudGFsbmVzcw0Kb3V0bGllcl90cmFja3MkbGl2ZW5lc3MgPC0gYXVkaW9feiRsaXZlbmVzcw0Kb3V0bGllcl90cmFja3MkbG91ZG5lc3MgPC0gYXVkaW9feiRsb3VkbmVzcw0Kb3V0bGllcl90cmFja3Mkc3BlZWNoaW5lc3MgPC0gYXVkaW9feiRzcGVlY2hpbmVzcw0Kb3V0bGllcl90cmFja3MkdGVtcG8gPC0gYXVkaW9feiR0ZW1wbw0Kb3V0bGllcl90cmFja3MkdmFsZW5jZSA8LSBhdWRpb196JHZhbGVuY2UNCg0KIyBGaW5hbGx5LCBjb21iaW5pbmcgcmVzaWR1YWxzIGFuZCBzY29yZXMgKGkuZS4gSG90ZWxsaW5nJ3MgVF4yKQ0KDQojIGN1bXN1bShwY19jb3Ikc2Rldl4yIC8gc3VtKHBjX2NvciRzZGV2XjIpKVtudW1fY29tcF0NCg0KcGxvdCh0c3F1YXJlZCwgU1BFXzIsIA0KICAgICB4bGFiID0gcGFzdGUoIkhvdGVsbGluZydzIFQyICgiLCByb3VuZChjdW1zdW0ocGNfY29yJHNkZXZeMiAvIHN1bShwY19jb3Ikc2Rldl4yKSlbbnVtX2NvbXBdKjEwMCwgZGlnaXRzID0gMiksICIlKSIpLA0KICAgICB5bGFiID0gcGFzdGUoIlEtcmVzaWR1YWxzICgiLCByb3VuZCgoMS1jdW1zdW0ocGNfY29yJHNkZXZeMiAvIHN1bShwY19jb3Ikc2Rldl4yKSlbbnVtX2NvbXBdKSoxMDAsIGRpZ2l0cyA9IDIpLCAiJSkiKSkNCmFibGluZShoID0gbGltX2syXzk1LCBsdHkgPSAyLCBjb2wgPSAnZGFya2dyZWVuJykgIyBzZXZlbiByZXNpZHVhbCBvdXRsaWVycw0KYWJsaW5lKHYgPSB0c3F1YXJlZF9saW05NSwgbHR5ID0gMiwgY29sID0gJ2RhcmtncmVlbicpICMgIHR3byBzY29yZSBvdXRsaWVycw0KDQpsaWJyYXJ5KCJyb2J1c3RiYXNlIikNCm91dCA8LSBhZGpPdXRseWluZ25lc3MoeiwgbmRpcj01MDAwLCBjbG93ZXI9MCwgY3VwcGVyPTApDQpoaXN0KG91dCRhZGpvdXQpDQpydWcob3V0JGFkam91dCkNCnpbIW91dCRub25PdXQsXSMgIGFzIHdlIGNhbiBjb25maXJtLCB0aGUgb3V0bHlpbmcgY2FzZXMgZGlmZmVyIHN0cm9uZ2x5IGluIHRoZWlyIGNoYXJhY3RlcmlzdGljcyBmcm9tIHRoZWlyIHJlc3BlY3RpdmUgICAgICAgICAgICAgICAgICAgICAgICAgY2VudGVycywgdGhhdCBpcyBlc3BlY2lhbGx5IGxpdmVuZXNzIGFuZCBlbmVyZ3kgYXJlIGluY29uc2lzdGVudCB3aXRoIHRoZSBtb2RlbA0KDQp4W3Jvd25hbWVzKHpbIW91dCRub25PdXQsXSksYygiVHJhY2tfVGl0bGUiLCAiVHJhY2tfQXJ0aXN0IiwgIkdlbnJlIiwgIlRyYWNrX1BvcHVsYXJpdHkiKV0NCg0KbGlicmFyeSgicGFyYW4iKQ0KcGFyYW4oeiwgY2VudGlsZT05NSwgYWxsPVQsIGdyYXBoPVQpDQoNCngkcGNfMSA8LSBwY19jb3IkeFssMV0NCngkcGNfMiA8LSBwY19jb3IkeFssMl0NCg0KYGBgDQoNCmBgYHtyfQ0KDQp4JFJlbGVhc2VfRGF0ZSA8LSBhcy5EYXRlKHBhc3RlKHgkUmVsZWFzZV9EYXRlLCAxLCAxLCBzZXAgPSAiLSIpKQ0KeCRkYXlzX3JlbGVhc2Vfb3JpZyA8LSBhcy5pbnRlZ2VyKHJvdW5kKGRpZmZ0aW1lKCcyMDIwLTAzLTAxJywgeCRSZWxlYXNlX0RhdGUsIHVuaXRzID0gImRheXMiKSwgZGlnaXRzID0gMCkpDQp4JFJlbGVhc2VfRGF0ZSA8LSBOVUxMDQp4JGRheXNfcmVsZWFzZSA8LSBhcy5udW1lcmljKHNjYWxlKHgkZGF5c19yZWxlYXNlX29yaWcpKQ0KDQoNCmRyb3AuY29scyA8LSBjKCdBcnRpc3RfSUQnLCAnR2VucmUnLCAnVHJhY2tfQXJ0aXN0JywnVHJhY2tfSUQnLCAnVHJhY2tfVGl0bGUnLCAna2V5JywNCiAgICAgICAgICAgICAgICdtb2RlJywgJ3RpbWVfc2lnbmF0dXJlJywgJ3ZpZGVvX0lEJywgJ0NoYXJ0cycsICdhY291c3RpY25lc3MnLCAnZGFuY2VhYmlsaXR5JywgJ2VuZXJneScsICdpbnN0cnVtZW50YWxuZXNzJywNCiAgICAgICAgICAgICAgICdsaXZlbmVzcycsICdsb3VkbmVzcycsICdzcGVlY2hpbmVzcycsICd0ZW1wbycsICd2YWxlbmNlJywgJ0FydGlzdF9Qb3B1bGFyaXR5JywgJ1RyYWNrX1BvcHVsYXJpdHknLCAnQXJ0aXN0X1BvcHVsYXJpdHlfcXVhbnRpbGUnLCAndmlld3NDb3VudF9xdWFudGlsZScsICdkYXlzX3JlbGVhc2Vfb3JpZycsICdwY18xJywgJ3BjXzInKQ0KDQp4X3NlbCA8LSBkcGx5cjo6c2VsZWN0KHgsIC1vbmVfb2YoZHJvcC5jb2xzKSkNCmNvbXBsZXRlX2luZGV4IDwtIGFzLm51bWVyaWMocm93bmFtZXMoeF9zZWwpKQ0KDQp6IDwtIHNjYWxlKHhfc2VsKQ0KDQojIGludmVyc2UgYW5kIHBhcnRpYWwgY29ycmVsYXRpb25zDQpwICA8LSBzb2x2ZShjb3IoeiwgdXNlPSJjb21wbGV0ZS5vYnMiKSkNCg0KIyBpZiB0aGUgbW9kZWwgaG9sZHMgdGhlbiB0aGUgbm9uLWRpYWdvbmFsIGVsZW1lbnRzIG9mIFL0gICAMQ0KIyBtdXN0IGJlIGNsb3NlIHRvIHplcm8gKHJlbGF0aXZlIHRvIHRoZSBkaWFnb25hbCBlbGVtZW50KQ0KDQpsZXZlbHBsb3QocCwgc2NhbGVzPWxpc3QoeD1saXN0KHJvdD05MCkpKQ0KDQojIFBhcnRpYWwgY29ycmVsYXRpb25zDQpwciA8LSAtcC9zcXJ0KG91dGVyKGRpYWcocCksIGRpYWcocCkpKQ0KbGV2ZWxwbG90KHByLCBzY2FsZXM9bGlzdCh4PWxpc3Qocm90PTkwKSkpDQoNCktNTyh6KQ0KIyByZXN1bHQ6IDAuNzQgb3ZlcmFsbDogbWlkZGxpbmcsIGV2ZXJ5IGluZGl2aWR1YWwgTVNBIHZhbHVlIGlzIGFzIGxlYXN0IGFzIGhpZ2ggYXMgMC41MyAobWlkZGxpbmcpDQoNCmNvcnRlc3QuYmFydGxldHQoeikgIyByZXN1bHQ6IGNvcnJlbGF0aW9uIG1hdHJpeCBpcyBOT1QgYW4gaWRlbnRpdHkgbWF0cml4LCBzbyBwcm9jZWVkIHdpdGggZmFjdG9yIGFuYWx5c2lzDQoNCnNjcmVlKHopDQoNCmxpYnJhcnkoInBhcmFuIikNCnBhcmFuKHosIGNlbnRpbGU9OTUsIGFsbD1ULCBncmFwaD1UKSAjIGZvdXIgZmFjdG9ycyBhcmUgYXBwcm9wcmlhdGUNCg0KDQpgYGANCg0KTG9hZGluZ3MNCg0KYGBge3J9DQoNCnBjYSA8LSBwcmluY2lwYWwoeiwgbmZhY3RvcnM9NCwgcm90YXRlPSJub25lIikgIyBoMiBhcmUgImNvbW11bmFsaXRpZXMiDQpwYSA8LSBmYSh6LCBuZmFjdG9ycz00LCByb3RhdGU9Im5vbmUiLCBmbT0icGEiKSAjIHByaW5jaXBhbCBheGlzIGV4dHJhY3Rpb24NCnVscyA8LSBmYSh6LCBuZmFjdG9ycz00LCByb3RhdGU9Im5vbmUiKQ0KbWwgPC0gZmEoeiwgbmZhY3RvcnM9NCwgcm90YXRlPSJub25lIiwgZm09Im1sIikNCmxpYnJhcnkoInh0YWJsZSIpDQojIHh0YWJsZSh1bmNsYXNzKG1sJGxvYWRpbmdzKSkNCnByaW50KG1sJGxvYWRpbmdzLCBzb3J0ID0gVCkNCnByaW50KG1sJGxvYWRpbmdzLCBjdXRvZmY9LjQsIHNvcnQgPSBUKQ0KDQpzZXQuc2VlZCg0MikNCm1sLnZhcmltYXggPC0gZmEoeiwgbmZhY3RvcnM9NCwgcm90YXRlPSJ2YXJpbWF4IiwgZm09Im1sIikgDQpwcmludChtbC52YXJpbWF4JGxvYWRpbmdzLCBjdXRvZmY9LjQsIHNvcnQgPSBUKQ0KIyB4dGFibGUodW5jbGFzcyhtbC52YXJpbWF4JGxvYWRpbmdzKSkNCg0KZmEuY29uZ3J1ZW5jZShtbCwgbWwudmFyaW1heCkNCg0KDQpwYXIobWZyb3c9YygxLDEpKQ0KDQp0aHJlc2hvbGQgPC0gMC40DQpwbG90KG1sLnZhcmltYXgkbG9hZGluZ3NbLDFdLCBtbC52YXJpbWF4JGxvYWRpbmdzWywyXSwgeGxpbSA9IGMoLTEsIDEpLCB5bGltID0gYygtMSwxLjEpLCB4bGFiID0gJ01MMicsIHlsYWIgPSAnTUwzJykNCnBhbGV0dGUgPC0gYygncmVkJywgJ2JsdWUnKQ0KY29sX2luZGV4IDwtIGlmZWxzZSgoYWJzKG1sLnZhcmltYXgkbG9hZGluZ3NbLDFdKSA+IHRocmVzaG9sZCkgfCAoYWJzKG1sLnZhcmltYXgkbG9hZGluZ3NbLDJdKSA+IHRocmVzaG9sZCksIDEsIDApDQpjb2xzIDwtIHBhbGV0dGVbYXMubnVtZXJpYyhhcy5mYWN0b3IoY29sX2luZGV4KSldDQpwb2ludHMobWwudmFyaW1heCRsb2FkaW5nc1ssMV0sIG1sLnZhcmltYXgkbG9hZGluZ3NbLDJdLCBwY2g9MTksIGNvbD1jb2xzKQ0KYWJsaW5lKGggPSAwLCB2PSAwKQ0KdGV4dChtbC52YXJpbWF4JGxvYWRpbmdzWywxOjJdLCBhcy5jaGFyYWN0ZXIocm93bmFtZXMobWwudmFyaW1heCRsb2FkaW5ncykpLCBwb3MgPSAxLCBjZXggPSAwLjYsIG9mZnNldCA9IDAuNSkNCnJlY3QoeGxlZnQgPSAtdGhyZXNob2xkLCB5Ym90dG9tID0gLXRocmVzaG9sZCwgeHJpZ2h0ID0gdGhyZXNob2xkLCB5dG9wID0gdGhyZXNob2xkKQ0KIyBmYS5kaWFncmFtKG1sLnZhcmltYXgsIHNpbXBsZT1UUlVFLCBjdXQ9LjIsIGRpZ2l0cz0yKQ0Kc2V0LnNlZWQoNDIpDQptbC5wcm9tYXggPC0gZmEoeiwgbmZhY3RvcnM9NCwgcm90YXRlPSJwcm9tYXgiLCBmbT0ibWwiKQ0KZmEuY29uZ3J1ZW5jZShtbC5wcm9tYXgsIG1sLnZhcmltYXgpDQoNCm1sLnByb21heCRQaGkNCnByaW50KG1sLnByb21heCRsb2FkaW5ncywgY3V0b2ZmPS40LCBzb3J0ID0gVCkNCiN4dGFibGUodW5jbGFzcyhtbC5wcm9tYXgkbG9hZGluZ3MpKQ0KDQpwcmludChtbC5wcm9tYXgkU3RydWN0dXJlLCBjdXRvZmYgPSAwLjQpDQoNCiMgRm9yIG9ydGhvZ2hvbmFsIHJvdGF0aW9uczogY29yKGl0ZW0sIGZhY3RvcikgPSBsb2FkaW5ncyAoInBhdHRlcm4iKSBtYXRyaXggPSBzdHJ1Y3R1cmUgbWF0cml4LCANCiMgc2luY2Ugc3RydWN0dXJlIG1hdHJpeCA9IGxvYWRpbmdzIG1hdHJpeCAqJSogZmFjdG9yIGludGVyY29ycmVsYXRpb24gbWF0cml4ICh3aGljaCBpcyBpZGVudGl0eSBmb3Igb3J0aG9nb25hbCByb3RhdGlvbnMpDQojIGV4YW1wbGU6IG1sLnByb21heCRsb2FkaW5ncyAlKiUgbWwucHJvbWF4JFBoaSA9IHN0cnVjdHVyZSBtYXRyaXgNCg0KDQpwYXIobWZyb3c9YygxLDEpKQ0KDQp0aHJlc2hvbGQgPC0gMC41DQpwbG90KG1sLnByb21heCRTdHJ1Y3R1cmVbLDFdLCBtbC5wcm9tYXgkU3RydWN0dXJlWywyXSwgeGxpbSA9IGMoLTEsIDEpLCB5bGltID0gYygtMSwxLjEpLCB4bGFiID0gJ01MMicsIHlsYWIgPSAnTUwzJykNCnBhbGV0dGUgPC0gYygncmVkJywgJ2JsdWUnKQ0KY29sX2luZGV4IDwtIGlmZWxzZSgoYWJzKG1sLnByb21heCRTdHJ1Y3R1cmVbLDFdKSA+IHRocmVzaG9sZCkgfCAoYWJzKG1sLnByb21heCRTdHJ1Y3R1cmVbLDJdKSA+IHRocmVzaG9sZCksIDEsIDApDQpjb2xzIDwtIHBhbGV0dGVbYXMubnVtZXJpYyhhcy5mYWN0b3IoY29sX2luZGV4KSldDQpwb2ludHMobWwucHJvbWF4JFN0cnVjdHVyZVssMV0sIG1sLnByb21heCRTdHJ1Y3R1cmVbLDJdLCBwY2g9MTksIGNvbD1jb2xzKQ0KYWJsaW5lKGggPSAwLCB2PSAwKQ0KdGV4dChtbC5wcm9tYXgkU3RydWN0dXJlWywxOjJdLCBhcy5jaGFyYWN0ZXIocm93bmFtZXMobWwucHJvbWF4JFN0cnVjdHVyZSkpLCBwb3MgPSAxLCBjZXggPSAwLjYsIG9mZnNldCA9IDAuNSkNCnJlY3QoeGxlZnQgPSAtdGhyZXNob2xkLCB5Ym90dG9tID0gLXRocmVzaG9sZCwgeHJpZ2h0ID0gdGhyZXNob2xkLCB5dG9wID0gdGhyZXNob2xkKQ0KDQoNCg0KZmExIDwtZmFjdGFuYWwoeiwgZmFjdG9ycz00LCBzY29yZXMgPSAncmVncmVzc2lvbicsIGxvd2VyID0gMC4xKSAjIE1MIHdpdGggS2Fpc2VyIG5vcm1hbGl6YXRpb24gDQpoZWFkKGZhMSRzY29yZXMpDQpmYTIgPC0gZmEoeiwgbmZhY3RvcnM9NCkgIyBvYmxpbWluIHJvdGF0aW9uIHdpdGhvdXQgS2Fpc2VyIG5vcm1hbGl6YXRpb24NCmhlYWQoZmEyJHNjb3JlcykNCmNvcihmYTEkc2NvcmVzLCBmYTIkc2NvcmVzKQ0KY29yKGZhMSRzY29yZXMsIG1sLnByb21heCRzY29yZXMpDQpjb3IobWwucHJvbWF4JHNjb3JlcywgbWwudmFyaW1heCRzY29yZXMpDQoNCmZhX2JhcnRsZXR0X3Njb3JlcyA8LSBmYSh6LCBuZmFjdG9ycz00LCByb3RhdGU9InZhcmltYXgiLCBmbT0ibWwiLCBzY29yZXMgPSAnQmFydGxldHQnKQ0KY29yKG1sLnZhcmltYXgkc2NvcmVzLCBmYV9iYXJ0bGV0dF9zY29yZXMkc2NvcmVzKQ0KY29yKGZhX2JhcnRsZXR0X3Njb3JlcyRzY29yZXMpDQoNCg0KDQpgYGANCg0KYGBge3J9DQoNCiMgc3VtIHNjb3Jlcw0KDQp0aHJlc2hvbGQgPC0gMC41DQoNCnZhcnNfMSA8LSBhYnMobWwucHJvbWF4JFN0cnVjdHVyZVssMV0pPnRocmVzaG9sZA0KdmFyc18yIDwtIGFicyhtbC5wcm9tYXgkU3RydWN0dXJlWywyXSk+dGhyZXNob2xkDQp2YXJzXzMgPC0gYWJzKG1sLnByb21heCRTdHJ1Y3R1cmVbLDNdKT50aHJlc2hvbGQNCnZhcnNfNCA8LSBhYnMobWwucHJvbWF4JFN0cnVjdHVyZVssNF0pPnRocmVzaG9sZA0KDQprZXkubGlzdCA8LSBsaXN0KG9uZSA9IGFzLm51bWVyaWMod2hpY2godmFyc18xPT0xKSksIA0KICAgICAgICAgICAgICAgIHR3byA9IGFzLm51bWVyaWMod2hpY2godmFyc18yPT0xKSksIA0KICAgICAgICAgICAgICAgIHRocmVlID0gYXMubnVtZXJpYyh3aGljaCh2YXJzXzM9PTEpKSwgDQogICAgICAgICAgICAgICAgZm91ciA9IGFzLm51bWVyaWMod2hpY2godmFyc180PT0xKSkpDQoNCnNpZ24ubWF0IDwtIGNiaW5kKHNpZ24obWwucHJvbWF4JFN0cnVjdHVyZVssMV0pLCANCiAgICAgICAgICAgICAgICAgICAgICBzaWduKG1sLnByb21heCRTdHJ1Y3R1cmVbLDJdKSwNCiAgICAgICAgICAgICAgICAgICAgICBzaWduKG1sLnByb21heCRTdHJ1Y3R1cmVbLDNdKSwNCiAgICAgICAgICAgICAgICAgICAgICBzaWduKG1sLnByb21heCRTdHJ1Y3R1cmVbLDRdKSkNCg0Ka2V5cyA8LSBtYWtlLmtleXMoeiwga2V5Lmxpc3QsaXRlbS5sYWJlbHMgPSBjb2xuYW1lcyh6KSkNCg0Kc2kgPC0gc2NvcmVJdGVtcyhrZXlzICogc2lnbi5tYXQsIHopICMgdGhlc2UgYXJlIHRoZSBzY29yZXMgSSBmaW5hbGx5IHVzZQ0KDQpwYWlycyhjYmluZChzaSRzY29yZXNbLDFdLCBzaSRzY29yZXNbLDJdLCBzaSRzY29yZXNbLDNdLCBzaSRzY29yZXNbLDRdKSkNCmNvcihjYmluZChzaSRzY29yZXNbLDFdLCBzaSRzY29yZXNbLDJdLCBzaSRzY29yZXNbLDNdLCBzaSRzY29yZXNbLDRdKSkNCg0KcHJpbnQobWwucHJvbWF4JGxvYWRpbmdzLCBjdXRvZmYgPSAwLjUsIHNvcnQgPSBUKQ0KDQojIEkgYXBwbHkgYSB0aHJlc2hvbGQgb2YgMC41IGZvciBtYW51YWwgY29tcHV0YXRpb24gb2Ygc3VtIHNjb3Jlcw0KDQpmMiA8LSAoelssMTFdICsgelssMTJdICsgelssMTNdICsgelssMTRdKS80ICMgY29tbWVudENvdW50LCBkaXNsaWtlQ291bnQsIGxpa2VDb3VudCwgdmlld3NDb3VudA0KZjMgPC0gKHpbLDFdICsgelssMl0gKyB6Wyw1XSkvMyAjIEFydGlzdF9BbGJ1bXNfTnVtYmVyLCBBcnRpc3RfQ29tcGlsYXRpb25zX051bWJlciwgQXJ0aXN0X0NvbXBpbGF0aW9uc19UcmFja3NfTnVtYmVyDQpmNCA8LSAoelssMl0gKyB6WywzXSArIHpbLDRdKS8zICMgQXJ0aXN0X0FsYnVtc19UcmFja3NfTnVtYmVyLCBBcnRpc3RfQXBwZWFyYW5jZXNfTnVtYmVyLCBBcnRpc3RfQXBwZWFyYW5jZXNfVHJhY2tzX051bWJlcg0KZjEgPC0gKHpbLDhdICsgelssOV0pLzIgIyBBcnRpc3RfU2luZ2xlc19OdW1iZXIsIEFydGlzdF9TaW5nbGVzX1RyYWNrc19OdW1iZXINCg0KcGFpcnMoY2JpbmQoZjIsIGYzLCBmNCwgZjEpKQ0KY29yKGNiaW5kKGYyLCBmMywgZjQsIGYxKSkNCg0KcHN5Y2g6OmFscGhhKGNvcih6Wyx2YXJzXzFdKSwgY2hlY2sua2V5cyA9IFQpICMgY29tbWVudENvdW50LCBkaXNsaWtlQ291bnQsIGxpa2VDb3VudCwgdmlld3NDb3VudA0KcHN5Y2g6OmFscGhhKGNvcih6Wyx2YXJzXzJdKSwgY2hlY2sua2V5cyA9IFQpICMgQXJ0aXN0X0FsYnVtc19OdW1iZXIsIEFydGlzdF9BbGJ1bXNfVHJhY2tzX051bWJlciwgQXJ0aXN0X0NvbXBpbGF0aW9uc19OdW1iZXIsIEFydGlzdF9Db21waWxhdGlvbnNfVHJhY2tzX051bWJlciwgQXJ0aXN0X1NpbmdsZXNfTnVtYmVyDQpwc3ljaDo6YWxwaGEoY29yKHpbLHZhcnNfM10pLCBjaGVjay5rZXlzID0gVCkgIyBBcnRpc3RfQWxidW1zX1RyYWNrc19OdW1iZXIsIEFydGlzdF9BcHBlYXJhbmNlc19OdW1iZXIsIEFydGlzdF9BcHBlYXJhbmNlc19UcmFja3NfTnVtYmVyLCBUcmFja19EdXJhdGlvbl9tcyANCnBzeWNoOjphbHBoYShjb3IoelssdmFyc180XSksIGNoZWNrLmtleXMgPSBUKSAjIEFydGlzdF9TaW5nbGVzX051bWJlciwgQXJ0aXN0X1NpbmdsZXNfVHJhY2tzX051bWJlcg0KDQppdGVtc18xIDwtIHJldmVyc2UuY29kZSgoa2V5c1ssMV0gKiBzaWduLm1hdFssMV0pWyhrZXlzWywxXSAqIHNpZ24ubWF0WywxXSkgIT0gMF0sIHpbLCB2YXJzXzFdKQ0KaXRlbXNfMiA8LSByZXZlcnNlLmNvZGUoKGtleXNbLDJdICogc2lnbi5tYXRbLDJdKVsoa2V5c1ssMl0gKiBzaWduLm1hdFssMl0pICE9IDBdLCB6WywgdmFyc18yXSkNCml0ZW1zXzMgPC0gcmV2ZXJzZS5jb2RlKChrZXlzWywzXSAqIHNpZ24ubWF0WywzXSlbKGtleXNbLDNdICogc2lnbi5tYXRbLDNdKSAhPSAwXSwgelssIHZhcnNfM10pDQppdGVtc180IDwtIHJldmVyc2UuY29kZSgoa2V5c1ssNF0gKiBzaWduLm1hdFssNF0pWyhrZXlzWyw0XSAqIHNpZ24ubWF0Wyw0XSkgIT0gMF0sIHpbLCB2YXJzXzRdKQ0KDQpsaWJyYXJ5KCJhZGRpdGl2aXR5VGVzdHMiKQ0KDQp0dWtleS50ZXN0KGl0ZW1zXzEpICMgcmVzdWx0OiBIMCByZWplY3RlZCwgaS5lLiBzdW0gc2NvcmUgaXMgaW5zdWZmaWNpZW50IHRvIHJlcHJlc2VudCB0aGUgdmFyaWFibGVzIChmYWN0b3IgMikNCnR1a2V5LnRlc3QoaXRlbXNfMikgIyByZXN1bHQ6IEgwIHJlamVjdGVkIChmYWN0b3IgMykNCnR1a2V5LnRlc3QoaXRlbXNfMykgIyByZXN1bHQ6IEgwIGNhbm5vdCBiZSByZWplY3RlZCAoZmFjdG9yIDQpDQp0dWtleS50ZXN0KGl0ZW1zXzQpICMgcmVzdWx0OiBIMCBjYW5ub3QgYmUgcmVqZWN0ZWQgKGZhY3RvciAxKQ0KDQpjb3IobWwucHJvbWF4JHNjb3Jlcywgc2kkc2NvcmVzKQ0KZmFfYmFydGxldHRfc2NvcmVzIDwtIGZhKHosIG5mYWN0b3JzPTQsIHJvdGF0ZT0icHJvbWF4IiwgZm09Im1sIiwgc2NvcmVzID0gJ0JhcnRsZXR0JykNCmNvcihtbC5wcm9tYXgkc2NvcmVzLCBmYV9iYXJ0bGV0dF9zY29yZXMkc2NvcmVzKQ0KY29yKHNpJHNjb3JlcywgZmFfYmFydGxldHRfc2NvcmVzJHNjb3JlcykNCmNvcihjYmluZChmMiwgZjMsIGY0LCBmMSksIHNpJHNjb3JlcykgIyBhcmUgYWxtb3N0IGlkZW50aWNhbCBub3cNCmNvcihjYmluZChmMiwgZjMsIGY0LCBmMSksIGZhX2JhcnRsZXR0X3Njb3JlcyRzY29yZXMpDQpjb3Ioc2kkc2NvcmVzLCBmYV9iYXJ0bGV0dF9zY29yZXMkc2NvcmVzKQ0KDQojIEkgY2hvb3NlIHRoZSBtYW51YWxseSBjb21wdXRlZCBzY29yZUl0ZW1zIGFuZCBzdW0gc2NvcmVzIG9taXR0aW5nIDw9IDAuNSBzdHJ1Y3R1cmUgbG9hZGluZ3MgYW5kIGFzc2lnbmluZyB0aGUgaXRlbSB3aXRoIHRoZSBsYXJnZXIgbG9hZGluZyB0byBhIHNpbmdsZSBmYWN0b3IgaW4gY2FzZSBvZiBhbWJpZ3VpdHkNCg0KcGFyKG1mcm93PWMoMSwyKSkNCmhpc3QobWwucHJvbWF4JHNjb3Jlc1ssMV0sIG1haW4gPSAnWW91dHViZSBwb3B1bGFyaXR5IGZhY3RvcicsIHhsYWIgPSAnU2NvcmUgY29ycmVzcG9uZGluZyB0byBmYWN0b3IgMiAocHJvbWF4KScpDQpydWcobWwucHJvbWF4JHNjb3Jlc1tpZmVsc2UoeFtjb21wbGV0ZV9pbmRleCwgJ0NoYXJ0cyddID09IDEsIFRSVUUsIEZBTFNFKSwgMV0sIGNvbD0iYmx1ZSIpDQpydWcobWwucHJvbWF4JHNjb3Jlc1tpZmVsc2UoeFtjb21wbGV0ZV9pbmRleCwgJ0NoYXJ0cyddICE9IDEsIFRSVUUsIEZBTFNFKSwgMV0sIGNvbD0icmVkIikNCg0KaGlzdChtbC5wcm9tYXgkc2NvcmVzWywyXSwgbWFpbiA9ICdNdXNpYyBzdXBwbHkgZmFjdG9yJywgeGxhYiA9ICdTY29yZSBjb3JyZXNwb25kaW5nIHRvIGZhY3RvciAzIChwcm9tYXgpJykNCnJ1ZyhtbC5wcm9tYXgkc2NvcmVzW2lmZWxzZSh4W2NvbXBsZXRlX2luZGV4LCAnQ2hhcnRzJ10gPT0gMSwgVFJVRSwgRkFMU0UpLCAyXSwgY29sPSJibHVlIikNCnJ1ZyhtbC5wcm9tYXgkc2NvcmVzW2lmZWxzZSh4W2NvbXBsZXRlX2luZGV4LCAnQ2hhcnRzJ10gIT0gMSwgVFJVRSwgRkFMU0UpLCAyXSwgY29sPSJyZWQiKQ0KDQpwYXIobWZyb3c9YygxLDIpKQ0KaGlzdChzaSRzY29yZXNbLDFdLCBtYWluID0gJ1lvdXR1YmUgcG9wdWxhcml0eSBmYWN0b3InLCB4bGFiID0gJ1Njb3JlIGNvcnJlc3BvbmRpbmcgdG8gZmFjdG9yIDIgKHByb21heCknKQ0KcnVnKHNpJHNjb3Jlc1tpZmVsc2UoeFtjb21wbGV0ZV9pbmRleCwgJ0NoYXJ0cyddID09IDEsIFRSVUUsIEZBTFNFKSwgMV0sIGNvbD0iYmx1ZSIpDQpydWcoc2kkc2NvcmVzW2lmZWxzZSh4W2NvbXBsZXRlX2luZGV4LCAnQ2hhcnRzJ10gIT0gMSwgVFJVRSwgRkFMU0UpLCAxXSwgY29sPSJyZWQiKQ0KDQpoaXN0KHNpJHNjb3Jlc1ssMl0sIG1haW4gPSAnTXVzaWMgc3VwcGx5IGZhY3RvcicsIHhsYWIgPSAnU2NvcmUgY29ycmVzcG9uZGluZyB0byBmYWN0b3IgMyAocHJvbWF4KScpDQpydWcoc2kkc2NvcmVzW2lmZWxzZSh4W2NvbXBsZXRlX2luZGV4LCAnQ2hhcnRzJ10gPT0gMSwgVFJVRSwgRkFMU0UpLCAyXSwgY29sPSJibHVlIikNCnJ1ZyhzaSRzY29yZXNbaWZlbHNlKHhbY29tcGxldGVfaW5kZXgsICdDaGFydHMnXSAhPSAxLCBUUlVFLCBGQUxTRSksIDJdLCBjb2w9InJlZCIpDQoNCnBhcihtZnJvdz1jKDEsMikpDQpoaXN0KGYyLCBtYWluID0gJ1lvdXR1YmUgcG9wdWxhcml0eSBmYWN0b3InLCB4bGFiID0gJ1Njb3JlIGNvcnJlc3BvbmRpbmcgdG8gZmFjdG9yIDIgKHByb21heCknKQ0KcnVnKGYyW2lmZWxzZSh4W2NvbXBsZXRlX2luZGV4LCAnQ2hhcnRzJ10gPT0gMSwgVFJVRSwgRkFMU0UpXSwgY29sPSJibHVlIikNCnJ1ZyhmMltpZmVsc2UoeFtjb21wbGV0ZV9pbmRleCwgJ0NoYXJ0cyddICE9IDEsIFRSVUUsIEZBTFNFKV0sIGNvbD0icmVkIikNCg0KaGlzdChmMywgbWFpbiA9ICdNdXNpYyBzdXBwbHkgZmFjdG9yJywgeGxhYiA9ICdTY29yZSBjb3JyZXNwb25kaW5nIHRvIGZhY3RvciAzIChwcm9tYXgpJykNCnJ1ZyhmM1tpZmVsc2UoeFtjb21wbGV0ZV9pbmRleCwgJ0NoYXJ0cyddID09IDEsIFRSVUUsIEZBTFNFKV0sIGNvbD0iYmx1ZSIpDQpydWcoZjNbaWZlbHNlKHhbY29tcGxldGVfaW5kZXgsICdDaGFydHMnXSAhPSAxLCBUUlVFLCBGQUxTRSldLCBjb2w9InJlZCIpDQoNCngkZmFjdG9yXzIgPC0gc2kkc2NvcmVzWywxXQ0KeCRmYWN0b3JfMyA8LSBzaSRzY29yZXNbLDJdDQp4JGZhY3Rvcl80IDwtIHNpJHNjb3Jlc1ssM10NCngkZmFjdG9yXzEgPC0gc2kkc2NvcmVzWyw0XQ0KDQp4JGZhY3Rvcl8ybWFuIDwtIGYyDQp4JGZhY3Rvcl8zbWFuIDwtIGYzDQp4JGZhY3Rvcl80bWFuIDwtIGY0DQp4JGZhY3Rvcl8xbWFuIDwtIGYxDQoNCmNvcihjYmluZChzaSRzY29yZXNbLDFdLCBzaSRzY29yZXNbLDJdLCBzaSRzY29yZXNbLDNdLCBzaSRzY29yZXNbLDRdKSwgY2JpbmQoZjIsIGYzLCBmNCwgZjEpKQ0KDQpjb3IoY2JpbmQoc2kkc2NvcmVzWywxXSwgc2kkc2NvcmVzWywyXSwgc2kkc2NvcmVzWywzXSwgc2kkc2NvcmVzWyw0XSksIGZhX2JhcnRsZXR0X3Njb3JlcyRzY29yZXMpDQoNCmNvcihjYmluZChzaSRzY29yZXNbLDFdLCBzaSRzY29yZXNbLDJdLCBzaSRzY29yZXNbLDNdLCBzaSRzY29yZXNbLDRdKSwgbWwucHJvbWF4JHNjb3JlcykNCg0KeCRmYWN0b3JfMmJhcnRsZXR0IDwtIGZhX2JhcnRsZXR0X3Njb3JlcyRzY29yZXNbLDFdDQp4JGZhY3Rvcl8zYmFydGxldHQgPC0gZmFfYmFydGxldHRfc2NvcmVzJHNjb3Jlc1ssMl0NCngkZmFjdG9yXzRiYXJ0bGV0dCA8LSBmYV9iYXJ0bGV0dF9zY29yZXMkc2NvcmVzWywzXQ0KeCRmYWN0b3JfMWJhcnRsZXR0IDwtIGZhX2JhcnRsZXR0X3Njb3JlcyRzY29yZXNbLDRdDQoNCmBgYA0KDQoNCmBgYHtyfQ0KDQp6IDwtIHNjYWxlKHhfZmVhdHVyZXMpDQoNCmxpYnJhcnkoZmFjdG9leHRyYSkNCg0KIyBFbGJvdyBtZXRob2QNCmZ2aXpfbmJjbHVzdCh6LCBrbWVhbnMsIG1ldGhvZCA9ICJ3c3MiKSArDQogIGdlb21fdmxpbmUoeGludGVyY2VwdCA9IDQsIGxpbmV0eXBlID0gMikgKyBsYWJzKHRpdGxlID0gIiIpDQoNCnNldC5zZWVkKDQyKQ0Ka3ogPC0ga21lYW5zKHosIGM9NCkNCg0Ka190ZWNobm8gPC0gbmFtZXMoa3okY2x1c3RlcltreiRjbHVzdGVyID09IDFdKQ0Ka19oaXBob3AgPC0gbmFtZXMoa3okY2x1c3RlcltreiRjbHVzdGVyID09IDJdKQ0Ka19wb3AgPC0gbmFtZXMoa3okY2x1c3RlcltreiRjbHVzdGVyID09IDNdKQ0Ka19jbGFzc2ljIDwtIG5hbWVzKGt6JGNsdXN0ZXJba3okY2x1c3RlciA9PSA0XSkNCg0Ka3okY2x1c3RlcltrX3RlY2hub10gPC0gNA0Ka3okY2x1c3RlcltrX3BvcF0gPC0gMw0Ka3okY2x1c3RlcltrX2hpcGhvcF0gPC0gMg0Ka3okY2x1c3RlcltrX2NsYXNzaWNdIDwtIDENCg0KcGFyKG1mY29sPWMoMSwxKSkNCnBsb3QocGNfY29yJHhbLDFdLCBwY19jb3IkeFssMl0sIGNvbD1reiRjbHVzdGVyLCBwY2g9MTksIHhsYWIgPSAnUEMxJywgeWxhYiA9ICdQQzInKQ0KDQojIHByb2plY3QgY2x1c3RlciBjZW50ZXJzIHRvIFBDQSBmZWF0dXJlIHNwYWNlOg0KDQpjbHVzdGVyX2NlbnRlcnNfcGNhIDwtIGt6JGNlbnRlcnMgJSolIHBjX2NvciRyb3RhdGlvblssIDE6Ml0NCnBvaW50cyhjbHVzdGVyX2NlbnRlcnNfcGNhWywxXSwgY2x1c3Rlcl9jZW50ZXJzX3BjYVssMl0sIGNvbCA9ICdtYWdlbnRhJywgcGNoID0gMTAsIGNleCA9IDMpDQoNCnBsb3QocGNfY29yJHhbLDFdLCBwY19jb3IkeFssMl0sIGNvbD1hcy5udW1lcmljKHgkR2VucmUpLCBwY2g9MTksIHhsYWIgPSAnUEMxJywgeWxhYiA9ICdQQzInKQ0KDQpsaWJyYXJ5KCJjbHVzdGVyIikNCg0Kc2tfNCA8LSBzaWxob3VldHRlKGt6JGNsdXN0ZXIsIGRpc3QoeikpDQpwbG90KHNrXzQsIGNvbD0xOjQsIGJvcmRlcj1OQSwgYW5uID0gVCwgbWFpbiA9ICIiKQ0KDQpsaWJyYXJ5KCJjYXJldCIpDQoNCmNvbmZ1c2lvbk1hdHJpeChhcy5mYWN0b3IoYXMubnVtZXJpYyhreiRjbHVzdGVyKSksIGFzLmZhY3Rvcihhcy5udW1lcmljKHgkR2VucmUpKSkNCg0KIyBBbHRob3VnaCBrLW1lYW5zIGlzIG5vdCBhIGNsYXNzaWZpY2F0aW9uIGFsZ29yaXRobSwgd2UgY2FuIGNvbXB1dGUgdGhlIHByb3BvcnRpb25zIG9mIGdlbnJlcyBwZXIgY2x1c3RlciB0byBhc3NpZ24NCiMgYW4gaW50ZXJwcmV0YXRpb24gZm9yIGVhY2ggY2x1c3Rlci4NCg0KdGFiX2ttZWFucyA8LSB0YWJsZShreiRjbHVzdGVyLCB4JEdlbnJlKQ0KI3h0YWJsZShyb3VuZCh0YWJfa21lYW5zL2NvbFN1bXModGFiX2ttZWFucyksIGRpZ2l0cyA9IDIpKjEwMCkNCnJvdW5kKHRhYl9rbWVhbnMvY29sU3Vtcyh0YWJfa21lYW5zKSwgZGlnaXRzID0gMikqMTAwDQoNCiMgSXQgc2VlbXMgdGhhdCBDbGFzc2ljIG11c2ljIGFuZCBUZWNobm8gbXVzaWMgY2FuIGJlIGFjY3VyYXRlbHkgZGlzdGluZ3Vpc2hlZCBidXQgYmV0d2VlbiBIaXAgSG9wIGFuZCBQb3AgdGhlcmUgYXJlIA0KIyBtYW55IGZhbHNlIG5lZ2F0aXZlcywgZS5nLiA2MiUgb2YgdGhlIHRyYWNrcyB3ZXJlIHByZWRpY3RlZCB0byBiZSBvZiB0aGUgZ2VucmUgUG9wIGFsdGhvdWdoIHRoZSB0cnV0aCB3YXMgdGhhdCB0aGV5IHdlcmUNCiMgb2YgY2xhc3MgSGlwIEhvcCwgdW5kZXJsaW5pbmcgdGhlIGFtYmlndWl0eSBiZXR3ZWVuIHRob3NlIGdlbnJlcyBpbiB0ZXJtcyBvZiBtdXNpYyB0aGVvcmV0aWNhbCBjaGFyYWN0ZXJpc3RpY3Mgbm93YWRheXMuDQojIFZpY2UgdmVyc2EsIDE2JSBvZiB0aGUgdHJhY2tzIHByZWRpY3RlZCB0byBiZSBIaXAgSG9wIG11c2ljIHdlcmUgYWN0dWFsbHkgUG9wIG11c2ljLg0KDQoNCg0KDQoNCmBgYA0KYGBge3J9DQpzZXQuc2VlZCg0MikNCmNsMiA8LSBwYW0oeiwgNCkNCnBsb3QocGNfY29yJHhbLDFdLCBwY19jb3IkeFssMl0sIGNvbD1jbDIkY2x1c3RlcmluZykNCg0KDQprX2NsYXNzaWMgPC0gbmFtZXMoY2wyJGNsdXN0ZXJpbmdbY2wyJGNsdXN0ZXJpbmcgPT0gMV0pDQprX3RlY2hubyA8LSBuYW1lcyhjbDIkY2x1c3RlcmluZ1tjbDIkY2x1c3RlcmluZyA9PSAyXSkNCmtfcG9wIDwtIG5hbWVzKGNsMiRjbHVzdGVyaW5nW2NsMiRjbHVzdGVyaW5nID09IDNdKQ0Ka19oaXBob3AgPC0gbmFtZXMoY2wyJGNsdXN0ZXJpbmdbY2wyJGNsdXN0ZXJpbmcgPT0gNF0pDQoNCmNsMiRjbHVzdGVyaW5nW2tfdGVjaG5vXSA8LSA0DQpjbDIkY2x1c3RlcmluZ1trX3BvcF0gPC0gMw0KY2wyJGNsdXN0ZXJpbmdba19oaXBob3BdIDwtIDINCmNsMiRjbHVzdGVyaW5nW2tfY2xhc3NpY10gPC0gMQ0KDQoNCnBhcihtZmNvbD1jKDEsMSkpDQpwbG90KHBjX2NvciR4WywxXSwgcGNfY29yJHhbLDJdLCBjb2w9Y2wyJGNsdXN0ZXJpbmcsIHBjaD0xOSwgeGxhYiA9ICdQQzEnLCB5bGFiID0gJ1BDMicpDQpwb2ludHMocGNfY29yJHhbcm93bmFtZXMoY2wyJG1lZG9pZHMpLDFdLCBwY19jb3IkeFtyb3duYW1lcyhjbDIkbWVkb2lkcyksMl0sIGNvbCA9ICdtYWdlbnRhJywgcGNoID0gMTAsIGNleCA9IDMpDQoNCnBsb3QocGNfY29yJHhbLDFdLCBwY19jb3IkeFssMl0sIGNvbD1hcy5udW1lcmljKHgkR2VucmUpLCBwY2g9MTksIHhsYWIgPSAnUEMxJywgeWxhYiA9ICdQQzInKQ0KDQojIHh0YWJsZSh4W3Jvd25hbWVzKGNsMiRtZWRvaWRzKSwgYygnVHJhY2tfVGl0bGUnLCAnVHJhY2tfQXJ0aXN0JywgJ0dlbnJlJyldKQ0KDQp4W3Jvd25hbWVzKGNsMiRtZWRvaWRzKSwgYygnVHJhY2tfVGl0bGUnLCAnVHJhY2tfQXJ0aXN0JywgJ0dlbnJlJyldDQoNCnNtXzQgPC0gc2lsaG91ZXR0ZShjbDIkY2x1c3RlcmluZywgZGlzdCh6KSkNCnBsb3Qoc21fNCwgY29sPTE6NCwgYm9yZGVyPU5BLCBtYWluID0gIiIpDQoNCmNvbmZ1c2lvbk1hdHJpeChhcy5mYWN0b3IoYXMubnVtZXJpYyhjbDIkY2x1c3RlcmluZykpLCBhcy5mYWN0b3IoYXMubnVtZXJpYyh4JEdlbnJlKSkpDQoNCnRhYl9rbWVkb2lkcyA8LSB0YWJsZShjbDIkY2x1c3RlcmluZywgeCRHZW5yZSkNCiMgeHRhYmxlKChyb3VuZCh0YWJfa21lZG9pZHMvY29sU3Vtcyh0YWJfa21lZG9pZHMpLCBkaWdpdHMgPSAyKSkqMTAwKQ0Kcm91bmQodGFiX2ttZWRvaWRzL2NvbFN1bXModGFiX2ttZWRvaWRzKSwgZGlnaXRzID0gMikqMTAwDQpgYGANCg0KYGBge3J9DQoNCiMgRmFjdG9yIGFuYWx5c2lzICsgay1tZWFucyBvbiBzY29yZXMNCg0KbGlicmFyeSgicHN5Y2giKQ0KS01PKHopDQpzY3JlZSh6KQ0KbGlicmFyeSgicGFyYW4iKQ0KcGFyYW4oeiwgY2VudGlsZT05NSwgYWxsPVQsIGdyYXBoPVQpICMgdHdvIGZhY3RvcnMgYXJlIGFwcHJvcHJpYXRlDQpmYSA8LSBmYSh6LCBuZmFjdG9ycz0yLCByb3RhdGU9InZhcmltYXgiKQ0KbGlicmFyeSgicGxvdC5tYXRyaXgiKQ0KcGxvdChsb2FkaW5ncyhmYSksIGxhcz0xKQ0KYWwxIDwtIHBzeWNoOjphbHBoYSh6WyxjKCJhY291c3RpY25lc3MiLCAiZGFuY2VhYmlsaXR5IiwgImVuZXJneSIsICJsb3VkbmVzcyIpXSwgY2hlY2sua2V5cyA9IFRSVUUpDQphbDENCmFsMiA8LSBwc3ljaDo6YWxwaGEoelssYygiaW5zdHJ1bWVudGFsbmVzcyIsICJ2YWxlbmNlIildLCBjaGVjay5rZXlzID0gVFJVRSkNCmFsMg0Kc2NhbGUgPC0gY2JpbmQoYWwxJHNjb3JlcywgYWwyJHNjb3JlcykNCg0KZnZpel9uYmNsdXN0KHNjYWxlLCBrbWVhbnMsIG1ldGhvZCA9ICJ3c3MiKSArDQogIGdlb21fdmxpbmUoeGludGVyY2VwdCA9IDQsIGxpbmV0eXBlID0gMikrDQogIGxhYnMoc3VidGl0bGUgPSAiRWxib3cgbWV0aG9kIikNCg0KcmVvcmRlciA8LSBmdW5jdGlvbih4LCAuLi4pIHsNCiAgZHIgPC0gZGlzdCh4KQ0KICBociA8LSBoY2x1c3QoZHIpDQogIGRjIDwtIGRpc3QodCh4KSkNCiAgaGMgPC0gaGNsdXN0KGRjKQ0KICB4W2hyJG9yZGVyLCBoYyRvcmRlcl0NCn0NCg0Kc2V0LnNlZWQoNDIpDQpjbDEgPC0ga21lYW5zKHosIDQpDQpzZXQuc2VlZCg0MikNCmNsMiA8LSBrbWVhbnMoc2NhbGUsIDQpDQoNCnByaW50KHJlb3JkZXIodGFibGUoY2wxJGNsdXN0ZXIsY2wyJGNsdXN0ZXIpKSkNCg0KcGFyKG1mY29sPWMoMSwzKSkNCg0KcGxvdChwY19jb3IkeFssMV0sIHBjX2NvciR4WywyXSwgY29sPWNsMSRjbHVzdGVyLCBwY2g9MTkpDQpwbG90KHBjX2NvciR4WywxXSwgcGNfY29yJHhbLDJdLCBjb2w9Y2wyJGNsdXN0ZXIsIHBjaD0xOSkNCnBsb3QocGNfY29yJHhbLDFdLCBwY19jb3IkeFssMl0sIGNvbD1hcy5udW1lcmljKHgkR2VucmUpLCBwY2g9MTkpDQoNCnBhcihtZmNvbD1jKDEsMikpDQpsaWJyYXJ5KCJlMTA3MSIpDQpzZXQuc2VlZCg0MikNCmNsMSA8LSBjbWVhbnMoeiwgNCkNCm1jb2xvciA8LSBjb2xvclJhbXAoYygiYmxhY2siLCAiZ3JlZW4iLCAiYmx1ZSIsICJyZWQiKSkNCmNvbCA8LSByZ2IobWNvbG9yKGNsMSRtZW1iZXJzaGlwWywxXSksIG1heD0yNTUpDQpwbG90KHBjX2NvciR4WywxXSwgcGNfY29yJHhbLDJdLCBwY2g9MTksIGNvbD1jb2wsIG1haW4gPSAnU29mdC1jbHVzdGVyaW5nIChmdXp6eSBjLW1lYW5zLCBrID0gNCknKQ0KDQpgYGANCmBgYHtyfQ0KDQpjbCAgPC0gZGlhbmEoeikNCmhjbCA8LSBjdXRyZWUoYXMuaGNsdXN0KGNsKSwgayA9IDQpDQpwYXIobWZyb3c9YygxLDEpKQ0KcGxvdChwY19jb3IkeFssMV0sIHBjX2NvciR4WywyXSwgY29sPWhjbCwgDQogICAgIHBjaD0xOSwgY2V4PTAuNSkNCg0KDQoNCg0KYGBgDQoNCmBgYHtyfQ0KDQpsaWJyYXJ5KCJwcm94eSIpDQoNCmQgPC0gYXMubWF0cml4KGRpc3QoeikpDQpoZWF0bWFwKGQpDQpzIDwtIHByX2Rpc3Qyc2ltaWwoZCkgIyB0aGUgZGFya2VyLCB0aGUgbW9yZSBzaW1pbGFyDQpoZWF0bWFwKHMpDQoNCmBgYA0KDQpgYGB7cn0NCg0KcGFyKG1mY29sPWMoMSwxKSkNCg0KZCA8LSBkaXN0KHopDQojIGhjbHVzdA0KY2wxIDwtIGhjbHVzdChkLCBtZXRob2QgPSAnd2FyZC5EMicpDQptZW1iIDwtIGN1dHJlZShjbDEsIDQpDQpwbG90KHBjX2NvciR4WywxXSwgcGNfY29yJHhbLDJdLCBjb2w9bWVtYikNCnBsb3QoY2wxLCBhbm4gPSBGKQ0KdGl0bGUoeGxhYiA9ICJXYXJkLkQyIiwgeWxhYiA9ICJIZWlnaHQiKQ0KYGBgDQoNCmBgYHtyfQ0KDQpsaWJyYXJ5KCJnZ3Bsb3QyIikNCmxpYnJhcnkoInRpYmJsZSIpDQoNCmdncGxvdChjbDEkaGVpZ2h0ICU+JQ0KYXNfdGliYmxlKCkgJT4lDQphZGRfY29sdW1uKGdyb3VwcyA9IGxlbmd0aChjbDEkaGVpZ2h0KToxKSAlPiUNCnJlbmFtZShoZWlnaHQ9dmFsdWUpLA0KYWVzKHg9Z3JvdXBzLCB5PWhlaWdodCkpICsNCmdlb21fcG9pbnQoKSArDQpnZW9tX2xpbmUoKQ0KDQpgYGANCmBgYHtyfQ0KDQpsaWJyYXJ5KCJtY2x1c3QiKQ0Kc2V0LnNlZWQoNDIpDQpjbCA8LSBNY2x1c3QoeikNCnN1bW1hcnkoY2wpDQpwYXIobWZyb3c9YygyLDIpKQ0KcGxvdChjbCwgIkJJQyIpDQpwbG90KHBjX2NvciR4WywxXSwgcGNfY29yJHhbLDJdLCBjb2wgPSBjbCRjbGFzc2lmaWNhdGlvbikNCkVNX2RlbnNpdHlfcGxvdCA8LSBwbG90KGNsLCAiZGVuc2l0eSIpDQoNCg0KYGBgDQoNCmBgYHtyfQ0KDQpwYXIobWZjb2w9YygyLDIpKQ0KDQpsaWJyYXJ5KCJmcGMiKQ0Kc2V0LnNlZWQoNDIpDQpjbCA8LSBkYnNjYW4oeiwgMC41LCBzY2FsZT1GLCBNaW5QdHMgPSA1KQ0KY29sIDwtIGMoJ2dyZXknLCByYWluYm93KG1heChjbCRjbHVzdGVyKSkpDQpwbG90KHBjX2NvciR4WywxXSwgcGNfY29yJHhbLDJdLCBwY2g9MTksIGNvbD1jb2xbMStjbCRjbHVzdGVyXSkNCg0Kc2V0LnNlZWQoNDIpDQpjbCA8LSBkYnNjYW4oeiwgMSwgc2NhbGU9RiwgTWluUHRzID0gNSkNCmNvbCA8LSBjKCdncmV5JywgcmFpbmJvdyhtYXgoY2wkY2x1c3RlcikpKQ0KcGxvdChwY19jb3IkeFssMV0sIHBjX2NvciR4WywyXSwgcGNoPTE5LCBjb2w9Y29sWzErY2wkY2x1c3Rlcl0pDQoNCnNldC5zZWVkKDQyKQ0KY2wgPC0gZGJzY2FuKHosIDEuNSwgc2NhbGU9RiwgTWluUHRzID0gNSkNCmNvbCA8LSBjKCdncmV5JywgcmFpbmJvdyhtYXgoY2wkY2x1c3RlcikpKQ0KcGxvdChwY19jb3IkeFssMV0sIHBjX2NvciR4WywyXSwgcGNoPTE5LCBjb2w9Y29sWzErY2wkY2x1c3Rlcl0pDQoNCnNldC5zZWVkKDQyKQ0KY2wgPC0gZGJzY2FuKHosIDIsIHNjYWxlPUYsIE1pblB0cyA9IDUpDQpjb2wgPC0gYygnZ3JleScsIHJhaW5ib3cobWF4KGNsJGNsdXN0ZXIpKSkNCnBsb3QocGNfY29yJHhbLDFdLCBwY19jb3IkeFssMl0sIHBjaD0xOSwgY29sPWNvbFsxK2NsJGNsdXN0ZXJdKQ0KDQpgYGANCg0KDQpgYGB7cn0NCg0KIyBNaXhlZCBjbHVzdGVyaW5nDQoNCg0KIyBoY2x1c3QNCmQgICAgPC0gZGlzdCh6KQ0Kc2V0LnNlZWQoNDIpDQpjbDEgIDwtIGhjbHVzdChkLCBtZXRob2Q9IndhcmQuRDIiKQ0KbWVtYiA8LSBjdXRyZWUoY2wxLCA0KQ0KIyBrbWVhbnMNCmdyb3VwbSAgPC0gYWdncmVnYXRlKHosIGxpc3QobWVtYiksIG1lYW4pDQpjZW50ZXJzIDwtIGNiaW5kKGdyb3VwbSRhY291c3RpY25lc3MsIA0KICAgICAgICAgICAgICAgICBncm91cG0kZGFuY2VhYmlsaXR5LCANCiAgICAgICAgICAgICAgICAgZ3JvdXBtJGVuZXJneSwgDQogICAgICAgICAgICAgICAgIGdyb3VwbSRpbnN0cnVtZW50YWxuZXNzLA0KICAgICAgICAgICAgICAgICBncm91cG0kbGl2ZW5lc3MsDQogICAgICAgICAgICAgICAgIGdyb3VwbSRsb3VkbmVzcywNCiAgICAgICAgICAgICAgICAgZ3JvdXBtJHNwZWVjaGluZXNzLA0KICAgICAgICAgICAgICAgICBncm91cG0kdGVtcG8sDQogICAgICAgICAgICAgICAgIGdyb3VwbSR2YWxlbmNlKQ0Kc2V0LnNlZWQoNDIpDQpjbDIgPC0ga21lYW5zKHosIGNlbnRlcnMgPSBjZW50ZXJzKQ0KIyBjb21wYXJlIHJlc3VsdHMgKGNvbmZ1c2lvbiBtYXRyaXgpDQp0YWJsZShtZW1iLCBjbDIkY2x1c3RlcikgIyByZXN1bHQ6IGstbWVhbnMgaGFzIGNsYXNzaWZpZWQgNCBvYnNlcnZhdGlvbnMgZGlmZmVyZW50bHkgdGhhbiBoY2x1c3QNCg0KDQpwYXIobWZjb2w9YygxLDIpKQ0KcGxvdChwY19jb3IkeFssMV0sIHBjX2NvciR4WywyXSwgY29sPWNsMiRjbHVzdGVyLA0KICAgICBtYWluPSJDbHVzdGVyIHByZWRpY3Rpb25zLCBtaXhlZCBjbHVzdGVyaW5nIiwgcGNoPTE5LCB4bGFiID0gJ1BDMScsIHlsYWIgPSAnUEMyJykNCg0KIyBwcm9qZWN0IGNsdXN0ZXIgY2VudGVycyB0byBQQ0EgZmVhdHVyZSBzcGFjZToNCg0KY2x1c3Rlcl9jZW50ZXJzX3BjYSA8LSBjbDIkY2VudGVycyAlKiUgcGNfY29yJHJvdGF0aW9uWywgMToyXQ0KcG9pbnRzKGNsdXN0ZXJfY2VudGVyc19wY2FbLDFdLCBjbHVzdGVyX2NlbnRlcnNfcGNhWywyXSwgY29sID0gJ21hZ2VudGEnLCBwY2ggPSAxMCwgY2V4ID0gMykNCg0KcGxvdChwY19jb3IkeFssMV0sIHBjX2NvciR4WywyXSwgY29sPWFzLm51bWVyaWMoeCRHZW5yZSksDQogICAgIG1haW49IlRydXRoIiwgcGNoPTE5LCB4bGFiID0gJ1BDMScsIHlsYWIgPSAnUEMyJykNCg0KYGBgDQpgYGB7cn0NCg0KbGlicmFyeSgiTmJDbHVzdCIpDQoNCiMgVG90YWwgdmFyaWFuY2UgZXhwbGFpbmVkDQp0dmUgPC0gcmVwKE5BLCAxNSkNCmZvciAoayBpbiAyOjE1KSB7DQogIGNsayA8LSBrbWVhbnMoeiwgaykNCiAgdHZlW2tdIDwtIDEtY2xrJHRvdC53aXRoaW5zcy9jbGskdG90c3MNCn0NCnBsb3QodHZlLCB0eXBlPSJiIikNCg0Kc2V0LnNlZWQoNDIpDQpOYkNsdXN0KHosIG1ldGhvZD0id2FyZC5EMiIsIGluZGV4PSJjaCIpICMgcmVzdWx0OiBrKj0yDQoNCnNldC5zZWVkKDQyKQ0KTmJDbHVzdCh6LCBtZXRob2Q9IndhcmQuRDIiKSAjIHJlc3VsdDogbWFqb3JpdHkgdm90ZSAoMTIpIGZvciBrKj0zLg0KDQpgYGANCg0KYGBge3J9DQoNCiMgU2lsaG91ZXR0ZSBtZXRob2QNCnNldC5zZWVkKDQyKQ0KZnZpel9uYmNsdXN0KHosIGttZWFucywgbWV0aG9kID0gInNpbGhvdWV0dGUiKSsNCiAgbGFicyhzdWJ0aXRsZSA9ICJTaWxob3VldHRlIG1ldGhvZCIpDQoNCmQgPC0gZGlzdCh6KQ0KY2wxIDwtIGhjbHVzdChkLCBtZXRob2Q9IndhcmQuRDIiKQ0KbWVtYjIgPC0gY3V0cmVlKGNsMSwgMikNCm1lbWIzIDwtIGN1dHJlZShjbDEsIDMpDQptZW1iNCA8LSBjdXRyZWUoY2wxLCA0KQ0KDQpsaWJyYXJ5KCJjbHVzdGVyIikNCnBhcihtZmNvbD1jKDIsMikpDQpwbG90KHBjX2NvciR4WywxXSwgcGNfY29yJHhbLDJdLCBjb2w9bWVtYjIpDQpzMiA8LSBzaWxob3VldHRlKG1lbWIyLCBkKQ0KcGxvdChzMiwgY29sPTE6MiwgYm9yZGVyPU5BKQ0KcGxvdChwY19jb3IkeFssMV0sIHBjX2NvciR4WywyXSwgY29sPW1lbWIzKQ0KczMgPC0gc2lsaG91ZXR0ZShtZW1iMywgZCkNCnBsb3QoczMsIGNvbD0xOjMsIGJvcmRlcj1OQSkNCg0KcGFyKG1mY29sPWMoMSwzKSkNCmNsdXNwbG90KHosIG1lbWIyLCBjb2wucD1tZW1iMikNCmNsdXNwbG90KHosIG1lbWIzLCBjb2wucD1tZW1iMykNCmNsdXNwbG90KHosIG1lbWI0LCBjb2wucD1tZW1iNCkNCg0KDQpgYGANCg0KYGBge3J9DQoNCmxpYnJhcnkoImNsdXN0ZXIiKQ0Kc2V0LnNlZWQoNDIpDQpjbDIgPC0gcGFtKHosIDQpDQprX2NsYXNzaWMgPC0gbmFtZXMoY2wyJGNsdXN0ZXJpbmdbY2wyJGNsdXN0ZXJpbmcgPT0gMV0pDQprX3RlY2hubyA8LSBuYW1lcyhjbDIkY2x1c3RlcmluZ1tjbDIkY2x1c3RlcmluZyA9PSAyXSkNCmtfcG9wIDwtIG5hbWVzKGNsMiRjbHVzdGVyaW5nW2NsMiRjbHVzdGVyaW5nID09IDNdKQ0Ka19oaXBob3AgPC0gbmFtZXMoY2wyJGNsdXN0ZXJpbmdbY2wyJGNsdXN0ZXJpbmcgPT0gNF0pDQoNCmNsMiRjbHVzdGVyaW5nW2tfdGVjaG5vXSA8LSA0DQpjbDIkY2x1c3RlcmluZ1trX3BvcF0gPC0gMw0KY2wyJGNsdXN0ZXJpbmdba19oaXBob3BdIDwtIDINCmNsMiRjbHVzdGVyaW5nW2tfY2xhc3NpY10gPC0gMQ0KDQp4JGNsdXN0ZXIgPC0gYXMuZmFjdG9yKGNsMiRjbHVzdGVyaW5nKQ0KDQpgYGANCg0KDQpgYGB7cn0NCg0KeCRBcnRpc3RfRm9sbG93ZXIgPC0geCRBcnRpc3RfRm9sbG93ZXIvMTAwMDAwMA0KDQp4JGZhXzEgPC0geCRmYWN0b3JfMQ0KeCRmYV8yIDwtIHgkZmFjdG9yXzINCngkZmFfMyA8LSB4JGZhY3Rvcl8zDQp4JGZhXzQgPC0geCRmYWN0b3JfNA0KDQoNCnBhcihtZnJvdz1jKDMsMykpDQpueCA8LSBjKCJmYV8xIiwgImZhXzIiLCAiZmFfMyIsICJmYV80IiwgInBjXzEiLCAicGNfMiIsICJBcnRpc3RfRm9sbG93ZXIiLCAiZGF5c19yZWxlYXNlX29yaWciLCAiVHJhY2tfUG9wdWxhcml0eSIpDQpmb3IgKGkgaW4gMTpsZW5ndGgobngpKSB7DQogIGxtaSA8LSBsbShhcy5mb3JtdWxhKHBhc3RlKCdBcnRpc3RfUG9wdWxhcml0eScsIG54W2ldLCBzZXA9In4iKSksIGRhdGE9eCkNCiAgc3VtbWFyeShsbWkpDQogIHBsb3QoeFssbnhbaV1dLCB4WywnQXJ0aXN0X1BvcHVsYXJpdHknXSwgeGxhYj1ueFtpXSwgeWxhYj0nQXJ0aXN0IFBvcHVsYXJpdHknLCBtYWluPXNwcmludGYoIlJeMj0lLjJmIiwgc3VtbWFyeShsbWkpJHIuc3F1YXJlZCkpDQogIGFibGluZShsbWksIGNvbD0icmVkIikNCn0NCg0KDQoNCmBgYA0KDQoNCmBgYHtyfQ0KDQptb2RlbDEgPC0gbG0oQXJ0aXN0X1BvcHVsYXJpdHkgfiBmYV8xICsgZmFfMiArIGZhXzMgKyBmYV80ICsgcGNfMSArIHBjXzIgKyBBcnRpc3RfRm9sbG93ZXIgKyBkYXlzX3JlbGVhc2Vfb3JpZywgZGF0YSA9IHgpDQoNCnN1bW1hcnkobW9kZWwxKQ0KDQojIGZhXzI6ICJZb3V0dWJlIg0KIyBmYV8zOiAiQXJ0aXN0IGNvbnRlbnQgc3VwcGx5Ig0KIyBmYV80OiAiQXJ0aXN0IGxvbmctdGVybSBhY3Rpdml0eSINCiMgZmFfMTogIk9uZSBoaXQgd29uZGVyIg0KDQojIHBjXzEgKGJldHdlZW4gY2xhc3NpYyBhbmQgUG9wLCBIaXAgSG9wLCBUZWNobm8pOiBzdHJvbmcgbmVnYXRpdmUgbG9hZGluZ3M6IGFjb3VzdGljbmVzcyBhbmQgaW5zdHJ1bWVudGFsbmVzcywgc3Ryb25nIHBvc2l0aXZlIGxvYWRpbmdzOiBkYW5jZWFiaWxpdHksIGVuZXJneSBhbmQgbG91ZG5lc3MNCiMgcGNfMiAoYmV0d2VlbiBUZWNobm8gYW5kIFBvcCwgSGlwIEhvcCk6IHN0cm9uZyBuZWdhdGl2ZSBsb2FkaW5nczogaW5zdHJ1bWVudGFsbmVzcyBhbmQgdGVtcG8sIHN0cm9uZyBwb3NpdGl2ZSBsb2FkaW5nczogc3BlZWNoaW5lc3MgYW5kIHZhbGVuY2UNCg0KdmlmKG1vZGVsMSkgIyBmYV8xLCBmYV8zLCBmYV80LCBwY18xIG1pbGQgY29sbGluZWFyaXR5DQpsaWJyYXJ5KCJwZXJ0dXJiIikgIyBjb25kaXRpb24gaW5kZXgNCmNvbGxkaWFnKG1vZGVsMSkNCg0KIyBJIHJlbW92ZSBzb21lIHZhcmlhYmxlcyBhY2NvcmRpbmcgdG8gaW5zaWduaWZpY2FuY2UgKGZhXzEsIGZhXzQpDQoNCm1vZGVsMiA8LSBsbShBcnRpc3RfUG9wdWxhcml0eSB+IGZhXzIgKyBmYV8zICsgcGNfMSArIHBjXzIgKyBBcnRpc3RfRm9sbG93ZXIgKyBkYXlzX3JlbGVhc2Vfb3JpZywgZGF0YSA9IHgpDQojIHN1bW1hcnkobW9kZWwyKQ0KdmlmKG1vZGVsMikNCg0Kc2VsZWN0LmNvbHMgPC0gYygiQXJ0aXN0X1BvcHVsYXJpdHkiLCAiZmFfMiIsICJmYV8zIiwgInBjXzEiLCAicGNfMiIsICJBcnRpc3RfRm9sbG93ZXIiLCAiZGF5c19yZWxlYXNlX29yaWciKQ0KDQp6IDwtIGFzLmRhdGEuZnJhbWUoc2NhbGUoZHBseXI6OnNlbGVjdCh4LCBzZWxlY3QuY29scykpKQ0KDQptb2RlbDJfeiA8LSBsbShBcnRpc3RfUG9wdWxhcml0eSB+IGZhXzIgKyBmYV8zICsgcGNfMSArIHBjXzIgKyBBcnRpc3RfRm9sbG93ZXIgKyBkYXlzX3JlbGVhc2Vfb3JpZywgZGF0YSA9IHopDQojIHN1bW1hcnkobW9kZWwyX3opDQp2aWYobW9kZWwyX3opDQptb2RlbDJfeiRjb2VmZmljaWVudHNeMg0Kc3VtKG1vZGVsMl96JGNvZWZmaWNpZW50c14yKSAjIDYwJSBpcyB0aGUgcHJvcG9ydGlvbiBpbiB0aGUgdmFyaWFuY2Ugb2YgeSBleHBsYWluZWQgYnkgWCBhbmQgNDAlIG9mIHRoZSB2YXJpYW5jZSBvZiB5IGlzIGR1ZSB0byB0aGUgdmFyaWFuY2Ugb2YgdGhlIHJlc2lkdWFscyBvdXRzaWRlIG9mIHRoZSBtb2RlbCBJRkYgdGhlcmUgd2FzIHplcm8gbXVsdGljb2xsaW5lYXJpdHkuIEluIHRlcm1zIG9mIHJlbGV2YW5jZSwgMzIlIG9mIHZhcmlhbmNlIGluIHkgaXMgZXhwbGFpbmVkIGJ5IHBjXzIsIDEwJSBieSBBcnRpc3RfRm9sbG93ZXIsIDglIGJ5IGZhXzMNCnNvcnQocm91bmQoKG1vZGVsMl96JGNvZWZmaWNpZW50cyleMiwgZGlnaXRzID0gNCksIGRlY3JlYXNpbmcgPSBUKQ0KDQoNCmBgYA0KDQoNCmBgYHtyfQ0KIyBRdWFkcmF0aWMgc3BlY2lmaWNhdGlvbiBzZWVtcyBhcHByb3ByaWF0ZToNCg0KIyB2YXJpYWJsZXMgd2l0aG91dCBhbmQgd2l0aCBxdWFkcmF0aWMgdGVybXMgKGZhXzIsIHBjXzIsIEFydGlzdF9Gb2xsb3dlcikNCg0KcGFyKG1mY29sPWMoMiwzKSkNCg0KbG1fZmEyIDwtIGxtKEFydGlzdF9Qb3B1bGFyaXR5IH4gZmFfMiwgZGF0YT14KQ0KcGxvdCh4WywnZmFfMiddLCB4WywnQXJ0aXN0X1BvcHVsYXJpdHknXSwgeGxhYj0nZmFfMicsIG1haW49c3ByaW50ZigiUl4yPSUuMmYiLCBzdW1tYXJ5KGxtX2ZhMikkci5zcXVhcmVkKSwNCiAgICAgeWxpbSA9IGMobWluKG1pbihmaXR0ZWQobG1fZmEyKSksIG1pbih4JEFydGlzdF9Qb3B1bGFyaXR5KSksIG1heChtYXgoZml0dGVkKGxtX2ZhMikpLCBtYXgoeCRBcnRpc3RfUG9wdWxhcml0eSkpKSwgeWxhYiA9ICdBcnRpc3RfUG9wdWxhcml0eScpDQphYmxpbmUobG1fZmEyLCBjb2w9InJlZCIpDQoNCmxtcV9mYTIgPC0gbG0oQXJ0aXN0X1BvcHVsYXJpdHkgfiBwb2x5KGZhXzIsIDIpLCBkYXRhPXgpDQpvIDwtIG9yZGVyKHgkZmFfMikNCnBsb3QoeFssJ2ZhXzInXSwgeFssJ0FydGlzdF9Qb3B1bGFyaXR5J10sIHhsYWI9ICdmYV8yICsgZmFfMl4yJywgbWFpbj1zcHJpbnRmKCJSXjI9JS4yZiIsIHN1bW1hcnkobG1xX2ZhMikkci5zcXVhcmVkKSwNCiAgICAgeWxpbSA9IGMobWluKG1pbihmaXR0ZWQobG1xX2ZhMikpLCBtaW4oeCRBcnRpc3RfUG9wdWxhcml0eSkpLCBtYXgobWF4KGZpdHRlZChsbXFfZmEyKSksIG1heCh4JEFydGlzdF9Qb3B1bGFyaXR5KSkpLCB5bGFiID0gJ0FydGlzdF9Qb3B1bGFyaXR5JykNCmxpbmVzKHgkZmFfMltvXSwgZml0dGVkKGxtcV9mYTIpW29dLCBjb2w9InJlZCIpDQoNCmxtX2ZvbGxvd2VyIDwtIGxtKEFydGlzdF9Qb3B1bGFyaXR5IH4gQXJ0aXN0X0ZvbGxvd2VyLCBkYXRhPXgpDQpwbG90KHhbLCdBcnRpc3RfRm9sbG93ZXInXSwgeFssJ0FydGlzdF9Qb3B1bGFyaXR5J10sIHhsYWI9J0FydGlzdF9Gb2xsb3dlcicsIG1haW49c3ByaW50ZigiUl4yPSUuMmYiLCBzdW1tYXJ5KGxtX2ZvbGxvd2VyKSRyLnNxdWFyZWQpLA0KICAgICB5bGltID0gYyhtaW4obWluKGZpdHRlZChsbV9mb2xsb3dlcikpLCBtaW4oeCRBcnRpc3RfUG9wdWxhcml0eSkpLCBtYXgobWF4KGZpdHRlZChsbV9mb2xsb3dlcikpLCBtYXgoeCRBcnRpc3RfUG9wdWxhcml0eSkpKSwgeWxhYiA9ICdBcnRpc3RfUG9wdWxhcml0eScpDQphYmxpbmUobG1fZm9sbG93ZXIsIGNvbD0icmVkIikNCg0KbG1xX2ZvbGxvd2VyIDwtIGxtKEFydGlzdF9Qb3B1bGFyaXR5IH4gcG9seShBcnRpc3RfRm9sbG93ZXIsIDIpLCBkYXRhPXgpDQpvIDwtIG9yZGVyKHgkQXJ0aXN0X0ZvbGxvd2VyKQ0KcGxvdCh4WywnQXJ0aXN0X0ZvbGxvd2VyJ10sIHhbLCdBcnRpc3RfUG9wdWxhcml0eSddLCB4bGFiPSAnQXJ0aXN0X0ZvbGxvd2VyICsgQXJ0aXN0X0ZvbGxvd2VyXjInLCBtYWluPXNwcmludGYoIlJeMj0lLjJmIiwgc3VtbWFyeShsbXFfZm9sbG93ZXIpJHIuc3F1YXJlZCksDQogICAgIHlsaW0gPSBjKG1pbihtaW4oZml0dGVkKGxtcV9mb2xsb3dlcikpLCBtaW4oeCRBcnRpc3RfUG9wdWxhcml0eSkpLCBtYXgobWF4KGZpdHRlZChsbXFfZm9sbG93ZXIpKSwgbWF4KHgkQXJ0aXN0X1BvcHVsYXJpdHkpKSksIHlsYWIgPSAnQXJ0aXN0X1BvcHVsYXJpdHknKQ0KbGluZXMoeCRBcnRpc3RfRm9sbG93ZXJbb10sIGZpdHRlZChsbXFfZm9sbG93ZXIpW29dLCBjb2w9InJlZCIpDQoNCmxtX3BjMiA8LSBsbShBcnRpc3RfUG9wdWxhcml0eSB+IHBjXzIsIGRhdGE9eCkNCnBsb3QoeFssJ3BjXzInXSwgeFssJ0FydGlzdF9Qb3B1bGFyaXR5J10sIHhsYWI9J3BjXzInLCBtYWluPXNwcmludGYoIlJeMj0lLjJmIiwgc3VtbWFyeShsbV9wYzIpJHIuc3F1YXJlZCksDQogICAgIHlsaW0gPSBjKG1pbihtaW4oZml0dGVkKGxtX3BjMikpLCBtaW4oeCRBcnRpc3RfUG9wdWxhcml0eSkpLCBtYXgobWF4KGZpdHRlZChsbV9wYzIpKSwgbWF4KHgkQXJ0aXN0X1BvcHVsYXJpdHkpKSksIHlsYWIgPSAnQXJ0aXN0X1BvcHVsYXJpdHknKQ0KYWJsaW5lKGxtX3BjMiwgY29sPSJyZWQiKQ0KDQpsbXFfcGMyIDwtIGxtKEFydGlzdF9Qb3B1bGFyaXR5IH4gcG9seShwY18yLCAyKSwgZGF0YT14KQ0KbyA8LSBvcmRlcih4JHBjXzIpDQpwbG90KHhbLCdwY18yJ10sIHhbLCdBcnRpc3RfUG9wdWxhcml0eSddLCB4bGFiPSAncGNfMiArIHBjXzJeMicsIG1haW49c3ByaW50ZigiUl4yPSUuMmYiLCBzdW1tYXJ5KGxtcV9wYzIpJHIuc3F1YXJlZCksDQogICAgIHlsaW0gPSBjKG1pbihtaW4oZml0dGVkKGxtcV9wYzIpKSwgbWluKHgkQXJ0aXN0X1BvcHVsYXJpdHkpKSwgbWF4KG1heChmaXR0ZWQobG1xX3BjMikpLCBtYXgoeCRBcnRpc3RfUG9wdWxhcml0eSkpKSwgeWxhYiA9ICdBcnRpc3RfUG9wdWxhcml0eScpDQpsaW5lcyh4JHBjXzJbb10sIGZpdHRlZChsbXFfcGMyKVtvXSwgY29sPSJyZWQiKQ0KDQoNCmBgYA0KDQoNCmBgYHtyfQ0KDQojIFJlc2lkdWFscyBub3JtYWxpdHkNCg0Ka3MudGVzdChtb2RlbDEkcmVzaWR1YWxzLCAicG5vcm0iLCBtZWFuID0gbWVhbihtb2RlbDEkcmVzaWR1YWxzKSwgc2Q9c2QobW9kZWwxJHJlc2lkdWFscykpJHN0YXRpc3RpYw0KMS4zNTgxIC8gc3FydChsZW5ndGgobW9kZWwxJHJlc2lkdWFscykpDQoNCmtzLnRlc3QobW9kZWwyJHJlc2lkdWFscywgInBub3JtIiwgbWVhbiA9IG1lYW4obW9kZWwyJHJlc2lkdWFscyksIHNkPXNkKG1vZGVsMiRyZXNpZHVhbHMpKSRzdGF0aXN0aWMNCjEuMzU4MSAvIHNxcnQobGVuZ3RoKG1vZGVsMiRyZXNpZHVhbHMpKQ0KDQpwYXIobWZjb2w9YygyLDIpKQ0KcGxvdChtb2RlbDIpDQoNCnBhcihtZmNvbD1jKDIsMykpDQpueCA8LSBuYW1lcyhtb2RlbDIkbW9kZWwpDQpmb3IgKGkgaW4gMjpsZW5ndGgobW9kZWwyJG1vZGVsKSkgew0KICBwbG90KG1vZGVsMiRtb2RlbFssaV0sIHJlc2lkdWFscyhtb2RlbDIpLCB4bGFiPW54W2ldKQ0KICBsaW5lcyhsb3dlc3MobW9kZWwyJG1vZGVsWyxpXSwgcmVzaWR1YWxzKG1vZGVsMikpLCBjb2wgPSAicmVkIikNCiAgYWJsaW5lKGggPSAwLCBjb2wgPSAiYmxhY2siLCBsdHkgPSAyKQ0KfQ0KDQpwYXIobWZjb2w9YygxLDEpKQ0KcGxvdChtb2RlbDIkZml0dGVkLnZhbHVlcywgcmVzaWR1YWxzKG1vZGVsMikpDQpsaW5lcyhsb3dlc3MobW9kZWwyJGZpdHRlZC52YWx1ZXMsIHJlc2lkdWFscyhtb2RlbDIpKSwgY29sID0gInJlZCIpDQphYmxpbmUoaCA9IDAsIGNvbCA9ICJibGFjayIsIGx0eSA9IDIpDQoNCiMgcmVzaWR1YWxzIHZzLiBwcmVkaWN0b3JzIGFuZCBmaXR0ZWQgdmFsdWVzIHN1Z2dlc3RzIG5vbmxpbmVhcml0eSBvZiB0aGUgcmVncmVzc2lvbiBmdW5jdGlvbg0KDQpgYGANCg0KYGBge3J9DQoNCnBhcihtZmNvbD1jKDIsMykpDQpueCA8LSBuYW1lcyhtb2RlbDIkbW9kZWwpDQpmb3IgKGkgaW4gMjpsZW5ndGgobW9kZWwyJG1vZGVsKSkgew0KICBwbG90KG1vZGVsMiRtb2RlbFssaV0sIHJzdGFuZGFyZChtb2RlbDIpLCB4bGFiPW54W2ldKQ0KICBsaW5lcyhsb3dlc3MobW9kZWwyJG1vZGVsWyxpXSwgcnN0YW5kYXJkKG1vZGVsMikpLCBjb2wgPSAicmVkIikNCiAgIyB3ZW5uIExpbmllIHZvbiAwIGFid2VpY2h0OiBSZXNpZHVlbiBzaW5kIGJpYXNlZCwgc3ByaWNoIGRlcmVuIE1pdHRlbHdlcnQgbmljaHQgMA0KICBhYmxpbmUoaCA9IDAsIGNvbCA9ICJibGFjayIsIGx0eSA9IDIpDQp9DQoNCiMgc3RhbmRhcmRpemVkIHJlc2lkdWFscyB2cy4gZml0dGVkIHZhbHVlcw0KDQpwYXIobWZjb2w9YygxLDEpKQ0KcGxvdChtb2RlbDIkZml0dGVkLnZhbHVlcywgcnN0YW5kYXJkKG1vZGVsMiksIG1haW4gPSAnU3RhbmRhcmRpemVkIHJlc2lkdWFscyB2cy4gZml0dGVkIHZhbHVlcycpDQpsaW5lcyhsb3dlc3MobW9kZWwyJGZpdHRlZC52YWx1ZXMsIHJzdGFuZGFyZChtb2RlbDIpKSwgY29sID0gInJlZCIpDQphYmxpbmUoaCA9IDAsIGNvbCA9ICJibGFjayIsIGx0eSA9IDIpDQoNCg0KYGBgDQoNCmBgYHtyfQ0KDQojIEJyZXVzY2gtUGFnYW4gVGVzdCArIER1cmJpbi1XYXRzb24gVGVzdCAvIHNwaGVyaWNhbCBlcnJvcnMgKGkuZS4gaWlkKQ0KIyBtb2RlbDIgdXNlcyB1bnNjYWxlZCBkYXRhDQphdXhpbGlhcnlfcmVnIDwtIGxtKG1vZGVsMiRyZXNpZHVhbHNeMiB+IGZhXzIgKyBmYV8zICsgcGNfMSArIHBjXzIgKyBBcnRpc3RfRm9sbG93ZXIgKyBkYXlzX3JlbGVhc2Vfb3JpZywgZGF0YSA9IHgpDQpzdW1tYXJ5KGF1eGlsaWFyeV9yZWcpDQojIFJeMjogMC4xMDY3DQojIE4gPSAxOTYsIE4gPSBucm93KHgpIA0KIyB0ZXN0IHN0YXQ6IA0Kc3VtbWFyeShhdXhpbGlhcnlfcmVnKSRyLnNxdWFyZSAqIG5yb3coeCkNCiMgdGVzdCBzdGF0OiAyMC45MDQ5Nw0KcCA8LSBuY29sKG1vZGVsMiRtb2RlbCktMQ0KcWNoaXNxKC45NSwgZGYgPSBwKSAjIGRmID0gbnVtYmVyIG9mIHBhcmFtZXRlcnMgcCAod2l0aG91dCB0aGUgY29uc3RhbnQhKSwgYWNjLiB0byBLbGlua2U6IHEgPSA1Kig1KzMpLzIgV1RGPw0KIyBwX3ZhbHVlID0gUCh6ID4gMTIuNTkxNTkpID0gMS1wY2hpc3EoMjAuOTA0OTcsIHA9NikgPSAwLjAwMTkwODE0NiA8IDAuMDUgLCAgcmVqZWN0IEgwIG9mIGhvbW9za2VkYXN0aWNpdHkgYW5kIGNvbmNsdWRlIHRoZXJlIGlzIGhldGVyb3NrZWRhc3RpY2l0eQ0KMS1wY2hpc3EoMjAuOTA0OTcsIHApDQoNCmxpYnJhcnkoImxtdGVzdCIpDQpicHRlc3QobW9kZWwyKQ0KIyB0ZXN0IHN0YXRpc3RpYyBuKlJeMl9hdXggPiBYXjJfY3JpdCA9IHFjaGlzcSguOTUsIGRmID0gNSksIGhlbmNlIHJlamVjdCBIMCBhbmQgY29uY2x1ZGUgdGhhdCB0aGVyZSdzDQojIGhldGVyb3NrZWRhc3RpY2l0eSBhbmQgY29uc3RhbmN5IG9mIGVycm9yIHZhcmlhbmNlIGRvZXMgbm90IGhvbGQgLS0+IGluZmVyZW5jZSBpbnZhbGlkYXRlZA0KDQpkd3Rlc3QobW9kZWwyKQ0KIyBEdXJiaW4gV2F0c29uIHRlc3QgY29uZmlybXMgdGhhdCB0aGUgZXJyb3IgdmFyaWFuY2UtY292YXJpYW5jZSBpcyBkaWFnb25hbCwgaS5lLiB2YXIoZXBzIHwgWCkgPSAgXE9tZWdhICogSQ0KIyBmYWlsIHRvIHJlamVjdCBIMDogcmhvID0gMCAoYWx0ZXJuYXRpdmUgaHlwb3RoZXNpcyBpcyB0aGF0IHRoZXJlIGlzIGF1dG9jb3JyZWxhdGlvbiBpbiB0aGUgZXJyb3JzKQ0KDQoNCg0KYGBgDQpgYGB7cn0NCiMgb3V0bGllciBkZXRlY3Rpb24gLyBsZXZlcmFnZQ0KDQpwYXIobWZjb2w9YygxLDIpKQ0KcGxvdChoYXR2YWx1ZXMobW9kZWwyKSwgcGNoPTE5LCBtYWluPSJMZXZlcmFnZSIsIGNleD0wLjUpDQpuIDwtIG5yb3cobW9kZWwyJG1vZGVsKQ0KcCA8LSBuY29sKG1vZGVsMiRtb2RlbCktMQ0KYWJsaW5lKGg9KDE6MykqKHArMSkvbiwgY29sPWMoImJsYWNrIiwgImRhcmtyZWQiLCAicmVkIikpDQoNCnBsb3QoY29va3MuZGlzdGFuY2UobW9kZWwyKSwgcGNoPTE5LCBtYWluPSJDb29rJ3MgZGlzdGFuY2VzIiwNCiAgICAgY2V4PTAuNSkNCm4gPC0gbnJvdyhtb2RlbDIkbW9kZWwpDQpwIDwtIG5jb2wobW9kZWwyJG1vZGVsKS0xDQphYmxpbmUoaD00L24sIGNvbD0icmVkIikNCg0Kb3V0bGllcl9pbmRleF9sZXZlcmFnZSA8LSBhcy5udW1lcmljKHJvd25hbWVzKHhbaGF0dmFsdWVzKG1vZGVsMikgPiAzKihwKzEpL24sIF0pKQ0Kb3V0bGllcl9pbmRleF9maW5hbCA8LSBvdXRsaWVyX2luZGV4X2xldmVyYWdlDQpsZW5ndGgob3V0bGllcl9pbmRleF9sZXZlcmFnZSkNCiMgVGhlcmUgYXJlIDEwIG91dGxpZXJzIHRoYXQgbWlnaHQgYmUgaW5mbHVlbnRpYWwgb24gdGhlIHJlZ3Jlc3Npb24NCnhbb3V0bGllcl9pbmRleF9sZXZlcmFnZSwgYygiVHJhY2tfVGl0bGUiLCJUcmFja19BcnRpc3QiLCAiQXJ0aXN0X1BvcHVsYXJpdHkiLCAiQXJ0aXN0X0ZvbGxvd2VyIiwgInZpZXdzQ291bnQiLCAicGNfMSIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgInBjXzIiKV0NCg0KIyBSdWxlIG9mIHRodW1iIENvb2sncyBkaXN0YW5jZQ0KIyBtb3ZlbWVudCBvZiByZWdyZXNzaW9uIGNvZWZmaWNpZW50cyBhbGwgdG9nZXRoZXIgaWYgaXRoIG9ic2VydmF0aW9uIGlzIGV4Y2x1ZGVkDQpvdXRsaWVyX2luZGV4X2Nvb2tzZCA8LSBhcy5udW1lcmljKHJvd25hbWVzKHhbY29va3MuZGlzdGFuY2UobW9kZWwyKSA+IDMqKHArMSkvbiwgXSkpDQpsZW5ndGgob3V0bGllcl9pbmRleF9jb29rc2QpDQp4W291dGxpZXJfaW5kZXhfY29va3NkLCBjKCJUcmFja19UaXRsZSIsIlRyYWNrX0FydGlzdCIsICJBcnRpc3RfUG9wdWxhcml0eSIsICJBcnRpc3RfRm9sbG93ZXIiLCAidmlld3NDb3VudCIsICJwY18xIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAicGNfMiIpXQ0KDQpgYGANCg0KYGBge3J9DQoNCiMgZGlmZmVyZW5jZXMNCg0KIyBTREJFVEENCg0KU0RCRVRBIDwtIGRmYmV0YXMobW9kZWwyKQ0KbiA8LSBucm93KG1vZGVsMiRtb2RlbCkNCnBhcihtZmNvbD1jKDIsMykpDQpwbG90KFNEQkVUQVssICdmYV8yJ10sIG1haW49ImZhXzIiLCBwY2g9MTkpDQphYmxpbmUoaD1jKC0yLDIpL3NxcnQobiksIGNvbD0icmVkIikNCnBsb3QoU0RCRVRBWywgJ2ZhXzMnXSwgbWFpbj0iZmFfMyIsIHBjaD0xOSkNCmFibGluZShoPWMoLTIsMikvc3FydChuKSwgY29sPSJyZWQiKQ0KcGxvdChTREJFVEFbLCAncGNfMSddLCBtYWluPSJwY18xIiwgcGNoPTE5KQ0KYWJsaW5lKGg9YygtMiwyKS9zcXJ0KG4pLCBjb2w9InJlZCIpDQpwbG90KFNEQkVUQVssICdwY18yJ10sIG1haW49InBjXzIiLCBwY2g9MTkpDQphYmxpbmUoaD1jKC0yLDIpL3NxcnQobiksIGNvbD0icmVkIikNCnBsb3QoU0RCRVRBWywgJ0FydGlzdF9Gb2xsb3dlciddLCBtYWluPSJBcnRpc3RfRm9sbG93ZXIiLCBwY2g9MTkpDQphYmxpbmUoaD1jKC0yLDIpL3NxcnQobiksIGNvbD0icmVkIikNCnBsb3QoU0RCRVRBWywgJ2RheXNfcmVsZWFzZV9vcmlnJ10sIG1haW49ImRheXNfcmVsZWFzZV9vcmlnIiwgcGNoPTE5KQ0KYWJsaW5lKGg9YygtMiwyKS9zcXJ0KG4pLCBjb2w9InJlZCIpDQoNCiMgU0RGSVRTDQpwYXIobWZjb2w9YygxLDEpKQ0KU0RGSVRTIDwtIGRmZml0cyhtb2RlbDIpDQpwbG90KFNERklUUywgcGNoPTE5KQ0KYWJsaW5lKGg9YygtMSwxKSwgY29sPSJyZWQiKQ0KbiA8LSBucm93KG1vZGVsMiRtb2RlbCkNCnAgPC0gbmNvbChtb2RlbDIkbW9kZWwpLTENCmFibGluZShoPWMoLTIsMikqc3FydChwL24pLCBjb2w9ImRhcmtyZWQiKQ0KDQojIEFzIHRoaXMgaXMgYSBzbWFsbCBkYXRhIHNldCwgaXQgaXMgc3VmZmljaWVudCB0byBhbmFseXplIHxcZGVsdGEqeV9pfCA+IDEsIGluIHRoaXMgY2FzZSB0d28gb2JzZXJ2YXRpb25zIGFyZSBpZGVudGlmaWVkDQojIG9mIGV4Y2VlZGluZyB0aGlzIGxpbWl0LiBUaGV5IGFyZQ0KDQpvdXRsaWVyX2luZGV4X3NkZml0cyA8LSBhcy5udW1lcmljKG5hbWVzKHdoaWNoKGFicyhTREZJVFMpID4gMSkpKSANCg0KeFtvdXRsaWVyX2luZGV4X3NkZml0cywgYygiVHJhY2tfVGl0bGUiLCJUcmFja19BcnRpc3QiLCAiQXJ0aXN0X1BvcHVsYXJpdHkiLCAiQXJ0aXN0X0ZvbGxvd2VyIiwgInZpZXdzQ291bnQiLCAicGNfMSIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICJwY18yIildDQoNCmBgYA0KDQoNCmBgYHtyfQ0KIyBBZGQgcXVhZHJhdGljIHRlcm1zIGZvciBmYV8yLCBBcnRpc3RfRm9sbG93ZXIgYW5kIHBjXzIgZmlyc3QgYmVmb3JlIGV4Y2x1ZGluZyBvdXRsaWVycyENCg0KeCRmYV8yc3EgPC0geCRmYV8yXjINCngkcGNfMnNxIDwtIHgkcGNfMl4yDQp4JEFydGlzdF9Gb2xsb3dlcnNxIDwtIHgkQXJ0aXN0X0ZvbGxvd2VyXjINCiMgbW9kZWwyOiBsbShBcnRpc3RfUG9wdWxhcml0eSB+IGZhXzIgKyBmYV8zICsgcGNfMSArIHBjXzIgKyBBcnRpc3RfRm9sbG93ZXIgKyBkYXlzX3JlbGVhc2Vfb3JpZywgZGF0YSA9IHgpDQoNCg0KbW9kZWwzIDwtIGxtKEFydGlzdF9Qb3B1bGFyaXR5IH4gZmFfMiArIGZhXzJzcSArIGZhXzMgKyBwY18xICsgcGNfMiArIHBjXzJzcSArIEFydGlzdF9Gb2xsb3dlciArIEFydGlzdF9Gb2xsb3dlcnNxICsgZGF5c19yZWxlYXNlX29yaWcsIGRhdGEgPSB4KQ0Kc3VtbWFyeShtb2RlbDMpDQp2aWYobW9kZWwzKQ0KYGBgDQoNCmBgYHtyfQ0KDQprcy50ZXN0KG1vZGVsMyRyZXNpZHVhbHMsICJwbm9ybSIsIG1lYW4gPSBtZWFuKG1vZGVsMyRyZXNpZHVhbHMpLCBzZD1zZChtb2RlbDMkcmVzaWR1YWxzKSkkc3RhdGlzdGljDQoxLjM1ODEgLyBzcXJ0KGxlbmd0aChtb2RlbDMkcmVzaWR1YWxzKSkNCg0KDQpwYXIobWZjb2w9YygyLDIpKQ0KcGxvdChtb2RlbDIpDQoNCnBhcihtZmNvbD1jKDMsMykpDQpueCA8LSBuYW1lcyhtb2RlbDMkbW9kZWwpDQpmb3IgKGkgaW4gMjpsZW5ndGgobW9kZWwzJG1vZGVsKSkgew0KICBwbG90KG1vZGVsMyRtb2RlbFssaV0sIHJlc2lkdWFscyhtb2RlbDMpLCB4bGFiPW54W2ldKQ0KICBsaW5lcyhsb3dlc3MobW9kZWwzJG1vZGVsWyxpXSwgcmVzaWR1YWxzKG1vZGVsMykpLCBjb2wgPSAicmVkIikNCiAgYWJsaW5lKGggPSAwLCBjb2wgPSAiYmxhY2siLCBsdHkgPSAyKQ0KfQ0KDQpwYXIobWZjb2w9YygxLDEpKQ0KcGxvdChtb2RlbDMkZml0dGVkLnZhbHVlcywgcmVzaWR1YWxzKG1vZGVsMykpDQpsaW5lcyhsb3dlc3MobW9kZWwzJGZpdHRlZC52YWx1ZXMsIHJlc2lkdWFscyhtb2RlbDMpKSwgY29sID0gInJlZCIpDQphYmxpbmUoaCA9IDAsIGNvbCA9ICJibGFjayIsIGx0eSA9IDIpDQoNCiMgcmVzaWR1YWxzIHZzLiBwcmVkaWN0b3JzIGFuZCBmaXR0ZWQgdmFsdWVzIHN1Z2dlc3RzIGFjY291bnRpbmcgZm9yIG5vbmxpbmVhcml0eSBvZiB0aGUgcmVncmVzc2lvbiBmdW5jdGlvbiBoYXMgaW1wcm92ZWQNCg0KcGFyKG1mY29sPWMoMywzKSkNCm54IDwtIG5hbWVzKG1vZGVsMyRtb2RlbCkNCmZvciAoaSBpbiAyOmxlbmd0aChtb2RlbDMkbW9kZWwpKSB7DQogIHBsb3QobW9kZWwzJG1vZGVsWyxpXSwgcnN0YW5kYXJkKG1vZGVsMyksIHhsYWI9bnhbaV0pDQogIGxpbmVzKGxvd2Vzcyhtb2RlbDMkbW9kZWxbLGldLCByc3RhbmRhcmQobW9kZWwzKSksIGNvbCA9ICJyZWQiKQ0KICAjIHdlbm4gTGluaWUgdm9uIDAgYWJ3ZWljaHQ6IFJlc2lkdWVuIHNpbmQgYmlhc2VkLCBzcHJpY2ggZGVyZW4gTWl0dGVsd2VydCBuaWNodCAwDQogIGFibGluZShoID0gMCwgY29sID0gImJsYWNrIiwgbHR5ID0gMikNCn0NCg0KIyBzdGFuZGFyZGl6ZWQgcmVzaWR1YWxzIHZzLiBmaXR0ZWQgdmFsdWVzDQoNCnBhcihtZmNvbD1jKDEsMSkpDQpwbG90KG1vZGVsMyRmaXR0ZWQudmFsdWVzLCByc3RhbmRhcmQobW9kZWwzKSwgbWFpbiA9ICdTdGFuZGFyZGl6ZWQgcmVzaWR1YWxzIHZzLiBmaXR0ZWQgdmFsdWVzJykNCmxpbmVzKGxvd2Vzcyhtb2RlbDMkZml0dGVkLnZhbHVlcywgcnN0YW5kYXJkKG1vZGVsMykpLCBjb2wgPSAicmVkIikNCmFibGluZShoID0gMCwgY29sID0gImJsYWNrIiwgbHR5ID0gMikNCg0KIyBCcmV1c2NoLVBhZ2FuIFRlc3QgKyBEdXJiaW4tV2F0c29uIFRlc3QgLyBzcGhlcmljYWwgZXJyb3JzIChpLmUuIGlpZCkNCiMgbW9kZWwyIHVzZXMgdW5zY2FsZWQgZGF0YQ0KYXV4aWxpYXJ5X3JlZyA8LSBsbShtb2RlbDMkcmVzaWR1YWxzXjIgfiBmYV8yICsgZmFfMnNxICsgZmFfMyArIHBjXzEgKyBwY18yICsgcGNfMnNxICsgQXJ0aXN0X0ZvbGxvd2VyICsgQXJ0aXN0X0ZvbGxvd2Vyc3EgKyBkYXlzX3JlbGVhc2Vfb3JpZywgZGF0YSA9IHgpDQpzdW1tYXJ5KGF1eGlsaWFyeV9yZWcpDQojIFJeMjogMC4wNjc1OA0KIyBOID0gMTk2LCBOID0gbnJvdyh4KSANCiMgdGVzdCBzdGF0OiANCnN1bW1hcnkoYXV4aWxpYXJ5X3JlZykkci5zcXVhcmUgKiBucm93KHgpDQojIHRlc3Qgc3RhdDogMTMuMjQ0OTkNCnAgPC0gbmNvbChtb2RlbDMkbW9kZWwpLTENCnFjaGlzcSguOTUsIGRmID0gcCkgIyBkZiA9IG51bWJlciBvZiBwYXJhbWV0ZXJzIHAgKHdpdGhvdXQgdGhlIGNvbnN0YW50ISksIGFjYy4gdG8gS2xpbmtlOiBxID0gNSooNSszKS8yIFdURj8NCiMgcF92YWx1ZSA9IFAoeiA+IDE2LjkxODk4KSA9IDEtcGNoaXNxKDEzLjI0NDk5LCBwPTkpID0gMC4xNTE4MzA0ID4gMC4wNSAsICBmYWlsIHRvIHJlamVjdCBIMCBvZiBob21vc2tlZGFzdGljaXR5LCBpLmUuIGNvbnN0YW50IHZhcmlhbmNlIG9mIGVycm9yIHRlcm0NCjEtcGNoaXNxKDEzLjI0NDk5LCBwKQ0KDQpsaWJyYXJ5KCJsbXRlc3QiKQ0KYnB0ZXN0KG1vZGVsMykNCiMgdGVzdCBzdGF0aXN0aWMgbipSXjJfYXV4ID4gWF4yX2NyaXQgPSBxY2hpc3EoLjk1LCBkZiA9IDUpLCBoZW5jZSBmYWlsIHRvIHJlamVjdCBIMCBhbmQgY29uY2x1ZGUgdGhhdCB0aGVyZSdzDQojIG5vIGhldGVyb3NrZWRhc3RpY2l0eSAtLT4gdmFsaWQgaW5mZXJlbmNlDQoNCmR3dGVzdChtb2RlbDMpDQojIER1cmJpbiBXYXRzb24gdGVzdCBjb25maXJtcyB0aGF0IHRoZSBlcnJvciB2YXJpYW5jZS1jb3ZhcmlhbmNlIGlzIGRpYWdvbmFsLCBpLmUuIHZhcihlcHMgfCBYKSA9ICBcT21lZ2EgKiBJDQojIGZhaWwgdG8gcmVqZWN0IEgwOiByaG8gPSAwIChhbHRlcm5hdGl2ZSBoeXBvdGhlc2lzIGlzIHRoYXQgdGhlcmUgaXMgYXV0b2NvcnJlbGF0aW9uIGluIHRoZSBlcnJvcnMpDQoNCg0KYGBgDQoNCmBgYHtyfQ0KIyBvdXRsaWVyIGRldGVjdGlvbiAvIGxldmVyYWdlDQoNCnBhcihtZmNvbD1jKDEsMSkpDQpwbG90KGhhdHZhbHVlcyhtb2RlbDMpLCBwY2g9MTksIGNleD0wLjUpDQpuIDwtIG5yb3cobW9kZWwzJG1vZGVsKQ0KcCA8LSBuY29sKG1vZGVsMyRtb2RlbCktMQ0KYWJsaW5lKGg9KDE6MykqKHArMSkvbiwgY29sPWMoImJsYWNrIiwgImRhcmtyZWQiLCAicmVkIikpDQoNCnBsb3QoY29va3MuZGlzdGFuY2UobW9kZWwzKSwgcGNoPTE5LA0KICAgICBjZXg9MC41KQ0KbiA8LSBucm93KG1vZGVsMyRtb2RlbCkNCnAgPC0gbmNvbChtb2RlbDMkbW9kZWwpLTENCmFibGluZShoPTQvbiwgY29sPSJyZWQiKQ0KDQpvdXRsaWVyX2luZGV4X2xldmVyYWdlIDwtIGFzLm51bWVyaWMocm93bmFtZXMoeFtoYXR2YWx1ZXMobW9kZWwzKSA+IDMqKHArMSkvbiwgXSkpDQpvdXRsaWVyX2luZGV4X2ZpbmFsIDwtIG91dGxpZXJfaW5kZXhfbGV2ZXJhZ2UNCmxlbmd0aChvdXRsaWVyX2luZGV4X2xldmVyYWdlKQ0KIyBUaGVyZSBhcmUgMTAgb3V0bGllcnMgdGhhdCBtaWdodCBiZSBpbmZsdWVudGlhbCBvbiB0aGUgcmVncmVzc2lvbg0KeFtvdXRsaWVyX2luZGV4X2xldmVyYWdlLCBjKCJUcmFja19UaXRsZSIsIlRyYWNrX0FydGlzdCIsICJHZW5yZSIsICJBcnRpc3RfUG9wdWxhcml0eSIsICJBcnRpc3RfRm9sbG93ZXIiLCAidmlld3NDb3VudCIpXQ0KDQoNCg0KDQojIFJ1bGUgb2YgdGh1bWIgQ29vaydzIGRpc3RhbmNlDQojIG1vdmVtZW50IG9mIHJlZ3Jlc3Npb24gY29lZmZpY2llbnRzIGFsbCB0b2dldGhlciBpZiBpdGggb2JzZXJ2YXRpb24gaXMgZXhjbHVkZWQNCm91dGxpZXJfaW5kZXhfY29va3NkIDwtIGFzLm51bWVyaWMocm93bmFtZXMoeFtjb29rcy5kaXN0YW5jZShtb2RlbDMpID4gMyoocCsxKS9uLCBdKSkNCmxlbmd0aChvdXRsaWVyX2luZGV4X2Nvb2tzZCkNCnhbb3V0bGllcl9pbmRleF9jb29rc2QsIGMoIlRyYWNrX1RpdGxlIiwiVHJhY2tfQXJ0aXN0IiwgIkFydGlzdF9Qb3B1bGFyaXR5IiwgIkFydGlzdF9Gb2xsb3dlciIsICJ2aWV3c0NvdW50IiwgInBjXzEiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICJwY18yIildDQoNCiMgc2FtZSBvdXRsaWVycyBkZXRlY3RlZCBhcyBpbiBtb2RlbDIgZXZlbiBhZnRlciBtb2RpZmljYXRpb24gb2YgdGhlIG1vZGVsDQoNCg0KYGBgDQoNCmBgYHtyfQ0KDQojIGRpZmZlcmVuY2VzDQoNCiMgU0RCRVRBDQoNClNEQkVUQSA8LSBkZmJldGFzKG1vZGVsMykNCm4gPC0gbnJvdyhtb2RlbDMkbW9kZWwpDQpwYXIobWZjb2w9YygzLDMpKQ0KcGxvdChTREJFVEFbLCAnZmFfMiddLCBtYWluPSJmYV8yIiwgcGNoPTE5LCB5bGFiID0gImZhXzIiKQ0KYWJsaW5lKGg9YygtMiwyKS9zcXJ0KG4pLCBjb2w9InJlZCIpDQpwbG90KFNEQkVUQVssICdmYV8yc3EnXSwgbWFpbj0iZmFfMnNxIiwgcGNoPTE5LCB5bGFiID0gImZhXzJzcSIpDQphYmxpbmUoaD1jKC0yLDIpL3NxcnQobiksIGNvbD0icmVkIikNCnBsb3QoU0RCRVRBWywgJ2ZhXzMnXSwgbWFpbj0iZmFfMyIsIHBjaD0xOSwgeWxhYiA9ICJmYV8zIikNCmFibGluZShoPWMoLTIsMikvc3FydChuKSwgY29sPSJyZWQiKQ0KcGxvdChTREJFVEFbLCAncGNfMSddLCBtYWluPSJwY18xIiwgcGNoPTE5LCB5bGFiID0gInBjXzEiKQ0KYWJsaW5lKGg9YygtMiwyKS9zcXJ0KG4pLCBjb2w9InJlZCIpDQpwbG90KFNEQkVUQVssICdwY18yJ10sIG1haW49InBjXzIiLCBwY2g9MTksIHlsYWIgPSAicGNfMiIpDQphYmxpbmUoaD1jKC0yLDIpL3NxcnQobiksIGNvbD0icmVkIikNCnBsb3QoU0RCRVRBWywgJ3BjXzJzcSddLCBtYWluPSJwY18yc3EiLCBwY2g9MTksIHlsYWIgPSAicGNfMnNxIikNCmFibGluZShoPWMoLTIsMikvc3FydChuKSwgY29sPSJyZWQiKQ0KcGxvdChTREJFVEFbLCAnQXJ0aXN0X0ZvbGxvd2VyJ10sIG1haW49IkFydGlzdF9Gb2xsb3dlciIsIHBjaD0xOSwgeWxhYiA9ICJBcnRpc3RfRm9sbG93ZXIiKQ0KYWJsaW5lKGg9YygtMiwyKS9zcXJ0KG4pLCBjb2w9InJlZCIpDQpwbG90KFNEQkVUQVssICdBcnRpc3RfRm9sbG93ZXJzcSddLCBtYWluPSJBcnRpc3RfRm9sbG93ZXJzcSIsIHBjaD0xOSwgeWxhYiA9ICdBcnRpc3RfRm9sbG93ZXJzcScpDQphYmxpbmUoaD1jKC0yLDIpL3NxcnQobiksIGNvbD0icmVkIikNCnBsb3QoU0RCRVRBWywgJ2RheXNfcmVsZWFzZV9vcmlnJ10sIG1haW49ImRheXNfcmVsZWFzZV9vcmlnIiwgcGNoPTE5LCB5bGFiID0gJ2RheXNfcmVsZWFzZScpDQphYmxpbmUoaD1jKC0yLDIpL3NxcnQobiksIGNvbD0icmVkIikNCg0KIyBTREZJVFMNCnBhcihtZmNvbD1jKDEsMSkpDQpTREZJVFMgPC0gZGZmaXRzKG1vZGVsMykNCnBsb3QoU0RGSVRTLCBwY2g9MTkpDQphYmxpbmUoaD1jKC0xLDEpLCBjb2w9InJlZCIpDQpuIDwtIG5yb3cobW9kZWwzJG1vZGVsKQ0KcCA8LSBuY29sKG1vZGVsMyRtb2RlbCktMQ0KYWJsaW5lKGg9YygtMiwyKSpzcXJ0KHAvbiksIGNvbD0iZGFya3JlZCIpDQoNCiMgQXMgdGhpcyBpcyBhIHNtYWxsIGRhdGEgc2V0LCBpdCBpcyBzdWZmaWNpZW50IHRvIGFuYWx5emUgfFxkZWx0YSp5X2l8ID4gMSwgaW4gdGhpcyBjYXNlIHR3byBvYnNlcnZhdGlvbnMgYXJlIGlkZW50aWZpZWQNCiMgb2YgZXhjZWVkaW5nIHRoaXMgbGltaXQuIFRoZXkgYXJlDQoNCm91dGxpZXJfaW5kZXhfc2RmaXRzIDwtIGFzLm51bWVyaWMobmFtZXMod2hpY2goYWJzKFNERklUUykgPiAxKSkpIA0KDQp4W291dGxpZXJfaW5kZXhfc2RmaXRzLCBjKCJUcmFja19UaXRsZSIsIlRyYWNrX0FydGlzdCIsICJBcnRpc3RfUG9wdWxhcml0eSIsICJBcnRpc3RfRm9sbG93ZXIiLCAidmlld3NDb3VudCIsICJwY18xIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgInBjXzIiKV0NCg0KDQpgYGANCg0KDQpgYGB7cn0NCiMgTm93IGV4Y2x1ZGUgdGhlIG91dGxpZXJzIGZyb20gbGV2ZXJhZ2UgYW5hbHlzaXMNCg0KeF9ub291dCA8LSB4Wy1vdXRsaWVyX2luZGV4X2ZpbmFsLCBdDQoNCmxtX2ZpbmFsIDwtIGxtKEFydGlzdF9Qb3B1bGFyaXR5IH4gZmFfMiArIGZhXzJzcSArIGZhXzMgKyBwY18xICsgcGNfMiArIHBjXzJzcSArIEFydGlzdF9Gb2xsb3dlciArIEFydGlzdF9Gb2xsb3dlcnNxICsgZGF5c19yZWxlYXNlX29yaWcsIGRhdGEgPSB4X25vb3V0KQ0Kc3VtbWFyeShsbV9maW5hbCkgIyAgcmVzdWx0OiBSXjIgZGVjcmVhc2VkIGEgbGl0dGxlIGJpdA0KdmlmKGxtX2ZpbmFsKQ0KDQojIGxpYnJhcnkoInN0YXJnYXplciIpDQojIHN0YXJnYXplcjo6c3RhcmdhemVyKG1vZGVsMSwgbW9kZWwyLCBtb2RlbDMsIGxtX2ZpbmFsKQ0KDQpsaWJyYXJ5KCJib290IikNCmNpIDwtIGNvbmZpbnQobG1fZmluYWwsIGxldmVsID0gMC45NSkNCg0KcGxvdC5jb25maW50IDwtIGZ1bmN0aW9uIChiLCBiLmJvb3QsIGNpLCBtYWluKSB7DQogIGhpc3QoYi5ib290LCBtYWluPW1haW4pOyBydWcoYi5ib290KQ0KICBhYmxpbmUodj1iLCBjb2w9InJlZCIpOyBhYmxpbmUodj1jaSwgY29sPSJibHVlIikNCiAgYWJsaW5lKHY9cXVhbnRpbGUoYi5ib290LCBjKDAuMDI1LCAwLjk3NSkpLCBjb2w9ImdyZWVuIikNCn0NCg0KYmV0YWhhdCA8LSBib290KGxtX2ZpbmFsJG1vZGVsLCBSPTk5OSwgDQogICAgICAgICAgICAgICAgZnVuY3Rpb24oeCwgaW5kZXgpIHsgbG0oQXJ0aXN0X1BvcHVsYXJpdHkgfiBmYV8yICsgZmFfMnNxICsgZmFfMyArIHBjXzEgKyBwY18yICsgcGNfMnNxICsgQXJ0aXN0X0ZvbGxvd2VyICsgQXJ0aXN0X0ZvbGxvd2Vyc3EgKyBkYXlzX3JlbGVhc2Vfb3JpZywgZGF0YSA9IHhfbm9vdXQsIHN1YnNldD1pbmRleCkkY29lZmZpY2llbnRzIH0pDQoNCnBhcihtZmNvbD1jKDIsMykpDQoNCnBsb3QuY29uZmludChsbV9maW5hbCRjb2VmZmljaWVudHNbMV0sIGJldGFoYXQkdFssMV0sIGNpWzEsXSwgIkludGVyY2VwdCIpDQpwbG90LmNvbmZpbnQobG1fZmluYWwkY29lZmZpY2llbnRzWzJdLCBiZXRhaGF0JHRbLDJdLCBjaVsyLF0sICJmYV8yIikNCnBsb3QuY29uZmludChsbV9maW5hbCRjb2VmZmljaWVudHNbM10sIGJldGFoYXQkdFssM10sIGNpWzMsXSwgImZhXzJzcSIpDQpwbG90LmNvbmZpbnQobG1fZmluYWwkY29lZmZpY2llbnRzWzRdLCBiZXRhaGF0JHRbLDRdLCBjaVs0LF0sICJmYV8zIikNCnBsb3QuY29uZmludChsbV9maW5hbCRjb2VmZmljaWVudHNbNV0sIGJldGFoYXQkdFssNV0sIGNpWzUsXSwgInBjXzEiKQ0KcGxvdC5jb25maW50KGxtX2ZpbmFsJGNvZWZmaWNpZW50c1s2XSwgYmV0YWhhdCR0Wyw2XSwgY2lbNixdLCAicGNfMiIpDQoNCnBhcihtZmNvbD1jKDIsMikpDQoNCnBsb3QuY29uZmludChsbV9maW5hbCRjb2VmZmljaWVudHNbN10sIGJldGFoYXQkdFssN10sIGNpWzcsXSwgInBjXzJzcSIpDQpwbG90LmNvbmZpbnQobG1fZmluYWwkY29lZmZpY2llbnRzWzhdLCBiZXRhaGF0JHRbLDhdLCBjaVs4LF0sICJBcnRpc3RfRm9sbG93ZXIiKQ0KcGxvdC5jb25maW50KGxtX2ZpbmFsJGNvZWZmaWNpZW50c1s5XSwgYmV0YWhhdCR0Wyw5XSwgY2lbOSxdLCAiQXJ0aXN0X0ZvbGxvd2Vyc3EiKQ0KcGxvdC5jb25maW50KGxtX2ZpbmFsJGNvZWZmaWNpZW50c1sxMF0sIGJldGFoYXQkdFssMTBdLCBjaVsxMCxdLCAiZGF5c19yZWxlYXNlIikNCg0KDQpgYGANCg0KDQpgYGB7cn0NCg0KbW9kZWw1IDwtIGxtKEFydGlzdF9Qb3B1bGFyaXR5IH4gZmFfMiArIGZhXzJzcSwgZGF0YSA9IHgpDQpzdW1tYXJ5KG1vZGVsNSkNCg0KdmVydGV4IDwtIC1tb2RlbDUkY29lZmZpY2llbnRzWzJdIC8gKDIqbW9kZWw1JGNvZWZmaWNpZW50c1szXSkNCnZlcnRleA0KDQoodmVydGV4IDw9IG1heCh4JGZhXzIpKSAmICh2ZXJ0ZXggPj0gbWluKHgkZmFfMikpICMgc28gdmVydGV4IGlzIGluc2lkZSBvZiByYW5nZSBvZiBBcnRpc3RfRm9sbG93ZXIgYW5kIHRoZXJlIGV4aXN0cyBhIHF1YWRyYXRpYyBjb21wb25lbnQNCg0KIyBodHRwczovL3N0YXRzLmlkcmUudWNsYS5lZHUvci9mYXEvaG93LWNhbi1pLWVzdGltYXRlLXRoZS1zdGFuZGFyZC1lcnJvci1vZi10cmFuc2Zvcm1lZC1yZWdyZXNzaW9uLXBhcmFtZXRlcnMtaW4tci11c2luZy10aGUtZGVsdGEtbWV0aG9kLw0KIyBodHRwczovL3d3dy5zdGF0YWxpc3Qub3JnL2ZvcnVtcy9mb3J1bS9nZW5lcmFsLXN0YXRhLWRpc2N1c3Npb24vZ2VuZXJhbC8xMzc2OTM2LXRlc3Rpbmctd2hldGhlci10by1pbmNsdWRlLWEtc3F1YXJlZC10ZXJtDQojIGh0dHBzOi8vd3d3LnN0YXRhbGlzdC5vcmcvZm9ydW1zL2ZvcnVtL2dlbmVyYWwtc3RhdGEtZGlzY3Vzc2lvbi9nZW5lcmFsLzEzNDQwODktaG93LXRvLWZpbmQtdGhlLXR1cm5pbmctcG9pbnQtZm9yLWEtcXVhZHJhdGljLWZ1bmN0aW9uDQojIGh0dHBzOi8vd3d3LnN0YXRhbGlzdC5vcmcvZm9ydW1zL2ZvcnVtL2dlbmVyYWwtc3RhdGEtZGlzY3Vzc2lvbi9nZW5lcmFsLzE0MDg0MTMtc2lnbmlmaWNhbmNlLWxldmVsLW9mLXF1YWRyYXRpYy10ZXJtDQojIGh0dHBzOi8vcHN5Y2gudW5sLmVkdS9wc3ljcnMvc3RhdHBhZ2Uvbm9ubGluX2VnLnBkZiAoInRydWUiIHF1YWRyYXRpYyBjb21wb25lbnQgdnMuIHNrZXdlZCBwcmVkaWN0b3IpDQoNCg0KbGlicmFyeSgibXNtIikgIyBlcXVpdmFsZW50IHRvIHRoZSBubGNvbSBmdW5jdGlvbiBpbiBTdGF0YQ0KDQojIHotdmFsdWUgYWNjb3JkaW5nIHRvIHRoZSBEZWx0YSBtZXRob2QNCg0KdmVydGV4IC8gbXNtOjpkZWx0YW1ldGhvZCh+IC14MiAvICgyICogeDMpLCBjb2VmKG1vZGVsNSksIHZjb3YobW9kZWw1KSkgIyA9IDEyLjg0MjENCg0KIyBDSSBhY2NvcmRpbmcgdG8gU3RhdGENCg0KdmVydGV4IC0gcXQoMC45NzUsIGRmID0gMTAwMDAwMDApICogbXNtOjpkZWx0YW1ldGhvZCh+IC14MiAvICgyICogeDMpLCBjb2VmKG1vZGVsNSksIHZjb3YobW9kZWw1KSkNCnZlcnRleCArIHF0KDAuOTc1LCBkZiA9IDEwMDAwMDAwKSAqIG1zbTo6ZGVsdGFtZXRob2QofiAteDIgLyAoMiAqIHgzKSwgY29lZihtb2RlbDUpLCB2Y292KG1vZGVsNSkpDQoNCm4gPC0gbnJvdyhtb2RlbDUkbW9kZWwpDQpkIDwtIGxlbmd0aChtb2RlbDUkY29lZmZpY2llbnRzKQ0KZGYgPC0gbiAtIGQNCg0KIyBodHRwOi8vc2lhLndlYnBvcGl4Lm9yZy9ub25saW5lYXJSZWdyZXNzaW9uLmh0bWwNCg0KIyBwX3ZhbHVlID0gUCh6ID4gMS45NTk5NjQpID0gMS1wdCh6LCBkZj1kZikgPSAzLjI3MDY1OWUtMTAgPCAwLjA1IDwtLSByZWplY3QgSDAgdGhhdCB2ZXJ0ZXggaXMgZXF1YWwgdG8gemVybw0KDQojIHZlcnRleCBmb3IgZmFfMg0KDQp2ZXJ0ZXggPC0gLW1vZGVsMyRjb2VmZmljaWVudHNbMl0gLyAoMiptb2RlbDMkY29lZmZpY2llbnRzWzNdKQ0KdmVydGV4DQoodmVydGV4IDw9IG1heCh4JGZhXzIpKSAmICh2ZXJ0ZXggPj0gbWluKHgkZmFfMikpIA0KeiA8LSB2ZXJ0ZXggLyBtc206OmRlbHRhbWV0aG9kKH4gLXgyIC8gKDIgKiB4MyksIGNvZWYobW9kZWwzKSwgdmNvdihtb2RlbDMpKQ0Keg0KbG93ZXJfQ0kgPC0gdmVydGV4IC0gcXQoMC45NzUsIGRmID0gMTAwMDAwMDApICogbXNtOjpkZWx0YW1ldGhvZCh+IC14MiAvICgyICogeDMpLCBjb2VmKG1vZGVsMyksIHZjb3YobW9kZWwzKSkNCnVwcGVyX0NJIDwtIHZlcnRleCArIHF0KDAuOTc1LCBkZiA9IDEwMDAwMDAwKSAqIG1zbTo6ZGVsdGFtZXRob2QofiAteDIgLyAoMiAqIHgzKSwgY29lZihtb2RlbDMpLCB2Y292KG1vZGVsMykpDQpuIDwtIG5yb3cobW9kZWwzJG1vZGVsKQ0KZCA8LSBsZW5ndGgobW9kZWwzJGNvZWZmaWNpZW50cykNCmRmIDwtIG4gLSBkDQoxLXB0KHosIGRmPWRmKQ0KIyBwX3ZhbHVlID0gUCh6ID4gMS45NTk5NjQpID0gMS1wdCh6LCBkZj1kZikgPSA0LjQ0MDg5MmUtMTYgPCAwLjA1IDwtLSByZWplY3QgSDAgdGhhdCB2ZXJ0ZXggaXMgZXF1YWwgdG8gemVybw0KDQojIHZlcnRleCBmb3IgcGNfMg0KDQp2ZXJ0ZXggPC0gLW1vZGVsMyRjb2VmZmljaWVudHNbNl0gLyAoMiptb2RlbDMkY29lZmZpY2llbnRzWzddKQ0KdmVydGV4DQoodmVydGV4IDw9IG1heCh4JHBjXzIpKSAmICh2ZXJ0ZXggPj0gbWluKHgkcGNfMikpIA0KeiA8LSB2ZXJ0ZXggLyBtc206OmRlbHRhbWV0aG9kKH4gLXg2IC8gKDIgKiB4NyksIGNvZWYobW9kZWwzKSwgdmNvdihtb2RlbDMpKQ0Keg0KbG93ZXJfQ0kgPC0gdmVydGV4IC0gcXQoMC45NzUsIGRmID0gMTAwMDAwMDApICogbXNtOjpkZWx0YW1ldGhvZCh+IC14NiAvICgyICogeDcpLCBjb2VmKG1vZGVsMyksIHZjb3YobW9kZWwzKSkNCnVwcGVyX0NJIDwtIHZlcnRleCArIHF0KDAuOTc1LCBkZiA9IDEwMDAwMDAwKSAqIG1zbTo6ZGVsdGFtZXRob2QofiAteDYgLyAoMiAqIHg3KSwgY29lZihtb2RlbDMpLCB2Y292KG1vZGVsMykpDQpuIDwtIG5yb3cobW9kZWwzJG1vZGVsKQ0KZCA8LSBsZW5ndGgobW9kZWwzJGNvZWZmaWNpZW50cykNCmRmIDwtIG4gLSBkDQoxLXB0KHosIGRmPWRmKQ0KIyBwX3ZhbHVlID0gUCh6ID4gMS45NTk5NjQpID0gMS1wdCh6LCBkZj1kZikgPSA1LjEyNTI0M2UtMDYgPCAwLjA1IDwtLSByZWplY3QgSDAgdGhhdCB2ZXJ0ZXggaXMgZXF1YWwgdG8gemVybw0KDQojIHZlcnRleCBmb3IgQXJ0aXN0X0ZvbGxvd2VyDQoNCnZlcnRleCA8LSAtbW9kZWwzJGNvZWZmaWNpZW50c1s4XSAvICgyKm1vZGVsMyRjb2VmZmljaWVudHNbOV0pDQp2ZXJ0ZXgNCih2ZXJ0ZXggPD0gbWF4KHgkQXJ0aXN0X0ZvbGxvd2VyKSkgJiAodmVydGV4ID49IG1pbih4JEFydGlzdF9Gb2xsb3dlcikpIA0KeiA8LSB2ZXJ0ZXggLyBtc206OmRlbHRhbWV0aG9kKH4gLXg4IC8gKDIgKiB4OSksIGNvZWYobW9kZWwzKSwgdmNvdihtb2RlbDMpKQ0Keg0KbG93ZXJfQ0kgPC0gdmVydGV4IC0gcXQoMC45NzUsIGRmID0gMTAwMDAwMDApICogbXNtOjpkZWx0YW1ldGhvZCh+IC14OCAvICgyICogeDkpLCBjb2VmKG1vZGVsMyksIHZjb3YobW9kZWwzKSkNCnVwcGVyX0NJIDwtIHZlcnRleCArIHF0KDAuOTc1LCBkZiA9IDEwMDAwMDAwKSAqIG1zbTo6ZGVsdGFtZXRob2QofiAteDggLyAoMiAqIHg5KSwgY29lZihtb2RlbDMpLCB2Y292KG1vZGVsMykpDQpuIDwtIG5yb3cobW9kZWwzJG1vZGVsKQ0KZCA8LSBsZW5ndGgobW9kZWwzJGNvZWZmaWNpZW50cykNCmRmIDwtIG4gLSBkDQoxLXB0KHosIGRmPWRmKQ0KDQoNCg0KDQojcGxvdChBcnRpc3RfRm9sbG93ZXJfdHJhbnMsIHgkQXJ0aXN0X1BvcHVsYXJpdHkpDQoNCiNtaW5fbWF4X25vcm1hbGl6ZSA8LSBmdW5jdGlvbih4KQ0KI3sNCiMgIHJldHVybiggKDEwLTEpKigoeC0gbWluKHgpKSAvKG1heCh4KS1taW4oeCkpKSArIDEpDQojfQ0KDQojbWluX2ZhXzIgPC0gb3B0aW1pemUoa3NELCBjKC0xMCwxMCksIHg9bWluX21heF9ub3JtYWxpemUoeCRwY18yKSkkbWluaW11bQ0KI2ZhXzJiYyA8LSBiY1Bvd2VyKG1pbl9tYXhfbm9ybWFsaXplKHgkcGNfMiksIG1pbl9mYV8yKQ0KDQojZmFfMmJjc3FyciA8LSBmYV8yYmMgKiogMC41DQojZmFfMmJjc3FycmNlbnRlciA8LSBmYV8yYmNzcXJyIC0gbWVhbihmYV8yYmNzcXJyKQ0KI2ZhXzJiY3NxcnJjZW50ZXJzcSA8LSBmYV8yYmNzcXJyY2VudGVyICoqIDINCg0KI2NvcihjYmluZCh4JEFydGlzdF9Qb3B1bGFyaXR5LCBmYV8yYmNzcXJyY2VudGVyLCBmYV8yYmNzcXJyY2VudGVyc3EpKQ0KDQoNCg0KDQoNCiNwbG90KHgkZmFfMiwgeCRBcnRpc3RfUG9wdWxhcml0eSkNCiNwbG90KHgkcGNfMiwgeCRBcnRpc3RfUG9wdWxhcml0eSkNCg0KYGBgDQoNCmBgYHtyfQ0KDQojIGJhc2VsaW5lIG1vZGVsOg0KIyBmb3J3YXJkDQoNCmxtaSA8LSBsbShBcnRpc3RfUG9wdWxhcml0eX4xLCBkYXRhPWxtX2ZpbmFsJG1vZGVsKQ0KbG1mIDwtIE1BU1M6OnN0ZXBBSUMobG1pLCBzY29wZT1+DQogICAgICAgICAgICAgICAgZmFfMiANCiAgICAgICAgICAgICAgICsgZmFfMnNxDQogICAgICAgICAgICAgICArIGZhXzMgDQogICAgICAgICAgICAgICArIHhfbm9vdXRbLCdmYV80J10gDQogICAgICAgICAgICAgICArIHBjXzENCiAgICAgICAgICAgICAgICsgcGNfMg0KICAgICAgICAgICAgICAgKyBwY18yc3ENCiAgICAgICAgICAgICAgICsgQXJ0aXN0X0ZvbGxvd2VyDQogICAgICAgICAgICAgICArIEFydGlzdF9Gb2xsb3dlcnNxDQogICAgICAgICAgICAgICArIGRheXNfcmVsZWFzZV9vcmlnDQogICAgICAgICAgICAgICArIHhfbm9vdXRbLCAnZmFfMSddDQogICAgICAgICAgICAgICArIHhfbm9vdXRbLCAnVHJhY2tfRHVyYXRpb25fbXMnXQ0KICAgICAgICAgICAgICAgKyB4X25vb3V0WywgJ1RyYWNrX1BvcHVsYXJpdHknXQ0KICAgICAgICAgICAgICAgLGRpcmVjdGlvbiA9ICJmb3J3YXJkIikNCg0Kc3VtbWFyeShsbWYpDQoNCiMgYmFja3dhcmQ6DQpsbWEgPC0gbG0oQXJ0aXN0X1BvcHVsYXJpdHkgfiBmYV8yICsgZmFfMnNxICsgZmFfMyArIGZhXzQgKyBwY18xICsgcGNfMiArIHBjXzJzcSArIEFydGlzdF9Gb2xsb3dlciANCiAgICAgICAgICArIEFydGlzdF9Gb2xsb3dlcnNxICsgZmFfMSArIFRyYWNrX0R1cmF0aW9uX21zDQogICAgICAgICAgKyBkYXlzX3JlbGVhc2Vfb3JpZyArIFRyYWNrX1BvcHVsYXJpdHksIA0KICAgICAgICAgIGRhdGE9IHhfbm9vdXQpDQoNCmxtYiA8LSBNQVNTOjpzdGVwQUlDKGxtYSkNCnN1bW1hcnkobG1iKQ0KDQoNCmBgYA0KDQpgYGB7cn0NCg0KIyBSZWd1bGFyaXphdGlvbg0KDQpzZWwudmFyIDwtIGMoImZhXzEiLCAiZmFfMiIsICJmYV8yc3EiLCAiZmFfMyIsICJmYV80IiwgInBjXzEiLCAicGNfMiIsICJwY18yc3EiLCAiQXJ0aXN0X0ZvbGxvd2VyIiwgIkFydGlzdF9Gb2xsb3dlcnNxIiwgIkFydGlzdF9Qb3B1bGFyaXR5IiwgImRheXNfcmVsZWFzZV9vcmlnIiwgIlRyYWNrX1BvcHVsYXJpdHkiLCAiVHJhY2tfRHVyYXRpb25fbXMiKSANCg0KZGYgPC0gZHBseXI6OnNlbGVjdCh4X25vb3V0LCBzZWwudmFyKQ0KDQpsbWZ1bGwgPC0gbG0oQXJ0aXN0X1BvcHVsYXJpdHl+LiwgZGF0YT1kZikNCmxtYmFjayA8LSBzdGVwKGxtZnVsbCkNCg0KDQpsaWJyYXJ5KCJnbG1uZXQiKQ0KeGwgPC0gYXMubWF0cml4KGRmWywtMTFdKQ0KeWwgPC0gZGZbLDExXQ0Kc2V0LnNlZWQoNDIpDQpsbWxhc3NvIDwtIGN2LmdsbW5ldCh4bCwgeWwsIGFscGhhID0xKSAjIGNyb3NzLXZhbGlkYXRpb24gZmluZHMgdGhlIHZhbHVlIG9mIGxhbWJkYSB3aGljaCBtaW5pbWl6ZXMgTVNFLCBhbHBoYSA9IDEgZm9yIExBU1NPDQpwbG90KGxtbGFzc28pICMgZGlzcGxheXMgdGhlIGJpYXMtdmFyaWFuY2UgdHJhZGUtb2ZmIChNU0UgPSB2YXJpYW5jZSArIGJpYXNeMikNCmJlc3QubGFtYmRhIDwtIGxtbGFzc28kbGFtYmRhLm1pbg0KbG1sYXNzbyRsYW1iZGEuMXNlDQoNCmNsIDwtIGxpc3QoY29lZihsbWxhc3NvLCBzPSJsYW1iZGEubWluIilbLDFdLCBjb2VmKGxtbGFzc28sIHM9ImxhbWJkYS4xc2UiKVssMV0sIGNvZWYobG1iYWNrKSwgY29lZihsbWZ1bGwpKQ0KY2YgPC0gbWF0cml4KE5BLCBucm93PWRpbSh4bClbMl0rMSwgbmNvbD1sZW5ndGgoY2wpKQ0Kcm93Lm5hbWVzKGNmKSA8LSBuYW1lcyhsbWZ1bGwkY29lZmZpY2llbnRzKQ0KaSA8LSAxDQpmb3IgKGNpIGluIGNsKSB7DQogIGNmW25hbWVzKGNpKSxpXSA8LSBjaVtuYW1lcyhjaSldDQogIGkgPC0gaSsxDQp9DQpjb2xuYW1lcyhjZikgPC0gYygibWluIiwgIjFzZSIsICJiYWNrIiwgImZ1bGwiKQ0KY2ZbY2Y9PTBdIDwtIE5BDQpjZg0KDQojIHZhcmlhYmxlIGltcG9ydGFuY2UNCmdsbW1vZCA8LSBnbG1uZXQoeGwsIHlsLCBsYW1iZGEgPSBiZXN0LmxhbWJkYSkNCmNvZWZzID0gY29lZihnbG1tb2QpWywxXQ0KY29lZnMgPSBzb3J0KGFicyhjb2VmcyksIGRlY3JlYXNpbmcgPSBUKQ0KY29lZnMNCmNvZWYobG1sYXNzbywgcz0ibGFtYmRhLjFzZSIpDQoNCg0KYGBgDQoNCmBgYHtyfQ0KIyMjIyMjIyMjIyMjIyMjIyMjIyMjIw0KIyBQZXJmb3JtYW5jZSBtZXRyaWNzIC0gZnVsbCBkYXRhDQoNCnBlcmZvcm1hY2VfbWV0cmljcyA8LSBtYXRyaXgobnJvdyA9IDEwLCBuY29sID0gNikNCg0KUk1TRShwcmVkaWN0KGxtZnVsbCwgbmV3ZGF0YSA9IGRmKSwgZGYkQXJ0aXN0X1BvcHVsYXJpdHkpDQpSMihwcmVkaWN0KGxtZnVsbCwgbmV3ZGF0YSA9IGRmKSwgZGYkQXJ0aXN0X1BvcHVsYXJpdHkpDQoNCnBlcmZvcm1hY2VfbWV0cmljc1sxLDFdIDwtIFJNU0UocHJlZGljdChsbWZ1bGwsIG5ld2RhdGEgPSBkZiksIGRmJEFydGlzdF9Qb3B1bGFyaXR5KQ0KcGVyZm9ybWFjZV9tZXRyaWNzWzEsMl0gPC0gUjIocHJlZGljdChsbWZ1bGwsIG5ld2RhdGEgPSBkZiksIGRmJEFydGlzdF9Qb3B1bGFyaXR5KQ0KDQoNCiMgeF9tb2RlbCA8LSBtb2RlbC5tYXRyaXgoQXJ0aXN0X1BvcHVsYXJpdHl+LiwgeF9ub291dCkNCg0KbGlicmFyeSgiZ2xtbmV0IikNCiJSaWRnZSByZWdyZXNzaW9uIg0Kc2V0LnNlZWQoNDIpDQpiZXN0LmxhbWJkYSA8LSBjdi5nbG1uZXQoeCA9IGFzLm1hdHJpeChkZlssLTExXSksIHkgPSBhcy5tYXRyaXgoZGZbLDExXSksIGFscGhhID0gMCkkbGFtYmRhLm1pbg0KcmlkZ2UuY3YgPC0gZ2xtbmV0KHggPSBhcy5tYXRyaXgoZGZbLC0xMV0pLCB5ID0gYXMubWF0cml4KGRmWywxMV0pLCBhbHBoYSA9IDAsIGxhbWJkYSA9IGJlc3QubGFtYmRhKQ0KIyBhcy5udW1lcmljKGNvZWYocmlkZ2UuY3YpWywxXSkgJSolIGFzLm51bWVyaWMoeF9tb2RlbFsxLF0pIGVxdWl2YWxlbnQNCiMgcHJlZGljdChyaWRnZS5jdiwgbmV3eCA9IGFzLm1hdHJpeCh4X25vb3V0WzEsLThdKSkgZXF1aXZhbGVudA0KUk1TRShwcmVkaWN0KHJpZGdlLmN2LCBuZXd4ID0gYXMubWF0cml4KGRmWywtMTFdKSksIGFzLm1hdHJpeChkZlssMTFdKSkNClIyKHByZWRpY3QocmlkZ2UuY3YsIG5ld3ggPSBhcy5tYXRyaXgoZGZbLC0xMV0pKSwgYXMubWF0cml4KGRmWywxMV0pKQ0KDQpwZXJmb3JtYWNlX21ldHJpY3NbMiwxXSA8LSBSTVNFKHByZWRpY3QocmlkZ2UuY3YsIG5ld3ggPSBhcy5tYXRyaXgoZGZbLC0xMV0pKSwgYXMubWF0cml4KGRmWywxMV0pKQ0KcGVyZm9ybWFjZV9tZXRyaWNzWzIsMl0gPC0gUjIocHJlZGljdChyaWRnZS5jdiwgbmV3eCA9IGFzLm1hdHJpeChkZlssLTExXSkpLCBhcy5tYXRyaXgoZGZbLDExXSkpDQoNCiJMQVNTTyByZWdyZXNzaW9uIg0Kc2V0LnNlZWQoNDIpDQpiZXN0LmxhbWJkYSA8LSBjdi5nbG1uZXQoeCA9IGFzLm1hdHJpeChkZlssLTExXSksIHkgPSBhcy5tYXRyaXgoZGZbLDExXSksIGFscGhhID0gMSkkbGFtYmRhLm1pbg0KbGFzc28uY3YgPC0gZ2xtbmV0KHggPSBhcy5tYXRyaXgoZGZbLC0xMV0pLCB5ID0gYXMubWF0cml4KGRmWywxMV0pLCBhbHBoYSA9IDEsIGxhbWJkYSA9IGJlc3QubGFtYmRhKQ0KUk1TRShwcmVkaWN0KGxhc3NvLmN2LCBuZXd4ID0gYXMubWF0cml4KGRmWywtMTFdKSksIGFzLm1hdHJpeChkZlssMTFdKSkNClIyKHByZWRpY3QobGFzc28uY3YsIG5ld3ggPSBhcy5tYXRyaXgoZGZbLC0xMV0pKSwgYXMubWF0cml4KGRmWywxMV0pKQ0KDQpwZXJmb3JtYWNlX21ldHJpY3NbMywxXSA8LSBSTVNFKHByZWRpY3QobGFzc28uY3YsIG5ld3ggPSBhcy5tYXRyaXgoZGZbLC0xMV0pKSwgYXMubWF0cml4KGRmWywxMV0pKQ0KcGVyZm9ybWFjZV9tZXRyaWNzWzMsMl0gPC0gUjIocHJlZGljdChsYXNzby5jdiwgbmV3eCA9IGFzLm1hdHJpeChkZlssLTExXSkpLCBhcy5tYXRyaXgoZGZbLDExXSkpDQoNCiJFbGFzdGljbmV0IHJlZ3Jlc3Npb24iDQpzZXQuc2VlZCg0MikNCmJlc3QubGFtYmRhIDwtIGN2LmdsbW5ldCh4ID0gYXMubWF0cml4KGRmWywtMTFdKSwgeSA9IGFzLm1hdHJpeChkZlssMTFdKSwgYWxwaGEgPSAwLjUpJGxhbWJkYS5taW4NCmVsYXN0aWNuZXQuY3YgPC0gZ2xtbmV0KHggPSBhcy5tYXRyaXgoZGZbLC0xMV0pLCB5ID0gYXMubWF0cml4KGRmWywxMV0pLCBhbHBoYSA9IDAuNSwgbGFtYmRhID0gYmVzdC5sYW1iZGEpDQpSTVNFKHByZWRpY3QoZWxhc3RpY25ldC5jdiwgbmV3eCA9IGFzLm1hdHJpeChkZlssLTExXSkpLCBhcy5tYXRyaXgoZGZbLDExXSkpDQpSMihwcmVkaWN0KGVsYXN0aWNuZXQuY3YsIG5ld3ggPSBhcy5tYXRyaXgoZGZbLC0xMV0pKSwgYXMubWF0cml4KGRmWywxMV0pKQ0KDQpwZXJmb3JtYWNlX21ldHJpY3NbNCwxXSA8LSBSTVNFKHByZWRpY3QoZWxhc3RpY25ldC5jdiwgbmV3eCA9IGFzLm1hdHJpeChkZlssLTExXSkpLCBhcy5tYXRyaXgoZGZbLDExXSkpDQpwZXJmb3JtYWNlX21ldHJpY3NbNCwyXSA8LSBSMihwcmVkaWN0KGVsYXN0aWNuZXQuY3YsIG5ld3ggPSBhcy5tYXRyaXgoZGZbLC0xMV0pKSwgYXMubWF0cml4KGRmWywxMV0pKQ0KDQp4X2Z1bGwgPC0gbW9kZWwubWF0cml4KEFydGlzdF9Qb3B1bGFyaXR5fi4sIGRmKVssLTFdDQoNCnJpZGdlX3JlczIgPC0gYXMubnVtZXJpYyhkZiRBcnRpc3RfUG9wdWxhcml0eSAtIHByZWRpY3QocmlkZ2UuY3YsIHhfZnVsbCkpXjINCmxhc3NvX3JlczIgPC0gYXMubnVtZXJpYyhkZiRBcnRpc3RfUG9wdWxhcml0eSAtIHByZWRpY3QobGFzc28uY3YsIHhfZnVsbCkpXjINCmVsYXN0aWNuZXRfcmVzMiA8LSBhcy5udW1lcmljKGRmJEFydGlzdF9Qb3B1bGFyaXR5IC0gcHJlZGljdChlbGFzdGljbmV0LmN2LCB4X2Z1bGwpKV4yDQoNCnBhcihtZmNvbD1jKDIsMikpDQpiYXJwbG90KChkZiRBcnRpc3RfUG9wdWxhcml0eSAtIHByZWRpY3QobG1mdWxsLCBkZikpXjIpDQpBeGlzKHNpZGU9MikNCnRpdGxlKG1haW4gPSBwYXN0ZSgiTE0gKGZ1bGwgc2FtcGxlKTogUk1TRSA9Iixyb3VuZChSTVNFKHByZWRpY3QobG1mdWxsLCBkZiksIGRmJEFydGlzdF9Qb3B1bGFyaXR5KSwgZGlnaXRzID0gMiksICIsIFJeMjoiLCByb3VuZChSMihwcmVkaWN0KGxtZnVsbCwgZGYpLCBkZiRBcnRpc3RfUG9wdWxhcml0eSksIGRpZ2l0cyA9IDIpKSkNCmJhcnBsb3QocmlkZ2VfcmVzMiwgbWFpbiA9IHBhc3RlKCJSaWRnZSAoZnVsbCBzYW1wbGUpOiBSTVNFID0iLHJvdW5kKFJNU0UocHJlZGljdChyaWRnZS5jdiwgeF9mdWxsKSwgZGYkQXJ0aXN0X1BvcHVsYXJpdHkpLCBkaWdpdHMgPSAyKSwgIiwgUl4yOiIsIHJvdW5kKFIyKHByZWRpY3QocmlkZ2UuY3YsIHhfZnVsbCksIGRmJEFydGlzdF9Qb3B1bGFyaXR5KSwgZGlnaXRzID0gMikpKQ0KYmFycGxvdChsYXNzb19yZXMyLCBtYWluID0gcGFzdGUoIkxBU1NPIChmdWxsIHNhbXBsZSk6IFJNU0UgPSIscm91bmQoUk1TRShwcmVkaWN0KGxhc3NvLmN2LCB4X2Z1bGwpLCBkZiRBcnRpc3RfUG9wdWxhcml0eSksIGRpZ2l0cyA9IDIpLCAiLCBSXjI6Iiwgcm91bmQoUjIocHJlZGljdChsYXNzby5jdiwgeF9mdWxsKSwgZGYkQXJ0aXN0X1BvcHVsYXJpdHkpLCBkaWdpdHMgPSAyKSkpDQpiYXJwbG90KGVsYXN0aWNuZXRfcmVzMiwgbWFpbiA9IHBhc3RlKCJFbGFzdGljbmV0IChmdWxsIHNhbXBsZSk6IFJNU0UgPSIscm91bmQoUk1TRShwcmVkaWN0KGVsYXN0aWNuZXQuY3YsIHhfZnVsbCksIGRmJEFydGlzdF9Qb3B1bGFyaXR5KSwgZGlnaXRzID0gMiksICIsIFJeMjoiLCByb3VuZChSMihwcmVkaWN0KGVsYXN0aWNuZXQuY3YsIHhfZnVsbCksIGRmJEFydGlzdF9Qb3B1bGFyaXR5KSwgZGlnaXRzID0gMikpKQ0KDQoNCg0KDQoNCmBgYA0KDQoNCmBgYHtyfQ0KDQpsaWJyYXJ5KCJjYXJldCIpDQoNCiMgU3BsaXR0aW5nIGluIHRyYWluIGFuZCB0ZXN0IGRhdGENCg0Kc2V0LnNlZWQoNDIpDQppZHgudHJhaW4gPC0gY3JlYXRlRGF0YVBhcnRpdGlvbih5ID0gZGYkQXJ0aXN0X1BvcHVsYXJpdHksIHAgPSAwLjc1LCBsaXN0ID0gRkFMU0UpDQp0cmFpbiA8LSBkZltpZHgudHJhaW4sIF0NCnRlc3QgPC0gZGZbLWlkeC50cmFpbixdDQoNCmxtX3RyYWluIDwtIGxtKEFydGlzdF9Qb3B1bGFyaXR5fi4sIGRhdGE9dHJhaW4pDQpSTVNFKHByZWRpY3QobG1fdHJhaW4sIG5ld2RhdGEgPSB0cmFpbiksIHRyYWluJEFydGlzdF9Qb3B1bGFyaXR5KQ0KUjIocHJlZGljdChsbV90cmFpbiwgbmV3ZGF0YSA9IHRyYWluKSwgdHJhaW4kQXJ0aXN0X1BvcHVsYXJpdHkpDQoNCnBlcmZvcm1hY2VfbWV0cmljc1sxLDNdIDwtIFJNU0UocHJlZGljdChsbV90cmFpbiwgbmV3ZGF0YSA9IHRyYWluKSwgdHJhaW4kQXJ0aXN0X1BvcHVsYXJpdHkpDQpwZXJmb3JtYWNlX21ldHJpY3NbMSw0XSA8LSBSMihwcmVkaWN0KGxtX3RyYWluLCBuZXdkYXRhID0gdHJhaW4pLCB0cmFpbiRBcnRpc3RfUG9wdWxhcml0eSkNCg0KUk1TRShwcmVkaWN0KGxtX3RyYWluLCBuZXdkYXRhID0gdGVzdCksIHRlc3QkQXJ0aXN0X1BvcHVsYXJpdHkpDQpSMihwcmVkaWN0KGxtX3RyYWluLCBuZXdkYXRhID0gdGVzdCksIHRlc3QkQXJ0aXN0X1BvcHVsYXJpdHkpDQoNCnBlcmZvcm1hY2VfbWV0cmljc1sxLDVdIDwtIFJNU0UocHJlZGljdChsbV90cmFpbiwgbmV3ZGF0YSA9IHRlc3QpLCB0ZXN0JEFydGlzdF9Qb3B1bGFyaXR5KQ0KcGVyZm9ybWFjZV9tZXRyaWNzWzEsNl0gPC0gUjIocHJlZGljdChsbV90cmFpbiwgbmV3ZGF0YSA9IHRlc3QpLCB0ZXN0JEFydGlzdF9Qb3B1bGFyaXR5KQ0KDQoiUmlkZ2UgcmVncmVzc2lvbiINCnNldC5zZWVkKDQyKQ0KYmVzdC5sYW1iZGEgPC0gY3YuZ2xtbmV0KHggPSBhcy5tYXRyaXgodHJhaW5bLC0xMV0pLCB5ID0gYXMubWF0cml4KHRyYWluWywxMV0pLCBhbHBoYSA9IDApJGxhbWJkYS5taW4NCnJpZGdlLmN2IDwtIGdsbW5ldCh4ID0gYXMubWF0cml4KHRyYWluWywtMTFdKSwgeSA9IGFzLm1hdHJpeCh0cmFpblssMTFdKSwgYWxwaGEgPSAwLCBsYW1iZGEgPSBiZXN0LmxhbWJkYSkNCiMgYXMubnVtZXJpYyhjb2VmKHJpZGdlLmN2KVssMV0pICUqJSBhcy5udW1lcmljKHhfbW9kZWxbMSxdKSBlcXVpdmFsZW50DQojIHByZWRpY3QocmlkZ2UuY3YsIG5ld3ggPSBhcy5tYXRyaXgoeF9ub291dFsxLC04XSkpIGVxdWl2YWxlbnQNClJNU0UocHJlZGljdChyaWRnZS5jdiwgbmV3eCA9IGFzLm1hdHJpeCh0cmFpblssLTExXSkpLCBhcy5tYXRyaXgodHJhaW5bLDExXSkpDQpSMihwcmVkaWN0KHJpZGdlLmN2LCBuZXd4ID0gYXMubWF0cml4KHRyYWluWywtMTFdKSksIGFzLm1hdHJpeCh0cmFpblssMTFdKSkNCg0KcGVyZm9ybWFjZV9tZXRyaWNzWzIsM10gPC0gUk1TRShwcmVkaWN0KHJpZGdlLmN2LCBuZXd4ID0gYXMubWF0cml4KHRyYWluWywtMTFdKSksIGFzLm1hdHJpeCh0cmFpblssMTFdKSkNCnBlcmZvcm1hY2VfbWV0cmljc1syLDRdIDwtIFIyKHByZWRpY3QocmlkZ2UuY3YsIG5ld3ggPSBhcy5tYXRyaXgodHJhaW5bLC0xMV0pKSwgYXMubWF0cml4KHRyYWluWywxMV0pKQ0KDQpSTVNFKHByZWRpY3QocmlkZ2UuY3YsIG5ld3ggPSBhcy5tYXRyaXgodGVzdFssLTExXSkpLCBhcy5tYXRyaXgodGVzdFssMTFdKSkNClIyKHByZWRpY3QocmlkZ2UuY3YsIG5ld3ggPSBhcy5tYXRyaXgodGVzdFssLTExXSkpLCBhcy5tYXRyaXgodGVzdFssMTFdKSkNCg0KcGVyZm9ybWFjZV9tZXRyaWNzWzIsNV0gPC0gUk1TRShwcmVkaWN0KHJpZGdlLmN2LCBuZXd4ID0gYXMubWF0cml4KHRlc3RbLC0xMV0pKSwgYXMubWF0cml4KHRlc3RbLDExXSkpIA0KcGVyZm9ybWFjZV9tZXRyaWNzWzIsNl0gPC0gUjIocHJlZGljdChyaWRnZS5jdiwgbmV3eCA9IGFzLm1hdHJpeCh0ZXN0WywtMTFdKSksIGFzLm1hdHJpeCh0ZXN0WywxMV0pKQ0KDQoNCiJMQVNTTyByZWdyZXNzaW9uIg0Kc2V0LnNlZWQoNDIpDQpiZXN0LmxhbWJkYSA8LSBjdi5nbG1uZXQoeCA9IGFzLm1hdHJpeCh0cmFpblssLTExXSksIHkgPSBhcy5tYXRyaXgodHJhaW5bLDExXSksIGFscGhhID0gMSkkbGFtYmRhLm1pbg0KbGFzc28uY3YgPC0gZ2xtbmV0KHggPSBhcy5tYXRyaXgodHJhaW5bLC0xMV0pLCB5ID0gYXMubWF0cml4KHRyYWluWywxMV0pLCBhbHBoYSA9IDEsIGxhbWJkYSA9IGJlc3QubGFtYmRhKQ0KUk1TRShwcmVkaWN0KGxhc3NvLmN2LCBuZXd4ID0gYXMubWF0cml4KHRyYWluWywtMTFdKSksIGFzLm1hdHJpeCh0cmFpblssMTFdKSkNClIyKHByZWRpY3QobGFzc28uY3YsIG5ld3ggPSBhcy5tYXRyaXgodHJhaW5bLC0xMV0pKSwgYXMubWF0cml4KHRyYWluWywxMV0pKQ0KDQpwZXJmb3JtYWNlX21ldHJpY3NbMywzXSA8LSBSTVNFKHByZWRpY3QobGFzc28uY3YsIG5ld3ggPSBhcy5tYXRyaXgodHJhaW5bLC0xMV0pKSwgYXMubWF0cml4KHRyYWluWywxMV0pKQ0KcGVyZm9ybWFjZV9tZXRyaWNzWzMsNF0gPC0gUjIocHJlZGljdChsYXNzby5jdiwgbmV3eCA9IGFzLm1hdHJpeCh0cmFpblssLTExXSkpLCBhcy5tYXRyaXgodHJhaW5bLDExXSkpDQoNClJNU0UocHJlZGljdChsYXNzby5jdiwgbmV3eCA9IGFzLm1hdHJpeCh0ZXN0WywtMTFdKSksIGFzLm1hdHJpeCh0ZXN0WywxMV0pKQ0KUjIocHJlZGljdChsYXNzby5jdiwgbmV3eCA9IGFzLm1hdHJpeCh0ZXN0WywtMTFdKSksIGFzLm1hdHJpeCh0ZXN0WywxMV0pKQ0KDQpwZXJmb3JtYWNlX21ldHJpY3NbMyw1XSA8LSBSTVNFKHByZWRpY3QobGFzc28uY3YsIG5ld3ggPSBhcy5tYXRyaXgodGVzdFssLTExXSkpLCBhcy5tYXRyaXgodGVzdFssMTFdKSkNCnBlcmZvcm1hY2VfbWV0cmljc1szLDZdIDwtIFIyKHByZWRpY3QobGFzc28uY3YsIG5ld3ggPSBhcy5tYXRyaXgodGVzdFssLTExXSkpLCBhcy5tYXRyaXgodGVzdFssMTFdKSkNCiAgDQoiRWxhc3RpY25ldCByZWdyZXNzaW9uIg0Kc2V0LnNlZWQoNDIpDQpiZXN0LmxhbWJkYSA8LSBjdi5nbG1uZXQoeCA9IGFzLm1hdHJpeCh0cmFpblssLTExXSksIHkgPSBhcy5tYXRyaXgodHJhaW5bLDExXSksIGFscGhhID0gMC41KSRsYW1iZGEubWluDQplbGFzdGljbmV0LmN2IDwtIGdsbW5ldCh4ID0gYXMubWF0cml4KHRyYWluWywtMTFdKSwgeSA9IGFzLm1hdHJpeCh0cmFpblssMTFdKSwgYWxwaGEgPSAwLjUsIGxhbWJkYSA9IGJlc3QubGFtYmRhKQ0KUk1TRShwcmVkaWN0KGVsYXN0aWNuZXQuY3YsIG5ld3ggPSBhcy5tYXRyaXgodHJhaW5bLC0xMV0pKSwgYXMubWF0cml4KHRyYWluWywxMV0pKQ0KUjIocHJlZGljdChlbGFzdGljbmV0LmN2LCBuZXd4ID0gYXMubWF0cml4KHRyYWluWywtMTFdKSksIGFzLm1hdHJpeCh0cmFpblssMTFdKSkNCg0KcGVyZm9ybWFjZV9tZXRyaWNzWzQsM10gPC0gUk1TRShwcmVkaWN0KGVsYXN0aWNuZXQuY3YsIG5ld3ggPSBhcy5tYXRyaXgodHJhaW5bLC0xMV0pKSwgYXMubWF0cml4KHRyYWluWywxMV0pKQ0KcGVyZm9ybWFjZV9tZXRyaWNzWzQsNF0gPC0gUjIocHJlZGljdChlbGFzdGljbmV0LmN2LCBuZXd4ID0gYXMubWF0cml4KHRyYWluWywtMTFdKSksIGFzLm1hdHJpeCh0cmFpblssMTFdKSkNCg0KUk1TRShwcmVkaWN0KGVsYXN0aWNuZXQuY3YsIG5ld3ggPSBhcy5tYXRyaXgodGVzdFssLTExXSkpLCBhcy5tYXRyaXgodGVzdFssMTFdKSkNClIyKHByZWRpY3QoZWxhc3RpY25ldC5jdiwgbmV3eCA9IGFzLm1hdHJpeCh0ZXN0WywtMTFdKSksIGFzLm1hdHJpeCh0ZXN0WywxMV0pKQ0KDQpwZXJmb3JtYWNlX21ldHJpY3NbNCw1XSA8LSBSTVNFKHByZWRpY3QoZWxhc3RpY25ldC5jdiwgbmV3eCA9IGFzLm1hdHJpeCh0ZXN0WywtMTFdKSksIGFzLm1hdHJpeCh0ZXN0WywxMV0pKQ0KcGVyZm9ybWFjZV9tZXRyaWNzWzQsNl0gPC0gUjIocHJlZGljdChlbGFzdGljbmV0LmN2LCBuZXd4ID0gYXMubWF0cml4KHRlc3RbLC0xMV0pKSwgYXMubWF0cml4KHRlc3RbLDExXSkpDQogIA0KIyBHcmFwaGljcw0KDQojIFRyYWluaW5nIGRhdGENCg0KeF90cmFpbiA8LSBtb2RlbC5tYXRyaXgoQXJ0aXN0X1BvcHVsYXJpdHl+LiwgdHJhaW4pWywtMV0NCg0KcmlkZ2VfcmVzMiA8LSBhcy5udW1lcmljKHRyYWluJEFydGlzdF9Qb3B1bGFyaXR5IC0gcHJlZGljdChyaWRnZS5jdiwgeF90cmFpbikpXjINCmxhc3NvX3JlczIgPC0gYXMubnVtZXJpYyh0cmFpbiRBcnRpc3RfUG9wdWxhcml0eSAtIHByZWRpY3QobGFzc28uY3YsIHhfdHJhaW4pKV4yDQplbGFzdGljbmV0X3JlczIgPC0gYXMubnVtZXJpYyh0cmFpbiRBcnRpc3RfUG9wdWxhcml0eSAtIHByZWRpY3QoZWxhc3RpY25ldC5jdiwgeF90cmFpbikpXjINCg0KcGFyKG1mY29sPWMoMiwyKSkNCmJhcnBsb3QoKHRyYWluJEFydGlzdF9Qb3B1bGFyaXR5IC0gcHJlZGljdChsbV90cmFpbiwgdHJhaW4pKV4yLCBtYWluID0gcGFzdGUoIkxNICh0cmFpbik6IFJNU0UgPSIscm91bmQoUk1TRShwcmVkaWN0KGxtX3RyYWluLCB0cmFpbiksIHRyYWluJEFydGlzdF9Qb3B1bGFyaXR5KSwgZGlnaXRzID0gMiksICIsIFJeMjoiLCByb3VuZChSMihwcmVkaWN0KGxtX3RyYWluLCB0cmFpbiksIHRyYWluJEFydGlzdF9Qb3B1bGFyaXR5KSwgZGlnaXRzID0gMikpKQ0KYmFycGxvdChyaWRnZV9yZXMyLCBtYWluID0gcGFzdGUoIlJpZGdlICh0cmFpbik6IFJNU0UgPSIscm91bmQoUk1TRShwcmVkaWN0KHJpZGdlLmN2LCB4X3RyYWluKSwgdHJhaW4kQXJ0aXN0X1BvcHVsYXJpdHkpLCBkaWdpdHMgPSAyKSwgIiwgUl4yOiIsIHJvdW5kKFIyKHByZWRpY3QocmlkZ2UuY3YsIHhfdHJhaW4pLCB0cmFpbiRBcnRpc3RfUG9wdWxhcml0eSksIGRpZ2l0cyA9IDIpKSkNCmJhcnBsb3QobGFzc29fcmVzMiwgbWFpbiA9IHBhc3RlKCJMQVNTTyAodHJhaW4pOiBSTVNFID0iLHJvdW5kKFJNU0UocHJlZGljdChsYXNzby5jdiwgeF90cmFpbiksIHRyYWluJEFydGlzdF9Qb3B1bGFyaXR5KSwgZGlnaXRzID0gMiksICIsIFJeMjoiLCByb3VuZChSMihwcmVkaWN0KGxhc3NvLmN2LCB4X3RyYWluKSwgdHJhaW4kQXJ0aXN0X1BvcHVsYXJpdHkpLCBkaWdpdHMgPSAyKSkpDQpiYXJwbG90KGVsYXN0aWNuZXRfcmVzMiwgbWFpbiA9IHBhc3RlKCJFbGFzdGljbmV0ICh0cmFpbik6IFJNU0UgPSIscm91bmQoUk1TRShwcmVkaWN0KGVsYXN0aWNuZXQuY3YsIHhfdHJhaW4pLCB0cmFpbiRBcnRpc3RfUG9wdWxhcml0eSksIGRpZ2l0cyA9IDIpLCAiLCBSXjI6Iiwgcm91bmQoUjIocHJlZGljdChlbGFzdGljbmV0LmN2LCB4X3RyYWluKSwgdHJhaW4kQXJ0aXN0X1BvcHVsYXJpdHkpLCBkaWdpdHMgPSAyKSkpDQoNCiMgVGVzdCBkYXRhDQoNCnhfdGVzdCA8LSBtb2RlbC5tYXRyaXgoQXJ0aXN0X1BvcHVsYXJpdHl+LiwgdGVzdClbLC0xXQ0KDQpyaWRnZV9yZXMyIDwtIGFzLm51bWVyaWModGVzdCRBcnRpc3RfUG9wdWxhcml0eSAtIHByZWRpY3QocmlkZ2UuY3YsIHhfdGVzdCkpXjINCmxhc3NvX3JlczIgPC0gYXMubnVtZXJpYyh0ZXN0JEFydGlzdF9Qb3B1bGFyaXR5IC0gcHJlZGljdChsYXNzby5jdiwgeF90ZXN0KSleMg0KZWxhc3RpY25ldF9yZXMyIDwtIGFzLm51bWVyaWModGVzdCRBcnRpc3RfUG9wdWxhcml0eSAtIHByZWRpY3QoZWxhc3RpY25ldC5jdiwgeF90ZXN0KSleMg0KDQpwYXIobWZjb2w9YygyLDIpKQ0KYmFycGxvdCgodGVzdCRBcnRpc3RfUG9wdWxhcml0eSAtIHByZWRpY3QobG1fdHJhaW4sIHRlc3QpKV4yLCBtYWluID0gcGFzdGUoIkxNICh0ZXN0KTogUk1TRSA9Iixyb3VuZChSTVNFKHByZWRpY3QobG1fdHJhaW4sIHRlc3QpLCB0ZXN0JEFydGlzdF9Qb3B1bGFyaXR5KSwgZGlnaXRzID0gMiksICIsIFJeMjoiLCByb3VuZChSMihwcmVkaWN0KGxtX3RyYWluLCB0ZXN0KSwgdGVzdCRBcnRpc3RfUG9wdWxhcml0eSksIGRpZ2l0cyA9IDIpKSkNCmJhcnBsb3QocmlkZ2VfcmVzMiwgbWFpbiA9IHBhc3RlKCJSaWRnZSAodGVzdCk6IFJNU0UgPSIscm91bmQoUk1TRShwcmVkaWN0KHJpZGdlLmN2LCB4X3Rlc3QpLCB0ZXN0JEFydGlzdF9Qb3B1bGFyaXR5KSwgZGlnaXRzID0gMiksICIsIFJeMjoiLCByb3VuZChSMihwcmVkaWN0KHJpZGdlLmN2LCB4X3Rlc3QpLCB0ZXN0JEFydGlzdF9Qb3B1bGFyaXR5KSwgZGlnaXRzID0gMikpKQ0KYmFycGxvdChsYXNzb19yZXMyLCBtYWluID0gcGFzdGUoIkxBU1NPICh0ZXN0KTogUk1TRSA9Iixyb3VuZChSTVNFKHByZWRpY3QobGFzc28uY3YsIHhfdGVzdCksIHRlc3QkQXJ0aXN0X1BvcHVsYXJpdHkpLCBkaWdpdHMgPSAyKSwgIiwgUl4yOiIsIHJvdW5kKFIyKHByZWRpY3QobGFzc28uY3YsIHhfdGVzdCksIHRlc3QkQXJ0aXN0X1BvcHVsYXJpdHkpLCBkaWdpdHMgPSAyKSkpDQpiYXJwbG90KGVsYXN0aWNuZXRfcmVzMiwgbWFpbiA9IHBhc3RlKCJFbGFzdGljbmV0ICh0ZXN0KTogUk1TRSA9Iixyb3VuZChSTVNFKHByZWRpY3QoZWxhc3RpY25ldC5jdiwgeF90ZXN0KSwgdGVzdCRBcnRpc3RfUG9wdWxhcml0eSksIGRpZ2l0cyA9IDIpLCAiLCBSXjI6Iiwgcm91bmQoUjIocHJlZGljdChlbGFzdGljbmV0LmN2LCB4X3Rlc3QpLCB0ZXN0JEFydGlzdF9Qb3B1bGFyaXR5KSwgZGlnaXRzID0gMikpKQ0KDQoNCg0KDQoNCmBgYA0KDQpgYGB7cn0NCg0KDQojIE5vbi1wYXJhbWV0cmljIGFuZCBzZW1pcGFyYW1ldHJpYyByZWdyZXNzaW9uDQoNCiMgS2VybmVsIGRlbnNpdHkgZXN0aW1hdG9yDQoNCmxpYnJhcnkoIm5wIikNCmJ3ICAgPC0gbnB1ZGVuc2J3KH5UcmFja19EdXJhdGlvbl9tcywgZGF0YT1kZikgIyBUcmFja19EdXJhdGlvbl9tcyBiaS1tb2RhbD8NCmZoYXQgPC0gbnB1ZGVucyhidykNCmZoYXQNCnBsb3QoZmhhdCwgbWFpbj1zcHJpbnRmKCIlcyB3aXRoIGg9JS4yZiIsIGZoYXQkcGNrZXJ0eXBlLCBmaGF0JGJ3KSkNCnJ1ZyhkZiRUcmFja19EdXJhdGlvbl9tcykNCg0KYncgPC0gbnB1ZGVuc2J3KH5BcnRpc3RfUG9wdWxhcml0eStUcmFja19EdXJhdGlvbl9tcywgZGF0YT1kZiwgYndtZXRob2Q9Im5vcm1hbC1yZWZlcmVuY2UiKQ0KZmhhdCA8LSBucHVkZW5zKGJ3KQ0KIyBwbG90KGZoYXQpICMgYmktbW9kYWwNCg0KcGFyKG1mY29sPWMoMSwxKSkNCiMxKSBhdCBUcmFja19EdXJhdGlvbl9tcyB+IDEgYW5kIEFydGlzdF9Qb3B1bGFyaXR5IH4gLTEgKG1lYW5pbmcgbG9uZ2VyIHRyYWNrcycgYXJ0aXN0cyBhcmUgbGVzcyBwb3B1bGFyKQ0KcGxvdChmaGF0LCB2aWV3PSJmaXhlZCIsIHBoaT0xMCwgdGhldGE9MzIwLCBtYWluID0gIiIpDQpwYXIobWZjb2w9YygxLDEpKQ0KIyAyKSBhdCBUcmFja19EdXJhdGlvbl9tcyB+IC0wLjUgYW5kIEFydGlzdF9Qb3B1bGFyaXR5IH4gMQ0KcGxvdChmaGF0LCB2aWV3PSJmaXhlZCIsIHBoaT0xMCwgdGhldGE9MTU1LCBtYWluID0gIiIpDQoNCnBhcihtZmNvbD1jKDEsMSkpDQpudzIgPC0gbnByZWcoQXJ0aXN0X1BvcHVsYXJpdHl+cGNfMitUcmFja19EdXJhdGlvbl9tcywgZGF0YT1kZikNCnN1bW1hcnkobncyKQ0KIyBwbG90KG53MikNCg0KIyBOYWRhcmF5YS1XYXRzb24gZXN0aW1hdG9yDQoNCiMgYncgPC0gbnByZWdidyhBcnRpc3RfUG9wdWxhcml0eX5UcmFja19EdXJhdGlvbl9tcywgZGF0YT1kZikNCiMgbWhhdCA8LSBucHJlZyhidykNCiMgbWFpbiA8LSBzcHJpbnRmKCIlcyB3aXRoIGg9JS4yZiIsIG1oYXQkcGNrZXJ0eXBlLCBtaGF0JGJ3KQ0KIyBwbG90KGRmJEFydGlzdF9Qb3B1bGFyaXR5LCBkZiRUcmFja19EdXJhdGlvbl9tcywgcGNoPTE5LCBjZXg9MC4zLCBtYWluPW1haW4pDQojIGluZCA8LSBvcmRlcihkZiRUcmFja19EdXJhdGlvbl9tcykNCiMgeHMgIDwtIGNiaW5kKGRmJFRyYWNrX0R1cmF0aW9uX21zLCBmaXR0ZWQobWhhdCkpW2luZCxdDQojIGxpbmVzKHhzWywxXSwgeHNbLDJdLCBjb2w9InJlZCIsIGx3ZD0yKQ0KIyBydWcoZGYkVHJhY2tfRHVyYXRpb25fbXMpDQoNCiMgYncgPC0gbnByZWdidyhBcnRpc3RfUG9wdWxhcml0eX5UcmFja19EdXJhdGlvbl9tcytUcmFja19Qb3B1bGFyaXR5LCBkYXRhPWRmKQ0KIyBtaGF0IDwtIG5wcmVnKGJ3KQ0KIyBwbG90KG1oYXQpDQoNCg0KcGxvdENvbnRvdXIgPC0gZnVuY3Rpb24gKG1vZGVsLCBkYXRhLCBuPTMwKSB7DQogIG1mIDwtIG1vZGVsLmZyYW1lKG1vZGVsJHRlcm1zLCBkYXRhKQ0KICBtYyA8LSBsYXBwbHkoYXMubGlzdChtZlssLTFdKSwgcHJldHR5LCBuPW4pDQogIHpjIDwtIHByZWRpY3QobW9kZWwsIGV4cGFuZC5ncmlkKG1jKSkNCiAgZGltKHpjKSA8LSBzYXBwbHkobWMsIGxlbmd0aCkNCiAgcjIgPC0gMS12YXIocmVzaWR1YWxzKG1vZGVsKSkvdmFyKG1mWywxXSkNCiAgY29udG91cihtY1tbMV1dLCBtY1tbMl1dLCB6YywgeGxhYj1uYW1lcyhtZilbMl0sIHlsYWI9bmFtZXMobWYpWzNdLA0KICAgICAgICAgICAgbWFpbj1zcHJpbnRmKCJSXjI9JS4zZiIsIHIyKSkNCiAgY2MgPC0gZ3JheSgwLjc1LTAuNzUqKG1mWywxXS1taW4obWZbLDFdKSkvKG1heChtZlssMV0pLW1pbihtZlssMV0pKSkNCiAgcG9pbnRzKG1mWywyXSwgbWZbLDNdLCBwY2g9MTksIGNleD0wLjUsIGNvbD1jYykNCiAgfQ0KDQoNCm1vZGVsIDwtIGxtKEFydGlzdF9Qb3B1bGFyaXR5fnBjXzEgKyBwY18yLCBkYXRhPWRmKQ0KcGFyKG1mcm93PWMoMSwxKSkNCnBsb3RDb250b3VyKG1vZGVsLCBkZikNCg0KYGBgDQoNCg0KDQpgYGB7cn0NCg0KIyBQYXJ0aWFsIGxpbmVhciBtb2RlbCAobWl4aW5nIGxpbmVhciBhbmQgc3BsaW5lIHRlcm1zKQ0KDQpsaWJyYXJ5KCJtZ2N2IikNCm1vZGVsIDwtIGdhbShBcnRpc3RfUG9wdWxhcml0eX5zKFRyYWNrX1BvcHVsYXJpdHkpK1RyYWNrX0R1cmF0aW9uX21zLCBkYXRhPWRmKQ0KIyBwbG90Q29udG91cihtb2RlbCwgZGYkVHJhY2tfUG9wdWxhcml0eSwgZGYkVHJhY2tfRHVyYXRpb25fbXMsIGRmJEFydGlzdF9Qb3B1bGFyaXR5KQ0KIyBwbG90KG1vZGVsKQ0KDQojIEFkZGl0aXZlIG1vZGVsDQoNCmxpYnJhcnkoImdhbSIpDQoNCmFtMSA8LSBnYW0oQXJ0aXN0X1BvcHVsYXJpdHkgfiBzKGZhXzEpICsgcyhmYV8yKSArIHMoZmFfMykgKyBzKGZhXzQpICsgcyhwY18xKSArIHMocGNfMikgKyBzKEFydGlzdF9Gb2xsb3dlcikgKyBzKGRheXNfcmVsZWFzZV9vcmlnKSArIHMoVHJhY2tfUG9wdWxhcml0eSkgKyBzKFRyYWNrX0R1cmF0aW9uX21zKSArIHMoZmFfMnNxKSArIHMocGNfMnNxKSArIHMoQXJ0aXN0X0ZvbGxvd2Vyc3EpLCBkYXRhPWRmKQ0KcHJpbnQoYW0xKQ0KDQpwYXIobWZyb3c9YygzLDQpKQ0KcGxvdChhbTEpDQpwbG90KGZpdHRlZChhbTEpLCByZXNpZHVhbHMoYW0xKSkNCmxpbmVzKGxvd2VzcyhmaXR0ZWQoYW0xKSwgcmVzaWR1YWxzKGFtMSkpLCBjb2w9InJlZCIsIGx3ZD0yKQ0KbiA8LSBucm93KGRmKQ0KMS1zdW0ocmVzaWR1YWxzKGFtMSleMikvKChuLTEpKnZhcihkZiRBcnRpc3RfUG9wdWxhcml0eSkpDQoNCnBlcmZvcm1hY2VfbWV0cmljc1s1LDFdIDwtIFJNU0UocHJlZGljdChhbTEsIG5ld2RhdGEgPSBkZiksIGRmJEFydGlzdF9Qb3B1bGFyaXR5KQ0KcGVyZm9ybWFjZV9tZXRyaWNzWzUsMl0gPC0gUjIocHJlZGljdChhbTEsIG5ld2RhdGEgPSBkZiksIGRmJEFydGlzdF9Qb3B1bGFyaXR5KQ0KDQphbTFfdHJhaW4gPC0gZ2FtKEFydGlzdF9Qb3B1bGFyaXR5IH4gcyhmYV8xKSArIHMoZmFfMikgKyBzKGZhXzMpICsgcyhmYV80KSArIHMocGNfMSkgKyBzKHBjXzIpICsgcyhBcnRpc3RfRm9sbG93ZXIpICsgcyhkYXlzX3JlbGVhc2Vfb3JpZykgKyBzKFRyYWNrX1BvcHVsYXJpdHkpICsgcyhUcmFja19EdXJhdGlvbl9tcykgKyBzKGZhXzJzcSkgKyBzKHBjXzJzcSkgKyBzKEFydGlzdF9Gb2xsb3dlcnNxKSwgZGF0YT10cmFpbikNCg0KcGVyZm9ybWFjZV9tZXRyaWNzWzUsM10gPC0gUk1TRShwcmVkaWN0KGFtMV90cmFpbiwgbmV3ZGF0YSA9IHRyYWluKSwgdHJhaW4kQXJ0aXN0X1BvcHVsYXJpdHkpDQpwZXJmb3JtYWNlX21ldHJpY3NbNSw0XSA8LSBSMihwcmVkaWN0KGFtMV90cmFpbiwgbmV3ZGF0YSA9IHRyYWluKSwgdHJhaW4kQXJ0aXN0X1BvcHVsYXJpdHkpDQoNCnBlcmZvcm1hY2VfbWV0cmljc1s1LDVdIDwtIFJNU0UocHJlZGljdChhbTFfdHJhaW4sIG5ld2RhdGEgPSB0ZXN0KSwgdGVzdCRBcnRpc3RfUG9wdWxhcml0eSkNCnBlcmZvcm1hY2VfbWV0cmljc1s1LDZdIDwtIFIyKHByZWRpY3QoYW0xX3RyYWluLCBuZXdkYXRhID0gdGVzdCksIHRlc3QkQXJ0aXN0X1BvcHVsYXJpdHkpDQoNCg0KIyBTaW5nbGUgaW5kZXggbW9kZWwNCg0Kc2ltIDwtIG5waW5kZXgoQXJ0aXN0X1BvcHVsYXJpdHkgfiBmYV8xICsgZmFfMiArIGZhXzMgKyBmYV80ICsgcGNfMSArIHBjXzIgKyBBcnRpc3RfRm9sbG93ZXIgKyBkYXlzX3JlbGVhc2Vfb3JpZyArIFRyYWNrX1BvcHVsYXJpdHkgKyBUcmFja19EdXJhdGlvbl9tcyArIGZhXzJzcSArIHBjXzJzcSArIEFydGlzdF9Gb2xsb3dlcnNxLCBkYXRhPWRmKQ0Kc3VtbWFyeShzaW0pDQoNCnBlcmZvcm1hY2VfbWV0cmljc1s2LDFdIDwtIFJNU0UocHJlZGljdChzaW0sIG5ld2RhdGEgPSBkZiksIGRmJEFydGlzdF9Qb3B1bGFyaXR5KQ0KcGVyZm9ybWFjZV9tZXRyaWNzWzYsMl0gPC0gUjIocHJlZGljdChzaW0sIG5ld2RhdGEgPSBkZiksIGRmJEFydGlzdF9Qb3B1bGFyaXR5KQ0KDQpzaW1fdHJhaW4gPC0gbnBpbmRleChBcnRpc3RfUG9wdWxhcml0eSB+IGZhXzEgKyBmYV8yICsgZmFfMyArIGZhXzQgKyBwY18xICsgcGNfMiArIEFydGlzdF9Gb2xsb3dlciArIGRheXNfcmVsZWFzZV9vcmlnICsgVHJhY2tfUG9wdWxhcml0eSArIFRyYWNrX0R1cmF0aW9uX21zICsgZmFfMnNxICsgcGNfMnNxICsgQXJ0aXN0X0ZvbGxvd2Vyc3EsIGRhdGE9dHJhaW4pDQoNCnBlcmZvcm1hY2VfbWV0cmljc1s2LDNdIDwtIFJNU0UocHJlZGljdChzaW1fdHJhaW4sIG5ld2RhdGEgPSB0cmFpbiksIHRyYWluJEFydGlzdF9Qb3B1bGFyaXR5KQ0KcGVyZm9ybWFjZV9tZXRyaWNzWzYsNF0gPC0gUjIocHJlZGljdChzaW1fdHJhaW4sIG5ld2RhdGEgPSB0cmFpbiksIHRyYWluJEFydGlzdF9Qb3B1bGFyaXR5KQ0KDQpwZXJmb3JtYWNlX21ldHJpY3NbNiw1XSA8LSBSTVNFKHByZWRpY3Qoc2ltX3RyYWluLCBuZXdkYXRhID0gdGVzdCksIHRlc3QkQXJ0aXN0X1BvcHVsYXJpdHkpDQpwZXJmb3JtYWNlX21ldHJpY3NbNiw2XSA8LSBSMihwcmVkaWN0KHNpbV90cmFpbiwgbmV3ZGF0YSA9IHRlc3QpLCB0ZXN0JEFydGlzdF9Qb3B1bGFyaXR5KQ0KDQpwYXIobWZyb3c9YygzLDQpKQ0KcGxvdChzaW0pDQpwbG90KGZpdHRlZChzaW0pLCByZXNpZHVhbHMoc2ltKSkNCmxpbmVzKGxvd2VzcyhmaXR0ZWQoc2ltKSwgcmVzaWR1YWxzKHNpbSkpLCBjb2w9InJlZCIsIGx3ZD0yKQ0KbiA8LSBucm93KGRmKQ0KMS1zdW0ocmVzaWR1YWxzKHNpbSleMikvKChuLTEpKnZhcihkZiRBcnRpc3RfUG9wdWxhcml0eSkpDQoNCiMgUHJvamVjdGlvbiBQdXJzdWl0IFJlZ3Jlc3Npb24NCg0KcHByIDwtIHBwcihBcnRpc3RfUG9wdWxhcml0eSB+IGZhXzEgKyBmYV8yICsgZmFfMyArIGZhXzQgKyBwY18xICsgcGNfMiArIEFydGlzdF9Gb2xsb3dlciArIGRheXNfcmVsZWFzZV9vcmlnICsgVHJhY2tfUG9wdWxhcml0eSArIFRyYWNrX0R1cmF0aW9uX21zICsgZmFfMnNxICsgcGNfMnNxICsgQXJ0aXN0X0ZvbGxvd2Vyc3EsIGRhdGE9ZGYsIG50ZXJtPTUpDQoNCnBlcmZvcm1hY2VfbWV0cmljc1s3LDFdIDwtIFJNU0UocHJlZGljdChwcHIsIG5ld2RhdGEgPSBkZiksIGRmJEFydGlzdF9Qb3B1bGFyaXR5KQ0KcGVyZm9ybWFjZV9tZXRyaWNzWzcsMl0gPC0gUjIocHJlZGljdChwcHIsIG5ld2RhdGEgPSBkZiksIGRmJEFydGlzdF9Qb3B1bGFyaXR5KQ0KDQpwcHJfdHJhaW4gPC0gcHByKEFydGlzdF9Qb3B1bGFyaXR5IH4gZmFfMSArIGZhXzIgKyBmYV8zICsgZmFfNCArIHBjXzEgKyBwY18yICsgQXJ0aXN0X0ZvbGxvd2VyICsgZGF5c19yZWxlYXNlX29yaWcgKyBUcmFja19Qb3B1bGFyaXR5ICsgVHJhY2tfRHVyYXRpb25fbXMgKyBmYV8yc3EgKyBwY18yc3EgKyBBcnRpc3RfRm9sbG93ZXJzcSwgZGF0YT10cmFpbiwgbnRlcm09NSkNCg0KcGVyZm9ybWFjZV9tZXRyaWNzWzcsM10gPC0gUk1TRShwcmVkaWN0KHBwcl90cmFpbiwgbmV3ZGF0YSA9IHRyYWluKSwgdHJhaW4kQXJ0aXN0X1BvcHVsYXJpdHkpDQpwZXJmb3JtYWNlX21ldHJpY3NbNyw0XSA8LSBSMihwcmVkaWN0KHBwcl90cmFpbiwgbmV3ZGF0YSA9IHRyYWluKSwgdHJhaW4kQXJ0aXN0X1BvcHVsYXJpdHkpDQpwZXJmb3JtYWNlX21ldHJpY3NbNyw1XSA8LSBSTVNFKHByZWRpY3QocHByX3RyYWluLCBuZXdkYXRhID0gdGVzdCksIHRlc3QkQXJ0aXN0X1BvcHVsYXJpdHkpDQpwZXJmb3JtYWNlX21ldHJpY3NbNyw2XSA8LSBSMihwcmVkaWN0KHBwcl90cmFpbiwgbmV3ZGF0YSA9IHRlc3QpLCB0ZXN0JEFydGlzdF9Qb3B1bGFyaXR5KQ0KDQpzdW1tYXJ5KHBwcikNCnBhcihtZnJvdz1jKDEsMSkpDQpwbG90KHBwcikNCnBsb3QoZml0dGVkKHBwciksIHJlc2lkdWFscyhwcHIpKQ0KbGluZXMobG93ZXNzKGZpdHRlZChwcHIpLCByZXNpZHVhbHMocHByKSksIGNvbD0icmVkIiwgbHdkPTIpDQpuIDwtIG5yb3coZGYpDQoxLXN1bShyZXNpZHVhbHMocHByKV4yKS8oKG4tMSkqdmFyKGRmJEFydGlzdF9Qb3B1bGFyaXR5KSkNCg0KDQoNCmBgYA0KDQpgYGB7cn0NCg0KbGlicmFyeSgicnBhcnQiKQ0Kc2V0LnNlZWQoNDIpDQp0cjEgPC0gcnBhcnQoQXJ0aXN0X1BvcHVsYXJpdHl+ZmFfMStmYV8yK2ZhXzMrZmFfNCtwY18xK3BjXzIrQXJ0aXN0X0ZvbGxvd2VyK2RheXNfcmVsZWFzZV9vcmlnDQogICAgICAgICAgICAgK1RyYWNrX1BvcHVsYXJpdHkrVHJhY2tfRHVyYXRpb25fbXMrZmFfMnNxK3BjXzJzcStBcnRpc3RfRm9sbG93ZXJzcSwgZGF0YT1kZiwgY3AgPSAwKQ0KDQp0cjENCnBhcihtZnJvdz1jKDEsMSkpDQpwbG90KHRyMSkNCnRleHQodHIxKQ0KIyBzdW1tYXJ5KHRyMSkNCg0KbGlicmFyeSgicnBhcnQucGxvdCIpDQpycGFydC5wbG90KHRyMSwgY2V4ID0gMC43KQ0KDQpuIDwtIG5yb3coeF9ub291dCkNCjEtc3VtKHJlc2lkdWFscyh0cjEpXjIpLygobi0xKSp2YXIoZGYkQXJ0aXN0X1BvcHVsYXJpdHkpKQ0KDQpSTVNFKHByZWRpY3QodHIxLCBuZXd4ID0gYXMubWF0cml4KGRmWywtMTFdKSksIGFzLm1hdHJpeChkZlssMTFdKSkNClIyKHByZWRpY3QodHIxLCBuZXd4ID0gYXMubWF0cml4KGRmWywtMTFdKSksIGFzLm1hdHJpeChkZlssMTFdKSkNCg0KcGVyZm9ybWFjZV9tZXRyaWNzWzgsMV0gPC0gUk1TRShwcmVkaWN0KHRyMSwgbmV3eCA9IGFzLm1hdHJpeChkZlssLTExXSkpLCBhcy5tYXRyaXgoZGZbLDExXSkpDQpwZXJmb3JtYWNlX21ldHJpY3NbOCwyXSA8LSBSMihwcmVkaWN0KHRyMSwgbmV3eCA9IGFzLm1hdHJpeChkZlssLTExXSkpLCBhcy5tYXRyaXgoZGZbLDExXSkpDQoNCnBhcihtZnJvdz1jKDEsMSkpDQpwcmludGNwKHRyMSkNCnBsb3RjcCh0cjEpDQp0cjEkY3B0YWJsZQ0KYmVzdC5jcCA8LSB0cjEkY3B0YWJsZVt3aGljaC5taW4odHIxJGNwdGFibGVbLCJ4ZXJyb3IiXSksIkNQIl0NCmJlc3QuY3ANCg0KZHRfcHJ1bmVkIDwtIHBydW5lKHRyMSwgY3A9YmVzdC5jcCkNCg0KMS1zdW0ocmVzaWR1YWxzKGR0X3BydW5lZCleMikvKChuLTEpKnZhcihkZiRBcnRpc3RfUG9wdWxhcml0eSkpDQpSTVNFKHByZWRpY3QoZHRfcHJ1bmVkLCBuZXd4ID0gYXMubWF0cml4KGRmWywtMTFdKSksIGFzLm1hdHJpeChkZlssMTFdKSkNClIyKHByZWRpY3QoZHRfcHJ1bmVkLCBuZXd4ID0gYXMubWF0cml4KGRmWywtMTFdKSksIGFzLm1hdHJpeChkZlssMTFdKSkgIyBsb3dlciBSXjIgYWZ0ZXIgcHJ1bmluZw0KDQpgYGANCmBgYHtyfQ0KDQpsaWJyYXJ5KCJjYXJldCIpDQoNCiMgRFQgQ1Ygb24gZnVsbCBkYXRhDQoNCmNhcmV0LmNvbnRyb2wgPC0gdHJhaW5Db250cm9sKG1ldGhvZCA9ICJyZXBlYXRlZGN2IiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG51bWJlciA9IDEwLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcmVwZWF0cyA9IDMpDQpzZXQuc2VlZCg0MikNCnJwYXJ0LmN2IDwtIGNhcmV0Ojp0cmFpbihBcnRpc3RfUG9wdWxhcml0eSB+IC4sIA0KICAgICAgICAgICAgICAgICAgICAgICAgIGRhdGEgPSBkZiwNCiAgICAgICAgICAgICAgICAgICAgICAgICBtZXRob2QgPSAicnBhcnQiLA0KICAgICAgICAgICAgICAgICAgICAgICAgIHRyQ29udHJvbCA9IGNhcmV0LmNvbnRyb2wsDQogICAgICAgICAgICAgICAgICAgICAgICAgdHVuZUxlbmd0aCA9IDE1KQ0KDQpycGFydC5jdg0KcnBhcnQuYmVzdC5jdiA8LSBycGFydC5jdiRmaW5hbE1vZGVsDQpycGFydC5iZXN0LmN2DQpycGFydC5wbG90KHJwYXJ0LmJlc3QuY3YpDQoNClJNU0UocHJlZGljdChycGFydC5iZXN0LmN2LCBuZXdkYXRhID0gZGYpLCBkZiRBcnRpc3RfUG9wdWxhcml0eSkgIyBjb3JyZXNwb25kcyBleGFjdGx5IHRvIHRoZSBwcnVuZWQgdHJlZSBzb2x1dGlvbg0KUjIocHJlZGljdChycGFydC5iZXN0LmN2LCBuZXdkYXRhID0gZGYpLCBkZiRBcnRpc3RfUG9wdWxhcml0eSkgIyBjb3JyZXNwb25kcyBleGFjdGx5IHRvIHRoZSBwcnVuZWQgdHJlZSBzb2x1dGlvbg0KDQojIERUIENWIG9uIHNwbGl0IGRhdGENCg0Kc2V0LnNlZWQoNDIpDQppZHgudHJhaW4gPC0gY3JlYXRlRGF0YVBhcnRpdGlvbih5ID0gZGYkQXJ0aXN0X1BvcHVsYXJpdHksIHAgPSAwLjc1LCBsaXN0ID0gRkFMU0UpDQp0cmFpbi5kYXRhIDwtIGRmW2lkeC50cmFpbiwgXQ0KdGVzdC5kYXRhIDwtIGRmWy1pZHgudHJhaW4sXQ0KDQpjYXJldC5jb250cm9sIDwtIHRyYWluQ29udHJvbChtZXRob2QgPSAicmVwZWF0ZWRjdiIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBudW1iZXIgPSAxMCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJlcGVhdHMgPSAzKQ0Kc2V0LnNlZWQoNDIpDQpycGFydC5jdiA8LSBjYXJldDo6dHJhaW4oQXJ0aXN0X1BvcHVsYXJpdHkgfiAuLCANCiAgICAgICAgICAgICAgICAgIGRhdGEgPSB0cmFpbi5kYXRhLA0KICAgICAgICAgICAgICAgICAgbWV0aG9kID0gInJwYXJ0IiwNCiAgICAgICAgICAgICAgICAgIHRyQ29udHJvbCA9IGNhcmV0LmNvbnRyb2wsDQogICAgICAgICAgICAgICAgICB0dW5lTGVuZ3RoID0gMTUpDQoNCnJwYXJ0LmN2DQpycGFydC5iZXN0LmN2IDwtIHJwYXJ0LmN2JGZpbmFsTW9kZWwNCnJwYXJ0LmJlc3QuY3YNCnJwYXJ0LnBsb3QocnBhcnQuYmVzdC5jdikNCg0KDQoNClJNU0UocHJlZGljdChycGFydC5iZXN0LmN2LCBuZXdkYXRhID0gdHJhaW4uZGF0YSksIHRyYWluLmRhdGEkQXJ0aXN0X1BvcHVsYXJpdHkpDQpSMihwcmVkaWN0KHJwYXJ0LmJlc3QuY3YsIG5ld2RhdGEgPSB0cmFpbi5kYXRhKSwgdHJhaW4uZGF0YSRBcnRpc3RfUG9wdWxhcml0eSkNCg0KcGVyZm9ybWFjZV9tZXRyaWNzWzgsM10gPC0gUk1TRShwcmVkaWN0KHJwYXJ0LmJlc3QuY3YsIG5ld2RhdGEgPSB0cmFpbi5kYXRhKSwgdHJhaW4uZGF0YSRBcnRpc3RfUG9wdWxhcml0eSkNCnBlcmZvcm1hY2VfbWV0cmljc1s4LDRdIDwtIFIyKHByZWRpY3QocnBhcnQuYmVzdC5jdiwgbmV3ZGF0YSA9IHRyYWluLmRhdGEpLCB0cmFpbi5kYXRhJEFydGlzdF9Qb3B1bGFyaXR5KQ0KDQpSTVNFKHByZWRpY3QocnBhcnQuYmVzdC5jdiwgbmV3ZGF0YSA9IHRlc3QuZGF0YSksIHRlc3QuZGF0YSRBcnRpc3RfUG9wdWxhcml0eSkNClIyKHByZWRpY3QocnBhcnQuYmVzdC5jdiwgbmV3ZGF0YSA9IHRlc3QuZGF0YSksIHRlc3QuZGF0YSRBcnRpc3RfUG9wdWxhcml0eSkNCg0KcGVyZm9ybWFjZV9tZXRyaWNzWzgsNV0gPC0gUk1TRShwcmVkaWN0KHJwYXJ0LmJlc3QuY3YsIG5ld2RhdGEgPSB0ZXN0LmRhdGEpLCB0ZXN0LmRhdGEkQXJ0aXN0X1BvcHVsYXJpdHkpDQpwZXJmb3JtYWNlX21ldHJpY3NbOCw2XSA8LSBSMihwcmVkaWN0KHJwYXJ0LmJlc3QuY3YsIG5ld2RhdGEgPSB0ZXN0LmRhdGEpLCB0ZXN0LmRhdGEkQXJ0aXN0X1BvcHVsYXJpdHkpDQoNCiMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMNCg0KbGlicmFyeSgicmFuZG9tRm9yZXN0IikNCnNldC5zZWVkKDQyKQ0KcmYgPC1yYW5kb21Gb3Jlc3QoQXJ0aXN0X1BvcHVsYXJpdHl+ZmFfMStmYV8yK2ZhXzMrZmFfNCtwY18xK3BjXzIrQXJ0aXN0X0ZvbGxvd2VyK2RheXNfcmVsZWFzZV9vcmlnDQogICAgICAgICAgICAgICAgICArVHJhY2tfUG9wdWxhcml0eStUcmFja19EdXJhdGlvbl9tcytmYV8yc3ErcGNfMnNxK0FydGlzdF9Gb2xsb3dlcnNxLCBkYXRhPWRmLCBjcCA9IDAsIG1ldGhvZCA9ICdhbm92YScpDQpzdW1tYXJ5KHJmKQ0KaW1wIDwtIGltcG9ydGFuY2UocmYpDQppbmQgPC0gb3JkZXIoaW1wLCBkZWNyZWFzaW5nPVQpDQppbXBbaW5kLF0NCnJmX3ByZWQgPC0gcHJlZGljdChyZiwgZGYsIHR5cGU9ImNsYXNzIikNCm4gPC0gbnJvdyhkZikNCjEtc3VtKHJlc2lkdWFscyhyZileMikvKChuLTEpKnZhcihkZiRBcnRpc3RfUG9wdWxhcml0eSkpDQoNClJNU0UocHJlZGljdChyZiwgbmV3ZGF0YSA9IGRmKSwgZGYkQXJ0aXN0X1BvcHVsYXJpdHkpDQpSMihwcmVkaWN0KHJmLCBuZXdkYXRhID0gZGYpLCBkZiRBcnRpc3RfUG9wdWxhcml0eSkNCg0KcGVyZm9ybWFjZV9tZXRyaWNzWzksMV0gPC0gUk1TRShwcmVkaWN0KHJmLCBuZXdkYXRhID0gZGYpLCBkZiRBcnRpc3RfUG9wdWxhcml0eSkNCnBlcmZvcm1hY2VfbWV0cmljc1s5LDJdIDwtIFIyKHByZWRpY3QocmYsIG5ld2RhdGEgPSBkZiksIGRmJEFydGlzdF9Qb3B1bGFyaXR5KQ0KDQojIFJNU0UocHJlZGljdChyZiwgbmV3ZGF0YSA9IHRyYWluLmRhdGEpLCB0cmFpbi5kYXRhJEFydGlzdF9Qb3B1bGFyaXR5KQ0KIyBSMihwcmVkaWN0KHJmLCBuZXdkYXRhID0gdHJhaW4uZGF0YSksIHRyYWluLmRhdGEkQXJ0aXN0X1BvcHVsYXJpdHkpDQoNCiMgUk1TRShwcmVkaWN0KHJmLCBuZXdkYXRhID0gdGVzdC5kYXRhKSwgdGVzdC5kYXRhJEFydGlzdF9Qb3B1bGFyaXR5KQ0KIyBSMihwcmVkaWN0KHJmLCBuZXdkYXRhID0gdGVzdC5kYXRhKSwgdGVzdC5kYXRhJEFydGlzdF9Qb3B1bGFyaXR5KQ0KDQoNCiMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMNCmxpYnJhcnkoImRhdGEudGFibGUiKQ0KbGlicmFyeSgibWxyIikNCmxpYnJhcnkoInBhcmFsbGVsIikNCg0KcmZfdHJhaW4gPC0gYXMuZGF0YS50YWJsZSh0cmFpbikNCnJmX3Rlc3QgPC0gYXMuZGF0YS50YWJsZSh0ZXN0KQ0KDQp0YXNrIDwtIG1ha2VSZWdyVGFzayhkYXRhID0gdHJhaW4sIHRhcmdldCA9ICJBcnRpc3RfUG9wdWxhcml0eSIpDQoNCnJmX2xlYXJuZXIgPC0gbWFrZUxlYXJuZXIoInJlZ3IucmFuZG9tRm9yZXN0IiwgDQogICAgICAgICAgICAgICAgICAgICAgICAgIHByZWRpY3QudHlwZSA9ICJyZXNwb25zZSIpDQoNCnJmLnBhcm1zIDwtIG1ha2VQYXJhbVNldCgNCiAgIyBUaGUgcmVjb21tZW5kYXRpb24gZm9yIG10cnkgYnkgQnJlaW1hbiBpcyBzcXVhcmVyb290IG51bWJlciBvZiBjb2x1bW5zDQogIG1ha2VEaXNjcmV0ZVBhcmFtKCJtdHJ5IiwgdmFsdWVzID0gYygyLDMsNCw1LDYpKSwgIyBOdW1iZXIgb2YgZmVhdHVyZXMgc2VsZWN0ZWQgYXQgZWFjaCBub2RlLCBzbWFsbGVyIC0+IGZhc3Rlcg0KICBtYWtlRGlzY3JldGVQYXJhbSgic2FtcHNpemUiLCB2YWx1ZXMgPSAgYygzMCwgNTAsIDcwKSksICMgYm9vdHN0cmFwIHNhbXBsZSBzaXplLCBzbWFsbGVyIC0+IGZhc3Rlcg0KICBtYWtlRGlzY3JldGVQYXJhbSgibnRyZWUiLCB2YWx1ZXMgPSBjKDEwLDMwLDUwLDEwMCwgNTAwLCAxMDAwKSkgIyBOdW1iZXIgb2YgdHJlZSwgc21hbGxlciAtPiBmYXN0ZXINCikgDQoNCnR1bmVDb250cm9sIDwtIG1ha2VUdW5lQ29udHJvbEdyaWQocmVzb2x1dGlvbiA9IDMsIHR1bmUudGhyZXNob2xkID0gRkFMU0UpDQoNCnJkZXNjIDwtIG1ha2VSZXNhbXBsZURlc2MobWV0aG9kID0gIkNWIiwgaXRlcnMgPSA1LCBzdHJhdGlmeSA9IEZBTFNFKQ0KDQpsaWJyYXJ5KCJwYXJhbGxlbE1hcCIpDQoNCnBhcmFsbGVsU3RhcnRTb2NrZXQoNCwgbGV2ZWwgPSAibWxyLnR1bmVQYXJhbXMiKQ0KDQojdHVuaW5nIDwtIHR1bmVQYXJhbXMocmZfbGVhcm5lciwgdGFzayA9IHRhc2ssIHJlc2FtcGxpbmcgPSByZGVzYywNCiMgICAgICAgICAgICAgICAgICAgICBwYXIuc2V0ID0gcmYucGFybXMsIGNvbnRyb2wgPSB0dW5lQ29udHJvbCwgbWVhc3VyZXMgPSBtbHI6OnJtc2UpDQoNCiMgaHR0cHM6Ly9zdGFja292ZXJmbG93LmNvbS9xdWVzdGlvbnMvNTEzMzM0MTAvbWxyLXdoeS1kb2VzLXJlcHJvZHVjaWJpbGl0eS1vZi1oeXBlcnBhcmFtZXRlci10dW5pbmctZmFpbC11c2luZy1wYXJhbGxlbGl6YXRpbw0KDQpzdXBwcmVzc01lc3NhZ2VzKHsNCg0KICBzZXQuc2VlZCgxMjM0NTYsICJMJ0VjdXllci1DTVJHIikNCiAgY2x1c3RlclNldFJOR1N0cmVhbShpc2VlZCA9IDEyMzQ1NikNCiAgdHVuaW5nIDwtIHR1bmVQYXJhbXMocmZfbGVhcm5lciwgdGFzayA9IHRhc2ssIHJlc2FtcGxpbmcgPSByZGVzYywNCiAgICAgICAgICAgICAgICAgICAgIHBhci5zZXQgPSByZi5wYXJtcywgY29udHJvbCA9IHR1bmVDb250cm9sLCBtZWFzdXJlcyA9IG1scjo6cm1zZSkNCiAgDQp9KQ0KDQpwYXJhbGxlbFN0b3AoKQ0KdHVuaW5nJHgNCg0KdHVuaW5nX3Jlc3VsdHMgPC0gZ2VuZXJhdGVIeXBlclBhcnNFZmZlY3REYXRhKHR1bmluZywgcGFydGlhbC5kZXAgPSBUUlVFKQ0KDQp0dW5pbmdfcmVzdWx0cyRkYXRhDQoNCnRhcHBseSh0dW5pbmdfcmVzdWx0cyRkYXRhJHJtc2UudGVzdC5ybXNlLCBJTkRFWCA9IGModHVuaW5nX3Jlc3VsdHMkZGF0YSRtdHJ5KSwgbWVhbikNCnJmX3R1bmVkIDwtIHNldEh5cGVyUGFycyhyZl9sZWFybmVyLCBwYXIudmFscyA9IHR1bmluZyR4KQ0KcmZfbW9kZWwgPC0gbWxyOjp0cmFpbihyZl90dW5lZCwgdGFzayA9IHRhc2spDQpyZl9wcmVkX3RyYWluIDwtIHByZWRpY3QocmZfbW9kZWwsIG5ld2RhdGEgPSB0cmFpbiwgdHlwZSA9ICJyZXNwb25zZSIpDQpyZl9wcmVkX3Rlc3QgPC0gcHJlZGljdChyZl9tb2RlbCwgbmV3ZGF0YSA9IHRlc3QsIHR5cGUgPSAicmVzcG9uc2UiKQ0KIyByZl9wcmVkX3RyYWluJGRhdGEkcmVzcG9uc2UNCiMgcmZfcHJlZF90ZXN0JGRhdGEkcmVzcG9uc2UNCg0KUk1TRShwcmVkaWN0KHJmX21vZGVsLCBuZXdkYXRhID0gdHJhaW4sIHR5cGUgPSAicmVzcG9uc2UiKSRkYXRhJHJlc3BvbnNlLCB0cmFpbiRBcnRpc3RfUG9wdWxhcml0eSkNClIyKHByZWRpY3QocmZfbW9kZWwsIG5ld2RhdGEgPSB0cmFpbiwgdHlwZSA9ICJyZXNwb25zZSIpJGRhdGEkcmVzcG9uc2UsIHRyYWluJEFydGlzdF9Qb3B1bGFyaXR5KQ0KDQpwZXJmb3JtYWNlX21ldHJpY3NbOSwzXSA8LSBSTVNFKHByZWRpY3QocmZfbW9kZWwsIG5ld2RhdGEgPSB0cmFpbiwgdHlwZSA9ICJyZXNwb25zZSIpJGRhdGEkcmVzcG9uc2UsIHRyYWluJEFydGlzdF9Qb3B1bGFyaXR5KQ0KcGVyZm9ybWFjZV9tZXRyaWNzWzksNF0gPC0gUjIocHJlZGljdChyZl9tb2RlbCwgbmV3ZGF0YSA9IHRyYWluLCB0eXBlID0gInJlc3BvbnNlIikkZGF0YSRyZXNwb25zZSwgdHJhaW4kQXJ0aXN0X1BvcHVsYXJpdHkpDQoNClJNU0UocHJlZGljdChyZl9tb2RlbCwgbmV3ZGF0YSA9IHRlc3QsIHR5cGUgPSAiY2xhc3MiKSRkYXRhJHJlc3BvbnNlLCB0ZXN0JEFydGlzdF9Qb3B1bGFyaXR5KQ0KUjIocHJlZGljdChyZl9tb2RlbCwgbmV3ZGF0YSA9IHRlc3QsIHR5cGUgPSAiY2xhc3MiKSRkYXRhJHJlc3BvbnNlLCB0ZXN0JEFydGlzdF9Qb3B1bGFyaXR5KQ0KDQpwZXJmb3JtYWNlX21ldHJpY3NbOSw1XSA8LSBSTVNFKHByZWRpY3QocmZfbW9kZWwsIG5ld2RhdGEgPSB0ZXN0LCB0eXBlID0gInJlc3BvbnNlIikkZGF0YSRyZXNwb25zZSwgdGVzdCRBcnRpc3RfUG9wdWxhcml0eSkNCnBlcmZvcm1hY2VfbWV0cmljc1s5LDZdIDwtIFIyKHByZWRpY3QocmZfbW9kZWwsIG5ld2RhdGEgPSB0ZXN0LCB0eXBlID0gInJlc3BvbnNlIikkZGF0YSRyZXNwb25zZSwgdGVzdCRBcnRpc3RfUG9wdWxhcml0eSkNCg0KYGBgDQpgYGB7cn0NCg0KbGlicmFyeSgiTUFTUyIpDQpsaWJyYXJ5KCJubmV0IikNCg0KZGZfeiA8LSBzY2FsZShhcy5tYXRyaXgoZGYpKQ0KdHJhaW5feiA8LSBhcy5kYXRhLmZyYW1lKGRmX3pbaWR4LnRyYWluLCBdKQ0KdGVzdF96IDwtIGFzLmRhdGEuZnJhbWUoZGZfelstaWR4LnRyYWluLF0pDQoNCm1lYW5fbWF0IDwtIG1hdHJpeCgsIG5yb3cgPSBkaW0oZGYpWzFdLCBuY29sID0gZGltKGRmKVsyXSkNCm1lYW5fbWF0WzEsIF0gPC0gYXMubnVtZXJpYyhzYXBwbHkoZGYsIG1lYW4sIG5hLnJtID0gVFJVRSkpDQoNCmxpYnJhcnkoInpvbyIpDQptZWFuX21hdCA8LSBuYS5sb2NmKG1lYW5fbWF0KQ0KDQpkaWFnX3NkIDwtIGRpYWcoc2FwcGx5KGRmLCBzZCwgbmEucm0gPSBUUlVFKSkNCg0KIyBCYWNrdHJhbnNmb3JtYXRpb24gc2NhbGUgdG8gb3JnaW5hbA0KDQojIGFzLm1hdHJpeChkZl96KSAlKiUgZGlhZ19zZCArIG1lYW5fbWF0DQoNCnRhc2sgPC0gbWFrZVJlZ3JUYXNrKGRhdGEgPSB0cmFpbl96LCB0YXJnZXQgPSAiQXJ0aXN0X1BvcHVsYXJpdHkiKQ0Kbm5ldCA8LSBtYWtlTGVhcm5lcigicmVnci5ubmV0IiwgcHJlZGljdC50eXBlID0gInJlc3BvbnNlIiwgcGFyLnZhbHMgPSBsaXN0KCJ0cmFjZSIgPSBGQUxTRSwgIm1heGl0IiA9IDEwMDAsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiTWF4Tld0cyIgPSA4MDAwMCkpDQoNCm5uLnBhcm1zIDwtIG1ha2VQYXJhbVNldCgNCiAgbWFrZURpc2NyZXRlUGFyYW0oImRlY2F5IiwgdmFsdWVzID0gYygwLjAwMDEsMC4wMDEsIDAuMDEsIDAuMSkpLCANCiAgbWFrZURpc2NyZXRlUGFyYW0oInNpemUiLCB2YWx1ZXMgPSBjKDIsNCw2LDgsMTAsMTIsMTQpKSkNCg0KdHVuZUNvbnRyb2wgPC0gbWFrZVR1bmVDb250cm9sR3JpZChyZXNvbHV0aW9uID0gMywgdHVuZS50aHJlc2hvbGQgPSBGQUxTRSkNCg0KcmRlc2MgPC0gbWFrZVJlc2FtcGxlRGVzYyhtZXRob2QgPSAiQ1YiLCBpdGVycyA9IDUsIHN0cmF0aWZ5ID0gRkFMU0UpDQoNCg0KcGFyYWxsZWxTdGFydFNvY2tldCg0LCBsZXZlbCA9ICJtbHIudHVuZVBhcmFtcyIpDQoNCiMgdHVuaW5nIDwtIHR1bmVQYXJhbXMobm5ldCwgdGFzayA9IHRhc2ssIHJlc2FtcGxpbmcgPSByZGVzYywgcGFyLnNldCA9IG5uLnBhcm1zLCBjb250cm9sID0gdHVuZUNvbnRyb2wsDQojICAgICAgICAgICAgICAgICAgICAgbWVhc3VyZXMgPSBtbHI6OnJtc2UpDQoNCnN1cHByZXNzTWVzc2FnZXMoew0KDQogIHNldC5zZWVkKDEyMzQ1NiwgIkwnRWN1eWVyLUNNUkciKQ0KICBjbHVzdGVyU2V0Uk5HU3RyZWFtKGlzZWVkID0gMTIzNDU2KQ0KICB0dW5pbmcgPC0gdHVuZVBhcmFtcyhubmV0LCB0YXNrID0gdGFzaywgcmVzYW1wbGluZyA9IHJkZXNjLCBwYXIuc2V0ID0gbm4ucGFybXMsIGNvbnRyb2wgPSB0dW5lQ29udHJvbCwNCiAgICAgICAgICAgICAgICAgICAgIG1lYXN1cmVzID0gbWxyOjpybXNlKQ0KfSkNCg0KcGFyYWxsZWxTdG9wKCkNCnR1bmluZyR4ICMgcmVzdWx0IHdpdGggZnVsbCBkYXRhIHNldCB3YXMgZGVjYXkgPSAxZS0wNCwgc2l6ZSA9IDIsIHJtc2UudGVzdC5ybXNlID0gMC40MzM2NDg2DQp0dW5pbmdfcmVzdWx0cyA8LSBnZW5lcmF0ZUh5cGVyUGFyc0VmZmVjdERhdGEodHVuaW5nLCBwYXJ0aWFsLmRlcCA9IEZBTFNFKQ0KDQpwbG90SHlwZXJQYXJzRWZmZWN0KHR1bmluZ19yZXN1bHRzLCB4ID0gInNpemUiLCB5ID0gInJtc2UudGVzdC5ybXNlIikNCnBsb3RIeXBlclBhcnNFZmZlY3QodHVuaW5nX3Jlc3VsdHMsIHggPSAiZGVjYXkiLCB5ID0gInJtc2UudGVzdC5ybXNlIikNCg0Kbm5ldC50dW5lZCA8LSBzZXRIeXBlclBhcnMobm5ldCwgcGFyLnZhbHMgPSB0dW5pbmckeCkNCm5uX21vZGVsIDwtIG1scjo6dHJhaW4obm5ldC50dW5lZCwgdGFzayA9IHRhc2spDQoNCm5uX3ByZWRfdHJhaW4gPC0gcHJlZGljdChubl9tb2RlbCwgbmV3ZGF0YSA9IHRyYWluX3osIHR5cGUgPSAicmVzcG9uc2UiKQ0Kbm5fcHJlZF90ZXN0IDwtIHByZWRpY3Qobm5fbW9kZWwsIG5ld2RhdGEgPSB0ZXN0X3osIHR5cGUgPSAicmVzcG9uc2UiKQ0KDQpSTVNFKHByZWRpY3Qobm5fbW9kZWwsIG5ld2RhdGEgPSB0cmFpbl96LCB0eXBlID0gInJlc3BvbnNlIikkZGF0YSRyZXNwb25zZSwgdHJhaW5feiRBcnRpc3RfUG9wdWxhcml0eSkNClIyKHByZWRpY3Qobm5fbW9kZWwsIG5ld2RhdGEgPSB0cmFpbl96LCB0eXBlID0gInJlc3BvbnNlIikkZGF0YSRyZXNwb25zZSwgdHJhaW5feiRBcnRpc3RfUG9wdWxhcml0eSkNCg0KUk1TRShwcmVkaWN0KG5uX21vZGVsLCBuZXdkYXRhID0gdGVzdF96LCB0eXBlID0gInJlc3BvbnNlIikkZGF0YSRyZXNwb25zZSwgdGVzdF96JEFydGlzdF9Qb3B1bGFyaXR5KQ0KUjIocHJlZGljdChubl9tb2RlbCwgbmV3ZGF0YSA9IHRlc3RfeiwgdHlwZSA9ICJyZXNwb25zZSIpJGRhdGEkcmVzcG9uc2UsIHRlc3RfeiRBcnRpc3RfUG9wdWxhcml0eSkNCg0KIyBTY2FsZSBwcmVkaWN0aW9ucyBiYWNrIHRvIG9yaWdpbmFsOg0KDQojIGFzLm1hdHJpeCh0cmFpbl96WywgMTFdKSAlKiUgZGlhZ19zZFsxMSwxMV0gKyBtZWFuX21hdFsxLDExXSA9PSB0cmFpblssMTFdDQoNClJNU0UoYXMubWF0cml4KG5uX3ByZWRfdHJhaW4kZGF0YSRyZXNwb25zZSkgJSolIGRpYWdfc2RbMTEsMTFdICsgbWVhbl9tYXRbMSwxMV0sIHRyYWluJEFydGlzdF9Qb3B1bGFyaXR5KQ0KUjIoYXMubWF0cml4KG5uX3ByZWRfdHJhaW4kZGF0YSRyZXNwb25zZSkgJSolIGRpYWdfc2RbMTEsMTFdICsgbWVhbl9tYXRbMSwxMV0sIHRyYWluJEFydGlzdF9Qb3B1bGFyaXR5KQ0KDQpwZXJmb3JtYWNlX21ldHJpY3NbMTAsM10gPC0gUk1TRShhcy5tYXRyaXgobm5fcHJlZF90cmFpbiRkYXRhJHJlc3BvbnNlKSAlKiUgZGlhZ19zZFsxMSwxMV0gKyBtZWFuX21hdFsxLDExXSwgdHJhaW4kQXJ0aXN0X1BvcHVsYXJpdHkpDQpwZXJmb3JtYWNlX21ldHJpY3NbMTAsNF0gPC0gUjIoYXMubWF0cml4KG5uX3ByZWRfdHJhaW4kZGF0YSRyZXNwb25zZSkgJSolIGRpYWdfc2RbMTEsMTFdICsgbWVhbl9tYXRbMSwxMV0sIHRyYWluJEFydGlzdF9Qb3B1bGFyaXR5KQ0KDQpSTVNFKGFzLm1hdHJpeChubl9wcmVkX3Rlc3QkZGF0YSRyZXNwb25zZSkgJSolIGRpYWdfc2RbMTEsMTFdICsgbWVhbl9tYXRbMSwxMV0sIHRlc3QkQXJ0aXN0X1BvcHVsYXJpdHkpDQpSMihhcy5tYXRyaXgobm5fcHJlZF90ZXN0JGRhdGEkcmVzcG9uc2UpICUqJSBkaWFnX3NkWzExLDExXSArIG1lYW5fbWF0WzEsMTFdLCB0ZXN0JEFydGlzdF9Qb3B1bGFyaXR5KQ0KDQpwZXJmb3JtYWNlX21ldHJpY3NbMTAsNV0gPC0gUk1TRShhcy5tYXRyaXgobm5fcHJlZF90ZXN0JGRhdGEkcmVzcG9uc2UpICUqJSBkaWFnX3NkWzExLDExXSArIG1lYW5fbWF0WzEsMTFdLCB0ZXN0JEFydGlzdF9Qb3B1bGFyaXR5KQ0KcGVyZm9ybWFjZV9tZXRyaWNzWzEwLDZdIDwtIFIyKGFzLm1hdHJpeChubl9wcmVkX3Rlc3QkZGF0YSRyZXNwb25zZSkgJSolIGRpYWdfc2RbMTEsMTFdICsgbWVhbl9tYXRbMSwxMV0sIHRlc3QkQXJ0aXN0X1BvcHVsYXJpdHkpDQoNCg0KYGBgDQoNCg0KYGBge3J9DQoNCnJvdW5kKHBlcmZvcm1hY2VfbWV0cmljcywgZGlnaXRzID0gMikNCg0KDQoNCmBgYA0KDQoNCmBgYHtyfQ0KIyBDbGFzc2lmaWNhdGlvbg0KDQpkZiRDaGFydHMgPC0gYXMuZmFjdG9yKHhfbm9vdXQkQ2hhcnRzKQ0Kc2V0LnNlZWQoNDIpDQpycGFydCA8LSBycGFydChkYXRhID0gZGYsQ2hhcnRzfi4sbWV0aG9kID0gJ2NsYXNzJykNCnJwYXJ0LnBsb3QocnBhcnQpDQpyZl9wcmVkIDwtIHByZWRpY3QocnBhcnQsIG5ld2RhdGEgPSBkZiwgdHlwZSA9ICJjbGFzcyIpDQpjb25mTWF0IDwtIHRhYmxlKGRmJENoYXJ0cyxyZl9wcmVkKQ0KY29uZk1hdCAjIDA6IG5vdCBpbiBjaGFydHMsIDE6IGluIGNoYXJ0cw0KYWNjdXJhY3kgPC0gc3VtKGRpYWcoY29uZk1hdCkpL3N1bShjb25mTWF0KQ0KYWNjdXJhY3kNCg0KIyAwLjk2MjM2NTYNCg0KcGFyKG1mcm93PWMoMSwxKSkNCnByaW50Y3AocnBhcnQpDQpwbG90Y3AocnBhcnQpDQpycGFydCRjcHRhYmxlDQpiZXN0LmNwIDwtIHJwYXJ0JGNwdGFibGVbd2hpY2gubWluKHJwYXJ0JGNwdGFibGVbLCJ4ZXJyb3IiXSksIkNQIl0NCmJlc3QuY3ANCg0Kc2V0LnNlZWQoNDIpDQpycGFydF9wcnVuZWQgPC0gcHJ1bmUocnBhcnQsIGNwPWJlc3QuY3ApDQpycGFydC5wbG90KHJwYXJ0X3BydW5lZCkNCnJmX3ByZWQgPC0gcHJlZGljdChycGFydF9wcnVuZWQsIG5ld2RhdGEgPSBkZiwgdHlwZSA9ICJjbGFzcyIpDQpjb25mTWF0IDwtIHRhYmxlKGRmJENoYXJ0cyxyZl9wcmVkKQ0KY29uZk1hdCAjIDA6IG5vdCBpbiBjaGFydHMsIDE6IGluIGNoYXJ0cw0KYWNjdXJhY3kgPC0gc3VtKGRpYWcoY29uZk1hdCkpL3N1bShjb25mTWF0KQ0KYWNjdXJhY3kNCg0KIyAwLjk1Njk4OTINCg0KbGlicmFyeSgiY2FyZXQiKQ0KDQp0cmFpbi5kYXRhIDwtIGRmW2lkeC50cmFpbiwgXQ0KdGVzdC5kYXRhIDwtIGRmWy1pZHgudHJhaW4sXQ0KDQpjYXJldC5jb250cm9sIDwtIHRyYWluQ29udHJvbChtZXRob2QgPSAicmVwZWF0ZWRjdiIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBudW1iZXIgPSAxMCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJlcGVhdHMgPSAzKQ0KDQpzZXQuc2VlZCg0MikNCnJwYXJ0LmN2IDwtIGNhcmV0Ojp0cmFpbihDaGFydHMgfiAuLCANCiAgICAgICAgICAgICAgICAgICAgICAgICBkYXRhID0gdHJhaW4uZGF0YSwNCiAgICAgICAgICAgICAgICAgICAgICAgICBtZXRob2QgPSAicnBhcnQiLA0KICAgICAgICAgICAgICAgICAgICAgICAgIHRyQ29udHJvbCA9IGNhcmV0LmNvbnRyb2wsDQogICAgICAgICAgICAgICAgICAgICAgICAgdHVuZUxlbmd0aCA9IDE1KQ0KDQpycGFydC5jdg0KDQpzZXQuc2VlZCg0MikNCnJwYXJ0LmJlc3QuY3YgPC0gcnBhcnQuY3YkZmluYWxNb2RlbA0KcnBhcnQuYmVzdC5jdg0KcnBhcnQucGxvdChycGFydC5iZXN0LmN2KQ0KDQpycGFydF9wcmVkIDwtIHByZWRpY3QocnBhcnQuYmVzdC5jdiwgbmV3ZGF0YSA9IHRyYWluLmRhdGEsIHR5cGUgPSAiY2xhc3MiKQ0KY29uZk1hdCA8LSB0YWJsZSh0cmFpbi5kYXRhJENoYXJ0cyxycGFydF9wcmVkKQ0KY29uZk1hdCAjIDA6IG5vdCBpbiBjaGFydHMsIDE6IGluIGNoYXJ0cw0KYWNjdXJhY3kgPC0gc3VtKGRpYWcoY29uZk1hdCkpL3N1bShjb25mTWF0KQ0KYWNjdXJhY3kNCg0KIyAwLjk1MDcwNDINCg0KcnBhcnRfcHJlZCA8LSBwcmVkaWN0KHJwYXJ0LmJlc3QuY3YsIG5ld2RhdGEgPSB0ZXN0LmRhdGEsIHR5cGUgPSAiY2xhc3MiKQ0KY29uZk1hdCA8LSB0YWJsZSh0ZXN0LmRhdGEkQ2hhcnRzLHJwYXJ0X3ByZWQpDQpjb25mTWF0ICMgMDogbm90IGluIGNoYXJ0cywgMTogaW4gY2hhcnRzDQphY2N1cmFjeSA8LSBzdW0oZGlhZyhjb25mTWF0KSkvc3VtKGNvbmZNYXQpDQphY2N1cmFjeQ0KDQojIDAuOTMxODE4Mg0KDQojIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjDQoNCiMgUmFuZG9tIGZvcmVzdA0KDQpyZl90cmFpbiA8LSB0cmFpbi5kYXRhDQpyZl90ZXN0IDwtIHRlc3QuZGF0YQ0KdGFzayA8LSBtYWtlQ2xhc3NpZlRhc2soZGF0YSA9IHJmX3RyYWluLCB0YXJnZXQgPSAiQ2hhcnRzIiwgcG9zaXRpdmUgPSAiMSIpDQpyZl9sZWFybmVyIDwtIG1ha2VMZWFybmVyKCJjbGFzc2lmLnJhbmRvbUZvcmVzdCIsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICBwcmVkaWN0LnR5cGUgPSAicHJvYiIsICMgcHJlZGljdGlvbiB0eXBlIG5lZWRzIHRvIGJlIHNwZWNpZmllZCBmb3IgdGhlIGxlYXJuZXIgDQogICAgICAgICAgICAgICAgICAgICAgICAgIHBhci52YWxzID0gbGlzdCgicmVwbGFjZSIgPSBUUlVFLCAiaW1wb3J0YW5jZSIgPSBGQUxTRSkpDQoNCnJmLnBhcm1zIDwtIG1ha2VQYXJhbVNldCgNCiAgIyBUaGUgcmVjb21tZW5kYXRpb24gZm9yIG10cnkgYnkgQnJlaW1hbiBpcyBzcXVhcmVyb290IG51bWJlciBvZiBjb2x1bW5zDQogIG1ha2VEaXNjcmV0ZVBhcmFtKCJtdHJ5IiwgdmFsdWVzID0gYygyLDMsNCw1LDYpKSwgIyBOdW1iZXIgb2YgZmVhdHVyZXMgc2VsZWN0ZWQgYXQgZWFjaCBub2RlLCBzbWFsbGVyIC0+IGZhc3Rlcg0KICBtYWtlRGlzY3JldGVQYXJhbSgic2FtcHNpemUiLCB2YWx1ZXMgPSAgYygzMCwgNTAsIDcwKSksICMgYm9vdHN0cmFwIHNhbXBsZSBzaXplLCBzbWFsbGVyIC0+IGZhc3Rlcg0KICBtYWtlRGlzY3JldGVQYXJhbSgibnRyZWUiLCB2YWx1ZXMgPSBjKDEwLDMwLDUwLDEwMCwgNTAwLCAxMDAwKSkgIyBOdW1iZXIgb2YgdHJlZSwgc21hbGxlciAtPiBmYXN0ZXINCikgDQoNCnR1bmVDb250cm9sIDwtIG1ha2VUdW5lQ29udHJvbEdyaWQocmVzb2x1dGlvbiA9IDMsIHR1bmUudGhyZXNob2xkID0gRkFMU0UpDQoNCnJkZXNjIDwtIG1ha2VSZXNhbXBsZURlc2MobWV0aG9kID0gIkNWIiwgaXRlcnMgPSA1LCBzdHJhdGlmeSA9IFRSVUUpDQoNCnBhcmFsbGVsU3RhcnRTb2NrZXQoNCwgbGV2ZWwgPSAibWxyLnR1bmVQYXJhbXMiKQ0KDQoNCnN1cHByZXNzTWVzc2FnZXMoew0KDQogIHNldC5zZWVkKDEyMzQ1NiwgIkwnRWN1eWVyLUNNUkciKQ0KICBjbHVzdGVyU2V0Uk5HU3RyZWFtKGlzZWVkID0gMTIzNDU2KQ0KICB0dW5pbmcgPC0gdHVuZVBhcmFtcyhyZl9sZWFybmVyLCB0YXNrID0gdGFzaywgcmVzYW1wbGluZyA9IHJkZXNjLA0KICAgICAgICAgICAgICAgICAgICAgcGFyLnNldCA9IHJmLnBhcm1zLCBjb250cm9sID0gdHVuZUNvbnRyb2wsIG1lYXN1cmVzID0gbWxyOjphdWMpDQp9KQ0KDQoNCnBhcmFsbGVsU3RvcCgpDQp0dW5pbmckeA0KDQp0dW5pbmdfcmVzdWx0cyA8LSBnZW5lcmF0ZUh5cGVyUGFyc0VmZmVjdERhdGEodHVuaW5nLCBwYXJ0aWFsLmRlcCA9IFRSVUUpDQoNCnR1bmluZ19yZXN1bHRzJGRhdGENCnRhcHBseSh0dW5pbmdfcmVzdWx0cyRkYXRhJGF1Yy50ZXN0Lm1lYW4sIElOREVYID0gYyh0dW5pbmdfcmVzdWx0cyRkYXRhJG10cnkpLCBtZWFuKQ0KcmZfdHVuZWQgPC0gc2V0SHlwZXJQYXJzKHJmX2xlYXJuZXIsIHBhci52YWxzID0gdHVuaW5nJHgpDQpyZl9tb2RlbCA8LSBtbHI6OnRyYWluKHJmX3R1bmVkLCB0YXNrID0gdGFzaykNCnJmX3ByZWQgPC0gcHJlZGljdChyZl9tb2RlbCwgbmV3ZGF0YSA9IHRyYWluLmRhdGEsIHR5cGUgPSAiY2xhc3MiKQ0KIyByZl9wcmVkJGRhdGEkcmVzcG9uc2UNCmNvbmZNYXQgPC0gdGFibGUodHJhaW4uZGF0YSRDaGFydHMscmZfcHJlZCRkYXRhJHJlc3BvbnNlKQ0KY29uZk1hdCAjIDA6IG5vdCBpbiBjaGFydHMsIDE6IGluIGNoYXJ0cw0KYWNjdXJhY3kgPC0gc3VtKGRpYWcoY29uZk1hdCkpL3N1bShjb25mTWF0KQ0KYWNjdXJhY3kNCg0KIyAxDQoNCnJmX3ByZWQgPC0gcHJlZGljdChyZl9tb2RlbCwgbmV3ZGF0YSA9IHRlc3QuZGF0YSwgdHlwZSA9ICJjbGFzcyIpDQojIHJmX3ByZWQkZGF0YSRyZXNwb25zZQ0KY29uZk1hdCA8LSB0YWJsZSh0ZXN0LmRhdGEkQ2hhcnRzLHJmX3ByZWQkZGF0YSRyZXNwb25zZSkNCmNvbmZNYXQgIyAwOiBub3QgaW4gY2hhcnRzLCAxOiBpbiBjaGFydHMNCmFjY3VyYWN5IDwtIHN1bShkaWFnKGNvbmZNYXQpKS9zdW0oY29uZk1hdCkNCmFjY3VyYWN5DQoNCiMgMQ0KDQojIE5ldXJhbCBuZXQgY2xhc3NpZmljYXRpb24NCg0KZGYkQ2hhcnRzIDwtIHhfbm9vdXQkQ2hhcnRzDQp0cmFpbl96JENoYXJ0cyA8LSBkZltpZHgudHJhaW4sICJDaGFydHMiXQ0KdGVzdF96JENoYXJ0cyA8LSBkZlstaWR4LnRyYWluLCAiQ2hhcnRzIl0NCg0KDQp0YXNrIDwtIG1ha2VDbGFzc2lmVGFzayhkYXRhID0gdHJhaW5feiwgdGFyZ2V0ID0gIkNoYXJ0cyIsIHBvc2l0aXZlID0gIjEiKQ0Kbm5ldCA8LSBtYWtlTGVhcm5lcigiY2xhc3NpZi5ubmV0IiwgcHJlZGljdC50eXBlID0gInByb2IiLCBwYXIudmFscyA9IGxpc3QoInRyYWNlIiA9IEZBTFNFLCAibWF4aXQiID0gMTAwMCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJNYXhOV3RzIiA9IDgwMDAwKSkNCg0Kbm4ucGFybXMgPC0gbWFrZVBhcmFtU2V0KA0KICBtYWtlRGlzY3JldGVQYXJhbSgiZGVjYXkiLCB2YWx1ZXMgPSBjKDAuMDAwMSwwLjAwMSwgMC4wMSwgMC4xKSksIA0KICBtYWtlRGlzY3JldGVQYXJhbSgic2l6ZSIsIHZhbHVlcyA9IGMoMiw0LDYsOCwxMCwxMiwxNCkpKQ0KDQp0dW5lQ29udHJvbCA8LSBtYWtlVHVuZUNvbnRyb2xHcmlkKHJlc29sdXRpb24gPSAzLCB0dW5lLnRocmVzaG9sZCA9IEZBTFNFKQ0KDQpyZGVzYyA8LSBtYWtlUmVzYW1wbGVEZXNjKG1ldGhvZCA9ICJDViIsIGl0ZXJzID0gNSwgc3RyYXRpZnkgPSBUUlVFKQ0KDQoNCnBhcmFsbGVsU3RhcnRTb2NrZXQoNCwgbGV2ZWwgPSAibWxyLnR1bmVQYXJhbXMiKQ0KDQpzdXBwcmVzc01lc3NhZ2VzKHsNCg0KICBzZXQuc2VlZCgxMjM0NTYsICJMJ0VjdXllci1DTVJHIikNCiAgY2x1c3RlclNldFJOR1N0cmVhbShpc2VlZCA9IDEyMzQ1NikNCiAgdHVuaW5nIDwtIHR1bmVQYXJhbXMobm5ldCwgdGFzayA9IHRhc2ssIHJlc2FtcGxpbmcgPSByZGVzYywgcGFyLnNldCA9IG5uLnBhcm1zLCBjb250cm9sID0gdHVuZUNvbnRyb2wsDQogICAgICAgICAgICAgICAgICAgICBtZWFzdXJlcyA9IG1scjo6YXVjKQ0KfSkNCg0KcGFyYWxsZWxTdG9wKCkNCnR1bmluZyR4ICMgcmVzdWx0IHdpdGggZnVsbCBkYXRhIHNldCB3YXMgZGVjYXkgPSAxZS0wNCwgc2l6ZSA9IDIsIHJtc2UudGVzdC5ybXNlID0gMC40MzM2NDg2DQp0dW5pbmdfcmVzdWx0cyA8LSBnZW5lcmF0ZUh5cGVyUGFyc0VmZmVjdERhdGEodHVuaW5nLCBwYXJ0aWFsLmRlcCA9IEZBTFNFKQ0KDQpwbG90SHlwZXJQYXJzRWZmZWN0KHR1bmluZ19yZXN1bHRzLCB4ID0gInNpemUiLCB5ID0gImF1Yy50ZXN0Lm1lYW4iKQ0KcGxvdEh5cGVyUGFyc0VmZmVjdCh0dW5pbmdfcmVzdWx0cywgeCA9ICJkZWNheSIsIHkgPSAiYXVjLnRlc3QubWVhbiIpDQoNCm5uZXQudHVuZWQgPC0gc2V0SHlwZXJQYXJzKG5uZXQsIHBhci52YWxzID0gdHVuaW5nJHgpDQpubl9tb2RlbCA8LSBtbHI6OnRyYWluKG5uZXQudHVuZWQsIHRhc2sgPSB0YXNrKQ0KDQptb2RlbCA8LSBtbHI6OnRyYWluKG5uZXQudHVuZWQsIHRhc2sgPSB0YXNrKQ0KDQpubl9wcmVkX3RyYWluIDwtIHByZWRpY3QobW9kZWwsIG5ld2RhdGEgPSB0cmFpbl96LCB0eXBlID0gImNsYXNzIikNCm5uX3ByZWRfdGVzdCA8LSBwcmVkaWN0KG1vZGVsLCBuZXdkYXRhID0gdGVzdF96LCB0eXBlID0gImNsYXNzIikNCg0KY29uZk1hdCA8LSB0YWJsZSh0cmFpbl96JENoYXJ0cyxubl9wcmVkX3RyYWluJGRhdGEkcmVzcG9uc2UpDQpjb25mTWF0ICMgMDogbm90IGluIGNoYXJ0cywgMTogaW4gY2hhcnRzDQphY2N1cmFjeSA8LSBzdW0oZGlhZyhjb25mTWF0KSkvc3VtKGNvbmZNYXQpDQphY2N1cmFjeQ0KDQojIDENCg0KY29uZk1hdCA8LSB0YWJsZSh0ZXN0X3okQ2hhcnRzLG5uX3ByZWRfdGVzdCRkYXRhJHJlc3BvbnNlKQ0KY29uZk1hdCAjIDA6IG5vdCBpbiBjaGFydHMsIDE6IGluIGNoYXJ0cw0KYWNjdXJhY3kgPC0gc3VtKGRpYWcoY29uZk1hdCkpL3N1bShjb25mTWF0KQ0KYWNjdXJhY3kNCg0KIyAxDQpgYGANCg0KDQpgYGANCg0K