Khởi hành: Phân tích tương quan cổ điển
Chúng ta bắt đầu câu chuyện bằng một phân tích tương quan theo phương pháp cổ điển, sử dụng hệ số tương quan tuyến tính r (Pearson). Đây là một quy trình phổ biến trong bước thăm dò dữ liệu và những nghiên cứu mô tả.
Thí dụ minh họa trích từ một nghiên cứu cắt ngang, chủ đề lâm sàng nội khoa hô hấp, với 14 thông số chức năng hô hấp được khảo sát ở 2 nhóm bệnh nhân: Có và không có bệnh Xơ phổi mô kẽ.
library(tidyverse)
df = read.csv("RAW_DATA_ORG.csv",dec=",",sep=';')
neg_df = df%>%select(FEV1,Tiffneau,FVC,TLC,AV,RV,TLCO,KCO,TLNO,KNO,DLRatio,DmCO,DmNO,Vcap,ILD)%>%
filter(.$ILD=="Negative")%>%.[,-15]
pos_df = df%>%select(FEV1,Tiffneau,FVC,TLC,AV,RV,TLCO,KCO,TLNO,KNO,DLRatio,DmCO,DmNO,Vcap,ILD)%>%
filter(.$ILD=="Positive")%>%.[,-15]
neg_df=sapply(neg_df,as.numeric)
pos_df=sapply(pos_df,as.numeric)
14 thông số gồm: FEV1, tỉ số Tiffneau = FEV1/FVC khảo sát chức năng thông khí, FVC, TLC, RV, AV khảo sát Dung tích phổi, TLCO, KCO, TLNO, KNO, DLratio khảo sát khả năng trao đổi khí, DmCO và DmNO khảo sát dẫn suất của màng phế nang mao mạch, và Vcap khảo sát thể tích mao mạch phổi.
Nhóm Bệnh (Positive) có 147 bệnh nhân,
head(pos_df)%>%knitr::kable()
154 |
312 |
160 |
106 |
183 |
8 |
264 |
306 |
35 |
336 |
275 |
19 |
239 |
299 |
125 |
155 |
155 |
133 |
187 |
48 |
123 |
83 |
305 |
135 |
325 |
2 |
222 |
119 |
60 |
175 |
68 |
56 |
83 |
46 |
193 |
330 |
304 |
333 |
210 |
315 |
160 |
216 |
9 |
119 |
6 |
112 |
43 |
167 |
31 |
138 |
119 |
78 |
92 |
82 |
302 |
146 |
50 |
296 |
42 |
22 |
41 |
16 |
331 |
49 |
162 |
195 |
369 |
247 |
92 |
39 |
27 |
182 |
25 |
42 |
89 |
66 |
57 |
105 |
152 |
72 |
178 |
128 |
348 |
124 |
Nhóm Negative gồm 229 người không có bệnh.
head(neg_df)%>%knitr::kable()
105 |
150 |
126 |
177 |
213 |
140 |
274 |
251 |
44 |
346 |
343 |
36 |
255 |
297 |
151 |
269 |
164 |
187 |
173 |
114 |
184 |
182 |
18 |
313 |
342 |
24 |
244 |
181 |
108 |
331 |
97 |
144 |
164 |
127 |
79 |
63 |
222 |
79 |
305 |
204 |
49 |
176 |
45 |
44 |
71 |
123 |
170 |
126 |
25 |
30 |
204 |
47 |
351 |
282 |
127 |
72 |
68 |
276 |
63 |
84 |
123 |
89 |
90 |
106 |
299 |
283 |
358 |
11 |
231 |
97 |
16 |
30 |
30 |
179 |
116 |
181 |
71 |
94 |
233 |
185 |
335 |
280 |
125 |
118 |
Mục tiêu giả định là ta muốn khảo sát mối tương quan giữa 14 thông số chức năng này. Đây là một việc rất đơn giản như ta biết, chỉ cần áp dụng hàm cor trên data matrix có (k) biến số, ta sẽ có kết quả là một correlation matrix với kích thước (k x k)
cormat_pos = cor(pos_df, method="pearson")%>%round(.,3)
cormat_neg = cor(neg_df, method="pearson" )%>%round(.,3)
Ma trận tương quan 14 biến trên nhóm bệnh như sau:
cormat_pos %>% knitr::kable()
FEV1 |
1.000 |
0.074 |
0.939 |
0.748 |
0.650 |
0.150 |
-0.034 |
0.188 |
0.266 |
0.281 |
-0.008 |
-0.129 |
0.220 |
-0.015 |
Tiffneau |
0.074 |
1.000 |
-0.252 |
-0.305 |
-0.214 |
-0.209 |
0.106 |
0.098 |
-0.052 |
0.115 |
0.072 |
-0.059 |
-0.022 |
0.028 |
FVC |
0.939 |
-0.252 |
1.000 |
0.815 |
0.694 |
0.194 |
-0.071 |
0.144 |
0.266 |
0.242 |
-0.005 |
-0.106 |
0.214 |
-0.048 |
TLC |
0.748 |
-0.305 |
0.815 |
1.000 |
0.725 |
0.713 |
-0.074 |
0.056 |
0.305 |
0.141 |
-0.012 |
-0.048 |
0.153 |
0.005 |
AV |
0.650 |
-0.214 |
0.694 |
0.725 |
1.000 |
0.384 |
-0.027 |
-0.086 |
0.352 |
0.163 |
0.177 |
-0.048 |
0.149 |
0.034 |
RV |
0.150 |
-0.209 |
0.194 |
0.713 |
0.384 |
1.000 |
-0.060 |
-0.063 |
0.181 |
-0.020 |
-0.033 |
0.044 |
-0.019 |
0.075 |
TLCO |
-0.034 |
0.106 |
-0.071 |
-0.074 |
-0.027 |
-0.060 |
1.000 |
-0.159 |
-0.257 |
-0.012 |
0.207 |
0.070 |
-0.198 |
-0.081 |
KCO |
0.188 |
0.098 |
0.144 |
0.056 |
-0.086 |
-0.063 |
-0.159 |
1.000 |
0.285 |
0.553 |
-0.555 |
-0.111 |
0.175 |
-0.027 |
TLNO |
0.266 |
-0.052 |
0.266 |
0.305 |
0.352 |
0.181 |
-0.257 |
0.285 |
1.000 |
0.196 |
0.000 |
0.022 |
0.156 |
0.063 |
KNO |
0.281 |
0.115 |
0.242 |
0.141 |
0.163 |
-0.020 |
-0.012 |
0.553 |
0.196 |
1.000 |
-0.036 |
-0.038 |
0.105 |
0.009 |
DLRatio |
-0.008 |
0.072 |
-0.005 |
-0.012 |
0.177 |
-0.033 |
0.207 |
-0.555 |
0.000 |
-0.036 |
1.000 |
0.089 |
-0.078 |
-0.064 |
DmCO |
-0.129 |
-0.059 |
-0.106 |
-0.048 |
-0.048 |
0.044 |
0.070 |
-0.111 |
0.022 |
-0.038 |
0.089 |
1.000 |
-0.444 |
0.322 |
DmNO |
0.220 |
-0.022 |
0.214 |
0.153 |
0.149 |
-0.019 |
-0.198 |
0.175 |
0.156 |
0.105 |
-0.078 |
-0.444 |
1.000 |
-0.317 |
Vcap |
-0.015 |
0.028 |
-0.048 |
0.005 |
0.034 |
0.075 |
-0.081 |
-0.027 |
0.063 |
0.009 |
-0.064 |
0.322 |
-0.317 |
1.000 |
Còn đây là kết quả trên nhóm Không có bệnh:
cormat_neg %>% knitr::kable()
FEV1 |
1.000 |
0.265 |
0.927 |
0.555 |
0.659 |
-0.153 |
0.498 |
0.281 |
-0.164 |
0.228 |
0.023 |
-0.157 |
0.136 |
0.065 |
Tiffneau |
0.265 |
1.000 |
-0.059 |
-0.297 |
-0.124 |
-0.384 |
0.063 |
0.196 |
0.045 |
0.272 |
0.045 |
-0.063 |
-0.048 |
-0.014 |
FVC |
0.927 |
-0.059 |
1.000 |
0.685 |
0.716 |
-0.039 |
0.498 |
0.246 |
-0.162 |
0.164 |
-0.005 |
-0.160 |
0.167 |
0.075 |
TLC |
0.555 |
-0.297 |
0.685 |
1.000 |
0.762 |
0.659 |
0.348 |
0.003 |
-0.128 |
-0.054 |
0.136 |
-0.090 |
0.213 |
0.027 |
AV |
0.659 |
-0.124 |
0.716 |
0.762 |
1.000 |
0.345 |
0.405 |
-0.093 |
-0.140 |
-0.035 |
0.310 |
-0.121 |
0.143 |
-0.002 |
RV |
-0.153 |
-0.384 |
-0.039 |
0.659 |
0.345 |
1.000 |
-0.029 |
-0.271 |
0.003 |
-0.291 |
0.160 |
0.037 |
0.121 |
-0.016 |
TLCO |
0.498 |
0.063 |
0.498 |
0.348 |
0.405 |
-0.029 |
1.000 |
0.467 |
-0.161 |
0.538 |
-0.202 |
-0.054 |
0.094 |
-0.051 |
KCO |
0.281 |
0.196 |
0.246 |
0.003 |
-0.093 |
-0.271 |
0.467 |
1.000 |
0.102 |
0.727 |
-0.486 |
-0.022 |
0.046 |
-0.007 |
TLNO |
-0.164 |
0.045 |
-0.162 |
-0.128 |
-0.140 |
0.003 |
-0.161 |
0.102 |
1.000 |
0.069 |
-0.012 |
-0.029 |
0.067 |
-0.082 |
KNO |
0.228 |
0.272 |
0.164 |
-0.054 |
-0.035 |
-0.291 |
0.538 |
0.727 |
0.069 |
1.000 |
-0.064 |
-0.054 |
0.047 |
-0.077 |
DLRatio |
0.023 |
0.045 |
-0.005 |
0.136 |
0.310 |
0.160 |
-0.202 |
-0.486 |
-0.012 |
-0.064 |
1.000 |
-0.076 |
-0.009 |
-0.087 |
DmCO |
-0.157 |
-0.063 |
-0.160 |
-0.090 |
-0.121 |
0.037 |
-0.054 |
-0.022 |
-0.029 |
-0.054 |
-0.076 |
1.000 |
-0.462 |
0.173 |
DmNO |
0.136 |
-0.048 |
0.167 |
0.213 |
0.143 |
0.121 |
0.094 |
0.046 |
0.067 |
0.047 |
-0.009 |
-0.462 |
1.000 |
-0.210 |
Vcap |
0.065 |
-0.014 |
0.075 |
0.027 |
-0.002 |
-0.016 |
-0.051 |
-0.007 |
-0.082 |
-0.077 |
-0.087 |
0.173 |
-0.210 |
1.000 |
Trên thực tế, rất khó đọc kết quả trực tiếp từ correlation matrix, nên ta có thể chuyển những con số này thành hình ảnh (biểu đồ ma trận tương quan, correlograph) với package corrplot như sau:
Trước hết, Nhi viết 1 hàm để tạo ra correlation matrix với giá trị p_values thay vì hệ số tương quan r, kết quả này cho phép lọc bỏ những cặp tương quan không có ý nghĩa thống kê, thí dụ ở ngưỡng 0.05
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], ...)
p.mat[i, j] <- p.mat[j, i] <- tmp$p.value
}
}
colnames(p.mat) <- rownames(p.mat) <- colnames(mat)
p.mat
}
corsig_pos = cor.mtest(pos_df)%>%round(.,3)
corsig_neg = cor.mtest(neg_df)%>%round(.,3)
Biểu đồ tương quan 14 biến ở nhóm Bệnh nhân:
library(corrplot)
corrplot(cormat_pos,
col= pals::coolwarm(100),
method="pie",
type="upper",
order="hclust",
p.mat = corsig_pos,
sig.level = 0.05,
insig = "blank",
tl.col="black", tl.srt=45,tl.cex=0.8,
diag=FALSE
)

Biểu đồ của nhóm người Bình thường:
corrplot(cormat_neg,
col= pals::coolwarm(100),
method="pie",
type="upper",
order="hclust",
p.mat = corsig_neg,
sig.level = 0.05,
insig = "blank",
tl.col="black", tl.srt=45,tl.cex=0.8,
diag=FALSE
)

