1 Giới thiệu

Trong thời gian gần đây, Machine learning (ML) bắt đầu được ứng dụng phổ biến trong nghiên cứu y học lâm sàng. Các bác sĩ bắt đầu sử dụng những algorithm mới lạ, thí dụ SVM, Decision Tree, Random Forest,… ML cũng mang đến những quy trình mới, thí dụ cross validation, kiểm định độc lập và nhiều tiêu chí đánh giá, so sánh, lựa chọn mô hình mới. Tuy nhiên các bác sĩ vẫn gặp nhiều khó khăn khi tiếp thu dòng kiến thức cực lớn này. Một mặt, giới bác sĩ vẫn còn giữ quán tính rất lớn khi đi theo cách làm việc truyền thống và bị chi phối bởi cách suy nghĩ cũ theo trường phái thống kê y sinh. Thí dụ, khi nhắc đến việc dựng Mô hình, đa số bác sĩ thường chỉ nghĩ đến mục tiêu Diễn dịch (interpretive), họ chưa quen với mục tiêu tiên lượng. Trong các nghiên cứu y học, khái niệm mô hình gần như đồng nghĩa với Hồi quy tuyến tính, vì đây từng là công cụ phổ biến nhất mà môn thống kê hỗ trợ. Ngoài ra, việc kiểm tra đánh giá hiệu năng/phẩm chất của mô hình; so sánh mô hình, chọn lọc mô hình tối ưu… chưa được quan tâm đúng mức, và nếu có thực hiện cũng chỉ giới hạn trên dữ liệu hiện hành chứ không dùng dữ liệu độc lập. Bác sĩ cũng chưa quen với việc nhìn classifier đa biến như một test chẩn đoán để liên hệ chúng với những khái niệm đặc thù của ngành Y, thí dụ Likelihood ratios. Mặt khác vấn đề càng phức tạp hơn khi giữa ML và Thống kê có quá nhiều dị biệt về thuật ngữ (thậm chí có những điều cùng nguyên lý nhưng thuật ngữ cũng hoàn toàn khác nhau), ngăn cản bác sĩ học kiến thức từ Machine Learning và diễn đạt những tiêu chí đặc thù của ngành Y cho các data scientist bên ngoài.

Việc đánh giá mô hình chắc chắn cần thiết, ngay cả khi mục tiêu của nghiên cứu không phải là tiên lượng. Khi đánh giá mô hình, ta mới có thể kiểm soát, điều chỉnh những algorithm đạt hiệu quả tối ưu, so sánh giữa nhiều mô hình và chọn ra mô hình chính xác nhất, và xác nhận điều này trên một dữ liệu độc lập. Tuy nhiên câu hỏi cần giải đáp: Kiểm tra bằng những tiêu chí nào ?

Do đó, Nhi viết bài thực hành này với hy vọng giải quyết càng nhiều càng tốt các vấn đề nêu trên, với 3 mục tiêu chính:

  1. Liệt kê tất cả những chỉ số / tiêu chí có thể dùng để đánh giá hiệu năng, phẩm chất của một mô hình nhị phân, thí dụ Random Forest.

  2. Phân tích các dị biệt về thuật ngữ giữa ML và Thống kê y học, và chuẩn hóa chúng cho ứng dụng lâm sàng.

  3. Tính toán các chỉ số này trong R, theo cách hoàn toàn thủ công hoặc sử dụng các hàm viết sẵn từ các thư viện (R packages).

2 Thí dụ minh họa: Random Forest model

Để minh họa, ta phải có một mô hình. Nhi sẽ dùng lại bộ số liệu Heart disease. Đây là 1 tập hợp dữ liệu của hơn 600 bệnh nhân từ 4 bệnh viện (Cleveland,Budapest,Long Beach and Zurich). Ta sẽ dựng một mô hình tiên lượng cho bệnh Tim mạch dựa vào 14 biến số bao gồm Tuổi, Giới tính, Triệu chứng Đau ngực, cholesterol, fasting blood sugar test, và stress test gồm nhịp tim và đoạn ST của ECG. Algorithm được chọn là Random Forest. Công đoạn training sẽ được thực hiện bằng caret. Mô hình RF sẽ được huấn luyện trên 50% dữ liệu, 50% còn lại dùng để kiểm định độc lập. Quy trình huấn luyện do caret thực hiện tự động bằng cách tinh chỉnh tham số mtry, nhằm tối ưu hóa tiêu chí Accuracy.

library(tidyverse)

va=read.table("https://archive.ics.uci.edu/ml/machine-learning-databases/heart-disease/processed.va.data", sep =",",na.strings="?",strip.white=TRUE, fill = TRUE)%>%as_tibble()
hu=read.table("https://archive.ics.uci.edu/ml/machine-learning-databases/heart-disease/processed.hungarian.data", sep =",",na.strings="?",strip.white=TRUE, fill = TRUE)%>%as_tibble()
sw=read.table("https://archive.ics.uci.edu/ml/machine-learning-databases/heart-disease/processed.switzerland.data", sep =",",na.strings="?",strip.white=TRUE, fill = TRUE)%>%as_tibble()
cl=read.table("https://archive.ics.uci.edu/ml/machine-learning-databases/heart-disease/processed.cleveland.data", sep =",",na.strings="?",strip.white=TRUE, fill = TRUE)%>%as_tibble()

df=rbind(va,hu,sw,cl)

names(df)=c("Age","Sex","ChestPain",
            "RestBP","Chol","FBS",
            "RestECG","MaxHR","CPETAgina",
            "Oldpeak","Slope","CA","Thal","Class")

data<-df[,-c(11,12,13)]%>%filter(.,Chol!=0)%>%na.omit()

data$Sex%<>%as.factor()%>%
  recode_factor(.,`0` = "Female", 
                  `1` = "Male")

data$ChestPain%<>%as.factor()%>%
  recode_factor(.,`1` = "Typical", 
                `2` = "Atypical",
                `3` = "Non_aginal", 
                `4` = "asymptomatic" )

data$FBS%<>%as.factor()%>%
  recode_factor(.,`0` = "No", 
                `1` = "Yes")

data$RestECG%<>%as.factor()%>%
  recode_factor(.,`0` = "Normal", 
                `1` = "Abnormal_ST",
                `2` = "LVHypertrophy")

data$CPETAgina%<>%as.factor()%>%
  recode_factor(.,`0` = "No",
                `1` = "Yes")

data$Class%<>%as.factor()%>%
  recode_factor(.,`0` = "Negative",
                `1` = "Positive",
                `2` = "Positive", 
                `3` = "Positive",
                `4` = "Positive")

rm(cl,df,hu,sw,va)

library(caret)

set.seed(1234)
idTrain=createDataPartition(y=data$Class, p=0.5,list=FALSE)

trainset=data[idTrain,]
testset=data[-idTrain,]

#KRF

Control=trainControl(method= "repeatedcv",
                     number=5,
                     repeats=5,
                     classProbs=TRUE,
                     summaryFunction=multiClassSummary)

rfmod=caret::train(Class~.,
            data=trainset,
            method = "rf",
            trControl=Control,
            tuneLength=5)

Đầu tiên, ta có thể thấy việc đánh giá mô hình đã được caret áp dụng ngay từ công đoạn huấn luyện, caret lần lượt thử mtry=2,4,7,10… , mỗi lần như vậy mô hình được dựng trên 4 blocks dữ liệu và kiểm định trên block còn lại, lặp lại 5 lượt. Cuối cùng, tiêu chí Accuracy được chọn để quyết định mtry tối ưu=2.

Trong kết quả crossvalidation, ta cũng có thể thấy những chỉ số khác như AUC, Kappa, F1, Sensitivity, Specificity, Balanced accuracy. Chúng ta sẽ nói về chúng sâu hơn trong phần sau, các bạn kiên nhẫn.

rfmod
## Random Forest 
## 
## 331 samples
##  10 predictor
##   2 classes: 'Negative', 'Positive' 
## 
## No pre-processing
## Resampling: Cross-Validated (5 fold, repeated 5 times) 
## Summary of sample sizes: 265, 264, 265, 265, 265, 265, ... 
## Resampling results across tuning parameters:
## 
##   mtry  logLoss    AUC        prAUC      Accuracy   Kappa      F1       
##    2    0.4318672  0.8853035  0.8539108  0.7784629  0.5546227  0.7944399
##    4    0.4466792  0.8752756  0.8445773  0.7753411  0.5488683  0.7895396
##    7    0.4597419  0.8651575  0.8335033  0.7747536  0.5479423  0.7877628
##   10    0.4725115  0.8603050  0.8257138  0.7638525  0.5260140  0.7774332
##   13    0.4744599  0.8598974  0.8149111  0.7596005  0.5169086  0.7758893
##   Sensitivity  Specificity  Pos_Pred_Value  Neg_Pred_Value  Precision
##   0.8175462    0.7357258    0.7773326       0.7892797       0.7773326
##   0.8072605    0.7406452    0.7768561       0.7819277       0.7768561
##   0.8003697    0.7469355    0.7793826       0.7767778       0.7793826
##   0.7887731    0.7367339    0.7710711       0.7640531       0.7710711
##   0.7980504    0.7175000    0.7588405       0.7687769       0.7588405
##   Recall     Detection_Rate  Balanced_Accuracy
##   0.8175462  0.4296036       0.7766360        
##   0.8072605  0.4241397       0.7739528        
##   0.8003697  0.4205393       0.7736526        
##   0.7887731  0.4144597       0.7627535        
##   0.7980504  0.4192994       0.7577752        
## 
## Accuracy was used to select the optimal model using the largest value.
## The final value used for the model was mtry = 2.
plot(rfmod)

3 Dữ liệu kết quả tiên lượng

Việc kiểm định mô hình sẽ được thực hiện độc lập trên dữ liệu testset (n=330), đầu tiên, chúng ta tạo ra một dataframe có nội dung như sau:

dfpred=data_frame(Truth=testset$Class,
                  BinTruth=if_else(testset$Class=="Positive",1,0),
                  ClassNeg=predict(rfmod,newdata=testset,type="prob")%>%.[[1]],
                  ClassPos=predict(rfmod,newdata=testset,type="prob")%>%.[[2]],
                  ClassLab=predict(rfmod,newdata=testset))

dfpred%>%head()
## # A tibble: 6 x 5
##   Truth    BinTruth ClassNeg ClassPos ClassLab
##   <fctr>      <dbl>    <dbl>    <dbl> <fctr>  
## 1 Negative     0      0.560     0.440 Negative
## 2 Positive     1.00   0.0480    0.952 Positive
## 3 Positive     1.00   0.100     0.900 Positive
## 4 Negative     0      0.446     0.554 Positive
## 5 Positive     1.00   0.592     0.408 Negative
## 6 Negative     0      0.854     0.146 Negative
  1. Truth: Giá trị thực tế quan sát được,dưới dạng factor.

  2. BinTruth: Giá trị thực tế dưới dạng xác suất tuyệt đối: Positive=1, Negative=0.

  3. ClassNeg: Xác suất tiên lượng cho nhãn Negative.

  4. ClassPos: Xác suất tiên lượng cho nhãn Positive.

  5. ClassLab: Kết quả phân loại của mô hình.

Từ dữ liệu này, chúng ta có thể xác định được tất cả các tiêu chí về phẩm chất mô hình.

4 Confusion matrix

Đầu tiên, ta bàn về một hình thức tóm tắt kết quả về hiệu năng mô hình rất lý thú: Confusion matrix hay error matrix. Confusion matrix là thuật ngữ của giới Machine learning, tương đương với khái niệm contingency table hay cross-table bên giới thống kê. Nó có bản chất là một bảng phân phối tần số 2 chiều (bảng chéo) cho phép trình bày tỉ lệ tương hợp và bất xứng giữa Thực tế và kết quả phân loại của quy luật cần kiểm tra (mô hình).Trong bài này, ta chỉ xét trường hợp đơn giản nhất của confusion matrix áp dụng cho bài toán nhị phân (Binary classification).Khi đó, Confusion matrix trình bày tần suất của 4 tổ hợp: TP (True Positive), TN (True Positive), FP (False positive) và FN (False negetive). True (Đúng) chỉ sự tương hợp, khi kết quả phân loại phù hợp với giá trị thực tế; False (Sai) chỉ sự bất xứng hay nhầm lẫn, khi mô hình phân loại nhầm so với thực tế.

Ý nghĩa quan trọng của confusion matrix là ở chỗ TP,TN,FP,FN là 4 chỉ số cơ bản, từ đó cho phép suy ra kết quả của hầu hết những chỉ số khác.

Confusion matrix có thể tính tự động từ caret:

cf=caret::confusionMatrix(reference=dfpred$Truth,
                       data=dfpred$ClassLab,
                       positive="Positive",
                       mode="everything")

cf$table
##           Reference
## Prediction Negative Positive
##   Negative      139       32
##   Positive       34      125

Tuy nhiên, ta có thể tự dựng confusion matrix thủ công như sau:

confmat=table(Classification=dfpred$ClassLab,Truth=dfpred$Truth)

confmat
##               Truth
## Classification Negative Positive
##       Negative      139       32
##       Positive       34      125

Ta cũng có thể tính thủ công trực tiếp từng chỉ số một từ dữ liệu kiểm định:

TP=with(dfpred,sum(ClassLab==Truth & ClassLab=="Positive"))
TN=with(dfpred,sum(ClassLab==Truth & ClassLab!="Positive"))
FN=with(dfpred,sum(ClassLab!=Truth & Truth=="Positive"))
FP=with(dfpred,sum(ClassLab!=Truth & Truth!="Positive"))

cbind(TP,TN,FN,FP)
##       TP  TN FN FP
## [1,] 125 139 32 34

Lưu ý: Positive/negative chỉ là tên gọi của 2 nhãn giá trị, mang tính quy ước và có ý nghĩa tương đối tùy theo mục tiêu của người dùng (Dương tính = điều ta quan tâm tìm kiếm và Âm tính = loại trừ (tất cả mọi) thứ còn lại).

Trong y học lâm sàng còn dịch là “Thật” và “Giả”, nhưng Nhi cho rằng phát biểu “Mô hình phân loại sai, mô hình chẩn đoán lầm, loại trừ nhầm” thì dễ hiểu hơn so với “kết quả âm tính giả/dương tính giả”. Khi áp dụng cho mục tiêu chẩn đoán, TP có thể dịch là “chẩn đoán/phát hiện đúng/trúng”, TN là “loại trừ đúng”, FP là “chẩn đoán nhầm/sai”, FN là “bỏ sót, loại trừ nhầm”.

5 Tính hữu dụng:

Đầu tiên, ta sẽ kiểm tra xem mô hình hữu dụng đến mức nào, thông qua các chỉ số như sau:

  1. Sensitivity - Độ nhạy

True Positive rate (TPR) : tỉ lệ phân loại Positive đúng trên tổng số các trường hợp Positive:

\[TPR = Sensitivity = Recall = \frac{TP}{TP+FN} = \frac{TP}{\sum Positive}\]

Tỉ lệ TPR còn có tên gọi khác là Sensitivity (độ nhạy), hit rate (tỉ lệ trúng đích), và Recall. Do trên lâm sàng ta quen với khái niệm Sensitivity, Nhi sẽ sử dụng nó như thuật ngữ duy nhất. Recall là thuật ngữ ít thông dụng hơn nhưng có nhiều tài liệu sử dụng nó nên các bạn cần ghi nhớ.

  1. Specificity: Độ đặc hiệu

True negative rate (TNR) : tỉ lệ loại trừ đúng trên tổng số các trường hợp Negative hay còn gọi là Specificity (độ đặc hiệu) trong y học lâm sàng.

\[TNR = Specificity = \frac{TN}{TN+FP} = \frac{TP}{\sum Negative}\] Nhận xét:

Như đã nói ở trên, ý nghĩa của Positive/negative là tương đối, tương tự cho Sensitivity/Specificity. Nếu người bác sĩ đảo vị trí của mục tiêu phát hiện/loại trừ thì Sens/specs sẽ đổi vị trí cho nhau, nhưng chúng đều đo lường tính hữu dụng của mô hình (test chẩn đoán), hay khả năng cho phép đáp ứng mục tiêu phát hiện/loại trừ bệnh lý của người bác sĩ. Một mô hình có sensitivity cao cũng giống như một bác sĩ “can đảm” nói “Có” trước bệnh nhân, và specificity cao là sự can đảm nói “Không” khi loại trừ đối tượng không mắc bệnh.

Sensitivity và Specificity luôn phải đi cặp với nhau, vì chúng bổ sung cho nhau. Mỗi chỉ số này chỉ mới kiểm tra 1 hàng trong Confusion matrix (1 ứng dụng : phát hiện/loại trừ) và do đó, độc lập với ứng dụng còn lại. Một vài mô hình có thể rất tốt cho ứng dung phát hiện nhưng kém cho ứng dụng loại trừ và ngược lại. Mặt khác, một mô hình “cực đoan” (null model) - luôn luôn trả lời “Có” hoặc “Không” sẽ cho ra giá trị zero cho Sensitivity hoặc Specificity. Khi một tác giả nào đó chỉ trình bày Sensitivity hoặc Specificity nhưng cố tình lờ đi chỉ số còn lại, anh ta/chị ta đang muốn che giấu sự thật, và một nửa sự thật không phải là sự thật.

6 Tính chính xác

Khái niệm “chính xác” rất khó để diễn đạt, vì ở đây ta có đến 2 nhãn giá trị, do đó ta có thể bàn về tính chính xác chung chung, tổng quát với chỉ số Accuracy:

  1. Accuracy : (ACC)

Có thể tạm dịch thuật ngữ Acuracy như “độ chính xác tổng quát”, vì nó đơn giản là tỉ lệ của tất cả trường hợp phân loại Đúng (không phân biệt negative/positive) trên toàn bộ trường hợp trong mẫu kiểm định.

\[Accuracy = \frac{TP+TN}{TP+TN+FP+FN}\]

Đây là tiêu chí phổ biến nhất (thường được nghĩ đến đầu tiên) khi kiểm định hiệu năng của mô hình phân loại, tuy nhiên giá trị thực dụng của nó thường kém vì nó không đặc hiệu cho một mục tiêu nào cả.

  1. Balanced accuracy:

Khi 2 nhãn Positive/Negative bị mất cân đối, một tiêu chí khác phù hợp hơn là Balanced accuracy (BAC) hay độ chính xác sau cân bằng:

\[BAC = \frac{TPR+TNR}{2}\]

Cho mục tiêu chuyên biệt hơn, ta có thể sử dụng:

  1. Precision (độ chính xác) hay Positive predictive value (PPV):

Là tỉ lệ thực sự positive trên tổng số các trường hợp được mô hình dán nhãn “Positive”. Precision là một thuật ngữ bên Data science và trong y học nó tương đương với khái niệm « PPV ». Nó đo lường tính “xác định”, hay khả năng phân loại Positive chính xác của mô hình. Nhi thích dùng Precision hơn PPV, vì thuật ngữ “value” tuy chuyên biệt cho Positive, nhưng không nói lên ý nghĩa “khả năng” (performance), còn Positive hay Negative chỉ là tên gọi quy ước.

