1 Mở đầu câu chuyện

Cách đây vài ngày, GS. Nguyễn Văn Tuấn đã vận dụng lý thuyết xác suất và kiểm định thống kê để khẳng định có sự bất thường trong phân bố điểm thi ở tỉnh Hà Giang so với dữ liệu toàn quốc.

Trong bài thực hành hôm nay, Nhi sẽ thử áp dụng một cách “điều tra” khác, khách quan và dựa vào dữ liệu quan sát thực tế, với mục tiêu nhận diện những trường hợp và đặc điểm phân bố bất thường về điểm thi tại Hà Giang. Phương pháp thống kê sẽ được sử dụng là “K-mean clustering” (Phân tích cụm).

library(tidyverse)
## Warning: package 'tidyverse' was built under R version 3.5.1
## -- Attaching packages ----------------------------- tidyverse 1.2.1 --
## v ggplot2 3.0.0     v purrr   0.2.5
## v tibble  1.4.2     v dplyr   0.7.6
## v tidyr   0.8.1     v stringr 1.3.1
## v readr   1.1.1     v forcats 0.3.0
## Warning: package 'ggplot2' was built under R version 3.5.1
## Warning: package 'tibble' was built under R version 3.5.1
## Warning: package 'tidyr' was built under R version 3.5.1
## Warning: package 'readr' was built under R version 3.5.1
## Warning: package 'purrr' was built under R version 3.5.1
## Warning: package 'dplyr' was built under R version 3.5.1
## Warning: package 'stringr' was built under R version 3.5.1
## Warning: package 'forcats' was built under R version 3.5.1
## -- Conflicts -------------------------------- tidyverse_conflicts() --
## x dplyr::filter() masks stats::filter()
## x dplyr::lag()    masks stats::lag()

Đầu tiên, Nhi tải bộ dữ liệu điểm thi cho từng thí sinh ở Hà Giang (xin cảm ơn Gs. Tuấn đã công khai dữ liệu này).

hg_data=read.csv("THPT 2018 Ha Giang.csv")%>%as_tibble()
vn_data=read.csv("THPT 2018 Quoc gia.csv")%>%as_tibble()

Câu chuyện bắt đầu bằng những lời bàn tán trên mạng về phân bố điểm thi rất kì dị ở Hà Giang, như chúng ta có thể thấy trong biểu đồ sau:

library(ggridges)
## Warning: package 'ggridges' was built under R version 3.5.1
## 
## Attaching package: 'ggridges'
## The following object is masked from 'package:ggplot2':
## 
##     scale_discrete_manual
library(viridis)
## Warning: package 'viridis' was built under R version 3.5.1
## Loading required package: viridisLite
## Warning: package 'viridisLite' was built under R version 3.5.1
hg_data%>%
  gather(Math:Geography,key="Exam",value="Score")%>%
  ggplot()+
  geom_density_ridges_gradient(aes(x=Score,y=reorder(Exam,Score),fill=..x..),
                               scale=1,
                               show.legend = T)+
  scale_fill_viridis(option="D")+
  labs(y="Exams")+
  theme_bw()+ggtitle("Ha_Giang")
## Picking joint bandwidth of 0.305

Ở các môn: Toán, Vật lý, ngoại ngữ, Hóa học, mật độ điểm 9 cao một cách bất thường, so với phân bố chung của cả nước:

vn_data%>%
  gather(Math:Geography,key="Exam",value="Score")%>%
  ggplot()+
  geom_density_ridges_gradient(aes(x=Score,y=reorder(Exam,Score),fill=..x..),
                               scale=1,
                               show.legend = T)+
  scale_fill_viridis(option="C",direction = -1)+  
  labs(y="Exams")+
  theme_bw()+ggtitle("National")
## Picking joint bandwidth of 0.088

Với dữ liệu tóm tắt cho từng môn thi riêng lẻ như báo chí đã sử dụng, ta chỉ có thể quan sát đặc tính phân bố, đặt nghi vấn và dừng lại ở đó. Dữ liệu chi tiết toàn bộ môn thi, đến từng cá thể mà Gs. Tuấn công bố cho phép chúng ta đi xa hơn, thí dụ với phương pháp Clustering mà Nhi sắp thực hiện.

2 Phân tích cụm

Trước hết, Nhi đặt ra giả định như sau:

Tổ hợp giữa các môn Toán, Vật Lý, Hóa học, Sinh học có ý nghĩa quan trọng, quyết định khả năng vào Đại học của thí sinh, nên một học sinh nếu đã đặt ra kế hoạch nhắm vào một trường Đại học nào đó, chắc chắn họ sẽ phải đầu tư tốt nhất có thể và đồng đều vào CẢ 3 môn: Toán-Lý-Hóa hoặc Toán-Hóa-Sinh.

Các môn Toán, Lý, Hóa, Sinh đều thuộc ngành khoa học tự nhiên, chúng có chung yêu cầu về năng lực trí tuệ, phương pháp học tập và kỹ năng làm bài thi.

Vì 2 lý do này, nếu mọi sự diễn ra thuận theo tự nhiên, có nhiều khả năng điểm của 3/4 môn thi trong tổ hợp Toán/Lý/Hóa/Sinh phải tương quan chặt chẽ với nhau.

Do đó, Nhi sẽ trích xuất dữ liệu của riêng 4 môn thi nói trên để làm Clustering:

sub_hg_data=hg_data%>%select(Math,Physics,Chemistry,Biology)%>%
  na.omit()

head(sub_hg_data)
## # A tibble: 6 x 4
##    Math Physics Chemistry Biology
##   <dbl>   <dbl>     <dbl>   <dbl>
## 1   5.6    6.5       3.75    3.5 
## 2   3.8    2.75      2.75    2.75
## 3   2.4    2         3.5     3.25
## 4   9.4    9         3.25    3   
## 5   2.8    3         3.75    3.5 
## 6   3.6    2.5       4       4.75

Có 630 thí sinh ở Hà Giang đã thi đồng thời 4 môn Toán-Lý-Hóa-Sinh, dữ liệu này khá đủ cho một phân tích cụm:

Sau khi thăm dò số cluster cho phương pháp K-means, Nhi quyết định chọn số lượng cluster = 5 (đây là tham số chủ quan duy nhất trong quy trình)

fviz_nbclust(sub_hg_data, kmeans, method = "wss") +
  theme_bw()+
  geom_vline(xintercept =5, linetype = 2)

Việc phân cụm rất dễ dàng chỉ với 1 dòng code, kết quả được lưu lại trong object km.res

km.res <- kmeans(sub_hg_data, 5, nstart = 25)

Kết quả phân cụm có thể được biểu diễn trực quan như sau:

fviz_cluster(km.res,
             data=sub_hg_data,
             ellipse.type = "t", 
             ggtheme = theme_classic()
)

Để hiểu rõ bản chất của mỗi cluster là gì, ta thử tính Mode của điểm 4 môn thi cho từng cluster:

getmode <- function(v) {
  uniqv <- unique(v)
  uniqv[which.max(tabulate(match(v, uniqv)))]
}