Dựa trên 2 biểu đồ này, ta có thể cảm nhận trực quan rằng có sự thay đổi về cấu trúc các cặp tương quan giữa 2 trạng thái: Có bệnh và không có bệnh. Nguyên nhân vì bệnh xơ phổi gây tác động khác nhau lên mỗi chức năng sinh lý hô hấp, thí dụ nó làm giảm diện tích khả dụng của màng phế nang mao mạch, tăng bề dày của màng này, giảm khả năng trao đổi khí … trong khi chức năng thông khí có thể vẫn còn bình thường. Như vậy, tương quan giữa 2 biến (đại lượng), ngay cả ở cấp độ quần thể hay mẫu, thực ra là một đặc tính “Động”. Một nghiên cứu cắt ngang chỉ có thể ghi nhận được đặc tính này ở một trạng thái, tại thời điểm nào đó, nhưng kết quả này có thể thay đổi ở một thời điểm / điều kiện khác.
Theo cách làm cổ điển, chúng ta chỉ dừng lại ở đây, hệ số tương quan r và p_value chỉ cho phép chúng ta suy diễn thống kê về độ mạnh, chiều hướng và ý nghĩa thống kê của mối tương quan giữa từng CẶP biến, nhưng không cho phép nhận định về quan hệ phức tạp, chồng chéo giữa TẤT CẢ các biến, dù có correlation matrix trong tay.
Trong bài này, Nhi sẽ giới thiệu với các bạn phương pháp Network analysis cho phép ta đi xa hơn và khai thác thêm nhiều thông tin từ correlation matrix.
Từ Correlation matrix đến correlation network
Phân tích mạng lưới (network analysis), hay mô hình mạng (graphical model) là một nhánh của khoa học Thống kê, nhằm giải quyết những vấn đề về mối quan hệ, sự tương tác giữa các cá thể/phần tử trong một tập hợp/quần thể
Khái niệm “mối liên hệ” trong đời sống rất đa dạng, phong phú: việc kết bạn với nhau trên Facebook, đồng tác giả một bài báo, liên lạc điện thoại ,giao dịch ngân hàng, cuộc hôn nhân giữa thành viên 2 gia tộc,một cây cầu nối 2 tỉnh/thành phố, 2 biến cùng xuất hiện trong một mô hình…
Trong trường hợp phân tích tương quan, mối liên kết giữa 2 biến được xác lập nhờ vào giá trị của r và p_value. Ta sẽ mượn các công cụ và lý thuyết Network analysis cho mục tiêu phân tích tương quan. Nhưng trước hết, ta phải tạo ra correlation network. Bằng cách nào ?
Thực ra, một network có thể được dựng từ 2 cấu trúc dữ liệu cơ bản là “adjacency matrix”, hoặc “edges list”. Correlation matrix chính là một adjacency matrix.
Trong R, có 2 nhóm công cụ khác nhau cho Network analysis, đó là packages statnet và igraph. Mỗi nhóm sử dụng dữ liệu network có cấu trúc khác nhau (network và graph). Ta có thể hoán chuyển giữa graph và network bằng package intergraph.
Từ correlation matrix, Nhi sẽ dùng igraph để chuyển adjacency matrix thành graph. Đồng thời, ta cũng sẽ tạo thêm 1 vài thuộc tính cho edges (các mối nối liên kết trong mạng lưới):Direction = hướng quan hệ, nhận giá trị Proportional nếu r>0, Inverse nếu r<0; và Strength: độ mạnh tương quan: Strong nếu abs(r) >0.5 và Weak nếu <0.5.
Làm thử trên nhóm bệnh nhân;
m=cormat_pos
diag(m)<-0
cor_pos_df = data.frame(row=rownames(m)[row(m)[upper.tri(m)]],
col=colnames(m)[col(m)[upper.tri(m)]],
corr=m[upper.tri(m)])
m=corsig_pos
diag(m)<-0
sig_pos_df = data.frame(row=rownames(m)[row(m)[upper.tri(m)]],
col=colnames(m)[col(m)[upper.tri(m)]],
corr=m[upper.tri(m)])
cor_pos_df$sig = sig_pos_df$corr
rm(sig_pos_df)
names(cor_pos_df)=c("from","to","r","sig")
reduced_pos = cor_pos_df%>%filter(sig<0.05)
reduced_pos<-reduced_pos%>%mutate(Strength = if_else(abs(reduced_pos$r)>0.5,"Strong","Weak"),
Direction = if_else(reduced_pos$r>0,"Pro","Inv"))
library(igraph)
graph_pos<-graph_from_data_frame(reduced_pos,directed = F)
Đây là kết quả của package igraph (lưu ý: gần đây có thêm package ggraph cho phép vẽ một dữ liệu igraph thành biểu đồ mạng sử dụng ngữ pháp đồ họa của Wilkinsonnhuè ggplot2, nhưng Nhi không bàn đến nó trong bài này)
plot(graph_pos)

Sau đó, ta dùng intergaph để chuyển graph object thành network object, nguyên nhân Nhi chọn làm việc trên network chứ không phải graph, vì định dạng dữ liệu network tương thích với hệ sinh thái statnet, cho phép các bạn đi xa hơn sau này với rất nhiều phương pháp, mô hình mà hệ sinh thái này cung cấp.
library(intergraph)
library(network)
library(statnet)
pos_net <- asNetwork(graph_pos)
plot(pos_net,displaylabel=T,label.cex=0.8)

Đây là hình ảnh mạng lưới tương quan giữa 14 biến trong nhóm Bệnh nhân, Nhi sẽ đưa thêm vào thuộc tính của vertex (là các nút trên mạng lưới, còn gọi là actor), biến thuộc tính đưa vào là Physio, nhằm phân loại nhóm chức năng sinh lý cho các thông số: Vent = Ventilation, Vol = lung volume, Diff =Diffusion, Memb = membrane conductance, Cap = lung capillary volume:
set.vertex.attribute(pos_net, "Physio",
c("vent","vent","Vol","Vol","Vol","Vol",
"Diff","Diff","Memb","Memb","Memb",
"Memb","Diff","Cap"))
pos_net %v% "Physio"
## [1] "vent" "vent" "Vol" "Vol" "Vol" "Vol" "Diff" "Diff" "Memb" "Memb"
## [11] "Memb" "Memb" "Diff" "Cap"
Ta làm tương tự cho nhóm Negative:
# negative
m=cormat_neg
diag(m)<-0
cor_neg_df = data.frame(row=rownames(m)[row(m)[upper.tri(m)]],
col=colnames(m)[col(m)[upper.tri(m)]],
corr=m[upper.tri(m)])
m=corsig_neg
diag(m)<-0
sig_neg_df = data.frame(row=rownames(m)[row(m)[upper.tri(m)]],
col=colnames(m)[col(m)[upper.tri(m)]],
corr=m[upper.tri(m)])
cor_neg_df$sig = sig_neg_df$corr
rm(sig_neg_df)
names(cor_neg_df)=c("from","to","r","sig")
reduced_neg = cor_neg_df%>%filter(sig<0.05)
reduced_neg<-reduced_neg%>%mutate(Strength = if_else(abs(reduced_neg$r)>0.5,"Strong","Weak"),
Direction = if_else(reduced_neg$r>0,"Pro","Inv"))
graph_neg<-graph_from_data_frame(reduced_neg,directed = F)
plot(graph_neg)

neg_net <- asNetwork(graph_neg)
plot(neg_net,displaylabel=T,label.cex=0.8)

set.vertex.attribute(pos_net, "Physio",
c("vent","vent","Vol","Vol","Vol","Vol",
"Diff","Diff","Memb","Memb","Memb",
"Memb","Diff","Cap"))
Mô tả cấu trúc mạng tương quan
Thực ra, network không chỉ là một hình vẽ trình bày ý tưởng, khái niệm về sự tương quan, nhưng là một mô hình chính xác và toán học về kích thước, cấu trúc phân bố của các nút (node, vertex, actor) và mạng liên kết giữa chúng (edges, arcs).
Sau đây, Nhi sẽ làm một vài thống kê mô tả để khảo sát cấu trúc 2 network mà ta vừa tạo ra:
- Kích thước tính bằng số nodes hay vertices:
Cả 2 network đều có kích thước như nhau là 14 vertices
network.size(pos_net)
## [1] 14
network.size(neg_net)
## [1] 14
- Mô tả cấu trúc bằng hàm summary:
Nhóm Positive có 36 edges, 4 edges attributes là r, sig, strength và direction và 2 vertex atribute là vertex.names và Physio
summary(pos_net,print.adj=FALSE)
## Network attributes:
## vertices = 14
## directed = FALSE
## hyper = FALSE
## loops = FALSE
## multiple = FALSE
## bipartite = FALSE
## total edges = 36
## missing edges = 0
## non-missing edges = 36
## density = 0.3956044
##
## Vertex attributes:
##
## Physio:
## character valued attribute
## attribute summary:
## Cap Diff Memb vent Vol
## 1 3 4 2 4
## vertex.names:
## character valued attribute
## 14 valid vertex names
##
## Edge attributes:
##
## Direction:
## character valued attribute
## attribute summary:
## Inv Pro
## 9 27
##
## r:
## numeric valued attribute
## attribute summary:
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## -0.55500 0.07275 0.21700 0.21400 0.36000 0.93900
##
## sig:
## numeric valued attribute
## attribute summary:
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## 0.000000 0.000000 0.001000 0.007667 0.011250 0.049000
##
## Strength:
## character valued attribute
## attribute summary:
## Strong Weak
## 9 27
Nhóm negative có nhiều edges hơn (45)
summary(neg_net,print.adj=FALSE)
## Network attributes:
## vertices = 14
## directed = FALSE
## hyper = FALSE
## loops = FALSE
## multiple = FALSE
## bipartite = FALSE
## total edges = 45
## missing edges = 0
## non-missing edges = 45
## density = 0.4945055
##
## Vertex attributes:
## vertex.names:
## character valued attribute
## 14 valid vertex names
##
## Edge attributes:
##
## Direction:
## character valued attribute
## attribute summary:
## Inv Pro
## 15 30
##
## r:
## numeric valued attribute
## attribute summary:
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## -0.4860 -0.1600 0.1960 0.1818 0.4670 0.9270
##
## sig:
## numeric valued attribute
## attribute summary:
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## 0.000000 0.000000 0.000000 0.006533 0.013000 0.040000
##
## Strength:
## character valued attribute
## attribute summary:
## Strong Weak
## 9 36
- Mật độ của mạng lưới:
Mật độ của mạng tương quan cho phép hình dung về khả năng liên kết giữa các phần tử trong tập hợp (ở đây là các xét nghiệm, thông số chức năng sinh lý).
# density
gden(pos_net)
## [1] 0.3956044
gden(neg_net)
## [1] 0.4945055
Mạng tương quan của nhóm negative có mật độ cao hơn so với mạng của nhóm Positive, phù hợp với khác biệt về số cặp tương quan ta thấy ở trên. Nếu giả định nhóm không có bệnh thể hiện trạng thái sinh lý bình thường, thì kết quả này cho thấy bệnh lý đã làm đứt gãy nhiều cặp tương quan sinh lý, thí dụ giữa chức năng thông khí và chức năng trao đổi khí.
- Đường kính của mạng lưới
Cả 2 mạng lưới đều có diameter=3, đây là con đường nối từ đầu này sang đầu kia (2 vertices) của mạng lưới
# Diameter
diameter= function(network) {
component.largest(network,result="graph")%>%geodist()->gd
max(gd$gdist)
}
diameter(pos_net)
## Node 1, Reach 14, Total 14
## Node 2, Reach 14, Total 28
## Node 3, Reach 14, Total 42
## Node 4, Reach 14, Total 56
## Node 5, Reach 14, Total 70
## Node 6, Reach 14, Total 84
## Node 7, Reach 14, Total 98
## Node 8, Reach 14, Total 112
## Node 9, Reach 14, Total 126
## Node 10, Reach 14, Total 140
## Node 11, Reach 14, Total 154
## Node 12, Reach 14, Total 168
## Node 13, Reach 14, Total 182
## Node 14, Reach 14, Total 196
## [1] 3
diameter(neg_net)
## Node 1, Reach 14, Total 14
## Node 2, Reach 14, Total 28
## Node 3, Reach 14, Total 42
## Node 4, Reach 14, Total 56
## Node 5, Reach 14, Total 70
## Node 6, Reach 14, Total 84
## Node 7, Reach 14, Total 98
## Node 8, Reach 14, Total 112
## Node 9, Reach 14, Total 126
## Node 10, Reach 14, Total 140
## Node 11, Reach 14, Total 154
## Node 12, Reach 14, Total 168
## Node 13, Reach 14, Total 182
## Node 14, Reach 14, Total 196
## [1] 3
- Chỉ số Transitivity: tỉ lệ giữa số liên kết bộ ba khép kín trên tổng số liên kết bộ ba hở và kín
gtrans(neg_net)
## [1] 0.6
gtrans(pos_net)
## [1] 0.5698324
Mạng của nhóm negative có tỉ số transitivity cao hơn, cho thấy nó có nhiều liên kết bộ ba hơn so với mạng tương quan của nhóm bệnh nhân
Lúc này, cảm nhận mơ hồ của Nhi bên trên về sự khác biệt giữa 2 mạng lưới tương quan của nhóm Bệnh nhân và người bình thường đã được chứng thựcmột cách định lượng: Ở nhóm Negative (không có bệnh), có nhiều mối liên hệ hơn giữa các biến, và một vài liên kết trong số này đã bị đứt gãy (biến mất) do tác động của bệnh lý xơ phổi.
Ta có thể khảo sát một cách trực quan khác biệt này bằng cách so sánh hình ảnh 2 mạng lưới tương quan: Hình ảnh này dễ hiểu và rõ ràng hơn nhiều so với correlogram : Ta có thể thấy những liên kết đã bị mất đi bao gồm: Giữa RV và Dlratio, giữa Tiffneau và KNO, KCO, giữa TLC,FVC,FEV1 và DmCO, … hình ảnh này phù hợp với thực tế, khi đa số các bệnh nhân có bệnh lý hô hấp đều có sự bảo tồn chức năng thông khí trên xét nghiệm hô hấp ký: kết quả xét nghiệm này vẫn bình thường, nhưng thay đổi bệnh lýđược thể hiện ra ở các xét nghiệm khác như đo dung tích phổi và/hoặc TLCO.
# Comparative plots
coords<-plot(pos_net,label=pos_net%v%"vertex.names",label.cex=.5,pad=1)

par(mar=c(0,0,2,0),mfrow=c(1,2))
gplot(pos_net,gmode="graph",
mode="kamadakawai",
label=pos_net%v%"vertex.names",
label.cex=.5,pad=1,
vertex.col = "red",
vertex.cex=0.8,
edge.col="grey50",
coord = coords)
title("Patients")
gplot(neg_net,gmode="graph",
mode="kamadakawai",
label=pos_net%v%"vertex.names",
label.cex=.5,pad=1,
vertex.col = "blue",
edge.col="grey50",
vertex.cex=0.8,
coord = coords)
title("Normal")

par(mfrow=c(1,1))
Phần tử (biến) nào có vai trò quan trọng nhất ?
Có nhiều ý kiến khác nhau về tầm quan trọng của các xét nghiệm sinh lý hô hấp. Thí dụ, hiện nay hô hấp ký (FEV1, FVC) là xét nghiệm thông dụng nhất, trong khi có quan điểm cho rằng xét nghiệm DLCO nhạy hơn để phát hiện sớm tổn thương chức năng của phổi và cho phép tiên lượng nguy cơ tử vong…Mặt khác, nếu tồn tại mối tương quan giữa 2 thông số xét nghiệm, sẽ dẫn đến suy nghĩ là chỉ cần sử dụng 1 thông số là đủ khai thác thông tin ?
Phần tiếp theo, Nhi sẽ dùng phân tích mạng lưới để nhận diện những biến có vai trò trung tâm, đóng góp quan trọng vào việc kiến tạo mạng liên kết với những biến còn lại, từ đó trả lời câu hỏi: chức năng hô hấp nào là quan trọng nhất trong toàn bộ các xét nghiệm đang được nghiên cứu ?
Một phần tử có vai trò trung tâm khi nó có nhiều liên hệ nhất với các phần tử khác trong mạng lưới. Có nhiều chỉ số để ước lượng tính Trung tâm này, như: Degree, Betweeness, Closeness, Eigen vector, Bonacich power, Information, Harary graph:
# Centrality measures combined
cent_df = data_frame(Vertex = c(neg_net %v% "vertex.names",
pos_net %v% "vertex.names"),
Physio = c(rep(c("vent","vent","Vol","Vol","Vol","Vol",
"Diff","Diff","Memb","Memb","Memb",
"Memb","Diff","Cap"),2)),
Status = c(rep("Normal",14),rep("Patients",14)),
Degree = c(degree(pos_net, gmode="graph"),
degree(neg_net, gmode="graph")),
Closeness = c(closeness(pos_net, gmode="graph"),
closeness(neg_net, gmode="graph")),
Beetweeness = c(betweenness(pos_net, gmode="graph"),
betweenness(neg_net, gmode="graph")),
Eigen = c(evcent(pos_net, gmode="graph"),
evcent(neg_net, gmode="graph")),
BonPow = c(bonpow(pos_net, gmode="graph"),
bonpow(neg_net, gmode="graph")),
InfoCent = c(infocent(pos_net, gmode="graph"),
infocent(neg_net, gmode="graph"))
)
Có thể thấy, ở từng trạng thái khác nhau: Bệnh lý/Bình thường, mỗi biến có vai trò khác nhau trong việc kiến tạo ra mạng lưới quan hệ với các biến còn lại.
Một số biến có vai trò trung tâm nổi bật, như FEV1, FVC (xét nghiệm hô hấp ký). Ta cũng có thể so sánh vai trò trung tâm giữa các nhóm xét nghiệm chức năng:
cent_df%>%gather(Degree:InfoCent,key="Score",value="Centrality")%>%
ggplot(aes(x=Vertex,y=Centrality,fill=Status))+
geom_point(stat="identity",size=2,shape=21,col="black")+
theme_bw()+coord_flip()+
facet_wrap(~Score,ncol=3,scales="free")