\[Precision = PPV = \frac{TP}{TP+FP}\] 4) Tương tự: Negative predictive value (NPV) đo lường khả năng loại trừ chính xác:

\[NPV = \frac{TN}{TN+FN}\]

Lưu ý: Cẩn thận phân biệt giữa PPV/sensitivity và Recall/Precision. Precision đo lường tính “chuẩn xác”, Recall đo lường tính “hữu dụng”. Mẹo để nhớ là “Dám quyết định (Recall cao) và quyết định đúng (Precision cao)”. Cũng như cẩn thận khi dùng thuật ngữ “Độ chính xác” vì gây nhầm lẫn giữa Accuracy và Precision (Accuracy không phân biệt Positive/negative). Có thể dịch Precision là “Khả năng xác định”.

Precision không khảo sát mô hình môt cách độc lập, mà đặt mô hình vào một bối cảnh (dữ liệu). Do đó ta không nói chung chung:”mô hình chính xác”, mà là :” Mô hình chính xác đối với mẫu/dữ liệu hiện thời”.

  1. F score

Do Recall (Sensitivity) và Precision là hai khái niệm khác nhau, ta có chỉ số F1 score cho phép đánh giá cân bằng giữa 2 phẩm chất này.

F1 score: Được định nghĩa như trung bình điều hòa (harmonic mean) giữa Precision và Recall (PPV và Sens) . \[F1 = 2\times \frac{1}{\frac{1}{Recall}+\frac{1}{Precision}}\] \[F1 = 2\times \frac{Precision \times Recall}{Precision + Recall}\] Hoặc:

\[F1 = \frac{2\times TP}{2\times TP+FP+FN}\]

F1 là trường hợp đặc biệt của công thức tổng quát Fbeta:

\[F_{\beta } = (1+\beta ^{2})\times \frac{Precision\times Recall}{(\beta ^{2}\times Precision)+Recall}\]

Hay:

\[F_{\beta } = \frac{(1+\beta ^{2})\times TP}{(1+\beta ^{2})\times TP + (\beta ^{2}\times FN) + FP}\]

Từ công thức này ta còn tính được chỉ số F2 (đặt trọng số cao hơn cho Recall so với Precision) và F0.5 (trọng số cao hơn cho Precision so với Recall).

Như vậy F1 được dùng khi ta quan tâm đồng đều vai trò của cả Precision và Recall, nói cách khác ta muốn Mô hình (quy luật chẩn đoán) vừa Nhạy, vừa chính xác. Việc lựa chọn giữa Recall (Sensitivity) và Precision (PPV) tùy thuộc vào mục tiêu ứng dụng của mô hình: người bác sĩ muốn Tầm soát bệnh hay muốn Xác định bệnh ? Nếu xem cả 2 đều quan trọng như nhau thì F1 là tiêu chí thích hợp nhất khi kiểm định mô hình phân loại vì khi F1 đạt tối ưu thì cả Precision và Recall đều phải tối ưu, ngược lại chỉ cần một trong 2 có giá trị thấp thì F1 sẽ thấp.

  1. G index:

Cũng liên quan đến sự kết hợp Precision và Recall, ta có G measure hay còn gọi là Fowlkes–Mallows index (1983). G sử dụng trung bình nhân:

\[G = \sqrt{Recall \times Precision} = \sqrt{PPV \times TPR}\]

7 Sai sót và nhầm lẫn

Một cách tiếp cận khác để đánh giá mô hình, đó là ta quan tâm đến nguy cơ nhầm lẫn, sai sót. Bạn có thể nghĩ đơn giản về sự bù trừ giữa chính xác/nhầm lẫn, nhưng quan hệ này phức tạp hơn cho những mục tiêu chuyên biệt, và bài toán phân loại nhiều nhãn giá trị, khi đó tính chính xác tổng quát không đảm bảovề tỉ lệ nhầm lẫn tối thiểu.Trong y học,nguy cơ phân loại nhầm là tiêu chí quan trọng cần kiểm tra .

Ngay từ Confusion matrix, ta đã có tần suất nhầm lẫn tuyệt đối : FN và FP. Từ đó, ta có thể tính tỉ lệ sai sót

  1. False negative rate: Tỉ lệ loại trừ nhầm (FNR) và

  2. False positive rate = Tỉ lệ phát hiện nhầm (FPR).

\[FNR = \frac{FN}{\sum Positive} = \frac{FN}{FN+TP} = 1-TPR\]

\[FPR = \frac{FP}{\sum Negative} = \frac{FP}{FP+TN} = 1-TNR\]

Một mô hình tốt cần có FNR và FPR thấp. Lưu ý là tầm quan trọng của FNR và FPR tùy thuộc vào bản chất của vấn đề. Trong bài toán xếp loại tín dụng, việc phân loại nhầm một hồ sơ vay vốn từ tốt thành xấu sẽ gây hậu quả ít nghiêm trọng hơn trường hợp ngược lại. Trong Y học thì khác, việc loại trừ nhầm lẫn một căn bệnh đôi khi sẽ gây hậu quả nghiêm trọng vì bệnh nhân có thể tử vong khi không được điều trị kịp thời, trong khi việc chẩn đoán nhầm người bình thường thành bệnh nhân thì hậu quả có thể ít nghiêm trọng hơn (do vẫn còn cơ hội kiểm chứng lại với những xét nghiệm bổ sung).

Hai chỉ số khác hiếm gặp hơn là:

  1. False discovery rate (FDR), tỉ lệ phát hiện nhầm, có ý nghĩa nghịch với Precision (PPV).

\[FDR = \frac{FP}{FP+TP} = 1-PPV = 1 – Precision\]

  1. False omission rate (FOR): tỉ lệ loại trừ nhầm

\[FDR = \frac{FN}{FN+TN} = 1-NPV\]

Lưu ý: tạm thời chúng ta tách khái niệm FPR, FNR, FDR ra khỏi khái niệm Sai lầm type I và Type II trong kiểm định giả thuyết thống kê, dù chúng có liên hệ với nhau. Thuật ngữ Sai lầm type I và II không giúp ích cho việc kiểm định mô hình phân loại.

  1. Tương tự BAC, ta cũng có thể tính Balanced Error rate (tỉ lệ sai lầm sau cân bằng):

\[BER = \frac{FPR+FNR}{2}\]

  1. Brier score:

Điểm số Brier (BS) được giới thiệu bởi Glenn W. Brier vào năm 1950. BS được xác định như giá trị trung bình của các bình phương sai số giữa xác suất dự báo bởi mô hình và xác suất thực tế. Lưu ý: BS chuyên biệt cho từng nhãn giá trị, thí dụ nếu đặt Positive làm mục tiêu, thì xác suất thực tế của một trường hợp Positive sẽ = 1, và ngược lại trường hợp negative có xác suất thực tế =0. Nếu trên một cá thể Positive mà mô hình ước tính ra xác suất là 0.79 chẳng hạn, thì bình phương khác biệt cho case đó là (0.79-1)^2 = 0.0441.

Một mô hình tốt sẽ ước tính xác suất với sai biệt nhỏ nhất có thể, cho tất cả nhãn giá trị và tất cả trường hợp trong mẫu kiểm định, khi đó Brier score sẽ thấp. Giá trị tối ưu của BS là 0, và giá trị tồi tệ nhất là 1. Do đó Brier score được xem như 1 tiêu chí để đánh giá khả năng Sai lầm của mô hình (tương tự FNR và FPR).

Ta có thể tính BS hoàn toàn thủ công hoặc sử dụng hàm bierscore trong package scoring

  1. Logarithmic loss

Một chỉ số khác là logloss, được sử dụng nhiều trong giới Machine learning, logloss được định nghĩa như trung bình của -log(xác suất dự báo).

Cho bài toán nhị phân, ta mặc định tính logloss theo xác suất cho nhãn Positive.

\[logloss = \frac{\sum -log(p_{i})}{N}\]

Logloss nhắm đến mục tiêu đo lường sai biệt của mô hình, giá trị tối thiểu (mô hình chính xác nhất) của logloss=0, nhưng không có ngưỡng cao nhất.

  1. Tương tự, ta có chỉ số Logarithmic scoring rule (LSR)

\[LSR = mean(log(p_{i}))\] LSR trái dấu với logloss nhưng ý nghĩa tương đương, mô hình càng chính xác thì LSR càng gần 0. Mô hình càng kém chính xác thì LSR tiến về -Inf

  1. Một chỉ số khác có thể tính là SSR : Spherical Scoring Rule,

SSR được xác định bằng trung bình của tích xác suất tiên lượng của một nhãn cho mỗi trường hợp i và tổng xác suất tiên lượng cho (các) nhãn j còn lại cho trường hợp i này :

\[SSR = Mean(P_{i}(\sum P_{ij}))\]

Giá trị SSR có thể được tính bởi hàm sphscore của package scoring.

  1. Mean misclassification error (MMCE)

Cuối cùng: Nếu xem kết quả kiểm định mô hình là một biến logical: Y=1 nếu phân loại nhầm, Y=0 nếu phân loại đúng, ta sẽ thống kê được sai số trung bình: Mean misclassification error (MMCE) :

\[mmce = \frac{\sum Prediction \neq Truth}{N}\]

8 Mô hình như test chẩn đoán:

Một khái niệm khác, rất phổ biến trong thống kê y học nhưng không được quan tâm bên giới Machine learning, đó là Likelihood ratios (Positive và Negative), tức tỉ số khả dĩ Dương và Âm.

LR+ được xác định bằng độ nhạy (của test chẩn đoán) chia cho (1-độ đặc hiệu). LR- được tính bằng (1-độ nhạy) chia cho độ đặc hiệu:

\[LR+ = \frac{SEN}{1-SPEC}\] \[LR- = \frac{1-SEN}{SPEC}\]

Một cách tổng quát, LR đo lường mức độ liên hệ giữa quy luật chẩn đoán và trạng thái bệnh lý. Thí dụ, LR+ cao (>1) cho thấy kết quả xét nghiệm dương tính có liên quan đến sự hiện diện của bệnh lý. Cạnh đó, LR còn là phương tiện trung gian cho phép biện luận, so sánh hiệu năng của một xét nghiệm chẩn đoán này với một xét nghiệm khác. Việc so sánh này dựa vào Pretest probability và Posttest probability.

Posttest probability của một xét nghiệm có thể được xác định theo 3 bước:

\[ pretest Odds = Pretest Prob / (1-Pretest Prob)\]

\[ Posttest Odds = Pretest odds * LR \]

\[Post test Prob = Posttest odds/(1+Post test odds)\]

Như vậy, một xét nghiệm mới nếu muốn được công nhận là tốt hơn so với một xét nghiệm cũ, xét nghiệm mới này phải có Posttest Prob cao hơn so với Pretest Prob, tức là LR phải > 1 và càng cao càng tốt. Người ta ước tính rằng để Post test probability cho chẩn đoán Có bệnh tăng hơn 15% thì LR+ phải lớn hơn 2, tương tự để post test prob cho phép loại trừ bệnh thì LR- phải nhỏ hơn 0,2. LR càng gần 1 thì hiệu năng của xét nghiệm càng thấp (so với Pretest prob). Post test Prob của xét nghiệm mới này lại có thể được dùng như Pretest Prob cho một bước tiếp theo trong quy trình chẩn đoán.

Trong thí dụ này, giả sử ta có prevalence của bệnh lý Tim mạch được ước tính từ trainset là 47.6% và dùng nó như PretestProb, từ LR+ = 4.05 ta có thể ước tính được Posttest Prob của quy luật chẩn đoán mới (mô hình Random Forest) là 0.79, tức là với quy luật mới ta đã làm thay đổi Pretest Prob lên tới +65.5 %, cho thấy mô hình mới là một công cụ thực sự có ích so với suy đoán ngẫu nhiên..

9 Tính tương hợp Tiên lượng/Thực tế:

  1. Trị số Kappa của Cohen:

Kappa là một trị số thống kê do Jacob Cohen đề xuất năm 1960 nhằm đo lường mức độ tương hợp giữa 2 hay nhiều kết quả đo lường (inter-rater agreement). Kappa được định nghĩa như:

\[\kappa = 1- \frac{1-p_{o}}{1-p_{e}}\]

Trong đó po là tỉ lệ tương hợp quan sát được trên thực tế, và pe là tỉ lệ tương hợp ước đoán theo phân bố ngẫu nhiên. Nếu sự tương hợp là tuyệt đối, Kappa=1, Nếu sự tương hợp kém (so với phân bố ngẫu nhiên) thì kappa giảm về 0.

Cho mục tiêu kiểm định phẩm chất mô hình, ta có thể áp dụng hàm cohen.kappa của package psych để tính giá trị Kappa và Weighted Kappa cho cả kết quả là Label lẫn Xác suất dự báo.

  1. Hệ số tương quan Matthews (MCC)

MCC là một phương pháp riêng của giới Machine learning, dùng để đánh giá phẩm chất của mô hình phân loại nhị phân, được giới thiệu bởi Brian W. Matthews vào năm 1975. Mục tiêu của MCC là khắc phục vấn đề dữ liệu bị mất cân bằng. MCC có bản chất là một hệ số tương quan giữa giá trị Thực tế và Kết quả dự báo của mô hình, nó được xác định như sau:

\[MCC = \frac{(TP*TN)-(FP*FN)}{\sqrt{(TP+FP)*(TP+FN)*(TN+FP)*(TN+FN)}}\]

MCC có liên hệ trực tiếp với trị số thống kê Chi bình phương của bảng 2x2 :

\[\left | MCC \right | = \sqrt{\frac{\chi ^2}{N}}\]

Giá trị của MCC dao động trong khoảng -1 đến +1, MCC=+1 biểu thị cho “kết quả phân loại hoàn hảo”, MCC=0 cho thấy mô hình vô dụng (không hơn gì sự phán đoán ngẫu nhiên) còn MCC=-1 cho thấy mô hình không những vô dụng hoàn toàn mà còn tuyệt đối sai, vì kết quả phân loại hoàn toàn trái nghịch với quan sát thực tế.

MCC có thể được phân tích thành 2 nhân tố:

  1. Chỉ số Markedness (MK) được định nghĩa như:

\[MK = PPV + NPV – 1\]

  1. Chỉ số Informedness hay còn gọi là Bookmaker Informedness (BM):

\[BM = TPR + TNR – 1\]

Nếu tinh ý ta sẽ thấy BM chính là trị số Youden’s J trong phân tích ROC, vì:

\[Youden’s J = Sensitivity + Specificity – 1\]

MCC chính là trung bình nhân của Informedness (BM) và Markedness (MK)

\[\left | MCC \right | = \sqrt{BM *MK }\] # Phân tích ROC và AUC

Phương pháp cuối cùng mà chúng ta sẽ làm đó là phân tích ROC, cụ thể là diện tích dưới ROC (AUC). Khi nhắc đến đường cong ROC (Receiver operating characteristic), các bạn bác sĩ dễ dàng liên hệ tới phương pháp để đánh giá hiệu quả của một test chẩn đoán đơn biến. Mục tiêu là thiết lập một biểu đồ thể hiện sự dao động của TPR tùy theo FPR khi dịch chuyển qua các ngưỡng cắt khác nhau trên thang điểm chẩn đoán (Score).

Một cách tổng quát, phân tích ROC có thể áp dụng cho mọi mô hình nhị phân với Score chính là kết quả xác suất mà mô hình này tiên lượng.

Theo mặc định, ngưỡng cắt của xác suất phân loại là 0.5 nhưng trong một số hoàn cảnh, ngưỡng này có thể được điều chỉnh (gọi là cân bằng, calibration). Thí dụ nếu ta muốn tạo ra một quy luật chẩn đoán nhạy hơn so với mặc định, ta có thể giảm ngưỡng cắt còn 0.4 hay 0.3 cho nhãn Positive, như vậy sẽ làm tăng đồng thời FPR và TPR, và giảm đồng thời FNR, TNR theo luật bù trừ, làm giảm thiểu tỉ lệ bỏ sót chẩn đoán. Đường cong ROC cho phép khảo sát tất cả khả năng về ngưỡng cắt khác nhau và ảnh hưởng lên TPR, FPR. ROC cho phép đánh giá hiệu năng tổng quát của mô hình. Mô hình tối ưu sẽ có TPR cao và FPR thấp (gần góc trên bên trái ROC) và AUC gần 1 nhất có thể.

10 Cách làm gian lận

Đầu tiên, Nhi sẽ chia sẻ với các bạn cách tính nhanh nhất các tiêu chí đánh giá mô hình mà không cần bận tâm đến công thức của chúng. Nhi gọi đây là cách làm “đểu”, “cheating” hay “Múa rìu qua mắt thợ”, nhờ vào package mlr. Trong mlr có tích hợp hầu hết các chỉ số đánh giá mô hình, ngay cả những chỉ số hiếm gặp nhất. Nhưng vấn đề đó là các hàm này chỉ áp dụng được cho model do chính mlr huấn luyện và chỉ nhận object prediction của chính mlr. Do đó, ta phảican thiệp để đánh tráo nội dung prediction của một model bất kì, bằng kết quả prediction của mô hình mà ta đang muốn kiểm tra.

Chỉ với 5 bước, ta có kết quả như sau:

library(mlr)

# Step 1:
task=makeClassifTask(data=testset,target="Class")

# Step 2
dummylrner=makeLearner("classif.rpart",predict.type ="prob")
dummymod=train(dummylrner,task)
dummypred=predict(dummymod,task)

# Step 3

dummypred$data$truth<-dfpred$Truth
dummypred$data$prob.Negative<-dfpred$ClassNeg
dummypred$data$prob.Positive<-dfpred$ClassPos
dummypred$data$response<-dfpred$ClassLab

# Step 4

measure=list(mlr::acc,
             mlr::auc,
             mlr::bac,
             mlr::ber,
             mlr::brier,
             mlr::f1,
             mlr::gmean,
             mlr::gpr,
             mlr::kappa,
             mlr::logloss,
             mlr::lsr,
             mlr::mcc,
             mlr::mmce,
             mlr::qsr,
             mlr::ssr)

# Step 5

performance(dummypred,measure)%>%as.matrix()
##               [,1]
## acc      0.8000000
## auc      0.8711204
## bac      0.7998233
## ber      0.2001767
## brier    0.1461624
## f1       0.8081395
## gmean    0.7998150
## gpr      0.8081532
## kappa    0.5992935
## logloss  0.4498465
## lsr     -0.4498465
## mcc      0.5993377
## mmce     0.2000000
## qsr      0.7076752
## ssr      0.8365076

Oh yes ! Rất đơn giản phải không nè ?

Bây giờ ta sẽ làm “thật”, tức là tính thủ công lần lượt từng chỉ số :

11 Cách làm thật

11.1 Tính hữu dụng

TPR=TP/(TP+FN)
TNR=TN/(TN+FP)
SEN=TPR
SPEC=TNR
Recall=SEN

rbind(TPR,TNR,SEN,SPEC,Recall)
##             [,1]
## TPR    0.7961783
## TNR    0.8034682
## SEN    0.7961783
## SPEC   0.8034682
## Recall 0.7961783

11.2 Tính chính xác