aggregate(sub_hg_data, by=list(cluster=km.res$cluster), getmode)
##   cluster Math Physics Chemistry Biology
## 1       1  9.4    9.50      3.25    2.00
## 2       2  2.6    2.50      2.75    3.25
## 3       3  9.0    9.25      9.00    1.75
## 4       4  6.8    6.00      5.50    4.75
## 5       5  4.8    3.75      4.25    4.75

Ta cũng có thể dùng Trung bình để suy diễn:

aggregate(sub_hg_data, by=list(cluster=km.res$cluster), mean)
##   cluster     Math  Physics Chemistry  Biology
## 1       1 8.973913 9.184783  2.945652 2.554348
## 2       2 2.974194 2.620968  2.707373 3.443548
## 3       3 8.782353 9.022059  9.066176 3.007353
## 4       4 6.540260 5.943182  5.905844 4.951299
## 5       5 4.826733 3.883663  4.134901 4.518564

Kết quả này cho thấy:

  1. Cluster thứ 1 là những trường hợp đáng ngờ nhất vì tổ hợp điểm thi rất kì lạ : môn Toán và Vật lý rất cao, nhưng môn Hóa và Sinh học đều có điểm cực thấp.

  2. Cluster thứ 2 bao gồm những học sinh loại yếu kém, với điểm cả 4 môn thi đều thấp như nhau, những trường hợp này cũng có thể xem là outliers trong dữ liệu, tuy nhiên lại không bị nghi ngờ về sự gian lận.

  3. Cluster thứ 3 có vẻ đại diện cho những thí sinh có sở trường Khối A, với tổ hợp Toán-Lý-Hóa điểm rất cao, nhưng điểm môn Sinh học lại cực kì thấp, đây có thể, nhưng chưa hẳn là điều đáng ngờ, vì có nhiều học sinh không có lập trường xác định, họ thi cả 2 khối A,B một cách cầu may.

  4. Cluster thứ 4 tương ứng với những thí sinh có học lực trung bình, khá với điểm thi cả 2 môn Toán, Lý đều nhau và ở mức khá,môn Hóa và Sinh thì ở mức trung bình. Có thể loại nhóm này khỏi diện nghi ngờ.

  5. Cluster thứ 5 đại diện cho các thí sinh có học lực trung bình, yếu cho cả 4 môn, ta cũng có thể loại nhóm này khỏi sự nghi ngờ.

Ta cùng so sánh phân bố điểm cho từng môn thi giữa 5 Clusters:

library(lvplot)
## Warning: package 'lvplot' was built under R version 3.5.1
sub_hg_data%>%mutate(cluster=factor(km.res$cluster))%>%
  gather(Math:Biology,key="Exam",value="Score")%>%
  ggplot()+
  geom_lv(aes(x=Exam,y=Score,fill=..LV..),col="black",show.legend = F,)+
  facet_wrap(~cluster,ncol=2)+
  coord_flip()+
  scale_fill_brewer(palette="Reds",direction = -1)+
  theme_bw()
## Warning: package 'bindrcpp' was built under R version 3.5.1

3 Nhận diện cá thể bất thường

Để nhận diện những trường hợp bất thường, ta có thể: 1) dựa hoàn toàn vào kết quả K-mean cluster, ở đây là toàn bộ cluster 1 và cluster 3; hoặc 2) qua trung gian một chỉ số thống kê là “khoảng cách”trong không gian dữ liệu, hoặc kết hợp cả 2 tiêu chí.

Thí dụ nếu Nhi dùng 2 chỉ số khoảng cách Euclide và Manhattan, có thể thấy tỉ lệ thí sinh có giá trị khoảng cách cao bất thường trong dữ liệu hiện thời :

sub_hg_data%>%get_dist(method = "euclidean")%>%
  fviz_dist(gradient = list(low = "white", mid = "gold", high = "red"))+
  scale_x_discrete(labels = NULL,breaks=NULL)+
  scale_y_discrete(labels = NULL,breaks=NULL)+
  coord_equal()+
  ggtitle("Euclidian distance")

sub_hg_data%>%get_dist(method = "manhattan")%>%
  fviz_dist(gradient = list(low = "white", mid = "pink", high = "purple"))+
  scale_x_discrete(labels = NULL,breaks=NULL)+
  scale_y_discrete(labels = NULL,breaks=NULL)+
  coord_equal()+
  ggtitle("Manhattan distance")

Những vùng màu đậm nhất tương ứng với những thí sinh có thể xem là outliers (ghi chú: điểm thi của họ có thể rất thấp, hoặc rất cao so với những quan sát xung quanh).

Nếu ấn định một tỉ lệ nhất định các trường hợp có khoảng cách cao nhất, thí dụ 5% ta có thể phân lập những cá thể thuộc diện tình nghi này khỏi quần thể một cách trực quan như thí dụ minh họa sau

centers <- km.res$centers[km.res$cluster, ]

distances <- sqrt(rowSums((sub_hg_data - centers)^2)) 

outliers <- order(distances, decreasing=T)[1:30]

plot_df<-mutate(sub_hg_data,cluster=factor(km.res$cluster))

out_df<-plot_df%>%.[outliers,]
ggplot()+
  geom_point(data=plot_df,aes(x=Math,y=Physics,col=cluster),alpha=0.5)+
  geom_point(data=as.data.frame(km.res$centers),aes(x=Math,y=Physics,col=factor(c(1:5))),shape=15,size=3)+
  geom_point(data=out_df,aes(x=Math,y=Physics),shape=21,size=6,stroke=1,col="red",alpha=0.9)+
  theme_bw()

ggplot()+
  geom_point(data=plot_df,aes(x=Math,y=Chemistry,col=cluster),alpha=0.5)+
  geom_point(data=as.data.frame(km.res$centers),aes(x=Math,y=Chemistry,col=factor(c(1:5))),shape=15,size=3)+
  geom_point(data=out_df,aes(x=Math,y=Chemistry),shape=21,size=6,stroke=1,col="red",alpha=0.9)+
  theme_bw()

ggplot()+
  geom_point(data=plot_df,aes(x=Math,y=Biology,col=cluster),alpha=0.5)+
  geom_point(data=as.data.frame(km.res$centers),aes(x=Math,y=Biology,col=factor(c(1:5))),shape=15,size=3)+
  geom_point(data=out_df,aes(x=Math,y=Biology),shape=21,size=6,stroke=1,col="red",alpha=0.9)+
  theme_bw()

ggplot()+
  geom_point(data=plot_df,aes(x=Chemistry,y=Biology,col=cluster),alpha=0.5)+
  geom_point(data=as.data.frame(km.res$centers),aes(x=Chemistry,y=Biology,col=factor(c(1:5))),shape=15,size=3)+
  geom_point(data=out_df,aes(x=Chemistry,y=Biology),shape=21,size=6,stroke=1,col="red",alpha=0.9)+
  theme_bw()

Ta cũng có thể phân lập toàn bộ clusters 1 và 3 , được xem như “tình nghi cao”:

