Đặt vấn đề
Trong bài hôm nay, một lần nữa Nhi quay lại với chủ đề diễn giải mô hình. Thực ra không phải chờ đến thời đại hiện nay người ta mới quan tâm đến vai trò quan trọng của mô hình thống kê trong nghiên cứu Y học. Đã từ rắt lâu, khi phân tích dữ liệu bằng thống kê cổ điển, nghiên cứu sinh đã luôn vận dụng mô hình (ngay cả khi họ không ý thức về điều này), mặc dù chỉ có 1 loại mô hình duy nhất là hồi quy tuyến tính.
Như nhận xét của Georges Box : « tất cả mô hình đều sai, nhưng đôi khi chúng hữu dụng », tuy mô hình tuyến tính có nội dung rất đơn giản, nhưng nó vừa đủ để thỏa mãn hầu hết mục tiêu nghiên cứu. Thật vậy, trong hàng chục năm qua, rất nhiều kiến thức y học – bao gồm những quy luật sinh lý-bệnh học, quyết định điều trị và chẩn đoán đã được xác định dựa vào những mô hình tuyến tính đơn giản này.
Sơ đồ sau đây tóm tắt quy trình suy diễn thống kê dựa vào mô hình heo cách làm việc cũ:
Toàn bộ dữ liệu thực nghiệm được dùng để tạo ra mô hình thống kê. Mô hình này có bản chất là sự phản ánh một giả thuyết chủ quan, đơn giản của nhà nghiên cứu. Đây là sự giản lược, thu nhỏ thế giới phức tạp trên thực tế thành một quy luật đơn giản và tương đối chính xác, cho phép trả lời một số câu hỏi nhất định. Phẩm chất của mô hình thường được kiểm chứng trên chính dữ liệu gốc bằng một vài công cụ thống kê đơn giản như LR test, R2, BIC hay AIC… Sau đó việc suy diễn thống kê (thí dụ : so sánh, tương quan, liên hệ, nguy cơ…) được làm ở cấp độ quần thể, trực tiếp dựa vào các hệ số hồi quy trong mô hình. Kết quả suy diễn thống kê được kiểm chứng bằng phản nghiệm giả thuyết vô hiệu, và nếu có ý nghĩa, chúng sẽ được xem như tri thức mới, và quy trình dừng lại ở đó. Hiếm khi mô hình được áp dụng cho mục tiêu thực hành, thí dụ tiên lượng nguy cơ, phân loại chẩn đoán hoặc ước lượng trị số tham chiếu.
Gần đây, với sự phát triển thần tốc của trường phái Machine learning (thực ra: nó đã âm thầm song hành với thống kê quy ước từ thập niên 80), có nhiều ý kiến ủng hộ cho một sự hợp nhất giữa Thống kê cổ điển, Machine learning và khoa học máy tính, để cả ba có thể cùng tham gia hỗ trợ một cách hiệu quả nhất cho các nhà khoa học trong việc khai thác thông tin, kiến thức từ dữ liệu, cũng như những ứng dụng thiết thực và ở cấp độ cá thể.
Sự hợp nhất này không chỉ liên quan đến yếu tố công cụ (du nhập những algorithm mới lạ như Random Forest, SVM, Neural network… vào nghiên cứu khoa học), nhưng còn có thể dẫn đến sự thay đổi mang tính cách mạng về cách suy nghĩ, quy trình làm việc. Một số ý tưởng mới là hệ quả của sự giao thoa giữa 3 chuyên ngành khoa học dữ liệu được tóm tắt trong sơ đồ sau :
Nội dung những ý tưởng này bao gồm :
Không chỉ có mô hình tuyến tính, nhưng bất kì một mô hình nào, bao gồm những mô hình hộp đen (blackboxes) đều có thể sử dụng trong nghiên cứu khoa học, cho cả bài toán hồi quy (ước lượng giá trị biến liên tục) hoặc phân loại.
Những mô hình này có thể được ứng dụng cho nhiều mục tiêu khác nhau , bao gồm : Tiên lượng (phân loại, ước tính và đưa ra quyết định), kiểm tra phẩm chất của mô hình trên thực tế, suy diễn thống kê ở mức độ quần thể hay cá thể nhằm cung cấp kiến thức mới, kiểm tra giả thuyết nghiên cứu và giải thích cơ chế của mô hình.
Quy trình xây dựng mô hình sẽ áp dụng một số quy tắc mới, thí dụ : không sử dụng toàn bộ dữ liệu gốc cho việc tạo ra mô hình, nhưng chuẩn bị riêng các bộ dữ liệu kiểm định độc lập. Một số phương pháp kiểm chứng chéo, tái chọn mẫu (bootstrap) phải được áp dụng. Phẩm chất mô hình sẽ được kiểm tra chặt chẽ hơn bằng nhiều tiêu chí khác nhau trên dữ liệu độc lập trước khi suy diễn thống kê hoặc áp dụng vào thực hành.
Việc sử dụng các giải thuật (algorithm) phức tạp khác ngoài mô hình tuyến tính có thể mang lại nhiều lợi ích, như giảm nhẹ công sức của con người trong việc đặt giả thuyết, chọn lọc biến thủ công …, mô hình không phụ thuộc vào các giả định về phân bố nhưng có khả năng phát hiện được những hiệu ứng tương tác, quy luật phi tuyến tính/cục bộ, có thể tự chọn lọc biến, chấp nhận dữ liệu thiếu sót… và nhất là tính chính xác cao hơn.
Tuy nhiên, để có thể thực hiện 4 ý tưởng kể trên cho những nghiên cứu thuộc loại Suy diễn / Diễn dịch, nhà nghiên cứu bắt buộc phải hiểu được mô hình mình đang sử dụng. Việc « Hiểu » này đặt ra nhiều câu hỏi thú vị như :
Mô hình này chính xác đến đâu ? Có đáng tin cậy hay không ? Câu hỏi này dẫn đến việc kiểm định phẩm chất mô hình, không chỉ 1 lần trong khi làm nghiên cứu mà trong suốt quá trình lâu dài khi mà mô hình đó vẫn còn được ứng dụng trong thực hành lâm sàng (đối với mục tiêu tiên lượng) hoặc tồn tại trong thư viện y văn (đối với mô hình lý thuyết) .
Mô hình hoạt động như thế nào ? Nó đã học được gì từ dữ liệu ? Đây là câu hỏi rất quan trọng làm nền tảng cho việc diễn giải nội dung/cơ chế của mô hình. Giải đáp được câu hỏi này cho phép rút ra hàng loạt thông tin, suy diễn quan trọng, hữu ích bao gồm: Vai trò của mỗi biến ? Mối liên hệ bộ phận giữa mỗi biến và kết quả là gì ? (Ghi chú: nếu kết quả là một chẩn đoán / tiên lượng – câu hỏi này cho phép xác định yếu tố nguy cơ, nếu kết quả là một đại lượng hay con số - câu hỏi trên cho phép so sánh, phân cụm hoặc phân tích tương quan bộ phận). Kết quả của việc giải thích cơ chế này còn có thể được sử dụng để quay ngược lại cải thiện mô hình bằng cách bỏ bớt những biến không quan trọng, tinh chỉnh tham số của algorithm, thêm dữ liệu mới
Giải thích được cơ chế của mô hình ở cấp độ cá thể: Mô hình hoạt động có chính xác không cho trường hợp này ? Tại sao kết quả lại như vậy ? Biến nào có vai trò/ ảnh hưởng quan trọng nhất ở cá thể này ? v.v Trong bài trước Nhi đã bàn luận về những lợi ích của việc diễn giải mô hình cho từng cá thể, các bạn có thể đọc thêm ở đây:
Giới thiệu DALEX: một giải pháp mới
Trong bài thực hành hôm nay, Nhi sẽ giới thiệu với các bạn một công cụ mới – mà theo đánh giá của Nhi – là một giải pháp hữu hiệu cho phần lớn (nếu không muốn nói là tất cả ) những vấn đề nêu trên. Đó là package DALEX của tác giả Przemyslaw Biecek, mới công bố trên CRAN vào giữa tháng 6 năm 2018 vừa qua.
DALEX là tên rút gọn của « Descriptive mAchine Learning EXplanations ». Tác giả Biecek đã đi xa hơn bất cứ người nào khác trong việc diễn giải nội dung mô hình, với 3 ý tưởng độc đáo:
Đưa ra một quy trình giải nghĩa phổ quát cho mọi mô hình, bất kể bản chất của algorithm và mục tiêu nghiên cứu (hồi quy/tiên lượng, phân loại hay suy diễn) . DALEX tương thích với cả 3 giao thức Machine learning phổ biến hiện nay gồm caret, mlr và h2o, cũng như từng algorithm đơn lẻ khác như e1071, rf
Cho phép trình bày trực quan kết quả diễn giải của hàng loạt mô hình. Như vậy một công dụng khác của DALEX là so sánh và chọn lọc mô hình/algorithm.
Giải đáp hầu hết câu hỏi quan trọng để “hiểu” mô hình, bao gồm: Nội dung và cơ chế hoạt động : Tầm quan trọng của các biến, Quan hệ riêng phần của từng biến đối với kết quả (đặc biệt hữu ích cho bài toán hồi quy), độ chính xác của mô hình và diễn giải cho từng cá thể (theo phương pháp breakdown).
Cấu trúc của DALEX
Khi sử dụng DALEX, chỉ có ba bước cần nhớ trong một quy trình chung cho mọi mô hình :
- Đầu tiên, ta sẽ dùng hàm explain trên mô hình để tạo ra một metadata object cần thiết cho những bước diễn giải tiếp theo.
Hàm explain có cú pháp : explain(model, data, y, predict_function, link, …, label)
Các tùy chỉnh bao gồm:
model là object kết quả mô hình (DALEX tương thích với object kết quả của caret, mlr, h2o).
data là một bộ dữ liệu có cấu trúc giống như dữ liệu dùng để dựng mô hình nhưng không gồm biến kết quả. Có thể dùng dữ liệu gốc (trainset) cho mục tiêu giải nghĩa đơn thuần, hoặc dữ liệu mới (testset) cho mục tiêu kiểm định. Phần data này cần cho các quy trình: kiểm định (model performance), khảo sát vai trò các biến (variable importance)
y là vector biến kết quả từ tập dữ liệu data nói trên, cần để đánh giá variable importance
predict_function : là 1 hàm do người dùng quy định, cho phép trả kết quả là con số (outcome định lượng cho bài toán hồi quy, hay probability của 1 nhãn trong bài toán phân loại). Nếu không được xác định, hàm predict mặc định trong R sẽ được dùng, việc tùy chỉnh 1 hàm predict riêng là bắt buộc khi mô hình dựng bằng package h2o, cũng như các mô hình binary , multilabel classification…
link function cho phép tùy chỉnh 1 hàm hoán chuyển kết quả , để chuyển kết quả prediction của mô hình về thang đo và đơn vị nguyên thủy của outcome. Mặc định dùng hàm identity
label : dùng để đặt cho mỗi mô hình một tên gọi ngắn gọn, tên gọi này sẽ được dùng khi vẽ biểu đồ
Từ object này, ta có thể áp dụng tiếp một số hàm chuyên biệt nhằm trả lời cho từng câu hỏi nhất định, thí dụ đánh giá vai trò của các biến, quan hệ phụ thuộc riêng phần, giải thích cho cá thể, …
Sau cùng, kết quả từ bước 2 có thể được trình bày trực quan bằng biểu đồ với hàm plot( ). Đặc biệt kết quả diễn giải của nhiều mô hình có thể kết hợp với nhau trong hàm plot cho mục tiêu so sánh.
Diễn giải quan hệ bộ phận trong mô hình (1)
Đầu tiên, Ta sẽ dùng một dữ liệu mô phỏng để kiểm tra tính năng khảo sát quan hệ phụ thuộc riêng phần giữa kết quả Y là biến liên tục và những biến số Xi khác trong mô hình.
library(tidyverse)
fsim=function(X1,X2,X3,X4,X5){
(0.6*X1-0.2)^2+
sin(8*X2)+cos(5*X2)+
X3^0.5+
exp(X4)/(exp(X4)+1)+
log2(X5)+2
}
print(fsim)
## function(X1,X2,X3,X4,X5){
## (0.6*X1-0.2)^2+
## sin(8*X2)+cos(5*X2)+
## X3^0.5+
## exp(X4)/(exp(X4)+1)+
## log2(X5)+2
## }
N=300
set.seed(12345)
X1=runif(N)
X2=runif(N)
X3=runif(N)
X4=runif(N)
X5=runif(N)
Y=fsim(X1,X2,X3,X4,X5)
sim_df=data_frame(X1,X2,X3,X4,X5,Y)
head(sim_df)%>%knitr::kable()
0.7209039 |
0.0526883 |
0.6992631 |
0.7621013 |
0.0508540 |
0.6492452 |
0.8757732 |
0.5986573 |
0.0104070 |
0.1196780 |
0.8032334 |
0.4356465 |
0.7609823 |
0.7082906 |
0.5039071 |
0.8480882 |
0.5837758 |
1.1997625 |
0.8861246 |
0.6534407 |
0.7310592 |
0.3288868 |
0.5854832 |
0.9118694 |
0.4564810 |
0.5774140 |
0.3297780 |
0.2841243 |
0.3210260 |
-0.4524134 |
0.1663718 |
0.6782855 |
0.8204991 |
0.0524973 |
0.9584395 |
1.6429598 |
Do dữ liệu là mô phỏng nên ta có thể chủ động xác định trước bản chất quan hệ phụ thuộc riêng phần này bằng các hàm khác nhau
sim_df%>%gather(X1:X5,key="Predictors",value="Value")%>%
ggplot(aes(x=Value,y=Y,col=Predictors,fill=Predictors))+
geom_smooth(alpha=0.2)+
facet_wrap(~Predictors,ncol=3,scales="free")+
theme_bw()
## `geom_smooth()` using method = 'loess'

