Business Analytics Data Case Study: Property Market
Q1 (a)
Downloading and preparing data
#Load packages
library(xray)
library(corrplot)
library(mice)
library(sqldf)
library(lattice)
library(MASS)
library(nnet)
library(ggplot2)
library(ggthemes)
library(neuralnet)
library(NeuralNetTools)
#Set working directory
setwd("C:/Users/skych/OneDrive - University of Bath/Desktop/COURSE MATERIAL/MN50645 Data mining/assessment")
#Read file from working directory
property <- read.csv("MN50645_exam_dataset[23].csv", header=T)
Firstly, we apply str() to see the structure of this dataset. As shown below, there are 10000 observations of 13 variables.
str(property)
'data.frame': 10000 obs. of 13 variables:
$ Suburb : Factor w/ 327 levels "Abbotsford","Aberfeldie",..: 275 132 144 53 256 156 192 12 107 155 ...
$ Rooms : int 3 3 4 4 4 3 3 4 4 6 ...
$ Type : Factor w/ 3 levels "h","t","u": 2 1 1 1 1 1 1 1 1 1 ...
$ Price : int 520000 NA NA 1225000 920000 NA 695000 2000000 854000 2825000 ...
$ Method : Factor w/ 9 levels "PI","PN","S",..: 1 3 3 3 6 5 3 1 3 3 ...
$ Distance : num 14 3.5 16.7 11.8 23.5 7.5 12.7 6.3 22.2 5.3 ...
$ Bedroom2 : int 3 3 4 4 4 3 NA 4 4 6 ...
$ Bathroom : int 2 1 2 2 2 2 NA 3 2 5 ...
$ Car : int 2 3 2 2 2 2 NA 4 2 2 ...
$ Landsize : int 240 382 689 723 727 229 NA 544 676 NA ...
$ BuildingArea : num 134 103 NA 159 218 NA NA NA 181 NA ...
$ YearBuilt : int 2007 1900 NA 1970 1990 1900 NA NA 1975 1920 ...
$ Propertycount: int 14042 6244 15321 4480 11667 6482 4168 4836 3940 11308 ...
Then, the function of anomalies() can help us detection of anomalies and unusual values. The variables of “BuildingArea” and “YearBuilt” have missing values exceeding 50%; The variable of “Car” and “Bathroom” have 33.59% and 24.51% missing values and over 4% zore values; And the variable of “Bedroom2” and “Price” have over 20% missing values. In general, there is a great number of null values and some zero values in this data set that need further processing.
anomalies(property)
$variables
$problem_variables
NA
Q1 (b)
Before fixing the problem of our dataset, we should determine the importance of the problematic variables. As we should explorate the factors affecting propert prices and also build a ML forecasting model to predict price in the Q2 and Q3, we will caculate the corelartion bewteen the problematic variables and the price first.
property_cor1 <- cor(na.omit(sqldf("select Price, BuildingArea from property")))
property_cor2 <- cor(na.omit(sqldf("select Price, Landsize from property")))
property_cor3 <- cor(na.omit(sqldf("select Price, YearBuilt from property")))
property_cor4 <- cor(na.omit(sqldf("select Price, Car from property")))
property_cor5 <- cor(na.omit(sqldf("select Price, Bathroom from property")))
property_cor6 <- cor(na.omit(sqldf("select Price, Bedroom2 from property")))
par(mfcol=c(2,3))
corrplot.mixed(property_cor1, lower = "number", upper = "pie", tl.col = "black",lower.col = "black", number.cex = 1)
corrplot.mixed(property_cor2, lower = "number", upper = "pie", tl.col = "black",lower.col = "black", number.cex = 1)
corrplot.mixed(property_cor3, lower = "number", upper = "pie", tl.col = "black",lower.col = "black", number.cex = 1)
corrplot.mixed(property_cor4, lower = "number", upper = "pie", tl.col = "black",lower.col = "black", number.cex = 1)
corrplot.mixed(property_cor5, lower = "number", upper = "pie", tl.col = "black",lower.col = "black", number.cex = 1)
corrplot.mixed(property_cor6, lower = "number", upper = "pie", tl.col = "black",lower.col = "black", number.cex = 1)

As shown in above figure, “Landsize” and “Car” has few relevant to price while it has over 20% missing value, so we will give them up. For the rest problematic variables, we consider using appropriate methods to fill in missing values.
property <- property[,c(-1, -9,-10)]
For the missing value in “Price”, we will delete the missing value because “Price” is our dependent variable. And for “Propertycount”, we will also delete its missing value since the few number.
mvr1 = which(is.na(property$Price))
property <- property[-mvr1,]
mvr2 = which(is.na(property$Propertycount))
property <- property[-mvr2,]
By visualizing “YearBuilt”, we can see that most properties were built bewtween 1950 and 2010. Its mode is in 2000-2010. However, its median and mean value is about 1970. Thus, median may be more representative.
ggplot(property, aes(x = YearBuilt)) + geom_histogram(bins = 20, fill = "lightblue", colour = "black")