outliers <- order(distances, decreasing=T)[1:500]

out_df<-plot_df%>%.[outliers,]%>%filter(cluster== "1" | cluster =="3")
ggplot()+
  geom_point(data=plot_df,aes(x=Math,y=Physics,col=cluster),alpha=0.5)+
  geom_point(data=as.data.frame(km.res$centers),aes(x=Math,y=Physics,col=factor(c(1:5))),shape=15,size=3)+
  geom_point(data=out_df,aes(x=Math,y=Physics),shape=23,size=6,stroke=1,col="blue",alpha=0.9)+
  theme_bw()

ggplot()+
  geom_point(data=plot_df,aes(x=Math,y=Chemistry,col=cluster),alpha=0.5)+
  geom_point(data=as.data.frame(km.res$centers),aes(x=Math,y=Chemistry,col=factor(c(1:5))),shape=15,size=3)+
  geom_point(data=out_df,aes(x=Math,y=Chemistry),shape=23,size=6,stroke=1,col="blue",alpha=0.9)+
  theme_bw()

ggplot()+
  geom_point(data=plot_df,aes(x=Math,y=Biology,col=cluster),alpha=0.5)+
  geom_point(data=as.data.frame(km.res$centers),aes(x=Math,y=Biology,col=factor(c(1:5))),shape=15,size=3)+
  geom_point(data=out_df,aes(x=Math,y=Biology),shape=23,size=6,stroke=1,col="blue",alpha=0.9)+
  theme_bw()

ggplot()+
  geom_point(data=plot_df,aes(x=Chemistry,y=Biology,col=cluster),alpha=0.5)+
  geom_point(data=as.data.frame(km.res$centers),aes(x=Chemistry,y=Biology,col=factor(c(1:5))),shape=15,size=3)+
  geom_point(data=out_df,aes(x=Chemistry,y=Biology),shape=23,size=6,stroke=1,col="blue",alpha=0.9)+
  theme_bw()

4 Kết luận

Qua bài thực hành này, các bạn đã làm quen với phương pháp nhận diện giá trị bất thường trong dữ liệu là K-means clustering analysis và khoảng cách Euclide.

Điểm thú vị của phương pháp K-means Cluster, đó là tính khách quan, không phụ thuộc vào bất cứ giả thuyết chủ quan nào của người điều tra, khác với cách làm kiểm định giả thuyết vô hiệu hoặc suy diễn dựa vào biểu đồ. Việc suy diễn chỉ được làm sau khi có kết quả phân cụm, và kết quả này dựa vào dữ liệu.

P.S: Chú ý một điều: Cả 2 phương pháp không thể khẳng định điều gì về sự can thiệp hay gian lận, nhưng có thể nhận diện những đối tượng tình nghi.