cent_df%>%gather(Degree:InfoCent,key="Score",value="Centrality")%>%
ggplot(aes(x=Physio,y=Centrality,fill=Status))+
geom_boxplot(alpha=0.7)+
theme_bw()+coord_flip()+
facet_wrap(~Score,ncol=2,scales="free")

cent_df%>%gather(Degree:InfoCent,key="Score",value="Centrality")%>%
ggplot(aes(x=Status,y=Centrality,fill=Physio))+
geom_boxplot(alpha=0.7)+
theme_bw()+coord_flip()+
facet_wrap(~Score,ncol=2,scales="free")

Ta còn có thể phát hiện ra phần tử then chốt “cut-point” , có nghĩa là khi loại bỏ phần tử này, cấu trúc mạng lưới sẽ bị phân rã, chia cắt thành nhiều cụm. Trong trường hợp này, có 1 cutpoint chính là biến DmNO. Nếu loại bỏ DmNO, 2 biến DmCO và Vcap sẽ bị tách rời ra khỏi mạng lưới.
# Cutpoint
cp_pos <- cutpoints(pos_net,mode="graph",
return.indicator=TRUE)
gplot(pos_net,gmode="graph",
label=pos_net%v%"vertex.names",
label.cex=.8,pad=1,
vertex.col=cp_pos+1,coord=coords,
jitter=FALSE,displaylabels=TRUE)

Yếu tố mỹ thuật của mạng tương quan
Trước khi kết thúc, chúng ta sẽ làm một số thử nghiệm thay đổi tùy chỉnh mỹ thuật cho biểu đồ mạng tương quan:
Có nhiều hình thức trình bày mạng lưới tương quan, sau đây là 6 kiểu thông dụng nhất:
op <- par(mar=c(0,0,2,0),mfrow=c(2,3))
gplot(pos_net,gmode="graph",mode="circle",edge.col="grey",vertex.col = "red",
vertex.cex=1,main="Circle")
gplot(pos_net,gmode="graph",mode="random",edge.col="grey",vertex.col = "gold",
vertex.cex=1,main="Random layout")
gplot(pos_net,gmode="graph",mode="fruchtermanreingold",edge.col="grey",vertex.col = 3,
vertex.cex=1,main="Fruchterman-Reingold")
gplot(pos_net,gmode="graph",mode="spring",edge.col="grey",vertex.col = 4,
vertex.cex=1,main="Spring")
gplot(pos_net,gmode="graph",mode="eigen",edge.col="grey",vertex.col = 6,
vertex.cex=1,main="Eigenstructure")
gplot(pos_net,gmode="graph",mode="kamadakawai",edge.col="grey",vertex.col = "black",
vertex.cex=1,main="Kamadakawai")

par(op)
Ta có thể thấy rằng 3 kiểu trình bày: mạng lưới vòng (Circle), Kamadakawai và Fruchterman-Reingold là rõ ràng và đẹp nhất.
Ta có thể tùy chỉnh kích thước và màu vertices theo attribute của nó: thí dụ 3 chỉ số Dgree, Closeness và Betweeness:
deg <- degree(pos_net,gmode="graph")
cls <- closeness(pos_net,gmode="graph")
bet <- betweenness(pos_net,gmode="graph")
gplot(pos_net,
vertex.cex=log(deg),
label.cex=0.8,
label=pos_net%v%"vertex.names",
vertex.col=rgb(deg/max(deg),0.2,1-deg/max(deg)),
gmode="graph",
coord=coords)

gplot(pos_net,
vertex.cex=3*cls,
label.cex=0.8,
label=pos_net%v%"vertex.names",
vertex.col=rgb(cls/max(cls),0.3,1.2-cls/max(cls)),
gmode="graph",
coord=coords)

gplot(pos_net,
vertex.cex=sqrt(bet+1),
label.cex=0.8,
label=pos_net%v%"vertex.names",
vertex.col=rgb(bet/max(bet),1-bet/max(bet),0.1),
gmode="graph",
coord=coords)

Tương tự, ta có thể tùy chỉnh màu sắc, dán nhãn cho các edges:
edge_dir <- as.factor(pos_net%e%"Direction")
edge_dir_pal <- c("blue","red")
plot(pos_net,label=pos_net%v%"vertex.names",
displaylabels=T,
label.cex=0.8,
vertex.cex=3*cls,
vertex.col=rgb(cls/max(cls),0.3,1.2-cls/max(cls)),
edge.col=edge_dir_pal[edge_dir],edge.lwd=1,
edge.label=edge_dir,edge.label.cex=0.5,
edge.label.col=edge_dir_pal[edge_dir],
mode="kamadakawai")

edge_val <- as.factor(pos_net%e%"r")
edge_val_pal <- pals::coolwarm(length(edge_val))
plot(pos_net,label=pos_net%v%"vertex.names",
displaylabels=T,
vertex.cex=2*cls,
vertex.col=rgb(cls/max(cls),0.3,1.2-cls/max(cls)),
label.cex=0.5,
edge.col=edge_val_pal[edge_val],edge.lwd=1,
edge.label=edge_val,
edge.label.cex=0.5,
edge.label.col=edge_val_pal[edge_val],
mode="kamadakawai")

edge_val_pos <- as.factor(pos_net%e%"r")
edge_val_pos_pal <- pals::coolwarm(length(edge_val_pos))
edge_val_neg <- as.factor(neg_net%e%"r")
edge_val_neg_pal <- pals::coolwarm(length(edge_val_neg))
deg_pos <- degree(pos_net,gmode="graph")
deg_neg <- degree(neg_net,gmode="graph")
par(mar=c(0,0,2,0),mfrow=c(1,2))
plot(pos_net,gmode="graph",
mode="kamadakawai",
label=pos_net%v%"vertex.names",
label.cex=.5,pad=1,
vertex.col=rgb(deg_pos/max(deg_pos),0.2,1-deg_pos/max(deg_pos)),
vertex.cex=log(deg_pos)*2,
edge.col=edge_val_pos_pal[edge_val_pos],edge.lwd=1,
edge.label=edge_val_pos,
edge.label.cex=0.5,
edge.label.col=edge_val_pos_pal[edge_val_pos],
coord = coords)
title("Patients")
plot(neg_net,gmode="graph",
mode="kamadakawai",
label=pos_net%v%"vertex.names",
label.cex=.5,pad=1,
vertex.col=rgb(deg_neg/max(deg_neg),0.2,1-deg_neg/max(deg_neg)),
vertex.cex=log(deg_neg)*2,
edge.col=edge_val_neg_pal[edge_val_neg],edge.lwd=1,
edge.label=edge_val_neg,
edge.label.cex=0.5,
edge.label.col=edge_val_neg_pal[edge_val_neg],
coord = coords)
title("Normal")