ACC=(TP+TN)/(TP+FP+TN+FN)
BAC=(TPR+TNR)/2
PPV=TP/(TP+FP)
NPV=TN/(TN+FN)
Precision=PPV
G=sqrt(Recall*Precision)
F2=(1+2^2)*(Precision*Recall)/((2^2)*Precision+Recall)
F0.5=(1+(0.5)^2)*(Precision*Recall)/((0.5^2)*Precision+Recall)
F1=2*TP/(TP+TP+FP+FN)
Fscore=(1+1)*(Precision*Recall)/(1*Precision+Recall)

rbind(ACC,BAC,PPV,NPV,Precision,G,F1,F2,F0.5,Fscore)
##                [,1]
## ACC       0.8000000
## BAC       0.7998233
## PPV       0.7861635
## NPV       0.8128655
## Precision 0.7861635
## G         0.7911551
## F1        0.7911392
## F2        0.7941550
## F0.5      0.7881463
## Fscore    0.7911392

11.3 Nguy cơ sai lầm

FNR=FN/(FN+TP)
FPR=FP/(FP+TN)
FDR=FP/(TP+FP)
FOR=1-NPV
BER=with(dfpred,mean((ClassPos>0.5 & BinTruth==0) | (ClassPos<0.5 & BinTruth==1)))
logloss=with(dfpred,-mean(log10(ClassPos)))
LSR=with(dfpred,mean(log10(ClassPos)))
mmce=with(dfpred,mean(Truth!=ClassLab))

library(scoring)
brier= dfpred%>%scoring::brierscore(data=.,BinTruth~ClassPos)%>%mean()

SSR=dfpred%>%
  scoring::sphscore(data=.,BinTruth~ClassPos)%>%mean()

rbind(FNR,FPR,FDR,FOR,BER,logloss,LSR,mmce,brier,SSR)
##               [,1]
## FNR      0.2038217
## FPR      0.1965318
## FDR      0.2138365
## FOR      0.1871345
## BER      0.2000000
## logloss  0.4643810
## LSR     -0.4643810
## mmce     0.2000000
## brier    0.1461624
## SSR      0.1634924

11.4 Tính tương hợp

BM=TPR+TNR-1
MK=PPV+NPV-1
MCC=(TP*TN-FP*FN)/(sqrt((TP+FP)*(TP+FN)*(TN+FP)*(TN+FN)))
Kappa=psych::cohen.kappa(as.matrix(dfpred[,c(1,5)]))%>%.$weighted.kappa

rbind(BM,MK,MCC,Kappa)
##            [,1]
## BM    0.5996466
## MK    0.5990290
## MCC   0.5993377
## Kappa 0.5992935

11.5 Likelihood ratios

LRpos=SEN/(1-SPEC)
LRneg=(1-SEN)/SPEC

rbind(LRpos,LRneg)
##            [,1]
## LRpos 4.0511428
## LRneg 0.2536773

11.6 ROC-AUC

library(pROC)
myroc <- roc(Truth~ClassPos, data=dfpred)

AUC=myroc$auc

data_frame(fpr=1-myroc$specificities,
           tpr=myroc$sensitivities)%>%
  ggplot(aes(x=fpr,ymin=0,ymax=tpr))+
  geom_polygon(aes(y=tpr),fill="red",alpha=0.3)+
  geom_path(aes(y=tpr),col="red3",size=1)+
  geom_abline(linetype='dashed')+
  theme_bw()+
  coord_equal()+
  labs(x="False positive rate",y="True Positive rate")+
  ggtitle(paste0("AUC=",round(AUC,3)))

12 Tổng kết

Bài thực hành đến đây là hết. Chắc chắn nội dung của bài có nhiều thiếu sót, nhưng Nhi đã cố gắng hệ thống tất cả những gì Nhi học đươc từ giới Machine learning và diễn đạt nó theo hướng ứng dụng vào y học lâm sàng. Như các bạn đã thấy, việc tính thủ công những giá trị này hoàn toàn khả thi.Hy vọng bài viết giúp ích cho các bạn bác sĩ đang làm quen với ML. Chúc các bạn thành công.