LS0tDQp0aXRsZTogIlBow6F0IGhp4buHbiBi4bqldCB0aMaw4budbmcgYuG6sW5nIENsdXN0ZXIiDQphdXRob3I6ICJMw6ogTmfhu41jIEto4bqjIE5oaSINCmRhdGU6ICIxNyBUaMOhbmcgNyBuxINtIDIwMTgiDQpvdXRwdXQ6DQogIGh0bWxfZG9jdW1lbnQ6IA0KICAgIGNvZGVfZG93bmxvYWQ6IHRydWUNCiAgICBjb2RlX2ZvbGRpbmc6IGhpZGUNCiAgICBudW1iZXJfc2VjdGlvbnM6IHllcw0KICAgIHRoZW1lOiAiZGVmYXVsdCINCiAgICB0b2M6IFRSVUUNCiAgICB0b2NfZmxvYXQ6IFRSVUUNCi0tLQ0KDQoNCiFbXShPdXRsaWVyZGV0ZWN0aW9uLnBuZykNCg0KDQojIE3hu58gxJHhuqd1IGPDonUgY2h1eeG7h24NCg0KQ8OhY2ggxJHDonkgdsOgaSBuZ8OgeSwgR1MuIE5ndXnhu4VuIFbEg24gVHXhuqVuIMSRw6MgduG6rW4gZOG7pW5nIGzDvSB0aHV54bq/dCB4w6FjIHN14bqldCB2w6Aga2nhu4NtIMSR4buLbmggdGjhu5FuZyBrw6ogxJHhu4Mga2jhurNuZyDEkeG7i25oIGPDsyBz4buxIGLhuqV0IHRoxrDhu51uZyB0cm9uZyBwaMOibiBi4buRIMSRaeG7g20gdGhpIOG7nyB04buJbmggSMOgIEdpYW5nIHNvIHbhu5tpIGThu68gbGnhu4d1IHRvw6BuIHF14buRYy4NCg0KVHJvbmcgYsOgaSB0aOG7sWMgaMOgbmggaMO0bSBuYXksIE5oaSBz4bq9IHRo4butIMOhcCBk4bulbmcgbeG7mXQgY8OhY2ggIsSRaeG7gXUgdHJhIiBraMOhYywga2jDoWNoIHF1YW4gdsOgIGThu7FhIHbDoG8gZOG7ryBsaeG7h3UgcXVhbiBzw6F0IHRo4buxYyB04bq/LCB24bubaSBt4bulYyB0acOqdSBuaOG6rW4gZGnhu4duIG5o4buvbmcgdHLGsOG7nW5nIGjhu6NwIHbDoCDEkeG6t2MgxJFp4buDbSBwaMOibiBi4buRIGLhuqV0IHRoxrDhu51uZyB24buBIMSRaeG7g20gdGhpIHThuqFpIEjDoCBHaWFuZy4gUGjGsMahbmcgcGjDoXAgdGjhu5FuZyBrw6ogc+G6vSDEkcaw4bujYyBz4butIGThu6VuZyBsw6AgIkstbWVhbiBjbHVzdGVyaW5nIiAoUGjDom4gdMOtY2ggY+G7pW0pLg0KDQpgYGB7cn0NCmxpYnJhcnkodGlkeXZlcnNlKQ0KYGBgDQoNCsSQ4bqndSB0acOqbiwgTmhpIHThuqNpIGLhu5kgZOG7ryBsaeG7h3UgxJFp4buDbSB0aGkgY2hvIHThu6tuZyB0aMOtIHNpbmgg4bufIEjDoCBHaWFuZyAoeGluIGPhuqNtIMahbiBHcy4gVHXhuqVuIMSRw6MgY8O0bmcga2hhaSBk4buvIGxp4buHdSBuw6B5KS4NCg0KYGBge3J9DQpoZ19kYXRhPXJlYWQuY3N2KCJUSFBUIDIwMTggSGEgR2lhbmcuY3N2IiklPiVhc190aWJibGUoKQ0Kdm5fZGF0YT1yZWFkLmNzdigiVEhQVCAyMDE4IFF1b2MgZ2lhLmNzdiIpJT4lYXNfdGliYmxlKCkNCmBgYA0KDQpDw6J1IGNodXnhu4duIGLhuq90IMSR4bqndSBi4bqxbmcgbmjhu69uZyBs4budaSBiw6BuIHTDoW4gdHLDqm4gbeG6oW5nIHbhu4EgcGjDom4gYuG7kSDEkWnhu4NtIHRoaSBy4bqldCBrw6wgZOG7iyDhu58gSMOgIEdpYW5nLCBuaMawIGNow7puZyB0YSBjw7MgdGjhu4MgdGjhuqV5IHRyb25nIGJp4buDdSDEkeG7kyBzYXU6DQoNCmBgYHtyfQ0KbGlicmFyeShnZ3JpZGdlcykNCmxpYnJhcnkodmlyaWRpcykNCg0KaGdfZGF0YSU+JQ0KICBnYXRoZXIoTWF0aDpHZW9ncmFwaHksa2V5PSJFeGFtIix2YWx1ZT0iU2NvcmUiKSU+JQ0KICBnZ3Bsb3QoKSsNCiAgZ2VvbV9kZW5zaXR5X3JpZGdlc19ncmFkaWVudChhZXMoeD1TY29yZSx5PXJlb3JkZXIoRXhhbSxTY29yZSksZmlsbD0uLnguLiksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2NhbGU9MSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzaG93LmxlZ2VuZCA9IFQpKw0KICBzY2FsZV9maWxsX3ZpcmlkaXMob3B0aW9uPSJEIikrDQogIGxhYnMoeT0iRXhhbXMiKSsNCiAgdGhlbWVfYncoKStnZ3RpdGxlKCJIYV9HaWFuZyIpDQpgYGANCg0K4bueIGPDoWMgbcO0bjogVG/DoW4sIFbhuq10IGzDvSwgbmdv4bqhaSBuZ+G7rywgSMOzYSBo4buNYywgbeG6rXQgxJHhu5kgxJFp4buDbSA5IGNhbyBt4buZdCBjw6FjaCBi4bqldCB0aMaw4budbmcsIHNvIHbhu5tpIHBow6JuIGLhu5EgY2h1bmcgY+G7p2EgY+G6oyBuxrDhu5tjOg0KDQpgYGB7cn0NCnZuX2RhdGElPiUNCiAgZ2F0aGVyKE1hdGg6R2VvZ3JhcGh5LGtleT0iRXhhbSIsdmFsdWU9IlNjb3JlIiklPiUNCiAgZ2dwbG90KCkrDQogIGdlb21fZGVuc2l0eV9yaWRnZXNfZ3JhZGllbnQoYWVzKHg9U2NvcmUseT1yZW9yZGVyKEV4YW0sU2NvcmUpLGZpbGw9Li54Li4pLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNjYWxlPTEsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2hvdy5sZWdlbmQgPSBUKSsNCiAgc2NhbGVfZmlsbF92aXJpZGlzKG9wdGlvbj0iQyIsZGlyZWN0aW9uID0gLTEpKyAgDQogIGxhYnMoeT0iRXhhbXMiKSsNCiAgdGhlbWVfYncoKStnZ3RpdGxlKCJOYXRpb25hbCIpDQpgYGANCg0KVuG7m2kgZOG7ryBsaeG7h3UgdMOzbSB04bqvdCBjaG8gdOG7q25nIG3DtG4gdGhpIHJpw6puZyBs4bq7IG5oxrAgYsOhbyBjaMOtIMSRw6Mgc+G7rSBk4bulbmcsIHRhIGNo4buJIGPDsyB0aOG7gyBxdWFuIHPDoXQgxJHhurdjIHTDrW5oIHBow6JuIGLhu5EsIMSR4bq3dCBuZ2hpIHbhuqVuIHbDoCBk4burbmcgbOG6oWkg4bufIMSRw7MuIEThu68gbGnhu4d1IGNoaSB0aeG6v3QgdG/DoG4gYuG7mSBtw7RuIHRoaSwgxJHhur9uIHThu6tuZyBjw6EgdGjhu4MgbcOgIEdzLiBUdeG6pW4gY8O0bmcgYuG7kSBjaG8gcGjDqXAgY2jDum5nIHRhIMSRaSB4YSBoxqFuLCB0aMOtIGThu6UgduG7m2kgcGjGsMahbmcgcGjDoXAgQ2x1c3RlcmluZyBtw6AgTmhpIHPhuq9wIHRo4buxYyBoaeG7h24uDQoNCiMgUGjDom4gdMOtY2ggY+G7pW0gDQoNClRyxrDhu5tjIGjhur90LCBOaGkgxJHhurd0IHJhIGdp4bqjIMSR4buLbmggbmjGsCBzYXU6DQoNClThu5UgaOG7o3AgZ2nhu69hIGPDoWMgbcO0biBUb8OhbiwgVuG6rXQgTMO9LCBIw7NhIGjhu41jLCBTaW5oIGjhu41jIGPDsyDDvSBuZ2jEqWEgcXVhbiB0cuG7jW5nLCBxdXnhur90IMSR4buLbmgga2jhuqMgbsSDbmcgdsOgbyDEkOG6oWkgaOG7jWMgY+G7p2EgdGjDrSBzaW5oLCBuw6puIG3hu5l0IGjhu41jIHNpbmggbuG6v3UgxJHDoyDEkeG6t3QgcmEga+G6vyBob+G6oWNoIG5o4bqvbSB2w6BvIG3hu5l0IHRyxrDhu51uZyDEkOG6oWkgaOG7jWMgbsOgbyDEkcOzLCBjaOG6r2MgY2jhuq9uIGjhu40gc+G6vSBwaOG6o2kgxJHhuqd1IHTGsCB04buRdCBuaOG6pXQgY8OzIHRo4buDIHbDoCDEkeG7k25nIMSR4buBdSB2w6BvIEPhuqIgMyBtw7RuOiBUb8Ohbi1Mw70tSMOzYSBob+G6t2MgVG/DoW4tSMOzYS1TaW5oLiANCg0KQ8OhYyBtw7RuIFRvw6FuLCBMw70sIEjDs2EsIFNpbmggxJHhu4F1IHRodeG7mWMgbmfDoG5oIGtob2EgaOG7jWMgdOG7sSBuaGnDqm4sIGNow7puZyBjw7MgY2h1bmcgecOqdSBj4bqndSB24buBIG7Eg25nIGzhu7FjIHRyw60gdHXhu4csIHBoxrDGoW5nIHBow6FwIGjhu41jIHThuq1wIHbDoCBr4bu5IG7Eg25nIGzDoG0gYsOgaSB0aGkuIA0KDQpWw6wgMiBsw70gZG8gbsOgeSwgbuG6v3UgbeG7jWkgc+G7sSBkaeG7hW4gcmEgdGh14bqtbiB0aGVvIHThu7Egbmhpw6puLCBjw7Mgbmhp4buBdSBraOG6oyBuxINuZyDEkWnhu4NtIGPhu6dhIDMvNCBtw7RuIHRoaSB0cm9uZyB04buVIGjhu6NwIFRvw6FuL0zDvS9Iw7NhL1NpbmggcGjhuqNpIHTGsMahbmcgcXVhbiBjaOG6t3QgY2jhur0gduG7m2kgbmhhdS4NCg0KRG8gxJHDsywgTmhpIHPhur0gdHLDrWNoIHh14bqldCBk4buvIGxp4buHdSBj4bunYSByacOqbmcgNCBtw7RuIHRoaSBuw7NpIHRyw6puIMSR4buDIGzDoG0gQ2x1c3RlcmluZzoNCg0KYGBge3J9DQpzdWJfaGdfZGF0YT1oZ19kYXRhJT4lc2VsZWN0KE1hdGgsUGh5c2ljcyxDaGVtaXN0cnksQmlvbG9neSklPiUNCiAgbmEub21pdCgpDQoNCmhlYWQoc3ViX2hnX2RhdGEpDQpgYGANCg0KQ8OzIDYzMCB0aMOtIHNpbmgg4bufIEjDoCBHaWFuZyDEkcOjIHRoaSDEkeG7k25nIHRo4budaSA0IG3DtG4gVG/DoW4tTMO9LUjDs2EtU2luaCwgZOG7ryBsaeG7h3UgbsOgeSBraMOhIMSR4bunIGNobyBt4buZdCBwaMOibiB0w61jaCBj4bulbToNCg0KU2F1IGtoaSB0aMSDbSBkw7Igc+G7kSBjbHVzdGVyIGNobyBwaMawxqFuZyBwaMOhcCBLLW1lYW5zLCBOaGkgcXV54bq/dCDEkeG7i25oIGNo4buNbiBz4buRIGzGsOG7o25nIGNsdXN0ZXIgPSA1ICjEkcOieSBsw6AgdGhhbSBz4buRIGNo4bunIHF1YW4gZHV5IG5o4bqldCB0cm9uZyBxdXkgdHLDrG5oKQ0KDQpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCBpbmNsdWRlPUZBTFNFfQ0KbGlicmFyeShjbHVzdGVyKQ0KbGlicmFyeShmYWN0b2V4dHJhKQ0KYGBgDQoNCmBgYHtyfQ0KZnZpel9uYmNsdXN0KHN1Yl9oZ19kYXRhLCBrbWVhbnMsIG1ldGhvZCA9ICJ3c3MiKSArDQogIHRoZW1lX2J3KCkrDQogIGdlb21fdmxpbmUoeGludGVyY2VwdCA9NSwgbGluZXR5cGUgPSAyKQ0KYGBgDQoNClZp4buHYyBwaMOibiBj4bulbSBy4bqldCBk4buFIGTDoG5nIGNo4buJIHbhu5tpIDEgZMOybmcgY29kZSwga+G6v3QgcXXhuqMgxJHGsOG7o2MgbMawdSBs4bqhaSB0cm9uZyBvYmplY3Qga20ucmVzDQoNCmBgYHtyfQ0Ka20ucmVzIDwtIGttZWFucyhzdWJfaGdfZGF0YSwgNSwgbnN0YXJ0ID0gMjUpDQpgYGANCg0KS+G6v3QgcXXhuqMgcGjDom4gY+G7pW0gY8OzIHRo4buDIMSRxrDhu6NjIGJp4buDdSBkaeG7hW4gdHLhu7FjIHF1YW4gbmjGsCBzYXU6DQoNCmBgYHtyfQ0KZnZpel9jbHVzdGVyKGttLnJlcywNCiAgICAgICAgICAgICBkYXRhPXN1Yl9oZ19kYXRhLA0KICAgICAgICAgICAgIGVsbGlwc2UudHlwZSA9ICJ0IiwgDQogICAgICAgICAgICAgZ2d0aGVtZSA9IHRoZW1lX2NsYXNzaWMoKQ0KKQ0KYGBgDQoNCsSQ4buDIGhp4buDdSByw7UgYuG6o24gY2jhuqV0IGPhu6dhIG3hu5dpIGNsdXN0ZXIgbMOgIGfDrCwgdGEgdGjhu60gdMOtbmggTW9kZSBj4bunYSDEkWnhu4NtIDQgbcO0biB0aGkgY2hvIHThu6tuZyBjbHVzdGVyOg0KDQpgYGB7cn0NCmdldG1vZGUgPC0gZnVuY3Rpb24odikgew0KICB1bmlxdiA8LSB1bmlxdWUodikNCiAgdW5pcXZbd2hpY2gubWF4KHRhYnVsYXRlKG1hdGNoKHYsIHVuaXF2KSkpXQ0KfQ0KDQphZ2dyZWdhdGUoc3ViX2hnX2RhdGEsIGJ5PWxpc3QoY2x1c3Rlcj1rbS5yZXMkY2x1c3RlciksIGdldG1vZGUpDQoNCmBgYA0KDQpUYSBjxaluZyBjw7MgdGjhu4MgZMO5bmcgVHJ1bmcgYsOsbmggxJHhu4Mgc3V5IGRp4buFbjoNCg0KYGBge3J9DQphZ2dyZWdhdGUoc3ViX2hnX2RhdGEsIGJ5PWxpc3QoY2x1c3Rlcj1rbS5yZXMkY2x1c3RlciksIG1lYW4pDQoNCmBgYA0KDQpL4bq/dCBxdeG6oyBuw6B5IGNobyB0aOG6pXk6DQoNCjEpIENsdXN0ZXIgdGjhu6kgMSBsw6Agbmjhu69uZyB0csaw4budbmcgaOG7o3AgxJHDoW5nIG5n4budIG5o4bqldCB2w6wgdOG7lSBo4bujcCDEkWnhu4NtIHRoaSBy4bqldCBrw6wgbOG6oSA6IG3DtG4gVG/DoW4gdsOgIFbhuq10IGzDvSBy4bqldCBjYW8sIG5oxrBuZyBtw7RuIEjDs2EgdsOgIFNpbmggaOG7jWMgxJHhu4F1IGPDsyDEkWnhu4NtIGPhu7FjIHRo4bqlcC4gDQoNCjIpIENsdXN0ZXIgdGjhu6kgMiBiYW8gZ+G7k20gbmjhu69uZyBo4buNYyBzaW5oIGxv4bqhaSB54bq/dSBrw6ltLCB24bubaSDEkWnhu4NtIGPhuqMgNCBtw7RuIHRoaSDEkeG7gXUgdGjhuqVwIG5oxrAgbmhhdSwgbmjhu69uZyB0csaw4budbmcgaOG7o3AgbsOgeSBjxaluZyBjw7MgdGjhu4MgeGVtIGzDoCBvdXRsaWVycyB0cm9uZyBk4buvIGxp4buHdSwgdHV5IG5oacOqbiBs4bqhaSBraMO0bmcgYuG7iyBuZ2hpIG5n4budIHbhu4Egc+G7sSBnaWFuIGzhuq1uLg0KDQozKSBDbHVzdGVyIHRo4bupIDMgY8OzIHbhursgxJHhuqFpIGRp4buHbiBjaG8gbmjhu69uZyB0aMOtIHNpbmggY8OzIHPhu58gdHLGsOG7nW5nIEto4buRaSBBLCB24bubaSB04buVIGjhu6NwIFRvw6FuLUzDvS1Iw7NhIMSRaeG7g20gcuG6pXQgY2FvLCBuaMawbmcgxJFp4buDbSBtw7RuIFNpbmggaOG7jWMgbOG6oWkgY+G7sWMga8OsIHRo4bqlcCwgxJHDonkgY8OzIHRo4buDLCBuaMawbmcgY2jGsGEgaOG6s24gbMOgIMSRaeG7gXUgxJHDoW5nIG5n4budLCB2w6wgY8OzIG5oaeG7gXUgaOG7jWMgc2luaCBraMO0bmcgY8OzIGzhuq1wIHRyxrDhu51uZyB4w6FjIMSR4buLbmgsIGjhu40gdGhpIGPhuqMgMiBraOG7kWkgQSxCIG3hu5l0IGPDoWNoIGPhuqd1IG1heS4NCg0KNCkgQ2x1c3RlciB0aOG7qSA0IHTGsMahbmcg4bupbmcgduG7m2kgbmjhu69uZyB0aMOtIHNpbmggY8OzIGjhu41jIGzhu7FjIHRydW5nIGLDrG5oLCBraMOhIHbhu5tpIMSRaeG7g20gdGhpIGPhuqMgMiBtw7RuIFRvw6FuLCBMw70gxJHhu4F1IG5oYXUgdsOgIOG7nyBt4bupYyBraMOhLG3DtG4gSMOzYSB2w6AgU2luaCB0aMOsIOG7nyBt4bupYyB0cnVuZyBiw6xuaC4gQ8OzIHRo4buDIGxv4bqhaSBuaMOzbSBuw6B5IGto4buPaSBkaeG7h24gbmdoaSBuZ+G7nS4NCg0KNSkgQ2x1c3RlciB0aOG7qSA1IMSR4bqhaSBkaeG7h24gY2hvIGPDoWMgdGjDrSBzaW5oIGPDsyBo4buNYyBs4buxYyB0cnVuZyBiw6xuaCwgeeG6v3UgY2hvIGPhuqMgNCBtw7RuLCB0YSBjxaluZyBjw7MgdGjhu4MgbG/huqFpIG5ow7NtIG7DoHkga2jhu49pIHPhu7EgbmdoaSBuZ+G7nS4NCg0KVGEgY8O5bmcgc28gc8OhbmggcGjDom4gYuG7kSDEkWnhu4NtIGNobyB04burbmcgbcO0biB0aGkgZ2nhu69hIDUgQ2x1c3RlcnM6DQoNCmBgYHtyfQ0KbGlicmFyeShsdnBsb3QpDQoNCnN1Yl9oZ19kYXRhJT4lbXV0YXRlKGNsdXN0ZXI9ZmFjdG9yKGttLnJlcyRjbHVzdGVyKSklPiUNCiAgZ2F0aGVyKE1hdGg6QmlvbG9neSxrZXk9IkV4YW0iLHZhbHVlPSJTY29yZSIpJT4lDQogIGdncGxvdCgpKw0KICBnZW9tX2x2KGFlcyh4PUV4YW0seT1TY29yZSxmaWxsPS4uTFYuLiksY29sPSJibGFjayIsc2hvdy5sZWdlbmQgPSBGLCkrDQogIGZhY2V0X3dyYXAofmNsdXN0ZXIsbmNvbD0yKSsNCiAgY29vcmRfZmxpcCgpKw0KICBzY2FsZV9maWxsX2JyZXdlcihwYWxldHRlPSJSZWRzIixkaXJlY3Rpb24gPSAtMSkrDQogIHRoZW1lX2J3KCkNCmBgYA0KDQojIE5o4bqtbiBkaeG7h24gY8OhIHRo4buDIGLhuqV0IHRoxrDhu51uZyANCg0KxJDhu4Mgbmjhuq1uIGRp4buHbiBuaOG7r25nIHRyxrDhu51uZyBo4bujcCBi4bqldCB0aMaw4budbmcsIHRhIGPDsyB0aOG7gzogMSkgZOG7sWEgaG/DoG4gdG/DoG4gdsOgbyBr4bq/dCBxdeG6oyBLLW1lYW4gY2x1c3Rlciwg4bufIMSRw6J5IGzDoCB0b8OgbiBi4buZIGNsdXN0ZXIgMSB2w6AgY2x1c3RlciAzOyBob+G6t2MgMikgcXVhIHRydW5nIGdpYW4gbeG7mXQgY2jhu4kgc+G7kSB0aOG7kW5nIGvDqiBsw6AgImtob+G6o25nIGPDoWNoInRyb25nIGtow7RuZyBnaWFuIGThu68gbGnhu4d1LCBob+G6t2Mga+G6v3QgaOG7o3AgY+G6oyAyIHRpw6p1IGNow60uDQoNClRow60gZOG7pSBu4bq/dSBOaGkgZMO5bmcgMiBjaOG7iSBz4buRIGtob+G6o25nIGPDoWNoIEV1Y2xpZGUgdsOgIE1hbmhhdHRhbiwgY8OzIHRo4buDIHRo4bqleSB04buJIGzhu4cgdGjDrSBzaW5oIGPDsyBnacOhIHRy4buLIGtob+G6o25nIGPDoWNoICBjYW8gYuG6pXQgdGjGsOG7nW5nIHRyb25nIGThu68gbGnhu4d1IGhp4buHbiB0aOG7nWkgOiANCg0KYGBge3J9DQpzdWJfaGdfZGF0YSU+JWdldF9kaXN0KG1ldGhvZCA9ICJldWNsaWRlYW4iKSU+JQ0KICBmdml6X2Rpc3QoZ3JhZGllbnQgPSBsaXN0KGxvdyA9ICJ3aGl0ZSIsIG1pZCA9ICJnb2xkIiwgaGlnaCA9ICJyZWQiKSkrDQogIHNjYWxlX3hfZGlzY3JldGUobGFiZWxzID0gTlVMTCxicmVha3M9TlVMTCkrDQogIHNjYWxlX3lfZGlzY3JldGUobGFiZWxzID0gTlVMTCxicmVha3M9TlVMTCkrDQogIGNvb3JkX2VxdWFsKCkrDQogIGdndGl0bGUoIkV1Y2xpZGlhbiBkaXN0YW5jZSIpDQoNCnN1Yl9oZ19kYXRhJT4lZ2V0X2Rpc3QobWV0aG9kID0gIm1hbmhhdHRhbiIpJT4lDQogIGZ2aXpfZGlzdChncmFkaWVudCA9IGxpc3QobG93ID0gIndoaXRlIiwgbWlkID0gInBpbmsiLCBoaWdoID0gInB1cnBsZSIpKSsNCiAgc2NhbGVfeF9kaXNjcmV0ZShsYWJlbHMgPSBOVUxMLGJyZWFrcz1OVUxMKSsNCiAgc2NhbGVfeV9kaXNjcmV0ZShsYWJlbHMgPSBOVUxMLGJyZWFrcz1OVUxMKSsNCiAgY29vcmRfZXF1YWwoKSsNCiAgZ2d0aXRsZSgiTWFuaGF0dGFuIGRpc3RhbmNlIikNCmBgYA0KDQpOaOG7r25nIHbDuW5nIG3DoHUgxJHhuq1tIG5o4bqldCB0xrDGoW5nIOG7qW5nIHbhu5tpIG5o4buvbmcgdGjDrSBzaW5oIGPDsyB0aOG7gyB4ZW0gbMOgIG91dGxpZXJzIChnaGkgY2jDujogxJFp4buDbSB0aGkgY+G7p2EgaOG7jSBjw7MgdGjhu4MgcuG6pXQgdGjhuqVwLCBob+G6t2MgcuG6pXQgY2FvIHNvIHbhu5tpIG5o4buvbmcgcXVhbiBzw6F0IHh1bmcgcXVhbmgpLg0KDQpO4bq/dSDhuqVuIMSR4buLbmggbeG7mXQgdOG7iSBs4buHIG5o4bqldCDEkeG7i25oIGPDoWMgdHLGsOG7nW5nIGjhu6NwIGPDsyBraG/huqNuZyBjw6FjaCBjYW8gbmjhuqV0LCB0aMOtIGThu6UgNSUgdGEgY8OzIHRo4buDIHBow6JuIGzhuq1wIG5o4buvbmcgY8OhIHRo4buDIHRodeG7mWMgZGnhu4duIHTDrG5oIG5naGkgbsOgeSBraOG7j2kgcXXhuqduIHRo4buDIG3hu5l0IGPDoWNoIHRy4buxYyBxdWFuIG5oxrAgdGjDrSBk4bulIG1pbmggaOG7jWEgc2F1DQoNCmBgYHtyfQ0KY2VudGVycyA8LSBrbS5yZXMkY2VudGVyc1trbS5yZXMkY2x1c3RlciwgXQ0KDQpkaXN0YW5jZXMgPC0gc3FydChyb3dTdW1zKChzdWJfaGdfZGF0YSAtIGNlbnRlcnMpXjIpKSANCg0Kb3V0bGllcnMgPC0gb3JkZXIoZGlzdGFuY2VzLCBkZWNyZWFzaW5nPVQpWzE6MzBdDQoNCnBsb3RfZGY8LW11dGF0ZShzdWJfaGdfZGF0YSxjbHVzdGVyPWZhY3RvcihrbS5yZXMkY2x1c3RlcikpDQoNCm91dF9kZjwtcGxvdF9kZiU+JS5bb3V0bGllcnMsXQ0KYGBgDQoNCmBgYHtyfQ0KZ2dwbG90KCkrDQogIGdlb21fcG9pbnQoZGF0YT1wbG90X2RmLGFlcyh4PU1hdGgseT1QaHlzaWNzLGNvbD1jbHVzdGVyKSxhbHBoYT0wLjUpKw0KICBnZW9tX3BvaW50KGRhdGE9YXMuZGF0YS5mcmFtZShrbS5yZXMkY2VudGVycyksYWVzKHg9TWF0aCx5PVBoeXNpY3MsY29sPWZhY3RvcihjKDE6NSkpKSxzaGFwZT0xNSxzaXplPTMpKw0KICBnZW9tX3BvaW50KGRhdGE9b3V0X2RmLGFlcyh4PU1hdGgseT1QaHlzaWNzKSxzaGFwZT0yMSxzaXplPTYsc3Ryb2tlPTEsY29sPSJyZWQiLGFscGhhPTAuOSkrDQogIHRoZW1lX2J3KCkNCg0KDQpnZ3Bsb3QoKSsNCiAgZ2VvbV9wb2ludChkYXRhPXBsb3RfZGYsYWVzKHg9TWF0aCx5PUNoZW1pc3RyeSxjb2w9Y2x1c3RlciksYWxwaGE9MC41KSsNCiAgZ2VvbV9wb2ludChkYXRhPWFzLmRhdGEuZnJhbWUoa20ucmVzJGNlbnRlcnMpLGFlcyh4PU1hdGgseT1DaGVtaXN0cnksY29sPWZhY3RvcihjKDE6NSkpKSxzaGFwZT0xNSxzaXplPTMpKw0KICBnZW9tX3BvaW50KGRhdGE9b3V0X2RmLGFlcyh4PU1hdGgseT1DaGVtaXN0cnkpLHNoYXBlPTIxLHNpemU9NixzdHJva2U9MSxjb2w9InJlZCIsYWxwaGE9MC45KSsNCiAgdGhlbWVfYncoKQ0KDQpnZ3Bsb3QoKSsNCiAgZ2VvbV9wb2ludChkYXRhPXBsb3RfZGYsYWVzKHg9TWF0aCx5PUJpb2xvZ3ksY29sPWNsdXN0ZXIpLGFscGhhPTAuNSkrDQogIGdlb21fcG9pbnQoZGF0YT1hcy5kYXRhLmZyYW1lKGttLnJlcyRjZW50ZXJzKSxhZXMoeD1NYXRoLHk9QmlvbG9neSxjb2w9ZmFjdG9yKGMoMTo1KSkpLHNoYXBlPTE1LHNpemU9MykrDQogIGdlb21fcG9pbnQoZGF0YT1vdXRfZGYsYWVzKHg9TWF0aCx5PUJpb2xvZ3kpLHNoYXBlPTIxLHNpemU9NixzdHJva2U9MSxjb2w9InJlZCIsYWxwaGE9MC45KSsNCiAgdGhlbWVfYncoKQ0KDQoNCmdncGxvdCgpKw0KICBnZW9tX3BvaW50KGRhdGE9cGxvdF9kZixhZXMoeD1DaGVtaXN0cnkseT1CaW9sb2d5LGNvbD1jbHVzdGVyKSxhbHBoYT0wLjUpKw0KICBnZW9tX3BvaW50KGRhdGE9YXMuZGF0YS5mcmFtZShrbS5yZXMkY2VudGVycyksYWVzKHg9Q2hlbWlzdHJ5LHk9QmlvbG9neSxjb2w9ZmFjdG9yKGMoMTo1KSkpLHNoYXBlPTE1LHNpemU9MykrDQogIGdlb21fcG9pbnQoZGF0YT1vdXRfZGYsYWVzKHg9Q2hlbWlzdHJ5LHk9QmlvbG9neSksc2hhcGU9MjEsc2l6ZT02LHN0cm9rZT0xLGNvbD0icmVkIixhbHBoYT0wLjkpKw0KICB0aGVtZV9idygpDQpgYGANCg0KVGEgY8WpbmcgY8OzIHRo4buDIHBow6JuIGzhuq1wIHRvw6BuIGLhu5kgY2x1c3RlcnMgMSB2w6AgMyAsIMSRxrDhu6NjIHhlbSBuaMawICJ0w6xuaCBuZ2hpIGNhbyI6DQoNCmBgYHtyfQ0Kb3V0bGllcnMgPC0gb3JkZXIoZGlzdGFuY2VzLCBkZWNyZWFzaW5nPVQpWzE6NTAwXQ0KDQpvdXRfZGY8LXBsb3RfZGYlPiUuW291dGxpZXJzLF0lPiVmaWx0ZXIoY2x1c3Rlcj09ICIxIiB8IGNsdXN0ZXIgPT0iMyIpDQpgYGANCg0KYGBge3J9DQpnZ3Bsb3QoKSsNCiAgZ2VvbV9wb2ludChkYXRhPXBsb3RfZGYsYWVzKHg9TWF0aCx5PVBoeXNpY3MsY29sPWNsdXN0ZXIpLGFscGhhPTAuNSkrDQogIGdlb21fcG9pbnQoZGF0YT1hcy5kYXRhLmZyYW1lKGttLnJlcyRjZW50ZXJzKSxhZXMoeD1NYXRoLHk9UGh5c2ljcyxjb2w9ZmFjdG9yKGMoMTo1KSkpLHNoYXBlPTE1LHNpemU9MykrDQogIGdlb21fcG9pbnQoZGF0YT1vdXRfZGYsYWVzKHg9TWF0aCx5PVBoeXNpY3MpLHNoYXBlPTIzLHNpemU9NixzdHJva2U9MSxjb2w9ImJsdWUiLGFscGhhPTAuOSkrDQogIHRoZW1lX2J3KCkNCg0KDQpnZ3Bsb3QoKSsNCiAgZ2VvbV9wb2ludChkYXRhPXBsb3RfZGYsYWVzKHg9TWF0aCx5PUNoZW1pc3RyeSxjb2w9Y2x1c3RlciksYWxwaGE9MC41KSsNCiAgZ2VvbV9wb2ludChkYXRhPWFzLmRhdGEuZnJhbWUoa20ucmVzJGNlbnRlcnMpLGFlcyh4PU1hdGgseT1DaGVtaXN0cnksY29sPWZhY3RvcihjKDE6NSkpKSxzaGFwZT0xNSxzaXplPTMpKw0KICBnZW9tX3BvaW50KGRhdGE9b3V0X2RmLGFlcyh4PU1hdGgseT1DaGVtaXN0cnkpLHNoYXBlPTIzLHNpemU9NixzdHJva2U9MSxjb2w9ImJsdWUiLGFscGhhPTAuOSkrDQogIHRoZW1lX2J3KCkNCg0KZ2dwbG90KCkrDQogIGdlb21fcG9pbnQoZGF0YT1wbG90X2RmLGFlcyh4PU1hdGgseT1CaW9sb2d5LGNvbD1jbHVzdGVyKSxhbHBoYT0wLjUpKw0KICBnZW9tX3BvaW50KGRhdGE9YXMuZGF0YS5mcmFtZShrbS5yZXMkY2VudGVycyksYWVzKHg9TWF0aCx5PUJpb2xvZ3ksY29sPWZhY3RvcihjKDE6NSkpKSxzaGFwZT0xNSxzaXplPTMpKw0KICBnZW9tX3BvaW50KGRhdGE9b3V0X2RmLGFlcyh4PU1hdGgseT1CaW9sb2d5KSxzaGFwZT0yMyxzaXplPTYsc3Ryb2tlPTEsY29sPSJibHVlIixhbHBoYT0wLjkpKw0KICB0aGVtZV9idygpDQoNCg0KZ2dwbG90KCkrDQogIGdlb21fcG9pbnQoZGF0YT1wbG90X2RmLGFlcyh4PUNoZW1pc3RyeSx5PUJpb2xvZ3ksY29sPWNsdXN0ZXIpLGFscGhhPTAuNSkrDQogIGdlb21fcG9pbnQoZGF0YT1hcy5kYXRhLmZyYW1lKGttLnJlcyRjZW50ZXJzKSxhZXMoeD1DaGVtaXN0cnkseT1CaW9sb2d5LGNvbD1mYWN0b3IoYygxOjUpKSksc2hhcGU9MTUsc2l6ZT0zKSsNCiAgZ2VvbV9wb2ludChkYXRhPW91dF9kZixhZXMoeD1DaGVtaXN0cnkseT1CaW9sb2d5KSxzaGFwZT0yMyxzaXplPTYsc3Ryb2tlPTEsY29sPSJibHVlIixhbHBoYT0wLjkpKw0KICB0aGVtZV9idygpDQpgYGANCg0KIyBL4bq/dCBsdeG6rW4NCg0KUXVhIGLDoGkgdGjhu7FjIGjDoG5oIG7DoHksIGPDoWMgYuG6oW4gxJHDoyBsw6BtIHF1ZW4gduG7m2kgcGjGsMahbmcgcGjDoXAgbmjhuq1uIGRp4buHbiBnacOhIHRy4buLIGLhuqV0IHRoxrDhu51uZyB0cm9uZyBk4buvIGxp4buHdSBsw6AgSy1tZWFucyBjbHVzdGVyaW5nIGFuYWx5c2lzIHbDoCAga2hv4bqjbmcgY8OhY2ggRXVjbGlkZS4gDQoNCsSQaeG7g20gdGjDuiB24buLIGPhu6dhIHBoxrDGoW5nIHBow6FwIEstbWVhbnMgQ2x1c3RlciwgxJHDsyBsw6AgdMOtbmgga2jDoWNoIHF1YW4sIGtow7RuZyBwaOG7pSB0aHXhu5ljIHbDoG8gYuG6pXQgY+G7qSBnaeG6oyB0aHV54bq/dCBjaOG7pyBxdWFuIG7DoG8gY+G7p2EgbmfGsOG7nWkgxJFp4buBdSB0cmEsIGtow6FjIHbhu5tpIGPDoWNoIGzDoG0ga2nhu4NtIMSR4buLbmggZ2nhuqMgdGh1eeG6v3QgdsO0IGhp4buHdSBob+G6t2Mgc3V5IGRp4buFbiBk4buxYSB2w6BvIGJp4buDdSDEkeG7ky4gVmnhu4djIHN1eSBkaeG7hW4gY2jhu4kgxJHGsOG7o2MgbMOgbSBzYXUga2hpIGPDsyBr4bq/dCBxdeG6oyBwaMOibiBj4bulbSwgdsOgIGvhur90IHF14bqjIG7DoHkgZOG7sWEgdsOgbyBk4buvIGxp4buHdS4NCg0KUC5TOiBDaMO6IMO9IG3hu5l0IMSRaeG7gXU6IEPhuqMgMiBwaMawxqFuZyBwaMOhcCBraMO0bmcgdGjhu4Mga2jhurNuZyDEkeG7i25oIMSRaeG7gXUgZ8OsIHbhu4Egc+G7sSBjYW4gdGhp4buHcCBoYXkgZ2lhbiBs4bqtbiwgbmjGsG5nIGPDsyB0aOG7gyBuaOG6rW4gZGnhu4duIG5o4buvbmcgxJHhu5FpIHTGsOG7o25nIHTDrG5oIG5naGku