par(mfrow=c(1,1))
Tổng kết
Bài thực hành đến đây là hết. Trong bài này, Nhi đã kết hợp giữa phân tích tương quan cổ điển và một phương pháp thống kê hiện đại là Network analysis. Kết quả là ta có thể khai thác nhiều thông tin hơn từ ma trận tương quan, khảo sát một cách định lượng cấu trúc của mối quan hệ đa chiều giữa các biến, so sánh 2 mạng lưới tương quan dựa vào các chỉ số về mật độ và vai trò của mỗi biến trong việc kiến tạo nên mạng lưới quan hệ giữa chúng.
LS0tDQp0aXRsZTogIlBow6JuIHTDrWNoIG3huqFuZyBsxrDhu5tpIHTGsMahbmcgcXVhbiINCmF1dGhvcjogIkzDqiBOZ+G7jWMgS2jhuqMgTmhpIg0KZGF0ZTogIjA5IFRow6FuZyAxMiAyMDE4Ig0Kb3V0cHV0Og0KICBodG1sX2RvY3VtZW50OiANCiAgICBjb2RlX2Rvd25sb2FkOiB0cnVlDQogICAgY29kZV9mb2xkaW5nOiBoaWRlDQogICAgbnVtYmVyX3NlY3Rpb25zOiB5ZXMNCiAgICB0aGVtZTogImRlZmF1bHQiDQogICAgdG9jOiBUUlVFDQogICAgdG9jX2Zsb2F0OiBUUlVFDQogICAgZGV2OiAnc3ZnJw0KLS0tDQoNCmBgYHtyIHNldHVwLGluY2x1ZGU9RkFMU0V9DQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUpDQpgYGANCg0KIVtdKGNvcnJlbGF0aW9uX25ldC5wbmcpDQoNCiMgS2jhu59pIGjDoG5oOiBQaMOibiB0w61jaCB0xrDGoW5nIHF1YW4gY+G7lSDEkWnhu4NuDQoNCkNow7puZyB0YSBi4bqvdCDEkeG6p3UgY8OidSBjaHV54buHbiBi4bqxbmcgbeG7mXQgcGjDom4gdMOtY2ggdMawxqFuZyBxdWFuIHRoZW8gcGjGsMahbmcgcGjDoXAgY+G7lSDEkWnhu4NuLCBz4butIGThu6VuZyBo4buHIHPhu5EgdMawxqFuZyBxdWFuIHR1eeG6v24gdMOtbmggciAoUGVhcnNvbikuIMSQw6J5IGzDoCBt4buZdCBxdXkgdHLDrG5oIHBo4buVIGJp4bq/biB0cm9uZyBixrDhu5tjIHRoxINtIGTDsiBk4buvIGxp4buHdSB2w6Agbmjhu69uZyBuZ2hpw6puIGPhu6l1IG3DtCB04bqjLg0KDQpUaMOtIGThu6UgbWluaCBo4buNYSB0csOtY2ggdOG7qyBt4buZdCBuZ2hpw6puIGPhu6l1IGPhuq90IG5nYW5nLCBjaOG7pyDEkeG7gSBsw6JtIHPDoG5nIG7hu5lpIGtob2EgaMO0IGjhuqVwLCB24bubaSAxNCB0aMO0bmcgc+G7kSBjaOG7qWMgbsSDbmcgaMO0IGjhuqVwIMSRxrDhu6NjIGto4bqjbyBzw6F0IOG7nyAyIG5ow7NtIGLhu4duaCBuaMOibjogQ8OzIHbDoCBraMO0bmcgY8OzIGLhu4duaCBYxqEgcGjhu5VpIG3DtCBr4bq9Lg0KDQpgYGB7cixtZXNzYWdlID0gRkFMU0Usd2FybmluZz1GQUxTRX0NCmxpYnJhcnkodGlkeXZlcnNlKQ0KDQpkZiA9IHJlYWQuY3N2KCJSQVdfREFUQV9PUkcuY3N2IixkZWM9IiwiLHNlcD0nOycpDQoNCm5lZ19kZiA9IGRmJT4lc2VsZWN0KEZFVjEsVGlmZm5lYXUsRlZDLFRMQyxBVixSVixUTENPLEtDTyxUTE5PLEtOTyxETFJhdGlvLERtQ08sRG1OTyxWY2FwLElMRCklPiUNCiAgZmlsdGVyKC4kSUxEPT0iTmVnYXRpdmUiKSU+JS5bLC0xNV0NCg0KcG9zX2RmID0gZGYlPiVzZWxlY3QoRkVWMSxUaWZmbmVhdSxGVkMsVExDLEFWLFJWLFRMQ08sS0NPLFRMTk8sS05PLERMUmF0aW8sRG1DTyxEbU5PLFZjYXAsSUxEKSU+JQ0KICBmaWx0ZXIoLiRJTEQ9PSJQb3NpdGl2ZSIpJT4lLlssLTE1XQ0KDQpuZWdfZGY9c2FwcGx5KG5lZ19kZixhcy5udW1lcmljKQ0KcG9zX2RmPXNhcHBseShwb3NfZGYsYXMubnVtZXJpYykNCmBgYA0KDQoxNCB0aMO0bmcgc+G7kSBn4buTbTogRkVWMSwgdOG7iSBz4buRIFRpZmZuZWF1ID0gRkVWMS9GVkMga2jhuqNvIHPDoXQgY2jhu6ljIG7Eg25nIHRow7RuZyBraMOtLCBGVkMsIFRMQywgUlYsIEFWIGto4bqjbyBzw6F0IER1bmcgdMOtY2ggcGjhu5VpLCBUTENPLCBLQ08sIFRMTk8sIEtOTywgRExyYXRpbyBraOG6o28gc8OhdCBraOG6oyBuxINuZyB0cmFvIMSR4buVaSBraMOtLCBEbUNPIHbDoCBEbU5PIGto4bqjbyBzw6F0IGThuqtuIHN14bqldCBj4bunYSBtw6BuZyBwaOG6vyBuYW5nIG1hbyBt4bqhY2gsIHbDoCBWY2FwIGto4bqjbyBzw6F0IHRo4buDIHTDrWNoIG1hbyBt4bqhY2ggcGjhu5VpLg0KDQpOaMOzbSBC4buHbmggKFBvc2l0aXZlKSBjw7MgMTQ3IGLhu4duaCBuaMOibiwgDQoNCmBgYHtyLG1lc3NhZ2UgPSBGQUxTRSx3YXJuaW5nPUZBTFNFfQ0KaGVhZChwb3NfZGYpJT4la25pdHI6OmthYmxlKCkNCmBgYA0KDQpOaMOzbSBOZWdhdGl2ZSBn4buTbSAyMjkgbmfGsOG7nWkga2jDtG5nIGPDsyBi4buHbmguDQoNCmBgYHtyLG1lc3NhZ2UgPSBGQUxTRSx3YXJuaW5nPUZBTFNFfQ0KaGVhZChuZWdfZGYpJT4la25pdHI6OmthYmxlKCkNCmBgYA0KDQpN4bulYyB0acOqdSBnaeG6oyDEkeG7i25oIGzDoCB0YSBtdeG7kW4ga2jhuqNvIHPDoXQgbeG7kWkgdMawxqFuZyBxdWFuIGdp4buvYSAxNCB0aMO0bmcgc+G7kSBjaOG7qWMgbsSDbmcgbsOgeS4gxJDDonkgbMOgIG3hu5l0IHZp4buHYyBy4bqldCDEkcahbiBnaeG6o24gbmjGsCB0YSBiaeG6v3QsIGNo4buJIGPhuqduIMOhcCBk4bulbmcgaMOgbSBjb3IgdHLDqm4gZGF0YSBtYXRyaXggY8OzIChrKSBiaeG6v24gc+G7kSwgdGEgc+G6vSBjw7Mga+G6v3QgcXXhuqMgbMOgIG3hu5l0IGNvcnJlbGF0aW9uIG1hdHJpeCB24bubaSBrw61jaCB0aMaw4bubYyAoayB4IGspDQoNCmBgYHtyLG1lc3NhZ2UgPSBGQUxTRSx3YXJuaW5nPUZBTFNFfQ0KY29ybWF0X3BvcyA9IGNvcihwb3NfZGYsIG1ldGhvZD0icGVhcnNvbiIpJT4lcm91bmQoLiwzKQ0KY29ybWF0X25lZyA9IGNvcihuZWdfZGYsIG1ldGhvZD0icGVhcnNvbiIgKSU+JXJvdW5kKC4sMykNCmBgYA0KDQpNYSB0cuG6rW4gdMawxqFuZyBxdWFuIDE0IGJp4bq/biB0csOqbiBuaMOzbSBi4buHbmggbmjGsCBzYXU6DQoNCmBgYHtyLG1lc3NhZ2UgPSBGQUxTRSx3YXJuaW5nPUZBTFNFfQ0KY29ybWF0X3BvcyAlPiUga25pdHI6OmthYmxlKCkNCmBgYA0KDQpDw7JuIMSRw6J5IGzDoCBr4bq/dCBxdeG6oyB0csOqbiBuaMOzbSBLaMO0bmcgY8OzIGLhu4duaDoNCg0KYGBge3IsbWVzc2FnZSA9IEZBTFNFLHdhcm5pbmc9RkFMU0V9DQpjb3JtYXRfbmVnICU+JSBrbml0cjo6a2FibGUoKQ0KYGBgDQoNClRyw6puIHRo4buxYyB04bq/LCBy4bqldCBraMOzIMSR4buNYyBr4bq/dCBxdeG6oyB0cuG7sWMgdGnhur9wICB04burICBjb3JyZWxhdGlvbiBtYXRyaXgsIG7Dqm4gdGEgY8OzIHRo4buDIGNodXnhu4NuIG5o4buvbmcgY29uIHPhu5EgbsOgeSB0aMOgbmggaMOsbmgg4bqjbmggKGJp4buDdSDEkeG7kyBtYSB0cuG6rW4gdMawxqFuZyBxdWFuLCBjb3JyZWxvZ3JhcGgpIHbhu5tpIHBhY2thZ2UgY29ycnBsb3QgbmjGsCBzYXU6DQoNClRyxrDhu5tjIGjhur90LCBOaGkgdmnhur90IDEgaMOgbSDEkeG7gyB04bqhbyByYSBjb3JyZWxhdGlvbiBtYXRyaXggduG7m2kgZ2nDoSB0cuG7iyBwX3ZhbHVlcyB0aGF5IHbDrCBo4buHIHPhu5EgdMawxqFuZyBxdWFuIHIsIGvhur90IHF14bqjIG7DoHkgY2hvIHBow6lwIGzhu41jIGLhu48gbmjhu69uZyBj4bq3cCB0xrDGoW5nIHF1YW4ga2jDtG5nIGPDsyDDvSBuZ2jEqWEgdGjhu5FuZyBrw6osIHRow60gZOG7pSDhu58gbmfGsOG7oW5nIDAuMDUNCg0KYGBge3IsbWVzc2FnZSA9IEZBTFNFLHdhcm5pbmc9RkFMU0V9DQpjb3IubXRlc3QgPC0gZnVuY3Rpb24obWF0LCAuLi4pIHsNCiAgbWF0IDwtIGFzLm1hdHJpeChtYXQpDQogIG4gPC0gbmNvbChtYXQpDQogIHAubWF0PC0gbWF0cml4KE5BLCBuLCBuKQ0KICBkaWFnKHAubWF0KSA8LSAwDQogIGZvciAoaSBpbiAxOihuIC0gMSkpIHsNCiAgICBmb3IgKGogaW4gKGkgKyAxKTpuKSB7DQogICAgICB0bXAgPC0gY29yLnRlc3QobWF0WywgaV0sIG1hdFssIGpdLCAuLi4pDQogICAgICBwLm1hdFtpLCBqXSA8LSBwLm1hdFtqLCBpXSA8LSB0bXAkcC52YWx1ZQ0KICAgIH0NCiAgfQ0KICBjb2xuYW1lcyhwLm1hdCkgPC0gcm93bmFtZXMocC5tYXQpIDwtIGNvbG5hbWVzKG1hdCkNCiAgcC5tYXQNCn0NCg0KY29yc2lnX3BvcyA9IGNvci5tdGVzdChwb3NfZGYpJT4lcm91bmQoLiwzKQ0KY29yc2lnX25lZyA9IGNvci5tdGVzdChuZWdfZGYpJT4lcm91bmQoLiwzKQ0KYGBgDQoNCkJp4buDdSDEkeG7kyB0xrDGoW5nIHF1YW4gMTQgYmnhur9uIOG7nyBuaMOzbSBC4buHbmggbmjDom46IA0KDQpgYGB7cixtZXNzYWdlID0gRkFMU0Usd2FybmluZz1GQUxTRX0NCmxpYnJhcnkoY29ycnBsb3QpDQoNCmNvcnJwbG90KGNvcm1hdF9wb3MsIA0KICAgICAgICAgY29sPSBwYWxzOjpjb29sd2FybSgxMDApLA0KICAgICAgICAgbWV0aG9kPSJwaWUiLA0KICAgICAgICAgdHlwZT0idXBwZXIiLA0KICAgICAgICAgb3JkZXI9ImhjbHVzdCIsDQogICAgICAgICBwLm1hdCA9IGNvcnNpZ19wb3MsIA0KICAgICAgICAgc2lnLmxldmVsID0gMC4wNSwNCiAgICAgICAgIGluc2lnID0gImJsYW5rIiwNCiAgICAgICAgIHRsLmNvbD0iYmxhY2siLCB0bC5zcnQ9NDUsdGwuY2V4PTAuOCwNCiAgICAgICAgIGRpYWc9RkFMU0UNCiAgICAgICAgICkNCmBgYA0KDQpCaeG7g3UgxJHhu5MgY+G7p2EgbmjDs20gbmfGsOG7nWkgQsOsbmggdGjGsOG7nW5nOg0KDQpgYGB7ciAsbWVzc2FnZSA9IEZBTFNFLHdhcm5pbmc9RkFMU0V9DQpjb3JycGxvdChjb3JtYXRfbmVnLCANCiAgICAgICAgIGNvbD0gcGFsczo6Y29vbHdhcm0oMTAwKSwNCiAgICAgICAgIG1ldGhvZD0icGllIiwNCiAgICAgICAgIHR5cGU9InVwcGVyIiwNCiAgICAgICAgIG9yZGVyPSJoY2x1c3QiLA0KICAgICAgICAgcC5tYXQgPSBjb3JzaWdfbmVnLCANCiAgICAgICAgIHNpZy5sZXZlbCA9IDAuMDUsDQogICAgICAgICBpbnNpZyA9ICJibGFuayIsDQogICAgICAgICB0bC5jb2w9ImJsYWNrIiwgdGwuc3J0PTQ1LHRsLmNleD0wLjgsDQogICAgICAgICBkaWFnPUZBTFNFDQopDQpgYGANCg0KROG7sWEgdHLDqm4gMiBiaeG7g3UgxJHhu5MgbsOgeSwgdGEgY8OzIHRo4buDIGPhuqNtIG5o4bqtbiB0cuG7sWMgcXVhbiBy4bqxbmcgY8OzIHPhu7EgdGhheSDEkeG7lWkgduG7gSBj4bqldSB0csO6YyBjw6FjIGPhurdwIHTGsMahbmcgcXVhbiBnaeG7r2EgMiB0cuG6oW5nIHRow6FpOiBDw7MgYuG7h25oIHbDoCBraMO0bmcgY8OzIGLhu4duaC4gTmd1ecOqbiBuaMOibiB2w6wgYuG7h25oIHjGoSBwaOG7lWkgZ8OieSB0w6FjIMSR4buZbmcga2jDoWMgbmhhdSBsw6puIG3hu5dpIGNo4bupYyBuxINuZyBzaW5oIGzDvSBow7QgaOG6pXAsIHRow60gZOG7pSBuw7MgbMOgbSBnaeG6o20gZGnhu4duIHTDrWNoIGto4bqjIGThu6VuZyBj4bunYSBtw6BuZyBwaOG6vyBuYW5nIG1hbyBt4bqhY2gsIHTEg25nIGLhu4EgZMOgeSBj4bunYSBtw6BuZyBuw6B5LCBnaeG6o20ga2jhuqMgbsSDbmcgdHJhbyDEkeG7lWkga2jDrSAuLi4gdHJvbmcga2hpIGNo4bupYyBuxINuZyB0aMO0bmcga2jDrSBjw7MgdGjhu4MgduG6q24gY8OybiBiw6xuaCB0aMaw4budbmcuIE5oxrAgduG6rXksIHTGsMahbmcgcXVhbiBnaeG7r2EgMiBiaeG6v24gKMSR4bqhaSBsxrDhu6NuZyksIG5nYXkgY+G6oyDhu58gY+G6pXAgxJHhu5kgcXXhuqduIHRo4buDIGhheSBt4bqrdSwgdGjhu7FjIHJhIGzDoCBt4buZdCDEkeG6t2MgdMOtbmggIsSQ4buZbmciLiBN4buZdCBuZ2hpw6puIGPhu6l1IGPhuq90IG5nYW5nIGNo4buJIGPDsyB0aOG7gyBnaGkgbmjhuq1uIMSRxrDhu6NjIMSR4bq3YyB0w61uaCBuw6B5IOG7nyBt4buZdCB0cuG6oW5nIHRow6FpLCB04bqhaSB0aOG7nWkgxJFp4buDbSBuw6BvIMSRw7MsIG5oxrBuZyBr4bq/dCBxdeG6oyBuw6B5IGPDsyB0aOG7gyB0aGF5IMSR4buVaSDhu58gbeG7mXQgdGjhu51pIMSRaeG7g20gLyDEkWnhu4F1IGtp4buHbiBraMOhYy4NCg0KVGhlbyBjw6FjaCBsw6BtIGPhu5UgxJFp4buDbiwgY2jDum5nIHRhIGNo4buJIGThu6tuZyBs4bqhaSDhu58gxJHDonksIGjhu4cgc+G7kSB0xrDGoW5nIHF1YW4gciB2w6AgcF92YWx1ZSBjaOG7iSBjaG8gcGjDqXAgY2jDum5nIHRhIHN1eSBkaeG7hW4gdGjhu5FuZyBrw6ogduG7gSDEkeG7mSBt4bqhbmgsIGNoaeG7gXUgaMaw4bubbmcgdsOgIMO9IG5naMSpYSB0aOG7kW5nIGvDqiBj4bunYSBt4buRaSB0xrDGoW5nIHF1YW4gZ2nhu69hIHThu6tuZyBD4bq2UCBiaeG6v24sIG5oxrBuZyBraMO0bmcgY2hvIHBow6lwIG5o4bqtbiDEkeG7i25oIHbhu4EgcXVhbiBo4buHIHBo4bupYyB04bqhcCwgY2jhu5NuZyBjaMOpbyBnaeG7r2EgVOG6pFQgQ+G6oiBjw6FjIGJp4bq/biwgZMO5IGPDsyBjb3JyZWxhdGlvbiBtYXRyaXggdHJvbmcgdGF5LiANCg0KVHJvbmcgYsOgaSBuw6B5LCBOaGkgc+G6vSBnaeG7m2kgdGhp4buHdSB24bubaSBjw6FjIGLhuqFuIHBoxrDGoW5nIHBow6FwIE5ldHdvcmsgYW5hbHlzaXMgY2hvIHBow6lwIHRhIMSRaSB4YSBoxqFuIHbDoCBraGFpIHRow6FjIHRow6ptIG5oaeG7gXUgdGjDtG5nIHRpbiB04burIGNvcnJlbGF0aW9uIG1hdHJpeC4NCg0KIyBU4burIENvcnJlbGF0aW9uIG1hdHJpeCDEkeG6v24gY29ycmVsYXRpb24gbmV0d29yaw0KDQpQaMOibiB0w61jaCBt4bqhbmcgbMaw4bubaSAobmV0d29yayBhbmFseXNpcyksIGhheSBtw7QgaMOsbmggbeG6oW5nIChncmFwaGljYWwgbW9kZWwpIGzDoCBt4buZdCBuaMOhbmggY+G7p2Ega2hvYSBo4buNYyBUaOG7kW5nIGvDqiwgbmjhurFtIGdp4bqjaSBxdXnhur90IG5o4buvbmcgduG6pW4gxJHhu4EgduG7gSBt4buRaSBxdWFuIGjhu4csIHPhu7EgdMawxqFuZyB0w6FjIGdp4buvYSBjw6FjIGPDoSB0aOG7gy9waOG6p24gdOG7rSB0cm9uZyBt4buZdCB04bqtcCBo4bujcC9xdeG6p24gdGjhu4MNCg0KS2jDoWkgbmnhu4dtICJt4buRaSBsacOqbiBo4buHIiB0cm9uZyDEkeG7nWkgc+G7kW5nIHLhuqV0IMSRYSBk4bqhbmcsIHBob25nIHBow7o6IHZp4buHYyBr4bq/dCBi4bqhbiB24bubaSBuaGF1IHRyw6puIEZhY2Vib29rLCDEkeG7k25nIHTDoWMgZ2nhuqMgbeG7mXQgYsOgaSBiw6FvLCBsacOqbiBs4bqhYyDEkWnhu4duIHRob+G6oWkgLGdpYW8gZOG7i2NoIG5nw6JuIGjDoG5nLCBjdeG7mWMgaMO0biBuaMOibiBnaeG7r2EgdGjDoG5oIHZpw6puIDIgZ2lhIHThu5ljLG3hu5l0IGPDonkgY+G6p3UgbuG7kWkgMiB04buJbmgvdGjDoG5oIHBo4buRLCAyIGJp4bq/biBjw7luZyB4deG6pXQgaGnhu4duIHRyb25nIG3hu5l0IG3DtCBow6xuaC4uLiANCg0KVHJvbmcgdHLGsOG7nW5nIGjhu6NwIHBow6JuIHTDrWNoIHTGsMahbmcgcXVhbiwgbeG7kWkgbGnDqm4ga+G6v3QgZ2nhu69hIDIgYmnhur9uIMSRxrDhu6NjIHjDoWMgbOG6rXAgbmjhu50gdsOgbyBnacOhIHRy4buLIGPhu6dhIHIgdsOgIHBfdmFsdWUuIFRhIHPhur0gbcaw4bujbiBjw6FjIGPDtG5nIGPhu6UgdsOgIGzDvSB0aHV54bq/dCBOZXR3b3JrIGFuYWx5c2lzIGNobyBt4bulYyB0acOqdSBwaMOibiB0w61jaCB0xrDGoW5nIHF1YW4uIE5oxrBuZyB0csaw4bubYyBo4bq/dCwgdGEgcGjhuqNpIHThuqFvIHJhIGNvcnJlbGF0aW9uIG5ldHdvcmsuIELhurFuZyBjw6FjaCBuw6BvID8NCg0KVGjhu7FjIHJhLCBt4buZdCBuZXR3b3JrIGPDsyB0aOG7gyDEkcaw4bujYyBk4buxbmcgdOG7qyAyIGPhuqV1IHRyw7pjIGThu68gbGnhu4d1IGPGoSBi4bqjbiBsw6AgImFkamFjZW5jeSBtYXRyaXgiLCBob+G6t2MgImVkZ2VzIGxpc3QiLiBDb3JyZWxhdGlvbiBtYXRyaXggY2jDrW5oIGzDoCBt4buZdCBhZGphY2VuY3kgbWF0cml4Lg0KDQpUcm9uZyBSLCBjw7MgMiBuaMOzbSBjw7RuZyBj4bulIGtow6FjIG5oYXUgY2hvIE5ldHdvcmsgYW5hbHlzaXMsIMSRw7MgbMOgIHBhY2thZ2VzIHN0YXRuZXQgdsOgIGlncmFwaC4gTeG7l2kgbmjDs20gc+G7rSBk4bulbmcgZOG7ryBsaeG7h3UgbmV0d29yayBjw7MgY+G6pXUgdHLDumMga2jDoWMgbmhhdSAobmV0d29yayB2w6AgZ3JhcGgpLiBUYSBjw7MgdGjhu4MgaG/DoW4gY2h1eeG7g24gZ2nhu69hIGdyYXBoIHbDoCBuZXR3b3JrIGLhurFuZyBwYWNrYWdlIGludGVyZ3JhcGguDQoNClThu6sgY29ycmVsYXRpb24gbWF0cml4LCBOaGkgc+G6vSBkw7luZyBpZ3JhcGggxJHhu4MgY2h1eeG7g24gYWRqYWNlbmN5IG1hdHJpeCB0aMOgbmggZ3JhcGguIMSQ4buTbmcgdGjhu51pLCB0YSBjxaluZyBz4bq9IHThuqFvIHRow6ptIDEgdsOgaSB0aHXhu5ljIHTDrW5oIGNobyBlZGdlcyAoY8OhYyBt4buRaSBu4buRaSBsacOqbiBr4bq/dCB0cm9uZyBt4bqhbmcgbMaw4bubaSk6RGlyZWN0aW9uID0gaMaw4bubbmcgcXVhbiBo4buHLCBuaOG6rW4gZ2nDoSB0cuG7iyBQcm9wb3J0aW9uYWwgbuG6v3Ugcj4wLCBJbnZlcnNlIG7hur91IHI8MDsgdsOgIFN0cmVuZ3RoOiDEkeG7mSBt4bqhbmggdMawxqFuZyBxdWFuOiBTdHJvbmcgbuG6v3UgYWJzKHIpID4wLjUgdsOgIFdlYWsgbuG6v3UgPDAuNS4NCg0KTMOgbSB0aOG7rSB0csOqbiBuaMOzbSBi4buHbmggbmjDom47DQoNCmBgYHtyLG1lc3NhZ2UgPSBGQUxTRSx3YXJuaW5nPUZBTFNFfQ0KbT1jb3JtYXRfcG9zDQpkaWFnKG0pPC0wDQpjb3JfcG9zX2RmID0gZGF0YS5mcmFtZShyb3c9cm93bmFtZXMobSlbcm93KG0pW3VwcGVyLnRyaShtKV1dLCANCiAgICAgICAgICAgIGNvbD1jb2xuYW1lcyhtKVtjb2wobSlbdXBwZXIudHJpKG0pXV0sIA0KICAgICAgICAgICAgY29ycj1tW3VwcGVyLnRyaShtKV0pDQoNCm09Y29yc2lnX3Bvcw0KZGlhZyhtKTwtMA0Kc2lnX3Bvc19kZiA9IGRhdGEuZnJhbWUocm93PXJvd25hbWVzKG0pW3JvdyhtKVt1cHBlci50cmkobSldXSwgDQogICAgICAgICAgICAgICAgICAgICAgICBjb2w9Y29sbmFtZXMobSlbY29sKG0pW3VwcGVyLnRyaShtKV1dLCANCiAgICAgICAgICAgICAgICAgICAgICAgIGNvcnI9bVt1cHBlci50cmkobSldKQ0KDQpjb3JfcG9zX2RmJHNpZyA9IHNpZ19wb3NfZGYkY29ycg0Kcm0oc2lnX3Bvc19kZikNCg0KbmFtZXMoY29yX3Bvc19kZik9YygiZnJvbSIsInRvIiwiciIsInNpZyIpDQoNCnJlZHVjZWRfcG9zID0gY29yX3Bvc19kZiU+JWZpbHRlcihzaWc8MC4wNSkNCnJlZHVjZWRfcG9zPC1yZWR1Y2VkX3BvcyU+JW11dGF0ZShTdHJlbmd0aCA9IGlmX2Vsc2UoYWJzKHJlZHVjZWRfcG9zJHIpPjAuNSwiU3Ryb25nIiwiV2VhayIpLA0KICAgICAgICAgICAgICAgICAgICAgRGlyZWN0aW9uID0gaWZfZWxzZShyZWR1Y2VkX3BvcyRyPjAsIlBybyIsIkludiIpKQ0KDQpsaWJyYXJ5KGlncmFwaCkNCg0KZ3JhcGhfcG9zPC1ncmFwaF9mcm9tX2RhdGFfZnJhbWUocmVkdWNlZF9wb3MsZGlyZWN0ZWQgPSBGKQ0KYGBgDQoNCsSQw6J5IGzDoCBr4bq/dCBxdeG6oyBj4bunYSBwYWNrYWdlIGlncmFwaCAobMawdSDDvTogZ+G6p24gxJHDonkgY8OzIHRow6ptIHBhY2thZ2UgZ2dyYXBoIGNobyBwaMOpcCB24bq9IG3hu5l0IGThu68gbGnhu4d1IGlncmFwaCB0aMOgbmggYmnhu4N1IMSR4buTIG3huqFuZyBz4butIGThu6VuZyBuZ+G7ryBwaMOhcCDEkeG7kyBo4buNYSBj4bunYSBXaWxraW5zb25uaHXDqCBnZ3Bsb3QyLCBuaMawbmcgTmhpIGtow7RuZyBiw6BuIMSR4bq/biBuw7MgdHJvbmcgYsOgaSBuw6B5KQ0KDQpgYGB7cixtZXNzYWdlID0gRkFMU0Usd2FybmluZz1GQUxTRX0NCnBsb3QoZ3JhcGhfcG9zKQ0KYGBgDQoNClNhdSDEkcOzLCB0YSBkw7luZyBpbnRlcmdhcGggxJHhu4MgY2h1eeG7g24gZ3JhcGggb2JqZWN0IHRow6BuaCBuZXR3b3JrIG9iamVjdCwgbmd1ecOqbiBuaMOibiBOaGkgY2jhu41uIGzDoG0gdmnhu4djIHRyw6puIG5ldHdvcmsgY2jhu6kga2jDtG5nIHBo4bqjaSBncmFwaCwgdsOsIMSR4buLbmggZOG6oW5nIGThu68gbGnhu4d1IG5ldHdvcmsgdMawxqFuZyB0aMOtY2ggduG7m2kgaOG7hyBzaW5oIHRow6FpIHN0YXRuZXQsIGNobyBwaMOpcCBjw6FjIGLhuqFuIMSRaSB4YSBoxqFuIHNhdSBuw6B5IHbhu5tpIHLhuqV0IG5oaeG7gXUgcGjGsMahbmcgcGjDoXAsIG3DtCBow6xuaCBtw6AgaOG7hyBzaW5oIHRow6FpIG7DoHkgY3VuZyBj4bqlcC4NCg0KYGBge3IgLG1lc3NhZ2UgPSBGQUxTRSx3YXJuaW5nPUZBTFNFfQ0KbGlicmFyeShpbnRlcmdyYXBoKQ0KbGlicmFyeShuZXR3b3JrKQ0KbGlicmFyeShzdGF0bmV0KQ0KDQpwb3NfbmV0IDwtIGFzTmV0d29yayhncmFwaF9wb3MpDQoNCnBsb3QocG9zX25ldCxkaXNwbGF5bGFiZWw9VCxsYWJlbC5jZXg9MC44KQ0KYGBgDQoNCsSQw6J5IGzDoCBow6xuaCDhuqNuaCBt4bqhbmcgbMaw4bubaSB0xrDGoW5nIHF1YW4gZ2nhu69hIDE0IGJp4bq/biB0cm9uZyBuaMOzbSBC4buHbmggbmjDom4sIE5oaSBz4bq9IMSRxrBhIHRow6ptIHbDoG8gdGh14buZYyB0w61uaCBj4bunYSB2ZXJ0ZXggKGzDoCBjw6FjIG7DunQgdHLDqm4gbeG6oW5nIGzGsOG7m2ksIGPDsm4gZ+G7jWkgbMOgIGFjdG9yKSwgYmnhur9uIHRodeG7mWMgdMOtbmggxJHGsGEgdsOgbyBsw6AgUGh5c2lvLCBuaOG6sW0gcGjDom4gbG/huqFpIG5ow7NtIGNo4bupYyBuxINuZyBzaW5oIGzDvSBjaG8gY8OhYyB0aMO0bmcgc+G7kTogVmVudCA9IFZlbnRpbGF0aW9uLCBWb2wgPSBsdW5nIHZvbHVtZSwgRGlmZiA9RGlmZnVzaW9uLCBNZW1iID0gbWVtYnJhbmUgY29uZHVjdGFuY2UsIENhcCA9IGx1bmcgY2FwaWxsYXJ5IHZvbHVtZToNCg0KYGBge3IgLG1lc3NhZ2UgPSBGQUxTRSx3YXJuaW5nPUZBTFNFfQ0Kc2V0LnZlcnRleC5hdHRyaWJ1dGUocG9zX25ldCwgIlBoeXNpbyIsDQogICAgICAgICAgICAgICAgICAgICBjKCJ2ZW50IiwidmVudCIsIlZvbCIsIlZvbCIsIlZvbCIsIlZvbCIsDQogICAgICAgICAgICAgICAgICAgICAgICJEaWZmIiwiRGlmZiIsIk1lbWIiLCJNZW1iIiwiTWVtYiIsICAgIA0KICAgICAgICAgICAgICAgICAgICAgICAiTWVtYiIsIkRpZmYiLCJDYXAiKSkNCmBgYA0KDQpgYGB7cixtZXNzYWdlID0gRkFMU0Usd2FybmluZz1GQUxTRX0NCnBvc19uZXQgJXYlICJQaHlzaW8iDQpgYGANCg0KVGEgbMOgbSB0xrDGoW5nIHThu7EgY2hvIG5ow7NtIE5lZ2F0aXZlOg0KDQpgYGB7ciAsbWVzc2FnZSA9IEZBTFNFLHdhcm5pbmc9RkFMU0V9DQojIG5lZ2F0aXZlDQoNCm09Y29ybWF0X25lZw0KZGlhZyhtKTwtMA0KY29yX25lZ19kZiA9IGRhdGEuZnJhbWUocm93PXJvd25hbWVzKG0pW3JvdyhtKVt1cHBlci50cmkobSldXSwgDQogICAgICAgICAgICAgICAgICAgICAgICBjb2w9Y29sbmFtZXMobSlbY29sKG0pW3VwcGVyLnRyaShtKV1dLCANCiAgICAgICAgICAgICAgICAgICAgICAgIGNvcnI9bVt1cHBlci50cmkobSldKQ0KDQptPWNvcnNpZ19uZWcNCmRpYWcobSk8LTANCnNpZ19uZWdfZGYgPSBkYXRhLmZyYW1lKHJvdz1yb3duYW1lcyhtKVtyb3cobSlbdXBwZXIudHJpKG0pXV0sIA0KICAgICAgICAgICAgICAgICAgICAgICAgY29sPWNvbG5hbWVzKG0pW2NvbChtKVt1cHBlci50cmkobSldXSwgDQogICAgICAgICAgICAgICAgICAgICAgICBjb3JyPW1bdXBwZXIudHJpKG0pXSkNCg0KY29yX25lZ19kZiRzaWcgPSBzaWdfbmVnX2RmJGNvcnINCnJtKHNpZ19uZWdfZGYpDQoNCm5hbWVzKGNvcl9uZWdfZGYpPWMoImZyb20iLCJ0byIsInIiLCJzaWciKQ0KDQpyZWR1Y2VkX25lZyA9IGNvcl9uZWdfZGYlPiVmaWx0ZXIoc2lnPDAuMDUpDQpyZWR1Y2VkX25lZzwtcmVkdWNlZF9uZWclPiVtdXRhdGUoU3RyZW5ndGggPSBpZl9lbHNlKGFicyhyZWR1Y2VkX25lZyRyKT4wLjUsIlN0cm9uZyIsIldlYWsiKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBEaXJlY3Rpb24gPSBpZl9lbHNlKHJlZHVjZWRfbmVnJHI+MCwiUHJvIiwiSW52IikpDQoNCmdyYXBoX25lZzwtZ3JhcGhfZnJvbV9kYXRhX2ZyYW1lKHJlZHVjZWRfbmVnLGRpcmVjdGVkID0gRikNCg0KcGxvdChncmFwaF9uZWcpDQoNCm5lZ19uZXQgPC0gYXNOZXR3b3JrKGdyYXBoX25lZykNCg0KcGxvdChuZWdfbmV0LGRpc3BsYXlsYWJlbD1ULGxhYmVsLmNleD0wLjgpDQoNCnNldC52ZXJ0ZXguYXR0cmlidXRlKHBvc19uZXQsICJQaHlzaW8iLA0KICAgICAgICAgICAgICAgICAgICAgYygidmVudCIsInZlbnQiLCJWb2wiLCJWb2wiLCJWb2wiLCJWb2wiLA0KICAgICAgICAgICAgICAgICAgICAgICAiRGlmZiIsIkRpZmYiLCJNZW1iIiwiTWVtYiIsIk1lbWIiLCAgICANCiAgICAgICAgICAgICAgICAgICAgICAgIk1lbWIiLCJEaWZmIiwiQ2FwIikpDQpgYGANCg0KIyBNw7QgdOG6oyBj4bqldSB0csO6YyBt4bqhbmcgdMawxqFuZyBxdWFuDQoNClRo4buxYyByYSwgbmV0d29yayBraMO0bmcgY2jhu4kgbMOgIG3hu5l0IGjDrG5oIHbhur0gdHLDrG5oIGLDoHkgw70gdMaw4bufbmcsIGtow6FpIG5p4buHbSB24buBIHPhu7EgdMawxqFuZyBxdWFuLCBuaMawbmcgbMOgIG3hu5l0IG3DtCBow6xuaCBjaMOtbmggeMOhYyB2w6AgdG/DoW4gaOG7jWMgduG7gSBrw61jaCB0aMaw4bubYywgY+G6pXUgdHLDumMgcGjDom4gYuG7kSBj4bunYSBjw6FjIG7DunQgKG5vZGUsIHZlcnRleCwgYWN0b3IpIHbDoCBt4bqhbmcgbGnDqm4ga+G6v3QgZ2nhu69hIGNow7puZyAoZWRnZXMsIGFyY3MpLiANCg0KU2F1IMSRw6J5LCBOaGkgc+G6vSBsw6BtIG3hu5l0IHbDoGkgdGjhu5FuZyBrw6ogbcO0IHThuqMgxJHhu4Mga2jhuqNvIHPDoXQgY+G6pXUgdHLDumMgMiBuZXR3b3JrIG3DoCB0YSB24burYSB04bqhbyByYToNCg0KMSkgS8OtY2ggdGjGsOG7m2MgdMOtbmggYuG6sW5nIHPhu5Egbm9kZXMgaGF5IHZlcnRpY2VzOg0KDQpD4bqjIDIgbmV0d29yayDEkeG7gXUgY8OzIGvDrWNoIHRoxrDhu5tjIG5oxrAgbmhhdSBsw6AgMTQgdmVydGljZXMNCg0KYGBge3IgLG1lc3NhZ2UgPSBGQUxTRSx3YXJuaW5nPUZBTFNFfQ0KbmV0d29yay5zaXplKHBvc19uZXQpDQpuZXR3b3JrLnNpemUobmVnX25ldCkNCmBgYA0KDQoyKSBNw7QgdOG6oyBj4bqldSB0csO6YyBi4bqxbmcgaMOgbSBzdW1tYXJ5Og0KDQpOaMOzbSBQb3NpdGl2ZSBjw7MgMzYgZWRnZXMsIDQgZWRnZXMgYXR0cmlidXRlcyBsw6Agciwgc2lnLCBzdHJlbmd0aCB2w6AgZGlyZWN0aW9uIHbDoCAyIHZlcnRleCBhdHJpYnV0ZSBsw6AgdmVydGV4Lm5hbWVzIHbDoCBQaHlzaW8NCg0KYGBge3IsbWVzc2FnZSA9IEZBTFNFLHdhcm5pbmc9RkFMU0V9DQpzdW1tYXJ5KHBvc19uZXQscHJpbnQuYWRqPUZBTFNFKQ0KYGBgDQoNCk5ow7NtIG5lZ2F0aXZlIGPDsyBuaGnhu4F1IGVkZ2VzIGjGoW4gKDQ1KQ0KDQpgYGB7cixtZXNzYWdlID0gRkFMU0Usd2FybmluZz1GQUxTRX0NCnN1bW1hcnkobmVnX25ldCxwcmludC5hZGo9RkFMU0UpDQoNCmBgYA0KDQozKSBN4bqtdCDEkeG7mSBj4bunYSBt4bqhbmcgbMaw4bubaToNCg0KTeG6rXQgxJHhu5kgY+G7p2EgbeG6oW5nIHTGsMahbmcgcXVhbiBjaG8gcGjDqXAgaMOsbmggZHVuZyB24buBIGto4bqjIG7Eg25nIGxpw6puIGvhur90IGdp4buvYSBjw6FjIHBo4bqnbiB04butIHRyb25nIHThuq1wIGjhu6NwICjhu58gxJHDonkgbMOgIGPDoWMgeMOpdCBuZ2hp4buHbSwgdGjDtG5nIHPhu5EgY2jhu6ljIG7Eg25nIHNpbmggbMO9KS4gDQoNCmBgYHtyLG1lc3NhZ2UgPSBGQUxTRSx3YXJuaW5nPUZBTFNFfQ0KIyBkZW5zaXR5DQoNCmdkZW4ocG9zX25ldCkNCmdkZW4obmVnX25ldCkNCmBgYA0KDQpN4bqhbmcgdMawxqFuZyBxdWFuIGPhu6dhIG5ow7NtIG5lZ2F0aXZlIGPDsyBt4bqtdCDEkeG7mSBjYW8gaMahbiBzbyB24bubaSBt4bqhbmcgY+G7p2EgbmjDs20gUG9zaXRpdmUsIHBow7kgaOG7o3AgduG7m2kga2jDoWMgYmnhu4d0IHbhu4Egc+G7kSBj4bq3cCB0xrDGoW5nIHF1YW4gdGEgdGjhuqV5IOG7nyB0csOqbi4gTuG6v3UgZ2nhuqMgxJHhu4tuaCBuaMOzbSBraMO0bmcgY8OzIGLhu4duaCB0aOG7gyBoaeG7h24gdHLhuqFuZyB0aMOhaSBzaW5oIGzDvSBiw6xuaCB0aMaw4budbmcsIHRow6wga+G6v3QgcXXhuqMgbsOgeSBjaG8gdGjhuqV5IGLhu4duaCBsw70gxJHDoyBsw6BtIMSR4bupdCBnw6N5IG5oaeG7gXUgY+G6t3AgdMawxqFuZyBxdWFuIHNpbmggbMO9LCB0aMOtIGThu6UgZ2nhu69hIGNo4bupYyBuxINuZyB0aMO0bmcga2jDrSB2w6AgY2jhu6ljIG7Eg25nIHRyYW8gxJHhu5VpIGtow60uIA0KDQo0KSDEkMaw4budbmcga8OtbmggY+G7p2EgbeG6oW5nIGzGsOG7m2kNCg0KQ+G6oyAyIG3huqFuZyBsxrDhu5tpIMSR4buBdSBjw7MgZGlhbWV0ZXI9MywgxJHDonkgbMOgIGNvbiDEkcaw4budbmcgbuG7kWkgdOG7qyDEkeG6p3UgbsOgeSBzYW5nIMSR4bqndSBraWEgKDIgdmVydGljZXMpIGPhu6dhIG3huqFuZyBsxrDhu5tpIA0KDQpgYGB7cixtZXNzYWdlID0gRkFMU0Usd2FybmluZz1GQUxTRX0NCiMgRGlhbWV0ZXINCg0KZGlhbWV0ZXI9IGZ1bmN0aW9uKG5ldHdvcmspIHsNCiAgY29tcG9uZW50Lmxhcmdlc3QobmV0d29yayxyZXN1bHQ9ImdyYXBoIiklPiVnZW9kaXN0KCktPmdkDQogIG1heChnZCRnZGlzdCkNCiAgfQ0KDQpkaWFtZXRlcihwb3NfbmV0KQ0KZGlhbWV0ZXIobmVnX25ldCkNCmBgYA0KDQo1KSBDaOG7iSBz4buRIFRyYW5zaXRpdml0eTogdOG7iSBs4buHIGdp4buvYSBz4buRIGxpw6puIGvhur90IGLhu5kgYmEga2jDqXAga8OtbiB0csOqbiB04buVbmcgc+G7kSBsacOqbiBr4bq/dCBi4buZIGJhIGjhu58gdsOgIGvDrW4gDQoNCmBgYHtyLG1lc3NhZ2UgPSBGQUxTRSx3YXJuaW5nPUZBTFNFfQ0KZ3RyYW5zKG5lZ19uZXQpDQpndHJhbnMocG9zX25ldCkNCmBgYA0KDQpN4bqhbmcgY+G7p2EgbmjDs20gbmVnYXRpdmUgY8OzIHThu4kgc+G7kSB0cmFuc2l0aXZpdHkgY2FvIGjGoW4sIGNobyB0aOG6pXkgbsOzIGPDsyBuaGnhu4F1IGxpw6puIGvhur90IGLhu5kgYmEgaMahbiBzbyB24bubaSBt4bqhbmcgdMawxqFuZyBxdWFuIGPhu6dhIG5ow7NtIGLhu4duaCBuaMOibg0KDQpMw7pjIG7DoHksIGPhuqNtIG5o4bqtbiBtxqEgaOG7kyBj4bunYSBOaGkgYsOqbiB0csOqbiB24buBIHPhu7Ega2jDoWMgYmnhu4d0IGdp4buvYSAyIG3huqFuZyBsxrDhu5tpIHTGsMahbmcgcXVhbiBj4bunYSBuaMOzbSBC4buHbmggbmjDom4gdsOgIG5nxrDhu51pIGLDrG5oIHRoxrDhu51uZyDEkcOjIMSRxrDhu6NjIGNo4bupbmcgdGjhu7FjbeG7mXQgY8OhY2ggxJHhu4tuaCBsxrDhu6NuZzog4bueIG5ow7NtIE5lZ2F0aXZlIChraMO0bmcgY8OzIGLhu4duaCksIGPDsyBuaGnhu4F1IG3hu5FpIGxpw6puIGjhu4cgaMahbiBnaeG7r2EgY8OhYyBiaeG6v24sIHbDoCBt4buZdCB2w6BpIGxpw6puIGvhur90IHRyb25nIHPhu5EgbsOgeSDEkcOjIGLhu4sgxJHhu6l0IGfDo3kgKGJp4bq/biBt4bqldCkgZG8gdMOhYyDEkeG7mW5nIGPhu6dhIGLhu4duaCBsw70geMahIHBo4buVaS4gDQoNClRhIGPDsyB0aOG7gyBraOG6o28gc8OhdCBt4buZdCBjw6FjaCB0cuG7sWMgcXVhbiBraMOhYyBiaeG7h3QgbsOgeSBi4bqxbmcgY8OhY2ggc28gc8OhbmggaMOsbmgg4bqjbmggMiBt4bqhbmcgbMaw4bubaSB0xrDGoW5nIHF1YW46IEjDrG5oIOG6o25oIG7DoHkgZOG7hSBoaeG7g3UgdsOgIHLDtSByw6BuZyBoxqFuIG5oaeG7gXUgc28gduG7m2kgY29ycmVsb2dyYW0gOiBUYSBjw7MgdGjhu4MgdGjhuqV5IG5o4buvbmcgbGnDqm4ga+G6v3QgxJHDoyBi4buLIG3huqV0IMSRaSBiYW8gZ+G7k206IEdp4buvYSBSViB2w6AgRGxyYXRpbywgZ2nhu69hIFRpZmZuZWF1IHbDoCBLTk8sIEtDTywgZ2nhu69hIFRMQyxGVkMsRkVWMSB2w6AgRG1DTywgLi4uIGjDrG5oIOG6o25oIG7DoHkgcGjDuSBo4bujcCB24bubaSB0aOG7sWMgdOG6vywga2hpIMSRYSBz4buRIGPDoWMgYuG7h25oIG5ow6JuIGPDsyBi4buHbmggbMO9IGjDtCBo4bqlcCDEkeG7gXUgY8OzIHPhu7EgYuG6o28gdOG7k24gY2jhu6ljIG7Eg25nIHRow7RuZyBraMOtIHRyw6puIHjDqXQgbmdoaeG7h20gaMO0IGjhuqVwIGvDvToga+G6v3QgcXXhuqMgeMOpdCBuZ2hp4buHbSBuw6B5IHbhuqtuIGLDrG5oIHRoxrDhu51uZywgbmjGsG5nIHRoYXkgxJHhu5VpIGLhu4duaCBsw73Ekcaw4bujYyB0aOG7gyBoaeG7h24gcmEg4bufIGPDoWMgeMOpdCBuZ2hp4buHbSBraMOhYyBuaMawIMSRbyBkdW5nIHTDrWNoIHBo4buVaSB2w6AvaG/hurdjIFRMQ08uDQoNCmBgYHtyLG1lc3NhZ2UgPSBGQUxTRSx3YXJuaW5nPUZBTFNFfQ0KIyBDb21wYXJhdGl2ZSBwbG90cw0KDQpjb29yZHM8LXBsb3QocG9zX25ldCxsYWJlbD1wb3NfbmV0JXYlInZlcnRleC5uYW1lcyIsbGFiZWwuY2V4PS41LHBhZD0xKQ0KDQpwYXIobWFyPWMoMCwwLDIsMCksbWZyb3c9YygxLDIpKQ0KDQpncGxvdChwb3NfbmV0LGdtb2RlPSJncmFwaCIsDQogICAgICBtb2RlPSJrYW1hZGFrYXdhaSIsDQogICAgICBsYWJlbD1wb3NfbmV0JXYlInZlcnRleC5uYW1lcyIsDQogICAgICBsYWJlbC5jZXg9LjUscGFkPTEsDQogICAgICB2ZXJ0ZXguY29sID0gInJlZCIsDQogICAgICB2ZXJ0ZXguY2V4PTAuOCwNCiAgICAgIGVkZ2UuY29sPSJncmV5NTAiLA0KICAgICAgY29vcmQgPSBjb29yZHMpDQp0aXRsZSgiUGF0aWVudHMiKQ0KDQpncGxvdChuZWdfbmV0LGdtb2RlPSJncmFwaCIsDQogICAgICBtb2RlPSJrYW1hZGFrYXdhaSIsDQogICAgICBsYWJlbD1wb3NfbmV0JXYlInZlcnRleC5uYW1lcyIsDQogICAgICBsYWJlbC5jZXg9LjUscGFkPTEsDQogICAgICB2ZXJ0ZXguY29sID0gImJsdWUiLA0KICAgICAgZWRnZS5jb2w9ImdyZXk1MCIsDQogICAgICB2ZXJ0ZXguY2V4PTAuOCwNCiAgICAgIGNvb3JkID0gY29vcmRzKQ0KdGl0bGUoIk5vcm1hbCIpDQpwYXIobWZyb3c9YygxLDEpKQ0KYGBgDQoNCiMgUGjhuqduIHThu60gKGJp4bq/bikgbsOgbyBjw7MgdmFpIHRyw7IgcXVhbiB0cuG7jW5nIG5o4bqldCA/DQoNCkPDsyBuaGnhu4F1IMO9IGtp4bq/biBraMOhYyBuaGF1IHbhu4EgdOG6p20gcXVhbiB0cuG7jW5nIGPhu6dhIGPDoWMgeMOpdCBuZ2hp4buHbSBzaW5oIGzDvSBow7QgaOG6pXAuIFRow60gZOG7pSwgaGnhu4duIG5heSBow7QgaOG6pXAga8O9IChGRVYxLCBGVkMpIGzDoCB4w6l0IG5naGnhu4dtIHRow7RuZyBk4bulbmcgbmjhuqV0LCB0cm9uZyBraGkgY8OzIHF1YW4gxJFp4buDbSBjaG8gcuG6sW5nIHjDqXQgbmdoaeG7h20gRExDTyBuaOG6oXkgaMahbiDEkeG7gyBwaMOhdCBoaeG7h24gc+G7m20gdOG7lW4gdGjGsMahbmcgY2jhu6ljIG7Eg25nIGPhu6dhIHBo4buVaSB2w6AgY2hvIHBow6lwIHRpw6puIGzGsOG7o25nIG5ndXkgY8ahIHThu60gdm9uZy4uLk3hurd0IGtow6FjLCBu4bq/dSB04buTbiB04bqhaSBt4buRaSB0xrDGoW5nIHF1YW4gZ2nhu69hIDIgdGjDtG5nIHPhu5EgeMOpdCBuZ2hp4buHbSwgc+G6vSBk4bqrbiDEkeG6v24gc3V5IG5naMSpIGzDoCBjaOG7iSBj4bqnbiBz4butIGThu6VuZyAxIHRow7RuZyBz4buRIGzDoCDEkeG7pyBraGFpIHRow6FjIHRow7RuZyB0aW4gPyANCg0KUGjhuqduIHRp4bq/cCB0aGVvLCBOaGkgc+G6vSBkw7luZyBwaMOibiB0w61jaCBt4bqhbmcgbMaw4bubaSDEkeG7gyBuaOG6rW4gZGnhu4duIG5o4buvbmcgYmnhur9uIGPDsyB2YWkgdHLDsiB0cnVuZyB0w6JtLCDEkcOzbmcgZ8OzcCBxdWFuIHRy4buNbmcgdsOgbyB2aeG7h2Mga2nhur9uIHThuqFvIG3huqFuZyBsacOqbiBr4bq/dCB24bubaSBuaOG7r25nIGJp4bq/biBjw7JuIGzhuqFpLCB04burIMSRw7MgdHLhuqMgbOG7nWkgY8OidSBo4buPaTogY2jhu6ljIG7Eg25nIGjDtCBo4bqlcCBuw6BvIGzDoCBxdWFuIHRy4buNbmcgbmjhuqV0IHRyb25nIHRvw6BuIGLhu5kgY8OhYyB4w6l0IG5naGnhu4dtIMSRYW5nIMSRxrDhu6NjIG5naGnDqm4gY+G7qXUgPw0KDQpN4buZdCBwaOG6p24gdOG7rSBjw7MgdmFpIHRyw7IgdHJ1bmcgdMOibSBraGkgbsOzIGPDsyBuaGnhu4F1IGxpw6puIGjhu4cgbmjhuqV0IHbhu5tpIGPDoWMgcGjhuqduIHThu60ga2jDoWMgdHJvbmcgbeG6oW5nIGzGsOG7m2kuIEPDsyBuaGnhu4F1IGNo4buJIHPhu5EgxJHhu4MgxrDhu5tjIGzGsOG7o25nIHTDrW5oIFRydW5nIHTDom0gbsOgeSwgbmjGsDogRGVncmVlLCBCZXR3ZWVuZXNzLCBDbG9zZW5lc3MsIEVpZ2VuIHZlY3RvciwgQm9uYWNpY2ggcG93ZXIsIEluZm9ybWF0aW9uLCBIYXJhcnkgZ3JhcGg6DQoNCmBgYHtyLG1lc3NhZ2UgPSBGQUxTRSx3YXJuaW5nPUZBTFNFfQ0KIyBDZW50cmFsaXR5IG1lYXN1cmVzIGNvbWJpbmVkDQoNCmNlbnRfZGYgPSBkYXRhX2ZyYW1lKFZlcnRleCA9IGMobmVnX25ldCAldiUgInZlcnRleC5uYW1lcyIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHBvc19uZXQgJXYlICJ2ZXJ0ZXgubmFtZXMiKSwNCiAgICAgICAgICAgICAgICAgICAgIFBoeXNpbyA9IGMocmVwKGMoInZlbnQiLCJ2ZW50IiwiVm9sIiwiVm9sIiwiVm9sIiwiVm9sIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIkRpZmYiLCJEaWZmIiwiTWVtYiIsIk1lbWIiLCJNZW1iIiwgICAgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJNZW1iIiwiRGlmZiIsIkNhcCIpLDIpKSwNCiAgICAgICAgICAgICAgICAgICAgIA0KICAgICAgICAgICAgICAgICAgICAgU3RhdHVzID0gYyhyZXAoIk5vcm1hbCIsMTQpLHJlcCgiUGF0aWVudHMiLDE0KSksDQogICAgICAgICAgICAgICAgICAgICAgIA0KICAgICAgICAgICAgICAgICAgICAgRGVncmVlID0gYyhkZWdyZWUocG9zX25ldCwgZ21vZGU9ImdyYXBoIiksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRlZ3JlZShuZWdfbmV0LCBnbW9kZT0iZ3JhcGgiKSksDQogICAgICAgICAgICAgICAgICAgICANCiAgICAgICAgICAgICAgICAgICAgIENsb3NlbmVzcyA9IGMoY2xvc2VuZXNzKHBvc19uZXQsIGdtb2RlPSJncmFwaCIpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjbG9zZW5lc3MobmVnX25ldCwgZ21vZGU9ImdyYXBoIikpLA0KICAgICAgICAgICAgICAgICAgICAgDQogICAgICAgICAgICAgICAgICAgICBCZWV0d2VlbmVzcyA9IGMoYmV0d2Vlbm5lc3MocG9zX25ldCwgZ21vZGU9ImdyYXBoIiksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYmV0d2Vlbm5lc3MobmVnX25ldCwgZ21vZGU9ImdyYXBoIikpLA0KICAgICAgICAgICAgICAgICAgICAgDQogICAgICAgICAgICAgICAgICAgICBFaWdlbiA9IGMoZXZjZW50KHBvc19uZXQsIGdtb2RlPSJncmFwaCIpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGV2Y2VudChuZWdfbmV0LCBnbW9kZT0iZ3JhcGgiKSksDQogICAgICAgICAgICAgICAgICAgICANCiAgICAgICAgICAgICAgICAgICAgIEJvblBvdyA9IGMoYm9ucG93KHBvc19uZXQsIGdtb2RlPSJncmFwaCIpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBib25wb3cobmVnX25ldCwgZ21vZGU9ImdyYXBoIikpLA0KICAgICAgICAgICAgICAgICAgICAgDQogICAgICAgICAgICAgICAgICAgICBJbmZvQ2VudCA9IGMoaW5mb2NlbnQocG9zX25ldCwgZ21vZGU9ImdyYXBoIiksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaW5mb2NlbnQobmVnX25ldCwgZ21vZGU9ImdyYXBoIikpDQogICAgICAgICAgICAgICAgICAgICApDQoNCmBgYA0KDQpDw7MgdGjhu4MgdGjhuqV5LCDhu58gdOG7q25nIHRy4bqhbmcgdGjDoWkga2jDoWMgbmhhdTogQuG7h25oIGzDvS9Cw6xuaCB0aMaw4budbmcsIG3hu5dpIGJp4bq/biBjw7MgdmFpIHRyw7Iga2jDoWMgbmhhdSB0cm9uZyB2aeG7h2Mga2nhur9uIHThuqFvIHJhIG3huqFuZyBsxrDhu5tpIHF1YW4gaOG7hyB24bubaSBjw6FjIGJp4bq/biBjw7JuIGzhuqFpLiANCg0KTeG7mXQgc+G7kSBiaeG6v24gY8OzIHZhaSB0csOyIHRydW5nIHTDom0gbuG7lWkgYuG6rXQsIG5oxrAgRkVWMSwgRlZDICh4w6l0IG5naGnhu4dtIGjDtCBo4bqlcCBrw70pLiBUYSBjxaluZyBjw7MgdGjhu4Mgc28gc8OhbmggdmFpIHRyw7IgdHJ1bmcgdMOibSBnaeG7r2EgY8OhYyBuaMOzbSB4w6l0IG5naGnhu4dtIGNo4bupYyBuxINuZzoNCg0KYGBge3IsbWVzc2FnZSA9IEZBTFNFLHdhcm5pbmc9RkFMU0V9DQpjZW50X2RmJT4lZ2F0aGVyKERlZ3JlZTpJbmZvQ2VudCxrZXk9IlNjb3JlIix2YWx1ZT0iQ2VudHJhbGl0eSIpJT4lDQogIGdncGxvdChhZXMoeD1WZXJ0ZXgseT1DZW50cmFsaXR5LGZpbGw9U3RhdHVzKSkrDQogIGdlb21fcG9pbnQoc3RhdD0iaWRlbnRpdHkiLHNpemU9MixzaGFwZT0yMSxjb2w9ImJsYWNrIikrDQogIHRoZW1lX2J3KCkrY29vcmRfZmxpcCgpKw0KICBmYWNldF93cmFwKH5TY29yZSxuY29sPTMsc2NhbGVzPSJmcmVlIikNCg0KY2VudF9kZiU+JWdhdGhlcihEZWdyZWU6SW5mb0NlbnQsa2V5PSJTY29yZSIsdmFsdWU9IkNlbnRyYWxpdHkiKSU+JQ0KICBnZ3Bsb3QoYWVzKHg9UGh5c2lvLHk9Q2VudHJhbGl0eSxmaWxsPVN0YXR1cykpKw0KICBnZW9tX2JveHBsb3QoYWxwaGE9MC43KSsNCiAgdGhlbWVfYncoKStjb29yZF9mbGlwKCkrDQogIGZhY2V0X3dyYXAoflNjb3JlLG5jb2w9MixzY2FsZXM9ImZyZWUiKQ0KDQpjZW50X2RmJT4lZ2F0aGVyKERlZ3JlZTpJbmZvQ2VudCxrZXk9IlNjb3JlIix2YWx1ZT0iQ2VudHJhbGl0eSIpJT4lDQogIGdncGxvdChhZXMoeD1TdGF0dXMseT1DZW50cmFsaXR5LGZpbGw9UGh5c2lvKSkrDQogIGdlb21fYm94cGxvdChhbHBoYT0wLjcpKw0KICB0aGVtZV9idygpK2Nvb3JkX2ZsaXAoKSsNCiAgZmFjZXRfd3JhcCh+U2NvcmUsbmNvbD0yLHNjYWxlcz0iZnJlZSIpDQpgYGANCg0KVGEgY8OybiBjw7MgdGjhu4MgcGjDoXQgaGnhu4duIHJhIHBo4bqnbiB04butIHRoZW4gY2jhu5F0ICJjdXQtcG9pbnQiICwgY8OzIG5naMSpYSBsw6Aga2hpIGxv4bqhaSBi4buPIHBo4bqnbiB04butIG7DoHksIGPhuqV1IHRyw7pjIG3huqFuZyBsxrDhu5tpIHPhur0gYuG7iyBwaMOibiByw6MsIGNoaWEgY+G6r3QgdGjDoG5oIG5oaeG7gXUgY+G7pW0uIFRyb25nIHRyxrDhu51uZyBo4bujcCBuw6B5LCBjw7MgMSBjdXRwb2ludCBjaMOtbmggbMOgIGJp4bq/biBEbU5PLiBO4bq/dSBsb+G6oWkgYuG7jyBEbU5PLCAyIGJp4bq/biBEbUNPIHbDoCBWY2FwIHPhur0gYuG7iyB0w6FjaCBy4budaSByYSBraOG7j2kgbeG6oW5nIGzGsOG7m2kuDQoNCmBgYHtyLG1lc3NhZ2UgPSBGQUxTRSx3YXJuaW5nPUZBTFNFfQ0KIyBDdXRwb2ludA0KDQpjcF9wb3MgPC0gY3V0cG9pbnRzKHBvc19uZXQsbW9kZT0iZ3JhcGgiLA0KICAgICAgICAgICAgICAgICAgIHJldHVybi5pbmRpY2F0b3I9VFJVRSkNCg0KZ3Bsb3QocG9zX25ldCxnbW9kZT0iZ3JhcGgiLA0KICAgICAgbGFiZWw9cG9zX25ldCV2JSJ2ZXJ0ZXgubmFtZXMiLA0KICAgICAgbGFiZWwuY2V4PS44LHBhZD0xLA0KICAgICAgdmVydGV4LmNvbD1jcF9wb3MrMSxjb29yZD1jb29yZHMsDQogICAgICBqaXR0ZXI9RkFMU0UsZGlzcGxheWxhYmVscz1UUlVFKQ0KYGBgDQoNCiMgWeG6v3UgdOG7kSBt4bu5IHRodeG6rXQgY+G7p2EgbeG6oW5nIHTGsMahbmcgcXVhbg0KDQpUcsaw4bubYyBraGkga+G6v3QgdGjDumMsIGNow7puZyB0YSBz4bq9IGzDoG0gbeG7mXQgc+G7kSB0aOG7rSBuZ2hp4buHbSB0aGF5IMSR4buVaSB0w7l5IGNo4buJbmggbeG7uSB0aHXhuq10IGNobyBiaeG7g3UgxJHhu5MgbeG6oW5nIHTGsMahbmcgcXVhbjoNCg0KQ8OzIG5oaeG7gXUgaMOsbmggdGjhu6ljIHRyw6xuaCBiw6B5IG3huqFuZyBsxrDhu5tpIHTGsMahbmcgcXVhbiwgc2F1IMSRw6J5IGzDoCA2IGtp4buDdSB0aMO0bmcgZOG7pW5nIG5o4bqldDoNCg0KYGBge3IsbWVzc2FnZSA9IEZBTFNFLHdhcm5pbmc9RkFMU0V9DQpvcCA8LSBwYXIobWFyPWMoMCwwLDIsMCksbWZyb3c9YygyLDMpKQ0KZ3Bsb3QocG9zX25ldCxnbW9kZT0iZ3JhcGgiLG1vZGU9ImNpcmNsZSIsZWRnZS5jb2w9ImdyZXkiLHZlcnRleC5jb2wgPSAicmVkIiwNCiAgICAgIHZlcnRleC5jZXg9MSxtYWluPSJDaXJjbGUiKQ0KZ3Bsb3QocG9zX25ldCxnbW9kZT0iZ3JhcGgiLG1vZGU9InJhbmRvbSIsZWRnZS5jb2w9ImdyZXkiLHZlcnRleC5jb2wgPSAiZ29sZCIsDQogICAgICB2ZXJ0ZXguY2V4PTEsbWFpbj0iUmFuZG9tIGxheW91dCIpDQpncGxvdChwb3NfbmV0LGdtb2RlPSJncmFwaCIsbW9kZT0iZnJ1Y2h0ZXJtYW5yZWluZ29sZCIsZWRnZS5jb2w9ImdyZXkiLHZlcnRleC5jb2wgPSAzLA0KICAgICAgdmVydGV4LmNleD0xLG1haW49IkZydWNodGVybWFuLVJlaW5nb2xkIikNCmdwbG90KHBvc19uZXQsZ21vZGU9ImdyYXBoIixtb2RlPSJzcHJpbmciLGVkZ2UuY29sPSJncmV5Iix2ZXJ0ZXguY29sID0gNCwNCiAgICAgIHZlcnRleC5jZXg9MSxtYWluPSJTcHJpbmciKQ0KZ3Bsb3QocG9zX25ldCxnbW9kZT0iZ3JhcGgiLG1vZGU9ImVpZ2VuIixlZGdlLmNvbD0iZ3JleSIsdmVydGV4LmNvbCA9IDYsDQogICAgICB2ZXJ0ZXguY2V4PTEsbWFpbj0iRWlnZW5zdHJ1Y3R1cmUiKQ0KZ3Bsb3QocG9zX25ldCxnbW9kZT0iZ3JhcGgiLG1vZGU9ImthbWFkYWthd2FpIixlZGdlLmNvbD0iZ3JleSIsdmVydGV4LmNvbCA9ICJibGFjayIsDQogICAgICB2ZXJ0ZXguY2V4PTEsbWFpbj0iS2FtYWRha2F3YWkiKQ0KcGFyKG9wKQ0KYGBgDQoNClRhIGPDsyB0aOG7gyB0aOG6pXkgcuG6sW5nIDMga2nhu4N1IHRyw6xuaCBiw6B5OiBt4bqhbmcgbMaw4bubaSB2w7JuZyAoQ2lyY2xlKSwgS2FtYWRha2F3YWkgdsOgIEZydWNodGVybWFuLVJlaW5nb2xkIGzDoCByw7UgcsOgbmcgdsOgIMSR4bq5cCBuaOG6pXQuDQoNClRhIGPDsyB0aOG7gyB0w7l5IGNo4buJbmgga8OtY2ggdGjGsOG7m2MgdsOgIG3DoHUgdmVydGljZXMgdGhlbyBhdHRyaWJ1dGUgY+G7p2EgbsOzOiB0aMOtIGThu6UgMyBjaOG7iSBz4buRIERncmVlLCBDbG9zZW5lc3MgdsOgIEJldHdlZW5lc3M6DQoNCmBgYHtyLG1lc3NhZ2UgPSBGQUxTRSx3YXJuaW5nPUZBTFNFfQ0KZGVnIDwtIGRlZ3JlZShwb3NfbmV0LGdtb2RlPSJncmFwaCIpDQpjbHMgPC0gY2xvc2VuZXNzKHBvc19uZXQsZ21vZGU9ImdyYXBoIikNCmJldCA8LSBiZXR3ZWVubmVzcyhwb3NfbmV0LGdtb2RlPSJncmFwaCIpDQoNCmdwbG90KHBvc19uZXQsIA0KICAgICAgdmVydGV4LmNleD1sb2coZGVnKSwgDQogICAgICBsYWJlbC5jZXg9MC44LA0KICAgICAgbGFiZWw9cG9zX25ldCV2JSJ2ZXJ0ZXgubmFtZXMiLA0KICAgICAgdmVydGV4LmNvbD1yZ2IoZGVnL21heChkZWcpLDAuMiwxLWRlZy9tYXgoZGVnKSksDQogICAgICBnbW9kZT0iZ3JhcGgiLA0KICAgICAgY29vcmQ9Y29vcmRzKQ0KYGBgDQoNCmBgYHtyLG1lc3NhZ2UgPSBGQUxTRSx3YXJuaW5nPUZBTFNFfQ0KZ3Bsb3QocG9zX25ldCwgDQogICAgICB2ZXJ0ZXguY2V4PTMqY2xzLCANCiAgICAgIGxhYmVsLmNleD0wLjgsDQogICAgICBsYWJlbD1wb3NfbmV0JXYlInZlcnRleC5uYW1lcyIsDQogICAgICB2ZXJ0ZXguY29sPXJnYihjbHMvbWF4KGNscyksMC4zLDEuMi1jbHMvbWF4KGNscykpLA0KICAgICAgZ21vZGU9ImdyYXBoIiwNCiAgICAgIGNvb3JkPWNvb3JkcykNCmBgYA0KDQpgYGB7cixtZXNzYWdlID0gRkFMU0Usd2FybmluZz1GQUxTRX0NCmdwbG90KHBvc19uZXQsIA0KICAgICAgdmVydGV4LmNleD1zcXJ0KGJldCsxKSwgDQogICAgICBsYWJlbC5jZXg9MC44LA0KICAgICAgbGFiZWw9cG9zX25ldCV2JSJ2ZXJ0ZXgubmFtZXMiLA0KICAgICAgdmVydGV4LmNvbD1yZ2IoYmV0L21heChiZXQpLDEtYmV0L21heChiZXQpLDAuMSksDQogICAgICBnbW9kZT0iZ3JhcGgiLA0KICAgICAgY29vcmQ9Y29vcmRzKQ0KYGBgDQoNClTGsMahbmcgdOG7sSwgdGEgY8OzIHRo4buDIHTDuXkgY2jhu4luaCBtw6B1IHPhuq9jLCBkw6FuIG5ow6NuIGNobyBjw6FjIGVkZ2VzOg0KDQpgYGB7cixtZXNzYWdlID0gRkFMU0Usd2FybmluZz1GQUxTRX0NCmVkZ2VfZGlyIDwtIGFzLmZhY3Rvcihwb3NfbmV0JWUlIkRpcmVjdGlvbiIpDQplZGdlX2Rpcl9wYWwgPC0gYygiYmx1ZSIsInJlZCIpDQoNCnBsb3QocG9zX25ldCxsYWJlbD1wb3NfbmV0JXYlInZlcnRleC5uYW1lcyIsDQogICAgIGRpc3BsYXlsYWJlbHM9VCwNCiAgICAgbGFiZWwuY2V4PTAuOCwNCiAgICAgdmVydGV4LmNleD0zKmNscywNCiAgICAgdmVydGV4LmNvbD1yZ2IoY2xzL21heChjbHMpLDAuMywxLjItY2xzL21heChjbHMpKSwNCiAgICAgZWRnZS5jb2w9ZWRnZV9kaXJfcGFsW2VkZ2VfZGlyXSxlZGdlLmx3ZD0xLA0KICAgICBlZGdlLmxhYmVsPWVkZ2VfZGlyLGVkZ2UubGFiZWwuY2V4PTAuNSwNCiAgICAgZWRnZS5sYWJlbC5jb2w9ZWRnZV9kaXJfcGFsW2VkZ2VfZGlyXSwNCiAgICAgbW9kZT0ia2FtYWRha2F3YWkiKQ0KYGBgDQoNCmBgYHtyLG1lc3NhZ2UgPSBGQUxTRSx3YXJuaW5nPUZBTFNFfQ0KZWRnZV92YWwgPC0gYXMuZmFjdG9yKHBvc19uZXQlZSUiciIpDQplZGdlX3ZhbF9wYWwgPC0gcGFsczo6Y29vbHdhcm0obGVuZ3RoKGVkZ2VfdmFsKSkNCg0KcGxvdChwb3NfbmV0LGxhYmVsPXBvc19uZXQldiUidmVydGV4Lm5hbWVzIiwNCiAgICAgZGlzcGxheWxhYmVscz1ULA0KICAgICB2ZXJ0ZXguY2V4PTIqY2xzLA0KICAgICB2ZXJ0ZXguY29sPXJnYihjbHMvbWF4KGNscyksMC4zLDEuMi1jbHMvbWF4KGNscykpLA0KICAgICBsYWJlbC5jZXg9MC41LA0KICAgICBlZGdlLmNvbD1lZGdlX3ZhbF9wYWxbZWRnZV92YWxdLGVkZ2UubHdkPTEsDQogICAgIGVkZ2UubGFiZWw9ZWRnZV92YWwsDQogICAgIGVkZ2UubGFiZWwuY2V4PTAuNSwNCiAgICAgZWRnZS5sYWJlbC5jb2w9ZWRnZV92YWxfcGFsW2VkZ2VfdmFsXSwNCiAgICAgbW9kZT0ia2FtYWRha2F3YWkiKQ0KYGBgDQoNCmBgYHtyLG1lc3NhZ2UgPSBGQUxTRSx3YXJuaW5nPUZBTFNFfQ0KDQplZGdlX3ZhbF9wb3MgPC0gYXMuZmFjdG9yKHBvc19uZXQlZSUiciIpDQplZGdlX3ZhbF9wb3NfcGFsIDwtIHBhbHM6OmNvb2x3YXJtKGxlbmd0aChlZGdlX3ZhbF9wb3MpKQ0KDQplZGdlX3ZhbF9uZWcgPC0gYXMuZmFjdG9yKG5lZ19uZXQlZSUiciIpDQplZGdlX3ZhbF9uZWdfcGFsIDwtIHBhbHM6OmNvb2x3YXJtKGxlbmd0aChlZGdlX3ZhbF9uZWcpKQ0KDQpkZWdfcG9zIDwtIGRlZ3JlZShwb3NfbmV0LGdtb2RlPSJncmFwaCIpDQpkZWdfbmVnIDwtIGRlZ3JlZShuZWdfbmV0LGdtb2RlPSJncmFwaCIpDQoNCnBhcihtYXI9YygwLDAsMiwwKSxtZnJvdz1jKDEsMikpDQoNCnBsb3QocG9zX25ldCxnbW9kZT0iZ3JhcGgiLA0KICAgICAgbW9kZT0ia2FtYWRha2F3YWkiLA0KICAgICAgbGFiZWw9cG9zX25ldCV2JSJ2ZXJ0ZXgubmFtZXMiLA0KICAgICAgbGFiZWwuY2V4PS41LHBhZD0xLA0KICAgICAgdmVydGV4LmNvbD1yZ2IoZGVnX3Bvcy9tYXgoZGVnX3BvcyksMC4yLDEtZGVnX3Bvcy9tYXgoZGVnX3BvcykpLA0KICAgICAgdmVydGV4LmNleD1sb2coZGVnX3BvcykqMiwgDQogICAgICBlZGdlLmNvbD1lZGdlX3ZhbF9wb3NfcGFsW2VkZ2VfdmFsX3Bvc10sZWRnZS5sd2Q9MSwNCiAgICAgIGVkZ2UubGFiZWw9ZWRnZV92YWxfcG9zLA0KICAgICAgZWRnZS5sYWJlbC5jZXg9MC41LA0KICAgICAgZWRnZS5sYWJlbC5jb2w9ZWRnZV92YWxfcG9zX3BhbFtlZGdlX3ZhbF9wb3NdLA0KICAgICAgY29vcmQgPSBjb29yZHMpDQp0aXRsZSgiUGF0aWVudHMiKQ0KDQpwbG90KG5lZ19uZXQsZ21vZGU9ImdyYXBoIiwNCiAgICAgIG1vZGU9ImthbWFkYWthd2FpIiwNCiAgICAgIGxhYmVsPXBvc19uZXQldiUidmVydGV4Lm5hbWVzIiwNCiAgICAgIGxhYmVsLmNleD0uNSxwYWQ9MSwNCiAgICAgIHZlcnRleC5jb2w9cmdiKGRlZ19uZWcvbWF4KGRlZ19uZWcpLDAuMiwxLWRlZ19uZWcvbWF4KGRlZ19uZWcpKSwNCiAgICAgIHZlcnRleC5jZXg9bG9nKGRlZ19uZWcpKjIsIA0KICAgICAgZWRnZS5jb2w9ZWRnZV92YWxfbmVnX3BhbFtlZGdlX3ZhbF9uZWddLGVkZ2UubHdkPTEsDQogICAgICBlZGdlLmxhYmVsPWVkZ2VfdmFsX25lZywNCiAgICAgIGVkZ2UubGFiZWwuY2V4PTAuNSwNCiAgICAgIGVkZ2UubGFiZWwuY29sPWVkZ2VfdmFsX25lZ19wYWxbZWRnZV92YWxfbmVnXSwNCiAgICAgIGNvb3JkID0gY29vcmRzKQ0KdGl0bGUoIk5vcm1hbCIpDQpwYXIobWZyb3c9YygxLDEpKQ0KYGBgDQoNCg0KIyBU4buVbmcga+G6v3QNCg0KQsOgaSB0aOG7sWMgaMOgbmggxJHhur9uIMSRw6J5IGzDoCBo4bq/dC4gVHJvbmcgYsOgaSBuw6B5LCBOaGkgxJHDoyBr4bq/dCBo4bujcCBnaeG7r2EgcGjDom4gdMOtY2ggdMawxqFuZyBxdWFuIGPhu5UgxJFp4buDbiB2w6AgbeG7mXQgcGjGsMahbmcgcGjDoXAgdGjhu5FuZyBrw6ogaGnhu4duIMSR4bqhaSBsw6AgTmV0d29yayBhbmFseXNpcy4gS+G6v3QgcXXhuqMgbMOgIHRhIGPDsyB0aOG7gyBraGFpIHRow6FjIG5oaeG7gXUgdGjDtG5nIHRpbiBoxqFuIHThu6sgbWEgdHLhuq1uIHTGsMahbmcgcXVhbiwga2jhuqNvIHPDoXQgbeG7mXQgY8OhY2ggxJHhu4tuaCBsxrDhu6NuZyBj4bqldSB0csO6YyBj4bunYSBt4buRaSBxdWFuIGjhu4cgxJFhIGNoaeG7gXUgZ2nhu69hIGPDoWMgYmnhur9uLCBzbyBzw6FuaCAyIG3huqFuZyBsxrDhu5tpIHTGsMahbmcgcXVhbiBk4buxYSB2w6BvIGPDoWMgY2jhu4kgc+G7kSB24buBIG3huq10IMSR4buZIHbDoCB2YWkgdHLDsiBj4bunYSBt4buXaSBiaeG6v24gdHJvbmcgdmnhu4djIGtp4bq/biB04bqhbyBuw6puIG3huqFuZyBsxrDhu5tpIHF1YW4gaOG7hyBnaeG7r2EgY2jDum5nLg==