LS0tDQp0aXRsZTogIsSQw6FuaCBnacOhIG3DtCBow6xuaCBwaMOibiBsb+G6oWkiIA0KYXV0aG9yOiAiTMOqIE5n4buNYyBLaOG6oyBOaGkiDQpkYXRlOiAiMDggVGjDoW5nIDEgMjAxOCINCm91dHB1dDoNCiAgaHRtbF9kb2N1bWVudDogDQogICAgY29kZV9kb3dubG9hZDogdHJ1ZQ0KICAgIGNvZGVfZm9sZGluZzogaGlkZQ0KICAgIG51bWJlcl9zZWN0aW9uczogeWVzDQogICAgdGhlbWU6ICJkZWZhdWx0Ig0KICAgIHRvYzogVFJVRQ0KICAgIHRvY19mbG9hdDogVFJVRQ0KLS0tDQoNCmBgYHtyIHNldHVwLGluY2x1ZGU9RkFMU0V9DQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUpDQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCmBgYA0KDQohW10oY29uZm1hdC5wbmcpDQoNCiMgR2nhu5tpIHRoaeG7h3UNCg0KVHJvbmcgdGjhu51pIGdpYW4gZ+G6p24gxJHDonksIE1hY2hpbmUgbGVhcm5pbmcgKE1MKSBi4bqvdCDEkeG6p3UgxJHGsOG7o2Mg4bupbmcgZOG7pW5nIHBo4buVIGJp4bq/biB0cm9uZyBuZ2hpw6puIGPhu6l1IHkgaOG7jWMgbMOibSBzw6BuZy4gQ8OhYyBiw6FjIHPEqSBi4bqvdCDEkeG6p3Ugc+G7rSBk4bulbmcgbmjhu69uZyBhbGdvcml0aG0gbeG7m2kgbOG6oSwgdGjDrSBk4bulIFNWTSwgRGVjaXNpb24gVHJlZSwgUmFuZG9tIEZvcmVzdCzigKYgTUwgY8WpbmcgbWFuZyDEkeG6v24gbmjhu69uZyBxdXkgdHLDrG5oIG3hu5tpLCB0aMOtIGThu6UgY3Jvc3MgdmFsaWRhdGlvbiwga2nhu4NtIMSR4buLbmggxJHhu5ljIGzhuq1wIHbDoCBuaGnhu4F1IHRpw6p1IGNow60gxJHDoW5oIGdpw6EsIHNvIHPDoW5oLCBs4buxYSBjaOG7jW4gbcO0IGjDrG5oIG3hu5tpLiBUdXkgbmhpw6puIGPDoWMgYsOhYyBzxKkgduG6q24gZ+G6t3Agbmhp4buBdSBraMOzIGtoxINuIGtoaSB0aeG6v3AgdGh1IGTDsm5nIGtp4bq/biB0aOG7qWMgY+G7sWMgbOG7m24gbsOgeS4gTeG7mXQgbeG6t3QsIGdp4bubaSBiw6FjIHPEqSB24bqrbiBjw7JuIGdp4buvIHF1w6FuIHTDrW5oIHLhuqV0IGzhu5tuIGtoaSDEkWkgdGhlbyBjw6FjaCBsw6BtIHZp4buHYyB0cnV54buBbiB0aOG7kW5nIHbDoCBi4buLIGNoaSBwaOG7kWkgYuG7n2kgY8OhY2ggc3V5IG5naMSpIGPFqSB0aGVvIHRyxrDhu51uZyBwaMOhaSB0aOG7kW5nIGvDqiB5IHNpbmguIFRow60gZOG7pSwga2hpIG5o4bqvYyDEkeG6v24gdmnhu4djIGThu7FuZyBNw7QgaMOsbmgsIMSRYSBz4buRIGLDoWMgc8SpIHRoxrDhu51uZyBjaOG7iSBuZ2jEqSDEkeG6v24gbeG7pWMgdGnDqnUgRGnhu4VuIGThu4tjaCAoaW50ZXJwcmV0aXZlKSwgaOG7jSBjaMawYSBxdWVuIHbhu5tpIG3hu6VjIHRpw6p1IHRpw6puIGzGsOG7o25nLiBUcm9uZyBjw6FjIG5naGnDqm4gY+G7qXUgeSBo4buNYywga2jDoWkgbmnhu4dtIG3DtCBow6xuaCAgZ+G6p24gbmjGsCDEkeG7k25nIG5naMSpYSB24bubaSBI4buTaSBxdXkgdHV54bq/biB0w61uaCwgdsOsIMSRw6J5IHThu6tuZyBsw6AgY8O0bmcgY+G7pSBwaOG7lSBiaeG6v24gbmjhuqV0IG3DoCBtw7RuIHRo4buRbmcga8OqIGjhu5cgdHLhu6MuIE5nb8OgaSByYSwgdmnhu4djIGtp4buDbSB0cmEgxJHDoW5oIGdpw6EgaGnhu4d1IG7Eg25nL3Bo4bqpbSBjaOG6pXQgY+G7p2EgbcO0IGjDrG5oOyBzbyBzw6FuaCBtw7QgaMOsbmgsIGNo4buNbiBs4buNYyBtw7QgaMOsbmggdOG7kWkgxrB1Li4uIGNoxrBhIMSRxrDhu6NjIHF1YW4gdMOibSDEkcO6bmcgbeG7qWMsIHbDoCBu4bq/dSBjw7MgdGjhu7FjIGhp4buHbiBjxaluZyBjaOG7iSBnaeG7m2kgaOG6oW4gdHLDqm4gZOG7ryBsaeG7h3UgaGnhu4duIGjDoG5oIGNo4bupIGtow7RuZyBkw7luZyBk4buvIGxp4buHdSDEkeG7mWMgbOG6rXAuIELDoWMgc8SpIGPFqW5nIGNoxrBhIHF1ZW4gduG7m2kgdmnhu4djIG5ow6xuIGNsYXNzaWZpZXIgxJFhIGJp4bq/biBuaMawIG3hu5l0IHRlc3QgY2jhuqluIMSRb8OhbiDEkeG7gyBsacOqbiBo4buHIGNow7puZyB24bubaSBuaOG7r25nIGtow6FpIG5p4buHbSDEkeG6t2MgdGjDuSBj4bunYSBuZ8OgbmggWSwgdGjDrSBk4bulIExpa2VsaWhvb2QgcmF0aW9zLiBN4bq3dCBraMOhYyB24bqlbiDEkeG7gSBjw6BuZyBwaOG7qWMgdOG6oXAgaMahbiBraGkgZ2nhu69hICBNTCB2w6AgVGjhu5FuZyBrw6ogY8OzIHF1w6Egbmhp4buBdSBk4buLIGJp4buHdCB24buBIHRodeG6rXQgbmfhu68gKHRo4bqtbSBjaMOtIGPDsyBuaOG7r25nIMSRaeG7gXUgY8O5bmcgbmd1ecOqbiBsw70gbmjGsG5nIHRodeG6rXQgbmfhu68gY8WpbmcgaG/DoG4gdG/DoG4ga2jDoWMgbmhhdSksIG5nxINuIGPhuqNuIGLDoWMgc8SpIGjhu41jIGtp4bq/biB0aOG7qWMgdOG7qyBNYWNoaW5lIExlYXJuaW5nIHbDoCBkaeG7hW4gxJHhuqF0IG5o4buvbmcgdGnDqnUgY2jDrSDEkeG6t2MgdGjDuSBj4bunYSBuZ8OgbmggWSBjaG8gY8OhYyBkYXRhIHNjaWVudGlzdCBiw6puIG5nb8OgaS4NCg0KVmnhu4djIMSRw6FuaCBnacOhIG3DtCBow6xuaCBjaOG6r2MgY2jhuq9uIGPhuqduIHRoaeG6v3QsIG5nYXkgY+G6oyBraGkgbeG7pWMgdGnDqnUgY+G7p2EgbmdoacOqbiBj4bupdSBraMO0bmcgcGjhuqNpIGzDoCB0acOqbiBsxrDhu6NuZy4gS2hpIMSRw6FuaCBnacOhIG3DtCBow6xuaCwgdGEgbeG7m2kgY8OzIHRo4buDIGtp4buDbSBzb8OhdCwgxJFp4buBdSBjaOG7iW5oIG5o4buvbmcgYWxnb3JpdGhtIMSR4bqhdCBoaeG7h3UgcXXhuqMgdOG7kWkgxrB1LCBzbyBzw6FuaCBnaeG7r2Egbmhp4buBdSBtw7QgaMOsbmggdsOgIGNo4buNbiByYSBtw7QgaMOsbmggY2jDrW5oIHjDoWMgbmjhuqV0LCB2w6AgeMOhYyBuaOG6rW4gxJFp4buBdSBuw6B5IHRyw6puIG3hu5l0IGThu68gbGnhu4d1IMSR4buZYyBs4bqtcC4gVHV5IG5oacOqbiBjw6J1IGjhu49pIGPhuqduIGdp4bqjaSDEkcOhcDogS2nhu4NtIHRyYSBi4bqxbmcgbmjhu69uZyB0acOqdSBjaMOtIG7DoG8gPw0KDQpEbyDEkcOzLCBOaGkgdmnhur90IGLDoGkgdGjhu7FjIGjDoG5oIG7DoHkgduG7m2kgaHkgduG7jW5nIGdp4bqjaSBxdXnhur90IGPDoG5nIG5oaeG7gXUgY8OgbmcgdOG7kXQgY8OhYyB24bqlbiDEkeG7gSBuw6p1IHRyw6puLCB24bubaSAzIG3hu6VjIHRpw6p1IGNow61uaDoNCg0KMSkJTGnhu4d0IGvDqiB04bqldCBj4bqjIG5o4buvbmcgY2jhu4kgc+G7kSAvIHRpw6p1IGNow60gY8OzIHRo4buDIGTDuW5nIMSR4buDIMSRw6FuaCBnacOhIGhp4buHdSBuxINuZywgcGjhuqltIGNo4bqldCBj4bunYSBt4buZdCBtw7QgaMOsbmggbmjhu4sgcGjDom4sIHRow60gZOG7pSBSYW5kb20gRm9yZXN0Lg0KDQoyKSBQaMOibiB0w61jaCBjw6FjIGThu4sgYmnhu4d0IHbhu4EgdGh14bqtdCBuZ+G7ryBnaeG7r2EgTUwgdsOgIFRo4buRbmcga8OqIHkgaOG7jWMsIHbDoCBjaHXhuqluIGjDs2EgY2jDum5nIGNobyDhu6luZyBk4bulbmcgbMOibSBzw6BuZy4NCg0KMykgVMOtbmggdG/DoW4gY8OhYyBjaOG7iSBz4buRIG7DoHkgdHJvbmcgUiwgdGhlbyBjw6FjaCBob8OgbiB0b8OgbiB0aOG7pyBjw7RuZyBob+G6t2Mgc+G7rSBk4bulbmcgY8OhYyBow6BtIHZp4bq/dCBz4bq1biB04burIGPDoWMgdGjGsCB2aeG7h24gKFIgcGFja2FnZXMpLiAgDQoNCg0KIyBUaMOtIGThu6UgbWluaCBo4buNYTogUmFuZG9tIEZvcmVzdCBtb2RlbA0KDQrEkOG7gyBtaW5oIGjhu41hLCB0YSBwaOG6o2kgY8OzIG3hu5l0IG3DtCBow6xuaC4gTmhpIHPhur0gZMO5bmcgbOG6oWkgYuG7mSBz4buRIGxp4buHdSBIZWFydCBkaXNlYXNlLiDEkMOieSBsw6AgMSB04bqtcCBo4bujcCBk4buvIGxp4buHdSBj4bunYSBoxqFuIDYwMCBi4buHbmggbmjDom4gdOG7qyA0IGLhu4duaCB2aeG7h24gKENsZXZlbGFuZCxCdWRhcGVzdCxMb25nIEJlYWNoIGFuZCBadXJpY2gpLiBUYSBz4bq9IGThu7FuZyBt4buZdCBtw7QgaMOsbmggdGnDqm4gbMaw4bujbmcgY2hvIGLhu4duaCBUaW0gbeG6oWNoIGThu7FhIHbDoG8gMTQgYmnhur9uIHPhu5EgYmFvIGfhu5NtIFR14buVaSwgR2nhu5tpIHTDrW5oLCBUcmnhu4d1IGNo4bupbmcgxJBhdSBuZ+G7sWMsIGNob2xlc3Rlcm9sLCBmYXN0aW5nIGJsb29kIHN1Z2FyIHRlc3QsIHbDoCBzdHJlc3MgdGVzdCBn4buTbSBuaOG7i3AgdGltIHbDoCDEkW/huqFuIFNUIGPhu6dhIEVDRy4gQWxnb3JpdGhtIMSRxrDhu6NjIGNo4buNbiBsw6AgUmFuZG9tIEZvcmVzdC4gQ8O0bmcgxJFv4bqhbiB0cmFpbmluZyBz4bq9IMSRxrDhu6NjIHRo4buxYyBoaeG7h24gYuG6sW5nIGNhcmV0LiBNw7QgaMOsbmggUkYgc+G6vSDEkcaw4bujYyBodeG6pW4gbHV54buHbiB0csOqbiA1MCUgZOG7ryBsaeG7h3UsIDUwJSBjw7JuIGzhuqFpIGTDuW5nIMSR4buDIGtp4buDbSDEkeG7i25oIMSR4buZYyBs4bqtcC4gUXV5IHRyw6xuaCBodeG6pW4gbHV54buHbiBkbyBjYXJldCB0aOG7sWMgaGnhu4duIHThu7EgxJHhu5luZyBi4bqxbmcgY8OhY2ggdGluaCBjaOG7iW5oIHRoYW0gc+G7kSBtdHJ5LCBuaOG6sW0gdOG7kWkgxrB1IGjDs2EgdGnDqnUgY2jDrSBBY2N1cmFjeS4NCg0KYGBge3IsbWVzc2FnZSA9IEZBTFNFLHdhcm5pbmc9RkFMU0V9DQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCg0KdmE9cmVhZC50YWJsZSgiaHR0cHM6Ly9hcmNoaXZlLmljcy51Y2kuZWR1L21sL21hY2hpbmUtbGVhcm5pbmctZGF0YWJhc2VzL2hlYXJ0LWRpc2Vhc2UvcHJvY2Vzc2VkLnZhLmRhdGEiLCBzZXAgPSIsIixuYS5zdHJpbmdzPSI/IixzdHJpcC53aGl0ZT1UUlVFLCBmaWxsID0gVFJVRSklPiVhc190aWJibGUoKQ0KaHU9cmVhZC50YWJsZSgiaHR0cHM6Ly9hcmNoaXZlLmljcy51Y2kuZWR1L21sL21hY2hpbmUtbGVhcm5pbmctZGF0YWJhc2VzL2hlYXJ0LWRpc2Vhc2UvcHJvY2Vzc2VkLmh1bmdhcmlhbi5kYXRhIiwgc2VwID0iLCIsbmEuc3RyaW5ncz0iPyIsc3RyaXAud2hpdGU9VFJVRSwgZmlsbCA9IFRSVUUpJT4lYXNfdGliYmxlKCkNCnN3PXJlYWQudGFibGUoImh0dHBzOi8vYXJjaGl2ZS5pY3MudWNpLmVkdS9tbC9tYWNoaW5lLWxlYXJuaW5nLWRhdGFiYXNlcy9oZWFydC1kaXNlYXNlL3Byb2Nlc3NlZC5zd2l0emVybGFuZC5kYXRhIiwgc2VwID0iLCIsbmEuc3RyaW5ncz0iPyIsc3RyaXAud2hpdGU9VFJVRSwgZmlsbCA9IFRSVUUpJT4lYXNfdGliYmxlKCkNCmNsPXJlYWQudGFibGUoImh0dHBzOi8vYXJjaGl2ZS5pY3MudWNpLmVkdS9tbC9tYWNoaW5lLWxlYXJuaW5nLWRhdGFiYXNlcy9oZWFydC1kaXNlYXNlL3Byb2Nlc3NlZC5jbGV2ZWxhbmQuZGF0YSIsIHNlcCA9IiwiLG5hLnN0cmluZ3M9Ij8iLHN0cmlwLndoaXRlPVRSVUUsIGZpbGwgPSBUUlVFKSU+JWFzX3RpYmJsZSgpDQoNCmRmPXJiaW5kKHZhLGh1LHN3LGNsKQ0KDQpuYW1lcyhkZik9YygiQWdlIiwiU2V4IiwiQ2hlc3RQYWluIiwNCiAgICAgICAgICAgICJSZXN0QlAiLCJDaG9sIiwiRkJTIiwNCiAgICAgICAgICAgICJSZXN0RUNHIiwiTWF4SFIiLCJDUEVUQWdpbmEiLA0KICAgICAgICAgICAgIk9sZHBlYWsiLCJTbG9wZSIsIkNBIiwiVGhhbCIsIkNsYXNzIikNCg0KZGF0YTwtZGZbLC1jKDExLDEyLDEzKV0lPiVmaWx0ZXIoLixDaG9sIT0wKSU+JW5hLm9taXQoKQ0KDQpkYXRhJFNleCU8PiVhcy5mYWN0b3IoKSU+JQ0KICByZWNvZGVfZmFjdG9yKC4sYDBgID0gIkZlbWFsZSIsIA0KICAgICAgICAgICAgICAgICAgYDFgID0gIk1hbGUiKQ0KDQpkYXRhJENoZXN0UGFpbiU8PiVhcy5mYWN0b3IoKSU+JQ0KICByZWNvZGVfZmFjdG9yKC4sYDFgID0gIlR5cGljYWwiLCANCiAgICAgICAgICAgICAgICBgMmAgPSAiQXR5cGljYWwiLA0KICAgICAgICAgICAgICAgIGAzYCA9ICJOb25fYWdpbmFsIiwgDQogICAgICAgICAgICAgICAgYDRgID0gImFzeW1wdG9tYXRpYyIgKQ0KDQpkYXRhJEZCUyU8PiVhcy5mYWN0b3IoKSU+JQ0KICByZWNvZGVfZmFjdG9yKC4sYDBgID0gIk5vIiwgDQogICAgICAgICAgICAgICAgYDFgID0gIlllcyIpDQoNCmRhdGEkUmVzdEVDRyU8PiVhcy5mYWN0b3IoKSU+JQ0KICByZWNvZGVfZmFjdG9yKC4sYDBgID0gIk5vcm1hbCIsIA0KICAgICAgICAgICAgICAgIGAxYCA9ICJBYm5vcm1hbF9TVCIsDQogICAgICAgICAgICAgICAgYDJgID0gIkxWSHlwZXJ0cm9waHkiKQ0KDQpkYXRhJENQRVRBZ2luYSU8PiVhcy5mYWN0b3IoKSU+JQ0KICByZWNvZGVfZmFjdG9yKC4sYDBgID0gIk5vIiwNCiAgICAgICAgICAgICAgICBgMWAgPSAiWWVzIikNCg0KZGF0YSRDbGFzcyU8PiVhcy5mYWN0b3IoKSU+JQ0KICByZWNvZGVfZmFjdG9yKC4sYDBgID0gIk5lZ2F0aXZlIiwNCiAgICAgICAgICAgICAgICBgMWAgPSAiUG9zaXRpdmUiLA0KICAgICAgICAgICAgICAgIGAyYCA9ICJQb3NpdGl2ZSIsIA0KICAgICAgICAgICAgICAgIGAzYCA9ICJQb3NpdGl2ZSIsDQogICAgICAgICAgICAgICAgYDRgID0gIlBvc2l0aXZlIikNCg0Kcm0oY2wsZGYsaHUsc3csdmEpDQoNCmxpYnJhcnkoY2FyZXQpDQoNCnNldC5zZWVkKDEyMzQpDQppZFRyYWluPWNyZWF0ZURhdGFQYXJ0aXRpb24oeT1kYXRhJENsYXNzLCBwPTAuNSxsaXN0PUZBTFNFKQ0KDQp0cmFpbnNldD1kYXRhW2lkVHJhaW4sXQ0KdGVzdHNldD1kYXRhWy1pZFRyYWluLF0NCg0KI0tSRg0KDQpDb250cm9sPXRyYWluQ29udHJvbChtZXRob2Q9ICJyZXBlYXRlZGN2IiwNCiAgICAgICAgICAgICAgICAgICAgIG51bWJlcj01LA0KICAgICAgICAgICAgICAgICAgICAgcmVwZWF0cz01LA0KICAgICAgICAgICAgICAgICAgICAgY2xhc3NQcm9icz1UUlVFLA0KICAgICAgICAgICAgICAgICAgICAgc3VtbWFyeUZ1bmN0aW9uPW11bHRpQ2xhc3NTdW1tYXJ5KQ0KDQpyZm1vZD1jYXJldDo6dHJhaW4oQ2xhc3N+LiwNCiAgICAgICAgICAgIGRhdGE9dHJhaW5zZXQsDQogICAgICAgICAgICBtZXRob2QgPSAicmYiLA0KICAgICAgICAgICAgdHJDb250cm9sPUNvbnRyb2wsDQogICAgICAgICAgICB0dW5lTGVuZ3RoPTUpDQoNCmBgYA0KDQrEkOG6p3UgdGnDqm4sIHRhIGPDsyB0aOG7gyB0aOG6pXkgdmnhu4djIMSRw6FuaCBnacOhIG3DtCBow6xuaCDEkcOjIMSRxrDhu6NjIGNhcmV0IMOhcCBk4bulbmcgbmdheSB04burIGPDtG5nIMSRb+G6oW4gaHXhuqVuIGx1eeG7h24sIGNhcmV0IGzhuqduIGzGsOG7o3QgdGjhu60gbXRyeT0yLDQsNywxMC4uLiAsIG3hu5dpIGzhuqduIG5oxrAgduG6rXkgbcO0IGjDrG5oIMSRxrDhu6NjIGThu7FuZyB0csOqbiA0IGJsb2NrcyBk4buvIGxp4buHdSB2w6Aga2nhu4NtIMSR4buLbmggdHLDqm4gYmxvY2sgY8OybiBs4bqhaSwgbOG6t3AgbOG6oWkgNSBsxrDhu6N0LiBDdeG7kWkgY8O5bmcsIHRpw6p1IGNow60gQWNjdXJhY3kgxJHGsOG7o2MgY2jhu41uIMSR4buDIHF1eeG6v3QgxJHhu4tuaCBtdHJ5IHThu5FpIMawdT0yLiANCg0KVHJvbmcga+G6v3QgcXXhuqMgY3Jvc3N2YWxpZGF0aW9uLCB0YSBjxaluZyBjw7MgdGjhu4MgdGjhuqV5IG5o4buvbmcgY2jhu4kgc+G7kSBraMOhYyBuaMawIEFVQywgS2FwcGEsIEYxLCBTZW5zaXRpdml0eSwgU3BlY2lmaWNpdHksIEJhbGFuY2VkIGFjY3VyYWN5LiBDaMO6bmcgdGEgc+G6vSBuw7NpIHbhu4EgY2jDum5nIHPDonUgaMahbiB0cm9uZyBwaOG6p24gc2F1LCBjw6FjIGLhuqFuIGtpw6puIG5o4bqrbi4NCg0KYGBge3J9DQpyZm1vZA0KDQpwbG90KHJmbW9kKQ0KYGBgDQoNCiMgROG7ryBsaeG7h3Uga+G6v3QgcXXhuqMgdGnDqm4gbMaw4bujbmcNCg0KVmnhu4djIGtp4buDbSDEkeG7i25oIG3DtCBow6xuaCBz4bq9IMSRxrDhu6NjIHRo4buxYyBoaeG7h24gxJHhu5ljIGzhuq1wIHRyw6puIGThu68gbGnhu4d1IHRlc3RzZXQgKG49MzMwKSwgxJHhuqd1IHRpw6puLCBjaMO6bmcgdGEgdOG6oW8gcmEgbeG7mXQgZGF0YWZyYW1lIGPDsyBu4buZaSBkdW5nIG5oxrAgc2F1Og0KDQpgYGB7cixtZXNzYWdlID0gRkFMU0Usd2FybmluZz1GQUxTRX0NCmRmcHJlZD1kYXRhX2ZyYW1lKFRydXRoPXRlc3RzZXQkQ2xhc3MsDQogICAgICAgICAgICAgICAgICBCaW5UcnV0aD1pZl9lbHNlKHRlc3RzZXQkQ2xhc3M9PSJQb3NpdGl2ZSIsMSwwKSwNCiAgICAgICAgICAgICAgICAgIENsYXNzTmVnPXByZWRpY3QocmZtb2QsbmV3ZGF0YT10ZXN0c2V0LHR5cGU9InByb2IiKSU+JS5bWzFdXSwNCiAgICAgICAgICAgICAgICAgIENsYXNzUG9zPXByZWRpY3QocmZtb2QsbmV3ZGF0YT10ZXN0c2V0LHR5cGU9InByb2IiKSU+JS5bWzJdXSwNCiAgICAgICAgICAgICAgICAgIENsYXNzTGFiPXByZWRpY3QocmZtb2QsbmV3ZGF0YT10ZXN0c2V0KSkNCg0KZGZwcmVkJT4laGVhZCgpDQpgYGANCg0KMSkgVHJ1dGg6IEdpw6EgdHLhu4sgdGjhu7FjIHThur8gcXVhbiBzw6F0IMSRxrDhu6NjLGTGsOG7m2kgZOG6oW5nIGZhY3Rvci4NCg0KMikgQmluVHJ1dGg6IEdpw6EgdHLhu4sgdGjhu7FjIHThur8gZMaw4bubaSBk4bqhbmcgeMOhYyBzdeG6pXQgdHV54buHdCDEkeG7kWk6IFBvc2l0aXZlPTEsIE5lZ2F0aXZlPTAuDQoNCjMpIENsYXNzTmVnOiBYw6FjIHN14bqldCB0acOqbiBsxrDhu6NuZyBjaG8gbmjDo24gTmVnYXRpdmUuDQoNCjQpIENsYXNzUG9zOiBYw6FjIHN14bqldCB0acOqbiBsxrDhu6NuZyBjaG8gbmjDo24gUG9zaXRpdmUuDQoNCjUpIENsYXNzTGFiOiBL4bq/dCBxdeG6oyBwaMOibiBsb+G6oWkgY+G7p2EgbcO0IGjDrG5oLg0KDQpU4burIGThu68gbGnhu4d1IG7DoHksIGNow7puZyB0YSBjw7MgdGjhu4MgeMOhYyDEkeG7i25oIMSRxrDhu6NjIHThuqV0IGPhuqMgY8OhYyB0acOqdSBjaMOtIHbhu4EgcGjhuqltIGNo4bqldCBtw7QgaMOsbmguDQoNCiMgQ29uZnVzaW9uIG1hdHJpeCANCg0KxJDhuqd1IHRpw6puLCB0YSBiw6BuIHbhu4EgbeG7mXQgaMOsbmggdGjhu6ljIHTDs20gdOG6r3Qga+G6v3QgcXXhuqMgduG7gSBoaeG7h3UgbsSDbmcgbcO0IGjDrG5oIHLhuqV0IGzDvSB0aMO6OiBDb25mdXNpb24gbWF0cml4IGhheSBlcnJvciBtYXRyaXguIENvbmZ1c2lvbiBtYXRyaXggbMOgIHRodeG6rXQgbmfhu68gY+G7p2EgZ2nhu5tpIE1hY2hpbmUgbGVhcm5pbmcsIHTGsMahbmcgxJHGsMahbmcgduG7m2kga2jDoWkgbmnhu4dtIGNvbnRpbmdlbmN5IHRhYmxlIGhheSBjcm9zcy10YWJsZSBiw6puIGdp4bubaSB0aOG7kW5nIGvDqi4gTsOzIGPDsyBi4bqjbiBjaOG6pXQgbMOgIG3hu5l0IGLhuqNuZyBwaMOibiBwaOG7kWkgdOG6p24gc+G7kSAyIGNoaeG7gXUgKGLhuqNuZyBjaMOpbykgY2hvIHBow6lwIHRyw6xuaCBiw6B5IHThu4kgbOG7hyB0xrDGoW5nIGjhu6NwIHbDoCBi4bqldCB44bupbmcgZ2nhu69hIFRo4buxYyB04bq/IHbDoCBr4bq/dCBxdeG6oyBwaMOibiBsb+G6oWkgY+G7p2EgcXV5IGx14bqtdCBj4bqnbiBraeG7g20gdHJhIChtw7QgaMOsbmgpLlRyb25nIGLDoGkgbsOgeSwgdGEgY2jhu4kgeMOpdCB0csaw4budbmcgaOG7o3AgxJHGoW4gZ2nhuqNuIG5o4bqldCBj4bunYSBjb25mdXNpb24gbWF0cml4IMOhcCBk4bulbmcgY2hvIGLDoGkgdG/DoW4gbmjhu4sgcGjDom4gKEJpbmFyeSBjbGFzc2lmaWNhdGlvbikuS2hpIMSRw7MsIENvbmZ1c2lvbiBtYXRyaXggdHLDrG5oIGLDoHkgdOG6p24gc3XhuqV0IGPhu6dhIDQgdOG7lSBo4bujcDogVFAgKFRydWUgUG9zaXRpdmUpLCBUTiAoVHJ1ZSBQb3NpdGl2ZSksIEZQIChGYWxzZSBwb3NpdGl2ZSkgdsOgIEZOIChGYWxzZSBuZWdldGl2ZSkuIFRydWUgKMSQw7puZykgY2jhu4kgc+G7sSB0xrDGoW5nIGjhu6NwLCBraGkga+G6v3QgcXXhuqMgcGjDom4gbG/huqFpIHBow7kgaOG7o3AgduG7m2kgZ2nDoSB0cuG7iyB0aOG7sWMgdOG6vzsgRmFsc2UgKFNhaSkgY2jhu4kgc+G7sSBi4bqldCB44bupbmcgaGF5IG5o4bqnbSBs4bqrbiwga2hpIG3DtCBow6xuaCBwaMOibiBsb+G6oWkgbmjhuqdtIHNvIHbhu5tpIHRo4buxYyB04bq/LiANCg0KIVtdKGNvbmZtYXQxLnBuZykNCg0Kw50gbmdoxKlhIHF1YW4gdHLhu41uZyBj4bunYSBjb25mdXNpb24gbWF0cml4IGzDoCDhu58gY2jhu5cgVFAsVE4sRlAsRk4gbMOgIDQgY2jhu4kgc+G7kSBjxqEgYuG6o24sIHThu6sgxJHDsyBjaG8gcGjDqXAgc3V5IHJhIGvhur90IHF14bqjIGPhu6dhIGjhuqd1IGjhur90IG5o4buvbmcgY2jhu4kgc+G7kSBraMOhYy4NCg0KQ29uZnVzaW9uIG1hdHJpeCBjw7MgdGjhu4MgdMOtbmggdOG7sSDEkeG7mW5nIHThu6sgY2FyZXQ6DQoNCmBgYHtyLG1lc3NhZ2UgPSBGQUxTRSx3YXJuaW5nPUZBTFNFfQ0KY2Y9Y2FyZXQ6OmNvbmZ1c2lvbk1hdHJpeChyZWZlcmVuY2U9ZGZwcmVkJFRydXRoLA0KICAgICAgICAgICAgICAgICAgICAgICBkYXRhPWRmcHJlZCRDbGFzc0xhYiwNCiAgICAgICAgICAgICAgICAgICAgICAgcG9zaXRpdmU9IlBvc2l0aXZlIiwNCiAgICAgICAgICAgICAgICAgICAgICAgbW9kZT0iZXZlcnl0aGluZyIpDQoNCmNmJHRhYmxlDQpgYGANCg0KVHV5IG5oacOqbiwgdGEgY8OzIHRo4buDIHThu7EgZOG7sW5nIGNvbmZ1c2lvbiBtYXRyaXggdGjhu6cgY8O0bmcgbmjGsCBzYXU6DQoNCmBgYHtyLG1lc3NhZ2UgPSBGQUxTRSx3YXJuaW5nPUZBTFNFfQ0KY29uZm1hdD10YWJsZShDbGFzc2lmaWNhdGlvbj1kZnByZWQkQ2xhc3NMYWIsVHJ1dGg9ZGZwcmVkJFRydXRoKQ0KDQpjb25mbWF0DQpgYGANCg0KVGEgY8WpbmcgY8OzIHRo4buDIHTDrW5oIHRo4bunIGPDtG5nIHRy4buxYyB0aeG6v3AgdOG7q25nIGNo4buJIHPhu5EgbeG7mXQgdOG7qyBk4buvIGxp4buHdSBraeG7g20gxJHhu4tuaDoNCg0KYGBge3IsbWVzc2FnZSA9IEZBTFNFLHdhcm5pbmc9RkFMU0V9DQpUUD13aXRoKGRmcHJlZCxzdW0oQ2xhc3NMYWI9PVRydXRoICYgQ2xhc3NMYWI9PSJQb3NpdGl2ZSIpKQ0KVE49d2l0aChkZnByZWQsc3VtKENsYXNzTGFiPT1UcnV0aCAmIENsYXNzTGFiIT0iUG9zaXRpdmUiKSkNCkZOPXdpdGgoZGZwcmVkLHN1bShDbGFzc0xhYiE9VHJ1dGggJiBUcnV0aD09IlBvc2l0aXZlIikpDQpGUD13aXRoKGRmcHJlZCxzdW0oQ2xhc3NMYWIhPVRydXRoICYgVHJ1dGghPSJQb3NpdGl2ZSIpKQ0KDQpjYmluZChUUCxUTixGTixGUCkNCmBgYA0KDQpMxrB1IMO9OiBQb3NpdGl2ZS9uZWdhdGl2ZSBjaOG7iSBsw6AgdMOqbiBn4buNaSBj4bunYSAyIG5ow6NuIGdpw6EgdHLhu4ssIG1hbmcgdMOtbmggcXV5IMaw4bubYyB2w6AgY8OzIMO9IG5naMSpYSB0xrDGoW5nIMSR4buRaSB0w7l5IHRoZW8gbeG7pWMgdGnDqnUgY+G7p2EgbmfGsOG7nWkgZMO5bmcgKETGsMahbmcgdMOtbmggPSDEkWnhu4F1IHRhIHF1YW4gdMOibSB0w6xtIGtp4bq/bSB2w6Agw4JtIHTDrW5oID0gbG/huqFpIHRy4burICh04bqldCBj4bqjIG3hu41pKSB0aOG7qSBjw7JuIGzhuqFpKS4NCg0KVHJvbmcgeSBo4buNYyBsw6JtIHPDoG5nIGPDsm4gZOG7i2NoIGzDoCDigJxUaOG6rXTigJ0gdsOgIOKAnEdp4bqj4oCdLCBuaMawbmcgTmhpIGNobyBy4bqxbmcgcGjDoXQgYmnhu4N1IOKAnE3DtCBow6xuaCBwaMOibiBsb+G6oWkgc2FpLCBtw7QgaMOsbmggY2jhuqluIMSRb8OhbiBs4bqnbSwgbG/huqFpIHRy4burIG5o4bqnbeKAnSB0aMOsIGThu4UgaGnhu4N1IGjGoW4gc28gduG7m2kg4oCca+G6v3QgcXXhuqMgw6JtIHTDrW5oIGdp4bqjL2TGsMahbmcgdMOtbmggZ2nhuqPigJ0uICBLaGkgw6FwIGThu6VuZyBjaG8gbeG7pWMgdGnDqnUgY2jhuqluIMSRb8OhbiwgVFAgY8OzIHRo4buDIGThu4tjaCBsw6Ag4oCcY2jhuqluIMSRb8Ohbi9waMOhdCBoaeG7h24gxJHDum5nL3Ryw7puZ+KAnSwgVE4gbMOgIOKAnGxv4bqhaSB0cuG7qyDEkcO6bmfigJ0sIEZQIGzDoCDigJxjaOG6qW4gxJFvw6FuIG5o4bqnbS9zYWnigJ0sIEZOIGzDoCDigJxi4buPIHPDs3QsIGxv4bqhaSB0cuG7qyBuaOG6p23igJ0uDQoNCiMgVMOtbmggaOG7r3UgZOG7pW5nOiANCg0KxJDhuqd1IHRpw6puLCB0YSBz4bq9IGtp4buDbSB0cmEgeGVtIG3DtCBow6xuaCBo4buvdSBk4bulbmcgxJHhur9uIG3hu6ljIG7DoG8sIHRow7RuZyBxdWEgY8OhYyBjaOG7iSBz4buRIG5oxrAgc2F1Og0KDQoxKSBTZW5zaXRpdml0eSAtIMSQ4buZIG5o4bqheQ0KDQpUcnVlIFBvc2l0aXZlIHJhdGUgKFRQUikgOiB04buJIGzhu4cgcGjDom4gbG/huqFpIFBvc2l0aXZlIMSRw7puZyB0csOqbiB04buVbmcgc+G7kSBjw6FjIHRyxrDhu51uZyBo4bujcCBQb3NpdGl2ZToNCg0KJCRUUFIgPSBTZW5zaXRpdml0eSA9IFJlY2FsbCA9IFxmcmFje1RQfXtUUCtGTn0gPSBcZnJhY3tUUH17XHN1bSBQb3NpdGl2ZX0kJA0KDQpU4buJIGzhu4cgVFBSIGPDsm4gY8OzIHTDqm4gZ+G7jWkga2jDoWMgbMOgIFNlbnNpdGl2aXR5ICjEkeG7mSBuaOG6oXkpLCBoaXQgcmF0ZSAodOG7iSBs4buHIHRyw7puZyDEkcOtY2gpLCB2w6AgUmVjYWxsLiBEbyB0csOqbiBsw6JtIHPDoG5nIHRhIHF1ZW4gduG7m2kga2jDoWkgbmnhu4dtIFNlbnNpdGl2aXR5LCBOaGkgc+G6vSBz4butIGThu6VuZyBuw7MgbmjGsCB0aHXhuq10IG5n4buvIGR1eSBuaOG6pXQuIFJlY2FsbCBsw6AgdGh14bqtdCBuZ+G7ryDDrXQgdGjDtG5nIGThu6VuZyBoxqFuIG5oxrBuZyBjw7Mgbmhp4buBdSB0w6BpIGxp4buHdSBz4butIGThu6VuZyBuw7MgbsOqbiBjw6FjIGLhuqFuIGPhuqduIGdoaSBuaOG7my4NCg0KMikgU3BlY2lmaWNpdHk6IMSQ4buZIMSR4bq3YyBoaeG7h3UNCg0KVHJ1ZSBuZWdhdGl2ZSByYXRlIChUTlIpIDogdOG7iSBs4buHIGxv4bqhaSB0cuG7qyDEkcO6bmcgdHLDqm4gdOG7lW5nIHPhu5EgY8OhYyB0csaw4budbmcgaOG7o3AgTmVnYXRpdmUgaGF5IGPDsm4gZ+G7jWkgbMOgIFNwZWNpZmljaXR5ICjEkeG7mSDEkeG6t2MgaGnhu4d1KSB0cm9uZyB5IGjhu41jIGzDom0gc8OgbmcuIA0KDQokJFROUiA9IFNwZWNpZmljaXR5ID0gXGZyYWN7VE59e1ROK0ZQfSA9IFxmcmFje1RQfXtcc3VtIE5lZ2F0aXZlfSQkDQpOaOG6rW4geMOpdDogDQoNCk5oxrAgxJHDoyBuw7NpIOG7nyB0csOqbiwgw70gbmdoxKlhIGPhu6dhIFBvc2l0aXZlL25lZ2F0aXZlIGzDoCB0xrDGoW5nIMSR4buRaSwgdMawxqFuZyB04buxIGNobyBTZW5zaXRpdml0eS9TcGVjaWZpY2l0eS4gTuG6v3UgbmfGsOG7nWkgYsOhYyBzxKkgxJHhuqNvIHbhu4sgdHLDrSBj4bunYSBt4bulYyB0acOqdSBwaMOhdCBoaeG7h24vbG/huqFpIHRy4burIHRow6wgU2Vucy9zcGVjcyBz4bq9IMSR4buVaSB24buLIHRyw60gY2hvIG5oYXUsIG5oxrBuZyBjaMO6bmcgxJHhu4F1IMSRbyBsxrDhu51uZyB0w61uaCBo4buvdSBk4bulbmcgY+G7p2EgbcO0IGjDrG5oICh0ZXN0IGNo4bqpbiDEkW/DoW4pLCBoYXkga2jhuqMgbsSDbmcgY2hvIHBow6lwIMSRw6FwIOG7qW5nIG3hu6VjIHRpw6p1IHBow6F0IGhp4buHbi9sb+G6oWkgdHLhu6sgYuG7h25oIGzDvSBj4bunYSBuZ8aw4budaSBiw6FjIHPEqS4gDQpN4buZdCBtw7QgaMOsbmggY8OzIHNlbnNpdGl2aXR5IGNhbyBjxaluZyBnaeG7kW5nIG5oxrAgbeG7mXQgYsOhYyBzxKkg4oCcY2FuIMSR4bqjbeKAnSBuw7NpIOKAnEPDs+KAnSB0csaw4bubYyBi4buHbmggbmjDom4sIHbDoCBzcGVjaWZpY2l0eSBjYW8gbMOgIHPhu7EgY2FuIMSR4bqjbSBuw7NpIOKAnEtow7RuZ+KAnSBraGkgbG/huqFpIHRy4burIMSR4buRaSB0xrDhu6NuZyBraMO0bmcgbeG6r2MgYuG7h25oLg0KDQpTZW5zaXRpdml0eSB2w6AgU3BlY2lmaWNpdHkgbHXDtG4gcGjhuqNpIMSRaSBj4bq3cCB24bubaSBuaGF1LCB2w6wgY2jDum5nIGLhu5Ugc3VuZyBjaG8gbmhhdS4gTeG7l2kgY2jhu4kgc+G7kSBuw6B5IGNo4buJIG3hu5tpIGtp4buDbSB0cmEgMSBow6BuZyB0cm9uZyBDb25mdXNpb24gbWF0cml4ICgxIOG7qW5nIGThu6VuZyA6IHBow6F0IGhp4buHbi9sb+G6oWkgdHLhu6spIHbDoCBkbyDEkcOzLCDEkeG7mWMgbOG6rXAgduG7m2kg4bupbmcgZOG7pW5nIGPDsm4gbOG6oWkuIE3hu5l0IHbDoGkgbcO0IGjDrG5oIGPDsyB0aOG7gyBy4bqldCB04buRdCBjaG8g4bupbmcgZHVuZyBwaMOhdCBoaeG7h24gIG5oxrBuZyBrw6ltIGNobyDhu6luZyBk4bulbmcgbG/huqFpIHRy4burIHbDoCBuZ8aw4bujYyBs4bqhaS4gTeG6t3Qga2jDoWMsIG3hu5l0IG3DtCBow6xuaCDigJxj4buxYyDEkW9hbiIgKG51bGwgbW9kZWwpIC0gbHXDtG4gbHXDtG4gdHLhuqMgbOG7nWkg4oCcQ8Oz4oCdIGhv4bq3YyDigJxLaMO0bmfigJ0gc+G6vSBjaG8gcmEgZ2nDoSB0cuG7iyB6ZXJvIGNobyBTZW5zaXRpdml0eSBob+G6t2MgU3BlY2lmaWNpdHkuIEtoaSBt4buZdCB0w6FjIGdp4bqjIG7DoG8gxJHDsyBjaOG7iSB0csOsbmggYsOgeSBTZW5zaXRpdml0eSBob+G6t2MgU3BlY2lmaWNpdHkgbmjGsG5nIGPhu5EgdMOsbmggbOG7nSDEkWkgY2jhu4kgc+G7kSBjw7JuIGzhuqFpLCBhbmggdGEvY2jhu4sgdGEgxJFhbmcgbXXhu5FuIGNoZSBnaeG6pXUgc+G7sSB0aOG6rXQsIHbDoCBt4buZdCBu4butYSBz4buxIHRo4bqtdCBraMO0bmcgcGjhuqNpIGzDoCBz4buxIHRo4bqtdC4NCg0KDQojIFTDrW5oIGNow61uaCB4w6FjDQoNCktow6FpIG5p4buHbSAiY2jDrW5oIHjDoWMiIHLhuqV0IGtow7MgxJHhu4MgZGnhu4VuIMSR4bqhdCwgdsOsIOG7nyDEkcOieSB0YSBjw7MgxJHhur9uIDIgbmjDo24gZ2nDoSB0cuG7iywgZG8gxJHDsyB0YSBjw7MgdGjhu4MgYsOgbiB24buBIHTDrW5oIGNow61uaCB4w6FjIGNodW5nIGNodW5nLCB04buVbmcgcXXDoXQgduG7m2kgY2jhu4kgc+G7kSBBY2N1cmFjeToNCg0KMSkgQWNjdXJhY3kgOiAoQUNDKQ0KDQpDw7MgdGjhu4MgdOG6oW0gZOG7i2NoIHRodeG6rXQgbmfhu68gQWN1cmFjeSBuaMawIOKAnMSR4buZIGNow61uaCB4w6FjIHThu5VuZyBxdcOhdOKAnSwgdsOsIG7DsyDEkcahbiBnaeG6o24gbMOgIHThu4kgbOG7hyBj4bunYSB04bqldCBj4bqjIHRyxrDhu51uZyBo4bujcCBwaMOibiBsb+G6oWkgxJDDum5nIChraMO0bmcgcGjDom4gYmnhu4d0IG5lZ2F0aXZlL3Bvc2l0aXZlKSB0csOqbiB0b8OgbiBi4buZIHRyxrDhu51uZyBo4bujcCB0cm9uZyBt4bqrdSBraeG7g20gxJHhu4tuaC4NCg0KJCRBY2N1cmFjeSA9IFxmcmFje1RQK1ROfXtUUCtUTitGUCtGTn0kJA0KDQrEkMOieSBsw6AgdGnDqnUgY2jDrSBwaOG7lSBiaeG6v24gbmjhuqV0ICh0aMaw4budbmcgxJHGsOG7o2MgbmdoxKkgxJHhur9uIMSR4bqndSB0acOqbikga2hpIGtp4buDbSDEkeG7i25oIGhp4buHdSBuxINuZyBj4bunYSBtw7QgaMOsbmggcGjDom4gbG/huqFpLCB0dXkgbmhpw6puIGdpw6EgdHLhu4sgdGjhu7FjIGThu6VuZyBj4bunYSBuw7MgdGjGsOG7nW5nIGvDqW0gdsOsIG7DsyBraMO0bmcgxJHhurdjIGhp4buHdSBjaG8gbeG7mXQgbeG7pWMgdGnDqnUgbsOgbyBj4bqjLiANCg0KMikgQmFsYW5jZWQgYWNjdXJhY3k6DQoNCktoaSAyIG5ow6NuIFBvc2l0aXZlL05lZ2F0aXZlIGLhu4sgbeG6pXQgY8OibiDEkeG7kWksIG3hu5l0IHRpw6p1IGNow60ga2jDoWMgcGjDuSBo4bujcCBoxqFuIGzDoCBCYWxhbmNlZCBhY2N1cmFjeSAoQkFDKSBoYXkgxJHhu5kgY2jDrW5oIHjDoWMgc2F1IGPDom4gYuG6sW5nOg0KDQokJEJBQyA9IFxmcmFje1RQUitUTlJ9ezJ9JCQNCg0KQ2hvIG3hu6VjIHRpw6p1IGNodXnDqm4gYmnhu4d0IGjGoW4sIHRhIGPDsyB0aOG7gyBz4butIGThu6VuZzoNCg0KMykgUHJlY2lzaW9uICjEkeG7mSBjaMOtbmggeMOhYykgaGF5IFBvc2l0aXZlIHByZWRpY3RpdmUgdmFsdWUgKFBQVik6DQoNCkzDoCB04buJIGzhu4cgdGjhu7FjIHPhu7EgcG9zaXRpdmUgdHLDqm4gdOG7lW5nIHPhu5EgY8OhYyB0csaw4budbmcgaOG7o3AgxJHGsOG7o2MgbcO0IGjDrG5oIGTDoW4gbmjDo24g4oCcUG9zaXRpdmXigJ0uIFByZWNpc2lvbiBsw6AgbeG7mXQgdGh14bqtdCBuZ+G7ryBiw6puIERhdGEgc2NpZW5jZSB2w6AgdHJvbmcgeSBo4buNYyBuw7MgdMawxqFuZyDEkcawxqFuZyB24bubaSBraMOhaSBuaeG7h20gwqsgUFBWIMK7LiBOw7MgxJFvIGzGsOG7nW5nIHTDrW5oIOKAnHjDoWMgxJHhu4tuaOKAnSwgaGF5IGto4bqjIG7Eg25nIHBow6JuIGxv4bqhaSBQb3NpdGl2ZSBjaMOtbmggeMOhYyBj4bunYSBtw7QgaMOsbmguIE5oaSB0aMOtY2ggZMO5bmcgUHJlY2lzaW9uIGjGoW4gUFBWLCB2w6wgdGh14bqtdCBuZ+G7ryDigJx2YWx1ZeKAnSB0dXkgY2h1ecOqbiBiaeG7h3QgY2hvIFBvc2l0aXZlLCBuaMawbmcga2jDtG5nIG7Ds2kgbMOqbiDDvSBuZ2jEqWEg4oCca2jhuqMgbsSDbmfigJ0gKHBlcmZvcm1hbmNlKSwgY8OybiBQb3NpdGl2ZSBoYXkgTmVnYXRpdmUgY2jhu4kgbMOgIHTDqm4gZ+G7jWkgcXV5IMaw4bubYy4NCg0KJCRQcmVjaXNpb24gPSBQUFYgPSBcZnJhY3tUUH17VFArRlB9JCQNCjQpIFTGsMahbmcgdOG7sTogTmVnYXRpdmUgcHJlZGljdGl2ZSB2YWx1ZSAoTlBWKSDEkW8gbMaw4budbmcga2jhuqMgbsSDbmcgbG/huqFpIHRy4burIGNow61uaCB4w6FjOg0KDQokJE5QViA9IFxmcmFje1ROfXtUTitGTn0kJA0KDQpMxrB1IMO9OiBD4bqpbiB0aOG6rW4gcGjDom4gYmnhu4d0IGdp4buvYSBQUFYvc2Vuc2l0aXZpdHkgdsOgIFJlY2FsbC9QcmVjaXNpb24uIFByZWNpc2lvbiDEkW8gbMaw4budbmcgdMOtbmgg4oCcY2h14bqpbiB4w6Fj4oCdLCBSZWNhbGwgxJFvIGzGsOG7nW5nIHTDrW5oIOKAnGjhu691IGThu6VuZ+KAnS4gTeG6uW8gxJHhu4Mgbmjhu5sgbMOgIOKAnETDoW0gcXV54bq/dCDEkeG7i25oIChSZWNhbGwgY2FvKSB2w6AgcXV54bq/dCDEkeG7i25oIMSRw7puZyAoUHJlY2lzaW9uIGNhbynigJ0uIEPFqW5nIG5oxrAgY+G6qW4gdGjhuq1uIGtoaSBkw7luZyB0aHXhuq10IG5n4buvIOKAnMSQ4buZIGNow61uaCB4w6Fj4oCdIHbDrCBnw6J5IG5o4bqnbSBs4bqrbiBnaeG7r2EgQWNjdXJhY3kgdsOgIFByZWNpc2lvbiAoQWNjdXJhY3kga2jDtG5nIHBow6JuIGJp4buHdCBQb3NpdGl2ZS9uZWdhdGl2ZSkuIEPDsyB0aOG7gyBk4buLY2ggUHJlY2lzaW9uIGzDoCDigJxLaOG6oyBuxINuZyB4w6FjIMSR4buLbmjigJ0uIA0KDQpQcmVjaXNpb24ga2jDtG5nIGto4bqjbyBzw6F0IG3DtCBow6xuaCBtw7R0IGPDoWNoIMSR4buZYyBs4bqtcCwgbcOgIMSR4bq3dCBtw7QgaMOsbmggdsOgbyBt4buZdCBi4buRaSBj4bqjbmggKGThu68gbGnhu4d1KS4gRG8gxJHDsyB0YSBraMO0bmcgbsOzaSBjaHVuZyBjaHVuZzrigJ1tw7QgaMOsbmggY2jDrW5oIHjDoWPigJ0sIG3DoCBsw6AgOuKAnSBNw7QgaMOsbmggY2jDrW5oIHjDoWMgxJHhu5FpIHbhu5tpIG3huqt1L2Thu68gbGnhu4d1IGhp4buHbiB0aOG7nWnigJ0uDQoNCjUpIEYgc2NvcmUNCg0KRG8gUmVjYWxsIChTZW5zaXRpdml0eSkgdsOgIFByZWNpc2lvbiBsw6AgaGFpIGtow6FpIG5p4buHbSBraMOhYyBuaGF1LCB0YSBjw7MgY2jhu4kgc+G7kSBGMSBzY29yZSBjaG8gcGjDqXAgxJHDoW5oIGdpw6EgY8OibiBi4bqxbmcgZ2nhu69hIDIgcGjhuqltIGNo4bqldCBuw6B5Lg0KDQpGMSBzY29yZTogxJDGsOG7o2MgxJHhu4tuaCBuZ2jEqWEgbmjGsCB0cnVuZyBiw6xuaCDEkWnhu4F1IGjDsmEgKGhhcm1vbmljIG1lYW4pIGdp4buvYSBQcmVjaXNpb24gdsOgIFJlY2FsbCAoUFBWIHbDoCBTZW5zKQ0KLiANCiQkRjEgPSAyXHRpbWVzIFxmcmFjezF9e1xmcmFjezF9e1JlY2FsbH0rXGZyYWN7MX17UHJlY2lzaW9ufX0kJA0KJCRGMSA9IDJcdGltZXMgXGZyYWN7UHJlY2lzaW9uIFx0aW1lcyBSZWNhbGx9e1ByZWNpc2lvbiArIFJlY2FsbH0kJA0KSG/hurdjOg0KDQokJEYxID0gXGZyYWN7Mlx0aW1lcyBUUH17Mlx0aW1lcyBUUCtGUCtGTn0kJA0KDQpGMSBsw6AgdHLGsOG7nW5nIGjhu6NwIMSR4bq3YyBiaeG7h3QgY+G7p2EgY8O0bmcgdGjhu6ljIHThu5VuZyBxdcOhdCBGYmV0YToNCg0KJCRGX3tcYmV0YSB9ID0gKDErXGJldGEgXnsyfSlcdGltZXMgXGZyYWN7UHJlY2lzaW9uXHRpbWVzIFJlY2FsbH17KFxiZXRhIF57Mn1cdGltZXMgUHJlY2lzaW9uKStSZWNhbGx9JCQNCg0KSGF5Og0KDQokJEZfe1xiZXRhIH0gPSBcZnJhY3soMStcYmV0YSBeezJ9KVx0aW1lcyBUUH17KDErXGJldGEgXnsyfSlcdGltZXMgVFAgKyAoXGJldGEgXnsyfVx0aW1lcyBGTikgKyBGUH0kJA0KDQpU4burIGPDtG5nIHRo4bupYyBuw6B5IHRhIGPDsm4gdMOtbmggxJHGsOG7o2MgY2jhu4kgc+G7kSBGMiAoxJHhurd0IHRy4buNbmcgc+G7kSBjYW8gaMahbiBjaG8gUmVjYWxsIHNvIHbhu5tpIFByZWNpc2lvbikgdsOgIEYwLjUgKHRy4buNbmcgc+G7kSBjYW8gaMahbiBjaG8gUHJlY2lzaW9uIHNvIHbhu5tpIFJlY2FsbCkuIA0KDQpOaMawIHbhuq15IEYxIMSRxrDhu6NjIGTDuW5nIGtoaSB0YSBxdWFuIHTDom0gxJHhu5NuZyDEkeG7gXUgdmFpIHRyw7IgY+G7p2EgY+G6oyBQcmVjaXNpb24gdsOgIFJlY2FsbCwgbsOzaSBjw6FjaCBraMOhYyB0YSBtdeG7kW4gTcO0IGjDrG5oIChxdXkgbHXhuq10IGNo4bqpbiDEkW/DoW4pIHbhu6thIE5o4bqheSwgduG7q2EgY2jDrW5oIHjDoWMuIFZp4buHYyBs4buxYSBjaOG7jW4gZ2nhu69hIFJlY2FsbCAoU2Vuc2l0aXZpdHkpIHbDoCBQcmVjaXNpb24gKFBQVikgdMO5eSB0aHXhu5ljIHbDoG8gbeG7pWMgdGnDqnUg4bupbmcgZOG7pW5nIGPhu6dhIG3DtCBow6xuaDogbmfGsOG7nWkgYsOhYyBzxKkgbXXhu5FuIFThuqdtIHNvw6F0IGLhu4duaCBoYXkgbXXhu5FuIFjDoWMgxJHhu4tuaCBi4buHbmggPyBO4bq/dSB4ZW0gY+G6oyAyIMSR4buBdSBxdWFuIHRy4buNbmcgbmjGsCBuaGF1IHRow6wgRjEgbMOgIHRpw6p1IGNow60gdGjDrWNoIGjhu6NwIG5o4bqldCBraGkga2nhu4NtIMSR4buLbmggbcO0IGjDrG5oIHBow6JuIGxv4bqhaSB2w6wga2hpIEYxIMSR4bqhdCB04buRaSDGsHUgdGjDrCBj4bqjIFByZWNpc2lvbiB2w6AgUmVjYWxsIMSR4buBdSBwaOG6o2kgdOG7kWkgxrB1LCBuZ8aw4bujYyBs4bqhaSBjaOG7iSBj4bqnbiBt4buZdCB0cm9uZyAyIGPDsyBnacOhIHRy4buLIHRo4bqlcCB0aMOsIEYxIHPhur0gdGjhuqVwLg0KDQo2KSBHIGluZGV4Og0KDQpDxaluZyBsacOqbiBxdWFuIMSR4bq/biBz4buxIGvhur90IGjhu6NwIFByZWNpc2lvbiB2w6AgUmVjYWxsLCB0YSBjw7MgRyBtZWFzdXJlIGhheSBjw7JuIGfhu41pIGzDoCBGb3dsa2Vz4oCTTWFsbG93cyBpbmRleCAoMTk4MykuIEcgc+G7rSBk4bulbmcgdHJ1bmcgYsOsbmggbmjDom46DQoNCiQkRyA9IFxzcXJ0e1JlY2FsbCBcdGltZXMgUHJlY2lzaW9ufSAgPSBcc3FydHtQUFYgXHRpbWVzIFRQUn0kJA0KDQojIFNhaSBzw7N0IHbDoCBuaOG6p20gbOG6q24NCg0KTeG7mXQgY8OhY2ggdGnhur9wIGPhuq1uIGtow6FjIMSR4buDIMSRw6FuaCBnacOhIG3DtCBow6xuaCwgxJHDsyBsw6AgdGEgcXVhbiB0w6JtIMSR4bq/biBuZ3V5IGPGoSBuaOG6p20gbOG6q24sIHNhaSBzw7N0LiBC4bqhbiBjw7MgdGjhu4MgbmdoxKkgxJHGoW4gZ2nhuqNuIHbhu4Egc+G7sSBiw7kgdHLhu6sgZ2nhu69hIGNow61uaCB4w6FjL25o4bqnbSBs4bqrbiwgbmjGsG5nIHF1YW4gaOG7hyBuw6B5IHBo4bupYyB04bqhcCBoxqFuIGNobyBuaOG7r25nIG3hu6VjIHRpw6p1IGNodXnDqm4gYmnhu4d0LCB2w6AgYsOgaSB0b8OhbiBwaMOibiBsb+G6oWkgbmhp4buBdSBuaMOjbiBnacOhIHRy4buLLCBraGkgxJHDsyB0w61uaCBjaMOtbmggeMOhYyB04buVbmcgcXXDoXQga2jDtG5nIMSR4bqjbSBi4bqjb3bhu4EgdOG7iSBs4buHIG5o4bqnbSBs4bqrbiB04buRaSB0aGnhu4N1LlRyb25nIHkgaOG7jWMsbmd1eSBjxqEgcGjDom4gbG/huqFpIG5o4bqnbSBsw6AgdGnDqnUgY2jDrSBxdWFuIHRy4buNbmcgY+G6p24ga2nhu4NtIHRyYSAuDQoNCk5nYXkgdOG7qyBDb25mdXNpb24gbWF0cml4LCB0YSDEkcOjIGPDsyB04bqnbiBzdeG6pXQgbmjhuqdtIGzhuqtuIHR1eeG7h3QgxJHhu5FpIDogRk4gdsOgIEZQLiBU4burIMSRw7MsIHRhIGPDsyB0aOG7gyB0w61uaCB04buJIGzhu4cgc2FpIHPDs3QNCg0KMSkgRmFsc2UgbmVnYXRpdmUgcmF0ZTogVOG7iSBs4buHIGxv4bqhaSB0cuG7qyBuaOG6p20gKEZOUikgdsOgDQoNCjIpIEZhbHNlIHBvc2l0aXZlIHJhdGUgPSBU4buJIGzhu4cgcGjDoXQgaGnhu4duIG5o4bqnbSAoRlBSKS4NCg0KJCRGTlIgPSBcZnJhY3tGTn17XHN1bSBQb3NpdGl2ZX0gPSBcZnJhY3tGTn17Rk4rVFB9ID0gMS1UUFIkJA0KDQokJEZQUiA9IFxmcmFje0ZQfXtcc3VtIE5lZ2F0aXZlfSA9IFxmcmFje0ZQfXtGUCtUTn0gPSAxLVROUiQkDQoNCk3hu5l0IG3DtCBow6xuaCB04buRdCBj4bqnbiBjw7MgRk5SIHbDoCBGUFIgdGjhuqVwLiBMxrB1IMO9IGzDoCB04bqnbSBxdWFuIHRy4buNbmcgY+G7p2EgRk5SIHbDoCBGUFIgdMO5eSB0aHXhu5ljIHbDoG8gYuG6o24gY2jhuqV0IGPhu6dhIHbhuqVuIMSR4buBLiBUcm9uZyBiw6BpIHRvw6FuIHjhur9wIGxv4bqhaSB0w61uIGThu6VuZywgdmnhu4djIHBow6JuIGxv4bqhaSBuaOG6p20gbeG7mXQgaOG7kyBzxqEgdmF5IHbhu5FuIHThu6sgdOG7kXQgdGjDoG5oIHjhuqV1IHPhur0gZ8OieSBo4bqtdSBxdeG6oyDDrXQgbmdoacOqbSB0cuG7jW5nIGjGoW4gdHLGsOG7nW5nIGjhu6NwIG5nxrDhu6NjIGzhuqFpLiBUcm9uZyBZIGjhu41jIHRow6wga2jDoWMsIHZp4buHYyBsb+G6oWkgdHLhu6sgbmjhuqdtIGzhuqtuIG3hu5l0IGPEg24gYuG7h25oIMSRw7RpIGtoaSBz4bq9IGfDonkgaOG6rXUgcXXhuqMgbmdoacOqbSB0cuG7jW5nIHbDrCBi4buHbmggbmjDom4gY8OzIHRo4buDIHThu60gdm9uZyBraGkga2jDtG5nIMSRxrDhu6NjIMSRaeG7gXUgdHLhu4sga+G7i3AgdGjhu51pLCB0cm9uZyBraGkgdmnhu4djIGNo4bqpbiDEkW/DoW4gbmjhuqdtIG5nxrDhu51pIGLDrG5oIHRoxrDhu51uZyB0aMOgbmggYuG7h25oIG5ow6JuIHRow6wgaOG6rXUgcXXhuqMgY8OzIHRo4buDIMOtdCBuZ2hpw6ptIHRy4buNbmcgaMahbiAoZG8gduG6q24gY8OybiBjxqEgaOG7mWkga2nhu4NtIGNo4bupbmcgbOG6oWkgduG7m2kgbmjhu69uZyB4w6l0IG5naGnhu4dtIGLhu5Ugc3VuZykuDQoNCkhhaSBjaOG7iSBz4buRIGtow6FjIGhp4bq/bSBn4bq3cCBoxqFuIGzDoDoNCg0KMykgRmFsc2UgZGlzY292ZXJ5IHJhdGUgKEZEUiksIHThu4kgbOG7hyBwaMOhdCBoaeG7h24gbmjhuqdtLCBjw7Mgw70gbmdoxKlhIG5naOG7i2NoIHbhu5tpIFByZWNpc2lvbiAoUFBWKS4gDQoNCiQkRkRSID0gXGZyYWN7RlB9e0ZQK1RQfSA9IDEtUFBWID0gMSDigJMgUHJlY2lzaW9uJCQNClbDoCANCg0KNCkgRmFsc2Ugb21pc3Npb24gcmF0ZSAoRk9SKTogdOG7iSBs4buHIGxv4bqhaSB0cuG7qyBuaOG6p20NCg0KJCRGRFIgPSBcZnJhY3tGTn17Rk4rVE59ID0gMS1OUFYkJA0KDQpMxrB1IMO9OiB04bqhbSB0aOG7nWkgY2jDum5nIHRhIHTDoWNoIGtow6FpIG5p4buHbSBGUFIsIEZOUiwgRkRSIHJhIGto4buPaSBraMOhaSBuaeG7h20gU2FpIGzhuqdtIHR5cGUgSSB2w6AgVHlwZSBJSSB0cm9uZyBraeG7g20gxJHhu4tuaCBnaeG6oyB0aHV54bq/dCB0aOG7kW5nIGvDqiwgZMO5IGNow7puZyBjw7MgbGnDqm4gaOG7hyB24bubaSBuaGF1LiBUaHXhuq10IG5n4buvIFNhaSBs4bqnbSB0eXBlIEkgdsOgIElJIGtow7RuZyBnacO6cCDDrWNoIGNobyB2aeG7h2Mga2nhu4NtIMSR4buLbmggbcO0IGjDrG5oIHBow6JuIGxv4bqhaS4NCg0KNSkgVMawxqFuZyB04buxIEJBQywgdGEgY8WpbmcgY8OzIHRo4buDIHTDrW5oIEJhbGFuY2VkIEVycm9yIHJhdGUgKHThu4kgbOG7hyBzYWkgbOG6p20gc2F1IGPDom4gYuG6sW5nKToNCg0KJCRCRVIgPSBcZnJhY3tGUFIrRk5SfXsyfSQkDQoNCjYpIEJyaWVyIHNjb3JlOg0KDQrEkGnhu4NtIHPhu5EgQnJpZXIgKEJTKSDEkcaw4bujYyBnaeG7m2kgdGhp4buHdSBi4bufaSBHbGVubiBXLiBCcmllciB2w6BvIG7Eg20gMTk1MC4gQlMgxJHGsOG7o2MgeMOhYyDEkeG7i25oIG5oxrAgZ2nDoSB0cuG7iyB0cnVuZyBiw6xuaCBj4bunYSBjw6FjIGLDrG5oIHBoxrDGoW5nIHNhaSBz4buRIGdp4buvYSB4w6FjIHN14bqldCBk4buxIGLDoW8gYuG7n2kgbcO0IGjDrG5oIHbDoCB4w6FjIHN14bqldCB0aOG7sWMgdOG6vy4gTMawdSDDvTogQlMgY2h1ecOqbiBiaeG7h3QgY2hvIHThu6tuZyBuaMOjbiBnacOhIHRy4buLLCB0aMOtIGThu6UgbuG6v3UgxJHhurd0IFBvc2l0aXZlIGzDoG0gbeG7pWMgdGnDqnUsIHRow6wgeMOhYyBzdeG6pXQgdGjhu7FjIHThur8gY+G7p2EgbeG7mXQgdHLGsOG7nW5nIGjhu6NwIFBvc2l0aXZlIHPhur0gPSAxLCB2w6AgbmfGsOG7o2MgbOG6oWkgdHLGsOG7nW5nIGjhu6NwIG5lZ2F0aXZlIGPDsyB4w6FjIHN14bqldCB0aOG7sWMgdOG6vyA9MC4gTuG6v3UgdHLDqm4gbeG7mXQgY8OhIHRo4buDIFBvc2l0aXZlIG3DoCBtw7QgaMOsbmggxrDhu5tjIHTDrW5oIHJhIHjDoWMgc3XhuqV0IGzDoCAwLjc5IGNo4bqzbmcgaOG6oW4sIHRow6wgYsOsbmggcGjGsMahbmcga2jDoWMgYmnhu4d0IGNobyBjYXNlIMSRw7MgbMOgICgwLjc5LTEpXjIgPSAwLjA0NDEuDQoNCk3hu5l0IG3DtCBow6xuaCB04buRdCBz4bq9IMaw4bubYyB0w61uaCB4w6FjIHN14bqldCB24bubaSBzYWkgYmnhu4d0IG5o4buPIG5o4bqldCBjw7MgdGjhu4MsIGNobyB04bqldCBj4bqjIG5ow6NuIGdpw6EgdHLhu4sgdsOgIHThuqV0IGPhuqMgdHLGsOG7nW5nIGjhu6NwIHRyb25nIG3huqt1IGtp4buDbSDEkeG7i25oLCBraGkgxJHDsyBCcmllciBzY29yZSBz4bq9IHRo4bqlcC4gR2nDoSB0cuG7iyB04buRaSDGsHUgY+G7p2EgQlMgbMOgIDAsIHbDoCBnacOhIHRy4buLIHThu5NpIHThu4cgbmjhuqV0IGzDoCAxLiBEbyDEkcOzIEJyaWVyIHNjb3JlIMSRxrDhu6NjIHhlbSBuaMawIDEgdGnDqnUgY2jDrSDEkeG7gyDEkcOhbmggZ2nDoSBraOG6oyBuxINuZyBTYWkgbOG6p20gY+G7p2EgbcO0IGjDrG5oICh0xrDGoW5nIHThu7EgRk5SIHbDoCBGUFIpLg0KDQpUYSBjw7MgdGjhu4MgdMOtbmggQlMgaG/DoG4gdG/DoG4gdGjhu6cgY8O0bmcgaG/hurdjIHPhu60gZOG7pW5nIGjDoG0gYmllcnNjb3JlIHRyb25nIHBhY2thZ2Ugc2NvcmluZw0KDQo3KSBMb2dhcml0aG1pYyBsb3NzDQoNCk3hu5l0IGNo4buJIHPhu5Ega2jDoWMgbMOgIGxvZ2xvc3MsIMSRxrDhu6NjIHPhu60gZOG7pW5nIG5oaeG7gXUgdHJvbmcgZ2nhu5tpIE1hY2hpbmUgbGVhcm5pbmcsIGxvZ2xvc3MgxJHGsOG7o2MgxJHhu4tuaCBuZ2jEqWEgbmjGsCB0cnVuZyBiw6xuaCBj4bunYSAtbG9nKHjDoWMgc3XhuqV0IGThu7EgYsOhbykuDQoNCkNobyBiw6BpIHRvw6FuIG5o4buLIHBow6JuLCB0YSBt4bq3YyDEkeG7i25oIHTDrW5oIGxvZ2xvc3MgdGhlbyB4w6FjIHN14bqldCBjaG8gbmjDo24gUG9zaXRpdmUuIA0KDQokJGxvZ2xvc3MgPSBcZnJhY3tcc3VtIC1sb2cocF97aX0pfXtOfSQkDQoNCkxvZ2xvc3Mgbmjhuq9tIMSR4bq/biBt4bulYyB0acOqdSDEkW8gbMaw4budbmcgc2FpIGJp4buHdCBj4bunYSBtw7QgaMOsbmgsIGdpw6EgdHLhu4sgdOG7kWkgdGhp4buDdSAobcO0IGjDrG5oIGNow61uaCB4w6FjIG5o4bqldCkgY+G7p2EgbG9nbG9zcz0wLCBuaMawbmcga2jDtG5nIGPDsyBuZ8aw4buhbmcgY2FvIG5o4bqldC4gDQoNCjgpIFTGsMahbmcgdOG7sSwgdGEgY8OzIGNo4buJIHPhu5EgTG9nYXJpdGhtaWMgc2NvcmluZyBydWxlIChMU1IpIA0KDQokJExTUiA9IG1lYW4obG9nKHBfe2l9KSkkJA0KTFNSIHRyw6FpIGThuqV1IHbhu5tpIGxvZ2xvc3MgbmjGsG5nIMO9IG5naMSpYSB0xrDGoW5nIMSRxrDGoW5nLCBtw7QgaMOsbmggY8OgbmcgY2jDrW5oIHjDoWMgdGjDrCBMU1IgY8OgbmcgZ+G6p24gMC4gTcO0IGjDrG5oIGPDoG5nIGvDqW0gY2jDrW5oIHjDoWMgdGjDrCBMU1IgdGnhur9uIHbhu4EgLUluZg0KDQo5KSBN4buZdCBjaOG7iSBz4buRIGtow6FjIGPDsyB0aOG7gyB0w61uaCBsw6AgU1NSIDogU3BoZXJpY2FsIFNjb3JpbmcgUnVsZSwNCg0KU1NSIMSRxrDhu6NjIHjDoWMgxJHhu4tuaCBi4bqxbmcgdHJ1bmcgYsOsbmggY+G7p2EgdMOtY2ggeMOhYyBzdeG6pXQgdGnDqm4gbMaw4bujbmcgY+G7p2EgbeG7mXQgbmjDo24gY2hvIG3hu5dpIHRyxrDhu51uZyBo4bujcCBpIHbDoCB04buVbmcgeMOhYyBzdeG6pXQgdGnDqm4gbMaw4bujbmcgY2hvIChjw6FjKSBuaMOjbiBqIGPDsm4gbOG6oWkgY2hvIHRyxrDhu51uZyBo4bujcCBpIG7DoHkgOg0KDQokJFNTUiA9IE1lYW4oUF97aX0oXHN1bSBQX3tpan0pKSQkDQoNCkdpw6EgdHLhu4sgU1NSIGPDsyB0aOG7gyDEkcaw4bujYyB0w61uaCBi4bufaSBow6BtIHNwaHNjb3JlIGPhu6dhIHBhY2thZ2Ugc2NvcmluZy4NCg0KMTApICBNZWFuIG1pc2NsYXNzaWZpY2F0aW9uIGVycm9yIChNTUNFKQ0KDQpDdeG7kWkgY8O5bmc6IE7hur91IHhlbSBr4bq/dCBxdeG6oyBraeG7g20gxJHhu4tuaCBtw7QgaMOsbmggbMOgIG3hu5l0IGJp4bq/biBsb2dpY2FsOiBZPTEgbuG6v3UgcGjDom4gbG/huqFpIG5o4bqnbSwgWT0wIG7hur91IHBow6JuIGxv4bqhaSDEkcO6bmcsIHRhIHPhur0gdGjhu5FuZyBrw6ogxJHGsOG7o2Mgc2FpIHPhu5EgdHJ1bmcgYsOsbmg6IE1lYW4gbWlzY2xhc3NpZmljYXRpb24gZXJyb3IgKE1NQ0UpIDoNCg0KJCRtbWNlID0gXGZyYWN7XHN1bSBQcmVkaWN0aW9uIFxuZXEgVHJ1dGh9e059JCQNCg0KIyBNw7QgaMOsbmggbmjGsCB0ZXN0IGNo4bqpbiDEkW/DoW46IA0KDQpN4buZdCBraMOhaSBuaeG7h20ga2jDoWMsIHLhuqV0IHBo4buVIGJp4bq/biB0cm9uZyB0aOG7kW5nIGvDqiB5IGjhu41jIG5oxrBuZyBraMO0bmcgxJHGsOG7o2MgcXVhbiB0w6JtIGLDqm4gZ2nhu5tpIE1hY2hpbmUgbGVhcm5pbmcsIMSRw7MgbMOgIExpa2VsaWhvb2QgcmF0aW9zIChQb3NpdGl2ZSB2w6AgTmVnYXRpdmUpLCB04bupYyB04buJIHPhu5Ega2jhuqMgZMSpIETGsMahbmcgdsOgIMOCbS4NCg0KTFIrIMSRxrDhu6NjIHjDoWMgxJHhu4tuaCBi4bqxbmcgxJHhu5kgbmjhuqF5IChj4bunYSB0ZXN0IGNo4bqpbiDEkW/DoW4pIGNoaWEgY2hvICgxLcSR4buZIMSR4bq3YyBoaeG7h3UpLiBMUi0gxJHGsOG7o2MgdMOtbmggYuG6sW5nICgxLcSR4buZIG5o4bqheSkgY2hpYSBjaG8gxJHhu5kgxJHhurdjIGhp4buHdToNCg0KJCRMUisgPSBcZnJhY3tTRU59ezEtU1BFQ30kJA0KJCRMUi0gPSBcZnJhY3sxLVNFTn17U1BFQ30kJA0KDQpN4buZdCBjw6FjaCB04buVbmcgcXXDoXQsIExSIMSRbyBsxrDhu51uZyBt4bupYyDEkeG7mSBsacOqbiBo4buHIGdp4buvYSBxdXkgbHXhuq10IGNo4bqpbiDEkW/DoW4gdsOgIHRy4bqhbmcgdGjDoWkgYuG7h25oIGzDvS4gVGjDrSBk4bulLCBMUisgY2FvICg+MSkgY2hvIHRo4bqleSBr4bq/dCBxdeG6oyB4w6l0IG5naGnhu4dtIGTGsMahbmcgdMOtbmggY8OzIGxpw6puIHF1YW4gxJHhur9uIHPhu7EgaGnhu4duIGRp4buHbiBj4bunYSBi4buHbmggbMO9LiBD4bqhbmggxJHDsywgTFIgY8OybiBsw6AgcGjGsMahbmcgdGnhu4duIHRydW5nIGdpYW4gY2hvIHBow6lwIGJp4buHbiBsdeG6rW4sIHNvIHPDoW5oIGhp4buHdSBuxINuZyBj4bunYSBt4buZdCB4w6l0IG5naGnhu4dtIGNo4bqpbiDEkW/DoW4gbsOgeSB24bubaSBt4buZdCB4w6l0IG5naGnhu4dtIGtow6FjLiBWaeG7h2Mgc28gc8OhbmggbsOgeSBk4buxYSB2w6BvIFByZXRlc3QgcHJvYmFiaWxpdHkgdsOgIFBvc3R0ZXN0IHByb2JhYmlsaXR5LiANCg0KUG9zdHRlc3QgcHJvYmFiaWxpdHkgY+G7p2EgbeG7mXQgeMOpdCBuZ2hp4buHbSBjw7MgdGjhu4MgxJHGsOG7o2MgeMOhYyDEkeG7i25oIHRoZW8gMyBixrDhu5tjOg0KDQokJCBwcmV0ZXN0IE9kZHMgPSBQcmV0ZXN0IFByb2IgLyAoMS1QcmV0ZXN0IFByb2IpJCQNCg0KJCQgUG9zdHRlc3QgT2RkcyA9IFByZXRlc3Qgb2RkcyAqIExSICQkDQoNCiQkUG9zdCB0ZXN0IFByb2IgPSBQb3N0dGVzdCBvZGRzLygxK1Bvc3QgdGVzdCBvZGRzKSQkDQoNCk5oxrAgduG6rXksIG3hu5l0IHjDqXQgbmdoaeG7h20gbeG7m2kgbuG6v3UgbXXhu5FuIMSRxrDhu6NjIGPDtG5nIG5o4bqtbiBsw6AgdOG7kXQgaMahbiBzbyB24bubaSBt4buZdCB4w6l0IG5naGnhu4dtIGPFqSwgeMOpdCBuZ2hp4buHbSBt4bubaSBuw6B5IHBo4bqjaSBjw7MgUG9zdHRlc3QgUHJvYiBjYW8gaMahbiBzbyB24bubaSBQcmV0ZXN0IFByb2IsIHThu6ljIGzDoCBMUiBwaOG6o2kgPiAxIHbDoCBjw6BuZyBjYW8gY8OgbmcgdOG7kXQuIE5nxrDhu51pIHRhIMaw4bubYyB0w61uaCBy4bqxbmcgxJHhu4MgUG9zdCB0ZXN0IHByb2JhYmlsaXR5IGNobyBjaOG6qW4gxJFvw6FuIEPDsyBi4buHbmggdMSDbmcgIGjGoW4gMTUlIHRow6wgTFIrIHBo4bqjaSBs4bubbiBoxqFuIDIsIHTGsMahbmcgdOG7sSDEkeG7gyBwb3N0IHRlc3QgcHJvYiBjaG8gcGjDqXAgbG/huqFpIHRy4burIGLhu4duaCB0aMOsIExSLSBwaOG6o2kgbmjhu48gaMahbiAwLDIuIExSIGPDoG5nIGfhuqduIDEgdGjDrCBoaeG7h3UgbsSDbmcgY+G7p2EgeMOpdCBuZ2hp4buHbSBjw6BuZyB0aOG6pXAgKHNvIHbhu5tpIFByZXRlc3QgcHJvYikuIFBvc3QgdGVzdCBQcm9iIGPhu6dhIHjDqXQgbmdoaeG7h20gbeG7m2kgbsOgeSBs4bqhaSBjw7MgdGjhu4MgxJHGsOG7o2MgZMO5bmcgbmjGsCBQcmV0ZXN0IFByb2IgY2hvIG3hu5l0IGLGsOG7m2MgdGnhur9wIHRoZW8gdHJvbmcgcXV5IHRyw6xuaCBjaOG6qW4gxJFvw6FuLg0KDQpUcm9uZyB0aMOtIGThu6UgbsOgeSwgZ2nhuqMgc+G7rSB0YSBjw7MgcHJldmFsZW5jZSBj4bunYSBi4buHbmggbMO9IFRpbSBt4bqhY2ggxJHGsOG7o2MgxrDhu5tjIHTDrW5oIHThu6sgdHJhaW5zZXQgbMOgIDQ3LjYlIHbDoCBkw7luZyBuw7MgbmjGsCBQcmV0ZXN0UHJvYiwgdOG7qyBMUisgPSA0LjA1IHRhIGPDsyB0aOG7gyDGsOG7m2MgdMOtbmggxJHGsOG7o2MgUG9zdHRlc3QgUHJvYiBj4bunYSBxdXkgbHXhuq10IGNo4bqpbiDEkW/DoW4gbeG7m2kgKG3DtCBow6xuaCBSYW5kb20gRm9yZXN0KSBsw6AgMC43OSwgdOG7qWMgbMOgIHbhu5tpIHF1eSBsdeG6rXQgbeG7m2kgdGEgxJHDoyBsw6BtIHRoYXkgxJHhu5VpIFByZXRlc3QgUHJvYiBsw6puIHThu5tpICs2NS41ICUsIGNobyB0aOG6pXkgbcO0IGjDrG5oIG3hu5tpIGzDoCBt4buZdCBjw7RuZyBj4bulIHRo4buxYyBz4buxIGPDsyDDrWNoIHNvIHbhu5tpIHN1eSDEkW/DoW4gbmfhuqt1IG5oacOqbi4uDQoNCiMgVMOtbmggdMawxqFuZyBo4bujcCBUacOqbiBsxrDhu6NuZy9UaOG7sWMgdOG6vzoNCg0KMSkgVHLhu4sgc+G7kSBLYXBwYSBj4bunYSBDb2hlbjoNCg0KS2FwcGEgbMOgIG3hu5l0IHRy4buLIHPhu5EgdGjhu5FuZyBrw6ogZG8gSmFjb2IgQ29oZW4gxJHhu4EgeHXhuqV0IG7Eg20gMTk2MCBuaOG6sW0gxJFvIGzGsOG7nW5nIG3hu6ljIMSR4buZIHTGsMahbmcgaOG7o3AgZ2nhu69hIDIgaGF5IG5oaeG7gXUga+G6v3QgcXXhuqMgxJFvIGzGsOG7nW5nIChpbnRlci1yYXRlciBhZ3JlZW1lbnQpLiBLYXBwYSDEkcaw4bujYyDEkeG7i25oIG5naMSpYSBuaMawOg0KDQokJFxrYXBwYSA9IDEtIFxmcmFjezEtcF97b319ezEtcF97ZX19JCQNCg0KVHJvbmcgxJHDsyBwbyBsw6AgdOG7iSBs4buHIHTGsMahbmcgaOG7o3AgcXVhbiBzw6F0IMSRxrDhu6NjIHRyw6puIHRo4buxYyB04bq/LCB2w6AgcGUgbMOgIHThu4kgbOG7hyB0xrDGoW5nIGjhu6NwIMaw4bubYyDEkW/DoW4gdGhlbyBwaMOibiBi4buRIG5n4bqrdSBuaGnDqm4uIE7hur91IHPhu7EgdMawxqFuZyBo4bujcCBsw6AgdHV54buHdCDEkeG7kWksIEthcHBhPTEsIE7hur91IHPhu7EgdMawxqFuZyBo4bujcCBrw6ltIChzbyB24bubaSBwaMOibiBi4buRIG5n4bqrdSBuaGnDqm4pIHRow6wga2FwcGEgZ2nhuqNtIHbhu4EgMC4NCg0KQ2hvIG3hu6VjIHRpw6p1IGtp4buDbSDEkeG7i25oIHBo4bqpbSBjaOG6pXQgbcO0IGjDrG5oLCB0YSBjw7MgdGjhu4Mgw6FwIGThu6VuZyBow6BtIGNvaGVuLmthcHBhIGPhu6dhIHBhY2thZ2UgcHN5Y2ggxJHhu4MgdMOtbmggZ2nDoSB0cuG7iyBLYXBwYSB2w6AgV2VpZ2h0ZWQgS2FwcGEgY2hvIGPhuqMga+G6v3QgcXXhuqMgbMOgIExhYmVsIGzhuqtuIFjDoWMgc3XhuqV0IGThu7EgYsOhby4NCg0KMikgSOG7hyBz4buRIHTGsMahbmcgcXVhbiBNYXR0aGV3cyAoTUNDKSANCg0KTUNDIGzDoCBt4buZdCBwaMawxqFuZyBwaMOhcCByacOqbmcgY+G7p2EgZ2nhu5tpIE1hY2hpbmUgbGVhcm5pbmcsIGTDuW5nIMSR4buDIMSRw6FuaCBnacOhIHBo4bqpbSBjaOG6pXQgY+G7p2EgbcO0IGjDrG5oIHBow6JuIGxv4bqhaSBuaOG7iyBwaMOibiwgxJHGsOG7o2MgZ2nhu5tpIHRoaeG7h3UgYuG7n2kgQnJpYW4gVy4gTWF0dGhld3MgIHbDoG8gbsSDbSAxOTc1LiBN4bulYyB0acOqdSBj4bunYSBNQ0MgbMOgIGto4bqvYyBwaOG7pWMgduG6pW4gxJHhu4EgZOG7ryBsaeG7h3UgYuG7iyBt4bqldCBjw6JuIGLhurFuZy4gTUNDIGPDsyBi4bqjbiBjaOG6pXQgbMOgIG3hu5l0IGjhu4cgc+G7kSB0xrDGoW5nIHF1YW4gZ2nhu69hIGdpw6EgdHLhu4sgVGjhu7FjIHThur8gdsOgIEvhur90IHF14bqjIGThu7EgYsOhbyBj4bunYSBtw7QgaMOsbmgsIG7DsyDEkcaw4bujYyB4w6FjIMSR4buLbmggbmjGsCBzYXU6DQoNCiQkTUNDID0gXGZyYWN7KFRQKlROKS0oRlAqRk4pfXtcc3FydHsoVFArRlApKihUUCtGTikqKFROK0ZQKSooVE4rRk4pfX0kJA0KDQpNQ0MgY8OzIGxpw6puIGjhu4cgdHLhu7FjIHRp4bq/cCB24bubaSB0cuG7iyBz4buRIHRo4buRbmcga8OqIENoaSBiw6xuaCBwaMawxqFuZyBj4bunYSBi4bqjbmcgMngyIDoNCg0KJCRcbGVmdCB8IE1DQyBccmlnaHQgfCA9IFxzcXJ0e1xmcmFje1xjaGkgXjJ9e059fSQkDQoNCkdpw6EgdHLhu4sgY+G7p2EgTUNDIGRhbyDEkeG7mW5nIHRyb25nIGtob+G6o25nIC0xIMSR4bq/biArMSwgTUNDPSsxIGJp4buDdSB0aOG7iyBjaG8g4oCca+G6v3QgcXXhuqMgcGjDom4gbG/huqFpIGhvw6BuIGjhuqNv4oCdLCBNQ0M9MCBjaG8gdGjhuqV5IG3DtCBow6xuaCB2w7QgZOG7pW5nIChraMO0bmcgaMahbiBnw6wgc+G7sSBwaMOhbiDEkW/DoW4gbmfhuqt1IG5oacOqbikgY8OybiBNQ0M9LTEgY2hvIHRo4bqleSBtw7QgaMOsbmgga2jDtG5nIG5o4buvbmcgdsO0IGThu6VuZyBob8OgbiB0b8OgbiBtw6AgY8OybiB0dXnhu4d0IMSR4buRaSBzYWksIHbDrCBr4bq/dCBxdeG6oyBwaMOibiBsb+G6oWkgaG/DoG4gdG/DoG4gdHLDoWkgbmdo4buLY2ggduG7m2kgcXVhbiBzw6F0IHRo4buxYyB04bq/Lg0KDQpNQ0MgY8OzIHRo4buDIMSRxrDhu6NjIHBow6JuIHTDrWNoIHRow6BuaCAyIG5ow6JuIHThu5E6IA0KDQozKSBDaOG7iSBz4buRIE1hcmtlZG5lc3MgKE1LKSDEkcaw4bujYyDEkeG7i25oIG5naMSpYSBuaMawOg0KDQokJE1LID0gUFBWICsgTlBWIOKAkyAxJCQNCg0KNCkgQ2jhu4kgc+G7kSBJbmZvcm1lZG5lc3MgaGF5IGPDsm4gZ+G7jWkgbMOgIEJvb2ttYWtlciBJbmZvcm1lZG5lc3MgKEJNKToNCg0KJCRCTSA9IFRQUiArIFROUiDigJMgMSQkDQoNCk7hur91IHRpbmggw70gdGEgc+G6vSB0aOG6pXkgQk0gY2jDrW5oIGzDoCB0cuG7iyBz4buRIFlvdWRlbuKAmXMgSiB0cm9uZyBwaMOibiB0w61jaCBST0MsIHbDrDoNCg0KJCRZb3VkZW7igJlzIEogPSBTZW5zaXRpdml0eSArIFNwZWNpZmljaXR5IOKAkyAxJCQNCg0KTUNDIGNow61uaCBsw6AgdHJ1bmcgYsOsbmggbmjDom4gY+G7p2EgSW5mb3JtZWRuZXNzIChCTSkgdsOgIE1hcmtlZG5lc3MgKE1LKQ0KDQokJFxsZWZ0IHwgTUNDIFxyaWdodCB8ID0gXHNxcnR7Qk0gKk1LIH0kJA0KIyBQaMOibiB0w61jaCBST0MgdsOgIEFVQw0KDQpQaMawxqFuZyBwaMOhcCBjdeG7kWkgY8O5bmcgbcOgIGNow7puZyB0YSBz4bq9IGzDoG0gxJHDsyBsw6AgcGjDom4gdMOtY2ggUk9DLCBj4bulIHRo4buDIGzDoCBkaeG7h24gdMOtY2ggZMaw4bubaSBST0MgKEFVQykuIEtoaSBuaOG6r2MgxJHhur9uIMSRxrDhu51uZyBjb25nIFJPQyAoUmVjZWl2ZXIgb3BlcmF0aW5nIGNoYXJhY3RlcmlzdGljKSwgY8OhYyBi4bqhbiBiw6FjIHPEqSBk4buFIGTDoG5nIGxpw6puIGjhu4cgdOG7m2kgcGjGsMahbmcgcGjDoXAgxJHhu4MgxJHDoW5oIGdpw6EgaGnhu4d1IHF14bqjIGPhu6dhIG3hu5l0IHRlc3QgY2jhuqluIMSRb8OhbiDEkcahbiBiaeG6v24uIE3hu6VjIHRpw6p1IGzDoCB0aGnhur90IGzhuq1wIG3hu5l0IGJp4buDdSDEkeG7kyB0aOG7gyBoaeG7h24gc+G7sSBkYW8gxJHhu5luZyBj4bunYSBUUFIgdMO5eSB0aGVvIEZQUiBraGkgZOG7i2NoIGNodXnhu4NuIHF1YSBjw6FjIG5nxrDhu6FuZyBj4bqvdCBraMOhYyBuaGF1IHRyw6puIHRoYW5nIMSRaeG7g20gY2jhuqluIMSRb8OhbiAoU2NvcmUpLiANCg0KIVtdKHJvYy5wbmcpDQoNCk3hu5l0IGPDoWNoIHThu5VuZyBxdcOhdCwgcGjDom4gdMOtY2ggUk9DIGPDsyB0aOG7gyDDoXAgZOG7pW5nIGNobyBt4buNaSBtw7QgaMOsbmggbmjhu4sgcGjDom4gduG7m2kgU2NvcmUgY2jDrW5oIGzDoCBr4bq/dCBxdeG6oyB4w6FjIHN14bqldCBtw6AgbcO0IGjDrG5oIG7DoHkgdGnDqm4gbMaw4bujbmcuIA0KDQpUaGVvIG3hurdjIMSR4buLbmgsIG5nxrDhu6FuZyBj4bqvdCBj4bunYSB4w6FjIHN14bqldCBwaMOibiBsb+G6oWkgbMOgIDAuNSBuaMawbmcgdHJvbmcgbeG7mXQgc+G7kSBob8OgbiBj4bqjbmgsIG5nxrDhu6FuZyBuw6B5IGPDsyB0aOG7gyDEkcaw4bujYyDEkWnhu4F1IGNo4buJbmggKGfhu41pIGzDoCBjw6JuIGLhurFuZywgY2FsaWJyYXRpb24pLiBUaMOtIGThu6UgbuG6v3UgdGEgbXXhu5FuIHThuqFvIHJhIG3hu5l0IHF1eSBsdeG6rXQgY2jhuqluIMSRb8OhbiBuaOG6oXkgaMahbiBzbyB24bubaSBt4bq3YyDEkeG7i25oLCB0YSBjw7MgdGjhu4MgZ2nhuqNtIG5nxrDhu6FuZyBj4bqvdCBjw7JuIDAuNCBoYXkgMC4zIGNobyBuaMOjbiBQb3NpdGl2ZSwgbmjGsCB24bqteSBz4bq9IGzDoG0gdMSDbmcgxJHhu5NuZyB0aOG7nWkgRlBSIHbDoCBUUFIsIHbDoCBnaeG6o20gxJHhu5NuZyB0aOG7nWkgRk5SLCBUTlIgdGhlbyBsdeG6rXQgYsO5IHRy4burLCBsw6BtIGdp4bqjbSB0aGnhu4N1IHThu4kgbOG7hyBi4buPIHPDs3QgY2jhuqluIMSRb8Ohbi4gxJDGsOG7nW5nIGNvbmcgUk9DIGNobyBwaMOpcCBraOG6o28gc8OhdCB04bqldCBj4bqjIGto4bqjIG7Eg25nIHbhu4EgbmfGsOG7oW5nIGPhuq90IGtow6FjIG5oYXUgdsOgIOG6o25oIGjGsOG7n25nIGzDqm4gVFBSLCBGUFIuIFJPQyBjaG8gcGjDqXAgxJHDoW5oIGdpw6EgaGnhu4d1IG7Eg25nIHThu5VuZyBxdcOhdCBj4bunYSBtw7QgaMOsbmguIE3DtCBow6xuaCB04buRaSDGsHUgc+G6vSBjw7MgVFBSIGNhbyB2w6AgRlBSIHRo4bqlcCAoZ+G6p24gZ8OzYyB0csOqbiBiw6puIHRyw6FpIFJPQykgdsOgIEFVQyBn4bqnbiAxIG5o4bqldCBjw7MgdGjhu4MuIA0KDQojIEPDoWNoIGzDoG0gZ2lhbiBs4bqtbg0KDQrEkOG6p3UgdGnDqm4sIE5oaSBz4bq9IGNoaWEgc+G6uyB24bubaSBjw6FjIGLhuqFuIGPDoWNoIHTDrW5oIG5oYW5oIG5o4bqldCBjw6FjIHRpw6p1IGNow60gxJHDoW5oIGdpw6EgbcO0IGjDrG5oIG3DoCBraMO0bmcgY+G6p24gYuG6rW4gdMOibSDEkeG6v24gY8O0bmcgdGjhu6ljIGPhu6dhIGNow7puZy4gTmhpIGfhu41pIMSRw6J5IGzDoCBjw6FjaCBsw6BtICLEkeG7g3UiLCAiY2hlYXRpbmciIGhheSAiTcO6YSByw6x1IHF1YSBt4bqvdCB0aOG7oyIsIG5o4budIHbDoG8gcGFja2FnZSBtbHIuIFRyb25nIG1sciBjw7MgdMOtY2ggaOG7o3AgaOG6p3UgaOG6v3QgY8OhYyBjaOG7iSBz4buRIMSRw6FuaCBnacOhIG3DtCBow6xuaCwgbmdheSBj4bqjIG5o4buvbmcgY2jhu4kgc+G7kSBoaeG6v20gZ+G6t3AgbmjhuqV0LiBOaMawbmcgduG6pW4gxJHhu4EgxJHDsyBsw6AgY8OhYyBow6BtIG7DoHkgY2jhu4kgw6FwIGThu6VuZyDEkcaw4bujYyBjaG8gbW9kZWwgZG8gY2jDrW5oIG1sciBodeG6pW4gbHV54buHbiB2w6AgY2jhu4kgbmjhuq1uIG9iamVjdCBwcmVkaWN0aW9uIGPhu6dhIGNow61uaCBtbHIuIERvIMSRw7MsIHRhIHBo4bqjaWNhbiB0aGnhu4dwIMSR4buDIMSRw6FuaCB0csOhbyBu4buZaSBkdW5nIHByZWRpY3Rpb24gY+G7p2EgbeG7mXQgbW9kZWwgYuG6pXQga8OsLCBi4bqxbmcga+G6v3QgcXXhuqMgcHJlZGljdGlvbiBj4bunYSBtw7QgaMOsbmggbcOgIHRhIMSRYW5nIG114buRbiBraeG7g20gdHJhLiANCg0KQ2jhu4kgduG7m2kgNSBixrDhu5tjLCB0YSBjw7Mga+G6v3QgcXXhuqMgbmjGsCBzYXU6DQoNCmBgYHtyLG1lc3NhZ2UgPSBGQUxTRSx3YXJuaW5nPUZBTFNFfQ0KbGlicmFyeShtbHIpDQoNCiMgU3RlcCAxOg0KdGFzaz1tYWtlQ2xhc3NpZlRhc2soZGF0YT10ZXN0c2V0LHRhcmdldD0iQ2xhc3MiKQ0KDQojIFN0ZXAgMg0KZHVtbXlscm5lcj1tYWtlTGVhcm5lcigiY2xhc3NpZi5ycGFydCIscHJlZGljdC50eXBlID0icHJvYiIpDQpkdW1teW1vZD10cmFpbihkdW1teWxybmVyLHRhc2spDQpkdW1teXByZWQ9cHJlZGljdChkdW1teW1vZCx0YXNrKQ0KDQojIFN0ZXAgMw0KDQpkdW1teXByZWQkZGF0YSR0cnV0aDwtZGZwcmVkJFRydXRoDQpkdW1teXByZWQkZGF0YSRwcm9iLk5lZ2F0aXZlPC1kZnByZWQkQ2xhc3NOZWcNCmR1bW15cHJlZCRkYXRhJHByb2IuUG9zaXRpdmU8LWRmcHJlZCRDbGFzc1Bvcw0KZHVtbXlwcmVkJGRhdGEkcmVzcG9uc2U8LWRmcHJlZCRDbGFzc0xhYg0KDQojIFN0ZXAgNA0KDQptZWFzdXJlPWxpc3QobWxyOjphY2MsDQogICAgICAgICAgICAgbWxyOjphdWMsDQogICAgICAgICAgICAgbWxyOjpiYWMsDQogICAgICAgICAgICAgbWxyOjpiZXIsDQogICAgICAgICAgICAgbWxyOjpicmllciwNCiAgICAgICAgICAgICBtbHI6OmYxLA0KICAgICAgICAgICAgIG1scjo6Z21lYW4sDQogICAgICAgICAgICAgbWxyOjpncHIsDQogICAgICAgICAgICAgbWxyOjprYXBwYSwNCiAgICAgICAgICAgICBtbHI6OmxvZ2xvc3MsDQogICAgICAgICAgICAgbWxyOjpsc3IsDQogICAgICAgICAgICAgbWxyOjptY2MsDQogICAgICAgICAgICAgbWxyOjptbWNlLA0KICAgICAgICAgICAgIG1scjo6cXNyLA0KICAgICAgICAgICAgIG1scjo6c3NyKQ0KDQojIFN0ZXAgNQ0KDQpwZXJmb3JtYW5jZShkdW1teXByZWQsbWVhc3VyZSklPiVhcy5tYXRyaXgoKQ0KDQpgYGANCg0KT2ggeWVzICEgUuG6pXQgxJHGoW4gZ2nhuqNuIHBo4bqjaSBraMO0bmcgbsOoID8NCg0KQsOieSBnaeG7nSB0YSBz4bq9IGzDoG0gInRo4bqtdCIsIHThu6ljIGzDoCB0w61uaCB0aOG7pyBjw7RuZyBs4bqnbiBsxrDhu6N0IHThu6tuZyBjaOG7iSBz4buRIDoNCg0KIyBDw6FjaCBsw6BtIHRo4bqtdA0KDQojIyBUw61uaCBo4buvdSBk4bulbmcNCg0KYGBge3IsbWVzc2FnZSA9IEZBTFNFLHdhcm5pbmc9RkFMU0V9DQpUUFI9VFAvKFRQK0ZOKQ0KVE5SPVROLyhUTitGUCkNClNFTj1UUFINClNQRUM9VE5SDQpSZWNhbGw9U0VODQoNCnJiaW5kKFRQUixUTlIsU0VOLFNQRUMsUmVjYWxsKQ0KYGBgDQoNCiMjIFTDrW5oIGNow61uaCB4w6FjDQoNCmBgYHtyLG1lc3NhZ2UgPSBGQUxTRSx3YXJuaW5nPUZBTFNFfQ0KQUNDPShUUCtUTikvKFRQK0ZQK1ROK0ZOKQ0KQkFDPShUUFIrVE5SKS8yDQpQUFY9VFAvKFRQK0ZQKQ0KTlBWPVROLyhUTitGTikNClByZWNpc2lvbj1QUFYNCkc9c3FydChSZWNhbGwqUHJlY2lzaW9uKQ0KRjI9KDErMl4yKSooUHJlY2lzaW9uKlJlY2FsbCkvKCgyXjIpKlByZWNpc2lvbitSZWNhbGwpDQpGMC41PSgxKygwLjUpXjIpKihQcmVjaXNpb24qUmVjYWxsKS8oKDAuNV4yKSpQcmVjaXNpb24rUmVjYWxsKQ0KRjE9MipUUC8oVFArVFArRlArRk4pDQpGc2NvcmU9KDErMSkqKFByZWNpc2lvbipSZWNhbGwpLygxKlByZWNpc2lvbitSZWNhbGwpDQoNCnJiaW5kKEFDQyxCQUMsUFBWLE5QVixQcmVjaXNpb24sRyxGMSxGMixGMC41LEZzY29yZSkNCmBgYA0KDQojIyBOZ3V5IGPGoSBzYWkgbOG6p20NCg0KYGBge3IsbWVzc2FnZSA9IEZBTFNFLHdhcm5pbmc9RkFMU0V9DQpGTlI9Rk4vKEZOK1RQKQ0KRlBSPUZQLyhGUCtUTikNCkZEUj1GUC8oVFArRlApDQpGT1I9MS1OUFYNCkJFUj13aXRoKGRmcHJlZCxtZWFuKChDbGFzc1Bvcz4wLjUgJiBCaW5UcnV0aD09MCkgfCAoQ2xhc3NQb3M8MC41ICYgQmluVHJ1dGg9PTEpKSkNCmxvZ2xvc3M9d2l0aChkZnByZWQsLW1lYW4obG9nMTAoQ2xhc3NQb3MpKSkNCkxTUj13aXRoKGRmcHJlZCxtZWFuKGxvZzEwKENsYXNzUG9zKSkpDQptbWNlPXdpdGgoZGZwcmVkLG1lYW4oVHJ1dGghPUNsYXNzTGFiKSkNCg0KbGlicmFyeShzY29yaW5nKQ0KYnJpZXI9IGRmcHJlZCU+JXNjb3Jpbmc6OmJyaWVyc2NvcmUoZGF0YT0uLEJpblRydXRofkNsYXNzUG9zKSU+JW1lYW4oKQ0KDQpTU1I9ZGZwcmVkJT4lDQogIHNjb3Jpbmc6OnNwaHNjb3JlKGRhdGE9LixCaW5UcnV0aH5DbGFzc1BvcyklPiVtZWFuKCkNCg0KcmJpbmQoRk5SLEZQUixGRFIsRk9SLEJFUixsb2dsb3NzLExTUixtbWNlLGJyaWVyLFNTUikNCmBgYA0KDQojIyBUw61uaCB0xrDGoW5nIGjhu6NwDQoNCmBgYHtyLG1lc3NhZ2UgPSBGQUxTRSx3YXJuaW5nPUZBTFNFfQ0KQk09VFBSK1ROUi0xDQpNSz1QUFYrTlBWLTENCk1DQz0oVFAqVE4tRlAqRk4pLyhzcXJ0KChUUCtGUCkqKFRQK0ZOKSooVE4rRlApKihUTitGTikpKQ0KS2FwcGE9cHN5Y2g6OmNvaGVuLmthcHBhKGFzLm1hdHJpeChkZnByZWRbLGMoMSw1KV0pKSU+JS4kd2VpZ2h0ZWQua2FwcGENCg0KcmJpbmQoQk0sTUssTUNDLEthcHBhKQ0KYGBgDQoNCiMjTGlrZWxpaG9vZCByYXRpb3MNCg0KYGBge3IsbWVzc2FnZSA9IEZBTFNFLHdhcm5pbmc9RkFMU0V9DQpMUnBvcz1TRU4vKDEtU1BFQykNCkxSbmVnPSgxLVNFTikvU1BFQw0KDQpyYmluZChMUnBvcyxMUm5lZykNCmBgYA0KDQojIyBST0MtQVVDDQoNCmBgYHtyLG1lc3NhZ2UgPSBGQUxTRSx3YXJuaW5nPUZBTFNFfQ0KbGlicmFyeShwUk9DKQ0KbXlyb2MgPC0gcm9jKFRydXRofkNsYXNzUG9zLCBkYXRhPWRmcHJlZCkNCg0KQVVDPW15cm9jJGF1Yw0KDQpkYXRhX2ZyYW1lKGZwcj0xLW15cm9jJHNwZWNpZmljaXRpZXMsDQogICAgICAgICAgIHRwcj1teXJvYyRzZW5zaXRpdml0aWVzKSU+JQ0KICBnZ3Bsb3QoYWVzKHg9ZnByLHltaW49MCx5bWF4PXRwcikpKw0KICBnZW9tX3BvbHlnb24oYWVzKHk9dHByKSxmaWxsPSJyZWQiLGFscGhhPTAuMykrDQogIGdlb21fcGF0aChhZXMoeT10cHIpLGNvbD0icmVkMyIsc2l6ZT0xKSsNCiAgZ2VvbV9hYmxpbmUobGluZXR5cGU9J2Rhc2hlZCcpKw0KICB0aGVtZV9idygpKw0KICBjb29yZF9lcXVhbCgpKw0KICBsYWJzKHg9IkZhbHNlIHBvc2l0aXZlIHJhdGUiLHk9IlRydWUgUG9zaXRpdmUgcmF0ZSIpKw0KICBnZ3RpdGxlKHBhc3RlMCgiQVVDPSIscm91bmQoQVVDLDMpKSkNCg0KYGBgDQoNCiMgVOG7lW5nIGvhur90DQoNCkLDoGkgdGjhu7FjIGjDoG5oIMSR4bq/biDEkcOieSBsw6AgaOG6v3QuIENo4bqvYyBjaOG6r24gbuG7mWkgZHVuZyBj4bunYSBiw6BpIGPDsyBuaGnhu4F1IHRoaeG6v3Ugc8OzdCwgbmjGsG5nIE5oaSDEkcOjIGPhu5EgZ+G6r25nIGjhu4cgdGjhu5FuZyB04bqldCBj4bqjIG5o4buvbmcgZ8OsIE5oaSBo4buNYyDEkcawxqFjIHThu6sgZ2nhu5tpIE1hY2hpbmUgbGVhcm5pbmcgdsOgIGRp4buFbiDEkeG6oXQgbsOzIHRoZW8gaMaw4bubbmcg4bupbmcgZOG7pW5nIHbDoG8geSBo4buNYyBsw6JtIHPDoG5nLiBOaMawIGPDoWMgYuG6oW4gxJHDoyB0aOG6pXksIHZp4buHYyB0w61uaCB0aOG7pyBjw7RuZyBuaOG7r25nIGdpw6EgdHLhu4sgbsOgeSBob8OgbiB0b8OgbiBraOG6oyB0aGkuSHkgduG7jW5nIGLDoGkgdmnhur90IGdpw7pwIMOtY2ggY2hvIGPDoWMgYuG6oW4gYsOhYyBzxKkgxJFhbmcgbMOgbSBxdWVuIHbhu5tpIE1MLiBDaMO6YyBjw6FjIGLhuqFuIHRow6BuaCBjw7RuZy4NCg==