Bây giờ giả định mục tiêu của chúng ta là xây dựng một mô hình hồi quy cho phép ước lượng Y từ X1,X2,X3,X4 và X5. Trong thống kê cổ điển, ta chỉ có thể dùng 1 công cụ là mô hình tuyến tính. Nhưng với Machine learning, ta có nhiều, thậm chí rất nhiều công cụ thay thế.
Trong thí dụ này, Nhi sẽ dựng 4 mô hình hồi quy khác nhau: 1 mô hình tuyến tính glm, một mô hình Random Forest, một mô hình Support vector machine (e1071) và 1 mô hình Gradient boosting GBM, sử dụng caret
library(randomForest)
library(e1071)
library(DALEX)
model_rf <- caret::train(Y~.,data= sim_df,method="rf",verbose=F)
model_svm <- svm(Y ~ ., sim_df)
model_gbm <- caret::train(Y~. , data = sim_df, method="gbm",verbose=F)
model_lm <- glm(Y ~ ., sim_df,family="gaussian")
Tiếp theo, giả định câu hỏi nghiên cứu của chúng ta là xác định mối liên hệ bộ phận giữa kết quả Y và mỗi predictors trong 4 mô hình trên: Trước hết ta dùng hàm explain của DALEX để tạo ra 4 objects metadata.
Ngoài ra, ta tạo thêm object thứ 5 chứa mô hình tuyến tính đã được dùng để mô phỏng dữ liệu ban đầu (hàm fsim). Mô hình này chắc chắn sẽ cho ra kết quả gần với sự thực nhất nên được xem như mốc tham chiếu
ex_rf <- explain(model_rf,data=sim_df[,-6],y=sim_df$Y,label="RandomForest")
ex_svm <- explain(model_svm,data=sim_df[,-6],y=sim_df$Y,label="SVM")
ex_lm <- explain(model_lm,data=sim_df[,-6],y=sim_df$Y,label="Linear")
ex_gbm <- explain(model_gbm,data=sim_df[,-6],y=sim_df$Y,label="GBM")
ex_tr <- explain(model_lm,data = sim_df[,-6], y=sim_df$Y,
predict_function = function(m, x) fsim(x[,1], x[,2], x[,3], x[,4], x[,5]), label = "True Model")
Để khảo sát liên hệ phụ thuộc riêng phần giữa mỗi predictor và kết quả, ta có thể sử dụng một trong 2 hàm : single_variable( ) hoặc variable_response().
Hai phương pháp có thể được chọn:
tùy chỉnh type=”pdp” sẽ áp dụng « Partial Dependence Plot» của Greenwell (2017 : The R Journal 9(1):421–36.https://journal.r-project.org/archive/2017/RJ-2017-016/index.html) ,
tùy chỉnh type=”ale” sẽ áp dụng phương pháp “Accumulated Local Effects” của Apley (2017: ALEPlot: Accumulated Local Effects (Ale) Plots and Partial Dependence (Pd) Plots. https://CRAN.R-project.org/package=ALEPlot)
Phương pháp ALE chính xác hơn trong trường hợp có hiện tượng đa cộng tuyến. Các biểu đồ này có ý nghĩa tương tự như “Marginalized effect plot” dành cho mô hình hồi quy, khảo sát quan hệ riêng phần giữa predictor và outcome đều là biến liên tục.
Nhi áp dụng hàng loạt hàm single_variable và kết nối chúng với nhau trong hàm plot cho từng predictor để so sánh kết quả phân tích giữa 5 mô hình
plot(single_variable(ex_rf, "X1"),
single_variable(ex_svm, "X1"),
single_variable(ex_lm, "X1"),
single_variable(ex_gbm, "X1"),
single_variable(ex_tr, "X1")
) +theme_bw() + ggtitle("X1")

plot(single_variable(ex_rf, "X2"),
single_variable(ex_svm, "X2"),
single_variable(ex_lm, "X2"),
single_variable(ex_gbm, "X2"),
single_variable(ex_tr, "X2")
) +theme_bw() + ggtitle("X2")

plot(single_variable(ex_rf, "X3"),
single_variable(ex_svm, "X3"),
single_variable(ex_lm, "X3"),
single_variable(ex_gbm, "X3"),
single_variable(ex_tr, "X3")
) +theme_bw() + ggtitle("X3")

plot(single_variable(ex_rf, "X4"),
single_variable(ex_svm, "X4"),
single_variable(ex_lm, "X4"),
single_variable(ex_gbm, "X4"),
single_variable(ex_tr, "X4")
) +theme_bw() + ggtitle("X4")

plot(single_variable(ex_rf, "X5"),
single_variable(ex_svm, "X5"),
single_variable(ex_lm, "X5"),
single_variable(ex_gbm, "X5"),
single_variable(ex_tr, "X5")
) +theme_bw() + ggtitle("X5")

Kết quả phân tích mối liên hệ phụ thuộc riêng phần không chỉ gợi ý về quy luật tương quan giữa mỗi predictor và kết quả Y, nhưng đồ thị của các hàm bộ phận này còn cho phép hình dung về cơ chế hoạt động của mỗi giải thuật (algorithm) của từng loại mô hình, giúp ta hiểu chúng đã học được thông tin gì từ dữ liệu và làm cách nào để có thể tái hiện lại mối tương quan thực tế giữaY và predictor.
Kết quả này rất thú vị, vì ta thấy rằng mô hình hồi quy tuyến tính (glm) chỉ có khả năng nhìn mọi thứ như một đường thẳng, dù cho quy luật tương quan trên thực tế là phi tuyến tính. Nó chỉ có thể thích nghi với quy luật này bằng cách gia giảm Intercept và Slope của đồ thị. Trái lại, những algorithm khác như SVM, GBM và RF có khả năng linh hoạt hơn nhiều, cho phép chúng “học” được chính xác hơn các quy luật phi tuyến tính và cố gắng nắm bắt, tái hiện lại những quy luật phức tạp này (bao gồm hàm sin và đa thức bậc cao) theo cách tốt nhất có thể.
So sánh phẩm chất giữa nhiều mô hình
DALEX cho phép so sánh hiệu năng giữa nhiều mô hình bằng hàm model_performance, tuy nhiên tính năng này chỉ có ý nghĩa với mô hình hồi quy, vì mặc định tiêu chí được khảo sát là root_mean_square. Tính năng này cũng có vẻ thừa vì ta hoàn toàn có thể tính thủ công hàng chục tiêu chí khác nhau, hoặc dựng 1 confusion matrix cho bài toán phân loại mà không cần dùng đến package nào.
Với thí dụ mô phỏng trên, không ngạc nhiên khi biết rằng mô hình tuyến tính là yếu kém nhất, trong khi Random Forest là chính xác tối ưu với rmse thấp nhất.
mp_rf <- model_performance(ex_rf)
mp_gbm <- model_performance(ex_gbm)
mp_lm <- model_performance(ex_lm)
mp_svm <- model_performance(ex_svm)
plot(mp_rf,mp_gbm,mp_lm,mp_svm,geom="boxplot")+theme_bw()

Đánh giá vai trò của từng biến trong mô hình
Khảo sát vai trò của mỗi predictor (feature) trong mô hình cũng là một cách để hiểu cơ chế hoạt động của mô hình. Hầu hết những công cụ Machine learning như mlr, h2o đều có hỗ trợ tính năng khảo sát variable importance được hỗ trợ, tuy nhiên khái niệm “mức độ quan trọng” có thể được định nghĩa theo nhiều cách khác nhau , thí dụ trong package randomForestexplainer có đến 7 tiêu chí khác nhau cho phép xác định một feature có quan trọng hay không.
Package DALEX chỉ sử dụng 1 phương pháp duy nhất khảo sát Variable Importance dựa vào kết quả của hàm loss qua nhiều lượt permutation. Để thử nghiệm tính năng này, Nhi sẽ đưa ra một thí dụ minh họa có thực: Đây là một nghiên cứu của Nierenberg DW et al. năm 1989 nhằm khảo sát quan hệ giữa nồng độ retinol trong máu (biến kết quả) với một số yếu tố khác.
betadf<-read.csv("https://www.openml.org/data/get_csv/52623/plasma_retinol.csv")
betadf%>%head()%>%knitr::kable()
64 |
Female |
Former |
21.48380 |
Yes_fairly_often |
1298.8 |
57.0 |
6.3 |
0.0 |
170.3 |
1945 |
890 |
200 |
915 |
76 |
Female |
Never |
23.87631 |
Yes_fairly_often |
1032.5 |
50.1 |
15.8 |
0.0 |
75.8 |
2653 |
451 |
124 |
727 |
38 |
Female |
Former |
20.01080 |
Yes_not_often |
2372.3 |
83.6 |
19.1 |
14.1 |
257.9 |
6321 |
660 |
328 |
721 |
40 |
Female |
Former |
25.14062 |
No |
2449.5 |
97.5 |
26.5 |
0.5 |
332.6 |
1061 |
864 |
153 |
615 |
72 |
Female |
Never |
20.98504 |
Yes_fairly_often |
1952.1 |
82.6 |
16.2 |
0.0 |
170.8 |
2863 |
1209 |
92 |
799 |
40 |
Female |
Former |
27.52136 |
No |
1366.9 |
56.0 |
9.6 |
1.3 |
154.6 |
1729 |
1439 |
148 |
654 |
Đây là một mạng lưới tương quan giữa các biến định lượng trong dữ liệu, các mối liên kết liên tục tương ứng với p_value<0.05 của 1 phân tích tương quan Pearson , ngược lại những đoạn đứt khúc có p_value > 0.05 (không có tương quan).
cormat=betadf%>%
dplyr::select(-c(SEX,SMOKSTAT,VITUSE))%>%
as.matrix()%>%
scale()
m=as.matrix(cor(cormat,
method="pearson",
use="pairwise.complete.obs"))
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
}
# matrix of the p-value of the correlation
p.mat <- cor.mtest(cormat)
library(igraph)
diag(p.mat)<-0
library(ggraph)
cg1=data.frame(row=rownames(p.mat)[row(p.mat)[upper.tri(p.mat)]],
col=colnames(p.mat)[col(p.mat)[upper.tri(p.mat)]],
corr=p.mat[upper.tri(p.mat)])
cg2=data.frame(row=rownames(m)[row(m)[upper.tri(m)]],
col=colnames(m)[col(m)[upper.tri(m)]],
corr=m[upper.tri(m)])
names(cg1)=c("from","to","pval")
names(cg2)=c("from","to","corr")
cg2$pval=cg1$pval
cg2=cg2%>%mutate(pVal=cg1$pval,Sig=if_else(cg1$pval<0.05,1,0))
gdf=filter(cg2,pval<0.05)
graph<-graph_from_data_frame(cg2)
ggraph(graph,circular=F,layout="kk")+
geom_edge_fan(aes(linetype=factor(Sig),col=factor(Sig)),
width=1,
show.legend = F,
alpha=0.5)+
geom_node_label(aes(label = name))+
coord_fixed()+
theme_graph()+
scale_edge_color_manual(values=c("red","violet"))

Nhi dựng 3 mô hình hồi quy khác nhau để ước lượng biến RetinolPlasma theo các biến còn lại
model_rf <- caret::train(RETPLASMA~.,data=betadf,method="rf",verbose=F)
model_svm <- svm(RETPLASMA~., betadf)
model_lm <- glm(RETPLASMA~., betadf,family="gaussian")
Sau đó, Nhi cũng tạo ra 3 object metadata cho 3 mô hình này bằng hàm explain.
ex_rf <- explain(model_rf,data=betadf[,-14],y=betadf$RETPLASMA,label="RandomForest")
ex_svm <- explain(model_svm,data=betadf[,-14],y=betadf$RETPLASMA,label="SVM")
ex_lm <- explain(model_lm,data=betadf[,-14],y=betadf$RETPLASMA,label="Linear")
Hàm variable_importance sẽ đánh giá vai trò của từng biến trong mô hình bằng phương pháp permutation của Fisher và công sự (Journal of Computational and Graphical Statistics. http://arxiv.org/abs/1801.01489 ; 2018) :
Cú pháp hàm này:
variable_importance(explainer, loss_function = function(observed, predicted) sum((observed - predicted)^2), …, type = “raw”, n_sample = 1000)
Nhiều phiên bản mô hình sẽ được dựng, trong đó từng biến sẽ được loại bỏ, kết quả của hàm loss_function sẽ lần lượt được tính cho : mô hình bão hòa, mô hình rỗng (cơ bản), và các phiên bản còn lại.
Đối với mô hình tuyến tính, variable importance được suy diễn trực tiếp từ hệ số hồi quy trong mô hình.
Cũng như hàm predict, nội dung hàm loss_function cũng có thể tùy chỉnh theo ý thích. Mặc định là root_mean_square
vi_rf <- variable_importance(ex_rf,type="difference")
vi_svm <- variable_importance(ex_svm,type="difference")
vi_lm <- variable_importance(ex_lm,type="difference")
plot(vi_rf,vi_svm,vi_lm)+theme_bw(8)

Diễn giải hiệu ứng bộ phận của biến rời rạc/phân nhóm
Trong thí dụ minh họa ở trên, Nhi đã khảo sát quan hệ phụ thuộc riêng phần/bộ phận giữa kết quả định lượng và predictor định lượng.
Trong trường hợp predictor là biến rời rạc hoặc phân nhóm, phương pháp “Factor Mering Path” của Sitko và Biece (2017) sẽ được áp dụng, với tùy chỉnh: type = “factor”. Kết quả là một biểu đồ so sánh hiệu hứng riêng phần cho mỗi bậc trong factor, hơn nữa 1 dendogram sẽ gom các levels có hiệu ứng tương đương nhau thành 1 cụm (cluster). Ý tưởng này gần giống như phân tích tương phản /post-hoc test trong các thiết kế ANOVA
plot(variable_response(ex_rf, "VITUSE",type="factor")
)+theme_bw(6)+ggtitle("Vitamin A use")

plot(variable_response(ex_svm, "VITUSE",type="factor") )+theme_bw(6)+ggtitle("Vitamin A use")

plot(variable_response(ex_lm, "VITUSE",type="factor")
)+theme_bw(6)+ggtitle("Vitamin A use")

Ta thử khảo sát quan hệ bộ phận giữa Retinol và 2 biến định lượng khác: Cholesterol và Alcohol
plot(single_variable(ex_rf, "ALCOHOL"),
single_variable(ex_svm, "ALCOHOL"),
single_variable(ex_lm, "ALCOHOL")
)+theme_bw()+ggtitle("ALCOHOL")

plot(variable_response(ex_rf, "CHOLESTEROL"),
variable_response(ex_svm, "CHOLESTEROL"),
variable_response(ex_lm, "CHOLESTEROL")
)+theme_bw()+ggtitle("CHOLESTEROL")

Diễn giải mô hình phân loại h2o
Để kết thúc bài này, Nhi sẽ thử áp dụng DALEX cho một mô hình GBM dựng bằng h2o. Thí dụ minh họa này dùng Dữ liệu Thoracic Surgery từ nghiên cứu của Tomczak và cộng sự (Balan) nhằm xây dựng mô hình tiên lượng nguy cơ tử vong trong vòng 1 năm ở bệnh nhân phẫu thuật lồng ngực. Dữ liệu gồm 16 predictors bao gồm triệu chứng chức năng tiền phẫu, chỉ số hô hấp ký, thông tin về chẩn đoán, bệnh lý đi kèm và tình trạng hút thuốc. Nghiên cứu gốc sử dụng giải thuật Support vector machine.
Reference: Tomczak et al. (2013). Boosted SVM for extracting rules from imbalanced data in application to prediction of the post-operative life expectancy in the lung cancer patients. Applied Soft Computing.
Nhi dựng một mô hình GBM bằng package h2o từ 90% dữ liệu gốc
library(h2o)
df=read.csv("https://www.openml.org/data/get_csv/1761654/phpGuu4iR.csv")%>%as_tibble()
colnames(df)<-c("DIAG","FVC","FEV1","ZUBROD","PAIN",
"HEMOP","DYSPN","COUGH","WEAK","TGRAD",
"DIAB","MI","PAD","SMOKE","ASTH","AGE","SURV")
df$SURV%<>%as.factor()%>%
recode_factor(.,`TRUE` = "Dead",`FALSE` = "Survived")
library(rsample)
set.seed(206)
data_split <- initial_split(df, strata = "SURV", prop=0.9)
trainset <- training(data_split)
testset <- testing(data_split)
h2o.init(nthreads = -1,max_mem_size ="4g")
train_hf <- as.h2o(trainset)
test_hf <- as.h2o(testset)
features=setdiff(colnames(train_hf),"SURV")
label="SURV"
gbmod=h2o.gbm(x = features,
y = label,
training_frame = train_hf,nfolds=10,
categorical_encoding="Enum",
balance_classes = TRUE,
ntrees =500, max_depth = 10,min_rows = 30,sample_rate=0.8,
keep_cross_validation_fold_assignment = F,
keep_cross_validation_predictions=F,
score_each_iteration = F,
seed=206)
Để có thể áp dụng các hàm của package DALEX, ta phải viết 1 hàm predict cho phép xuất kết quả là xác suất của 1 nhãn, thí dụ “Dead”,
Bạn có thể test thử hàm này trên 10 cases đầu tiên của testset nếu bạn thích
prob_predict <- function(model,newdata) {
newdata_h2o <- as.h2o(newdata)
res <- as.data.frame(h2o.predict(model, newdata_h2o))
return(as.numeric(res$Dead))
}
prob_predict(gbmod,testset[c(1:10),])
##
|
| | 0%
|
|=================================================================| 100%
##
|
| | 0%
|
|=================================================================| 100%
## [1] 2.318625e-03 7.419177e-01 8.655132e-07 8.346448e-05 6.471125e-06
## [6] 1.415870e-03 1.778604e-03 5.450894e-05 6.755577e-06 2.619766e-04
Giả sử ta muốn khảo sát quan hệ riêng phần giữa dung tích sống (FVC) tiền phẫu với xác suất tử vong, ta sẽ có kết quả như sau:
exp_gbm_train<- explain(model = gbmod,
data = trainset[,-17],
y = trainset$SURV,
predict_function =prob_predict,
label = "h2o_GBM")
pdp_FVC=variable_response(exp_gbm_train, variable = "FVC")
plot(pdp_FVC)+theme_bw()

Tương tự, ta có thể khảo sát quan hệ bộ phận giữa Diagnosis là một biến rời rạc nhiều giá trịvà nguy cơ tử vong
mppDiag=variable_response(exp_gbm_train, variable = "DIAG", type = "factor")
plot(mppDiag)+theme_bw()

Diễn giải cho từng cá thể
Sau cùng, DALEX cho phép diễn giải mô hình cho mỗi cá thể bằng phương pháp phân rã mô hình như trong packae breakdown mà Nhi đã giới thiệu ở bài trước, với hàm single_prediction( ).
Thí dụ ta diễn giải kết quả tiên lượng của mô hình cho 1 trường hợp tử vong
new_case <- testset[2,]
brk_gbm1 <- prediction_breakdown(exp_gbm_train,observation = new_case)
plot(brk_gbm1)+theme_bw()

Theo kết quả này, các biến : phân loại T trong TNM, FEV1 thấp, mã chẩn đoán DGN2, điểm Zubrod loại 2, có triệu chứng ho, có hút thuốc lần lượt có ảnh hưởng nhất định đến tiên lượng tử vong, trong khi sự vắng mặt của triệu chứng khó thở, và FVC=3.98 làm giảm nguy cơ tử vong ở bệnh nhân này.
Ta có thể làm tương tự cho 3 mô hình hồi quy ước lượng giá trị Retinol plasma ở trên: Tuy nhiên kết quả diễn giải có vẻ không tương đồng giữa 2 mô hình SVM và Random Forest
bkd_rf=prediction_breakdown(ex_rf,observation=betadf[50,])
bkd_svm=prediction_breakdown(ex_svm,observation=betadf[50,])
plot(bkd_rf,bkd_svm)+theme_bw(8)

Nhận xét
Sự góp mặt của package DALEX vào danh sách những công cụ diễn giải nội dung mô hình là một tín hiệu lạc quan cho thấy chúng ta đang tiến rất gần đến một thời kì mới, trong đó mô hình hồi quy tuyến tính không còn là sự lựa chọn duy nhất cho nghiên cứu Y học. Nhiều mô hình khác vốn thuộc về trường phái Statistical learning hay Machine learning cũng có thể được dùng như công cụ để suy diễn thống kê và khai thác thông tin từ dữ liệu thực nghiệm. Thậm chí những mô hình vốn được xem là hộp đen cũng có thể được phân tích về cấu trúc bên trong, cũng như giải thích được cơ chế hoạt động một cách trực quan.
LS0tDQp0aXRsZTogIkRBTEVYOiBnaeG6o2kgcGjDoXAgdG/DoG4gZGnhu4duIGdp4bqjaSB0aMOtY2ggbcO0IGjDrG5oIg0KYXV0aG9yOiAiTMOqIE5n4buNYyBLaOG6oyBOaGkiDQpkYXRlOiAiMjAgVGjDoW5nIDYgMjAxOCINCm91dHB1dDoNCiAgaHRtbF9kb2N1bWVudDogDQogICAgY29kZV9kb3dubG9hZDogdHJ1ZQ0KICAgIGNvZGVfZm9sZGluZzogaGlkZQ0KICAgIG51bWJlcl9zZWN0aW9uczogeWVzDQogICAgdGhlbWU6ICJzaW1wbGV4Ig0KICAgIHRvYzogVFJVRQ0KICAgIHRvY19mbG9hdDogVFJVRQ0KLS0tDQoNCiFbXShEQUxFWDEucG5nKQ0KDQojIMSQ4bq3dCB24bqlbiDEkeG7gQ0KDQpUcm9uZyBiw6BpIGjDtG0gbmF5LCBt4buZdCBs4bqnbiBu4buvYSBOaGkgcXVheSBs4bqhaSB24bubaSBjaOG7pyDEkeG7gSBkaeG7hW4gZ2nhuqNpIG3DtCBow6xuaC4gVGjhu7FjIHJhIGtow7RuZyBwaOG6o2kgY2jhu50gxJHhur9uIHRo4budaSDEkeG6oWkgaGnhu4duIG5heSBuZ8aw4budaSB0YSBt4bubaSBxdWFuIHTDom0gxJHhur9uIHZhaSB0csOyIHF1YW4gdHLhu41uZyBj4bunYSBtw7QgaMOsbmggdGjhu5FuZyBrw6ogdHJvbmcgbmdoacOqbiBj4bupdSBZIGjhu41jLiDEkMOjIHThu6sgcuG6r3QgbMOidSwga2hpIHBow6JuIHTDrWNoIGThu68gbGnhu4d1IGLhurFuZyB0aOG7kW5nIGvDqiBj4buVIMSRaeG7g24sIG5naGnDqm4gY+G7qXUgc2luaCDEkcOjIGx1w7RuIHbhuq1uIGThu6VuZyBtw7QgaMOsbmggKG5nYXkgY+G6oyBraGkgaOG7jSBraMO0bmcgw70gdGjhu6ljIHbhu4EgxJFp4buBdSBuw6B5KSwgbeG6t2MgZMO5IGNo4buJIGPDsyAxIGxv4bqhaSBtw7QgaMOsbmggZHV5IG5o4bqldCBsw6AgaOG7k2kgcXV5IHR1eeG6v24gdMOtbmguDQoNCk5oxrAgbmjhuq1uIHjDqXQgY+G7p2EgR2VvcmdlcyBCb3ggOiDCqyB04bqldCBj4bqjIG3DtCBow6xuaCDEkeG7gXUgc2FpLCBuaMawbmcgxJHDtGkga2hpIGNow7puZyBo4buvdSBk4bulbmcgwrssIHR1eSBtw7QgaMOsbmggdHV54bq/biB0w61uaCBjw7MgbuG7mWkgZHVuZyBy4bqldCDEkcahbiBnaeG6o24sIG5oxrBuZyBuw7MgduG7q2EgxJHhu6cgxJHhu4MgdGjhu49hIG3Do24gaOG6p3UgaOG6v3QgbeG7pWMgdGnDqnUgbmdoacOqbiBj4bupdS4gVGjhuq10IHbhuq15LCB0cm9uZyBow6BuZyBjaOG7pWMgbsSDbSBxdWEsIHLhuqV0IG5oaeG7gXUga2nhur9uIHRo4bupYyB5IGjhu41jIOKAkyBiYW8gZ+G7k20gbmjhu69uZyBxdXkgbHXhuq10IHNpbmggbMO9LWLhu4duaCBo4buNYywgcXV54bq/dCDEkeG7i25oIMSRaeG7gXUgdHLhu4sgdsOgIGNo4bqpbiDEkW/DoW4gxJHDoyDEkcaw4bujYyB4w6FjIMSR4buLbmggZOG7sWEgdsOgbyBuaOG7r25nIG3DtCBow6xuaCB0dXnhur9uIHTDrW5oIMSRxqFuIGdp4bqjbiBuw6B5LiAgIA0KDQpTxqEgxJHhu5Mgc2F1IMSRw6J5IHTDs20gdOG6r3QgcXV5IHRyw6xuaCBzdXkgZGnhu4VuIHRo4buRbmcga8OqIGThu7FhIHbDoG8gbcO0IGjDrG5oIGhlbyBjw6FjaCBsw6BtIHZp4buHYyBjxak6IA0KDQohW10oREFMRVgyLnBuZykNCg0KVG/DoG4gYuG7mSBk4buvIGxp4buHdSB0aOG7sWMgbmdoaeG7h20gxJHGsOG7o2MgZMO5bmcgxJHhu4MgdOG6oW8gcmEgbcO0IGjDrG5oIHRo4buRbmcga8OqLiBNw7QgaMOsbmggbsOgeSBjw7MgYuG6o24gY2jhuqV0IGzDoCBz4buxIHBo4bqjbiDDoW5oIG3hu5l0IGdp4bqjIHRodXnhur90IGNo4bunIHF1YW4sIMSRxqFuIGdp4bqjbiBj4bunYSBuaMOgIG5naGnDqm4gY+G7qXUuIMSQw6J5IGzDoCBz4buxIGdp4bqjbiBsxrDhu6NjLCB0aHUgbmjhu48gdGjhur8gZ2nhu5tpIHBo4bupYyB04bqhcCB0csOqbiB0aOG7sWMgdOG6vyB0aMOgbmggbeG7mXQgcXV5IGx14bqtdCDEkcahbiBnaeG6o24gdsOgIHTGsMahbmcgxJHhu5FpIGNow61uaCB4w6FjLCBjaG8gcGjDqXAgdHLhuqMgbOG7nWkgbeG7mXQgc+G7kSBjw6J1IGjhu49pIG5o4bqldCDEkeG7i25oLiBQaOG6qW0gY2jhuqV0IGPhu6dhIG3DtCBow6xuaCB0aMaw4budbmcgxJHGsOG7o2Mga2nhu4NtIGNo4bupbmcgdHLDqm4gY2jDrW5oIGThu68gbGnhu4d1IGfhu5FjIGLhurFuZyBt4buZdCB2w6BpIGPDtG5nIGPhu6UgdGjhu5FuZyBrw6ogxJHGoW4gZ2nhuqNuIG5oxrAgTFIgdGVzdCwgUjIsIEJJQyBoYXkgQUlD4oCmIFNhdSDEkcOzIHZp4buHYyBzdXkgZGnhu4VuIHRo4buRbmcga8OqICh0aMOtIGThu6UgOiBzbyBzw6FuaCwgdMawxqFuZyBxdWFuLCBsacOqbiBo4buHLCBuZ3V5IGPGoeKApikgxJHGsOG7o2MgbMOgbSDhu58gY+G6pXAgxJHhu5kgcXXhuqduIHRo4buDLCB0cuG7sWMgdGnhur9wIGThu7FhIHbDoG8gY8OhYyBo4buHIHPhu5EgaOG7k2kgcXV5IHRyb25nIG3DtCBow6xuaC4gS+G6v3QgcXXhuqMgc3V5IGRp4buFbiB0aOG7kW5nIGvDqiDEkcaw4bujYyBraeG7g20gY2jhu6luZyBi4bqxbmcgcGjhuqNuIG5naGnhu4dtIGdp4bqjIHRodXnhur90IHbDtCBoaeG7h3UsIHbDoCBu4bq/dSBjw7Mgw70gbmdoxKlhLCBjaMO6bmcgc+G6vSDEkcaw4bujYyB4ZW0gbmjGsCB0cmkgdGjhu6ljIG3hu5tpLCB2w6AgcXV5IHRyw6xuaCBk4burbmcgbOG6oWkg4bufIMSRw7MuIEhp4bq/bSBraGkgbcO0IGjDrG5oIMSRxrDhu6NjIMOhcCBk4bulbmcgY2hvIG3hu6VjIHRpw6p1IHRo4buxYyBow6BuaCwgdGjDrSBk4bulIHRpw6puIGzGsOG7o25nIG5ndXkgY8ahLCBwaMOibiBsb+G6oWkgY2jhuqluIMSRb8OhbiBob+G6t2MgxrDhu5tjIGzGsOG7o25nIHRy4buLIHPhu5EgdGhhbSBjaGnhur91LiANCg0KR+G6p24gxJHDonksIHbhu5tpIHPhu7EgcGjDoXQgdHJp4buDbiB0aOG6p24gdOG7kWMgY+G7p2EgdHLGsOG7nW5nIHBow6FpIE1hY2hpbmUgbGVhcm5pbmcgKHRo4buxYyByYTogbsOzIMSRw6Mgw6JtIHRo4bqnbSBzb25nIGjDoG5oIHbhu5tpIHRo4buRbmcga8OqIHF1eSDGsOG7m2MgdOG7qyB0aOG6rXAgbmnDqm4gODApLCBjw7Mgbmhp4buBdSDDvSBraeG6v24g4bunbmcgaOG7mSBjaG8gbeG7mXQgc+G7sSBo4bujcCBuaOG6pXQgZ2nhu69hIFRo4buRbmcga8OqIGPhu5UgxJFp4buDbiwgTWFjaGluZSBsZWFybmluZyB2w6Aga2hvYSBo4buNYyBtw6F5IHTDrW5oLCDEkeG7gyBj4bqjIGJhIGPDsyB0aOG7gyBjw7luZyB0aGFtIGdpYSBo4buXIHRy4bujIG3hu5l0IGPDoWNoIGhp4buHdSBxdeG6oyBuaOG6pXQgY2hvIGPDoWMgbmjDoCBraG9hIGjhu41jIHRyb25nIHZp4buHYyBraGFpIHRow6FjIHRow7RuZyB0aW4sIGtp4bq/biB0aOG7qWMgdOG7qyBk4buvIGxp4buHdSwgY8WpbmcgbmjGsCBuaOG7r25nIOG7qW5nIGThu6VuZyB0aGnhur90IHRo4buxYyB2w6Ag4bufIGPhuqVwIMSR4buZIGPDoSB0aOG7gy4gDQoNClPhu7EgaOG7o3AgbmjhuqV0IG7DoHkga2jDtG5nIGNo4buJIGxpw6puIHF1YW4gxJHhur9uIHnhur91IHThu5EgY8O0bmcgY+G7pSAoZHUgbmjhuq1wIG5o4buvbmcgYWxnb3JpdGhtIG3hu5tpIGzhuqEgbmjGsCBSYW5kb20gRm9yZXN0LCBTVk0sIE5ldXJhbCBuZXR3b3Jr4oCmIHbDoG8gbmdoacOqbiBj4bupdSBraG9hIGjhu41jKSwgbmjGsG5nIGPDsm4gY8OzIHRo4buDIGThuqtuIMSR4bq/biBz4buxIHRoYXkgxJHhu5VpIG1hbmcgdMOtbmggY8OhY2ggbeG6oW5nIHbhu4EgY8OhY2ggc3V5IG5naMSpLCBxdXkgdHLDrG5oIGzDoG0gdmnhu4djLiBN4buZdCBz4buRIMO9IHTGsOG7n25nIG3hu5tpIGzDoCBo4buHIHF14bqjIGPhu6dhIHPhu7EgZ2lhbyB0aG9hIGdp4buvYSAzIGNodXnDqm4gbmfDoG5oIGtob2EgaOG7jWMgZOG7ryBsaeG7h3UgxJHGsOG7o2MgdMOzbSB04bqvdCB0cm9uZyBzxqEgxJHhu5Mgc2F1IDoNCg0KIVtdKERBTEVYMy5wbmcpDQoNCk7hu5lpIGR1bmcgbmjhu69uZyDDvSB0xrDhu59uZyBuw6B5IGJhbyBn4buTbSA6DQoNCjEpCUtow7RuZyBjaOG7iSBjw7MgbcO0IGjDrG5oIHR1eeG6v24gdMOtbmgsIG5oxrBuZyBi4bqldCBrw6wgbeG7mXQgbcO0IGjDrG5oIG7DoG8sIGJhbyBn4buTbSBuaOG7r25nIG3DtCBow6xuaCBo4buZcCDEkWVuIChibGFja2JveGVzKSDEkeG7gXUgY8OzIHRo4buDIHPhu60gZOG7pW5nIHRyb25nIG5naGnDqm4gY+G7qXUga2hvYSBo4buNYywgY2hvIGPhuqMgYsOgaSB0b8OhbiBo4buTaSBxdXkgKMaw4bubYyBsxrDhu6NuZyBnacOhIHRy4buLIGJp4bq/biBsacOqbiB04bulYykgaG/hurdjIHBow6JuIGxv4bqhaS4NCg0KMikJTmjhu69uZyBtw7QgaMOsbmggbsOgeSBjw7MgdGjhu4MgxJHGsOG7o2Mg4bupbmcgZOG7pW5nIGNobyBuaGnhu4F1IG3hu6VjIHRpw6p1IGtow6FjIG5oYXUgLCBiYW8gZ+G7k20gOiBUacOqbiBsxrDhu6NuZyAocGjDom4gbG/huqFpLCDGsOG7m2MgdMOtbmggdsOgIMSRxrBhIHJhIHF1eeG6v3QgxJHhu4tuaCksIGtp4buDbSB0cmEgcGjhuqltIGNo4bqldCBj4bunYSBtw7QgaMOsbmggdHLDqm4gdGjhu7FjIHThur8sIHN1eSBkaeG7hW4gdGjhu5FuZyBrw6og4bufIG3hu6ljIMSR4buZIHF14bqnbiB0aOG7gyBoYXkgY8OhIHRo4buDIG5o4bqxbSBjdW5nIGPhuqVwIGtp4bq/biB0aOG7qWMgbeG7m2ksIGtp4buDbSB0cmEgZ2nhuqMgdGh1eeG6v3QgbmdoacOqbiBj4bupdSB2w6AgZ2nhuqNpIHRow61jaCBjxqEgY2jhur8gY+G7p2EgbcO0IGjDrG5oLg0KDQozKQlRdXkgdHLDrG5oIHjDonkgZOG7sW5nIG3DtCBow6xuaCBz4bq9IMOhcCBk4bulbmcgbeG7mXQgc+G7kSBxdXkgdOG6r2MgbeG7m2ksIHRow60gZOG7pSA6IGtow7RuZyBz4butIGThu6VuZyB0b8OgbiBi4buZIGThu68gbGnhu4d1IGfhu5FjIGNobyB2aeG7h2MgdOG6oW8gcmEgbcO0IGjDrG5oLCBuaMawbmcgY2h14bqpbiBi4buLIHJpw6puZyBjw6FjIGLhu5kgZOG7ryBsaeG7h3Uga2nhu4NtIMSR4buLbmggxJHhu5ljIGzhuq1wLiBN4buZdCBz4buRIHBoxrDGoW5nIHBow6FwIGtp4buDbSBjaOG7qW5nIGNow6lvLCB0w6FpIGNo4buNbiBt4bqrdSAoYm9vdHN0cmFwKSBwaOG6o2kgxJHGsOG7o2Mgw6FwIGThu6VuZy4gIFBo4bqpbSBjaOG6pXQgbcO0IGjDrG5oIHPhur0gxJHGsOG7o2Mga2nhu4NtIHRyYSBjaOG6t3QgY2jhur0gaMahbiBi4bqxbmcgbmhp4buBdSB0acOqdSBjaMOtIGtow6FjIG5oYXUgdHLDqm4gZOG7ryBsaeG7h3UgxJHhu5ljIGzhuq1wIHRyxrDhu5tjIGtoaSBzdXkgZGnhu4VuIHRo4buRbmcga8OqIGhv4bq3YyDDoXAgZOG7pW5nIHbDoG8gdGjhu7FjIGjDoG5oLg0KDQo0KQlWaeG7h2Mgc+G7rSBk4bulbmcgY8OhYyBnaeG6o2kgdGh14bqtdCAoYWxnb3JpdGhtKSBwaOG7qWMgdOG6oXAga2jDoWMgbmdvw6BpIG3DtCBow6xuaCB0dXnhur9uIHTDrW5oIGPDsyB0aOG7gyBtYW5nIGzhuqFpIG5oaeG7gXUgbOG7o2kgw61jaCwgbmjGsCBnaeG6o20gbmjhurkgY8O0bmcgc+G7qWMgY+G7p2EgY29uIG5nxrDhu51pIHRyb25nIHZp4buHYyDEkeG6t3QgZ2nhuqMgdGh1eeG6v3QsIGNo4buNbiBs4buNYyBiaeG6v24gdGjhu6cgY8O0bmcg4oCmLCBtw7QgaMOsbmgga2jDtG5nIHBo4bulIHRodeG7mWMgdsOgbyBjw6FjIGdp4bqjIMSR4buLbmggduG7gSBwaMOibiBi4buRIG5oxrBuZyBjw7Mga2jhuqMgbsSDbmcgcGjDoXQgaGnhu4duIMSRxrDhu6NjIG5o4buvbmcgaGnhu4d1IOG7qW5nIHTGsMahbmcgdMOhYywgcXV5IGx14bqtdCBwaGkgdHV54bq/biB0w61uaC9j4bulYyBi4buZLCBjw7MgdGjhu4MgdOG7sSBjaOG7jW4gbOG7jWMgYmnhur9uLCBjaOG6pXAgbmjhuq1uIGThu68gbGnhu4d1IHRoaeG6v3Ugc8OzdOKApiB2w6AgbmjhuqV0IGzDoCB0w61uaCBjaMOtbmggeMOhYyBjYW8gaMahbi4NCg0KIVtdKERBTEVYNC5wbmcpDQoNClR1eSBuaGnDqm4sIMSR4buDIGPDsyB0aOG7gyB0aOG7sWMgaGnhu4duIDQgw70gdMaw4bufbmcga+G7gyB0csOqbiBjaG8gbmjhu69uZyBuZ2hpw6puIGPhu6l1IHRodeG7mWMgbG/huqFpIFN1eSBkaeG7hW4gLyBEaeG7hW4gZOG7i2NoLCBuaMOgIG5naGnDqm4gY+G7qXUgYuG6r3QgYnXhu5ljIHBo4bqjaSBoaeG7g3UgxJHGsOG7o2MgbcO0IGjDrG5oIG3DrG5oIMSRYW5nIHPhu60gZOG7pW5nLiBWaeG7h2MgwqsgSGnhu4N1IMK7IG7DoHkgxJHhurd0IHJhIG5oaeG7gXUgY8OidSBo4buPaSB0aMO6IHbhu4sgbmjGsCA6DQoNCjEpCU3DtCBow6xuaCBuw6B5IGNow61uaCB4w6FjIMSR4bq/biDEkcOidSA/IEPDsyDEkcOhbmcgdGluIGPhuq15IGhheSBraMO0bmcgPyBDw6J1IGjhu49pIG7DoHkgZOG6q24gxJHhur9uIHZp4buHYyBraeG7g20gxJHhu4tuaCBwaOG6qW0gY2jhuqV0IG3DtCBow6xuaCwga2jDtG5nIGNo4buJIDEgbOG6p24gdHJvbmcga2hpIGzDoG0gbmdoacOqbiBj4bupdSBtw6AgdHJvbmcgc3Xhu5F0IHF1w6EgdHLDrG5oIGzDonUgZMOgaSBraGkgbcOgIG3DtCBow6xuaCDEkcOzIHbhuqtuIGPDsm4gxJHGsOG7o2Mg4bupbmcgZOG7pW5nIHRyb25nIHRo4buxYyBow6BuaCBsw6JtIHPDoG5nICjEkeG7kWkgduG7m2kgbeG7pWMgdGnDqnUgdGnDqm4gbMaw4bujbmcpIGhv4bq3YyB04buTbiB04bqhaSB0cm9uZyB0aMawIHZp4buHbiB5IHbEg24gKMSR4buRaSB24bubaSBtw7QgaMOsbmggbMO9IHRodXnhur90KSAuDQoNCjIpCSBNw7QgaMOsbmggaG/huqF0IMSR4buZbmcgbmjGsCB0aOG6vyBuw6BvID8gTsOzIMSRw6MgaOG7jWMgxJHGsOG7o2MgZ8OsIHThu6sgZOG7ryBsaeG7h3UgPyDEkMOieSBsw6AgY8OidSBo4buPaSBy4bqldCBxdWFuIHRy4buNbmcgbMOgbSBu4buBbiB04bqjbmcgY2hvIHZp4buHYyBkaeG7hW4gZ2nhuqNpIG7hu5lpIGR1bmcvY8ahIGNo4bq/IGPhu6dhIG3DtCBow6xuaC4gR2nhuqNpIMSRw6FwIMSRxrDhu6NjIGPDonUgaOG7j2kgbsOgeSBjaG8gcGjDqXAgcsO6dCByYSBow6BuZyBsb+G6oXQgdGjDtG5nIHRpbiwgc3V5IGRp4buFbiBxdWFuIHRy4buNbmcsIGjhu691IMOtY2ggYmFvIGfhu5NtOiBWYWkgdHLDsiBj4bunYSBt4buXaSBiaeG6v24gPyBN4buRaSBsacOqbiBo4buHIGLhu5kgcGjhuq1uIGdp4buvYSBt4buXaSBiaeG6v24gdsOgIGvhur90IHF14bqjICBsw6AgZ8OsID8gKEdoaSBjaMO6OiBu4bq/dSBr4bq/dCBxdeG6oyBsw6AgbeG7mXQgY2jhuqluIMSRb8OhbiAvIHRpw6puIGzGsOG7o25nIOKAkyBjw6J1IGjhu49pIG7DoHkgY2hvIHBow6lwIHjDoWMgxJHhu4tuaCB54bq/dSB04buRIG5ndXkgY8ahLCBu4bq/dSBr4bq/dCBxdeG6oyBsw6AgbeG7mXQgxJHhuqFpIGzGsOG7o25nIGhheSBjb24gc+G7kSAtIGPDonUgaOG7j2kgdHLDqm4gY2hvIHBow6lwIHNvIHPDoW5oLCBwaMOibiBj4bulbSBob+G6t2MgcGjDom4gdMOtY2ggdMawxqFuZyBxdWFuIGLhu5kgcGjhuq1uKS4gS+G6v3QgcXXhuqMgY+G7p2Egdmnhu4djIGdp4bqjaSB0aMOtY2ggY8ahIGNo4bq/IG7DoHkgY8OybiBjw7MgdGjhu4MgxJHGsOG7o2Mgc+G7rSBk4bulbmcgxJHhu4MgcXVheSBuZ8aw4bujYyBs4bqhaSBj4bqjaSB0aGnhu4duIG3DtCBow6xuaCBi4bqxbmcgY8OhY2ggYuG7jyBi4bubdCBuaOG7r25nIGJp4bq/biBraMO0bmcgcXVhbiB0cuG7jW5nLCB0aW5oIGNo4buJbmggdGhhbSBz4buRIGPhu6dhIGFsZ29yaXRobSwgdGjDqm0gZOG7ryBsaeG7h3UgbeG7m2kgDQoNCjMpCUdp4bqjaSB0aMOtY2ggxJHGsOG7o2MgY8ahIGNo4bq/IGPhu6dhIG3DtCBow6xuaCDhu58gY+G6pXAgxJHhu5kgY8OhIHRo4buDOiBNw7QgaMOsbmggaG/huqF0IMSR4buZbmcgY8OzIGNow61uaCB4w6FjIGtow7RuZyBjaG8gdHLGsOG7nW5nIGjhu6NwIG7DoHkgPyBU4bqhaSBzYW8ga+G6v3QgcXXhuqMgbOG6oWkgbmjGsCB24bqteSA/IEJp4bq/biBuw6BvIGPDsyB2YWkgdHLDsi8g4bqjbmggaMaw4bufbmcgcXVhbiB0cuG7jW5nIG5o4bqldCDhu58gY8OhIHRo4buDIG7DoHkgPyB2LnYgVHJvbmcgYsOgaSB0csaw4bubYyBOaGkgxJHDoyBiw6BuIGx14bqtbiB24buBIG5o4buvbmcgbOG7o2kgw61jaCBj4bunYSB2aeG7h2MgZGnhu4VuIGdp4bqjaSBtw7QgaMOsbmggY2hvIHThu6tuZyBjw6EgdGjhu4MsIGPDoWMgYuG6oW4gY8OzIHRo4buDIMSR4buNYyB0aMOqbSDhu58gxJHDonk6DQoNCiMgR2nhu5tpIHRoaeG7h3UgREFMRVg6IG3hu5l0IGdp4bqjaSBwaMOhcCBt4bubaQ0KDQpUcm9uZyBiw6BpIHRo4buxYyBow6BuaCBow7RtIG5heSwgTmhpIHPhur0gZ2nhu5tpIHRoaeG7h3UgduG7m2kgY8OhYyBi4bqhbiBt4buZdCBjw7RuZyBj4bulIG3hu5tpIOKAkyBtw6AgdGhlbyDEkcOhbmggZ2nDoSBj4bunYSBOaGkg4oCTIGzDoCBt4buZdCBnaeG6o2kgcGjDoXAgaOG7r3UgaGnhu4d1IGNobyBwaOG6p24gbOG7m24gKG7hur91IGtow7RuZyBtdeG7kW4gbsOzaSBsw6AgdOG6pXQgY+G6oyApIG5o4buvbmcgduG6pW4gxJHhu4EgbsOqdSB0csOqbi4gxJDDsyBsw6AgcGFja2FnZSBEQUxFWCBj4bunYSB0w6FjIGdp4bqjIFByemVteXNsYXcgQmllY2VrLCBt4bubaSBjw7RuZyBi4buRIHRyw6puIENSQU4gdsOgbyBnaeG7r2EgdGjDoW5nIDYgbsSDbSAyMDE4IHbhu6thIHF1YS4NCg0KREFMRVggbMOgIHTDqm4gcsO6dCBn4buNbiBj4bunYSDCqyBEZXNjcmlwdGl2ZSBtQWNoaW5lIExlYXJuaW5nIEVYcGxhbmF0aW9ucyDCuy4gVMOhYyBnaeG6oyBCaWVjZWsgxJHDoyDEkWkgeGEgaMahbiBi4bqldCBj4bupIG5nxrDhu51pIG7DoG8ga2jDoWMgdHJvbmcgdmnhu4djIGRp4buFbiBnaeG6o2kgbuG7mWkgZHVuZyBtw7QgaMOsbmgsIHbhu5tpIDMgw70gdMaw4bufbmcgxJHhu5ljIMSRw6FvOg0KDQoxKQnEkMawYSByYSBt4buZdCBxdXkgdHLDrG5oIGdp4bqjaSBuZ2jEqWEgcGjhu5UgcXXDoXQgY2hvIG3hu41pIG3DtCBow6xuaCwgYuG6pXQga+G7gyBi4bqjbiBjaOG6pXQgY+G7p2EgYWxnb3JpdGhtIHbDoCBt4bulYyB0acOqdSBuZ2hpw6puIGPhu6l1ICho4buTaSBxdXkvdGnDqm4gbMaw4bujbmcsIHBow6JuIGxv4bqhaSBoYXkgc3V5IGRp4buFbikgLiBEQUxFWCB0xrDGoW5nIHRow61jaCB24bubaSBj4bqjIDMgZ2lhbyB0aOG7qWMgTWFjaGluZSBsZWFybmluZyBwaOG7lSBiaeG6v24gaGnhu4duIG5heSBn4buTbSBjYXJldCwgbWxyIHbDoCBoMm8sIGPFqW5nIG5oxrAgdOG7q25nIGFsZ29yaXRobSDEkcahbiBs4bq7IGtow6FjIG5oxrAgZTEwNzEsIHJmDQoNCjIpCUNobyBwaMOpcCB0csOsbmggYsOgeSB0cuG7sWMgcXVhbiBr4bq/dCBxdeG6oyBkaeG7hW4gZ2nhuqNpIGPhu6dhIGjDoG5nIGxv4bqhdCBtw7QgaMOsbmguIE5oxrAgduG6rXkgbeG7mXQgY8O0bmcgZOG7pW5nIGtow6FjIGPhu6dhIERBTEVYIGzDoCBzbyBzw6FuaCB2w6AgY2jhu41uIGzhu41jIG3DtCBow6xuaC9hbGdvcml0aG0uDQoNCjMpCUdp4bqjaSDEkcOhcCBo4bqndSBo4bq/dCBjw6J1IGjhu49pIHF1YW4gdHLhu41uZyDEkeG7gyDigJxoaeG7g3XigJ0gbcO0IGjDrG5oLCBiYW8gZ+G7k206IE7hu5lpIGR1bmcgdsOgIGPGoSBjaOG6vyBob+G6oXQgxJHhu5luZyA6IFThuqdtIHF1YW4gdHLhu41uZyBj4bunYSBjw6FjIGJp4bq/biwgUXVhbiBo4buHIHJpw6puZyBwaOG6p24gY+G7p2EgdOG7q25nIGJp4bq/biDEkeG7kWkgduG7m2kga+G6v3QgcXXhuqMgKMSR4bq3YyBiaeG7h3QgaOG7r3Ugw61jaCBjaG8gYsOgaSB0b8OhbiBo4buTaSBxdXkpLCDEkeG7mSBjaMOtbmggeMOhYyBj4bunYSBtw7QgaMOsbmggdsOgIGRp4buFbiBnaeG6o2kgY2hvIHThu6tuZyBjw6EgdGjhu4MgKHRoZW8gcGjGsMahbmcgcGjDoXAgYnJlYWtkb3duKS4gDQoNCiMgQ+G6pXUgdHLDumMgY+G7p2EgREFMRVgNCg0KS2hpIHPhu60gZOG7pW5nIERBTEVYLCBjaOG7iSBjw7MgYmEgYsaw4bubYyBj4bqnbiBuaOG7myB0cm9uZyBt4buZdCBxdXkgdHLDrG5oIGNodW5nIGNobyBt4buNaSBtw7QgaMOsbmggOg0KDQoxKSDEkOG6p3UgdGnDqm4sIHRhIHPhur0gZMO5bmcgaMOgbSBleHBsYWluIHRyw6puIG3DtCBow6xuaCDEkeG7gyB04bqhbyByYSBt4buZdCBtZXRhZGF0YSBvYmplY3QgY+G6p24gdGhp4bq/dCBjaG8gbmjhu69uZyBixrDhu5tjIGRp4buFbiBnaeG6o2kgdGnhur9wIHRoZW8uDQoNCkjDoG0gZXhwbGFpbiBjw7MgY8O6IHBow6FwIDoNCmV4cGxhaW4obW9kZWwsIGRhdGEsIHksIHByZWRpY3RfZnVuY3Rpb24sIGxpbmssIC4uLiwgbGFiZWwpDQoNCkPDoWMgdMO5eSBjaOG7iW5oIGJhbyBn4buTbToNCg0KKyBtb2RlbCBsw6Agb2JqZWN0IGvhur90IHF14bqjIG3DtCBow6xuaCAoREFMRVggdMawxqFuZyB0aMOtY2ggduG7m2kgb2JqZWN0IGvhur90IHF14bqjIGPhu6dhIGNhcmV0LCBtbHIsIGgybykuDQoNCisgZGF0YSBsw6AgbeG7mXQgYuG7mSBk4buvIGxp4buHdSBjw7MgY+G6pXUgdHLDumMgZ2nhu5FuZyBuaMawIGThu68gbGnhu4d1IGTDuW5nIMSR4buDIGThu7FuZyBtw7QgaMOsbmggbmjGsG5nIGtow7RuZyBn4buTbSBiaeG6v24ga+G6v3QgcXXhuqMuIEPDsyB0aOG7gyBkw7luZyBk4buvIGxp4buHdSBn4buRYyAodHJhaW5zZXQpIGNobyBt4bulYyB0acOqdSBnaeG6o2kgbmdoxKlhIMSRxqFuIHRodeG6p24sIGhv4bq3YyBk4buvIGxp4buHdSBt4bubaSAodGVzdHNldCkgY2hvIG3hu6VjIHRpw6p1IGtp4buDbSDEkeG7i25oLiBQaOG6p24gZGF0YSBuw6B5IGPhuqduIGNobyBjw6FjIHF1eSB0csOsbmg6IGtp4buDbSDEkeG7i25oIChtb2RlbCBwZXJmb3JtYW5jZSksIGto4bqjbyBzw6F0IHZhaSB0csOyIGPDoWMgYmnhur9uICh2YXJpYWJsZSBpbXBvcnRhbmNlKQ0KDQorIHkgbMOgIHZlY3RvciBiaeG6v24ga+G6v3QgcXXhuqMgdOG7qyB04bqtcCBk4buvIGxp4buHdSBkYXRhIG7Ds2kgdHLDqm4sIGPhuqduIMSR4buDIMSRw6FuaCBnacOhIHZhcmlhYmxlIGltcG9ydGFuY2UNCg0KKyBwcmVkaWN0X2Z1bmN0aW9uIDogbMOgIDEgaMOgbSBkbyBuZ8aw4budaSBkw7luZyBxdXkgxJHhu4tuaCwgY2hvIHBow6lwIHRy4bqjIGvhur90IHF14bqjIGzDoCBjb24gc+G7kSAob3V0Y29tZSDEkeG7i25oIGzGsOG7o25nIGNobyBiw6BpIHRvw6FuIGjhu5NpIHF1eSwgaGF5IHByb2JhYmlsaXR5IGPhu6dhIDEgbmjDo24gdHJvbmcgYsOgaSB0b8OhbiBwaMOibiBsb+G6oWkpLiBO4bq/dSBraMO0bmcgxJHGsOG7o2MgeMOhYyDEkeG7i25oLCBow6BtIHByZWRpY3QgbeG6t2MgxJHhu4tuaCB0cm9uZyBSIHPhur0gxJHGsOG7o2MgZMO5bmcsIHZp4buHYyB0w7l5IGNo4buJbmggMSBow6BtIHByZWRpY3QgcmnDqm5nIGzDoCBi4bqvdCBideG7mWMga2hpIG3DtCBow6xuaCBk4buxbmcgYuG6sW5nIHBhY2thZ2UgaDJvLCBjxaluZyBuaMawIGPDoWMgbcO0IGjDrG5oIGJpbmFyeSAsIG11bHRpbGFiZWwgY2xhc3NpZmljYXRpb27igKYNCg0KKyBsaW5rIGZ1bmN0aW9uIGNobyBwaMOpcCB0w7l5IGNo4buJbmggMSBow6BtIGhvw6FuIGNodXnhu4NuIGvhur90IHF14bqjICwgxJHhu4MgY2h1eeG7g24ga+G6v3QgcXXhuqMgcHJlZGljdGlvbiBj4bunYSBtw7QgaMOsbmggduG7gSB0aGFuZyDEkW8gdsOgIMSRxqFuIHbhu4sgbmd1ecOqbiB0aOG7p3kgY+G7p2Egb3V0Y29tZS4gTeG6t2MgxJHhu4tuaCBkw7luZyBow6BtIGlkZW50aXR5DQoNCisgbGFiZWwgOiBkw7luZyDEkeG7gyDEkeG6t3QgY2hvIG3hu5dpIG3DtCBow6xuaCBt4buZdCB0w6puIGfhu41pIG5n4bqvbiBn4buNbiwgdMOqbiBn4buNaSBuw6B5IHPhur0gxJHGsOG7o2MgZMO5bmcga2hpIHbhur0gYmnhu4N1IMSR4buTDQoNCjIpIFThu6sgb2JqZWN0IG7DoHksIHRhIGPDsyB0aOG7gyDDoXAgZOG7pW5nIHRp4bq/cCBt4buZdCBz4buRIGjDoG0gY2h1ecOqbiBiaeG7h3QgbmjhurFtIHRy4bqjIGzhu51pIGNobyB04burbmcgY8OidSBo4buPaSBuaOG6pXQgxJHhu4tuaCwgdGjDrSBk4bulIMSRw6FuaCBnacOhIHZhaSB0csOyIGPhu6dhIGPDoWMgYmnhur9uLCBxdWFuIGjhu4cgcGjhu6UgdGh14buZYyByacOqbmcgcGjhuqduLCBnaeG6o2kgdGjDrWNoIGNobyBjw6EgdGjhu4MsIOKApg0KDQozKSBTYXUgY8O5bmcsIGvhur90IHF14bqjIHThu6sgYsaw4bubYyAyIGPDsyB0aOG7gyDEkcaw4bujYyB0csOsbmggYsOgeSB0cuG7sWMgcXVhbiBi4bqxbmcgYmnhu4N1IMSR4buTIHbhu5tpIGjDoG0gcGxvdCggKS4gxJDhurdjIGJp4buHdCBr4bq/dCBxdeG6oyBkaeG7hW4gZ2nhuqNpIGPhu6dhIG5oaeG7gXUgbcO0IGjDrG5oIGPDsyB0aOG7gyBr4bq/dCBo4bujcCB24bubaSBuaGF1IHRyb25nIGjDoG0gcGxvdCBjaG8gbeG7pWMgdGnDqnUgc28gc8OhbmguDQoNCiMgRGnhu4VuIGdp4bqjaSBxdWFuIGjhu4cgYuG7mSBwaOG6rW4gdHJvbmcgbcO0IGjDrG5oICgxKQ0KDQrEkOG6p3UgdGnDqm4sIFRhIHPhur0gZMO5bmcgbeG7mXQgZOG7ryBsaeG7h3UgbcO0IHBo4buPbmcgxJHhu4Mga2nhu4NtIHRyYSB0w61uaCBuxINuZyBraOG6o28gc8OhdCBxdWFuIGjhu4cgcGjhu6UgdGh14buZYyByacOqbmcgcGjhuqduIGdp4buvYSBr4bq/dCBxdeG6oyBZIGzDoCBiaeG6v24gbGnDqm4gdOG7pWMgdsOgIG5o4buvbmcgYmnhur9uIHPhu5EgWGkga2jDoWMgdHJvbmcgbcO0IGjDrG5oLiAgDQoNCmBgYHtyLG1lc3NhZ2UgPSBGQUxTRSx3YXJuaW5nPUZBTFNFfQ0KbGlicmFyeSh0aWR5dmVyc2UpDQoNCmZzaW09ZnVuY3Rpb24oWDEsWDIsWDMsWDQsWDUpew0KICAoMC42KlgxLTAuMileMisNCiAgICBzaW4oOCpYMikrY29zKDUqWDIpKw0KICAgIFgzXjAuNSsNCiAgICBleHAoWDQpLyhleHAoWDQpKzEpKw0KICAgIGxvZzIoWDUpKzINCn0NCg0KDQpwcmludChmc2ltKQ0KDQpOPTMwMA0Kc2V0LnNlZWQoMTIzNDUpDQoNClgxPXJ1bmlmKE4pDQpYMj1ydW5pZihOKQ0KWDM9cnVuaWYoTikNClg0PXJ1bmlmKE4pDQpYNT1ydW5pZihOKQ0KWT1mc2ltKFgxLFgyLFgzLFg0LFg1KQ0KDQpzaW1fZGY9ZGF0YV9mcmFtZShYMSxYMixYMyxYNCxYNSxZKQ0KDQpoZWFkKHNpbV9kZiklPiVrbml0cjo6a2FibGUoKQ0KYGBgDQoNCkRvIGThu68gbGnhu4d1IGzDoCBtw7QgcGjhu49uZyBuw6puIHRhIGPDsyB0aOG7gyBjaOG7pyDEkeG7mW5nIHjDoWMgxJHhu4tuaCB0csaw4bubYyBi4bqjbiBjaOG6pXQgcXVhbiBo4buHIHBo4bulIHRodeG7mWMgcmnDqm5nIHBo4bqnbiBuw6B5IGLhurFuZyBjw6FjIGjDoG0ga2jDoWMgbmhhdQ0KDQpgYGB7cn0NCnNpbV9kZiU+JWdhdGhlcihYMTpYNSxrZXk9IlByZWRpY3RvcnMiLHZhbHVlPSJWYWx1ZSIpJT4lDQogIGdncGxvdChhZXMoeD1WYWx1ZSx5PVksY29sPVByZWRpY3RvcnMsZmlsbD1QcmVkaWN0b3JzKSkrDQogIGdlb21fc21vb3RoKGFscGhhPTAuMikrDQogIGZhY2V0X3dyYXAoflByZWRpY3RvcnMsbmNvbD0zLHNjYWxlcz0iZnJlZSIpKw0KICB0aGVtZV9idygpDQpgYGANCg0KQsOieSBnaeG7nSBnaeG6oyDEkeG7i25oIG3hu6VjIHRpw6p1IGPhu6dhIGNow7puZyB0YSBsw6AgeMOieSBk4buxbmcgbeG7mXQgbcO0IGjDrG5oIGjhu5NpIHF1eSBjaG8gcGjDqXAgxrDhu5tjIGzGsOG7o25nIFkgdOG7qyBYMSxYMixYMyxYNCB2w6AgWDUuIFRyb25nIHRo4buRbmcga8OqIGPhu5UgxJFp4buDbiwgdGEgY2jhu4kgY8OzIHRo4buDIGTDuW5nIDEgY8O0bmcgY+G7pSBsw6AgbcO0IGjDrG5oIHR1eeG6v24gdMOtbmguIE5oxrBuZyB24bubaSBNYWNoaW5lIGxlYXJuaW5nLCB0YSBjw7Mgbmhp4buBdSwgdGjhuq1tIGNow60gcuG6pXQgbmhp4buBdSBjw7RuZyBj4bulIHRoYXkgdGjhur8uIA0KDQpUcm9uZyB0aMOtIGThu6UgbsOgeSwgTmhpIHPhur0gZOG7sW5nIDQgbcO0IGjDrG5oIGjhu5NpIHF1eSBraMOhYyBuaGF1OiAxIG3DtCBow6xuaCB0dXnhur9uIHTDrW5oIGdsbSwgbeG7mXQgbcO0IGjDrG5oIFJhbmRvbSBGb3Jlc3QsIG3hu5l0IG3DtCBow6xuaCAgU3VwcG9ydCB2ZWN0b3IgbWFjaGluZSAoZTEwNzEpIHbDoCAxIG3DtCBow6xuaCBHcmFkaWVudCBib29zdGluZyBHQk0sIHPhu60gZOG7pW5nIGNhcmV0DQoNCmBgYHtyLG1lc3NhZ2UgPSBGQUxTRSx3YXJuaW5nPUZBTFNFLHJlc3VsdHM9ImhpZGUifQ0KbGlicmFyeShyYW5kb21Gb3Jlc3QpDQpsaWJyYXJ5KGUxMDcxKQ0KbGlicmFyeShEQUxFWCkNCg0KbW9kZWxfcmYgPC0gY2FyZXQ6OnRyYWluKFl+LixkYXRhPSBzaW1fZGYsbWV0aG9kPSJyZiIsdmVyYm9zZT1GKQ0KbW9kZWxfc3ZtIDwtIHN2bShZIH4gLiwgc2ltX2RmKQ0KbW9kZWxfZ2JtIDwtIGNhcmV0Ojp0cmFpbihZfi4gLCBkYXRhID0gc2ltX2RmLCBtZXRob2Q9ImdibSIsdmVyYm9zZT1GKQ0KbW9kZWxfbG0gPC0gZ2xtKFkgfiAuLCBzaW1fZGYsZmFtaWx5PSJnYXVzc2lhbiIpDQpgYGANCg0KVGnhur9wIHRoZW8sIGdp4bqjIMSR4buLbmggY8OidSBo4buPaSBuZ2hpw6puIGPhu6l1IGPhu6dhIGNow7puZyB0YSBsw6AgeMOhYyDEkeG7i25oIG3hu5FpIGxpw6puIGjhu4cgYuG7mSBwaOG6rW4gZ2nhu69hIGvhur90IHF14bqjIFkgdsOgIG3hu5dpIHByZWRpY3RvcnMgdHJvbmcgNCBtw7QgaMOsbmggdHLDqm46IFRyxrDhu5tjIGjhur90IHRhIGTDuW5nIGjDoG0gZXhwbGFpbiBj4bunYSBEQUxFWCDEkeG7gyB04bqhbyByYSA0IG9iamVjdHMgbWV0YWRhdGEuDQoNCk5nb8OgaSByYSwgdGEgdOG6oW8gdGjDqm0gb2JqZWN0IHRo4bupIDUgY2jhu6lhIG3DtCBow6xuaCB0dXnhur9uIHTDrW5oIMSRw6MgxJHGsOG7o2MgZMO5bmcgxJHhu4MgbcO0IHBo4buPbmcgZOG7ryBsaeG7h3UgYmFuIMSR4bqndSAoaMOgbSBmc2ltKS4gTcO0IGjDrG5oIG7DoHkgY2jhuq9jIGNo4bqvbiBz4bq9IGNobyByYSBr4bq/dCBxdeG6oyBn4bqnbiB24bubaSBz4buxIHRo4buxYyBuaOG6pXQgbsOqbiDEkcaw4bujYyB4ZW0gbmjGsCBt4buRYyB0aGFtIGNoaeG6v3UNCg0KYGBge3IsbWVzc2FnZSA9IEZBTFNFLHdhcm5pbmc9RkFMU0V9DQpleF9yZiA8LSBleHBsYWluKG1vZGVsX3JmLGRhdGE9c2ltX2RmWywtNl0seT1zaW1fZGYkWSxsYWJlbD0iUmFuZG9tRm9yZXN0IikNCmV4X3N2bSA8LSBleHBsYWluKG1vZGVsX3N2bSxkYXRhPXNpbV9kZlssLTZdLHk9c2ltX2RmJFksbGFiZWw9IlNWTSIpDQpleF9sbSA8LSBleHBsYWluKG1vZGVsX2xtLGRhdGE9c2ltX2RmWywtNl0seT1zaW1fZGYkWSxsYWJlbD0iTGluZWFyIikNCmV4X2dibSA8LSBleHBsYWluKG1vZGVsX2dibSxkYXRhPXNpbV9kZlssLTZdLHk9c2ltX2RmJFksbGFiZWw9IkdCTSIpDQoNCmV4X3RyIDwtIGV4cGxhaW4obW9kZWxfbG0sZGF0YSA9IHNpbV9kZlssLTZdLCB5PXNpbV9kZiRZLA0KICAgICAgICAgICAgICAgICBwcmVkaWN0X2Z1bmN0aW9uID0gZnVuY3Rpb24obSwgeCkgZnNpbSh4WywxXSwgeFssMl0sIHhbLDNdLCB4Wyw0XSwgeFssNV0pLCBsYWJlbCA9ICJUcnVlIE1vZGVsIikNCmBgYA0KDQoNCsSQ4buDIGto4bqjbyBzw6F0IGxpw6puIGjhu4cgcGjhu6UgdGh14buZYyByacOqbmcgcGjhuqduIGdp4buvYSBt4buXaSBwcmVkaWN0b3IgdsOgIGvhur90IHF14bqjLCB0YSBjw7MgdGjhu4Mgc+G7rSBk4bulbmcgbeG7mXQgdHJvbmcgMiBow6BtIDogc2luZ2xlX3ZhcmlhYmxlKCApIGhv4bq3YyB2YXJpYWJsZV9yZXNwb25zZSgpLiANCg0KSGFpIHBoxrDGoW5nIHBow6FwIGPDsyB0aOG7gyDEkcaw4bujYyBjaOG7jW46IA0KDQoxKSB0w7l5IGNo4buJbmggdHlwZT3igJ1wZHDigJ0gc+G6vSDDoXAgZOG7pW5nIMKrIFBhcnRpYWwgRGVwZW5kZW5jZSBQbG90wrsgY+G7p2EgR3JlZW53ZWxsICgyMDE3IDogVGhlIFIgSm91cm5hbCA5KDEpOjQyMeKAkzM2LjxodHRwczovL2pvdXJuYWwuci1wcm9qZWN0Lm9yZy9hcmNoaXZlLzIwMTcvUkotMjAxNy0wMTYvaW5kZXguaHRtbD4pICwgDQoNCjIpIHTDuXkgY2jhu4luaCB0eXBlPeKAnWFsZeKAnSBz4bq9IMOhcCBk4bulbmcgcGjGsMahbmcgcGjDoXAg4oCcQWNjdW11bGF0ZWQgTG9jYWwgRWZmZWN0c+KAnSBj4bunYSBBcGxleSAoMjAxNzogQUxFUGxvdDogQWNjdW11bGF0ZWQgTG9jYWwgRWZmZWN0cyAoQWxlKSBQbG90cyBhbmQgUGFydGlhbCBEZXBlbmRlbmNlIChQZCkgUGxvdHMuIDxodHRwczovL0NSQU4uUi1wcm9qZWN0Lm9yZy9wYWNrYWdlPUFMRVBsb3Q+KQ0KDQpQaMawxqFuZyBwaMOhcCBBTEUgY2jDrW5oIHjDoWMgaMahbiB0cm9uZyB0csaw4budbmcgaOG7o3AgY8OzIGhp4buHbiB0xrDhu6NuZyDEkWEgY+G7mW5nIHR1eeG6v24uIEPDoWMgYmnhu4N1IMSR4buTIG7DoHkgY8OzIMO9IG5naMSpYSB0xrDGoW5nIHThu7EgbmjGsCDigJxNYXJnaW5hbGl6ZWQgZWZmZWN0IHBsb3TigJ0gZMOgbmggY2hvIG3DtCBow6xuaCBo4buTaSBxdXksIGto4bqjbyBzw6F0IHF1YW4gaOG7hyByacOqbmcgcGjhuqduIGdp4buvYSBwcmVkaWN0b3IgdsOgIG91dGNvbWUgxJHhu4F1IGzDoCBiaeG6v24gbGnDqm4gdOG7pWMuDQoNCk5oaSDDoXAgZOG7pW5nIGjDoG5nIGxv4bqhdCBow6BtIHNpbmdsZV92YXJpYWJsZSB2w6Aga+G6v3QgbuG7kWkgY2jDum5nIHbhu5tpIG5oYXUgdHJvbmcgaMOgbSBwbG90IGNobyB04burbmcgcHJlZGljdG9yIMSR4buDIHNvIHPDoW5oIGvhur90IHF14bqjIHBow6JuIHTDrWNoIGdp4buvYSA1IG3DtCBow6xuaA0KDQpgYGB7cixtZXNzYWdlID0gRkFMU0Usd2FybmluZz1GQUxTRX0NCnBsb3Qoc2luZ2xlX3ZhcmlhYmxlKGV4X3JmLCAiWDEiKSwNCiAgICAgc2luZ2xlX3ZhcmlhYmxlKGV4X3N2bSwgIlgxIiksDQogICAgIHNpbmdsZV92YXJpYWJsZShleF9sbSwgIlgxIiksDQogICAgIHNpbmdsZV92YXJpYWJsZShleF9nYm0sICJYMSIpLA0KICAgICBzaW5nbGVfdmFyaWFibGUoZXhfdHIsICJYMSIpDQogICAgICkgK3RoZW1lX2J3KCkgKyBnZ3RpdGxlKCJYMSIpDQoNCnBsb3Qoc2luZ2xlX3ZhcmlhYmxlKGV4X3JmLCAiWDIiKSwNCiAgICAgc2luZ2xlX3ZhcmlhYmxlKGV4X3N2bSwgIlgyIiksDQogICAgIHNpbmdsZV92YXJpYWJsZShleF9sbSwgIlgyIiksDQogICAgIHNpbmdsZV92YXJpYWJsZShleF9nYm0sICJYMiIpLA0KICAgICBzaW5nbGVfdmFyaWFibGUoZXhfdHIsICJYMiIpDQopICt0aGVtZV9idygpICsgZ2d0aXRsZSgiWDIiKQ0KDQpwbG90KHNpbmdsZV92YXJpYWJsZShleF9yZiwgIlgzIiksDQogICAgIHNpbmdsZV92YXJpYWJsZShleF9zdm0sICJYMyIpLA0KICAgICBzaW5nbGVfdmFyaWFibGUoZXhfbG0sICJYMyIpLA0KICAgICBzaW5nbGVfdmFyaWFibGUoZXhfZ2JtLCAiWDMiKSwNCiAgICAgc2luZ2xlX3ZhcmlhYmxlKGV4X3RyLCAiWDMiKQ0KKSArdGhlbWVfYncoKSArIGdndGl0bGUoIlgzIikNCg0KcGxvdChzaW5nbGVfdmFyaWFibGUoZXhfcmYsICJYNCIpLA0KICAgICBzaW5nbGVfdmFyaWFibGUoZXhfc3ZtLCAiWDQiKSwNCiAgICAgc2luZ2xlX3ZhcmlhYmxlKGV4X2xtLCAiWDQiKSwNCiAgICAgc2luZ2xlX3ZhcmlhYmxlKGV4X2dibSwgIlg0IiksDQogICAgIHNpbmdsZV92YXJpYWJsZShleF90ciwgIlg0IikNCikgK3RoZW1lX2J3KCkgKyBnZ3RpdGxlKCJYNCIpDQoNCnBsb3Qoc2luZ2xlX3ZhcmlhYmxlKGV4X3JmLCAiWDUiKSwNCiAgICAgc2luZ2xlX3ZhcmlhYmxlKGV4X3N2bSwgIlg1IiksDQogICAgIHNpbmdsZV92YXJpYWJsZShleF9sbSwgIlg1IiksDQogICAgIHNpbmdsZV92YXJpYWJsZShleF9nYm0sICJYNSIpLA0KICAgICBzaW5nbGVfdmFyaWFibGUoZXhfdHIsICJYNSIpDQopICt0aGVtZV9idygpICsgZ2d0aXRsZSgiWDUiKQ0KYGBgDQoNCkvhur90IHF14bqjIHBow6JuIHTDrWNoIG3hu5FpIGxpw6puIGjhu4cgcGjhu6UgdGh14buZYyByacOqbmcgcGjhuqduIGtow7RuZyBjaOG7iSBn4bujaSDDvSB24buBIHF1eSBsdeG6rXQgdMawxqFuZyBxdWFuIGdp4buvYSBt4buXaSBwcmVkaWN0b3IgdsOgIGvhur90IHF14bqjIFksIG5oxrBuZyDEkeG7kyB0aOG7iyBj4bunYSBjw6FjIGjDoG0gYuG7mSBwaOG6rW4gbsOgeSBjw7JuIGNobyBwaMOpcCBow6xuaCBkdW5nIHbhu4EgY8ahIGNo4bq/IGhv4bqhdCDEkeG7mW5nIGPhu6dhIG3hu5dpIGdp4bqjaSB0aHXhuq10IChhbGdvcml0aG0pIGPhu6dhIHThu6tuZyBsb+G6oWkgbcO0IGjDrG5oLCBnacO6cCB0YSBoaeG7g3UgY2jDum5nIMSRw6MgaOG7jWMgxJHGsOG7o2MgdGjDtG5nIHRpbiBnw6wgdOG7qyBk4buvIGxp4buHdSB2w6AgbMOgbSBjw6FjaCBuw6BvIMSR4buDIGPDsyB0aOG7gyB0w6FpIGhp4buHbiBs4bqhaSBt4buRaSB0xrDGoW5nIHF1YW4gdGjhu7FjIHThur8gZ2nhu69hWSB2w6AgcHJlZGljdG9yLg0KDQpL4bq/dCBxdeG6oyBuw6B5IHLhuqV0IHRow7ogduG7iywgdsOsIHRhIHRo4bqleSBy4bqxbmcgbcO0IGjDrG5oIGjhu5NpIHF1eSB0dXnhur9uIHTDrW5oIChnbG0pIGNo4buJIGPDsyBraOG6oyBuxINuZyBuaMOsbiBt4buNaSB0aOG7qSBuaMawIG3hu5l0IMSRxrDhu51uZyB0aOG6s25nLCBkw7kgY2hvIHF1eSBsdeG6rXQgdMawxqFuZyBxdWFuIHRyw6puIHRo4buxYyB04bq/IGzDoCBwaGkgdHV54bq/biB0w61uaC4gTsOzIGNo4buJIGPDsyB0aOG7gyB0aMOtY2ggbmdoaSB24bubaSBxdXkgbHXhuq10IG7DoHkgYuG6sW5nIGPDoWNoIGdpYSBnaeG6o20gSW50ZXJjZXB0IHbDoCBTbG9wZSBj4bunYSDEkeG7kyB0aOG7iy4gVHLDoWkgbOG6oWksIG5o4buvbmcgYWxnb3JpdGhtIGtow6FjIG5oxrAgU1ZNLCBHQk0gdsOgIFJGIGPDsyBraOG6oyBuxINuZyBsaW5oIGhv4bqhdCBoxqFuIG5oaeG7gXUsIGNobyBwaMOpcCBjaMO6bmcgImjhu41jIiDEkcaw4bujYyBjaMOtbmggeMOhYyBoxqFuIGPDoWMgcXV5IGx14bqtdCBwaGkgdHV54bq/biB0w61uaCB2w6AgY+G7kSBn4bqvbmcgbuG6r20gYuG6r3QsIHTDoWkgaGnhu4duIGzhuqFpIG5o4buvbmcgcXV5IGx14bqtdCBwaOG7qWMgdOG6oXAgbsOgeSAoYmFvIGfhu5NtIGjDoG0gc2luIHbDoCDEkWEgdGjhu6ljIGLhuq1jIGNhbykgdGhlbyBjw6FjaCB04buRdCBuaOG6pXQgY8OzIHRo4buDLiANCg0KIyBTbyBzw6FuaCBwaOG6qW0gY2jhuqV0IGdp4buvYSBuaGnhu4F1IG3DtCBow6xuaA0KDQpEQUxFWCBjaG8gcGjDqXAgc28gc8OhbmggaGnhu4d1IG7Eg25nIGdp4buvYSBuaGnhu4F1IG3DtCBow6xuaCBi4bqxbmcgaMOgbSBtb2RlbF9wZXJmb3JtYW5jZSwgdHV5IG5oacOqbiB0w61uaCBuxINuZyBuw6B5IGNo4buJIGPDsyDDvSBuZ2jEqWEgduG7m2kgbcO0IGjDrG5oIGjhu5NpIHF1eSwgdsOsIG3hurdjIMSR4buLbmggdGnDqnUgY2jDrSDEkcaw4bujYyBraOG6o28gc8OhdCBsw6Agcm9vdF9tZWFuX3NxdWFyZS4gVMOtbmggbsSDbmcgbsOgeSBjxaluZyBjw7MgduG6uyB0aOG7q2EgdsOsIHRhIGhvw6BuIHRvw6BuIGPDsyB0aOG7gyB0w61uaCB0aOG7pyBjw7RuZyBow6BuZyBjaOG7pWMgdGnDqnUgY2jDrSBraMOhYyBuaGF1LCBob+G6t2MgZOG7sW5nIDEgY29uZnVzaW9uIG1hdHJpeCBjaG8gYsOgaSB0b8OhbiBwaMOibiBsb+G6oWkgbcOgIGtow7RuZyBj4bqnbiBkw7luZyDEkeG6v24gcGFja2FnZSBuw6BvLg0KDQpW4bubaSB0aMOtIGThu6UgbcO0IHBo4buPbmcgdHLDqm4sIGtow7RuZyBuZ+G6oWMgbmhpw6puIGtoaSBiaeG6v3QgcuG6sW5nIG3DtCBow6xuaCB0dXnhur9uIHTDrW5oIGzDoCB54bq/dSBrw6ltIG5o4bqldCwgdHJvbmcga2hpIFJhbmRvbSBGb3Jlc3QgbMOgIGNow61uaCB4w6FjIHThu5FpIMawdSB24bubaSBybXNlIHRo4bqlcCBuaOG6pXQuDQoNCmBgYHtyLG1lc3NhZ2UgPSBGQUxTRSx3YXJuaW5nPUZBTFNFfQ0KbXBfcmYgPC0gbW9kZWxfcGVyZm9ybWFuY2UoZXhfcmYpDQptcF9nYm0gPC0gbW9kZWxfcGVyZm9ybWFuY2UoZXhfZ2JtKQ0KbXBfbG0gPC0gbW9kZWxfcGVyZm9ybWFuY2UoZXhfbG0pDQptcF9zdm0gPC0gbW9kZWxfcGVyZm9ybWFuY2UoZXhfc3ZtKQ0KDQpwbG90KG1wX3JmLG1wX2dibSxtcF9sbSxtcF9zdm0sZ2VvbT0iYm94cGxvdCIpK3RoZW1lX2J3KCkNCmBgYA0KDQojIMSQw6FuaCBnacOhIHZhaSB0csOyIGPhu6dhIHThu6tuZyBiaeG6v24gdHJvbmcgbcO0IGjDrG5oDQoNCkto4bqjbyBzw6F0IHZhaSB0csOyIGPhu6dhIG3hu5dpIHByZWRpY3RvciAoZmVhdHVyZSkgdHJvbmcgbcO0IGjDrG5oIGPFqW5nIGzDoCBt4buZdCBjw6FjaCDEkeG7gyBoaeG7g3UgY8ahIGNo4bq/IGhv4bqhdCDEkeG7mW5nIGPhu6dhIG3DtCBow6xuaC4gSOG6p3UgaOG6v3Qgbmjhu69uZyBjw7RuZyBj4bulIE1hY2hpbmUgbGVhcm5pbmcgbmjGsCBtbHIsIGgybyDEkeG7gXUgY8OzIGjhu5cgdHLhu6MgdMOtbmggbsSDbmcga2jhuqNvIHPDoXQgdmFyaWFibGUgaW1wb3J0YW5jZSDEkcaw4bujYyBo4buXIHRy4bujLCB0dXkgbmhpw6puIGtow6FpIG5p4buHbSAibeG7qWMgxJHhu5kgcXVhbiB0cuG7jW5nIiBjw7MgdGjhu4MgxJHGsOG7o2MgxJHhu4tuaCBuZ2jEqWEgdGhlbyBuaGnhu4F1IGPDoWNoIGtow6FjIG5oYXUgLCB0aMOtIGThu6UgdHJvbmcgcGFja2FnZSByYW5kb21Gb3Jlc3RleHBsYWluZXIgY8OzIMSR4bq/biA3IHRpw6p1IGNow60ga2jDoWMgbmhhdSBjaG8gcGjDqXAgeMOhYyDEkeG7i25oIG3hu5l0IGZlYXR1cmUgY8OzIHF1YW4gdHLhu41uZyBoYXkga2jDtG5nLg0KDQpQYWNrYWdlIERBTEVYIGNo4buJIHPhu60gZOG7pW5nIDEgcGjGsMahbmcgcGjDoXAgZHV5IG5o4bqldCBraOG6o28gc8OhdCBWYXJpYWJsZSBJbXBvcnRhbmNlIGThu7FhIHbDoG8ga+G6v3QgcXXhuqMgY+G7p2EgaMOgbSBsb3NzIHF1YSBuaGnhu4F1IGzGsOG7o3QgcGVybXV0YXRpb24uIMSQ4buDIHRo4butIG5naGnhu4dtIHTDrW5oIG7Eg25nIG7DoHksIE5oaSBz4bq9IMSRxrBhIHJhIG3hu5l0IHRow60gZOG7pSBtaW5oIGjhu41hIGPDsyB0aOG7sWM6IMSQw6J5IGzDoCBt4buZdCBuZ2hpw6puIGPhu6l1IGPhu6dhIE5pZXJlbmJlcmcgRFcgZXQgYWwuIG7Eg20gMTk4OSBuaOG6sW0ga2jhuqNvIHPDoXQgcXVhbiBo4buHIGdp4buvYSBu4buTbmcgxJHhu5kgcmV0aW5vbCB0cm9uZyBtw6F1IChiaeG6v24ga+G6v3QgcXXhuqMpIHbhu5tpIG3hu5l0IHPhu5EgeeG6v3UgdOG7kSBraMOhYy4NCg0KYGBge3IsbWVzc2FnZSA9IEZBTFNFLHdhcm5pbmc9RkFMU0V9DQpiZXRhZGY8LXJlYWQuY3N2KCJodHRwczovL3d3dy5vcGVubWwub3JnL2RhdGEvZ2V0X2Nzdi81MjYyMy9wbGFzbWFfcmV0aW5vbC5jc3YiKQ0KDQpiZXRhZGYlPiVoZWFkKCklPiVrbml0cjo6a2FibGUoKQ0KYGBgDQoNCsSQw6J5IGzDoCBt4buZdCBt4bqhbmcgbMaw4bubaSB0xrDGoW5nIHF1YW4gZ2nhu69hIGPDoWMgYmnhur9uIMSR4buLbmggbMaw4bujbmcgdHJvbmcgZOG7ryBsaeG7h3UsIGPDoWMgbeG7kWkgbGnDqm4ga+G6v3QgbGnDqm4gdOG7pWMgdMawxqFuZyDhu6luZyB24bubaSBwX3ZhbHVlPDAuMDUgY+G7p2EgMSBwaMOibiB0w61jaCB0xrDGoW5nIHF1YW4gUGVhcnNvbiAsIG5nxrDhu6NjIGzhuqFpIG5o4buvbmcgxJFv4bqhbiDEkeG7qXQga2jDumMgY8OzIHBfdmFsdWUgPiAwLjA1IChraMO0bmcgY8OzIHTGsMahbmcgcXVhbikuDQoNCmBgYHtyLG1lc3NhZ2UgPSBGQUxTRSx3YXJuaW5nPUZBTFNFfQ0KY29ybWF0PWJldGFkZiU+JQ0KICBkcGx5cjo6c2VsZWN0KC1jKFNFWCxTTU9LU1RBVCxWSVRVU0UpKSU+JQ0KICBhcy5tYXRyaXgoKSU+JQ0KICBzY2FsZSgpDQoNCm09YXMubWF0cml4KGNvcihjb3JtYXQsDQogICAgICAgICAgICAgICAgbWV0aG9kPSJwZWFyc29uIiwNCiAgICAgICAgICAgICAgICB1c2U9InBhaXJ3aXNlLmNvbXBsZXRlLm9icyIpKQ0KDQpjb3IubXRlc3QgPC0gZnVuY3Rpb24obWF0LCAuLi4pIHsNCiAgbWF0IDwtIGFzLm1hdHJpeChtYXQpDQogIG4gPC0gbmNvbChtYXQpDQogIHAubWF0PC0gbWF0cml4KE5BLCBuLCBuKQ0KICBkaWFnKHAubWF0KSA8LSAwDQogIGZvciAoaSBpbiAxOihuIC0gMSkpIHsNCiAgICBmb3IgKGogaW4gKGkgKyAxKTpuKSB7DQogICAgICB0bXAgPC0gY29yLnRlc3QobWF0WywgaV0sIG1hdFssIGpdLCAuLi4pDQogICAgICBwLm1hdFtpLCBqXSA8LSBwLm1hdFtqLCBpXSA8LSB0bXAkcC52YWx1ZQ0KICAgIH0NCiAgfQ0KICBjb2xuYW1lcyhwLm1hdCkgPC0gcm93bmFtZXMocC5tYXQpIDwtIGNvbG5hbWVzKG1hdCkNCiAgcC5tYXQNCn0NCg0KIyBtYXRyaXggb2YgdGhlIHAtdmFsdWUgb2YgdGhlIGNvcnJlbGF0aW9uDQpwLm1hdCA8LSBjb3IubXRlc3QoY29ybWF0KQ0KDQpsaWJyYXJ5KGlncmFwaCkNCg0KZGlhZyhwLm1hdCk8LTANCg0KbGlicmFyeShnZ3JhcGgpDQoNCmNnMT1kYXRhLmZyYW1lKHJvdz1yb3duYW1lcyhwLm1hdClbcm93KHAubWF0KVt1cHBlci50cmkocC5tYXQpXV0sIA0KICAgICAgICAgICAgICAgY29sPWNvbG5hbWVzKHAubWF0KVtjb2wocC5tYXQpW3VwcGVyLnRyaShwLm1hdCldXSwgDQogICAgICAgICAgICAgICBjb3JyPXAubWF0W3VwcGVyLnRyaShwLm1hdCldKQ0KDQpjZzI9ZGF0YS5mcmFtZShyb3c9cm93bmFtZXMobSlbcm93KG0pW3VwcGVyLnRyaShtKV1dLCANCiAgICAgICAgICAgICAgIGNvbD1jb2xuYW1lcyhtKVtjb2wobSlbdXBwZXIudHJpKG0pXV0sIA0KICAgICAgICAgICAgICAgY29ycj1tW3VwcGVyLnRyaShtKV0pDQoNCm5hbWVzKGNnMSk9YygiZnJvbSIsInRvIiwicHZhbCIpDQpuYW1lcyhjZzIpPWMoImZyb20iLCJ0byIsImNvcnIiKQ0KDQpjZzIkcHZhbD1jZzEkcHZhbA0KY2cyPWNnMiU+JW11dGF0ZShwVmFsPWNnMSRwdmFsLFNpZz1pZl9lbHNlKGNnMSRwdmFsPDAuMDUsMSwwKSkNCg0KZ2RmPWZpbHRlcihjZzIscHZhbDwwLjA1KQ0KDQpncmFwaDwtZ3JhcGhfZnJvbV9kYXRhX2ZyYW1lKGNnMikNCg0KZ2dyYXBoKGdyYXBoLGNpcmN1bGFyPUYsbGF5b3V0PSJrayIpKw0KICBnZW9tX2VkZ2VfZmFuKGFlcyhsaW5ldHlwZT1mYWN0b3IoU2lnKSxjb2w9ZmFjdG9yKFNpZykpLA0KICAgICAgICAgICAgICAgIHdpZHRoPTEsDQogICAgICAgICAgICAgICAgc2hvdy5sZWdlbmQgPSBGLA0KICAgICAgICAgICAgICAgIGFscGhhPTAuNSkrDQogIGdlb21fbm9kZV9sYWJlbChhZXMobGFiZWwgPSBuYW1lKSkrDQogIGNvb3JkX2ZpeGVkKCkrDQogIHRoZW1lX2dyYXBoKCkrDQogIHNjYWxlX2VkZ2VfY29sb3JfbWFudWFsKHZhbHVlcz1jKCJyZWQiLCJ2aW9sZXQiKSkNCmBgYA0KDQpOaGkgZOG7sW5nIDMgbcO0IGjDrG5oIGjhu5NpIHF1eSBraMOhYyBuaGF1IMSR4buDIMaw4bubYyBsxrDhu6NuZyBiaeG6v24gUmV0aW5vbFBsYXNtYSB0aGVvIGPDoWMgYmnhur9uIGPDsm4gbOG6oWkNCg0KYGBge3IsbWVzc2FnZSA9IEZBTFNFLHdhcm5pbmc9RkFMU0UscmVzdWx0cz0iaGlkZSJ9DQptb2RlbF9yZiA8LSBjYXJldDo6dHJhaW4oUkVUUExBU01Bfi4sZGF0YT1iZXRhZGYsbWV0aG9kPSJyZiIsdmVyYm9zZT1GKQ0KbW9kZWxfc3ZtIDwtIHN2bShSRVRQTEFTTUF+LiwgYmV0YWRmKQ0KbW9kZWxfbG0gPC0gZ2xtKFJFVFBMQVNNQX4uLCBiZXRhZGYsZmFtaWx5PSJnYXVzc2lhbiIpDQpgYGANCg0KU2F1IMSRw7MsIE5oaSBjxaluZyB04bqhbyByYSAzIG9iamVjdCBtZXRhZGF0YSBjaG8gMyBtw7QgaMOsbmggbsOgeSBi4bqxbmcgaMOgbSBleHBsYWluLg0KDQpgYGB7cixtZXNzYWdlID0gRkFMU0Usd2FybmluZz1GQUxTRX0NCmV4X3JmIDwtIGV4cGxhaW4obW9kZWxfcmYsZGF0YT1iZXRhZGZbLC0xNF0seT1iZXRhZGYkUkVUUExBU01BLGxhYmVsPSJSYW5kb21Gb3Jlc3QiKQ0KZXhfc3ZtIDwtIGV4cGxhaW4obW9kZWxfc3ZtLGRhdGE9YmV0YWRmWywtMTRdLHk9YmV0YWRmJFJFVFBMQVNNQSxsYWJlbD0iU1ZNIikNCmV4X2xtIDwtIGV4cGxhaW4obW9kZWxfbG0sZGF0YT1iZXRhZGZbLC0xNF0seT1iZXRhZGYkUkVUUExBU01BLGxhYmVsPSJMaW5lYXIiKQ0KDQpgYGANCg0KSMOgbSB2YXJpYWJsZV9pbXBvcnRhbmNlIHPhur0gxJHDoW5oIGdpw6EgdmFpIHRyw7IgY+G7p2EgdOG7q25nIGJp4bq/biB0cm9uZyBtw7QgaMOsbmggYuG6sW5nIHBoxrDGoW5nIHBow6FwIHBlcm11dGF0aW9uIGPhu6dhIEZpc2hlciB2w6AgY8O0bmcgc+G7sSAoSm91cm5hbCBvZiBDb21wdXRhdGlvbmFsIGFuZCBHcmFwaGljYWwgU3RhdGlzdGljcy4gaHR0cDovL2FyeGl2Lm9yZy9hYnMvMTgwMS4wMTQ4OSA7IDIwMTgpIDogDQoNCkPDuiBwaMOhcCBow6BtIG7DoHk6DQoNCnZhcmlhYmxlX2ltcG9ydGFuY2UoZXhwbGFpbmVyLCBsb3NzX2Z1bmN0aW9uID0gZnVuY3Rpb24ob2JzZXJ2ZWQsIHByZWRpY3RlZCkNCiAgc3VtKChvYnNlcnZlZCAtIHByZWRpY3RlZCleMiksIC4uLiwgdHlwZSA9ICJyYXciLCBuX3NhbXBsZSA9IDEwMDApDQogIA0KTmhp4buBdSBwaGnDqm4gYuG6o24gbcO0IGjDrG5oIHPhur0gxJHGsOG7o2MgZOG7sW5nLCB0cm9uZyDEkcOzIHThu6tuZyBiaeG6v24gc+G6vSDEkcaw4bujYyBsb+G6oWkgYuG7jywga+G6v3QgcXXhuqMgY+G7p2EgaMOgbSBsb3NzX2Z1bmN0aW9uIHPhur0gbOG6p24gbMaw4bujdCDEkcaw4bujYyB0w61uaCBjaG8gOiBtw7QgaMOsbmggYsOjbyBow7JhLCBtw7QgaMOsbmggcuG7l25nIChjxqEgYuG6o24pLCB2w6AgY8OhYyBwaGnDqm4gYuG6o24gY8OybiBs4bqhaS4NCg0KxJDhu5FpIHbhu5tpIG3DtCBow6xuaCB0dXnhur9uIHTDrW5oLCB2YXJpYWJsZSBpbXBvcnRhbmNlIMSRxrDhu6NjIHN1eSBkaeG7hW4gdHLhu7FjIHRp4bq/cCB04burIGjhu4cgc+G7kSBo4buTaSBxdXkgdHJvbmcgbcO0IGjDrG5oLg0KDQpDxaluZyBuaMawIGjDoG0gcHJlZGljdCwgbuG7mWkgZHVuZyBow6BtIGxvc3NfZnVuY3Rpb24gY8WpbmcgY8OzIHRo4buDIHTDuXkgY2jhu4luaCB0aGVvIMO9IHRow61jaC4gTeG6t2MgxJHhu4tuaCBsw6Agcm9vdF9tZWFuX3NxdWFyZQ0KDQpgYGB7cixtZXNzYWdlID0gRkFMU0Usd2FybmluZz1GQUxTRX0NCnZpX3JmIDwtIHZhcmlhYmxlX2ltcG9ydGFuY2UoZXhfcmYsdHlwZT0iZGlmZmVyZW5jZSIpDQp2aV9zdm0gPC0gdmFyaWFibGVfaW1wb3J0YW5jZShleF9zdm0sdHlwZT0iZGlmZmVyZW5jZSIpDQp2aV9sbSA8LSB2YXJpYWJsZV9pbXBvcnRhbmNlKGV4X2xtLHR5cGU9ImRpZmZlcmVuY2UiKQ0KDQpwbG90KHZpX3JmLHZpX3N2bSx2aV9sbSkrdGhlbWVfYncoOCkNCmBgYA0KDQojIERp4buFbiBnaeG6o2kgaGnhu4d1IOG7qW5nIGLhu5kgcGjhuq1uIGPhu6dhIGJp4bq/biBy4budaSBy4bqhYy9waMOibiBuaMOzbQ0KDQpUcm9uZyB0aMOtIGThu6UgbWluaCBo4buNYSDhu58gdHLDqm4sIE5oaSDEkcOjIGto4bqjbyBzw6F0IHF1YW4gaOG7hyBwaOG7pSB0aHXhu5ljIHJpw6puZyBwaOG6p24vYuG7mSBwaOG6rW4gZ2nhu69hIGvhur90IHF14bqjIMSR4buLbmggbMaw4bujbmcgdsOgIHByZWRpY3RvciDEkeG7i25oIGzGsOG7o25nLiANCg0KVHJvbmcgdHLGsOG7nW5nIGjhu6NwIHByZWRpY3RvciBsw6AgYmnhur9uIHLhu51pIHLhuqFjIGhv4bq3YyBwaMOibiBuaMOzbSwgcGjGsMahbmcgcGjDoXAg4oCcRmFjdG9yIE1lcmluZyBQYXRo4oCdIGPhu6dhIFNpdGtvIHbDoCBCaWVjZSAoMjAxNykgc+G6vSDEkcaw4bujYyDDoXAgZOG7pW5nLCB24bubaSB0w7l5IGNo4buJbmg6IHR5cGUgPSDigJxmYWN0b3LigJ0uIEvhur90IHF14bqjIGzDoCBt4buZdCBiaeG7g3UgxJHhu5Mgc28gc8OhbmggaGnhu4d1IGjhu6luZyByacOqbmcgcGjhuqduIGNobyBt4buXaSBi4bqtYyB0cm9uZyBmYWN0b3IsIGjGoW4gbuG7r2EgMSBkZW5kb2dyYW0gc+G6vSBnb20gY8OhYyBsZXZlbHMgY8OzIGhp4buHdSDhu6luZyB0xrDGoW5nIMSRxrDGoW5nIG5oYXUgdGjDoG5oIDEgY+G7pW0gKGNsdXN0ZXIpLiDDnSB0xrDhu59uZyBuw6B5IGfhuqduIGdp4buRbmcgbmjGsCBwaMOibiB0w61jaCB0xrDGoW5nIHBo4bqjbiAvcG9zdC1ob2MgdGVzdCB0cm9uZyBjw6FjIHRoaeG6v3Qga+G6vyBBTk9WQQ0KDQpgYGB7cixtZXNzYWdlID0gRkFMU0Usd2FybmluZz1GQUxTRX0NCnBsb3QodmFyaWFibGVfcmVzcG9uc2UoZXhfcmYsICJWSVRVU0UiLHR5cGU9ImZhY3RvciIpDQopK3RoZW1lX2J3KDYpK2dndGl0bGUoIlZpdGFtaW4gQSB1c2UiKQ0KDQpwbG90KHZhcmlhYmxlX3Jlc3BvbnNlKGV4X3N2bSwgIlZJVFVTRSIsdHlwZT0iZmFjdG9yIikgKSt0aGVtZV9idyg2KStnZ3RpdGxlKCJWaXRhbWluIEEgdXNlIikNCg0KcGxvdCh2YXJpYWJsZV9yZXNwb25zZShleF9sbSwgIlZJVFVTRSIsdHlwZT0iZmFjdG9yIikNCikrdGhlbWVfYncoNikrZ2d0aXRsZSgiVml0YW1pbiBBIHVzZSIpDQpgYGANCg0KVGEgdGjhu60ga2jhuqNvIHPDoXQgcXVhbiBo4buHIGLhu5kgcGjhuq1uIGdp4buvYSBSZXRpbm9sIHbDoCAyIGJp4bq/biDEkeG7i25oIGzGsOG7o25nIGtow6FjOiBDaG9sZXN0ZXJvbCB2w6AgQWxjb2hvbA0KDQpgYGB7cixtZXNzYWdlID0gRkFMU0Usd2FybmluZz1GQUxTRX0NCnBsb3Qoc2luZ2xlX3ZhcmlhYmxlKGV4X3JmLCAiQUxDT0hPTCIpLA0KICAgICBzaW5nbGVfdmFyaWFibGUoZXhfc3ZtLCAiQUxDT0hPTCIpLA0KICAgICBzaW5nbGVfdmFyaWFibGUoZXhfbG0sICJBTENPSE9MIikNCikrdGhlbWVfYncoKStnZ3RpdGxlKCJBTENPSE9MIikNCg0KcGxvdCh2YXJpYWJsZV9yZXNwb25zZShleF9yZiwgIkNIT0xFU1RFUk9MIiksDQogICAgIHZhcmlhYmxlX3Jlc3BvbnNlKGV4X3N2bSwgIkNIT0xFU1RFUk9MIiksDQogICAgIHZhcmlhYmxlX3Jlc3BvbnNlKGV4X2xtLCAiQ0hPTEVTVEVST0wiKQ0KKSt0aGVtZV9idygpK2dndGl0bGUoIkNIT0xFU1RFUk9MIikNCmBgYA0KDQojIERp4buFbiBnaeG6o2kgbcO0IGjDrG5oIHBow6JuIGxv4bqhaSBoMm8NCg0KxJDhu4Mga+G6v3QgdGjDumMgYsOgaSBuw6B5LCBOaGkgc+G6vSB0aOG7rSDDoXAgZOG7pW5nIERBTEVYIGNobyBt4buZdCBtw7QgaMOsbmggR0JNIGThu7FuZyBi4bqxbmcgaDJvLiBUaMOtIGThu6UgbWluaCBo4buNYSBuw6B5IGTDuW5nIEThu68gbGnhu4d1IFRob3JhY2ljIFN1cmdlcnkgdOG7qyBuZ2hpw6puIGPhu6l1IGPhu6dhIFRvbWN6YWsgdsOgIGPhu5luZyBz4buxIChCYWxhbikgbmjhurFtIHjDonkgZOG7sW5nIG3DtCBow6xuaCB0acOqbiBsxrDhu6NuZyBuZ3V5IGPGoSB04butIHZvbmcgdHJvbmcgdsOybmcgMSBuxINtIOG7nyBi4buHbmggbmjDom4gcGjhuqt1IHRodeG6rXQgbOG7k25nIG5n4buxYy4gROG7ryBsaeG7h3UgZ+G7k20gMTYgcHJlZGljdG9ycyBiYW8gZ+G7k20gdHJp4buHdSBjaOG7qW5nIGNo4bupYyBuxINuZyB0aeG7gW4gcGjhuqt1LCBjaOG7iSBz4buRIGjDtCBo4bqlcCBrw70sIHRow7RuZyB0aW4gduG7gSBjaOG6qW4gxJFvw6FuLCBi4buHbmggbMO9IMSRaSBrw6htIHbDoCB0w6xuaCB0cuG6oW5nIGjDunQgdGh14buRYy4gTmdoacOqbiBj4bupdSBn4buRYyBz4butIGThu6VuZyBnaeG6o2kgdGh14bqtdCBTdXBwb3J0IHZlY3RvciBtYWNoaW5lLg0KDQpSZWZlcmVuY2U6IFRvbWN6YWsgZXQgYWwuICgyMDEzKS4gQm9vc3RlZCBTVk0gZm9yIGV4dHJhY3RpbmcgcnVsZXMgZnJvbSBpbWJhbGFuY2VkIGRhdGEgaW4gYXBwbGljYXRpb24gdG8gcHJlZGljdGlvbiBvZiB0aGUgcG9zdC1vcGVyYXRpdmUgbGlmZSBleHBlY3RhbmN5IGluIHRoZSBsdW5nIGNhbmNlciBwYXRpZW50cy4gQXBwbGllZCBTb2Z0IENvbXB1dGluZy4NCg0KTmhpIGThu7FuZyBt4buZdCBtw7QgaMOsbmggR0JNIGLhurFuZyBwYWNrYWdlIGgybyB04burIDkwJSBk4buvIGxp4buHdSBn4buRYw0KDQpgYGB7cixtZXNzYWdlID0gRkFMU0Usd2FybmluZz1GQUxTRSxyZXN1bHRzPSJoaWRlIn0NCmxpYnJhcnkoaDJvKQ0KDQpkZj1yZWFkLmNzdigiaHR0cHM6Ly93d3cub3Blbm1sLm9yZy9kYXRhL2dldF9jc3YvMTc2MTY1NC9waHBHdXU0aVIuY3N2IiklPiVhc190aWJibGUoKQ0KDQpjb2xuYW1lcyhkZik8LWMoIkRJQUciLCJGVkMiLCJGRVYxIiwiWlVCUk9EIiwiUEFJTiIsDQogICAgICAgICAgICAgICAgIkhFTU9QIiwiRFlTUE4iLCJDT1VHSCIsIldFQUsiLCJUR1JBRCIsDQogICAgICAgICAgICAgICAgIkRJQUIiLCJNSSIsIlBBRCIsIlNNT0tFIiwiQVNUSCIsIkFHRSIsIlNVUlYiKQ0KDQpkZiRTVVJWJTw+JWFzLmZhY3RvcigpJT4lDQogIHJlY29kZV9mYWN0b3IoLixgVFJVRWAgPSAiRGVhZCIsYEZBTFNFYCA9ICJTdXJ2aXZlZCIpDQoNCmxpYnJhcnkocnNhbXBsZSkNCg0Kc2V0LnNlZWQoMjA2KQ0KDQpkYXRhX3NwbGl0IDwtIGluaXRpYWxfc3BsaXQoZGYsIHN0cmF0YSA9ICJTVVJWIiwgcHJvcD0wLjkpDQp0cmFpbnNldCA8LSB0cmFpbmluZyhkYXRhX3NwbGl0KQ0KdGVzdHNldCA8LSB0ZXN0aW5nKGRhdGFfc3BsaXQpDQoNCmgyby5pbml0KG50aHJlYWRzID0gLTEsbWF4X21lbV9zaXplID0iNGciKQ0KDQp0cmFpbl9oZiA8LSBhcy5oMm8odHJhaW5zZXQpDQp0ZXN0X2hmIDwtIGFzLmgybyh0ZXN0c2V0KQ0KDQpmZWF0dXJlcz1zZXRkaWZmKGNvbG5hbWVzKHRyYWluX2hmKSwiU1VSViIpDQpsYWJlbD0iU1VSViINCg0KZ2Jtb2Q9aDJvLmdibSh4ID0gZmVhdHVyZXMsDQogICAgICAgICAgICAgICB5ID0gbGFiZWwsDQogICAgICAgICAgICAgICB0cmFpbmluZ19mcmFtZSA9IHRyYWluX2hmLG5mb2xkcz0xMCwNCiAgICAgICAgICAgICAgIGNhdGVnb3JpY2FsX2VuY29kaW5nPSJFbnVtIiwNCiAgICAgICAgICAgICAgIGJhbGFuY2VfY2xhc3NlcyA9IFRSVUUsDQogICAgICAgICAgICAgICBudHJlZXMgPTUwMCwgbWF4X2RlcHRoID0gMTAsbWluX3Jvd3MgPSAzMCxzYW1wbGVfcmF0ZT0wLjgsDQogICAgICAgICAgICAgICBrZWVwX2Nyb3NzX3ZhbGlkYXRpb25fZm9sZF9hc3NpZ25tZW50ID0gRiwgDQogICAgICAgICAgICAgICBrZWVwX2Nyb3NzX3ZhbGlkYXRpb25fcHJlZGljdGlvbnM9RiwNCiAgICAgICAgICAgICAgIHNjb3JlX2VhY2hfaXRlcmF0aW9uID0gRiwNCiAgICAgICAgICAgICAgIHNlZWQ9MjA2KQ0KYGBgDQoNCsSQ4buDIGPDsyB0aOG7gyDDoXAgZOG7pW5nIGPDoWMgaMOgbSBj4bunYSBwYWNrYWdlIERBTEVYLCB0YSBwaOG6o2kgdmnhur90IDEgaMOgbSBwcmVkaWN0IGNobyBwaMOpcCB4deG6pXQga+G6v3QgcXXhuqMgbMOgIHjDoWMgc3XhuqV0IGPhu6dhIDEgbmjDo24sIHRow60gZOG7pSAiRGVhZCIsIA0KDQpC4bqhbiBjw7MgdGjhu4MgdGVzdCB0aOG7rSBow6BtIG7DoHkgdHLDqm4gMTAgY2FzZXMgxJHhuqd1IHRpw6puIGPhu6dhIHRlc3RzZXQgbuG6v3UgYuG6oW4gdGjDrWNoDQoNCmBgYHtyLG1lc3NhZ2UgPSBGQUxTRSx3YXJuaW5nPUZBTFNFfQ0KcHJvYl9wcmVkaWN0IDwtIGZ1bmN0aW9uKG1vZGVsLG5ld2RhdGEpICB7DQogIG5ld2RhdGFfaDJvIDwtIGFzLmgybyhuZXdkYXRhKQ0KICByZXMgPC0gYXMuZGF0YS5mcmFtZShoMm8ucHJlZGljdChtb2RlbCwgbmV3ZGF0YV9oMm8pKQ0KICByZXR1cm4oYXMubnVtZXJpYyhyZXMkRGVhZCkpDQp9DQoNCnByb2JfcHJlZGljdChnYm1vZCx0ZXN0c2V0W2MoMToxMCksXSkNCmBgYA0KDQpHaeG6oyBz4butIHRhIG114buRbiBraOG6o28gc8OhdCBxdWFuIGjhu4cgcmnDqm5nIHBo4bqnbiBnaeG7r2EgZHVuZyB0w61jaCBz4buRbmcgKEZWQykgdGnhu4FuIHBo4bqrdSB24bubaSB4w6FjIHN14bqldCB04butIHZvbmcsIHRhIHPhur0gY8OzIGvhur90IHF14bqjIG5oxrAgc2F1Og0KDQpgYGB7cixtZXNzYWdlID0gRkFMU0Usd2FybmluZz1GQUxTRSxyZXN1bHRzPSJoaWRlIn0NCmV4cF9nYm1fdHJhaW48LSBleHBsYWluKG1vZGVsID0gZ2Jtb2QsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkYXRhID0gdHJhaW5zZXRbLC0xN10sICANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgeSA9IHRyYWluc2V0JFNVUlYsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIHByZWRpY3RfZnVuY3Rpb24gPXByb2JfcHJlZGljdCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGFiZWwgPSAiaDJvX0dCTSIpDQoNCnBkcF9GVkM9dmFyaWFibGVfcmVzcG9uc2UoZXhwX2dibV90cmFpbiwgdmFyaWFibGUgPSAiRlZDIikNCmBgYA0KDQpgYGB7cixtZXNzYWdlID0gRkFMU0Usd2FybmluZz1GQUxTRX0NCnBsb3QocGRwX0ZWQykrdGhlbWVfYncoKQ0KYGBgDQoNClTGsMahbmcgdOG7sSwgdGEgY8OzIHRo4buDIGto4bqjbyBzw6F0ICBxdWFuIGjhu4cgYuG7mSBwaOG6rW4gZ2nhu69hIERpYWdub3NpcyBsw6AgbeG7mXQgYmnhur9uIHLhu51pIHLhuqFjIG5oaeG7gXUgZ2nDoSB0cuG7i3bDoCBuZ3V5IGPGoSB04butIHZvbmcNCg0KYGBge3IsbWVzc2FnZSA9IEZBTFNFLHdhcm5pbmc9RkFMU0UscmVzdWx0cz0iaGlkZSJ9DQptcHBEaWFnPXZhcmlhYmxlX3Jlc3BvbnNlKGV4cF9nYm1fdHJhaW4sIHZhcmlhYmxlID0gIkRJQUciLCB0eXBlID0gImZhY3RvciIpDQpgYGANCg0KYGBge3IsbWVzc2FnZSA9IEZBTFNFLHdhcm5pbmc9RkFMU0V9DQpwbG90KG1wcERpYWcpK3RoZW1lX2J3KCkNCmBgYA0KDQojIERp4buFbiBnaeG6o2kgY2hvIHThu6tuZyBjw6EgdGjhu4MNCg0KU2F1IGPDuW5nLCBEQUxFWCBjaG8gcGjDqXAgZGnhu4VuIGdp4bqjaSBtw7QgaMOsbmggY2hvIG3hu5dpIGPDoSB0aOG7gyBi4bqxbmcgcGjGsMahbmcgcGjDoXAgcGjDom4gcsOjIG3DtCBow6xuaCBuaMawIHRyb25nIHBhY2thZSBicmVha2Rvd24gbcOgIE5oaSDEkcOjIGdp4bubaSB0aGnhu4d1IOG7nyBiw6BpIHRyxrDhu5tjLCB24bubaSBow6BtIHNpbmdsZV9wcmVkaWN0aW9uKCApLg0KDQpUaMOtIGThu6UgdGEgZGnhu4VuIGdp4bqjaSBr4bq/dCBxdeG6oyB0acOqbiBsxrDhu6NuZyBj4bunYSBtw7QgaMOsbmggY2hvIDEgdHLGsOG7nW5nIGjhu6NwIHThu60gdm9uZw0KDQpgYGB7cixtZXNzYWdlID0gRkFMU0Usd2FybmluZz1GQUxTRSxyZXN1bHRzPSJoaWRlIn0NCm5ld19jYXNlIDwtIHRlc3RzZXRbMixdDQpicmtfZ2JtMSA8LSBwcmVkaWN0aW9uX2JyZWFrZG93bihleHBfZ2JtX3RyYWluLG9ic2VydmF0aW9uID0gbmV3X2Nhc2UpDQpgYGANCg0KYGBge3IsbWVzc2FnZSA9IEZBTFNFLHdhcm5pbmc9RkFMU0V9DQpwbG90KGJya19nYm0xKSt0aGVtZV9idygpDQpgYGANCg0KVGhlbyBr4bq/dCBxdeG6oyBuw6B5LCBjw6FjIGJp4bq/biA6IHBow6JuIGxv4bqhaSBUIHRyb25nIFROTSwgRkVWMSB0aOG6pXAsIG3DoyBjaOG6qW4gxJFvw6FuIERHTjIsIMSRaeG7g20gWnVicm9kIGxv4bqhaSAyLCBjw7MgdHJp4buHdSBjaOG7qW5nIGhvLCBjw7MgaMO6dCB0aHXhu5FjIGzhuqduIGzGsOG7o3QgY8OzIOG6o25oIGjGsOG7n25nIG5o4bqldCDEkeG7i25oIMSR4bq/biB0acOqbiBsxrDhu6NuZyB04butIHZvbmcsIHRyb25nIGtoaSBz4buxIHbhuq9uZyBt4bq3dCBj4bunYSB0cmnhu4d1IGNo4bupbmcga2jDsyB0aOG7nywgdsOgIEZWQz0zLjk4IGzDoG0gZ2nhuqNtIG5ndXkgY8ahIHThu60gdm9uZyDhu58gYuG7h25oIG5ow6JuIG7DoHkuDQoNClRhIGPDsyB0aOG7gyBsw6BtIHTGsMahbmcgdOG7sSBjaG8gMyBtw7QgaMOsbmggaOG7k2kgcXV5IMaw4bubYyBsxrDhu6NuZyBnacOhIHRy4buLIFJldGlub2wgcGxhc21hIOG7nyB0csOqbjogVHV5IG5oacOqbiBr4bq/dCBxdeG6oyBkaeG7hW4gZ2nhuqNpIGPDsyB24bq7IGtow7RuZyB0xrDGoW5nIMSR4buTbmcgZ2nhu69hIDIgbcO0IGjDrG5oIFNWTSB2w6AgUmFuZG9tIEZvcmVzdA0KDQpgYGB7cn0NCmJrZF9yZj1wcmVkaWN0aW9uX2JyZWFrZG93bihleF9yZixvYnNlcnZhdGlvbj1iZXRhZGZbNTAsXSkNCg0KYmtkX3N2bT1wcmVkaWN0aW9uX2JyZWFrZG93bihleF9zdm0sb2JzZXJ2YXRpb249YmV0YWRmWzUwLF0pDQoNCnBsb3QoYmtkX3JmLGJrZF9zdm0pK3RoZW1lX2J3KDgpDQpgYGANCg0KIyBOaOG6rW4geMOpdA0KDQpT4buxIGfDs3AgbeG6t3QgY+G7p2EgcGFja2FnZSBEQUxFWCB2w6BvIGRhbmggc8OhY2ggbmjhu69uZyBjw7RuZyBj4bulIGRp4buFbiBnaeG6o2kgbuG7mWkgZHVuZyBtw7QgaMOsbmggbMOgIG3hu5l0IHTDrW4gaGnhu4d1IGzhuqFjIHF1YW4gY2hvIHRo4bqleSBjaMO6bmcgdGEgxJFhbmcgdGnhur9uIHLhuqV0IGfhuqduIMSR4bq/biBt4buZdCB0aOG7nWkga8OsIG3hu5tpLCB0cm9uZyDEkcOzIG3DtCBow6xuaCBo4buTaSBxdXkgdHV54bq/biB0w61uaCBraMO0bmcgY8OybiBsw6Agc+G7sSBs4buxYSBjaOG7jW4gZHV5IG5o4bqldCBjaG8gbmdoacOqbiBj4bupdSBZIGjhu41jLiBOaGnhu4F1IG3DtCBow6xuaCBraMOhYyB24buRbiB0aHXhu5ljIHbhu4EgdHLGsOG7nW5nIHBow6FpIFN0YXRpc3RpY2FsIGxlYXJuaW5nIGhheSBNYWNoaW5lIGxlYXJuaW5nIGPFqW5nIGPDsyB0aOG7gyDEkcaw4bujYyBkw7luZyBuaMawIGPDtG5nIGPhu6UgxJHhu4Mgc3V5IGRp4buFbiB0aOG7kW5nIGvDqiB2w6Aga2hhaSB0aMOhYyB0aMO0bmcgdGluIHThu6sgZOG7ryBsaeG7h3UgdGjhu7FjIG5naGnhu4dtLiBUaOG6rW0gY2jDrSBuaOG7r25nIG3DtCBow6xuaCB24buRbiDEkcaw4bujYyB4ZW0gbMOgIGjhu5lwIMSRZW4gY8WpbmcgY8OzIHRo4buDIMSRxrDhu6NjIHBow6JuIHTDrWNoIHbhu4EgY+G6pXUgdHLDumMgYsOqbiB0cm9uZywgY8WpbmcgbmjGsCBnaeG6o2kgdGjDrWNoIMSRxrDhu6NjIGPGoSBjaOG6vyBob+G6oXQgxJHhu5luZyBt4buZdCBjw6FjaCB0cuG7sWMgcXVhbi4NCg0K