summary(property$YearBuilt)
Min. 1st Qu. Median Mean 3rd Qu. Max. NA's
1830 1950 1970 1968 2000 2018 4272
Therefore, we will fill with median.
property[is.na(property$YearBuilt),"YearBuilt"] <- median(property$YearBuilt,na.rm = T)
Factors such as the number of bedrooms, bathrooms, house size, room type, and price could be all related to each other. Thus, for “BuildingArea”, “Bathroom” and “Bedroom2”, we will delete the column with missing value of these three variables first.
property <-sqldf("select * from property
where BuildingArea is not null
or Bathroom is not null
or Bedroom2 is not null",row.names=TRUE)
Then we check the anomalies and unusual values again.
anomalies(property)
$variables
$problem_variables
NA
There is only 1 missing value in “Bathroom” which we can just delete and the missing value in “BuildingArea” is still occupy 48.69%. Hence, we should filling function to deal with the problem. Besides, we can aslo find there are 16 zero value in “BuildingArea”, which is unreasonable, we will also delete the line with the variable less than 10.
mvr3 = which(is.na(property$Bathroom))
property <- property[-mvr3,]
zvr1 = which(property$BuildingArea < 10)
property <- property[-zvr1,]
Mice() function can help use reasonable data values to estimate missing values. We try use all avriables to build model first.
imp<-mice(property, method="rf", seed=1234)
fit<-with(imp,lm(Price~.,data=property))
pool=pool(fit)
options(digits=3)
summary(pool)
As shown above, the p-values of “Method”, “Propertycount” and “Bedroom2” are not significant. So we will delete them and try again. The result shows that this model could do a good job.
imp<-mice(property[,c(1,2,3,5,7,8,9)], method="rf", seed=1234)
iter imp variable
1 1 BuildingArea
1 2 BuildingArea
1 3 BuildingArea
1 4 BuildingArea
1 5 BuildingArea
2 1 BuildingArea
2 2 BuildingArea
2 3 BuildingArea
2 4 BuildingArea
2 5 BuildingArea
3 1 BuildingArea
3 2 BuildingArea
3 3 BuildingArea
3 4 BuildingArea
3 5 BuildingArea
4 1 BuildingArea
4 2 BuildingArea
4 3 BuildingArea
4 4 BuildingArea
4 5 BuildingArea
5 1 BuildingArea
5 2 BuildingArea
5 3 BuildingArea
5 4 BuildingArea
5 5 BuildingArea
fit<-with(imp,lm(Price~.,data=property[,c(1,2,3,5,7,8,9)]))
pool=pool(fit)
options(digits=3)
summary(pool)
term estimate std.error statistic df p.value
1 (Intercept) 10108105 543209 18.61 3044 0.00e+00
2 Rooms 84002 13374 6.28 3044 3.85e-10
3 Typet -97499 31985 -3.05 3044 2.32e-03
4 Typeu -203481 28782 -7.07 3044 1.92e-12
5 Distance -29197 1347 -21.68 3044 0.00e+00
6 Bathroom 235531 15961 14.76 3044 0.00e+00
7 BuildingArea 2512 130 19.37 3044 0.00e+00
8 YearBuilt -4925 283 -17.43 3044 0.00e+00
property_mice=complete(imp,action=1)
Finally, we update the property data and check the anomalies and unusual values again. There is no missing value remaining and only two variables have zero values.
property[,8] <- property_mice[,6]
anomalies(property)
$variables
$problem_variables
NA
Q2 (a)
Basic descriptive statistics with summary() function. From the result, we can get a brief understand of the dataset. “Type” and “Method” are string format, which should do some transform later.
summary(property)
Rooms Type Price Method Distance Bedroom2
Min. : 1.00 h:4511 Min. : 145000 S :3917 Min. : 0.0 Min. : 0.00
1st Qu.: 2.00 t: 475 1st Qu.: 654500 SP : 744 1st Qu.: 6.4 1st Qu.: 2.00
Median : 3.00 u: 998 Median : 903000 PI : 718 Median :10.5 Median : 3.00
Mean : 3.07 Mean : 1083052 VB : 558 Mean :11.4 Mean : 3.05
3rd Qu.: 4.00 3rd Qu.: 1330875 SA : 47 3rd Qu.:14.2 3rd Qu.: 4.00
Max. :12.00 Max. :11200000 PN : 0 Max. :47.3 Max. :12.00
(Other): 0
Bathroom BuildingArea YearBuilt Propertycount
Min. :0.0 Min. : 11 Min. :1830 Min. : 83
1st Qu.:1.0 1st Qu.: 101 1st Qu.:1966 1st Qu.: 4380
Median :1.5 Median : 130 Median :1970 Median : 6567
Mean :1.6 Mean : 145 Mean :1969 Mean : 7552
3rd Qu.:2.0 3rd Qu.: 173 3rd Qu.:1977 3rd Qu.:10412
Max. :8.0 Max. :1561 Max. :2018 Max. :21650
To make above result more easy to unstanding, we can visualize them. The figure below shows the density distribution of BuildingArea variable. Most properties’s building size are bewteen 100 and 140. Only few of them larger than 400.
property_temp1 <- filter(property, BuildingArea <= 600)
ggplot(property_temp1, aes(x = BuildingArea)) +
geom_histogram(aes(y = ..density..), fill = "lightblue", binwidth = 20, color="gray") +
geom_density() +
theme_pander()+
ggtitle("BuildingArea Distribution")+
theme(plot.title = element_text(hjust = 0.5))

For the methods of the property sold, “property sold” occupies the majority which is almost 4000. “property sold prior” and “property passed in” have about 700, while “vendor bid” only have less than 600. For types of the preporty, h (house, cottage, villa, semi, terrace) occupies the majority. u (unit, duplex) hasabout 1000 and townhouse just have about 500.
ggplot(property) +
geom_bar(aes(x = reorder(Method, table(Method)[Method])), fill = "lightblue") +
coord_flip() +
theme_bw() +
labs(x = NULL)

ggplot(property) +
geom_bar(aes(x = reorder(Type, table(Type)[Type])), fill = "lightblue", width = 0.7) +
coord_flip() +
theme_bw() +
labs(x = NULL)

Then we plot the density distribution of price of different methods and types. We can see that different “Method” and “Type” have the almost similiar mode and median, which means “Method” and “Type” may have a few influence of price.
ggplot(property, aes(x = Price)) +
geom_density(fill="yellow") +
facet_wrap( ~ Method) +
geom_vline(aes(xintercept = median(Price)), linetype = 2) +
theme_bw()

ggplot(property, aes(x = Price)) +
geom_density(fill="yellow") +
facet_wrap( ~ Type) +
geom_vline(aes(xintercept = median(Price)), linetype = 2) +
theme_bw()

Q2 (b)
Question: 1. How distance influence the sales of property? 2. How “Method” influence the sales of property? 3. How “Type” influence the sales of property?
To solve the question 1, we plot the point plot to show the relationship between “Distance” and “Price” as well as a fitted line. It shows that distance will influence the price of the property. So the closer the property is to the city center, the higher the demand is (According to the Economics, demand determines price).
ggplot(property, aes(x = Distance, y = Price)) +
geom_point(size = 0.5, color="darkgray") +
geom_smooth(method = "lm") +
theme_bw()

As mentioned above, “BuildingArea” has the highest correlation with price. Then we could explore how building size influence the price of different “Method” and “Type”. From the fitted curve in two figures below, we can find that sold after auction and vendor bid properties’ building size have a higher unit price (price per size). For different types, townhouse may have a higher unit price. Thus, these features may improve the sales of property when the total area of the house sold is certain.
ggplot(property, aes(x = BuildingArea, y = Price)) +
geom_point(size = 0.5, color="darkgray") +
geom_smooth(method = "lm") +
facet_wrap( ~ Method) +
theme_bw()

ggplot(property, aes(x = BuildingArea, y = Price)) +
geom_point(size = 0.5, color="darkgray") +
geom_smooth(method = "lm") +
facet_wrap( ~ Type) +
theme_bw()

Q3 (a)
In this part, we need to build a machine learning model to predict the factors that affect the sales price. In machine learning models that solve numerical problems, Artificial Neural Network algorithms may have better performance. Its classification accuracy is high and its learning ability is extremely strong. It is robust and fault-tolerant to noisy data. In addition, it has the ability to associate and can approximate any nonlinear relationship. ***
Q3 (b)
As metioned above, we remoted the two variables “Lansdsize” and “Car” as they are not highly correlated with “Prices” and have more vacancies. Then we apply ggpairs() function to check the correlatin among these variable. The correlation between “Propertycount” and “Price” is only about 5%. The number of properties that exist in the suburb actually can not influent the property price in real world. To make our model more concise and efficient, we will delete this variable.
ggpairs(property, title = "Scatterplot Matrix of the Features of the Property Data Set")

property <- property[,-10]
Q3 (c)
Before applying the ANN function, we should transform “Method” and “Type” into numeric format.
property_ANN <- property
t = array(0, 5984)
h = array(0, 5984)
u = array(0, 5984)
S = array(0, 5984)
SP = array(0, 5984)
PI = array(0, 5984)
VB = array(0, 5984)
SA = array(0, 5984)
for (i in 1: 5984) {
if(property_ANN$Type[i] == "t") {t[i]=1
h[i]=0
u[i]=0}
if(property_ANN$Type[i] == "h") {t[i]=0
h[i]=1
u[i]=0}
if(property_ANN$Type[i] == "u") {t[i]=0
h[i]=0
u[i]=1}
if(property_ANN$Method[i] == "S") {S[i]=1
SP[i]=0
PI[i]=0
VB[i]=0
SA[i]=0}
if(property_ANN$Method[i] == "SP") {S[i]=0
SP[i]=1
PI[i]=0
VB[i]=0
SA[i]=0}
if(property_ANN$Method[i] == "PI") {S[i]=0
SP[i]=0
PI[i]=1
VB[i]=0
SA[i]=0}
if(property_ANN$Method[i] == "VB") {S[i]=0
SP[i]=0
PI[i]=0
VB[i]=1
SA[i]=0}
if(property_ANN$Method[i] == "SA") {S[i]=0
SP[i]=0
PI[i]=0
VB[i]=0
SA[i]=1}
}
property_ANN$Type_t=t
property_ANN$Type_h=h
property_ANN$Type_u=u
property_ANN$Method_S=S
property_ANN$Method_SP=SP
property_ANN$Method_PI=PI
property_ANN$Method_VB=VB
property_ANN$Method_SA=SA
property_ANN <- property_ANN[,c(-2,-4)]
Data observation should be scaled onto the [0,1] interval.
scale01 <- function(x){
(x - min(x)) / (max(x) - min(x))
}
property_ANN <- property_ANN %>%mutate_all(scale01)
Then spliting into test(0.2) and train sets (0.8).
set.seed(12341)
Property_Train <- sample_frac(tbl = property_ANN, replace = FALSE, size = 0.80)
Property_Test <- anti_join(property_ANN, Property_Train)
Firstly, we construct a model with 1-hidden layer. By using plot() function, we can get the weights learned by the ANN_model1 neural network.The plot also shows the sum of Squared Errors (SSE) of the training data set and displays the number of training iterations before convergence. In this model, SSE of training data set is 0.7018.
set.seed(12342)
ANN_model1 <- neuralnet(Price ~ ., data = Property_Train)
plot(ANN_model1)

Then we visualise the weights of variables. We can see that the “BuildingArea” has the highest importance of the price pridicting, so it may influence price most. “Distance” and “Bathroom” is the second and third importance factor. “Rooms” and “Type_u” have over 0.05 importance.
garson(ANN_model1) + theme(axis.text.x = element_text(angle = 45, hjust = 1))

plotnet(ANN_model1)

Q3 (d)
To evaluate the performance, we will use SSE, MSE and R-squared.
#create function to caculate performance metrics
ssefun <- function(pred, obs) sum((as.numeric(unlist(pred)) - obs)^2)/2
msefun <- function(pred, obs) mean((as.numeric(unlist(pred)) - obs)^2/2)
sstfun <- function(obs) sum((obs - mean(obs))^2)/2
By caculating the three metrics of the training model. According to the result, we can find that the SSE is 4.1184, Mse is 0.0009 and R-squared is 0.4948, which suggests results can be explained by the train model. Since R-squared is not so close to 1, it should get improved.
sse11 <- ssefun(ANN_model1$net.result, Property_Train[, 2])
mse11 <- msefun(ANN_model1$net.result, Property_Train[, 2])
rs11 <- 1-sse11/sstfun(Property_Train[, 2])
paste("Train SSE: ", round(sse11, 4), "; Train MSE: ", round(mse11, 4), "; Train R-squared: ", round(rs11, 4))
[1] "Train SSE: 4.1184 ; Train MSE: 9e-04 ; Train R-squared: 0.4948"
Then we run test observations and caculating the performance metrics. The SSE of test model is better than train model, which is only 1.0555. However, MSE did not get a significant improvement, so actually the test model has almost the same performance with the training one, which can also be found in R-squared.
#Run test observations
Test_ANN1_Output <- compute(ANN_model1, Property_Test[, -2])$net.result
sse12 <- ssefun(Test_ANN1_Output, Property_Test[, 2])
mse12 <- msefun(Test_ANN1_Output, Property_Test[, 2])
rs12 <- 1-sse12/sstfun(Property_Test[, 2])
paste("Test SSE: ", round(sse12, 4), "; Test MSE: ", round(mse12, 4), "; Test R-squared: ", round(rs12, 4))
[1] "Test SSE: 1.0555 ; Test MSE: 9e-04 ; Test R-squared: 0.5231"
Q3 (e)
To improve our model, we could use “rep” parameter to let the ANN algrithnm run many time and find the best one.
set.seed(12343)
ANN_model2 <- neuralnet(Price ~ ., data = Property_Train, rep = 5)
Warning in for (w in (length.weights - 1):1) { :
closing unused connection 5 (<-DESKTOP-KMTTC50:11822)
Warning in for (w in (length.weights - 1):1) { :
closing unused connection 4 (<-DESKTOP-KMTTC50:11822)
Warning in for (w in (length.weights - 1):1) { :
closing unused connection 3 (<-DESKTOP-KMTTC50:11822)
Algorithm did not converge in 1 of 5 repetition(s) within the stepmax.
plot(ANN_model2, rep = "best")

Min_num2 = which.min(ANN_model2$result.matrix[1,])
Then we caculate the performance of this train model and test model.
sse21 <- ssefun(ANN_model2$net.result[[Min_num2]], Property_Train[, 2])
mse21 <- msefun(ANN_model2$net.result[[Min_num2]], Property_Train[, 2])
rs21 <- 1-sse21/sstfun(Property_Train[, 2])
paste("Train SSE: ", round(sse21, 4), "; Train MSE: ", round(mse21, 4), "; Train R-squared: ", round(rs21, 4))
[1] "Train SSE: 4.1149 ; Train MSE: 9e-04 ; Train R-squared: 0.4952"
Test_ANN2_Output <- compute(ANN_model2, Property_Test[, -2], rep = Min_num2)$net.result
sse22 <- ssefun(Test_ANN2_Output, Property_Test[, 2])
mse22 <- msefun(Test_ANN2_Output, Property_Test[, 2])
rs22 <- 1-sse22/sstfun(Property_Test[, 2])
paste("Test SSE: ", round(sse22, 4), "; Test MSE: ", round(mse22, 4), "; Test R-squared: ", round(rs22, 4))
[1] "Test SSE: 1.0474 ; Test MSE: 9e-04 ; Test R-squared: 0.5268"
In addition, we can also increase the hidden neurons number into 10 (15 variables * 2/3). Both train model and test model get improved. 10 hidden could do a better job than the former one.
set.seed(12344)
ANN_model3 <- neuralnet(Price ~ ., data = Property_Train, hidden = 10)
sse31 <- ssefun(ANN_model3$net.result, Property_Train[, 2])
mse31 <- msefun(ANN_model3$net.result, Property_Train[, 2])
rs31 <- 1-sse31/sstfun(Property_Train[, 2])
paste("Train SSE: ", round(sse31, 4), "; Train MSE: ", round(mse31, 4), "; Train R-squared: ", round(rs31, 4))
[1] "Train SSE: 3.6439 ; Train MSE: 8e-04 ; Train R-squared: 0.553"
Test_ANN3_Output <- compute(ANN_model3, Property_Test[, -2])$net.result
sse32 <- ssefun(Test_ANN3_Output, Property_Test[, 2])
mse32 <- msefun(Test_ANN3_Output, Property_Test[, 2])
rs32 <- 1-sse32/sstfun(Property_Test[, 2])
paste("Test SSE: ", round(sse32, 4), "; Test MSE: ", round(mse32, 4), "; Test R-squared: ", round(rs32, 4))
[1] "Test SSE: 0.9688 ; Test MSE: 8e-04 ; Test R-squared: 0.5622"
We can also add layer into 2 (2-Hidden Layers, Layer-1 10-neurons, Layer-2, 3-neuron). The train model is better than the former one, but test model does not improve.
set.seed(12345)
ANN_model4 <- neuralnet(Price ~ ., data = Property_Train, hidden = c(10, 3), stepmax = 10000000)
sse41 <- ssefun(ANN_model4$net.result, Property_Train[, 2])
mse41 <- msefun(ANN_model4$net.result, Property_Train[, 2])
rs41 <- 1-sse41/sstfun(Property_Train[, 2])
paste("Train SSE: ", round(sse41, 4), "; Train MSE: ", round(mse41, 4), "; Train R-squared: ", round(rs41, 4))
[1] "Train SSE: 3.2109 ; Train MSE: 7e-04 ; Train R-squared: 0.6061"
Test_ANN4_Output <- compute(ANN_model4, Property_Test[, -2])$net.result
sse42 <- ssefun(Test_ANN4_Output, Property_Test[, 2])
mse42 <- msefun(Test_ANN4_Output, Property_Test[, 2])
rs42 <- 1-sse42/sstfun(Property_Test[, 2])
paste("Test SSE: ", round(sse42, 4), "; Test MSE: ", round(mse42, 4), "; Test R-squared: ", round(rs42, 4))
[1] "Test SSE: 1.0043 ; Test MSE: 8e-04 ; Test R-squared: 0.5462"
Changing activation function could be a way to improve our model. This model has higher R-squared and lower SSE/MSE in test model, but the improvement is not significant.
set.seed(12346)
ANN_model5 <- neuralnet(Price ~ ., data = Property_Train, hidden = 10, act.fct = "logistic")
sse51 <- ssefun(ANN_model5$net.result, Property_Train[, 2])
mse51 <- msefun(ANN_model5$net.result, Property_Train[, 2])
rs51 <- 1-sse51/sstfun(Property_Train[, 2])
paste("Train SSE: ", round(sse51, 4), "; Train MSE: ", round(mse51, 4), "; Train R-squared: ", round(rs51, 4))
[1] "Train SSE: 3.5889 ; Train MSE: 7e-04 ; Train R-squared: 0.5597"
Test_ANN5_Output <- compute(ANN_model5, Property_Test[, -2])$net.result
sse52 <- ssefun(Test_ANN5_Output, Property_Test[, 2])
mse52 <- msefun(Test_ANN5_Output, Property_Test[, 2])
rs52 <- 1-sse52/sstfun(Property_Test[, 2])
paste("Test SSE: ", round(sse52, 4), "; Test MSE: ", round(mse52, 4), "; Test R-squared: ", round(rs52, 4))
[1] "Test SSE: 0.9418 ; Test MSE: 8e-04 ; Test R-squared: 0.5745"
Therefore, we will try more models to find the best one.
set.seed(12347)
#hidden = 8
ANN_model6 <- neuralnet(Price ~ ., data = Property_Train, hidden = 8)
sse61 <- ssefun(ANN_model6$net.result, Property_Train[, 2])
mse61 <- msefun(ANN_model6$net.result, Property_Train[, 2])
rs61 <- 1-sse61/sstfun(Property_Train[, 2])
Test_ANN6_Output <- compute(ANN_model6, Property_Test[, -2])$net.result
sse62 <- ssefun(Test_ANN6_Output, Property_Test[, 2])
mse62 <- msefun(Test_ANN6_Output, Property_Test[, 2])
rs62 <- 1-sse62/sstfun(Property_Test[, 2])
#hidden = c(8, 3)
ANN_model7 <- neuralnet(Price ~ ., data = Property_Train, hidden = c(8, 3))
sse71 <- ssefun(ANN_model7$net.result, Property_Train[, 2])
mse71 <- msefun(ANN_model7$net.result, Property_Train[, 2])
rs71 <- 1-sse71/sstfun(Property_Train[, 2])
Test_ANN7_Output <- compute(ANN_model7, Property_Test[, -2])$net.result
sse72 <- ssefun(Test_ANN7_Output, Property_Test[, 2])
mse72 <- msefun(Test_ANN7_Output, Property_Test[, 2])
rs72 <- 1-sse72/sstfun(Property_Test[, 2])
#hidden = c(10, 3), act.fct = "logistic"
ANN_model8 <- neuralnet(Price ~ ., data = Property_Train, hidden = c(10, 3), act.fct = "logistic")
sse81 <- ssefun(ANN_model8$net.result, Property_Train[, 2])
mse81 <- msefun(ANN_model8$net.result, Property_Train[, 2])
rs81 <- 1-sse81/sstfun(Property_Train[, 2])
Test_ANN8_Output <- compute(ANN_model8, Property_Test[, -2])$net.result
sse82 <- ssefun(Test_ANN8_Output, Property_Test[, 2])
mse82 <- msefun(Test_ANN8_Output, Property_Test[, 2])
rs82 <- 1-sse82/sstfun(Property_Test[, 2])
#Rescale for tanh activation function
scale11 <- function(x) {
(2 * ((x - min(x))/(max(x) - min(x)))) - 1
}
Property_Train1 <- Property_Train %>% mutate_all(scale11)
Property_Test1 <- Property_Test %>% mutate_all(scale11)
#hidden = 10, act.fct = "tanh"
ANN_model9 <- neuralnet(Price ~ ., data = Property_Train1, act.fct = "tanh")
sse91 <- ssefun(ANN_model9$net.result, Property_Train1[, 2])
mse91 <- msefun(ANN_model9$net.result, Property_Train1[, 2])
rs91 <- 1-sse91/sstfun(Property_Train1[, 2])
Test_ANN9_Output <- compute(ANN_model9, Property_Test1[, -2])$net.result
sse92 <- ssefun(Test_ANN9_Output, Property_Test1[, 2])
mse92 <- msefun(Test_ANN9_Output, Property_Test1[, 2])
rs92 <- 1-sse92/sstfun(Property_Test1[, 2])
Then we visualize the metrics of these models. According to the results, ANN4 model perform better in train model while ANN8 model do a better job in test model.
tibble(Network = rep(c("ANN1", "ANN2", "ANN3", "ANN4", "ANN5", "ANN6", "ANN7", "ANN8",
"ANN9"), each = 2),
DataSet = rep(c("Train SSE", "Test SSE"), time = 9),
Metrics = c(sse11, sse12, sse21, sse22, sse31, sse32, sse41, sse42, sse51, sse52,
sse61, sse62, sse71, sse72, sse81, sse82, sse91, sse92)) %>%
ggplot(aes(Network, Metrics, fill = DataSet)) +
geom_text(aes(label=round(Metrics,3)), position=position_dodge(0.9), vjust = -0.9) +
geom_col(position = "dodge") + theme_bw() + ggtitle("Distribution of ANN's SSE")

tibble(Network = rep(c("ANN1", "ANN2", "ANN3", "ANN4", "ANN5", "ANN6", "ANN7", "ANN8",
"ANN9"), each = 2),
DataSet = rep(c("Train MSE", "Test MSE"),
time = 9),
Metrics = c(mse11, mse12, mse21, mse22, mse31, mse32, mse41, mse42, mse51, mse52,
mse61, mse62, mse71, mse72, mse81, mse82, mse91, mse92)) %>%
ggplot(aes(Network, Metrics, fill = DataSet)) +
geom_text(aes(label=round(Metrics,3)), position=position_dodge(0.9), vjust = -0.9) +
geom_col(position = "dodge") + theme_bw() + ggtitle("Distribution of ANN's MSE")

tibble(Network = rep(c("ANN1", "ANN2", "ANN3", "ANN4", "ANN5", "ANN6", "ANN7", "ANN8",
"ANN9"), each = 2),
DataSet = rep(c("Train R2", "Test R2"),
time = 9),
Metrics = c(rs11, rs12, rs21, rs22, rs31, rs32, rs41, rs42, rs51, rs52, rs61, rs62,
rs71, rs72, rs81, rs82, rs91, rs92)) %>%
ggplot(aes(Network, Metrics, fill = DataSet)) +
geom_text(aes(label=round(Metrics,3)), position=position_dodge(0.9), vjust = -0.9) +
geom_col(position = "dodge") + theme_bw() + ggtitle("Distribution of ANN's R-squared")

We plot the relationship map of ANN4.
plot(ANN_model4)

We plot the relationship map of ANN8.
plot(ANN_model8)

Due to the limited number of models we try, this method is not optimal. We can further try deep learning, such as convolutional neural networks. On the basis of the original multi-layer neural network, a feature learning part is added, which imitates the classification of signal processing by the human brain.
Q3 (f)
According to the results of data exploration (Figure 1 and Figure 2) and meachine learning model(Figure 3), “BuildingArea”, “Bathroom” and “Distance” have highly influence of the property price. So if the agency want to get more sales amount, they should consider get more property with bigger building size, more bathrooms and less distance to city center. And if they want to get more return of invest (ROI), unit price should be a important metrics. Thus, they should use more method of sold after auction and vendor bid. Townhouse will also help them to achieve it.
#Figure 1
ggplot(property, aes(x = BuildingArea, y = Price)) +
geom_point(size = 0.5, color="darkgray") +
geom_smooth(method = "lm") +
facet_wrap( ~ Method) +
theme_bw()

ggplot(property, aes(x = BuildingArea, y = Price)) +
geom_point(size = 0.5, color="darkgray") +
geom_smooth(method = "lm") +
facet_wrap( ~ Type) +
theme_bw()

#Figure 2
ggplot(property, aes(x = Distance, y = Price)) +
geom_point(size = 0.5, color="darkgray") +
geom_smooth(method = "lm") +
theme_bw()

#Figure 3
garson(ANN_model1) + theme(axis.text.x = element_text(angle = 45, hjust = 1))

LS0tDQp0aXRsZTogIlIgRXhhbSBOb3RlYm9vazogTU41MDY0NSINCnN1YnRpdGxlOiBVbml2ZXJzaXR5IG9mIEJhdGgsIFNjaG9vbCBPZiBNYW5hZ2VtZW50LCBNTjUwNjQ1IERhdGEgTWluaW5nIDIwMTkvMjANCm91dHB1dDogaHRtbF9ub3RlYm9vaw0KLS0tDQoNClN0dWRlbnQgaWQ6ICoqMTk5MDQyNzg4KioNCg0KTmFtZTogKipUaWFueXVuIENhaSoqDQoNCioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKg0KDQojIEJ1c2luZXNzIEFuYWx5dGljcyBEYXRhIENhc2UgU3R1ZHk6IFByb3BlcnR5IE1hcmtldA0KDQoNCiMjIFExIChhKQkNCioqKg0KDQoNCkRvd25sb2FkaW5nIGFuZCBwcmVwYXJpbmcgZGF0YQ0KYGBge3J9DQojTG9hZCBwYWNrYWdlcw0KbGlicmFyeSh4cmF5KQ0KbGlicmFyeShjb3JycGxvdCkNCmxpYnJhcnkobWljZSkNCmxpYnJhcnkoc3FsZGYpDQpsaWJyYXJ5KGxhdHRpY2UpDQpsaWJyYXJ5KE1BU1MpDQpsaWJyYXJ5KG5uZXQpDQpsaWJyYXJ5KGdncGxvdDIpDQpsaWJyYXJ5KGdndGhlbWVzKQ0KbGlicmFyeShuZXVyYWxuZXQpDQpsaWJyYXJ5KE5ldXJhbE5ldFRvb2xzKQ0KDQojU2V0IHdvcmtpbmcgZGlyZWN0b3J5DQpzZXR3ZCgiQzovVXNlcnMvc2t5Y2gvT25lRHJpdmUgLSBVbml2ZXJzaXR5IG9mIEJhdGgvRGVza3RvcC9DT1VSU0UgTUFURVJJQUwvTU41MDY0NSBEYXRhIG1pbmluZy9hc3Nlc3NtZW50IikNCg0KI1JlYWQgZmlsZSBmcm9tIHdvcmtpbmcgZGlyZWN0b3J5DQpwcm9wZXJ0eSA8LSByZWFkLmNzdigiTU41MDY0NV9leGFtX2RhdGFzZXRbMjNdLmNzdiIsIGhlYWRlcj1UKQ0KYGBgDQoqKioNCg0KDQpGaXJzdGx5LCB3ZSBhcHBseSBzdHIoKSB0byBzZWUgdGhlIHN0cnVjdHVyZSBvZiB0aGlzIGRhdGFzZXQuIEFzIHNob3duIGJlbG93LCB0aGVyZSBhcmUgMTAwMDAgb2JzZXJ2YXRpb25zIG9mIDEzIHZhcmlhYmxlcy4NCmBgYHtyfQ0Kc3RyKHByb3BlcnR5KQ0KYGBgDQoqKioNCg0KDQpUaGVuLCB0aGUgZnVuY3Rpb24gb2YgYW5vbWFsaWVzKCkgY2FuIGhlbHAgdXMgZGV0ZWN0aW9uIG9mIGFub21hbGllcyBhbmQgdW51c3VhbCB2YWx1ZXMuIFRoZSB2YXJpYWJsZXMgb2YgIkJ1aWxkaW5nQXJlYSIgYW5kICJZZWFyQnVpbHQiIGhhdmUgbWlzc2luZyB2YWx1ZXMgZXhjZWVkaW5nIDUwJTsgVGhlIHZhcmlhYmxlIG9mICJDYXIiIGFuZCAiQmF0aHJvb20iIGhhdmUgMzMuNTklIGFuZCAyNC41MSUgbWlzc2luZyB2YWx1ZXMgYW5kIG92ZXIgNCUgem9yZSB2YWx1ZXM7IEFuZCB0aGUgdmFyaWFibGUgb2YgIkJlZHJvb20yIiBhbmQgIlByaWNlIiBoYXZlIG92ZXIgMjAlIG1pc3NpbmcgdmFsdWVzLiBJbiBnZW5lcmFsLCB0aGVyZSBpcyBhIGdyZWF0IG51bWJlciBvZiBudWxsIHZhbHVlcyBhbmQgc29tZSB6ZXJvIHZhbHVlcyBpbiB0aGlzIGRhdGEgc2V0IHRoYXQgbmVlZCBmdXJ0aGVyIHByb2Nlc3NpbmcuDQpgYGB7cn0NCmFub21hbGllcyhwcm9wZXJ0eSkNCmBgYA0KKioqDQoNCiMjIFExIChiKQ0KKioqDQoNCg0KQmVmb3JlIGZpeGluZyB0aGUgcHJvYmxlbSBvZiBvdXIgZGF0YXNldCwgd2Ugc2hvdWxkIGRldGVybWluZSB0aGUgaW1wb3J0YW5jZSBvZiB0aGUgcHJvYmxlbWF0aWMgdmFyaWFibGVzLiBBcyB3ZSBzaG91bGQgZXhwbG9yYXRlIHRoZSBmYWN0b3JzIGFmZmVjdGluZyBwcm9wZXJ0IHByaWNlcyBhbmQgYWxzbyBidWlsZCBhIE1MIGZvcmVjYXN0aW5nIG1vZGVsIHRvIHByZWRpY3QgcHJpY2UgaW4gdGhlIFEyIGFuZCBRMywgd2Ugd2lsbCBjYWN1bGF0ZSB0aGUgY29yZWxhcnRpb24gYmV3dGVlbiB0aGUgcHJvYmxlbWF0aWMgdmFyaWFibGVzIGFuZCB0aGUgcHJpY2UgZmlyc3QuIA0KYGBge3J9DQpwcm9wZXJ0eV9jb3IxIDwtIGNvcihuYS5vbWl0KHNxbGRmKCJzZWxlY3QgUHJpY2UsIEJ1aWxkaW5nQXJlYSBmcm9tIHByb3BlcnR5IikpKQ0KcHJvcGVydHlfY29yMiA8LSBjb3IobmEub21pdChzcWxkZigic2VsZWN0IFByaWNlLCBMYW5kc2l6ZSBmcm9tIHByb3BlcnR5IikpKQ0KcHJvcGVydHlfY29yMyA8LSBjb3IobmEub21pdChzcWxkZigic2VsZWN0IFByaWNlLCBZZWFyQnVpbHQgZnJvbSBwcm9wZXJ0eSIpKSkNCnByb3BlcnR5X2NvcjQgPC0gY29yKG5hLm9taXQoc3FsZGYoInNlbGVjdCBQcmljZSwgQ2FyIGZyb20gcHJvcGVydHkiKSkpDQpwcm9wZXJ0eV9jb3I1IDwtIGNvcihuYS5vbWl0KHNxbGRmKCJzZWxlY3QgUHJpY2UsIEJhdGhyb29tIGZyb20gcHJvcGVydHkiKSkpDQpwcm9wZXJ0eV9jb3I2IDwtIGNvcihuYS5vbWl0KHNxbGRmKCJzZWxlY3QgUHJpY2UsIEJlZHJvb20yIGZyb20gcHJvcGVydHkiKSkpDQpwYXIobWZjb2w9YygyLDMpKQ0KY29ycnBsb3QubWl4ZWQocHJvcGVydHlfY29yMSwgbG93ZXIgPSAibnVtYmVyIiwgdXBwZXIgPSAicGllIiwgdGwuY29sID0gImJsYWNrIixsb3dlci5jb2wgPSAiYmxhY2siLCBudW1iZXIuY2V4ID0gMSkNCmNvcnJwbG90Lm1peGVkKHByb3BlcnR5X2NvcjIsIGxvd2VyID0gIm51bWJlciIsIHVwcGVyID0gInBpZSIsIHRsLmNvbCA9ICJibGFjayIsbG93ZXIuY29sID0gImJsYWNrIiwgbnVtYmVyLmNleCA9IDEpDQpjb3JycGxvdC5taXhlZChwcm9wZXJ0eV9jb3IzLCBsb3dlciA9ICJudW1iZXIiLCB1cHBlciA9ICJwaWUiLCB0bC5jb2wgPSAiYmxhY2siLGxvd2VyLmNvbCA9ICJibGFjayIsIG51bWJlci5jZXggPSAxKQ0KY29ycnBsb3QubWl4ZWQocHJvcGVydHlfY29yNCwgbG93ZXIgPSAibnVtYmVyIiwgdXBwZXIgPSAicGllIiwgdGwuY29sID0gImJsYWNrIixsb3dlci5jb2wgPSAiYmxhY2siLCBudW1iZXIuY2V4ID0gMSkNCmNvcnJwbG90Lm1peGVkKHByb3BlcnR5X2NvcjUsIGxvd2VyID0gIm51bWJlciIsIHVwcGVyID0gInBpZSIsIHRsLmNvbCA9ICJibGFjayIsbG93ZXIuY29sID0gImJsYWNrIiwgbnVtYmVyLmNleCA9IDEpDQpjb3JycGxvdC5taXhlZChwcm9wZXJ0eV9jb3I2LCBsb3dlciA9ICJudW1iZXIiLCB1cHBlciA9ICJwaWUiLCB0bC5jb2wgPSAiYmxhY2siLGxvd2VyLmNvbCA9ICJibGFjayIsIG51bWJlci5jZXggPSAxKQ0KYGBgDQoqKioNCg0KQXMgc2hvd24gaW4gYWJvdmUgZmlndXJlLCAiTGFuZHNpemUiIGFuZCAiQ2FyIiBoYXMgZmV3IHJlbGV2YW50IHRvIHByaWNlIHdoaWxlIGl0IGhhcyBvdmVyIDIwJSBtaXNzaW5nIHZhbHVlLCBzbyB3ZSB3aWxsIGdpdmUgdGhlbSB1cC4gRm9yIHRoZSByZXN0IHByb2JsZW1hdGljIHZhcmlhYmxlcywgd2UgY29uc2lkZXIgdXNpbmcgYXBwcm9wcmlhdGUgbWV0aG9kcyB0byBmaWxsIGluIG1pc3NpbmcgdmFsdWVzLg0KYGBge3J9DQpwcm9wZXJ0eSA8LSBwcm9wZXJ0eVssYygtMSwgLTksLTEwKV0NCmBgYA0KKioqDQoNCg0KRm9yIHRoZSBtaXNzaW5nIHZhbHVlIGluICJQcmljZSIsIHdlIHdpbGwgZGVsZXRlIHRoZSBtaXNzaW5nIHZhbHVlIGJlY2F1c2UgIlByaWNlIiBpcyBvdXIgZGVwZW5kZW50IHZhcmlhYmxlLiBBbmQgZm9yICJQcm9wZXJ0eWNvdW50Iiwgd2Ugd2lsbCBhbHNvIGRlbGV0ZSBpdHMgbWlzc2luZyB2YWx1ZSBzaW5jZSB0aGUgZmV3IG51bWJlci4NCmBgYHtyfQ0KbXZyMSA9IHdoaWNoKGlzLm5hKHByb3BlcnR5JFByaWNlKSkNCnByb3BlcnR5IDwtIHByb3BlcnR5Wy1tdnIxLF0NCm12cjIgPSB3aGljaChpcy5uYShwcm9wZXJ0eSRQcm9wZXJ0eWNvdW50KSkNCnByb3BlcnR5IDwtIHByb3BlcnR5Wy1tdnIyLF0NCmBgYA0KKioqDQoNCg0KQnkgdmlzdWFsaXppbmcgIlllYXJCdWlsdCIsIHdlIGNhbiBzZWUgdGhhdCBtb3N0IHByb3BlcnRpZXMgd2VyZSBidWlsdCBiZXd0d2VlbiAxOTUwIGFuZCAyMDEwLiBJdHMgbW9kZSBpcyBpbiAyMDAwLTIwMTAuIEhvd2V2ZXIsIGl0cyBtZWRpYW4gYW5kIG1lYW4gdmFsdWUgaXMgYWJvdXQgMTk3MC4gVGh1cywgbWVkaWFuIG1heSBiZSBtb3JlIHJlcHJlc2VudGF0aXZlLg0KYGBge3J9DQpnZ3Bsb3QocHJvcGVydHksIGFlcyh4ID0gWWVhckJ1aWx0KSkgKyBnZW9tX2hpc3RvZ3JhbShiaW5zID0gMjAsIGZpbGwgPSAibGlnaHRibHVlIiwgY29sb3VyID0gImJsYWNrIikNCnN1bW1hcnkocHJvcGVydHkkWWVhckJ1aWx0KQ0KYGBgDQoqKioNCg0KDQpUaGVyZWZvcmUsIHdlIHdpbGwgZmlsbCB3aXRoIG1lZGlhbi4NCmBgYHtyfQ0KcHJvcGVydHlbaXMubmEocHJvcGVydHkkWWVhckJ1aWx0KSwiWWVhckJ1aWx0Il0gPC0gbWVkaWFuKHByb3BlcnR5JFllYXJCdWlsdCxuYS5ybSA9IFQpDQpgYGANCioqKg0KDQpGYWN0b3JzIHN1Y2ggYXMgdGhlIG51bWJlciBvZiBiZWRyb29tcywgYmF0aHJvb21zLCBob3VzZSBzaXplLCByb29tIHR5cGUsIGFuZCBwcmljZSBjb3VsZCBiZSBhbGwgcmVsYXRlZCB0byBlYWNoIG90aGVyLiBUaHVzLCBmb3IgIkJ1aWxkaW5nQXJlYSIsICJCYXRocm9vbSIgYW5kICJCZWRyb29tMiIsIHdlIHdpbGwgZGVsZXRlIHRoZSBjb2x1bW4gd2l0aCBtaXNzaW5nIHZhbHVlIG9mIHRoZXNlIHRocmVlIHZhcmlhYmxlcyBmaXJzdC4NCmBgYHtyfQ0KcHJvcGVydHkgPC1zcWxkZigic2VsZWN0ICogZnJvbSBwcm9wZXJ0eSANCiAgICAgICAgICAgICAgd2hlcmUgQnVpbGRpbmdBcmVhIGlzIG5vdCBudWxsDQogICAgICAgICAgICAgIG9yIEJhdGhyb29tIGlzIG5vdCBudWxsDQogICAgICAgICAgICAgIG9yIEJlZHJvb20yIGlzIG5vdCBudWxsIixyb3cubmFtZXM9VFJVRSkNCg0KYGBgDQoqKioNCg0KDQpUaGVuIHdlIGNoZWNrIHRoZSBhbm9tYWxpZXMgYW5kIHVudXN1YWwgdmFsdWVzIGFnYWluLg0KYGBge3J9DQphbm9tYWxpZXMocHJvcGVydHkpDQpgYGANCioqKg0KDQoNClRoZXJlIGlzIG9ubHkgMSBtaXNzaW5nIHZhbHVlIGluICJCYXRocm9vbSIgd2hpY2ggd2UgY2FuIGp1c3QgZGVsZXRlIGFuZCB0aGUgbWlzc2luZyB2YWx1ZSBpbiAiQnVpbGRpbmdBcmVhIiBpcyBzdGlsbCBvY2N1cHkgNDguNjklLiBIZW5jZSwgd2Ugc2hvdWxkIGZpbGxpbmcgZnVuY3Rpb24gdG8gZGVhbCB3aXRoIHRoZSBwcm9ibGVtLiBCZXNpZGVzLCB3ZSBjYW4gYXNsbyBmaW5kIHRoZXJlIGFyZSAxNiB6ZXJvIHZhbHVlIGluICJCdWlsZGluZ0FyZWEiLCB3aGljaCBpcyB1bnJlYXNvbmFibGUsIHdlIHdpbGwgYWxzbyBkZWxldGUgdGhlIGxpbmUgd2l0aCB0aGUgdmFyaWFibGUgbGVzcyB0aGFuIDEwLg0KYGBge3J9DQptdnIzID0gd2hpY2goaXMubmEocHJvcGVydHkkQmF0aHJvb20pKQ0KcHJvcGVydHkgPC0gcHJvcGVydHlbLW12cjMsXQ0KenZyMSA9IHdoaWNoKHByb3BlcnR5JEJ1aWxkaW5nQXJlYSA8IDEwKQ0KcHJvcGVydHkgPC0gcHJvcGVydHlbLXp2cjEsXQ0KYGBgDQoqKioNCg0KDQpNaWNlKCkgZnVuY3Rpb24gY2FuIGhlbHAgdXNlIHJlYXNvbmFibGUgZGF0YSB2YWx1ZXMgdG8gZXN0aW1hdGUgbWlzc2luZyB2YWx1ZXMuIFdlIHRyeSB1c2UgYWxsIGF2cmlhYmxlcyB0byBidWlsZCBtb2RlbCBmaXJzdC4NCmBgYHtyfQ0KaW1wPC1taWNlKHByb3BlcnR5LCBtZXRob2Q9InJmIiwgc2VlZD0xMjM0KSAgDQpmaXQ8LXdpdGgoaW1wLGxtKFByaWNlfi4sZGF0YT1wcm9wZXJ0eSkpIA0KcG9vbD1wb29sKGZpdCkgDQpvcHRpb25zKGRpZ2l0cz0zKSAgDQpzdW1tYXJ5KHBvb2wpIA0KYGBgDQoqKioNCg0KDQpBcyBzaG93biBhYm92ZSwgdGhlIHAtdmFsdWVzIG9mICJNZXRob2QiLCAiUHJvcGVydHljb3VudCIgYW5kICJCZWRyb29tMiIgYXJlIG5vdCBzaWduaWZpY2FudC4gU28gd2Ugd2lsbCBkZWxldGUgdGhlbSBhbmQgdHJ5IGFnYWluLiBUaGUgcmVzdWx0IHNob3dzIHRoYXQgdGhpcyBtb2RlbCBjb3VsZCBkbyBhIGdvb2Qgam9iLg0KYGBge3J9DQppbXA8LW1pY2UocHJvcGVydHlbLGMoMSwyLDMsNSw3LDgsOSldLCBtZXRob2Q9InJmIiwgc2VlZD0xMjM0KSAgDQpmaXQ8LXdpdGgoaW1wLGxtKFByaWNlfi4sZGF0YT1wcm9wZXJ0eVssYygxLDIsMyw1LDcsOCw5KV0pKSANCnBvb2w9cG9vbChmaXQpIA0Kb3B0aW9ucyhkaWdpdHM9MykgIA0Kc3VtbWFyeShwb29sKQ0KcHJvcGVydHlfbWljZT1jb21wbGV0ZShpbXAsYWN0aW9uPTEpIA0KYGBgDQoqKioNCg0KDQpGaW5hbGx5LCB3ZSB1cGRhdGUgdGhlIHByb3BlcnR5IGRhdGEgYW5kIGNoZWNrIHRoZSBhbm9tYWxpZXMgYW5kIHVudXN1YWwgdmFsdWVzIGFnYWluLiBUaGVyZSBpcyBubyBtaXNzaW5nIHZhbHVlIHJlbWFpbmluZyBhbmQgb25seSB0d28gdmFyaWFibGVzIGhhdmUgemVybyB2YWx1ZXMuDQpgYGB7cn0NCnByb3BlcnR5Wyw4XSA8LSBwcm9wZXJ0eV9taWNlWyw2XQ0KDQphbm9tYWxpZXMocHJvcGVydHkpDQpgYGANCioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKg0KDQojIyBRMiAoYSkgCQkJDQoqKioNCg0KDQpCYXNpYyBkZXNjcmlwdGl2ZSBzdGF0aXN0aWNzIHdpdGggc3VtbWFyeSgpIGZ1bmN0aW9uLiBGcm9tIHRoZSByZXN1bHQsIHdlIGNhbiBnZXQgYSBicmllZiB1bmRlcnN0YW5kIG9mIHRoZSBkYXRhc2V0LiAiVHlwZSIgYW5kICJNZXRob2QiIGFyZSBzdHJpbmcgZm9ybWF0LCB3aGljaCBzaG91bGQgZG8gc29tZSB0cmFuc2Zvcm0gbGF0ZXIuDQpgYGB7cn0NCnN1bW1hcnkocHJvcGVydHkpDQpgYGANCioqKg0KDQoNClRvIG1ha2UgYWJvdmUgcmVzdWx0IG1vcmUgZWFzeSB0byB1bnN0YW5kaW5nLCB3ZSBjYW4gdmlzdWFsaXplIHRoZW0uIFRoZSBmaWd1cmUgYmVsb3cgc2hvd3MgdGhlIGRlbnNpdHkgZGlzdHJpYnV0aW9uIG9mIEJ1aWxkaW5nQXJlYSB2YXJpYWJsZS4gTW9zdCBwcm9wZXJ0aWVzJ3MgYnVpbGRpbmcgc2l6ZSBhcmUgYmV3dGVlbiAxMDAgYW5kIDE0MC4gT25seSBmZXcgb2YgdGhlbSBsYXJnZXIgdGhhbiA0MDAuDQpgYGB7cn0NCnByb3BlcnR5X3RlbXAxIDwtIGZpbHRlcihwcm9wZXJ0eSwgQnVpbGRpbmdBcmVhICA8PSA2MDApDQpnZ3Bsb3QocHJvcGVydHlfdGVtcDEsIGFlcyh4ID0gQnVpbGRpbmdBcmVhKSkgKw0KICBnZW9tX2hpc3RvZ3JhbShhZXMoeSA9IC4uZGVuc2l0eS4uKSwgZmlsbCA9ICJsaWdodGJsdWUiLCBiaW53aWR0aCA9IDIwLCBjb2xvcj0iZ3JheSIpICsgDQogIGdlb21fZGVuc2l0eSgpICsNCiAgdGhlbWVfcGFuZGVyKCkrDQogIGdndGl0bGUoIkJ1aWxkaW5nQXJlYSBEaXN0cmlidXRpb24iKSsNCiAgdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChoanVzdCA9IDAuNSkpDQpgYGANCioqKg0KDQoNCkZvciB0aGUgbWV0aG9kcyBvZiB0aGUgcHJvcGVydHkgc29sZCwgInByb3BlcnR5IHNvbGQiIG9jY3VwaWVzIHRoZSBtYWpvcml0eSB3aGljaCBpcyBhbG1vc3QgNDAwMC4gInByb3BlcnR5IHNvbGQgcHJpb3IiIGFuZCAicHJvcGVydHkgcGFzc2VkIGluIiBoYXZlIGFib3V0IDcwMCwgd2hpbGUgInZlbmRvciBiaWQiIG9ubHkgaGF2ZSBsZXNzIHRoYW4gNjAwLiBGb3IgdHlwZXMgb2YgdGhlIHByZXBvcnR5LCBoIChob3VzZSwgY290dGFnZSwgdmlsbGEsIHNlbWksIHRlcnJhY2UpIG9jY3VwaWVzIHRoZSBtYWpvcml0eS4gdSAodW5pdCwgZHVwbGV4KSBoYXNhYm91dCAxMDAwIGFuZCB0b3duaG91c2UganVzdCBoYXZlIGFib3V0IDUwMC4NCmBgYHtyfQ0KZ2dwbG90KHByb3BlcnR5KSArDQogIGdlb21fYmFyKGFlcyh4ID0gcmVvcmRlcihNZXRob2QsIHRhYmxlKE1ldGhvZClbTWV0aG9kXSkpLCBmaWxsID0gImxpZ2h0Ymx1ZSIpICsNCiAgY29vcmRfZmxpcCgpICsNCiAgdGhlbWVfYncoKSArDQogIGxhYnMoeCA9IE5VTEwpDQpnZ3Bsb3QocHJvcGVydHkpICsNCiAgZ2VvbV9iYXIoYWVzKHggPSByZW9yZGVyKFR5cGUsIHRhYmxlKFR5cGUpW1R5cGVdKSksIGZpbGwgPSAibGlnaHRibHVlIiwgd2lkdGggPSAwLjcpICsNCiAgY29vcmRfZmxpcCgpICsNCiAgdGhlbWVfYncoKSArDQogIGxhYnMoeCA9IE5VTEwpDQpgYGANCioqKg0KDQoNClRoZW4gd2UgcGxvdCB0aGUgZGVuc2l0eSBkaXN0cmlidXRpb24gb2YgcHJpY2Ugb2YgZGlmZmVyZW50IG1ldGhvZHMgYW5kIHR5cGVzLiBXZSBjYW4gc2VlIHRoYXQgZGlmZmVyZW50ICJNZXRob2QiIGFuZCAiVHlwZSIgaGF2ZSB0aGUgYWxtb3N0IHNpbWlsaWFyIG1vZGUgYW5kIG1lZGlhbiwgd2hpY2ggbWVhbnMgIk1ldGhvZCIgYW5kICJUeXBlIiBtYXkgaGF2ZSBhIGZldyBpbmZsdWVuY2Ugb2YgcHJpY2UuDQpgYGB7cn0NCmdncGxvdChwcm9wZXJ0eSwgYWVzKHggPSBQcmljZSkpICsgDQogIGdlb21fZGVuc2l0eShmaWxsPSJ5ZWxsb3ciKSArDQogIGZhY2V0X3dyYXAoIH4gTWV0aG9kKSArDQogIGdlb21fdmxpbmUoYWVzKHhpbnRlcmNlcHQgPSBtZWRpYW4oUHJpY2UpKSwgbGluZXR5cGUgPSAyKSArDQogIHRoZW1lX2J3KCkNCg0KZ2dwbG90KHByb3BlcnR5LCBhZXMoeCA9IFByaWNlKSkgKyANCiAgZ2VvbV9kZW5zaXR5KGZpbGw9InllbGxvdyIpICsNCiAgZmFjZXRfd3JhcCggfiBUeXBlKSArDQogIGdlb21fdmxpbmUoYWVzKHhpbnRlcmNlcHQgPSBtZWRpYW4oUHJpY2UpKSwgbGluZXR5cGUgPSAyKSArDQogIHRoZW1lX2J3KCkNCmBgYA0KKioqDQoNCiMjIFEyIChiKSANCioqKg0KDQoNClF1ZXN0aW9uOiAxLiBIb3cgZGlzdGFuY2UgaW5mbHVlbmNlIHRoZSBzYWxlcyBvZiBwcm9wZXJ0eT8NCiAgICAgICAgICAyLiBIb3cgIk1ldGhvZCIgaW5mbHVlbmNlIHRoZSBzYWxlcyBvZiBwcm9wZXJ0eT8NCiAgICAgICAgICAzLiBIb3cgIlR5cGUiIGluZmx1ZW5jZSB0aGUgc2FsZXMgb2YgcHJvcGVydHk/DQogICAgICAgICAgDQoqKioNCg0KDQpUbyBzb2x2ZSB0aGUgcXVlc3Rpb24gMSwgd2UgcGxvdCB0aGUgcG9pbnQgcGxvdCB0byBzaG93IHRoZSByZWxhdGlvbnNoaXAgYmV0d2VlbiAiRGlzdGFuY2UiIGFuZCAiUHJpY2UiIGFzIHdlbGwgYXMgYSBmaXR0ZWQgbGluZS4gSXQgc2hvd3MgdGhhdCBkaXN0YW5jZSB3aWxsIGluZmx1ZW5jZSB0aGUgcHJpY2Ugb2YgdGhlIHByb3BlcnR5LiBTbyB0aGUgY2xvc2VyIHRoZSBwcm9wZXJ0eSBpcyB0byB0aGUgY2l0eSBjZW50ZXIsIHRoZSBoaWdoZXIgdGhlIGRlbWFuZCBpcyAoQWNjb3JkaW5nIHRvIHRoZSBFY29ub21pY3MsIGRlbWFuZCBkZXRlcm1pbmVzIHByaWNlKS4gDQpgYGB7cn0NCmdncGxvdChwcm9wZXJ0eSwgYWVzKHggPSBEaXN0YW5jZSwgeSA9IFByaWNlKSkgKyANCiAgZ2VvbV9wb2ludChzaXplID0gMC41LCBjb2xvcj0iZGFya2dyYXkiKSArDQogIGdlb21fc21vb3RoKG1ldGhvZCA9ICJsbSIpICsNCiAgdGhlbWVfYncoKQ0KYGBgDQoqKioNCg0KDQpBcyBtZW50aW9uZWQgYWJvdmUsICJCdWlsZGluZ0FyZWEiIGhhcyB0aGUgaGlnaGVzdCBjb3JyZWxhdGlvbiB3aXRoIHByaWNlLiBUaGVuIHdlIGNvdWxkIGV4cGxvcmUgaG93IGJ1aWxkaW5nIHNpemUgaW5mbHVlbmNlIHRoZSBwcmljZSBvZiBkaWZmZXJlbnQgIk1ldGhvZCIgYW5kICJUeXBlIi4gRnJvbSB0aGUgZml0dGVkIGN1cnZlIGluIHR3byBmaWd1cmVzIGJlbG93LCB3ZSBjYW4gZmluZCB0aGF0IHNvbGQgYWZ0ZXIgYXVjdGlvbiBhbmQgdmVuZG9yIGJpZCBwcm9wZXJ0aWVzJyBidWlsZGluZyBzaXplIGhhdmUgYSBoaWdoZXIgdW5pdCBwcmljZSAocHJpY2UgcGVyIHNpemUpLiBGb3IgZGlmZmVyZW50IHR5cGVzLCB0b3duaG91c2UgbWF5IGhhdmUgYSBoaWdoZXIgdW5pdCBwcmljZS4gVGh1cywgdGhlc2UgZmVhdHVyZXMgbWF5IGltcHJvdmUgdGhlIHNhbGVzIG9mIHByb3BlcnR5IHdoZW4gdGhlIHRvdGFsIGFyZWEgb2YgdGhlIGhvdXNlIHNvbGQgaXMgY2VydGFpbi4NCmBgYHtyfQ0KZ2dwbG90KHByb3BlcnR5LCBhZXMoeCA9IEJ1aWxkaW5nQXJlYSwgeSA9IFByaWNlKSkgKyANCiAgZ2VvbV9wb2ludChzaXplID0gMC41LCBjb2xvcj0iZGFya2dyYXkiKSArDQogIGdlb21fc21vb3RoKG1ldGhvZCA9ICJsbSIpICsNCiAgZmFjZXRfd3JhcCggfiBNZXRob2QpICsNCiAgdGhlbWVfYncoKQ0KDQpnZ3Bsb3QocHJvcGVydHksIGFlcyh4ID0gQnVpbGRpbmdBcmVhLCB5ID0gUHJpY2UpKSArIA0KICBnZW9tX3BvaW50KHNpemUgPSAwLjUsIGNvbG9yPSJkYXJrZ3JheSIpICsNCiAgZ2VvbV9zbW9vdGgobWV0aG9kID0gImxtIikgKw0KICBmYWNldF93cmFwKCB+IFR5cGUpICsNCiAgdGhlbWVfYncoKQ0KYGBgDQoqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioNCg0KIyMgUTMgKGEpIA0KKioqDQoNCg0KSW4gdGhpcyBwYXJ0LCB3ZSBuZWVkIHRvIGJ1aWxkIGEgbWFjaGluZSBsZWFybmluZyBtb2RlbCB0byBwcmVkaWN0IHRoZSBmYWN0b3JzIHRoYXQgYWZmZWN0IHRoZSBzYWxlcyBwcmljZS4gSW4gbWFjaGluZSBsZWFybmluZyBtb2RlbHMgdGhhdCBzb2x2ZSBudW1lcmljYWwgcHJvYmxlbXMsIEFydGlmaWNpYWwgTmV1cmFsIE5ldHdvcmsgYWxnb3JpdGhtcyBtYXkgaGF2ZSBiZXR0ZXIgcGVyZm9ybWFuY2UuIEl0cyBjbGFzc2lmaWNhdGlvbiBhY2N1cmFjeSBpcyBoaWdoIGFuZCBpdHMgbGVhcm5pbmcgYWJpbGl0eSBpcyBleHRyZW1lbHkgc3Ryb25nLiBJdCBpcyByb2J1c3QgYW5kIGZhdWx0LXRvbGVyYW50IHRvIG5vaXN5IGRhdGEuIEluIGFkZGl0aW9uLCBpdCBoYXMgdGhlIGFiaWxpdHkgdG8gYXNzb2NpYXRlIGFuZCBjYW4gYXBwcm94aW1hdGUgYW55IG5vbmxpbmVhciByZWxhdGlvbnNoaXAuDQoqKioNCg0KIyMgUTMgKGIpDQoqKioNCg0KDQpBcyBtZXRpb25lZCBhYm92ZSwgd2UgcmVtb3RlZCB0aGUgdHdvIHZhcmlhYmxlcyAiTGFuc2RzaXplIiBhbmQgIkNhciIgYXMgdGhleSBhcmUgbm90IGhpZ2hseSBjb3JyZWxhdGVkIHdpdGggIlByaWNlcyIgYW5kIGhhdmUgbW9yZSB2YWNhbmNpZXMuIA0KVGhlbiB3ZSBhcHBseSBnZ3BhaXJzKCkgZnVuY3Rpb24gdG8gY2hlY2sgdGhlIGNvcnJlbGF0aW4gYW1vbmcgdGhlc2UgdmFyaWFibGUuIFRoZSBjb3JyZWxhdGlvbiBiZXR3ZWVuICJQcm9wZXJ0eWNvdW50IiBhbmQgIlByaWNlIiBpcyBvbmx5IGFib3V0IDUlLiBUaGUgbnVtYmVyIG9mIHByb3BlcnRpZXMgdGhhdCBleGlzdCBpbiB0aGUgc3VidXJiIGFjdHVhbGx5IGNhbiBub3QgaW5mbHVlbnQgdGhlIHByb3BlcnR5IHByaWNlIGluIHJlYWwgd29ybGQuIFRvIG1ha2Ugb3VyIG1vZGVsIG1vcmUgY29uY2lzZSBhbmQgZWZmaWNpZW50LCB3ZSB3aWxsIGRlbGV0ZSB0aGlzIHZhcmlhYmxlLg0KYGBge3J9DQpnZ3BhaXJzKHByb3BlcnR5LCB0aXRsZSA9ICJTY2F0dGVycGxvdCBNYXRyaXggb2YgdGhlIEZlYXR1cmVzIG9mIHRoZSBQcm9wZXJ0eSBEYXRhIFNldCIpDQpwcm9wZXJ0eSA8LSBwcm9wZXJ0eVssLTEwXQ0KYGBgDQoqKioNCg0KIyMgUTMgKGMpCQkJCQkJCQkJDQoqKioNCg0KDQpCZWZvcmUgYXBwbHlpbmcgdGhlIEFOTiBmdW5jdGlvbiwgd2Ugc2hvdWxkIHRyYW5zZm9ybSAiTWV0aG9kIiBhbmQgIlR5cGUiIGludG8gbnVtZXJpYyBmb3JtYXQuDQpgYGB7cn0NCnByb3BlcnR5X0FOTiA8LSBwcm9wZXJ0eQ0KDQp0ID0gYXJyYXkoMCwgNTk4NCkNCmggPSBhcnJheSgwLCA1OTg0KQ0KdSA9IGFycmF5KDAsIDU5ODQpDQpTID0gYXJyYXkoMCwgNTk4NCkNClNQID0gYXJyYXkoMCwgNTk4NCkNClBJID0gYXJyYXkoMCwgNTk4NCkNClZCID0gYXJyYXkoMCwgNTk4NCkNClNBID0gYXJyYXkoMCwgNTk4NCkNCg0KZm9yIChpIGluIDE6IDU5ODQpIHsNCiAgDQogIGlmKHByb3BlcnR5X0FOTiRUeXBlW2ldID09ICJ0Iikge3RbaV09MSAgDQogIGhbaV09MA0KICB1W2ldPTB9ICANCiAgDQogIGlmKHByb3BlcnR5X0FOTiRUeXBlW2ldID09ICJoIikge3RbaV09MA0KICBoW2ldPTENCiAgdVtpXT0wfSAgDQogIA0KICBpZihwcm9wZXJ0eV9BTk4kVHlwZVtpXSA9PSAidSIpIHt0W2ldPTANCiAgaFtpXT0wDQogIHVbaV09MX0NCiAgDQogIGlmKHByb3BlcnR5X0FOTiRNZXRob2RbaV0gPT0gIlMiKSB7U1tpXT0xICANCiAgU1BbaV09MA0KICBQSVtpXT0wDQogIFZCW2ldPTANCiAgU0FbaV09MH0NCiAgDQogIGlmKHByb3BlcnR5X0FOTiRNZXRob2RbaV0gPT0gIlNQIikge1NbaV09MCAgDQogIFNQW2ldPTENCiAgUElbaV09MA0KICBWQltpXT0wDQogIFNBW2ldPTB9DQogIA0KICBpZihwcm9wZXJ0eV9BTk4kTWV0aG9kW2ldID09ICJQSSIpIHtTW2ldPTAgIA0KICBTUFtpXT0wDQogIFBJW2ldPTENCiAgVkJbaV09MA0KICBTQVtpXT0wfSANCiAgDQogIGlmKHByb3BlcnR5X0FOTiRNZXRob2RbaV0gPT0gIlZCIikge1NbaV09MCAgDQogIFNQW2ldPTANCiAgUElbaV09MA0KICBWQltpXT0xDQogIFNBW2ldPTB9IA0KICANCiAgaWYocHJvcGVydHlfQU5OJE1ldGhvZFtpXSA9PSAiU0EiKSB7U1tpXT0wICANCiAgU1BbaV09MA0KICBQSVtpXT0wDQogIFZCW2ldPTANCiAgU0FbaV09MX0gDQp9DQpwcm9wZXJ0eV9BTk4kVHlwZV90PXQNCnByb3BlcnR5X0FOTiRUeXBlX2g9aA0KcHJvcGVydHlfQU5OJFR5cGVfdT11DQpwcm9wZXJ0eV9BTk4kTWV0aG9kX1M9Uw0KcHJvcGVydHlfQU5OJE1ldGhvZF9TUD1TUA0KcHJvcGVydHlfQU5OJE1ldGhvZF9QST1QSQ0KcHJvcGVydHlfQU5OJE1ldGhvZF9WQj1WQg0KcHJvcGVydHlfQU5OJE1ldGhvZF9TQT1TQQ0KcHJvcGVydHlfQU5OIDwtIHByb3BlcnR5X0FOTlssYygtMiwtNCldDQpgYGANCioqKg0KDQoNCkRhdGEgb2JzZXJ2YXRpb24gc2hvdWxkIGJlIHNjYWxlZCBvbnRvIHRoZSBbMCwxXSBpbnRlcnZhbC4NCmBgYHtyfQ0Kc2NhbGUwMSA8LSBmdW5jdGlvbih4KXsNCiAgKHggLSBtaW4oeCkpIC8gKG1heCh4KSAtIG1pbih4KSkNCn0NCnByb3BlcnR5X0FOTiA8LSBwcm9wZXJ0eV9BTk4gJT4lbXV0YXRlX2FsbChzY2FsZTAxKQ0KYGBgDQoqKioNCg0KDQpUaGVuIHNwbGl0aW5nIGludG8gdGVzdCgwLjIpIGFuZCB0cmFpbiBzZXRzICgwLjgpLiANCmBgYHtyfQ0Kc2V0LnNlZWQoMTIzNDEpDQpQcm9wZXJ0eV9UcmFpbiA8LSBzYW1wbGVfZnJhYyh0YmwgPSBwcm9wZXJ0eV9BTk4sIHJlcGxhY2UgPSBGQUxTRSwgc2l6ZSA9IDAuODApDQpQcm9wZXJ0eV9UZXN0IDwtIGFudGlfam9pbihwcm9wZXJ0eV9BTk4sIFByb3BlcnR5X1RyYWluKQ0KYGBgDQoqKioNCg0KDQpGaXJzdGx5LCB3ZSBjb25zdHJ1Y3QgYSBtb2RlbCB3aXRoIDEtaGlkZGVuIGxheWVyLg0KQnkgdXNpbmcgcGxvdCgpIGZ1bmN0aW9uLCB3ZSBjYW4gZ2V0IHRoZSB3ZWlnaHRzIGxlYXJuZWQgYnkgdGhlIEFOTl9tb2RlbDEgbmV1cmFsIG5ldHdvcmsuVGhlIHBsb3QgYWxzbyBzaG93cyB0aGUgc3VtIG9mIFNxdWFyZWQgRXJyb3JzIChTU0UpIG9mIHRoZSB0cmFpbmluZyBkYXRhIHNldCBhbmQgZGlzcGxheXMgdGhlIG51bWJlciBvZiB0cmFpbmluZyBpdGVyYXRpb25zIGJlZm9yZSBjb252ZXJnZW5jZS4gSW4gdGhpcyBtb2RlbCwgU1NFIG9mIHRyYWluaW5nIGRhdGEgc2V0IGlzIDAuNzAxOC4NCmBgYHtyfQ0Kc2V0LnNlZWQoMTIzNDIpDQpBTk5fbW9kZWwxIDwtIG5ldXJhbG5ldChQcmljZSB+IC4sIGRhdGEgPSBQcm9wZXJ0eV9UcmFpbikNCnBsb3QoQU5OX21vZGVsMSkNCmBgYA0KKioqDQoNCg0KVGhlbiB3ZSB2aXN1YWxpc2UgdGhlIHdlaWdodHMgb2YgdmFyaWFibGVzLiBXZSBjYW4gc2VlIHRoYXQgdGhlICJCdWlsZGluZ0FyZWEiIGhhcyB0aGUgaGlnaGVzdCBpbXBvcnRhbmNlIG9mIHRoZSBwcmljZSBwcmlkaWN0aW5nLCBzbyBpdCBtYXkgaW5mbHVlbmNlIHByaWNlIG1vc3QuICJEaXN0YW5jZSIgYW5kICJCYXRocm9vbSIgaXMgdGhlIHNlY29uZCBhbmQgdGhpcmQgaW1wb3J0YW5jZSBmYWN0b3IuICJSb29tcyIgYW5kICJUeXBlX3UiIGhhdmUgb3ZlciAwLjA1IGltcG9ydGFuY2UuDQpgYGB7cn0NCmdhcnNvbihBTk5fbW9kZWwxKSArIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIGhqdXN0ID0gMSkpDQpwbG90bmV0KEFOTl9tb2RlbDEpDQpgYGANCioqKg0KDQojIyBRMyAoZCkJCQkJDQoqKioNCg0KDQpUbyBldmFsdWF0ZSB0aGUgcGVyZm9ybWFuY2UsIHdlIHdpbGwgdXNlIFNTRSwgTVNFIGFuZCBSLXNxdWFyZWQuICANCmBgYHtyfQ0KI2NyZWF0ZSBmdW5jdGlvbiB0byBjYWN1bGF0ZSBwZXJmb3JtYW5jZSBtZXRyaWNzDQpzc2VmdW4gPC0gZnVuY3Rpb24ocHJlZCwgb2JzKSBzdW0oKGFzLm51bWVyaWModW5saXN0KHByZWQpKSAtIG9icyleMikvMg0KbXNlZnVuIDwtIGZ1bmN0aW9uKHByZWQsIG9icykgbWVhbigoYXMubnVtZXJpYyh1bmxpc3QocHJlZCkpIC0gb2JzKV4yLzIpDQpzc3RmdW4gPC0gZnVuY3Rpb24ob2JzKSBzdW0oKG9icyAtIG1lYW4ob2JzKSleMikvMg0KYGBgDQoqKioNCg0KDQpCeSBjYWN1bGF0aW5nIHRoZSB0aHJlZSBtZXRyaWNzIG9mIHRoZSB0cmFpbmluZyBtb2RlbC4gQWNjb3JkaW5nIHRvIHRoZSByZXN1bHQsIHdlIGNhbiBmaW5kIHRoYXQgdGhlIFNTRSBpcyA0LjExODQsIE1zZSBpcyAwLjAwMDkgYW5kIFItc3F1YXJlZCBpcyAwLjQ5NDgsIHdoaWNoIHN1Z2dlc3RzIHJlc3VsdHMgY2FuIGJlIGV4cGxhaW5lZCBieSB0aGUgdHJhaW4gbW9kZWwuIFNpbmNlIFItc3F1YXJlZCBpcyBub3Qgc28gY2xvc2UgdG8gMSwgaXQgc2hvdWxkIGdldCBpbXByb3ZlZC4NCmBgYHtyfQ0Kc3NlMTEgPC0gc3NlZnVuKEFOTl9tb2RlbDEkbmV0LnJlc3VsdCwgUHJvcGVydHlfVHJhaW5bLCAyXSkNCm1zZTExIDwtIG1zZWZ1bihBTk5fbW9kZWwxJG5ldC5yZXN1bHQsIFByb3BlcnR5X1RyYWluWywgMl0pDQpyczExIDwtIDEtc3NlMTEvc3N0ZnVuKFByb3BlcnR5X1RyYWluWywgMl0pDQpwYXN0ZSgiVHJhaW4gU1NFOiAiLCByb3VuZChzc2UxMSwgNCksICI7IFRyYWluIE1TRTogIiwgcm91bmQobXNlMTEsIDQpLCAiOyBUcmFpbiBSLXNxdWFyZWQ6ICIsIHJvdW5kKHJzMTEsIDQpKQ0KYGBgDQoqKioNCg0KDQpUaGVuIHdlIHJ1biB0ZXN0IG9ic2VydmF0aW9ucyBhbmQgY2FjdWxhdGluZyB0aGUgcGVyZm9ybWFuY2UgbWV0cmljcy4gVGhlIFNTRSBvZiB0ZXN0IG1vZGVsIGlzIGJldHRlciB0aGFuIHRyYWluIG1vZGVsLCB3aGljaCBpcyBvbmx5IDEuMDU1NS4gSG93ZXZlciwgTVNFIGRpZCBub3QgZ2V0IGEgc2lnbmlmaWNhbnQgaW1wcm92ZW1lbnQsIHNvIGFjdHVhbGx5IHRoZSB0ZXN0IG1vZGVsIGhhcyBhbG1vc3QgdGhlIHNhbWUgcGVyZm9ybWFuY2Ugd2l0aCB0aGUgdHJhaW5pbmcgb25lLCB3aGljaCBjYW4gYWxzbyBiZSBmb3VuZCBpbiBSLXNxdWFyZWQuDQpgYGB7cn0NCiNSdW4gdGVzdCBvYnNlcnZhdGlvbnMNClRlc3RfQU5OMV9PdXRwdXQgPC0gY29tcHV0ZShBTk5fbW9kZWwxLCBQcm9wZXJ0eV9UZXN0WywgLTJdKSRuZXQucmVzdWx0DQoNCnNzZTEyIDwtIHNzZWZ1bihUZXN0X0FOTjFfT3V0cHV0LCBQcm9wZXJ0eV9UZXN0WywgMl0pDQptc2UxMiA8LSBtc2VmdW4oVGVzdF9BTk4xX091dHB1dCwgUHJvcGVydHlfVGVzdFssIDJdKQ0KcnMxMiA8LSAxLXNzZTEyL3NzdGZ1bihQcm9wZXJ0eV9UZXN0WywgMl0pDQpwYXN0ZSgiVGVzdCBTU0U6ICIsIHJvdW5kKHNzZTEyLCA0KSwgIjsgVGVzdCBNU0U6ICIsIHJvdW5kKG1zZTEyLCA0KSwgIjsgVGVzdCBSLXNxdWFyZWQ6ICIsIHJvdW5kKHJzMTIsIDQpKQ0KYGBgDQoqKioNCg0KIyMgUTMgKGUpDQoqKioNCg0KDQpUbyBpbXByb3ZlIG91ciBtb2RlbCwgd2UgY291bGQgdXNlICJyZXAiIHBhcmFtZXRlciB0byBsZXQgdGhlIEFOTiBhbGdyaXRobm0gcnVuIG1hbnkgdGltZSBhbmQgZmluZCB0aGUgYmVzdCBvbmUuDQpgYGB7cn0NCnNldC5zZWVkKDEyMzQzKQ0KQU5OX21vZGVsMiA8LSBuZXVyYWxuZXQoUHJpY2UgfiAuLCBkYXRhID0gUHJvcGVydHlfVHJhaW4sIHJlcCA9IDUpDQpwbG90KEFOTl9tb2RlbDIsIHJlcCA9ICJiZXN0IikNCk1pbl9udW0yID0gd2hpY2gubWluKEFOTl9tb2RlbDIkcmVzdWx0Lm1hdHJpeFsxLF0pDQpgYGANCioqKg0KDQoNClRoZW4gd2UgY2FjdWxhdGUgdGhlIHBlcmZvcm1hbmNlIG9mIHRoaXMgdHJhaW4gbW9kZWwgYW5kIHRlc3QgbW9kZWwuDQpgYGB7cn0NCnNzZTIxIDwtIHNzZWZ1bihBTk5fbW9kZWwyJG5ldC5yZXN1bHRbW01pbl9udW0yXV0sIFByb3BlcnR5X1RyYWluWywgMl0pDQptc2UyMSA8LSBtc2VmdW4oQU5OX21vZGVsMiRuZXQucmVzdWx0W1tNaW5fbnVtMl1dLCBQcm9wZXJ0eV9UcmFpblssIDJdKQ0KcnMyMSA8LSAxLXNzZTIxL3NzdGZ1bihQcm9wZXJ0eV9UcmFpblssIDJdKQ0KcGFzdGUoIlRyYWluIFNTRTogIiwgcm91bmQoc3NlMjEsIDQpLCAiOyBUcmFpbiBNU0U6ICIsIHJvdW5kKG1zZTIxLCA0KSwgIjsgVHJhaW4gUi1zcXVhcmVkOiAiLCByb3VuZChyczIxLCA0KSkNCg0KVGVzdF9BTk4yX091dHB1dCA8LSBjb21wdXRlKEFOTl9tb2RlbDIsIFByb3BlcnR5X1Rlc3RbLCAtMl0sIHJlcCA9IE1pbl9udW0yKSRuZXQucmVzdWx0DQoNCnNzZTIyIDwtIHNzZWZ1bihUZXN0X0FOTjJfT3V0cHV0LCBQcm9wZXJ0eV9UZXN0WywgMl0pDQptc2UyMiA8LSBtc2VmdW4oVGVzdF9BTk4yX091dHB1dCwgUHJvcGVydHlfVGVzdFssIDJdKQ0KcnMyMiA8LSAxLXNzZTIyL3NzdGZ1bihQcm9wZXJ0eV9UZXN0WywgMl0pDQpwYXN0ZSgiVGVzdCBTU0U6ICIsIHJvdW5kKHNzZTIyLCA0KSwgIjsgVGVzdCBNU0U6ICIsIHJvdW5kKG1zZTIyLCA0KSwgIjsgVGVzdCBSLXNxdWFyZWQ6ICIsIHJvdW5kKHJzMjIsIDQpKQ0KYGBgDQoqKioNCg0KDQpJbiBhZGRpdGlvbiwgd2UgY2FuIGFsc28gaW5jcmVhc2UgdGhlIGhpZGRlbiBuZXVyb25zIG51bWJlciBpbnRvIDEwICgxNSB2YXJpYWJsZXMgKiAyLzMpLiBCb3RoIHRyYWluIG1vZGVsIGFuZCB0ZXN0IG1vZGVsIGdldCBpbXByb3ZlZC4gMTAgaGlkZGVuIGNvdWxkIGRvIGEgYmV0dGVyIGpvYiB0aGFuIHRoZSBmb3JtZXIgb25lLg0KYGBge3J9DQpzZXQuc2VlZCgxMjM0NCkNCkFOTl9tb2RlbDMgPC0gbmV1cmFsbmV0KFByaWNlIH4gLiwgZGF0YSA9IFByb3BlcnR5X1RyYWluLCBoaWRkZW4gPSAxMCkNCg0Kc3NlMzEgPC0gc3NlZnVuKEFOTl9tb2RlbDMkbmV0LnJlc3VsdCwgUHJvcGVydHlfVHJhaW5bLCAyXSkNCm1zZTMxIDwtIG1zZWZ1bihBTk5fbW9kZWwzJG5ldC5yZXN1bHQsIFByb3BlcnR5X1RyYWluWywgMl0pDQpyczMxIDwtIDEtc3NlMzEvc3N0ZnVuKFByb3BlcnR5X1RyYWluWywgMl0pDQpwYXN0ZSgiVHJhaW4gU1NFOiAiLCByb3VuZChzc2UzMSwgNCksICI7IFRyYWluIE1TRTogIiwgcm91bmQobXNlMzEsIDQpLCAiOyBUcmFpbiBSLXNxdWFyZWQ6ICIsIHJvdW5kKHJzMzEsIDQpKQ0KDQpUZXN0X0FOTjNfT3V0cHV0IDwtIGNvbXB1dGUoQU5OX21vZGVsMywgUHJvcGVydHlfVGVzdFssIC0yXSkkbmV0LnJlc3VsdA0KDQpzc2UzMiA8LSBzc2VmdW4oVGVzdF9BTk4zX091dHB1dCwgUHJvcGVydHlfVGVzdFssIDJdKQ0KbXNlMzIgPC0gbXNlZnVuKFRlc3RfQU5OM19PdXRwdXQsIFByb3BlcnR5X1Rlc3RbLCAyXSkNCnJzMzIgPC0gMS1zc2UzMi9zc3RmdW4oUHJvcGVydHlfVGVzdFssIDJdKQ0KcGFzdGUoIlRlc3QgU1NFOiAiLCByb3VuZChzc2UzMiwgNCksICI7IFRlc3QgTVNFOiAiLCByb3VuZChtc2UzMiwgNCksICI7IFRlc3QgUi1zcXVhcmVkOiAiLCByb3VuZChyczMyLCA0KSkNCmBgYA0KKioqDQoNCg0KV2UgY2FuIGFsc28gYWRkIGxheWVyIGludG8gMiAoMi1IaWRkZW4gTGF5ZXJzLCBMYXllci0xIDEwLW5ldXJvbnMsIExheWVyLTIsIDMtbmV1cm9uKS4gVGhlIHRyYWluIG1vZGVsIGlzIGJldHRlciB0aGFuIHRoZSBmb3JtZXIgb25lLCBidXQgdGVzdCBtb2RlbCBkb2VzIG5vdCBpbXByb3ZlLiANCmBgYHtyfQ0Kc2V0LnNlZWQoMTIzNDUpDQpBTk5fbW9kZWw0IDwtIG5ldXJhbG5ldChQcmljZSB+IC4sIGRhdGEgPSBQcm9wZXJ0eV9UcmFpbiwgaGlkZGVuID0gYygxMCwgMyksIHN0ZXBtYXggPSAxMDAwMDAwMCkNCg0Kc3NlNDEgPC0gc3NlZnVuKEFOTl9tb2RlbDQkbmV0LnJlc3VsdCwgUHJvcGVydHlfVHJhaW5bLCAyXSkNCm1zZTQxIDwtIG1zZWZ1bihBTk5fbW9kZWw0JG5ldC5yZXN1bHQsIFByb3BlcnR5X1RyYWluWywgMl0pDQpyczQxIDwtIDEtc3NlNDEvc3N0ZnVuKFByb3BlcnR5X1RyYWluWywgMl0pDQpwYXN0ZSgiVHJhaW4gU1NFOiAiLCByb3VuZChzc2U0MSwgNCksICI7IFRyYWluIE1TRTogIiwgcm91bmQobXNlNDEsIDQpLCAiOyBUcmFpbiBSLXNxdWFyZWQ6ICIsIHJvdW5kKHJzNDEsIDQpKQ0KDQpUZXN0X0FOTjRfT3V0cHV0IDwtIGNvbXB1dGUoQU5OX21vZGVsNCwgUHJvcGVydHlfVGVzdFssIC0yXSkkbmV0LnJlc3VsdA0KDQpzc2U0MiA8LSBzc2VmdW4oVGVzdF9BTk40X091dHB1dCwgUHJvcGVydHlfVGVzdFssIDJdKQ0KbXNlNDIgPC0gbXNlZnVuKFRlc3RfQU5ONF9PdXRwdXQsIFByb3BlcnR5X1Rlc3RbLCAyXSkNCnJzNDIgPC0gMS1zc2U0Mi9zc3RmdW4oUHJvcGVydHlfVGVzdFssIDJdKQ0KcGFzdGUoIlRlc3QgU1NFOiAiLCByb3VuZChzc2U0MiwgNCksICI7IFRlc3QgTVNFOiAiLCByb3VuZChtc2U0MiwgNCksICI7IFRlc3QgUi1zcXVhcmVkOiAiLCByb3VuZChyczQyLCA0KSkNCmBgYA0KKioqDQoNCg0KQ2hhbmdpbmcgYWN0aXZhdGlvbiBmdW5jdGlvbiBjb3VsZCBiZSBhIHdheSB0byBpbXByb3ZlIG91ciBtb2RlbC4gVGhpcyBtb2RlbCBoYXMgaGlnaGVyIFItc3F1YXJlZCBhbmQgbG93ZXIgU1NFL01TRSBpbiB0ZXN0IG1vZGVsLCBidXQgdGhlIGltcHJvdmVtZW50IGlzIG5vdCBzaWduaWZpY2FudC4NCmBgYHtyfQ0Kc2V0LnNlZWQoMTIzNDYpDQpBTk5fbW9kZWw1IDwtIG5ldXJhbG5ldChQcmljZSB+IC4sIGRhdGEgPSBQcm9wZXJ0eV9UcmFpbiwgaGlkZGVuID0gMTAsIGFjdC5mY3QgPSAibG9naXN0aWMiKQ0KDQpzc2U1MSA8LSBzc2VmdW4oQU5OX21vZGVsNSRuZXQucmVzdWx0LCBQcm9wZXJ0eV9UcmFpblssIDJdKQ0KbXNlNTEgPC0gbXNlZnVuKEFOTl9tb2RlbDUkbmV0LnJlc3VsdCwgUHJvcGVydHlfVHJhaW5bLCAyXSkNCnJzNTEgPC0gMS1zc2U1MS9zc3RmdW4oUHJvcGVydHlfVHJhaW5bLCAyXSkNCnBhc3RlKCJUcmFpbiBTU0U6ICIsIHJvdW5kKHNzZTUxLCA0KSwgIjsgVHJhaW4gTVNFOiAiLCByb3VuZChtc2U1MSwgNCksICI7IFRyYWluIFItc3F1YXJlZDogIiwgcm91bmQocnM1MSwgNCkpDQoNClRlc3RfQU5ONV9PdXRwdXQgPC0gY29tcHV0ZShBTk5fbW9kZWw1LCBQcm9wZXJ0eV9UZXN0WywgLTJdKSRuZXQucmVzdWx0DQoNCnNzZTUyIDwtIHNzZWZ1bihUZXN0X0FOTjVfT3V0cHV0LCBQcm9wZXJ0eV9UZXN0WywgMl0pDQptc2U1MiA8LSBtc2VmdW4oVGVzdF9BTk41X091dHB1dCwgUHJvcGVydHlfVGVzdFssIDJdKQ0KcnM1MiA8LSAxLXNzZTUyL3NzdGZ1bihQcm9wZXJ0eV9UZXN0WywgMl0pDQpwYXN0ZSgiVGVzdCBTU0U6ICIsIHJvdW5kKHNzZTUyLCA0KSwgIjsgVGVzdCBNU0U6ICIsIHJvdW5kKG1zZTUyLCA0KSwgIjsgVGVzdCBSLXNxdWFyZWQ6ICIsIHJvdW5kKHJzNTIsIDQpKQ0KYGBgDQoqKioNCg0KDQpUaGVyZWZvcmUsIHdlIHdpbGwgdHJ5IG1vcmUgbW9kZWxzIHRvIGZpbmQgdGhlIGJlc3Qgb25lLg0KYGBge3J9DQpzZXQuc2VlZCgxMjM0NykNCiNoaWRkZW4gPSA4DQpBTk5fbW9kZWw2IDwtIG5ldXJhbG5ldChQcmljZSB+IC4sIGRhdGEgPSBQcm9wZXJ0eV9UcmFpbiwgaGlkZGVuID0gOCkNCnNzZTYxIDwtIHNzZWZ1bihBTk5fbW9kZWw2JG5ldC5yZXN1bHQsIFByb3BlcnR5X1RyYWluWywgMl0pDQptc2U2MSA8LSBtc2VmdW4oQU5OX21vZGVsNiRuZXQucmVzdWx0LCBQcm9wZXJ0eV9UcmFpblssIDJdKQ0KcnM2MSA8LSAxLXNzZTYxL3NzdGZ1bihQcm9wZXJ0eV9UcmFpblssIDJdKQ0KVGVzdF9BTk42X091dHB1dCA8LSBjb21wdXRlKEFOTl9tb2RlbDYsIFByb3BlcnR5X1Rlc3RbLCAtMl0pJG5ldC5yZXN1bHQNCnNzZTYyIDwtIHNzZWZ1bihUZXN0X0FOTjZfT3V0cHV0LCBQcm9wZXJ0eV9UZXN0WywgMl0pDQptc2U2MiA8LSBtc2VmdW4oVGVzdF9BTk42X091dHB1dCwgUHJvcGVydHlfVGVzdFssIDJdKQ0KcnM2MiA8LSAxLXNzZTYyL3NzdGZ1bihQcm9wZXJ0eV9UZXN0WywgMl0pDQoNCiNoaWRkZW4gPSBjKDgsIDMpDQpBTk5fbW9kZWw3IDwtIG5ldXJhbG5ldChQcmljZSB+IC4sIGRhdGEgPSBQcm9wZXJ0eV9UcmFpbiwgaGlkZGVuID0gYyg4LCAzKSkNCnNzZTcxIDwtIHNzZWZ1bihBTk5fbW9kZWw3JG5ldC5yZXN1bHQsIFByb3BlcnR5X1RyYWluWywgMl0pDQptc2U3MSA8LSBtc2VmdW4oQU5OX21vZGVsNyRuZXQucmVzdWx0LCBQcm9wZXJ0eV9UcmFpblssIDJdKQ0KcnM3MSA8LSAxLXNzZTcxL3NzdGZ1bihQcm9wZXJ0eV9UcmFpblssIDJdKQ0KVGVzdF9BTk43X091dHB1dCA8LSBjb21wdXRlKEFOTl9tb2RlbDcsIFByb3BlcnR5X1Rlc3RbLCAtMl0pJG5ldC5yZXN1bHQNCnNzZTcyIDwtIHNzZWZ1bihUZXN0X0FOTjdfT3V0cHV0LCBQcm9wZXJ0eV9UZXN0WywgMl0pDQptc2U3MiA8LSBtc2VmdW4oVGVzdF9BTk43X091dHB1dCwgUHJvcGVydHlfVGVzdFssIDJdKQ0KcnM3MiA8LSAxLXNzZTcyL3NzdGZ1bihQcm9wZXJ0eV9UZXN0WywgMl0pDQoNCiNoaWRkZW4gPSBjKDEwLCAzKSwgYWN0LmZjdCA9ICJsb2dpc3RpYyINCkFOTl9tb2RlbDggPC0gbmV1cmFsbmV0KFByaWNlIH4gLiwgZGF0YSA9IFByb3BlcnR5X1RyYWluLCBoaWRkZW4gPSBjKDEwLCAzKSwgYWN0LmZjdCA9ICJsb2dpc3RpYyIpDQpzc2U4MSA8LSBzc2VmdW4oQU5OX21vZGVsOCRuZXQucmVzdWx0LCBQcm9wZXJ0eV9UcmFpblssIDJdKQ0KbXNlODEgPC0gbXNlZnVuKEFOTl9tb2RlbDgkbmV0LnJlc3VsdCwgUHJvcGVydHlfVHJhaW5bLCAyXSkNCnJzODEgPC0gMS1zc2U4MS9zc3RmdW4oUHJvcGVydHlfVHJhaW5bLCAyXSkNClRlc3RfQU5OOF9PdXRwdXQgPC0gY29tcHV0ZShBTk5fbW9kZWw4LCBQcm9wZXJ0eV9UZXN0WywgLTJdKSRuZXQucmVzdWx0DQpzc2U4MiA8LSBzc2VmdW4oVGVzdF9BTk44X091dHB1dCwgUHJvcGVydHlfVGVzdFssIDJdKQ0KbXNlODIgPC0gbXNlZnVuKFRlc3RfQU5OOF9PdXRwdXQsIFByb3BlcnR5X1Rlc3RbLCAyXSkNCnJzODIgPC0gMS1zc2U4Mi9zc3RmdW4oUHJvcGVydHlfVGVzdFssIDJdKQ0KDQojUmVzY2FsZSBmb3IgdGFuaCBhY3RpdmF0aW9uIGZ1bmN0aW9uDQpzY2FsZTExIDwtIGZ1bmN0aW9uKHgpIHsNCiAgKDIgKiAoKHggLSBtaW4oeCkpLyhtYXgoeCkgLSBtaW4oeCkpKSkgLSAxDQp9DQpQcm9wZXJ0eV9UcmFpbjEgPC0gUHJvcGVydHlfVHJhaW4gJT4lIG11dGF0ZV9hbGwoc2NhbGUxMSkNClByb3BlcnR5X1Rlc3QxIDwtIFByb3BlcnR5X1Rlc3QgJT4lIG11dGF0ZV9hbGwoc2NhbGUxMSkNCg0KI2hpZGRlbiA9IDEwLCBhY3QuZmN0ID0gInRhbmgiDQpBTk5fbW9kZWw5IDwtIG5ldXJhbG5ldChQcmljZSB+IC4sIGRhdGEgPSBQcm9wZXJ0eV9UcmFpbjEsIGFjdC5mY3QgPSAidGFuaCIpDQpzc2U5MSA8LSBzc2VmdW4oQU5OX21vZGVsOSRuZXQucmVzdWx0LCBQcm9wZXJ0eV9UcmFpbjFbLCAyXSkNCm1zZTkxIDwtIG1zZWZ1bihBTk5fbW9kZWw5JG5ldC5yZXN1bHQsIFByb3BlcnR5X1RyYWluMVssIDJdKQ0KcnM5MSA8LSAxLXNzZTkxL3NzdGZ1bihQcm9wZXJ0eV9UcmFpbjFbLCAyXSkNClRlc3RfQU5OOV9PdXRwdXQgPC0gY29tcHV0ZShBTk5fbW9kZWw5LCBQcm9wZXJ0eV9UZXN0MVssIC0yXSkkbmV0LnJlc3VsdA0Kc3NlOTIgPC0gc3NlZnVuKFRlc3RfQU5OOV9PdXRwdXQsIFByb3BlcnR5X1Rlc3QxWywgMl0pDQptc2U5MiA8LSBtc2VmdW4oVGVzdF9BTk45X091dHB1dCwgUHJvcGVydHlfVGVzdDFbLCAyXSkNCnJzOTIgPC0gMS1zc2U5Mi9zc3RmdW4oUHJvcGVydHlfVGVzdDFbLCAyXSkNCmBgYA0KKioqDQoNCg0KVGhlbiB3ZSB2aXN1YWxpemUgdGhlIG1ldHJpY3Mgb2YgdGhlc2UgbW9kZWxzLiBBY2NvcmRpbmcgdG8gdGhlIHJlc3VsdHMsIEFOTjQgbW9kZWwgcGVyZm9ybSBiZXR0ZXIgaW4gdHJhaW4gbW9kZWwgd2hpbGUgQU5OOCBtb2RlbCBkbyBhIGJldHRlciBqb2IgaW4gdGVzdCBtb2RlbC4NCmBgYHtyfQ0KdGliYmxlKE5ldHdvcmsgPSByZXAoYygiQU5OMSIsICJBTk4yIiwgIkFOTjMiLCAiQU5ONCIsICJBTk41IiwgIkFOTjYiLCAiQU5ONyIsICJBTk44IiwNCiAgICAgICAgICAgICAgICAgICAgICAgIkFOTjkiKSwgZWFjaCA9IDIpLCANCiAgICAgICBEYXRhU2V0ID0gcmVwKGMoIlRyYWluIFNTRSIsICJUZXN0IFNTRSIpLCB0aW1lID0gOSksIA0KICAgICAgIE1ldHJpY3MgPSBjKHNzZTExLCBzc2UxMiwgc3NlMjEsIHNzZTIyLCBzc2UzMSwgc3NlMzIsIHNzZTQxLCBzc2U0Miwgc3NlNTEsIHNzZTUyLA0KICAgICAgICAgICAgICAgICAgIHNzZTYxLCBzc2U2Miwgc3NlNzEsIHNzZTcyLCBzc2U4MSwgc3NlODIsIHNzZTkxLCBzc2U5MikpICU+JQ0KICBnZ3Bsb3QoYWVzKE5ldHdvcmssIE1ldHJpY3MsIGZpbGwgPSBEYXRhU2V0KSkgKyANCiAgZ2VvbV90ZXh0KGFlcyhsYWJlbD1yb3VuZChNZXRyaWNzLDMpKSwgcG9zaXRpb249cG9zaXRpb25fZG9kZ2UoMC45KSwgdmp1c3QgPSAtMC45KSArIA0KICBnZW9tX2NvbChwb3NpdGlvbiA9ICJkb2RnZSIpICsgdGhlbWVfYncoKSArIGdndGl0bGUoIkRpc3RyaWJ1dGlvbiBvZiBBTk4ncyBTU0UiKQ0KDQp0aWJibGUoTmV0d29yayA9IHJlcChjKCJBTk4xIiwgIkFOTjIiLCAiQU5OMyIsICJBTk40IiwgIkFOTjUiLCAiQU5ONiIsICJBTk43IiwgIkFOTjgiLA0KICAgICAgICAgICAgICAgICAgICAgICAiQU5OOSIpLCBlYWNoID0gMiksIA0KICAgICAgIERhdGFTZXQgPSByZXAoYygiVHJhaW4gTVNFIiwgIlRlc3QgTVNFIiksDQogICAgICAgICAgICAgICAgICAgICB0aW1lID0gOSksIA0KICAgICAgIE1ldHJpY3MgPSBjKG1zZTExLCBtc2UxMiwgbXNlMjEsIG1zZTIyLCBtc2UzMSwgbXNlMzIsIG1zZTQxLCBtc2U0MiwgbXNlNTEsIG1zZTUyLA0KICAgICAgICAgICAgICAgICAgIG1zZTYxLCBtc2U2MiwgbXNlNzEsIG1zZTcyLCBtc2U4MSwgbXNlODIsIG1zZTkxLCBtc2U5MikpICU+JQ0KICBnZ3Bsb3QoYWVzKE5ldHdvcmssIE1ldHJpY3MsIGZpbGwgPSBEYXRhU2V0KSkgKyANCiAgZ2VvbV90ZXh0KGFlcyhsYWJlbD1yb3VuZChNZXRyaWNzLDMpKSwgcG9zaXRpb249cG9zaXRpb25fZG9kZ2UoMC45KSwgdmp1c3QgPSAtMC45KSArIA0KICBnZW9tX2NvbChwb3NpdGlvbiA9ICJkb2RnZSIpICsgdGhlbWVfYncoKSArIGdndGl0bGUoIkRpc3RyaWJ1dGlvbiBvZiBBTk4ncyBNU0UiKQ0KDQp0aWJibGUoTmV0d29yayA9IHJlcChjKCJBTk4xIiwgIkFOTjIiLCAiQU5OMyIsICJBTk40IiwgIkFOTjUiLCAiQU5ONiIsICJBTk43IiwgIkFOTjgiLA0KICAgICAgICAgICAgICAgICAgICAgICAiQU5OOSIpLCBlYWNoID0gMiksIA0KICAgICAgIERhdGFTZXQgPSByZXAoYygiVHJhaW4gUjIiLCAiVGVzdCBSMiIpLA0KICAgICAgICAgICAgICAgICAgICAgdGltZSA9IDkpLCANCiAgICAgICBNZXRyaWNzID0gYyhyczExLCByczEyLCByczIxLCByczIyLCByczMxLCByczMyLCByczQxLCByczQyLCByczUxLCByczUyLCByczYxLCByczYyLA0KICAgICAgICAgICAgICAgICAgIHJzNzEsIHJzNzIsIHJzODEsIHJzODIsIHJzOTEsIHJzOTIpKSAlPiUNCiAgZ2dwbG90KGFlcyhOZXR3b3JrLCBNZXRyaWNzLCBmaWxsID0gRGF0YVNldCkpICsgDQogIGdlb21fdGV4dChhZXMobGFiZWw9cm91bmQoTWV0cmljcywzKSksIHBvc2l0aW9uPXBvc2l0aW9uX2RvZGdlKDAuOSksIHZqdXN0ID0gLTAuOSkgKyANCiAgZ2VvbV9jb2wocG9zaXRpb24gPSAiZG9kZ2UiKSArIHRoZW1lX2J3KCkgKyBnZ3RpdGxlKCJEaXN0cmlidXRpb24gb2YgQU5OJ3MgUi1zcXVhcmVkIikNCmBgYA0KKioqDQoNCg0KV2UgcGxvdCB0aGUgcmVsYXRpb25zaGlwIG1hcCBvZiBBTk40Lg0KYGBge3J9DQpwbG90KEFOTl9tb2RlbDQpDQpgYGANCioqKg0KDQoNCldlIHBsb3QgdGhlIHJlbGF0aW9uc2hpcCBtYXAgb2YgQU5OOC4NCmBgYHtyfQ0KcGxvdChBTk5fbW9kZWw4KQ0KYGBgDQoqKioNCg0KDQpEdWUgdG8gdGhlIGxpbWl0ZWQgbnVtYmVyIG9mIG1vZGVscyB3ZSB0cnksIHRoaXMgbWV0aG9kIGlzIG5vdCBvcHRpbWFsLiBXZSBjYW4gZnVydGhlciB0cnkgZGVlcCBsZWFybmluZywgc3VjaCBhcyBjb252b2x1dGlvbmFsIG5ldXJhbCBuZXR3b3Jrcy4gT24gdGhlIGJhc2lzIG9mIHRoZSBvcmlnaW5hbCBtdWx0aS1sYXllciBuZXVyYWwgbmV0d29yaywgYSBmZWF0dXJlIGxlYXJuaW5nIHBhcnQgaXMgYWRkZWQsIHdoaWNoIGltaXRhdGVzIHRoZSBjbGFzc2lmaWNhdGlvbiBvZiBzaWduYWwgcHJvY2Vzc2luZyBieSB0aGUgaHVtYW4gYnJhaW4uDQoNCioqKg0KDQojIyBRMyAoZikNCioqKg0KDQoNCkFjY29yZGluZyB0byB0aGUgcmVzdWx0cyBvZiBkYXRhIGV4cGxvcmF0aW9uIChGaWd1cmUgMSBhbmQgRmlndXJlIDIpIGFuZCBtZWFjaGluZSBsZWFybmluZyBtb2RlbChGaWd1cmUgMyksICJCdWlsZGluZ0FyZWEiLCAiQmF0aHJvb20iIGFuZCAiRGlzdGFuY2UiIGhhdmUgaGlnaGx5IGluZmx1ZW5jZSBvZiB0aGUgcHJvcGVydHkgcHJpY2UuIFNvIGlmIHRoZSBhZ2VuY3kgd2FudCB0byBnZXQgbW9yZSBzYWxlcyBhbW91bnQsIHRoZXkgc2hvdWxkIGNvbnNpZGVyIGdldCBtb3JlIHByb3BlcnR5IHdpdGggYmlnZ2VyIGJ1aWxkaW5nIHNpemUsIG1vcmUgYmF0aHJvb21zIGFuZCBsZXNzIGRpc3RhbmNlIHRvIGNpdHkgY2VudGVyLiBBbmQgaWYgdGhleSB3YW50IHRvIGdldCBtb3JlIHJldHVybiBvZiBpbnZlc3QgKFJPSSksIHVuaXQgcHJpY2Ugc2hvdWxkIGJlIGEgaW1wb3J0YW50IG1ldHJpY3MuIFRodXMsIHRoZXkgc2hvdWxkIHVzZSBtb3JlIG1ldGhvZCBvZiBzb2xkIGFmdGVyIGF1Y3Rpb24gYW5kIHZlbmRvciBiaWQuIFRvd25ob3VzZSB3aWxsIGFsc28gaGVscCB0aGVtIHRvIGFjaGlldmUgaXQuDQpgYGB7cn0NCiNGaWd1cmUgMQ0KZ2dwbG90KHByb3BlcnR5LCBhZXMoeCA9IEJ1aWxkaW5nQXJlYSwgeSA9IFByaWNlKSkgKyANCiAgZ2VvbV9wb2ludChzaXplID0gMC41LCBjb2xvcj0iZGFya2dyYXkiKSArDQogIGdlb21fc21vb3RoKG1ldGhvZCA9ICJsbSIpICsNCiAgZmFjZXRfd3JhcCggfiBNZXRob2QpICsNCiAgdGhlbWVfYncoKQ0KDQpnZ3Bsb3QocHJvcGVydHksIGFlcyh4ID0gQnVpbGRpbmdBcmVhLCB5ID0gUHJpY2UpKSArIA0KICBnZW9tX3BvaW50KHNpemUgPSAwLjUsIGNvbG9yPSJkYXJrZ3JheSIpICsNCiAgZ2VvbV9zbW9vdGgobWV0aG9kID0gImxtIikgKw0KICBmYWNldF93cmFwKCB+IFR5cGUpICsNCiAgdGhlbWVfYncoKQ0KDQojRmlndXJlIDINCmdncGxvdChwcm9wZXJ0eSwgYWVzKHggPSBEaXN0YW5jZSwgeSA9IFByaWNlKSkgKyANCiAgZ2VvbV9wb2ludChzaXplID0gMC41LCBjb2xvcj0iZGFya2dyYXkiKSArDQogIGdlb21fc21vb3RoKG1ldGhvZCA9ICJsbSIpICsNCiAgdGhlbWVfYncoKQ0KDQojRmlndXJlIDMNCmdhcnNvbihBTk5fbW9kZWwxKSArIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIGhqdXN0ID0gMSkpDQpgYGANCg0K