Giới thiệu
Thân chào các bạn, sau nhiều tháng gián đoạn, Nhi tiếp tục tái khởi công Project Bayes for Vietnam. Dự án này được phát động vào năm 2017 với mục tiêu phổ cập về phương pháp thống kê theo trường phái Bayes nhằm thay thế hoàn toàn những công cụ truyền thống. Đối tượng của chúng tôi là các bạn bác sĩ và sinh viên y khoa.
Qua những bài trước, Nhi và các bạn trong nhóm đã giới thiệu với các bạn cách thay thế những quy trình thống kê phổ biến như Bảng chéo, t-test, tương quan Pearson, và đặc biệt là thiết kế ANOVA (2 bài). Hôm nay, Nhi sẽ hướng dẫn các bạn mở rộng thiết kế ANOVA thành ANCOVA (phân tích hiệp phương sai) theo trường phái Bayes, thay thế cho ANCOVA cổ điển.
Do đã gián đoạn một thời gian, Nhi xin nhắc lại một số vấn đề cơ bản của phương pháp suy diễn thống kê theo trường phái Bayes như sau:
- Nguyên tắc tổng quát cho mọi suy diễn Bayes có thể tóm tắt bằng công thức :
\[p(\theta|outcome, data)\propto p(outcome|\theta) p(\theta ,data)\]
Mục tiêu của chúng ta là mô tả một phân phối hậu định của một tham số Theta (vế bên trái, chính là xác suất điều kiện giá trị của theta khi có thông tin về kết quả outcome và dữ liệu data). Đại lượng này tỉ lệ với tích của hàm likelihood (xác suất có điều kiện cho phép ước tính kết quả khi có theta và dữ liệu) và một phân phối tiền định (prior= một giả thuyết về phân phối của theta trước khi ta nhìn thấy dữ liệu và kết quả).
Tham số theta là mục tiêu trong mọi bài toán, chúng ta chỉ cần xác định được những bộ phận còn lại (likelihood, priors) và tạo ra một mô hình phù hợp có chứa theta.
Khi đã phác thảo được mô hình trên giấy thì việc viết code không khó khăn lắm. Thí dụ chỉ nắm vững code cho mô hình GLM, ta có thể bao quát hầu hết những công cụ thống kê cổ điển, bao gồm t-test, logistic, ANOVA, ANCOVA, MANOVA, vân vân.
Do đó ta có thể thay thế tất cả những công cụ thống kê truyền thống dùng null hypothesis testing và p_value bằng phân tích Bayes.
Mục tiêu
Bài hôm nay, như thường lệ sẽ đi theo 3 bước như sau:
Trước hết, Nhi ôn lại lý thuyết về thiết kế ANCOVA, đưa ra 1 bài toán tiêu biểu và tái hiện quy trình ANCOVA cổ điển.
Sau đó Nhi sẽ chuyển bài toán này thành mô hình Bayes, và hướng dẫn các bạn viết STAN code cho mô hình.
Cuối cùng, Nhi sẽ khai thác phân phối hậu định cho các tham số cần quan tâm, và suy diễn Bayes.
Thí dụ minh họa
Trong bài này, Nhi sử dụng một bộ dữ liệu có thực từ một nghiên cứu pilot nhằm kiểm định một công nghệ xét nghiệm chức năng hô hấp hiện đại kết hợp vi khí dung NaCl và đo hệ số khuếch tán của 2 loại khí CO/NO qua màng phế nang/mao mạch, cho phép ước tính bề dày của lớp màng này. Như ta biết, chức năng trao đổi khí ở người phụ thuộc vào đặc tính vật lý của màng phế nang, mao mạch như tỉ lệ thuận với diện tích khả dụng, và tỉ lệ nghịch với độ dày. Một số bệnh lý hô hấp làm biến đổi những chỉ số này, thí dụ khí phế thũng (emphysema) phá hủy cấu trúc mô phế nang còn bệnh xơ phổi (fibrosis) làm tăng độ dày của màng.
Ta sẽ dùng thiết kế ANCOVA để so sánh giá trị bề dày màng phế nang mao mạch (Thickness) giữa 3 nhóm: người bình thường (Control), Khí phế thũng (Emphysema) và Xơ phổi (Fibrosis).
Nguyên nhân chúng ta phải dùng thiết kế ANCOVA chứ không phải ANOVA đơn biến, vì qua thăm dò cho thấy biến Thickness có tương quan với BMI, nên BMI được xét như một yếu tố gây nhiễu mà ta cần khắc phục. Phần tiếp theo Nhi sẽ giải thích rõ hơn về điều này.
Bước 1: Thăm dò dữ liệu
library(tidyverse)
dat=read.csv("https://raw.githubusercontent.com/kinokoberuji/R-Tutorials/master/Membthickness.csv",sep=";")
dat$Diagnostic%<>%as.factor()%>%
recode_factor(.,`N`= "Control",`E`="Emphysema",`F`="Fibrosis")
data=dat
head(dat)%>%knitr::kable()
19.46740 |
Emphysema |
0.1427005 |
31.83391 |
Emphysema |
0.4760602 |
19.94450 |
Emphysema |
0.2029516 |
23.43750 |
Emphysema |
0.3968931 |
24.33748 |
Emphysema |
0.2606986 |
27.44455 |
Emphysema |
0.3658132 |
table(dat$Diagnostic)
##
## Control Emphysema Fibrosis
## 15 14 9
Như các bạn thấy, dữ liệu gồm 1 biến kết quả Thickness là số liên tục, 1 biến phân nhóm Diagnostic là biến rời rạc với 3 bậc giá trị; và BMI là biến số liên tục.
Trước hết, cỡ mẫu rất hạn chế cho từng phân nhóm, đặc biệt là nhóm Fibrosis chỉ có 9 trường hợp. Ngay cả cho một phân tích ANOVA đơn biến cổ điển, cỡ mẫu thấp là một nhược điểm rõ ràng, đây là một trong những nguyên nhân mà Nhi muốn dùng Bayes. Lưu ý rằng nếu cỡ mẫu quá thấp, suy diễn Bayes cũng có nhiều nguy cơ sai lầm không kém gì phái frequentist, vì lúc này phân phối hậu định sẽ phụ thuộc rất lớn vào prior, nếu prior sai thì ta sẽ dễ dàng suy diễn sai. Tuy nhiên, giữa một kết quả có sức mạnh thống kê chắc chắn thấp (frequentist) và một cơ hội thành công (Bayes), Nhi chọn điều thứ hai.
dat%>%group_by(Diagnostic)%>%
summarise_at("Thickness",
funs(Mean=mean,
Median=median,
Skewness=e1071::skewness(.),
kurtosis=e1071::kurtosis(.),
LL=quantile(.,probs=0.05),
UL=quantile(.,probs=0.95)
))%>%knitr::kable()
Control |
0.5909805 |
0.5801316 |
0.1429082 |
-0.7085155 |
0.4638749 |
0.7465380 |
Emphysema |
0.3074973 |
0.2893625 |
0.1528214 |
-1.5464368 |
0.1639136 |
0.4798438 |
Fibrosis |
0.9229586 |
0.9901514 |
-0.2625314 |
-1.7776112 |
0.7236907 |
1.0795619 |
library(ggridges)
sum_df=dat%>%group_by(Diagnostic)%>%
summarise_at("Thickness",median)
dat%>%ggplot(aes(x=Thickness,y=Diagnostic,fill=Diagnostic,col=Diagnostic))+
geom_density_ridges(alpha=0.6,size=1,scale=1)+
geom_point(alpha=0.3)+
geom_point(data=sum_df,aes(x=Thickness),
shape=23,size=5,fill="white",stroke=1.2)+
geom_rug(alpha=0.5)+
coord_flip()+
scale_color_manual(values=c("#021ce5","#350091","#d8021e"))+
scale_fill_manual(values=c("#1677ff","#a616ff","#ff1654"))+
theme_bw()
## Picking joint bandwidth of 0.0637

Thống kê mô tả gợi ý rằng giá trị Thickness tăng cao ở nhóm Fibrosis, và giảm ở nhóm Emphysema so với nhóm Control; Tuy không thể khẳng định chắc chắn rằng Thicknes phân bố bình thường ở các phân nhóm, nhất là nhóm Fibrosis nhưng trung vị gần trung bình và skewness rất gần 0 gợi ý về tính đối xứng của phân bố.
Một phân tích ANOVA đơn biến khẳng định có sự khác biệt ý nghĩa về Thickness giữa 3 phân nhóm Diagnostic
car::Anova(lm(Thickness~Diagnostic,dat),type="III")
## Anova Table (Type III tests)
##
## Response: Thickness
## Sum Sq Df F value Pr(>F)
## (Intercept) 5.2389 1 394.995 < 2.2e-16 ***
## Diagnostic 2.0916 2 78.852 1.085e-13 ***
## Residuals 0.4642 35
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
cor.test(dat$BMI,dat$Thickness)
##
## Pearson's product-moment correlation
##
## data: dat$BMI and dat$Thickness
## t = 3.151, df = 36, p-value = 0.003271
## alternative hypothesis: true correlation is not equal to 0
## 95 percent confidence interval:
## 0.1706224 0.6830964
## sample estimates:
## cor
## 0.4649452
Một phân tích tương quan cho thấy Thickness có quan hệ tuyến tính với BMI, yếu nhưng có ý nghĩa.
Ôn lại về ANOVA
Trong bài trước, chúng ta đã biết ANOVA có bản chất là một mô hình hồi quy tuyến tính có dạng Y ~ X nhằm ước lượng hiệu ứng của một yếu tố X (phân nhóm) làm thay đổi một kết quả định lượng Y.
ANOVA thường được áp dụng để so sánh, nhưng thực ra nó là một quy trình nhiều gồm nhiều bước :
Đầu tiên, ta bắt buộc phải kiểm tra những giả định cho thiết kế ANOVA, theo thứ tự quan trọng gồm các giả định về Phương sai đồng nhất giữa các phân nhóm, giả định về phân phối chuẩn của dữ liệu (hay của sai số phần dư của mô hình), tính độc lập cho mỗi quan sát cá thể.
Dựng mô hình hồi quy tuyến tính (thí dụ bằng hàm lm() của R) và kiểm định tính phù hợp dữ liệu của mô hình bằng Fisher’s F test (hàm aov() của R hoặc Anova của package car)
Hậu kiểm (posthoc test) hoặc phân tích mô hình với trọng số tương phản (contrast analysis), nhằm mục tiêu định vị sự khác biệt, so sánh bắt cặp tuần tự giữa các phân nhóm
Ước tính kích thước hiệu ứng (effect-size) của hiệu ứng (eta squared)
Từ ANOVA đến ANCOVA
Vì ANOVA là một mô hình hồi quy tuyến tính, ta hoàn toàn có thể đưa thêm vào mô hình những biến khác (gọi là hiệp biến – covariate) để tạo ra một mô hình hồi quy tuyến tính đa biến. Lúc này, thiết kế ANOVA được mở rộng sẽ trở thành thiết kế ANCOVA – phân tích hiệp phương sai (Analysis of covariance).
Một cách đơn giản, ANCOVA chính là một mô hình tuyến tính có cùng mục tiêu như ANOVA, nhưng chứa đồng thời biến phân nhóm X và một hiệp biến số C độc lập :
Y ~ (X + C)
Tại sao ta lại (phải) làm như thế ?
Sơ đồ sau đây sẽ giúp bạn hình dung điều gì xảy ra khi ta đưa thêm biến số C vào mô hình đơn biến của ANOVA: Như ta thấy, mô hình Y ~ X chỉ có 1 biến duy nhất, nó quá đơn giản. Sự đơn giản này có thể là vừa đủ để đáp ứng mục tiêu thực dụng: kiểm chứng hiệu ứng của thí nghiệm / so sánh giữa 3-4 phân nhóm. Nhưng đôi khi, suy nghĩ ngây thơ và chỉ nhìn thấy mục tiêu so sánh sẽ dẫn đến một kết quả F test yếu hoặc thậm chí âm tính; vì trị số F là tỉ lệ giữa phần phương sai mà mô hình cho phép giải thích (MSM) và phần còn lại mà mô hình không thể giải thích được (MSR).
Một mô hình đơn giản không bao giờ đủ để giải thích đủ thế giới hiện thực.
Khi đưa thêm 1 hiệp biến C vào mô hình, C cho phép giải thích thêm một phần phương sai và giá trị Residual sum of square (SSR) do đó sẽ thu nhỏ lại. Mô hình 2 biến mạnh hơn, phù hợp thực tế hơn.
Thiết kế ANCOVA sẽ hữu ích cho nhiều hoàn cảnh, như :
- Khi nghiên cứu sinh muốn điều chỉnh hiệu ứng của một yếu tố gây nhiễu đã được tiên liệu trước. Yếu tố nhiễu này có thể gây sai lệch kết quả suy diễn thống kê của ANOVA một cách hệ thống.
Đa số nghiên cứu y học cơ bản đều phải dùng đến ANCOVA để hiệu chỉnh các yếu tố như tuổi, cân nặng, chiều cao cho các đại lượng sinh lý, sinh hóa vì cơ thể con người là một mạng lưới tương quan phức tạp…
Thí dụ : một nghiên cứu về chức năng tim mạch được thực hiện đồng thời ở 2 địa điểm là Cao nguyên và Đồng bằng, thì những kết quả phân tích khí máu phải được hiệu chỉnh theo độ cao, vì độ cao làm thay đổi áp suất Oxy trong khí quyển. Tương tự, ta cũng cần hiệu chỉnh các chỉ số về chức năng trao đổi khí theo giá trị Haemoglobin hoặc giới tính, vì Nam giới có nồng độ Haemoglobin trong máu cao hơn so với phụ nữ. Hầu hết những nghiên cứu định lượng trong Y học đều hiệu chỉnh kết quả theo Tuổi của bệnh nhân để loại trừ ảnh hưởng của sự lão hóa.
Trong các nghiên cứu thử nghiệm hiệu quả điều trị, có khi chúng ta cần chuẩn hóa giá trị cơ bản của outcome Y ở thời điểm trước khi điều trị (Baseline) cho tất cả bệnh nhân, để đảm bảo đánh giá chính xác hiệu quả điều trị của thuốc (với giả định rằng các bệnh nhân có trạng thái bệnh lý tương đương nhau khi bắt đầu điều trị).
Khi nghiên cứu sinh chủ quan muốn khảo sát một giả thuyết đặc biệt, nhằm xác định vai trò của nhiều yếu tố khác nhau có ảnh hưởng đến bệnh lý.
Nghiên cứu sinh cũng có thể chưa hài lòng với ý nghĩa thống kê và Effect-size của hiệu ứng chính trong mô hình đơn giản ban đầu và hy vọng rằng việc mở rộng mô hình sẽ giúp họ làm cho kết qủa đẹp hơn ?
Cũng như mô hình Mixed model và thiết kế thử nghiệm lâm sàng có xét random effect theo block , mục tiêu của ANCOVA cũng là thu nhỏ phần phương sai mà mô hình không giải thích được; tuy nhiên khác biệt giữa 2 phương pháp Mixed model và ANCOVA nằm ở chỗ trong Mixed model, random effect nằm bên ngoài hiệu ứng chính và phần lớn trường hợp, cơ chế của sai số ngẫu nhiên này không thể định nghĩa hay giải thích được (thí dụ cơ địa của bệnh nhân). Trong khi đó, thiết kế ANOVA chủ động sử dụng một hiệp biến số thực sự được khảo sát, được tiên liệu và có tham gia vào hiệu ứng chính trong mô hình.
ANCOVA theo Frequentist
Bây giờ Ta sẽ làm một phân tích ANCOVA theo trường phái frequentist trong R:
Vì ANCOVA cơ bản cũng là một thiết kế ANOVA, nó cũng yêu cầu những giả định tương tự như ANOVA. Bên cạnh đó, thiết kế ANCOVA còn có một giả định khác riêng cho nó, đó là :
Giả định về quan hệ tuyến tính giữa Y và hiệp biến số C : Do ta đang sử dụng hồi quy tuyến tính, lý tưởng nhất là Y tương quan tuyến tính với C. Tương quan này càng mạnh, ảnh hưởng của C đối với phương sai của Y càng lớn, thậm chí lấn át cả hiệu ứng của X. Nếu giữa Y và C không có quan hệ tuyến tính thì không có cơ sở để xét C như hiệp biến số.
Giả định về sự độc lập giữa X và C : Không có sự khác biệt về C giữa các phân nhóm
Giả định về sự đồng nhất của liên hệ tuyến tính giữa biến kết quả Y và hiệp biến số C cho từng phân nhóm X : nói cách khác, đồ thị tuyến tính Y = C gần như song song với nhau (cùng Slope) giữa các phân nhóm, hay : không có hiệu ứng do tương tác giữa C và X.
Điều kiện này không phải luôn được thỏa mãn trên thực tế, và không hoàn toàn bắt buộc – nó chỉ thể hiện cho chính khuyết điểm của thiết kế ANCOVA vì trong mô hình Y = X+C, hiển nhiên chỉ có 1 slope được ước tính cho C.
Nếu dùng mô hình GLM tổng quát, ta không còn lệ thuộc vào các thiết kế kinh điển nữa nhưng hoàn toàn có thể mở rộng mô hình chứa quan hệ tương tác Y = C+X+C :X hay Y=C*X và phân tích hiệu ứng của cả 3 thành phần X, C, C :X. Dù sao trong bài này Nhi vẫn bám sát theo thiết kế cổ điển với hy vọng là mô hình Y=C cùng slope value cho cả 3 phân nhóm.
Đầu tiên, Levene test cho phép xác nhận giả định phươn sai đồng nhất:
# Levene test
aov(Thickness ~ Diagnostic, dat)%>%car::leveneTest()
## Levene's Test for Homogeneity of Variance (center = median)
## Df F value Pr(>F)
## group 2 1.1918 0.3157
## 35
Giả định Slope đồng nhất có thể kiểm tra một cách trực quan:
dat%>%ggplot(aes(x=BMI,y=Thickness,fill=Diagnostic,col=Diagnostic))+
geom_point(alpha=0.5,size=2)+
geom_smooth(method="lm",alpha=0.3)+
scale_color_manual(values=c("#021ce5","#350091","#d8021e"))+
scale_fill_manual(values=c("#1677ff","#a616ff","#ff1654"))+
theme_bw()

Biểu đồ cho thấy Slope có vẻ giống nhau giữa Control và Fibrosis, nhưng có khác biệt ở nhóm Emphysema.
Ta cũng có thể kiểm chứng lại giả định này dựa vào mô hình có cả hiệu ứng tương tác giữa BMI và Diagnostic:
lmod0=lm(Thickness ~ Diagnostic * BMI, data)
car::Anova(lmod0)
## Anova Table (Type II tests)
##
## Response: Thickness
## Sum Sq Df F value Pr(>F)
## Diagnostic 1.61433 2 76.2006 6.764e-13 ***
## BMI 0.07520 1 7.0989 0.01198 *
## Diagnostic:BMI 0.05005 2 2.3625 0.11041
## Residuals 0.33896 32
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
Có thể thấy rằng không có hiệu ứng tương tác ý nghĩa giữa BMI và Diagnostic (p=0.11)
Như vậy ta có thể áp dụng ANCOVA với mô hình : Thickness ~ BMI + Diagnostic. Tuy nhiên, trước hết ta sẽ đưa trung bình BMI về 0, vì thực chất biến BMI không phải là mục tiêu mà ta quan tâm trong mô hình. Nó có mặt ở đó chỉ nhằm hỗ trợ cho F test và hiệu chỉnh cho hiệu ứng của Phân nhóm:
`
data$BMI=scale(data$BMI,scale = F)
Nhi dựng 2 mô hình lm1 và lm2 lần lượt có công thức:
Thickness ~ BMI + Diagnostic
Thickness ~ BMI + Diagnostic -1
lm1 <- lm(Thickness ~ BMI + Diagnostic + BMI, data = data)
lm2 <- lm(Thickness ~ BMI + Diagnostic -1, data = data)
summary(lm1)
##
## Call:
## lm(formula = Thickness ~ BMI + Diagnostic + BMI, data = data)
##
## Residuals:
## Min 1Q Median 3Q Max
## -0.195395 -0.085894 -0.000105 0.071973 0.217252
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 0.625631 0.030748 20.347 < 2e-16 ***
## BMI 0.013195 0.005147 2.564 0.014951 *
## DiagnosticEmphysema -0.316207 0.041749 -7.574 8.49e-09 ***
## DiagnosticFibrosis 0.236581 0.058470 4.046 0.000284 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 0.107 on 34 degrees of freedom
## Multiple R-squared: 0.8478, Adjusted R-squared: 0.8344
## F-statistic: 63.13 on 3 and 34 DF, p-value: 5.555e-14
summary(lm2)
##
## Call:
## lm(formula = Thickness ~ BMI + Diagnostic - 1, data = data)
##
## Residuals:
## Min 1Q Median 3Q Max
## -0.195395 -0.085894 -0.000105 0.071973 0.217252
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## BMI 0.013195 0.005147 2.564 0.015 *
## DiagnosticControl 0.625631 0.030748 20.347 < 2e-16 ***
## DiagnosticEmphysema 0.309423 0.028598 10.820 1.49e-12 ***
## DiagnosticFibrosis 0.862212 0.042811 20.140 < 2e-16 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 0.107 on 34 degrees of freedom
## Multiple R-squared: 0.9735, Adjusted R-squared: 0.9704
## F-statistic: 312.6 on 4 and 34 DF, p-value: < 2.2e-16
Như ta thấy, ý nghĩa của 2 mô hình là khác nhau, mô hình lm1 có chứa Intercept , được hiểu như giá trị Thickness trung bình ở nhóm Control và có BMI=0, sau đó 2 dummy variable cho phân nhóm bệnh lý cho phép diễn giải: Thickness giảm 0.316 µm ở nhóm Emphysema và tăng 0.236 µm ở phân nhóm Fibrose.
Mô hình thứ hai không chứa Intercept, và hệ số hồi quy cho mỗi Dummy variable cũng chính là giá trị Thickness trung bình ở từng phân nhóm.
Trong cả 2 mô hình, hệ số hồi quy cho BMI như nhau = 0.013, cho biết Thickness sẽ tăng 0.013 µm cho mỗi đơn vị BMI.
Khi áp dụng hàm Anova, ta có kết quả của F test cho riêng BMI và Diagnostic. Kết quả cho thấy sau khi hiệu chỉnh cho hiệu ứng của BMI, Yếu tố bệnh lý có hiệu ứng độc lập làm thay đổi bề dày màng phế nang: F(3,34)=400.64; p_value < 2.10^-16
car::Anova(lm1, type = "III")
## Anova Table (Type III tests)
##
## Response: Thickness
## Sum Sq Df F value Pr(>F)
## (Intercept) 4.7368 1 413.9951 < 2.2e-16 ***
## BMI 0.0752 1 6.5722 0.01495 *
## Diagnostic 1.6143 2 70.5467 7.935e-13 ***
## Residuals 0.3890 34
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
car::Anova(lm2, type = "III")
## Anova Table (Type III tests)
##
## Response: Thickness
## Sum Sq Df F value Pr(>F)
## BMI 0.0752 1 6.5722 0.01495 *
## Diagnostic 13.7520 3 400.6443 < 2e-16 ***
## Residuals 0.3890 34
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
Marginal effect của Phân nhóm bệnh lý có thể trình bày như sau:
lmod <- lm(Thickness ~ as.vector(BMI)+Diagnostic, data = data)
newdata <- data_frame(Diagnostic = levels(data$Diagnostic),
BMI = mean(data$BMI, na.rm = TRUE))
adj_df <- predict(lmod, newdata = newdata, interval = "confidence")
adj_df=cbind(newdata,adj_df)
ggplot(adj_df,aes(y = fit, x = Diagnostic))+
geom_pointrange(aes(ymin = lwr, ymax = upr,col=Diagnostic),size=1.2) +
geom_line(aes(group=1),linetype=2,size=1.2)+
scale_y_continuous("Thickness")+
scale_x_discrete("Diagnostic")+
theme_bw()+
scale_color_manual(values=c("#021ce5","#350091","#d8021e"))

Lưu ý, giá trị thể hiện trên biểu đồ chính là hiệu ứng riêng phần của từng bệnh lý, sau khi hiệu chỉnh cho BMI.
Còn đây là hiệu ứng riêng phần của BMI
newdata <- expand.grid(Diagnostic = levels(data$Diagnostic),
BMI = seq(min(data$BMI),max(data$BMI),l=100))
pred_df <- predict(lmod, newdata = newdata, interval = "confidence")
pred_df <- cbind(newdata,pred_df)
obs_df <- cbind(data, obs = fitted(lmod) + resid(lmod))
ggplot(pred_df, aes(y = fit, x = BMI, group = Diagnostic))+
geom_point(data = obs_df,aes(y = obs, shape = Diagnostic,col=Diagnostic),size=1.5)+
geom_line(aes(col=Diagnostic),size=1)+
geom_ribbon(aes(ymin = lwr,ymax = upr,group=Diagnostic,fill=Diagnostic),alpha = 0.2)+
scale_y_continuous("Thickness")+
scale_x_continuous("BMI")+
theme_bw()+
scale_color_manual(values=c("#021ce5","#350091","#d8021e"))+
scale_fill_manual(values=c("#1677ff","#a616ff","#ff1654"))

Trong bài trước (Anova cho phép đo lặp lại), Nhi có giới thiệu về package afex rất hiệu quả cho phân tích phương sai (mọi thiết kế ANOVA). Ta sẽ thử áp dụng nó cho thiết kế ANCOVA như sau:
library(afex)
dat%>%mutate(id=rownames(.))%>%
aov_ez(id="id",
dv="Thickness",
data=.,
between="Diagnostic",
covariate="BMI",
factorize = F,
anova_table = list(es="pes",
p_adjust_method="bonferroni"))->cov1
Kết quả của afex “đẹp” hơn, vì ta có áp dụng một hiệu chỉnh Bonferroni cho p_value,ngoài F test , afex còn tính cho ta cả effect-size bộ phận cho mỗi yếu tố (partial eta squared: pes). Theo đó, Bệnh lý có kích thước hiệu ứng cao hơn nhiều so với BMI (pes=0.805 sv 0.162). Kết quả F test cho Diagnostic là F(2,34)=70,55; p< 1.5*10^-12
summary(cov1)
## Anova Table (Type 3 tests, bonferroni-adjusted)
##
## Response: Thickness
## num Df den Df MSE F pes Pr(>F)
## Diagnostic 2 34 0.011442 70.5467 0.80582 1.587e-12 ***
## BMI 1 34 0.011442 6.5722 0.16199 0.0299 *
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
Ta còn có thể trình bày kết quả trung bình và Ci của Thickness sau khi hiệu chỉnh cho BMI. So với kết quả thực tế (bên dưới), kết quả sau hiệu chỉnh có một số khác biệt nhỏ, thí dụ 95%CI mở rộng hơn, trung bình cao hơn
lsm=lsmeans(cov1,specs = "Diagnostic")
lsm
## Diagnostic lsmean SE df lower.CL upper.CL
## Control 0.6094171 0.03887345 32 0.5302345 0.6885997
## Emphysema 0.3114069 0.02753148 32 0.2553271 0.3674867
## Fibrosis 0.9062413 0.04952127 32 0.8053698 1.0071128
##
## Confidence level used: 0.95
dat%>%group_by(Diagnostic)%>%
summarise_at("Thickness",
funs(Mean=mean,
LL=quantile(.,probs=0.05),
UL=quantile(.,probs=0.95)
))%>%knitr::kable()
Control |
0.5909805 |
0.4638749 |
0.7465380 |
Emphysema |
0.3074973 |
0.1639136 |
0.4798438 |
Fibrosis |
0.9229586 |
0.7236907 |
1.0795619 |
Một lần nữa, ta lại có thể vẽ biểu đồ Marginal effect của Diagnostic
lsm%>%summary()%>%as_tibble()%>%
ggplot(aes(x=Diagnostic,
y=lsmean,
fill=lsmean,
group=1))+
geom_errorbar(aes(ymin=lsmean-SE,
ymax=lsmean+SE),
width=0.2,size=1) +
geom_line(size=1,col="grey",linetype=2)+
geom_point(size=5,shape=21,col="black",show.legend = F)+
scale_y_continuous("Thickness")+
scale_fill_gradient(low="blue",high="red")+
theme_bw()

package afex còn cho phép làm phân tích tương phản (contrast analysis) rất dễ dàng:
Thí dụ một giả thuyết tương phản với trọng số 0,-1/3,+1/3, hoặc tương phản theo hàm đa thức bậc 2
contrast(lsm,
list("Cont1"=c(0,-1,1)/3),
adjust="bonferroni")
## contrast estimate SE df t.ratio p.value
## Cont1 0.1982781 0.01888662 32 10.498 <.0001
contrast(lsm,method="poly")
## contrast estimate SE df t.ratio p.value
## linear 0.2968242 0.06295634 32 4.715 <.0001
## quadratic 0.8928446 0.08363869 32 10.675 <.0001
pcdf=contrast(lsm,method="poly")%>%coef()%>%as_tibble()
colnames(pcdf)=c("Step","linear","Quadratic")
pcdf%>%gather(linear:Quadratic,key="Poly",value="Coef")%>%
ggplot(aes(x=Step,y=Poly,fill=Coef))+
geom_tile(col="black")+
scale_fill_gradient(low="white",high="black")+
theme_bw()

Ta cũng có thể làm một post-hoc test với hiệu chỉnh Bonferroni :
contrast(lsm,"revpairwise",adjust="bonferroni")
## contrast estimate SE df t.ratio p.value
## Emphysema - Control -0.2980102 0.04763536 32 -6.256 <.0001
## Fibrosis - Control 0.2968242 0.06295634 32 4.715 0.0001
## Fibrosis - Emphysema 0.5948344 0.05665985 32 10.498 <.0001
##
## P value adjustment: bonferroni method for 3 tests
cdf=contrast(lsm,"revpairwise",adjust="bonferroni")%>%
coef()%>%
as_tibble()
colnames(cdf)=c("Group","E/C","F/C","F/E")
cdf%>%gather(`E/C`:`F/E`,key="Pairs",value="Coef")%>%
ggplot(aes(x=Group,y=Pairs,fill=as.factor(Coef)))+
geom_tile(col="black")+
scale_fill_manual(values=c("blue","white","red"))+
theme_bw()

Kết quả cho thấy có sự khác biệt ý nghĩa về giá trị Thickness giữa 2 bệnh lý Emphysema và Fibrosis so với nhóm chứng, cụ thể: Bệnh nhân khí phế thũng có bề dày màng phế nang giảm trung bình -0.298 µm so với người bình thường (p<0.0001) , trong khi bệnh xơ phổi làm tăng bề dày màng trung bình +0.297 µm (p=0.0001)
ANCOVA theo BAYES
Bây giờ, chúng ta sẽ thực hiện ANCOVA bằng phương pháp Bayes:
Phân tích Bayes cho thiết kế ANCOVA được bắt đầu với giả định: Giá trị quan sát Yik ở mỗi cá thể i trong phân nhóm Xk là kết quả của một biến ngẫu nhiên Y có phân phối chuẩn (Gaussian) được xác định bằng 2 tham số : Mu (trung bình) và Sigma (độ lệch chuẩn).
Giá trị tham số Mu được ước tính bằng một mô hình tuyến tính có nội dung:
\[\mu _{ik} = \beta _{c}C + \beta _{1}X_{1} + \beta _{2}X_{2} + \beta _{3}X_{3}\]
Giả thuyết tiền định (prior) cho beta và sigma gồm:
\[\beta \sim \mathit{N} (0,\sigma _{\beta })\]
\[\sigma \sim \mathit{Cauchy} (0,5)\]
Trước hết ta viết nội dung mô hình bằng STAN code
# stancode
model_code = "
// data block
data {
int<lower=1> n; // sample size
int<lower=1> nX; // factor level
vector [n] y; // response vector Y
matrix [n,nX] X; // design matrix of predictors
}
// para block
parameters {
vector[nX] beta; // beta coefs = vector
real<lower=0> sigma; // sigmaY = real number
}
transformed parameters { // MuY = vector
vector[n] mu;
mu = X*beta; // Estimate Mu
}
// Model block
model {
// Likelihood : y follows Gaussian dist.law, determined by sigma and mu
y~normal(mu,sigma);
// Priors for beta and sigma
beta ~ normal(0,10);
sigma~cauchy(0,5);
}
// Supp values block
generated quantities {
vector[n] log_lik;
for (i in 1:n) {
log_lik[i] = normal_lpdf(y[i] | mu[i], sigma);
}
}
"
STAN code được lưu dưới dạng chuỗi kí tự (string),
model.text <- “…..”
Một chương trình STAN được chia theo cấu trúc từng khối (block) và mỗi block có vai trò khác nhau:
- Block data: Tên gọi, loại và kích thước của từng phần trong dữ liệu đầu vào
n là một số nguyên dương chỉ cỡ mẫu
nX là 1 số nguyên dương, chỉ số predictors trong design matrix của mô hình (lưu ý: không có intercept, Diagnostic bị chia thành 3 dummy variables)
y là biến kết quả (Thickness), 1 vector số thực có độ dài = n
X là 1 design matrix có kích thước (n,nX) : lưu ý; tính kế thừa của STAN code, biến sau sử dụng thông tin của biến trước đã được khai báo
- Block parameter:
Như tên gọi của nó, đây là nơi khai báo về các tham số mà ta cần trong mô hình. Như đã trình bày, ta có 2 loại tham số: beta (trung bình mỗi phân nhóm, hay tham số hồi quy trong mô hình), là một vector số thực có kích thước nX = số cột trong design matrix; còn sigma là sd của residual, là 1 số thực.
- Block Transformed parameters:
Block này không bắt buộc phải có cho mọi mô hình vì nó tùy thuộc vào mục đích của người viết code và độ phức tạp của mô hình. Cần chú ý là mọi tham số tạo ra trong block này cũng sẽ được lấy mẫu cho chuỗi MCMC và xuất ra kết quả mô hình như các tham số trong block parameter.
Trong block này, ta khai báo tham số Mu là trung bình dự báo của Y, xác định từ beta và design matrix X.
- Block Model: Trong block này ta sẽ khai báo về thành phần likelihood và priors (giả thuyết tiền định về phân phối của từng tham số).
Trong mô hình này, likelihood là y ~ normal (mu, sigma);
Ta có 2 tham số là beta và Sigma. Nếu ta không khai báo prior, STAN sẽ mặc định dùng prior Uniform cho tham số đó. Ở đây ta dùng prior “non informative” là half-Cauchy cho sigma, và 1 giả thuyết tiền định rằng beta có phân phối chuẩn, dao động quanh trung bình = 0 và sd=10).
- Ngoài ra, ta còn có thể dùng thêm block thứ 5 để lấy mẫu cho các tham số phụ hay phái sinh từ các parameter có sẵn trong mô hình; tuy nhiên việc này không thực sự quan trọng, nhất là việc tính toán nhiều tham số bằng cách lấy mẫu sẽ làm quy trình tạo MCMC quá tải một cách không cần thiết.
Sau khi viết xong STAN code, ta sẽ tạo design matrix cho mô hình và data cho mô hình là 1 list
Xmat <- model.matrix(~BMI+Diagnostic-1, data)
data_list <- with(data, list(y = Thickness,
X = Xmat,
nX = ncol(Xmat),
n = nrow(data)))
data_list
## $y
## [1] 0.1427005 0.4760602 0.2029516 0.3968931 0.2606986 0.3658132 0.4868705
## [8] 0.1753361 0.4272775 0.3836717 0.2935065 0.2852184 0.1908612 0.2171024
## [15] 1.0247708 0.7409633 0.8390008 0.8316315 0.7121756 1.0190631 1.0488030
## [22] 1.1000678 0.9901514 0.5403678 0.5984456 0.6568714 0.5801316 0.6411785
## [29] 0.5188145 0.7352794 0.5209054 0.6217350 0.6701464 0.4871185 0.5732800
## [36] 0.4096397 0.7728080 0.5379854
##
## $X
## BMI DiagnosticControl DiagnosticEmphysema DiagnosticFibrosis
## 1 -4.85173804 0 1 0
## 2 7.51477071 0 1 0
## 3 -4.37463707 0 1 0
## 4 -0.88163933 0 1 0
## 5 0.01834039 0 1 0
## 6 3.12540572 0 1 0
## 7 3.69108440 0 1 0
## 8 -1.84793056 0 1 0
## 9 0.74016770 0 1 0
## 10 1.97934851 0 1 0
## 11 0.78719338 0 1 0
## 12 -1.26738442 0 1 0
## 13 -1.13574832 0 1 0
## 14 -5.54079466 0 1 0
## 15 10.91631112 0 0 1
## 16 5.61913228 0 0 1
## 17 -0.16032136 0 0 1
## 18 12.40933868 0 0 1
## 19 -0.88163933 0 0 1
## 20 6.47424311 0 0 1
## 21 3.69127262 0 0 1
## 22 1.56143131 0 0 1
## 23 1.80330965 0 0 1
## 24 -0.93045277 1 0 0
## 25 -2.61121594 1 0 0
## 26 -3.10082213 1 0 0
## 27 -6.39092390 1 0 0
## 28 -1.29345472 1 0 0
## 29 -0.93478559 1 0 0
## 30 -4.16292870 1 0 0
## 31 2.84135450 1 0 0
## 32 -5.41223831 1 0 0
## 33 -2.71420106 1 0 0
## 34 -3.66159933 1 0 0
## 35 -3.11603410 1 0 0
## 36 -6.31359916 1 0 0
## 37 1.10087528 1 0 0
## 38 -2.68949057 1 0 0
## attr(,"assign")
## [1] 1 2 2 2
## attr(,"contrasts")
## attr(,"contrasts")$Diagnostic
## [1] "contr.treatment"
##
##
## $nX
## [1] 4
##
## $n
## [1] 38
Ta đưa data vào mô hình và kích hoạt quy trình lấy mẫu MCMC với các tùy chỉnh Ta sẽ chạy 2 chuỗi song song: Trước hết sampler sẽ chạy 500 lượt khởi động, khi ổn định thì kết quả MCMC bắt đầu được ghi lại 2000 lượt cho mỗi chuỗi,rút gọn 1/2 kích thước mỗi chuỗi và chạy song song trên tối đa 4 cores của máy tính.
library(rstan)
rstan_options(auto_write = TRUE)
options(mc.cores = parallel::detectCores())
set.seed(123)
fit= stan(data = data_list,
model_code = model_code ,
chains = 2,
iter = 2500,
warmup = 500,
thin = 2)
## In file included from C:/Users/Admin/Documents/R/win-library/3.5/BH/include/boost/config.hpp:39:0,
## from C:/Users/Admin/Documents/R/win-library/3.5/BH/include/boost/math/tools/config.hpp:13,
## from C:/Users/Admin/Documents/R/win-library/3.5/StanHeaders/include/stan/math/rev/core/var.hpp:7,
## from C:/Users/Admin/Documents/R/win-library/3.5/StanHeaders/include/stan/math/rev/core/gevv_vvv_vari.hpp:5,
## from C:/Users/Admin/Documents/R/win-library/3.5/StanHeaders/include/stan/math/rev/core.hpp:12,
## from C:/Users/Admin/Documents/R/win-library/3.5/StanHeaders/include/stan/math/rev/mat.hpp:4,
## from C:/Users/Admin/Documents/R/win-library/3.5/StanHeaders/include/stan/math.hpp:4,
## from C:/Users/Admin/Documents/R/win-library/3.5/StanHeaders/include/src/stan/model/model_header.hpp:4,
## from file35ec56f4195.cpp:8:
## C:/Users/Admin/Documents/R/win-library/3.5/BH/include/boost/config/compiler/gcc.hpp:186:0: warning: "BOOST_NO_CXX11_RVALUE_REFERENCES" redefined
## # define BOOST_NO_CXX11_RVALUE_REFERENCES
## ^
## <command-line>:0:0: note: this is the location of the previous definition
## cc1plus.exe: warning: unrecognized command line option "-Wno-ignored-attributes"
Mô hình converge rất nhanh, kết quả xuất ra là những chuỗi MCMC cho 4 tham số beta (tương ứng trung bình của 3 phân nhóm và beta cho BMI), 1 tham số sigma và 38 giá trị dự báo cho Mu của từng cá thể
Ta tóm tắt kết quả của 4 tham số beta và tham số sigma như sau:
library(broom)
tidyMCMC(fit, conf.int = TRUE, conf.method = "HPDinterval", pars = c("beta", "sigma"))
## term estimate std.error conf.low conf.high
## 1 beta[1] 0.01324467 0.005430898 0.001801404 0.02333015
## 2 beta[2] 0.62519093 0.032571757 0.564763063 0.69212344
## 3 beta[3] 0.31030750 0.030162298 0.248693968 0.36638148
## 4 beta[4] 0.86053624 0.045440858 0.779930691 0.95755980
## 5 sigma 0.11104550 0.014152905 0.086291494 0.13969127
Ta khảo sát trực quan phân phối hậu nghiệm của beta cho 3 phân nhóm
postdf=fit%>%as.data.frame()%>%
as_tibble()%>%.[,c(1:5)]%>%
mutate(.,Iteration=as.numeric(rep(c(1:1000),2)),
Chain=as.factor(rep(c(1:2),each=1000)))
colnames(postdf)=c("BMI","Control","Emphysema","Fibrosis","Sigma","Iteration","Chain")
postdf$Pseudo=factor(rep(c(1:10),e=nrow(postdf)/10))
library(ggridges)
postdf%>%gather(Control,Emphysema,Fibrosis,
key="Predictors",value="Effect")%>%
ggplot()+
geom_density_ridges_gradient(aes(y=reorder(Predictors,Effect),
x=Effect,fill=-..x..),
linetype=1,col="black",
alpha=0.6,scale=1,
show.legend = F)+
geom_vline(xintercept=adj_df$fit[1],col="red3",linetype=2)+
theme_bw()+
scale_y_discrete("Factors")+
scale_x_continuous("Estimated Thickness")+
scale_fill_gradient(low="#f90046",
high="#f9cc00")+
coord_flip()

postdf%>%gather(Control,Emphysema,Fibrosis,
key="Predictors",value="Effect")%>%
ggplot()+
geom_density_ridges(aes(y=reorder(Predictors,Effect),
x=Effect,col=Pseudo),
linetype=1,fill="red",
alpha=0.03,scale=1,
show.legend = F)+
geom_vline(xintercept=adj_df$fit[1],col="red3",linetype=2)+
theme_bw()+
scale_y_discrete("Factors")+
scale_x_continuous("Estimated Thickness")+
coord_flip()+
scale_colour_brewer(palette = "Reds")

Hoặc phẩm chất của các chuỗi MCMC:
p1=postdf%>%gather(BMI:Sigma,key="Factor",value="Effect")%>%
ggplot(aes(x=Effect,fill=Chain,col=Chain))+
geom_density(alpha=0.3,show.legend = F)+
facet_wrap(~Factor,ncol=1,scale="free_y")+
theme_bw(8)
p2=postdf%>%gather(BMI:Sigma,key="Factor",value="Effect")%>%
ggplot(aes(y=Effect,x=Iteration,col=Chain))+
geom_path(alpha=0.5,show.legend = F)+
facet_wrap(~Factor,ncol=1,scale="free_y")+
theme_bw(8)
gridExtra::grid.arrange(p2,p1,ncol=2)

Từ mô hình Bayes, ta có thể trình bày Marginal effect của BMI điều kiện hóa theo phân nhóm:
library(coda)
mcmc = as.matrix(fit)
newdata=expand.grid(BMI = seq(min(data$BMI), max(data$BMI),len=100),
Group = levels(data$Diagnostic))
Xmat2 = model.matrix(~BMI + Group-1, newdata)
coefs = mcmc[, c("beta[1]", "beta[2]", "beta[3]", "beta[4]")]
pred = coefs %*% t(Xmat2)
newdata = newdata %>% cbind(tidyMCMC(pred, conf.int = TRUE, conf.method = "HPDinterval"))
newdata%>%
ggplot(aes(x=BMI,
y=estimate,
fill=Group,
col=Group))+
geom_ribbon(aes(ymin = conf.low,
ymax = conf.high), alpha = 0.2,col=NA)+
geom_line()+
geom_smooth(alpha=0.5)+
scale_y_continuous("Thickness")+
theme_bw()+
scale_color_manual(values=c("#021ce5","#350091","#d8021e"))+
scale_fill_manual(values=c("#1677ff","#a616ff","#ff1654"))

Mô hình này có khả năng giải thích đến 83.48 % phương sai của Thickness (Ci=0.785 đến 0.867)
mcmc <- as.matrix(fit)
Xmat = model.matrix(~BMI+Diagnostic-1,data)
wch = grep("beta", colnames(mcmc))
coefs = mcmc[, wch]
pred = coefs %*% t(Xmat)
resid = sweep(pred, 2, data$Thickness, "-")
var_f = apply(pred, 1, var)
var_e = apply(resid, 1, var)
R2 = var_f/(var_f + var_e)
tidyMCMC(as.mcmc(R2), conf.int = TRUE, conf.method = "HPDinterval")
## term estimate std.error conf.low conf.high
## 1 var1 0.8340875 0.02476514 0.7873155 0.8673647
Tiếp theo, Nhi sẽ làm 1 phân tích post-hoc theo Bayes. Nội dung của quy trình này là tạo ra các chuỗi MCMC cho khác biệt bắt cặp tuần tự giữa 3 phân nhóm
posthocdf=data.frame(
CvsE=rep(NA,2000),
CvsF=rep(NA,2000),
EvsF=rep(NA,2000)
)
for (i in 1:2000) {
posthocdf$CvsE[i]=postdf$Control[i]-postdf$Emphysema[i]
posthocdf$CvsF[i]=postdf$Fibrosis[i]-postdf$Control[i]
posthocdf$EvsF[i]=postdf$Fibrosis[i]-postdf$Emphysema[i]}
Sau đó ta có thể khảo sát những chuỗi MCMC này theo nhiều cách, hoặc dùng phản nghiệm (một loại Z-test), có thể xuất ra giá trị p
library(coda)
mcmcpvalue <- function(samp) {
if (length(dim(samp)) == 0) {
std <- backsolve(chol(var(samp)), cbind(0, t(samp)) - mean(samp),
transpose = TRUE)
sqdist <- colSums(std * std)
sum(sqdist[-1] > sqdist[1])/length(samp)
} else {
std <- backsolve(chol(var(samp)), cbind(0, t(samp)) - colMeans(samp),
transpose = TRUE)
sqdist <- colSums(std * std)
sum(sqdist[-1] > sqdist[1])/nrow(samp)
}
}
mcmcpvalue(posthocdf$CvsE)
## [1] 0
mcmcpvalue(posthocdf$CvsF)
## [1] 0
mcmcpvalue(posthocdf$EvsF)
## [1] 0
Kết quả cho thấy p_value cho cả 3 cặp so sánh đều rất thấp, cho thấy khác biệt có ý nghĩa thống kê
Một cách làm khác là suy diễn bằng ROPE và CompVal như Kruschke đã làm : Các bạn xem những bài trước để hiểu cơ chế của phép suy diễn này.
HDIF= function( sampleVec,credMass=0.975 ) {
sortedPts = sort( sampleVec )
ciIdxInc = ceiling( credMass * length( sortedPts ) )
nCIs = length( sortedPts ) - ciIdxInc
ciWidth = rep( 0 , nCIs )
for ( i in 1:nCIs ) {
ciWidth[ i ] = sortedPts[ i + ciIdxInc ] - sortedPts[ i ]
}
HDImin = sortedPts[ which.min( ciWidth ) ]
HDImax = sortedPts[ which.min( ciWidth ) + ciIdxInc ]
HDIlim = c( HDImin , HDImax )
return( HDIlim )
}
SUMK=function(paramSampleVec,compVal=NULL , ROPE=NULL , credMass=0.975) {
meanParam = mean( paramSampleVec )
medianParam = median( paramSampleVec )
dres = density( paramSampleVec )
modeParam = dres$x[which.max(dres$y)]
hdiLim = HDIF( paramSampleVec , credMass=credMass )
if ( !is.null(compVal) ) {
pcgtCompVal = ( 100 * sum( paramSampleVec > compVal )
/ length( paramSampleVec ) )
} else {
compVal=NA
pcgtCompVal=NA
}
if ( !is.null(ROPE) ) {
pcltRope = ( 100 * sum( paramSampleVec < ROPE[1] )
/ length( paramSampleVec ) )
pcgtRope = ( 100 * sum( paramSampleVec > ROPE[2] )
/ length( paramSampleVec ) )
pcinRope = 100-(pcltRope+pcgtRope)
} else {
ROPE = c(NA,NA)
pcltRope=NA
pcgtRope=NA
pcinRope=NA
}
return( c( Mean=meanParam , Median=medianParam , Mode=modeParam ,
HDIlevel=credMass , LL=hdiLim[1] , UL=hdiLim[2] ,
CompVal=compVal , PcntGtCompVal=pcgtCompVal ,
ROPElow=ROPE[1] , ROPEhigh=ROPE[2] ,
PcntLtROPE=pcltRope , PcntInROPE=pcinRope , PcntGtROPE=pcgtRope ) )
}
summaryKruschke=function(MCMC,compVal=NULL, rope=NULL,credMass=NULL){
summaryInfo = NULL
summaryInfo = cbind(summaryInfo, "Estimated"= SUMK(MCMC,
compVal=compVal,
ROPE=rope,credMass=credMass))
return(summaryInfo)
}
longpost=posthocdf%>%gather(CvsE,CvsF,EvsF,key="Pairs",value="Difference")
longpost%>%
split(.$Pairs)%>%
map(~summaryKruschke(MCMC = .$Difference,
compVal=0.0,
rope=c(0,0.1),
credMass=0.975)
)%>%as.data.frame()%>%as_tibble()->kruschke
colnames(kruschke)=unique(longpost$Pairs)
kruschke%>%knitr::kable()
Mean |
0.3148834 |
0.2353453 |
0.5502287 |
Median |
0.3147589 |
0.2375636 |
0.5504648 |
Mode |
0.3148886 |
0.2536895 |
0.5539946 |
HDIlevel |
0.9750000 |
0.9750000 |
0.9750000 |
LL |
0.2183531 |
0.1037730 |
0.4313252 |
UL |
0.4166552 |
0.3791614 |
0.6757934 |
CompVal |
0.0000000 |
0.0000000 |
0.0000000 |
PcntGtCompVal |
100.0000000 |
100.0000000 |
100.0000000 |
ROPElow |
0.0000000 |
0.0000000 |
0.0000000 |
ROPEhigh |
0.1000000 |
0.1000000 |
0.1000000 |
PcntLtROPE |
0.0000000 |
0.0000000 |
0.0000000 |
PcntInROPE |
0.0000000 |
1.6000000 |
0.0000000 |
PcntGtROPE |
100.0000000 |
98.4000000 |
100.0000000 |
Tóm tắt: CompVal là 1 ngưỡng vô nghĩa, thí dụ khác biệt = 0; ROPE là một khoảng vô nghĩa, gồm 2 đầu, thí dụ 0 và +0.1. Kết quả cho thấy 100% phân phối hậu nghiệm của khác biệt lớn hơn 0 cho 2 cặp Control vs Emphysema và Control vs Fibrosis, 99.95% phân bố hậu nghiệm lớn hơn 0 cho cặp Emphysema vs Fibrosis.
Tương tự: 100% phân bố hậu nghiệm nằm ngoài ROPE cho 2 cặp C vs E, Cvs F; và 98.45% nằm ngoài ROPE cho cặp E vsF.
Đơn giản hơn là mô tả trực quan phân phối hậu nghiệm của 3 cặp so sánh, người đọc có thể tự cảm nhận mức độ khả tín so với ngưỡng 0
longpost%>%
ggplot()+
geom_density_ridges_gradient(aes(y=reorder(Pairs,Difference),
x=Difference,fill=-..x..),
linetype=1,col="black",
alpha=0.6,scale=1,
show.legend = F)+
geom_vline(xintercept=0,col="red3",linetype=2)+
theme_bw()+scale_y_discrete("Pairs")+scale_x_continuous("Difference")+
scale_fill_gradient(low="#f90046",high="#f9cc00")+coord_flip()

Cuối cùng là suy diễn bằng Bayes Factor hay tỉ trọng chứng cứ phủ quyết cho 1 ngưỡng vô nghĩa nhất định từ 0 đến 0.7
threshold=rep(NA,33)
BayesFactor=rep(NA,33)
Pair=rep(NA,33)
thres=c(0,0.05,0.1,0.15,0.2,0.25,0.3,0.4,0.5,0.6,0.7)
pairlev=unique(longpost$Pairs)
n=0
for(i in (1:3)){
for (j in (1:11)){
tempdf=subset(longpost,Pairs==pairlev[i])
Pair[n+j]=pairlev[i]
thr=thres[j]
threshold[n+j]=thr
hyp=paste("Difference>",thr,sep="")
bf=brms::hypothesis(tempdf,hyp,alpha=0.05)
BayesFactor[n+j]=bf$hypothesis$Evid.Ratio
}
n=n+11
}
tempbfdf=cbind(Pair,threshold,BayesFactor)%>%as_tibble()
tempbfdf$threshold=as.numeric(tempbfdf$threshold)
tempbfdf$BayesFactor=as.numeric(tempbfdf$BayesFactor)
tempbfdf%>%
ggplot(aes(x=threshold,y=BayesFactor,fill=BayesFactor))+
geom_path()+
geom_point(show.legend = F,size=3,shape=21,col="black")+
theme_bw(10)+
geom_text(aes(label=round(tempbfdf$BayesFactor,2)),col="black",show.legend = F,
angle = 50,nudge_y=BayesFactor+300,nudge_x=0,size=3)+
geom_hline(yintercept =500,linetype=2,col="red")+
facet_wrap(~Pair,ncol=1,scales = "free")+
scale_fill_gradient(low="#f90046",high="#f9cc00")

tempbfdf%>%knitr::kable()
CvsE |
0.00 |
Inf |
CvsE |
0.05 |
Inf |
CvsE |
0.10 |
Inf |
CvsE |
0.15 |
Inf |
CvsE |
0.20 |
221.2222222 |
CvsE |
0.25 |
13.4927536 |
CvsE |
0.30 |
1.7322404 |
CvsE |
0.40 |
0.0288066 |
CvsE |
0.50 |
0.0000000 |
CvsE |
0.60 |
0.0000000 |
CvsE |
0.70 |
0.0000000 |
CvsF |
0.00 |
Inf |
CvsF |
0.05 |
499.0000000 |
CvsF |
0.10 |
61.5000000 |
CvsF |
0.15 |
10.6279070 |
CvsF |
0.20 |
2.6166365 |
CvsF |
0.25 |
0.7241379 |
CvsF |
0.30 |
0.1621150 |
CvsF |
0.40 |
0.0040161 |
CvsF |
0.50 |
0.0000000 |
CvsF |
0.60 |
0.0000000 |
CvsF |
0.70 |
0.0000000 |
EvsF |
0.00 |
Inf |
EvsF |
0.05 |
Inf |
EvsF |
0.10 |
Inf |
EvsF |
0.15 |
Inf |
EvsF |
0.20 |
Inf |
EvsF |
0.25 |
Inf |
EvsF |
0.30 |
Inf |
EvsF |
0.40 |
221.2222222 |
EvsF |
0.50 |
4.7971014 |
EvsF |
0.60 |
0.2202563 |
EvsF |
0.70 |
0.0035123 |
Diễn đạt văn bản khoa học
Phần này nhằm trình bày một văn bản khoa học mẫu cho phân tích ANCOVA theo trường phái Bayes.
Phương pháp: Một mô hình ANCOVA được thực hiện nhằm khảo sát thay đổi của bề dày màng PNMM ở các bệnh nhân khí phế thũng và xơ phổi so với nhóm chứng, sau khi hiệu chỉnh cho hiệu ứng của BMI. Các tham số trong mô hình được xác định bằng phương pháp Bayes với giả định kết quả có phân bố chuẩn,và prior cho Mu và sigma lần lượt là Normal(0,10) và Cauchy (0,5). Sự khác biệt giữa các phân nhóm được kiểm định bằng phương pháp suy diễn Bayes theo John Kruschke và Bayes Factor.
Kết quả:
Thăm dò dữ liệu cho thấy bề dày màng phế nang có quan hệ tuyến tính với BMI nhưng không có hiệu ứng tương tác giữa BMI và phân nhóm bệnh lý. Sau khi hiệu chỉnh cho BMI, kết quả mô hình ANCOVA Bayes cho thấy có sự thay đổi ý nghĩa về bề dày màng phế nang ở bệnh nhân có bệnh hô hấp so với nhóm chứng. Cụ thể, bệnh Khí phế thũng làm màng phế nang mỏng đi trung bình 0.32 µm (HDI:-0.20 đến 0.41 µm; BayesFactor>100 cho ngưỡng vô hiệu = -0.20 µm), trong khi bệnh xơ phổi làm màng phế nang dày thêm trung bình 0.24 µm (HDI:0.10 đến 0.38 µm; Bayesfactor =63 và 499 tương ứng với ngưỡng vô hiệu = +0.1 và +0.05 µm).
Kết luận
Do hầu hết những biến số trong nghiên cứu y học đều có mối tương quan phức tạp, thiết kế ANCOVA rất quan trọng. Trong bài thực hành này, các bạn đã làm quen với nó (nếu các bạn chưa biết) và thực hiện được một phân tích ANCOVA bằng cả 2 phương pháp Frequentist và Bayes. Bản chất của ANCOVA cũng như ANOVA, là một mô hình hồi quy tuyến tính nhưng có xét thêm hiệu ứng của hiệp biến số C mà trung bình đã được chuyển về zero. Mô hình này cho phép hiệu chỉnh tác động riêng của C đối với kết quả để đánh giá chính xác hơn về hiệu ứng chính của phân nhóm X. Sự hiệu chỉnh này có thể mang lại nhiều bất ngờ, thậm chí thay đổi kết quả suy diễn thống kê cho X trong một số trường hợp.
Chúc các bạn thực hành vui !
LS0tDQp0aXRsZTogIlBow6JuIHTDrWNoIGhp4buHcCBwaMawxqFuZyBzYWkgdGhlbyBCQVlFUyINCmF1dGhvcjogIkzDqiBOZ+G7jWMgS2jhuqMgTmhpIg0KZGF0ZTogIjMwIFRow6FuZyA1IDIwMTgiDQpvdXRwdXQ6DQogIGh0bWxfZG9jdW1lbnQ6IA0KICAgIGNvZGVfZG93bmxvYWQ6IHRydWUNCiAgICBjb2RlX2ZvbGRpbmc6IGhpZGUNCiAgICBudW1iZXJfc2VjdGlvbnM6IHllcw0KICAgIHRoZW1lOiAiZGVmYXVsdCINCiAgICB0b2M6IFRSVUUNCiAgICB0b2NfZmxvYXQ6IFRSVUUNCi0tLQ0KDQpgYGB7ciBzZXR1cCxpbmNsdWRlPUZBTFNFfQ0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGVjaG8gPSBUUlVFKQ0KYGBgDQoNCiFbXShCYXllc0FOQ09WQS5wbmcpDQoNCiMgR2nhu5tpIHRoaeG7h3UNCg0KVGjDom4gY2jDoG8gY8OhYyBi4bqhbiwgc2F1IG5oaeG7gXUgdGjDoW5nIGdpw6FuIMSRb+G6oW4sIE5oaSB0aeG6v3AgdOG7pWMgdMOhaSBraOG7n2kgY8O0bmcgUHJvamVjdCBCYXllcyBmb3IgVmlldG5hbS4gROG7sSDDoW4gbsOgeSDEkcaw4bujYyBwaMOhdCDEkeG7mW5nIHbDoG8gbsSDbSAyMDE3IHbhu5tpIG3hu6VjIHRpw6p1IHBo4buVIGPhuq1wIHbhu4EgcGjGsMahbmcgcGjDoXAgdGjhu5FuZyBrw6ogdGhlbyB0csaw4budbmcgcGjDoWkgQmF5ZXMgbmjhurFtIHRoYXkgdGjhur8gaG/DoG4gdG/DoG4gbmjhu69uZyBjw7RuZyBj4bulIHRydXnhu4FuIHRo4buRbmcuIMSQ4buRaSB0xrDhu6NuZyBj4bunYSBjaMO6bmcgdMO0aSBsw6AgY8OhYyBi4bqhbiBiw6FjIHPEqSB2w6Agc2luaCB2acOqbiB5IGtob2EuDQoNClF1YSBuaOG7r25nIGLDoGkgdHLGsOG7m2MsIE5oaSB2w6AgY8OhYyBi4bqhbiB0cm9uZyBuaMOzbSDEkcOjIGdp4bubaSB0aGnhu4d1IHbhu5tpIGPDoWMgYuG6oW4gY8OhY2ggdGhheSB0aOG6vyBuaOG7r25nIHF1eSB0csOsbmggdGjhu5FuZyBrw6ogcGjhu5UgYmnhur9uIG5oxrAgQuG6o25nIGNow6lvLCB0LXRlc3QsIHTGsMahbmcgcXVhbiBQZWFyc29uLCB2w6AgxJHhurdjIGJp4buHdCBsw6AgdGhp4bq/dCBr4bq/IEFOT1ZBICgyIGLDoGkpLiBIw7RtIG5heSwgTmhpIHPhur0gaMaw4bubbmcgZOG6q24gY8OhYyBi4bqhbiBt4bufIHLhu5luZyB0aGnhur90IGvhur8gQU5PVkEgdGjDoG5oIEFOQ09WQSAocGjDom4gdMOtY2ggaGnhu4dwIHBoxrDGoW5nIHNhaSkgdGhlbyB0csaw4budbmcgcGjDoWkgQmF5ZXMsIHRoYXkgdGjhur8gY2hvIEFOQ09WQSBj4buVIMSRaeG7g24uDQoNCkRvIMSRw6MgZ2nDoW4gxJFv4bqhbiBt4buZdCB0aOG7nWkgZ2lhbiwgTmhpIHhpbiBuaOG6r2MgbOG6oWkgbeG7mXQgc+G7kSB24bqlbiDEkeG7gSBjxqEgYuG6o24gY+G7p2EgcGjGsMahbmcgcGjDoXAgc3V5IGRp4buFbiB0aOG7kW5nIGvDqiB0aGVvIHRyxrDhu51uZyBwaMOhaSBCYXllcyBuaMawIHNhdToNCg0KMSkgTmd1ecOqbiB04bqvYyB04buVbmcgcXXDoXQgY2hvIG3hu41pIHN1eSBkaeG7hW4gQmF5ZXMgY8OzIHRo4buDIHTDs20gdOG6r3QgYuG6sW5nIGPDtG5nIHRo4bupYyA6DQoNCiQkcChcdGhldGF8b3V0Y29tZSwgZGF0YSlccHJvcHRvIHAob3V0Y29tZXxcdGhldGEpIHAoXHRoZXRhICxkYXRhKSQkDQoNCjIpIE3hu6VjIHRpw6p1IGPhu6dhIGNow7puZyB0YSBsw6AgbcO0IHThuqMgbeG7mXQgcGjDom4gcGjhu5FpIGjhuq11IMSR4buLbmggY+G7p2EgbeG7mXQgdGhhbSBz4buRIFRoZXRhICh24bq/IGLDqm4gdHLDoWksIGNow61uaCBsw6AgeMOhYyBzdeG6pXQgxJFp4buBdSBraeG7h24gZ2nDoSB0cuG7iyBj4bunYSB0aGV0YSBraGkgY8OzIHRow7RuZyB0aW4gduG7gSBr4bq/dCBxdeG6oyBvdXRjb21lIHbDoCBk4buvIGxp4buHdSBkYXRhKS4gxJDhuqFpIGzGsOG7o25nIG7DoHkgdOG7iSBs4buHIHbhu5tpIHTDrWNoIGPhu6dhIGjDoG0gbGlrZWxpaG9vZCAoeMOhYyBzdeG6pXQgY8OzIMSRaeG7gXUga2nhu4duIGNobyBwaMOpcCDGsOG7m2MgdMOtbmgga+G6v3QgcXXhuqMga2hpIGPDsyB0aGV0YSB2w6AgZOG7ryBsaeG7h3UpIHbDoCBt4buZdCBwaMOibiBwaOG7kWkgdGnhu4FuIMSR4buLbmggKHByaW9yPSBt4buZdCBnaeG6oyB0aHV54bq/dCB24buBIHBow6JuIHBo4buRaSBj4bunYSB0aGV0YSB0csaw4bubYyBraGkgdGEgbmjDrG4gdGjhuqV5IGThu68gbGnhu4d1IHbDoCBr4bq/dCBxdeG6oykuDQoNCjMpIFRoYW0gc+G7kSB0aGV0YSBsw6AgbeG7pWMgdGnDqnUgdHJvbmcgbeG7jWkgYsOgaSB0b8OhbiwgY2jDum5nIHRhIGNo4buJIGPhuqduIHjDoWMgxJHhu4tuaCDEkcaw4bujYyBuaOG7r25nIGLhu5kgcGjhuq1uIGPDsm4gbOG6oWkgKGxpa2VsaWhvb2QsIHByaW9ycykgdsOgIHThuqFvIHJhIG3hu5l0IG3DtCBow6xuaCBwaMO5IGjhu6NwIGPDsyBjaOG7qWEgdGhldGEuIA0KDQpLaGkgxJHDoyBwaMOhYyB0aOG6o28gxJHGsOG7o2MgbcO0IGjDrG5oIHRyw6puIGdp4bqleSB0aMOsIHZp4buHYyB2aeG6v3QgY29kZSBraMO0bmcga2jDsyBraMSDbiBs4bqvbS4gVGjDrSBk4bulIGNo4buJIG7huq9tIHbhu69uZyBjb2RlIGNobyBtw7QgaMOsbmggR0xNLCB0YSBjw7MgdGjhu4MgYmFvIHF1w6F0IGjhuqd1IGjhur90IG5o4buvbmcgY8O0bmcgY+G7pSB0aOG7kW5nIGvDqiBj4buVIMSRaeG7g24sIGJhbyBn4buTbSB0LXRlc3QsIGxvZ2lzdGljLCBBTk9WQSwgQU5DT1ZBLCBNQU5PVkEsIHbDom4gdsOibi4NCg0KRG8gxJHDsyB0YSBjw7MgdGjhu4MgdGhheSB0aOG6vyB04bqldCBj4bqjIG5o4buvbmcgY8O0bmcgY+G7pSB0aOG7kW5nIGvDqiB0cnV54buBbiB0aOG7kW5nIGTDuW5nIG51bGwgaHlwb3RoZXNpcyB0ZXN0aW5nIHbDoCBwX3ZhbHVlIGLhurFuZyBwaMOibiB0w61jaCBCYXllcy4NCg0KIyBN4bulYyB0acOqdQ0KDQpCw6BpIGjDtG0gbmF5LCBuaMawIHRoxrDhu51uZyBs4buHIHPhur0gxJFpIHRoZW8gMyBixrDhu5tjIG5oxrAgc2F1Og0KDQoxKSBUcsaw4bubYyBo4bq/dCwgTmhpIMO0biBs4bqhaSBsw70gdGh1eeG6v3QgduG7gSB0aGnhur90IGvhur8gQU5DT1ZBLCDEkcawYSByYSAxIGLDoGkgdG/DoW4gdGnDqnUgYmnhu4N1IHbDoCB0w6FpIGhp4buHbiBxdXkgdHLDrG5oIEFOQ09WQSBj4buVIMSRaeG7g24uDQoNCjIpIFNhdSDEkcOzIE5oaSBz4bq9IGNodXnhu4NuIGLDoGkgdG/DoW4gbsOgeSB0aMOgbmggbcO0IGjDrG5oIEJheWVzLCB2w6AgaMaw4bubbmcgZOG6q24gY8OhYyBi4bqhbiB2aeG6v3QgU1RBTiBjb2RlIGNobyBtw7QgaMOsbmguDQoNCjMpIEN14buRaSBjw7luZywgTmhpIHPhur0ga2hhaSB0aMOhYyBwaMOibiBwaOG7kWkgaOG6rXUgxJHhu4tuaCBjaG8gY8OhYyB0aGFtIHPhu5EgY+G6p24gcXVhbiB0w6JtLCB2w6Agc3V5IGRp4buFbiBCYXllcy4NCg0KIyBUaMOtIGThu6UgbWluaCBo4buNYSANCg0KVHJvbmcgYsOgaSBuw6B5LCBOaGkgc+G7rSBk4bulbmcgbeG7mXQgYuG7mSBk4buvIGxp4buHdSBjw7MgdGjhu7FjIHThu6sgbeG7mXQgbmdoacOqbiBj4bupdSBwaWxvdCBuaOG6sW0ga2nhu4NtIMSR4buLbmggbeG7mXQgY8O0bmcgbmdo4buHIHjDqXQgbmdoaeG7h20gY2jhu6ljIG7Eg25nIGjDtCBo4bqlcCBoaeG7h24gxJHhuqFpIGvhur90IGjhu6NwIHZpIGtow60gZHVuZyBOYUNsIHbDoCDEkW8gaOG7hyBz4buRIGtodeG6v2NoIHTDoW4gY+G7p2EgMiBsb+G6oWkga2jDrSBDTy9OTyBxdWEgbcOgbmcgcGjhur8gbmFuZy9tYW8gbeG6oWNoLCBjaG8gcGjDqXAgxrDhu5tjIHTDrW5oIGLhu4EgZMOgeSBj4bunYSBs4bubcCBtw6BuZyBuw6B5LiBOaMawIHRhIGJp4bq/dCwgY2jhu6ljIG7Eg25nIHRyYW8gxJHhu5VpIGtow60g4bufIG5nxrDhu51pIHBo4bulIHRodeG7mWMgdsOgbyDEkeG6t2MgdMOtbmggduG6rXQgbMO9IGPhu6dhIG3DoG5nIHBo4bq/IG5hbmcsIG1hbyBt4bqhY2ggbmjGsCB04buJIGzhu4cgdGh14bqtbiB24bubaSBkaeG7h24gdMOtY2gga2jhuqMgZOG7pW5nLCB2w6AgdOG7iSBs4buHIG5naOG7i2NoIHbhu5tpIMSR4buZIGTDoHkuIE3hu5l0IHPhu5EgYuG7h25oIGzDvSBow7QgaOG6pXAgbMOgbSBiaeG6v24gxJHhu5VpIG5o4buvbmcgY2jhu4kgc+G7kSBuw6B5LCB0aMOtIGThu6Uga2jDrSBwaOG6vyB0aMWpbmcgIChlbXBoeXNlbWEpIHBow6EgaOG7p3kgY+G6pXUgdHLDumMgbcO0IHBo4bq/IG5hbmcgY8OybiBi4buHbmggeMahIHBo4buVaSAoZmlicm9zaXMpIGzDoG0gdMSDbmcgxJHhu5kgZMOgeSBj4bunYSBtw6BuZy4gDQoNClRhIHPhur0gZMO5bmcgdGhp4bq/dCBr4bq/IEFOQ09WQSDEkeG7gyBzbyBzw6FuaCBnacOhIHRy4buLIGLhu4EgZMOgeSBtw6BuZyBwaOG6vyBuYW5nIG1hbyBt4bqhY2ggKFRoaWNrbmVzcykgZ2nhu69hIDMgbmjDs206IG5nxrDhu51pIGLDrG5oIHRoxrDhu51uZyAoQ29udHJvbCksIEtow60gcGjhur8gdGjFqW5nIChFbXBoeXNlbWEpIHbDoCBYxqEgcGjhu5VpIChGaWJyb3NpcykuIA0KDQpOZ3V5w6puIG5ow6JuIGNow7puZyB0YSBwaOG6o2kgZMO5bmcgdGhp4bq/dCBr4bq/IEFOQ09WQSBjaOG7qSBraMO0bmcgcGjhuqNpIEFOT1ZBIMSRxqFuIGJp4bq/biwgdsOsIHF1YSB0aMSDbSBkw7IgY2hvIHRo4bqleSBiaeG6v24gVGhpY2tuZXNzIGPDsyB0xrDGoW5nIHF1YW4gduG7m2kgQk1JLCBuw6puIEJNSSDEkcaw4bujYyB4w6l0IG5oxrAgbeG7mXQgeeG6v3UgdOG7kSBnw6J5IG5oaeG7hXUgbcOgIHRhIGPhuqduIGto4bqvYyBwaOG7pWMuIFBo4bqnbiB0aeG6v3AgdGhlbyBOaGkgc+G6vSBnaeG6o2kgdGjDrWNoIHLDtSBoxqFuIHbhu4EgxJFp4buBdSBuw6B5Lg0KDQojIELGsOG7m2MgMTogVGjEg20gZMOyIGThu68gbGnhu4d1DQoNCg0KYGBge3IsbWVzc2FnZSA9IEZBTFNFLHdhcm5pbmc9RkFMU0V9DQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCg0KZGF0PXJlYWQuY3N2KCJodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20va2lub2tvYmVydWppL1ItVHV0b3JpYWxzL21hc3Rlci9NZW1idGhpY2tuZXNzLmNzdiIsc2VwPSI7IikNCg0KZGF0JERpYWdub3N0aWMlPD4lYXMuZmFjdG9yKCklPiUNCiAgcmVjb2RlX2ZhY3RvciguLGBOYD0gIkNvbnRyb2wiLGBFYD0iRW1waHlzZW1hIixgRmA9IkZpYnJvc2lzIikNCg0KZGF0YT1kYXQNCg0KaGVhZChkYXQpJT4la25pdHI6OmthYmxlKCkNCg0KdGFibGUoZGF0JERpYWdub3N0aWMpDQpgYGANCg0KTmjGsCBjw6FjIGLhuqFuIHRo4bqleSwgZOG7ryBsaeG7h3UgZ+G7k20gMSBiaeG6v24ga+G6v3QgcXXhuqMgVGhpY2tuZXNzIGzDoCBz4buRIGxpw6puIHThu6VjLCAxIGJp4bq/biBwaMOibiBuaMOzbSBEaWFnbm9zdGljIGzDoCBiaeG6v24gcuG7nWkgcuG6oWMgduG7m2kgMyBi4bqtYyBnacOhIHRy4buLOyB2w6AgQk1JIGzDoCBiaeG6v24gc+G7kSBsacOqbiB04bulYy4NCg0KVHLGsOG7m2MgaOG6v3QsIGPhu6EgbeG6q3UgcuG6pXQgaOG6oW4gY2jhur8gY2hvIHThu6tuZyBwaMOibiBuaMOzbSwgxJHhurdjIGJp4buHdCBsw6AgbmjDs20gRmlicm9zaXMgY2jhu4kgY8OzIDkgdHLGsOG7nW5nIGjhu6NwLiBOZ2F5IGPhuqMgY2hvIG3hu5l0IHBow6JuIHTDrWNoIEFOT1ZBIMSRxqFuIGJp4bq/biBj4buVIMSRaeG7g24sIGPhu6EgbeG6q3UgdGjhuqVwIGzDoCBt4buZdCBuaMaw4bujYyDEkWnhu4NtIHLDtSByw6BuZywgxJHDonkgbMOgIG3hu5l0IHRyb25nIG5o4buvbmcgbmd1ecOqbiBuaMOibiBtw6AgTmhpIG114buRbiBkw7luZyBCYXllcy4gTMawdSDDvSBy4bqxbmcgbuG6v3UgY+G7oSBt4bqrdSBxdcOhIHRo4bqlcCwgc3V5IGRp4buFbiBCYXllcyBjxaluZyBjw7Mgbmhp4buBdSBuZ3V5IGPGoSBzYWkgbOG6p20ga2jDtG5nIGvDqW0gZ8OsIHBow6FpIGZyZXF1ZW50aXN0LCB2w6wgbMO6YyBuw6B5IHBow6JuIHBo4buRaSBo4bqtdSDEkeG7i25oIHPhur0gcGjhu6UgdGh14buZYyBy4bqldCBs4bubbiB2w6BvIHByaW9yLCBu4bq/dSBwcmlvciBzYWkgdGjDrCB0YSBz4bq9IGThu4UgZMOgbmcgc3V5IGRp4buFbiBzYWkuIFR1eSBuaGnDqm4sIGdp4buvYSBt4buZdCBr4bq/dCBxdeG6oyBjw7Mgc+G7qWMgbeG6oW5oIHRo4buRbmcga8OqIGNo4bqvYyBjaOG6r24gdGjhuqVwIChmcmVxdWVudGlzdCkgdsOgIG3hu5l0IGPGoSBo4buZaSB0aMOgbmggY8O0bmcgKEJheWVzKSwgTmhpIGNo4buNbiDEkWnhu4F1IHRo4bupIGhhaS4NCg0KYGBge3IsbWVzc2FnZSA9IEZBTFNFLHdhcm5pbmc9RkFMU0V9DQpkYXQlPiVncm91cF9ieShEaWFnbm9zdGljKSU+JQ0KICBzdW1tYXJpc2VfYXQoIlRoaWNrbmVzcyIsDQogICAgICAgICAgICAgICBmdW5zKE1lYW49bWVhbiwNCiAgICAgICAgICAgICAgIE1lZGlhbj1tZWRpYW4sDQogICAgICAgICAgICAgICBTa2V3bmVzcz1lMTA3MTo6c2tld25lc3MoLiksDQogICAgICAgICAgICAgICBrdXJ0b3Npcz1lMTA3MTo6a3VydG9zaXMoLiksDQogICAgICAgICAgICAgICBMTD1xdWFudGlsZSguLHByb2JzPTAuMDUpLA0KICAgICAgICAgICAgICAgVUw9cXVhbnRpbGUoLixwcm9icz0wLjk1KQ0KICAgICAgICAgICAgICAgKSklPiVrbml0cjo6a2FibGUoKQ0KYGBgDQoNCmBgYHtyfQ0KbGlicmFyeShnZ3JpZGdlcykNCg0Kc3VtX2RmPWRhdCU+JWdyb3VwX2J5KERpYWdub3N0aWMpJT4lDQogIHN1bW1hcmlzZV9hdCgiVGhpY2tuZXNzIixtZWRpYW4pDQoNCmRhdCU+JWdncGxvdChhZXMoeD1UaGlja25lc3MseT1EaWFnbm9zdGljLGZpbGw9RGlhZ25vc3RpYyxjb2w9RGlhZ25vc3RpYykpKw0KICBnZW9tX2RlbnNpdHlfcmlkZ2VzKGFscGhhPTAuNixzaXplPTEsc2NhbGU9MSkrDQogIGdlb21fcG9pbnQoYWxwaGE9MC4zKSsNCiAgZ2VvbV9wb2ludChkYXRhPXN1bV9kZixhZXMoeD1UaGlja25lc3MpLA0KICAgICAgICAgICAgIHNoYXBlPTIzLHNpemU9NSxmaWxsPSJ3aGl0ZSIsc3Ryb2tlPTEuMikrDQogIGdlb21fcnVnKGFscGhhPTAuNSkrDQogIGNvb3JkX2ZsaXAoKSsNCiAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcz1jKCIjMDIxY2U1IiwiIzM1MDA5MSIsIiNkODAyMWUiKSkrDQogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcz1jKCIjMTY3N2ZmIiwiI2E2MTZmZiIsIiNmZjE2NTQiKSkrDQogIHRoZW1lX2J3KCkNCmBgYA0KDQpUaOG7kW5nIGvDqiBtw7QgdOG6oyBn4bujaSDDvSBy4bqxbmcgZ2nDoSB0cuG7iyBUaGlja25lc3MgdMSDbmcgY2FvIOG7nyBuaMOzbSBGaWJyb3NpcywgdsOgIGdp4bqjbSDhu58gbmjDs20gRW1waHlzZW1hIHNvIHbhu5tpIG5ow7NtIENvbnRyb2w7IFR1eSBraMO0bmcgdGjhu4Mga2jhurNuZyDEkeG7i25oIGNo4bqvYyBjaOG6r24gcuG6sW5nIFRoaWNrbmVzIHBow6JuIGLhu5EgYsOsbmggdGjGsOG7nW5nIOG7nyBjw6FjIHBow6JuIG5ow7NtLCBuaOG6pXQgbMOgIG5ow7NtIEZpYnJvc2lzIG5oxrBuZyB0cnVuZyB24buLIGfhuqduIHRydW5nIGLDrG5oIHbDoCBza2V3bmVzcyBy4bqldCBn4bqnbiAwIGfhu6NpIMO9IHbhu4EgdMOtbmggxJHhu5FpIHjhu6luZyBj4bunYSBwaMOibiBi4buRLg0KDQpN4buZdCBwaMOibiB0w61jaCBBTk9WQSDEkcahbiBiaeG6v24ga2jhurNuZyDEkeG7i25oIGPDsyBz4buxIGtow6FjIGJp4buHdCDDvSBuZ2jEqWEgduG7gSBUaGlja25lc3MgZ2nhu69hIDMgcGjDom4gbmjDs20gRGlhZ25vc3RpYw0KDQpgYGB7cn0NCmNhcjo6QW5vdmEobG0oVGhpY2tuZXNzfkRpYWdub3N0aWMsZGF0KSx0eXBlPSJJSUkiKQ0KYGBgDQoNCmBgYHtyfQ0KY29yLnRlc3QoZGF0JEJNSSxkYXQkVGhpY2tuZXNzKQ0KYGBgDQoNCk3hu5l0IHBow6JuIHTDrWNoIHTGsMahbmcgcXVhbiBjaG8gdGjhuqV5IFRoaWNrbmVzcyBjw7MgcXVhbiBo4buHIHR1eeG6v24gdMOtbmggduG7m2kgQk1JLCB54bq/dSBuaMawbmcgY8OzIMO9IG5naMSpYS4NCg0KIyDDlG4gbOG6oWkgduG7gSBBTk9WQQ0KDQpUcm9uZyBiw6BpIHRyxrDhu5tjLCBjaMO6bmcgdGEgxJHDoyBiaeG6v3QgQU5PVkEgY8OzIGLhuqNuIGNo4bqldCBsw6AgbeG7mXQgbcO0IGjDrG5oIGjhu5NpIHF1eSB0dXnhur9uIHTDrW5oIGPDsyBk4bqhbmcgWSB+IFggbmjhurFtIMaw4bubYyBsxrDhu6NuZyBoaeG7h3Ug4bupbmcgY+G7p2EgbeG7mXQgeeG6v3UgdOG7kSBYIChwaMOibiBuaMOzbSkgbMOgbSB0aGF5IMSR4buVaSBt4buZdCBr4bq/dCBxdeG6oyDEkeG7i25oIGzGsOG7o25nIFkuIA0KDQpBTk9WQSB0aMaw4budbmcgxJHGsOG7o2Mgw6FwIGThu6VuZyDEkeG7gyBzbyBzw6FuaCwgbmjGsG5nIHRo4buxYyByYSBuw7MgbMOgIG3hu5l0IHF1eSB0csOsbmggbmhp4buBdSBn4buTbSBuaGnhu4F1IGLGsOG7m2MgOiANCg0KMSkJxJDhuqd1IHRpw6puLCB0YSBi4bqvdCBideG7mWMgcGjhuqNpIGtp4buDbSB0cmEgbmjhu69uZyBnaeG6oyDEkeG7i25oIGNobyB0aGnhur90IGvhur8gQU5PVkEsIHRoZW8gdGjhu6kgdOG7sSBxdWFuIHRy4buNbmcgZ+G7k20gY8OhYyBnaeG6oyDEkeG7i25oIHbhu4EgUGjGsMahbmcgc2FpIMSR4buTbmcgbmjhuqV0IGdp4buvYSBjw6FjIHBow6JuIG5ow7NtLCBnaeG6oyDEkeG7i25oIHbhu4EgcGjDom4gcGjhu5FpIGNodeG6qW4gY+G7p2EgZOG7ryBsaeG7h3UgKGhheSBj4bunYSBzYWkgc+G7kSBwaOG6p24gZMawIGPhu6dhIG3DtCBow6xuaCksIHTDrW5oIMSR4buZYyBs4bqtcCBjaG8gbeG7l2kgcXVhbiBzw6F0IGPDoSB0aOG7gy4gDQoNCjIpCUThu7FuZyBtw7QgaMOsbmggaOG7k2kgcXV5IHR1eeG6v24gdMOtbmggKHRow60gZOG7pSBi4bqxbmcgaMOgbSBsbSgpIGPhu6dhIFIpIHbDoCBraeG7g20gxJHhu4tuaCB0w61uaCBwaMO5IGjhu6NwIGThu68gbGnhu4d1IGPhu6dhIG3DtCBow6xuaCBi4bqxbmcgRmlzaGVy4oCZcyBGIHRlc3QgKGjDoG0gYW92KCkgY+G7p2EgUiBob+G6t2MgQW5vdmEgY+G7p2EgcGFja2FnZSBjYXIpDQoNCjMpCUjhuq11IGtp4buDbSAocG9zdGhvYyB0ZXN0KSBob+G6t2MgcGjDom4gdMOtY2ggbcO0IGjDrG5oIHbhu5tpIHRy4buNbmcgc+G7kSB0xrDGoW5nIHBo4bqjbiAoY29udHJhc3QgYW5hbHlzaXMpLCBuaOG6sW0gbeG7pWMgdGnDqnUgxJHhu4tuaCB24buLIHPhu7Ega2jDoWMgYmnhu4d0LCBzbyBzw6FuaCBi4bqvdCBj4bq3cCB0deG6p24gdOG7sSBnaeG7r2EgY8OhYyBwaMOibiBuaMOzbQ0KDQo0KQnGr+G7m2MgdMOtbmgga8OtY2ggdGjGsOG7m2MgaGnhu4d1IOG7qW5nIChlZmZlY3Qtc2l6ZSkgY+G7p2EgaGnhu4d1IOG7qW5nIChldGEgc3F1YXJlZCkgDQoNCiFbXShhbmNvdmExLnBuZykNCg0KIyBU4burIEFOT1ZBIMSR4bq/biBBTkNPVkENCg0KVsOsIEFOT1ZBIGzDoCBt4buZdCBtw7QgaMOsbmggaOG7k2kgcXV5IHR1eeG6v24gdMOtbmgsIHRhIGhvw6BuIHRvw6BuIGPDsyB0aOG7gyDEkcawYSB0aMOqbSB2w6BvIG3DtCBow6xuaCBuaOG7r25nIGJp4bq/biBraMOhYyAoZ+G7jWkgbMOgIGhp4buHcCBiaeG6v24g4oCTIGNvdmFyaWF0ZSkgxJHhu4MgdOG6oW8gcmEgbeG7mXQgbcO0IGjDrG5oIGjhu5NpIHF1eSB0dXnhur9uIHTDrW5oIMSRYSBiaeG6v24uIEzDumMgbsOgeSwgdGhp4bq/dCBr4bq/IEFOT1ZBIMSRxrDhu6NjIG3hu58gcuG7mW5nIHPhur0gdHLhu58gdGjDoG5oIHRoaeG6v3Qga+G6vyBBTkNPVkEg4oCTIHBow6JuIHTDrWNoIGhp4buHcCBwaMawxqFuZyBzYWkgKEFuYWx5c2lzIG9mIGNvdmFyaWFuY2UpLiANCg0KTeG7mXQgY8OhY2ggxJHGoW4gZ2nhuqNuLCBBTkNPVkEgY2jDrW5oIGzDoCBt4buZdCBtw7QgaMOsbmggdHV54bq/biB0w61uaCBjw7MgY8O5bmcgbeG7pWMgdGnDqnUgbmjGsCBBTk9WQSwgbmjGsG5nIGNo4bupYSDEkeG7k25nIHRo4budaSBiaeG6v24gcGjDom4gbmjDs20gWCB2w6AgbeG7mXQgaGnhu4dwIGJp4bq/biBz4buRIEMgxJHhu5ljIGzhuq1wIDoNCg0KWSB+IChYICsgQykNCg0KVOG6oWkgc2FvIHRhIGzhuqFpIChwaOG6o2kpIGzDoG0gbmjGsCB0aOG6vyA/DQoNClPGoSDEkeG7kyBzYXUgxJHDonkgc+G6vSBnacO6cCBi4bqhbiBow6xuaCBkdW5nIMSRaeG7gXUgZ8OsIHjhuqN5IHJhIGtoaSB0YSDEkcawYSB0aMOqbSBiaeG6v24gc+G7kSBDIHbDoG8gbcO0IGjDrG5oIMSRxqFuIGJp4bq/biBj4bunYSBBTk9WQTogTmjGsCB0YSB0aOG6pXksIG3DtCBow6xuaCBZIH4gWCBjaOG7iSBjw7MgMSBiaeG6v24gZHV5IG5o4bqldCwgbsOzIHF1w6EgxJHGoW4gZ2nhuqNuLiBT4buxIMSRxqFuIGdp4bqjbiBuw6B5IGPDsyB0aOG7gyBsw6AgduG7q2EgxJHhu6cgxJHhu4MgxJHDoXAg4bupbmcgbeG7pWMgdGnDqnUgdGjhu7FjIGThu6VuZzoga2nhu4NtIGNo4bupbmcgaGnhu4d1IOG7qW5nIGPhu6dhIHRow60gbmdoaeG7h20gLyBzbyBzw6FuaCBnaeG7r2EgMy00IHBow6JuIG5ow7NtLiBOaMawbmcgxJHDtGkga2hpLCBzdXkgbmdoxKkgbmfDonkgdGjGoSB2w6AgY2jhu4kgbmjDrG4gdGjhuqV5IG3hu6VjIHRpw6p1IHNvIHPDoW5oIHPhur0gZOG6q24gxJHhur9uIG3hu5l0IGvhur90IHF14bqjIEYgdGVzdCB54bq/dSBob+G6t2MgdGjhuq1tIGNow60gw6JtIHTDrW5oOyB2w6wgdHLhu4sgc+G7kSBGIGzDoCB04buJIGzhu4cgZ2nhu69hIHBo4bqnbiBwaMawxqFuZyBzYWkgbcOgIG3DtCBow6xuaCBjaG8gcGjDqXAgZ2nhuqNpIHRow61jaCAoTVNNKSB2w6AgcGjhuqduIGPDsm4gbOG6oWkgbcOgIG3DtCBow6xuaCBraMO0bmcgdGjhu4MgZ2nhuqNpIHRow61jaCDEkcaw4bujYyAoTVNSKS4NCg0KTeG7mXQgbcO0IGjDrG5oIMSRxqFuIGdp4bqjbiBraMO0bmcgYmFvIGdp4budIMSR4bunIMSR4buDIGdp4bqjaSB0aMOtY2ggxJHhu6cgdGjhur8gZ2nhu5tpIGhp4buHbiB0aOG7sWMuDQoNCktoaSDEkcawYSB0aMOqbSAxIGhp4buHcCBiaeG6v24gQyB2w6BvIG3DtCBow6xuaCwgQyBjaG8gcGjDqXAgZ2nhuqNpIHRow61jaCB0aMOqbSBt4buZdCBwaOG6p24gcGjGsMahbmcgc2FpIHbDoCBnacOhIHRy4buLIFJlc2lkdWFsIHN1bSBvZiBzcXVhcmUgKFNTUikgZG8gxJHDsyBz4bq9IHRodSBuaOG7jyBs4bqhaS4gTcO0IGjDrG5oIDIgYmnhur9uIG3huqFuaCBoxqFuLCBwaMO5IGjhu6NwIHRo4buxYyB04bq/IGjGoW4uDQoNCiFbXShhbmNvdmEyLnBuZykNCg0KVGhp4bq/dCBr4bq/IEFOQ09WQSBz4bq9IGjhu691IMOtY2ggY2hvIG5oaeG7gXUgaG/DoG4gY+G6o25oLCBuaMawIDoNCg0KMSkJS2hpIG5naGnDqm4gY+G7qXUgc2luaCBtdeG7kW4gxJFp4buBdSBjaOG7iW5oIGhp4buHdSDhu6luZyBj4bunYSBt4buZdCB54bq/dSB04buRIGfDonkgbmhp4buFdSDEkcOjIMSRxrDhu6NjIHRpw6puIGxp4buHdSB0csaw4bubYy4gWeG6v3UgdOG7kSBuaGnhu4V1IG7DoHkgY8OzIHRo4buDIGfDonkgc2FpIGzhu4djaCBr4bq/dCBxdeG6oyBzdXkgZGnhu4VuIHRo4buRbmcga8OqIGPhu6dhIEFOT1ZBIG3hu5l0IGPDoWNoIGjhu4cgdGjhu5FuZy4NCg0KxJBhIHPhu5EgbmdoacOqbiBj4bupdSB5IGjhu41jIGPGoSBi4bqjbiDEkeG7gXUgcGjhuqNpIGTDuW5nIMSR4bq/biBBTkNPVkEgxJHhu4MgaGnhu4d1IGNo4buJbmggY8OhYyB54bq/dSB04buRIG5oxrAgdHXhu5VpLCBjw6JuIG7hurduZywgY2hp4buBdSBjYW8gY2hvIGPDoWMgxJHhuqFpIGzGsOG7o25nIHNpbmggbMO9LCBzaW5oIGjDs2EgdsOsIGPGoSB0aOG7gyBjb24gbmfGsOG7nWkgbMOgIG3hu5l0IG3huqFuZyBsxrDhu5tpIHTGsMahbmcgcXVhbiBwaOG7qWMgdOG6oXAuLi4NCg0KVGjDrSBk4bulIDogbeG7mXQgbmdoacOqbiBj4bupdSB24buBIGNo4bupYyBuxINuZyB0aW0gbeG6oWNoIMSRxrDhu6NjIHRo4buxYyBoaeG7h24gxJHhu5NuZyB0aOG7nWkg4bufIDIgxJHhu4thIMSRaeG7g20gbMOgIENhbyBuZ3V5w6puIHbDoCDEkOG7k25nIGLhurFuZywgdGjDrCBuaOG7r25nIGvhur90IHF14bqjIHBow6JuIHTDrWNoIGtow60gbcOhdSBwaOG6o2kgxJHGsOG7o2MgaGnhu4d1IGNo4buJbmggdGhlbyDEkeG7mSBjYW8sIHbDrCDEkeG7mSBjYW8gbMOgbSB0aGF5IMSR4buVaSDDoXAgc3XhuqV0IE94eSB0cm9uZyBraMOtIHF1eeG7g24uIFTGsMahbmcgdOG7sSwgdGEgY8WpbmcgY+G6p24gaGnhu4d1IGNo4buJbmggY8OhYyBjaOG7iSBz4buRIHbhu4EgY2jhu6ljIG7Eg25nIHRyYW8gxJHhu5VpIGtow60gdGhlbyBnacOhIHRy4buLIEhhZW1vZ2xvYmluIGhv4bq3YyBnaeG7m2kgdMOtbmgsIHbDrCBOYW0gZ2nhu5tpIGPDsyBu4buTbmcgxJHhu5kgSGFlbW9nbG9iaW4gdHJvbmcgbcOhdSBjYW8gaMahbiBzbyB24bubaSBwaOG7pSBu4buvLiBI4bqndSBo4bq/dCBuaOG7r25nIG5naGnDqm4gY+G7qXUgxJHhu4tuaCBsxrDhu6NuZyB0cm9uZyBZIGjhu41jIMSR4buBdSBoaeG7h3UgY2jhu4luaCBr4bq/dCBxdeG6oyB0aGVvIFR14buVaSBj4bunYSBi4buHbmggbmjDom4gxJHhu4MgbG/huqFpIHRy4burIOG6o25oIGjGsOG7n25nIGPhu6dhIHPhu7EgbMOjbyBow7NhLg0KDQoyKQlUcm9uZyBjw6FjIG5naGnDqm4gY+G7qXUgdGjhu60gbmdoaeG7h20gaGnhu4d1IHF14bqjIMSRaeG7gXUgdHLhu4ssIGPDsyBraGkgY2jDum5nIHRhIGPhuqduIGNodeG6qW4gaMOzYSBnacOhIHRy4buLIGPGoSBi4bqjbiBj4bunYSBvdXRjb21lIFkg4bufIHRo4budaSDEkWnhu4NtIHRyxrDhu5tjIGtoaSDEkWnhu4F1IHRy4buLIChCYXNlbGluZSkgY2hvIHThuqV0IGPhuqMgYuG7h25oIG5ow6JuLCDEkeG7gyDEkeG6o20gYuG6o28gxJHDoW5oIGdpw6EgY2jDrW5oIHjDoWMgaGnhu4d1IHF14bqjIMSRaeG7gXUgdHLhu4sgY+G7p2EgdGh14buRYyAoduG7m2kgZ2nhuqMgxJHhu4tuaCBy4bqxbmcgY8OhYyBi4buHbmggbmjDom4gY8OzIHRy4bqhbmcgdGjDoWkgYuG7h25oIGzDvSB0xrDGoW5nIMSRxrDGoW5nIG5oYXUga2hpIGLhuq90IMSR4bqndSDEkWnhu4F1IHRy4buLKS4NCg0KMykJS2hpIG5naGnDqm4gY+G7qXUgc2luaCBjaOG7pyBxdWFuIG114buRbiBraOG6o28gc8OhdCBt4buZdCBnaeG6oyB0aHV54bq/dCDEkeG6t2MgYmnhu4d0LCBuaOG6sW0geMOhYyDEkeG7i25oIHZhaSB0csOyIGPhu6dhIG5oaeG7gXUgeeG6v3UgdOG7kSBraMOhYyBuaGF1IGPDsyDhuqNuaCBoxrDhu59uZyDEkeG6v24gYuG7h25oIGzDvS4NCg0KNCkJTmdoacOqbiBj4bupdSBzaW5oIGPFqW5nIGPDsyB0aOG7gyBjaMawYSBow6BpIGzDsm5nIHbhu5tpIMO9IG5naMSpYSB0aOG7kW5nIGvDqiB2w6AgRWZmZWN0LXNpemUgY+G7p2EgaGnhu4d1IOG7qW5nIGNow61uaCB0cm9uZyBtw7QgaMOsbmggxJHGoW4gZ2nhuqNuIGJhbiDEkeG6p3UgdsOgIGh5IHbhu41uZyBy4bqxbmcgdmnhu4djIG3hu58gcuG7mW5nIG3DtCBow6xuaCBz4bq9IGdpw7pwIGjhu40gbMOgbSBjaG8ga+G6v3QgceG7p2EgxJHhurlwIGjGoW4gPw0KDQohW10oYW5jb3ZhNC5wbmcpDQoNCkPFqW5nIG5oxrAgbcO0IGjDrG5oIE1peGVkIG1vZGVsIHbDoCB0aGnhur90IGvhur8gIHRo4butIG5naGnhu4dtIGzDom0gc8OgbmcgY8OzIHjDqXQgcmFuZG9tIGVmZmVjdCB0aGVvIGJsb2NrICwgbeG7pWMgdGnDqnUgY+G7p2EgQU5DT1ZBIGPFqW5nIGzDoCB0aHUgbmjhu48gcGjhuqduIHBoxrDGoW5nIHNhaSBtw6AgbcO0IGjDrG5oIGtow7RuZyBnaeG6o2kgdGjDrWNoIMSRxrDhu6NjOyB0dXkgbmhpw6puIGtow6FjIGJp4buHdCBnaeG7r2EgMiBwaMawxqFuZyBwaMOhcCBNaXhlZCBtb2RlbCB2w6AgQU5DT1ZBIG7hurFtIOG7nyBjaOG7lyB0cm9uZyBNaXhlZCBtb2RlbCwgcmFuZG9tIGVmZmVjdCBu4bqxbSBiw6puIG5nb8OgaSBoaeG7h3Ug4bupbmcgY2jDrW5oIHbDoCBwaOG6p24gbOG7m24gdHLGsOG7nW5nIGjhu6NwLCBjxqEgY2jhur8gY+G7p2Egc2FpIHPhu5Egbmfhuqt1IG5oacOqbiBuw6B5IGtow7RuZyB0aOG7gyDEkeG7i25oIG5naMSpYSBoYXkgZ2nhuqNpIHRow61jaCDEkcaw4bujYyAodGjDrSBk4bulIGPGoSDEkeG7i2EgY+G7p2EgYuG7h25oIG5ow6JuKS4gVHJvbmcga2hpIMSRw7MsIHRoaeG6v3Qga+G6vyBBTk9WQSBjaOG7pyDEkeG7mW5nIHPhu60gZOG7pW5nIG3hu5l0IGhp4buHcCBiaeG6v24gc+G7kSB0aOG7sWMgc+G7sSDEkcaw4bujYyBraOG6o28gc8OhdCwgxJHGsOG7o2MgdGnDqm4gbGnhu4d1IHbDoCBjw7MgdGhhbSBnaWEgdsOgbyBoaeG7h3Ug4bupbmcgY2jDrW5oIHRyb25nIG3DtCBow6xuaC4NCg0KIyBBTkNPVkEgdGhlbyBGcmVxdWVudGlzdA0KDQpCw6J5IGdp4budIFRhIHPhur0gbMOgbSBt4buZdCBwaMOibiB0w61jaCBBTkNPVkEgdGhlbyB0csaw4budbmcgcGjDoWkgZnJlcXVlbnRpc3QgdHJvbmcgUjoNCg0KVsOsIEFOQ09WQSBjxqEgYuG6o24gY8WpbmcgbMOgIG3hu5l0IHRoaeG6v3Qga+G6vyBBTk9WQSwgbsOzIGPFqW5nIHnDqnUgY+G6p3Ugbmjhu69uZyBnaeG6oyDEkeG7i25oIHTGsMahbmcgdOG7sSBuaMawIEFOT1ZBLiBCw6puIGPhuqFuaCDEkcOzLCB0aGnhur90IGvhur8gQU5DT1ZBIGPDsm4gY8OzIG3hu5l0IGdp4bqjIMSR4buLbmgga2jDoWMgIHJpw6puZyBjaG8gbsOzLCDEkcOzIGzDoCA6DQoNCjEpIEdp4bqjIMSR4buLbmggduG7gSBxdWFuIGjhu4cgdHV54bq/biB0w61uaCBnaeG7r2EgWSB2w6AgaGnhu4dwIGJp4bq/biBz4buRIEMgOiBEbyB0YSDEkWFuZyBz4butIGThu6VuZyBo4buTaSBxdXkgdHV54bq/biB0w61uaCwgbMO9IHTGsOG7n25nIG5o4bqldCBsw6AgWSB0xrDGoW5nIHF1YW4gdHV54bq/biB0w61uaCB24bubaSBDLiBUxrDGoW5nIHF1YW4gbsOgeSBjw6BuZyBt4bqhbmgsIOG6o25oIGjGsOG7n25nIGPhu6dhIEMgxJHhu5FpIHbhu5tpIHBoxrDGoW5nIHNhaSBj4bunYSBZIGPDoG5nIGzhu5tuLCB0aOG6rW0gY2jDrSBs4bqlbiDDoXQgY+G6oyBoaeG7h3Ug4bupbmcgY+G7p2EgWC4gTuG6v3UgZ2nhu69hIFkgdsOgIEMga2jDtG5nIGPDsyBxdWFuIGjhu4cgdHV54bq/biB0w61uaCB0aMOsIGtow7RuZyBjw7MgY8ahIHPhu58gxJHhu4MgeMOpdCBDIG5oxrAgaGnhu4dwIGJp4bq/biBz4buRLg0KDQoyKSBHaeG6oyDEkeG7i25oIHbhu4Egc+G7sSDEkeG7mWMgbOG6rXAgZ2nhu69hIFggdsOgIEMgOiBLaMO0bmcgY8OzIHPhu7Ega2jDoWMgYmnhu4d0IHbhu4EgQyBnaeG7r2EgY8OhYyBwaMOibiBuaMOzbSANCg0KMykgR2nhuqMgxJHhu4tuaCB24buBIHPhu7EgxJHhu5NuZyBuaOG6pXQgY+G7p2EgbGnDqm4gaOG7hyB0dXnhur9uIHTDrW5oIGdp4buvYSBiaeG6v24ga+G6v3QgcXXhuqMgWSB2w6AgaGnhu4dwIGJp4bq/biBz4buRIEMgY2hvIHThu6tuZyBwaMOibiBuaMOzbSBYIDogbsOzaSBjw6FjaCBraMOhYywgxJHhu5MgdGjhu4sgdHV54bq/biB0w61uaCBZID0gQyBn4bqnbiBuaMawIHNvbmcgc29uZyB24bubaSBuaGF1IChjw7luZyBTbG9wZSkgZ2nhu69hIGPDoWMgcGjDom4gbmjDs20sIGhheSA6IGtow7RuZyBjw7MgaGnhu4d1IOG7qW5nIGRvIHTGsMahbmcgdMOhYyBnaeG7r2EgQyB2w6AgWC4gDQoNCsSQaeG7gXUga2nhu4duIG7DoHkga2jDtG5nIHBo4bqjaSBsdcO0biDEkcaw4bujYyB0aOG7j2EgbcOjbiB0csOqbiB0aOG7sWMgdOG6vywgdsOgIGtow7RuZyBob8OgbiB0b8OgbiBi4bqvdCBideG7mWMg4oCTIG7DsyBjaOG7iSB0aOG7gyBoaeG7h24gY2hvIGNow61uaCBraHV54bq/dCDEkWnhu4NtIGPhu6dhIHRoaeG6v3Qga+G6vyBBTkNPVkEgdsOsIHRyb25nIG3DtCBow6xuaCBZID0gWCtDLCBoaeG7g24gbmhpw6puIGNo4buJIGPDsyAxIHNsb3BlIMSRxrDhu6NjIMaw4bubYyB0w61uaCBjaG8gQy4NCg0KTuG6v3UgZMO5bmcgbcO0IGjDrG5oIEdMTSB04buVbmcgcXXDoXQsIHRhIGtow7RuZyBjw7JuIGzhu4cgdGh14buZYyB2w6BvIGPDoWMgdGhp4bq/dCBr4bq/IGtpbmggxJFp4buDbiBu4buvYSBuaMawbmcgaG/DoG4gdG/DoG4gY8OzIHRo4buDIG3hu58gcuG7mW5nIG3DtCBow6xuaCBjaOG7qWEgcXVhbiBo4buHIHTGsMahbmcgdMOhYyBZID0gQytYK0MgOlggaGF5IFk9QypYIHbDoCBwaMOibiB0w61jaCBoaeG7h3Ug4bupbmcgY+G7p2EgY+G6oyAzIHRow6BuaCBwaOG6p24gWCwgQywgQyA6WC4gRMO5IHNhbyB0cm9uZyBiw6BpIG7DoHkgTmhpIHbhuqtuIGLDoW0gc8OhdCB0aGVvIHRoaeG6v3Qga+G6vyBj4buVIMSRaeG7g24gduG7m2kgaHkgduG7jW5nIGzDoCBtw7QgaMOsbmggWT1DIGPDuW5nIHNsb3BlIHZhbHVlIGNobyBj4bqjIDMgcGjDom4gbmjDs20uDQoNCiFbXShhbmNvdmEzLnBuZykNCg0KxJDhuqd1IHRpw6puLCBMZXZlbmUgdGVzdCBjaG8gcGjDqXAgeMOhYyBuaOG6rW4gZ2nhuqMgxJHhu4tuaCBwaMawxqFuIHNhaSDEkeG7k25nIG5o4bqldDoNCg0KYGBge3IsbWVzc2FnZSA9IEZBTFNFLHdhcm5pbmc9RkFMU0V9DQojIExldmVuZSB0ZXN0DQphb3YoVGhpY2tuZXNzIH4gRGlhZ25vc3RpYywgZGF0KSU+JWNhcjo6bGV2ZW5lVGVzdCgpDQpgYGANCg0KR2nhuqMgxJHhu4tuaCBTbG9wZSDEkeG7k25nIG5o4bqldCBjw7MgdGjhu4Mga2nhu4NtIHRyYSBt4buZdCBjw6FjaCB0cuG7sWMgcXVhbjoNCg0KYGBge3IsbWVzc2FnZSA9IEZBTFNFLHdhcm5pbmc9RkFMU0V9DQpkYXQlPiVnZ3Bsb3QoYWVzKHg9Qk1JLHk9VGhpY2tuZXNzLGZpbGw9RGlhZ25vc3RpYyxjb2w9RGlhZ25vc3RpYykpKw0KICBnZW9tX3BvaW50KGFscGhhPTAuNSxzaXplPTIpKw0KICBnZW9tX3Ntb290aChtZXRob2Q9ImxtIixhbHBoYT0wLjMpKw0KICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzPWMoIiMwMjFjZTUiLCIjMzUwMDkxIiwiI2Q4MDIxZSIpKSsNCiAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzPWMoIiMxNjc3ZmYiLCIjYTYxNmZmIiwiI2ZmMTY1NCIpKSsNCiAgdGhlbWVfYncoKQ0KYGBgDQoNCkJp4buDdSDEkeG7kyBjaG8gdGjhuqV5IFNsb3BlIGPDsyB24bq7IGdp4buRbmcgbmhhdSBnaeG7r2EgQ29udHJvbCB2w6AgRmlicm9zaXMsIG5oxrBuZyBjw7Mga2jDoWMgYmnhu4d0IOG7nyBuaMOzbSBFbXBoeXNlbWEuDQoNClRhIGPFqW5nIGPDsyB0aOG7gyBraeG7g20gY2jhu6luZyBs4bqhaSBnaeG6oyDEkeG7i25oIG7DoHkgZOG7sWEgdsOgbyBtw7QgaMOsbmggY8OzIGPhuqMgaGnhu4d1IOG7qW5nIHTGsMahbmcgdMOhYyBnaeG7r2EgQk1JIHbDoCBEaWFnbm9zdGljOg0KDQpgYGB7cixtZXNzYWdlID0gRkFMU0Usd2FybmluZz1GQUxTRX0NCmxtb2QwPWxtKFRoaWNrbmVzcyB+IERpYWdub3N0aWMgKiBCTUksIGRhdGEpDQoNCmNhcjo6QW5vdmEobG1vZDApDQpgYGANCg0KQ8OzIHRo4buDIHRo4bqleSBy4bqxbmcga2jDtG5nIGPDsyBoaeG7h3Ug4bupbmcgdMawxqFuZyB0w6FjIMO9IG5naMSpYSBnaeG7r2EgQk1JIHbDoCBEaWFnbm9zdGljIChwPTAuMTEpDQoNCk5oxrAgduG6rXkgdGEgY8OzIHRo4buDIMOhcCBk4bulbmcgQU5DT1ZBIHbhu5tpIG3DtCBow6xuaCA6IFRoaWNrbmVzcyB+IEJNSSArIERpYWdub3N0aWMuIFR1eSBuaGnDqm4sIHRyxrDhu5tjIGjhur90IHRhIHPhur0gxJHGsGEgdHJ1bmcgYsOsbmggQk1JIHbhu4EgMCwgdsOsIHRo4buxYyBjaOG6pXQgYmnhur9uIEJNSSBraMO0bmcgcGjhuqNpIGzDoCBt4bulYyB0acOqdSBtw6AgdGEgcXVhbiB0w6JtIHRyb25nIG3DtCBow6xuaC4gTsOzIGPDsyBt4bq3dCDhu58gxJHDsyBjaOG7iSBuaOG6sW0gaOG7lyB0cuG7oyBjaG8gRiB0ZXN0IHbDoCBoaeG7h3UgY2jhu4luaCBjaG8gaGnhu4d1IOG7qW5nIGPhu6dhIFBow6JuIG5ow7NtOg0KDQpgDQpgYGB7cixtZXNzYWdlID0gRkFMU0Usd2FybmluZz1GQUxTRX0NCmRhdGEkQk1JPXNjYWxlKGRhdGEkQk1JLHNjYWxlID0gRikNCmBgYA0KDQpOaGkgZOG7sW5nIDIgbcO0IGjDrG5oIGxtMSB2w6AgbG0yIGzhuqduIGzGsOG7o3QgY8OzIGPDtG5nIHRo4bupYzoNCg0KVGhpY2tuZXNzIH4gQk1JICsgRGlhZ25vc3RpYw0KDQpUaGlja25lc3MgfiBCTUkgKyBEaWFnbm9zdGljIC0xDQoNCmBgYHtyfQ0KbG0xIDwtIGxtKFRoaWNrbmVzcyB+IEJNSSArIERpYWdub3N0aWMgKyBCTUksIGRhdGEgPSBkYXRhKQ0KDQpsbTIgPC0gbG0oVGhpY2tuZXNzIH4gQk1JICsgRGlhZ25vc3RpYyAtMSwgZGF0YSA9IGRhdGEpDQoNCnN1bW1hcnkobG0xKQ0KDQpzdW1tYXJ5KGxtMikNCmBgYA0KDQpOaMawIHRhIHRo4bqleSwgw70gbmdoxKlhIGPhu6dhIDIgbcO0IGjDrG5oIGzDoCBraMOhYyBuaGF1LCBtw7QgaMOsbmggbG0xIGPDsyBjaOG7qWEgSW50ZXJjZXB0ICwgIMSRxrDhu6NjIGhp4buDdSBuaMawIGdpw6EgdHLhu4sgVGhpY2tuZXNzIHRydW5nIGLDrG5oIOG7nyBuaMOzbSBDb250cm9sIHbDoCBjw7MgQk1JPTAsIHNhdSDEkcOzIDIgZHVtbXkgdmFyaWFibGUgY2hvIHBow6JuIG5ow7NtIGLhu4duaCBsw70gY2hvIHBow6lwIGRp4buFbiBnaeG6o2k6IFRoaWNrbmVzcyBnaeG6o20gMC4zMTYgwrVtICDhu58gbmjDs20gRW1waHlzZW1hIHbDoCB0xINuZyAwLjIzNiDCtW0g4bufIHBow6JuIG5ow7NtIEZpYnJvc2UuDQoNCk3DtCBow6xuaCB0aOG7qSBoYWkga2jDtG5nIGNo4bupYSBJbnRlcmNlcHQsIHbDoCBo4buHIHPhu5EgaOG7k2kgcXV5IGNobyBt4buXaSBEdW1teSB2YXJpYWJsZSBjxaluZyBjaMOtbmggbMOgIGdpw6EgdHLhu4sgVGhpY2tuZXNzIHRydW5nIGLDrG5oIOG7nyB04burbmcgcGjDom4gbmjDs20uIA0KDQpUcm9uZyBj4bqjIDIgbcO0IGjDrG5oLCBo4buHIHPhu5EgaOG7k2kgcXV5IGNobyBCTUkgbmjGsCBuaGF1ID0gMC4wMTMsIGNobyBiaeG6v3QgVGhpY2tuZXNzIHPhur0gdMSDbmcgMC4wMTMgwrVtIGNobyBt4buXaSDEkcahbiB24buLIEJNSS4NCg0KS2hpIMOhcCBk4bulbmcgaMOgbSBBbm92YSwgdGEgY8OzIGvhur90IHF14bqjIGPhu6dhIEYgdGVzdCBjaG8gcmnDqm5nIEJNSSB2w6AgRGlhZ25vc3RpYy4gS+G6v3QgcXXhuqMgY2hvIHRo4bqleSBzYXUga2hpIGhp4buHdSBjaOG7iW5oIGNobyBoaeG7h3Ug4bupbmcgY+G7p2EgQk1JLCBZ4bq/dSB04buRIGLhu4duaCBsw70gY8OzIGhp4buHdSDhu6luZyDEkeG7mWMgbOG6rXAgbMOgbSB0aGF5IMSR4buVaSBi4buBIGTDoHkgbcOgbmcgcGjhur8gbmFuZzogRigzLDM0KT00MDAuNjQ7IHBfdmFsdWUgPCAyLjEwXi0xNg0KDQpgYGB7cixtZXNzYWdlID0gRkFMU0Usd2FybmluZz1GQUxTRX0NCmNhcjo6QW5vdmEobG0xLCB0eXBlID0gIklJSSIpDQoNCmNhcjo6QW5vdmEobG0yLCB0eXBlID0gIklJSSIpDQpgYGANCg0KTWFyZ2luYWwgZWZmZWN0IGPhu6dhIFBow6JuIG5ow7NtIGLhu4duaCBsw70gY8OzIHRo4buDIHRyw6xuaCBiw6B5IG5oxrAgc2F1Og0KDQpgYGB7cixtZXNzYWdlID0gRkFMU0Usd2FybmluZz1GQUxTRX0NCg0KbG1vZCA8LSBsbShUaGlja25lc3MgfiBhcy52ZWN0b3IoQk1JKStEaWFnbm9zdGljLCBkYXRhID0gZGF0YSkNCg0KbmV3ZGF0YSA8LSBkYXRhX2ZyYW1lKERpYWdub3N0aWMgPSBsZXZlbHMoZGF0YSREaWFnbm9zdGljKSwgDQogICAgICAgICAgICAgICAgICAgICAgQk1JID0gbWVhbihkYXRhJEJNSSwgbmEucm0gPSBUUlVFKSkNCg0KYWRqX2RmIDwtIHByZWRpY3QobG1vZCwgbmV3ZGF0YSA9IG5ld2RhdGEsIGludGVydmFsID0gImNvbmZpZGVuY2UiKQ0KDQphZGpfZGY9Y2JpbmQobmV3ZGF0YSxhZGpfZGYpDQoNCmdncGxvdChhZGpfZGYsYWVzKHkgPSBmaXQsIHggPSBEaWFnbm9zdGljKSkrDQogIGdlb21fcG9pbnRyYW5nZShhZXMoeW1pbiA9IGx3ciwgeW1heCA9IHVwcixjb2w9RGlhZ25vc3RpYyksc2l6ZT0xLjIpICsNCiAgZ2VvbV9saW5lKGFlcyhncm91cD0xKSxsaW5ldHlwZT0yLHNpemU9MS4yKSsNCiAgc2NhbGVfeV9jb250aW51b3VzKCJUaGlja25lc3MiKSsgDQogIHNjYWxlX3hfZGlzY3JldGUoIkRpYWdub3N0aWMiKSsNCiAgdGhlbWVfYncoKSsNCiAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcz1jKCIjMDIxY2U1IiwiIzM1MDA5MSIsIiNkODAyMWUiKSkNCmBgYA0KDQpMxrB1IMO9LCBnacOhIHRy4buLIHRo4buDIGhp4buHbiB0csOqbiBiaeG7g3UgxJHhu5MgY2jDrW5oIGzDoCBoaeG7h3Ug4bupbmcgcmnDqm5nIHBo4bqnbiBj4bunYSB04burbmcgYuG7h25oIGzDvSwgc2F1IGtoaSBoaeG7h3UgY2jhu4luaCBjaG8gQk1JLg0KDQpDw7JuIMSRw6J5IGzDoCBoaeG7h3Ug4bupbmcgcmnDqm5nIHBo4bqnbiBj4bunYSBCTUkNCg0KYGBge3J9DQpuZXdkYXRhIDwtIGV4cGFuZC5ncmlkKERpYWdub3N0aWMgPSBsZXZlbHMoZGF0YSREaWFnbm9zdGljKSwgDQogICAgICAgICAgICAgICAgICAgICAgIEJNSSA9IHNlcShtaW4oZGF0YSRCTUkpLG1heChkYXRhJEJNSSksbD0xMDApKQ0KDQpwcmVkX2RmIDwtIHByZWRpY3QobG1vZCwgbmV3ZGF0YSA9IG5ld2RhdGEsIGludGVydmFsID0gImNvbmZpZGVuY2UiKQ0KDQpwcmVkX2RmIDwtIGNiaW5kKG5ld2RhdGEscHJlZF9kZikNCg0Kb2JzX2RmIDwtIGNiaW5kKGRhdGEsIG9icyA9IGZpdHRlZChsbW9kKSArIHJlc2lkKGxtb2QpKQ0KDQpnZ3Bsb3QocHJlZF9kZiwgYWVzKHkgPSBmaXQsIHggPSBCTUksIGdyb3VwID0gRGlhZ25vc3RpYykpKyANCiAgZ2VvbV9wb2ludChkYXRhID0gb2JzX2RmLGFlcyh5ID0gb2JzLCBzaGFwZSA9IERpYWdub3N0aWMsY29sPURpYWdub3N0aWMpLHNpemU9MS41KSsgDQogIGdlb21fbGluZShhZXMoY29sPURpYWdub3N0aWMpLHNpemU9MSkrDQogIGdlb21fcmliYm9uKGFlcyh5bWluID0gbHdyLHltYXggPSB1cHIsZ3JvdXA9RGlhZ25vc3RpYyxmaWxsPURpYWdub3N0aWMpLGFscGhhID0gMC4yKSsNCiAgc2NhbGVfeV9jb250aW51b3VzKCJUaGlja25lc3MiKSsNCiAgc2NhbGVfeF9jb250aW51b3VzKCJCTUkiKSsNCiAgdGhlbWVfYncoKSsNCiAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcz1jKCIjMDIxY2U1IiwiIzM1MDA5MSIsIiNkODAyMWUiKSkrDQogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcz1jKCIjMTY3N2ZmIiwiI2E2MTZmZiIsIiNmZjE2NTQiKSkNCg0KYGBgDQoNClRyb25nIGLDoGkgdHLGsOG7m2MgKEFub3ZhIGNobyBwaMOpcCDEkW8gbOG6t3AgbOG6oWkpLCBOaGkgY8OzIGdp4bubaSB0aGnhu4d1IHbhu4EgcGFja2FnZSBhZmV4IHLhuqV0IGhp4buHdSBxdeG6oyBjaG8gcGjDom4gdMOtY2ggcGjGsMahbmcgc2FpICht4buNaSB0aGnhur90IGvhur8gQU5PVkEpLiBUYSBz4bq9IHRo4butIMOhcCBk4bulbmcgbsOzIGNobyB0aGnhur90IGvhur8gQU5DT1ZBIG5oxrAgc2F1Og0KDQpgYGB7cixtZXNzYWdlID0gRkFMU0Usd2FybmluZz1GQUxTRX0NCmxpYnJhcnkoYWZleCkNCg0KZGF0JT4lbXV0YXRlKGlkPXJvd25hbWVzKC4pKSU+JQ0KICBhb3ZfZXooaWQ9ImlkIiwNCiAgICAgICAgIGR2PSJUaGlja25lc3MiLCANCiAgICAgICAgIGRhdGE9LiwNCiAgICAgICAgIGJldHdlZW49IkRpYWdub3N0aWMiLA0KICAgICAgICAgY292YXJpYXRlPSJCTUkiLA0KICAgICAgICAgZmFjdG9yaXplID0gRiwNCiAgICAgICAgIGFub3ZhX3RhYmxlID0gbGlzdChlcz0icGVzIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBwX2FkanVzdF9tZXRob2Q9ImJvbmZlcnJvbmkiKSktPmNvdjENCg0KYGBgDQoNCkvhur90IHF14bqjIGPhu6dhIGFmZXggIsSR4bq5cCIgaMahbiwgdsOsIHRhIGPDsyDDoXAgZOG7pW5nIG3hu5l0IGhp4buHdSBjaOG7iW5oIEJvbmZlcnJvbmkgY2hvIHBfdmFsdWUsbmdvw6BpIEYgdGVzdCAsIGFmZXggY8OybiB0w61uaCBjaG8gdGEgY+G6oyBlZmZlY3Qtc2l6ZSBi4buZIHBo4bqtbiBjaG8gbeG7l2kgeeG6v3UgdOG7kSAocGFydGlhbCBldGEgc3F1YXJlZDogcGVzKS4gVGhlbyDEkcOzLCBC4buHbmggbMO9IGPDsyBrw61jaCB0aMaw4bubYyBoaeG7h3Ug4bupbmcgY2FvIGjGoW4gbmhp4buBdSBzbyB24bubaSBCTUkgKHBlcz0wLjgwNSBzdiAwLjE2MikuIEvhur90IHF14bqjIEYgdGVzdCBjaG8gRGlhZ25vc3RpYyBsw6AgRigyLDM0KT03MCw1NTsgcDwgMS41KjEwXi0xMiANCg0KYGBge3IsbWVzc2FnZSA9IEZBTFNFLHdhcm5pbmc9RkFMU0V9DQpzdW1tYXJ5KGNvdjEpDQpgYGANCg0KVGEgY8OybiBjw7MgdGjhu4MgdHLDrG5oIGLDoHkga+G6v3QgcXXhuqMgdHJ1bmcgYsOsbmggdsOgIENpIGPhu6dhIFRoaWNrbmVzcyBzYXUga2hpIGhp4buHdSBjaOG7iW5oIGNobyBCTUkuIFNvIHbhu5tpIGvhur90IHF14bqjIHRo4buxYyB04bq/IChiw6puIGTGsOG7m2kpLCBr4bq/dCBxdeG6oyBzYXUgaGnhu4d1IGNo4buJbmggY8OzIG3hu5l0IHPhu5Ega2jDoWMgYmnhu4d0IG5o4buPLCB0aMOtIGThu6UgOTUlQ0kgbeG7nyBy4buZbmcgaMahbiwgdHJ1bmcgYsOsbmggY2FvIGjGoW4NCg0KYGBge3IsbWVzc2FnZSA9IEZBTFNFLHdhcm5pbmc9RkFMU0V9DQpsc209bHNtZWFucyhjb3YxLHNwZWNzID0gIkRpYWdub3N0aWMiKQ0KDQpsc20NCg0KZGF0JT4lZ3JvdXBfYnkoRGlhZ25vc3RpYyklPiUNCiAgc3VtbWFyaXNlX2F0KCJUaGlja25lc3MiLA0KICAgICAgICAgICAgICAgZnVucyhNZWFuPW1lYW4sDQogICAgICAgICAgICAgICBMTD1xdWFudGlsZSguLHByb2JzPTAuMDUpLA0KICAgICAgICAgICAgICAgVUw9cXVhbnRpbGUoLixwcm9icz0wLjk1KQ0KICAgICAgICAgICAgICAgKSklPiVrbml0cjo6a2FibGUoKQ0KYGBgDQoNCk3hu5l0IGzhuqduIG7hu69hLCB0YSBs4bqhaSBjw7MgdGjhu4MgduG6vSBiaeG7g3UgxJHhu5MgTWFyZ2luYWwgZWZmZWN0IGPhu6dhIERpYWdub3N0aWMNCg0KYGBge3IsbWVzc2FnZSA9IEZBTFNFLHdhcm5pbmc9RkFMU0V9DQpsc20lPiVzdW1tYXJ5KCklPiVhc190aWJibGUoKSU+JQ0KICBnZ3Bsb3QoYWVzKHg9RGlhZ25vc3RpYywgDQogICAgICAgICAgICAgeT1sc21lYW4sDQogICAgICAgICAgICAgZmlsbD1sc21lYW4sDQogICAgICAgICAgICAgZ3JvdXA9MSkpKw0KICBnZW9tX2Vycm9yYmFyKGFlcyh5bWluPWxzbWVhbi1TRSwgDQogICAgICAgICAgICAgICAgICAgIHltYXg9bHNtZWFuK1NFKSwgDQogICAgICAgICAgICAgICAgd2lkdGg9MC4yLHNpemU9MSkgKw0KICBnZW9tX2xpbmUoc2l6ZT0xLGNvbD0iZ3JleSIsbGluZXR5cGU9MikrDQogIGdlb21fcG9pbnQoc2l6ZT01LHNoYXBlPTIxLGNvbD0iYmxhY2siLHNob3cubGVnZW5kID0gRikrDQogIHNjYWxlX3lfY29udGludW91cygiVGhpY2tuZXNzIikrDQogIHNjYWxlX2ZpbGxfZ3JhZGllbnQobG93PSJibHVlIixoaWdoPSJyZWQiKSsNCiAgdGhlbWVfYncoKQ0KYGBgDQoNCnBhY2thZ2UgYWZleCBjw7JuIGNobyBwaMOpcCBsw6BtIHBow6JuIHTDrWNoIHTGsMahbmcgcGjhuqNuIChjb250cmFzdCBhbmFseXNpcykgcuG6pXQgZOG7hSBkw6BuZzoNCg0KVGjDrSBk4bulIG3hu5l0IGdp4bqjIHRodXnhur90IHTGsMahbmcgcGjhuqNuIHbhu5tpIHRy4buNbmcgc+G7kSAwLC0xLzMsKzEvMywgaG/hurdjIHTGsMahbmcgcGjhuqNuIHRoZW8gaMOgbSDEkWEgdGjhu6ljIGLhuq1jIDINCg0KYGBge3IsbWVzc2FnZSA9IEZBTFNFLHdhcm5pbmc9RkFMU0V9DQpjb250cmFzdChsc20sIA0KICAgICAgICAgbGlzdCgiQ29udDEiPWMoMCwtMSwxKS8zKSwNCiAgICAgICAgIGFkanVzdD0iYm9uZmVycm9uaSIpDQoNCmNvbnRyYXN0KGxzbSxtZXRob2Q9InBvbHkiKQ0KDQpwY2RmPWNvbnRyYXN0KGxzbSxtZXRob2Q9InBvbHkiKSU+JWNvZWYoKSU+JWFzX3RpYmJsZSgpDQoNCmNvbG5hbWVzKHBjZGYpPWMoIlN0ZXAiLCJsaW5lYXIiLCJRdWFkcmF0aWMiKQ0KDQpwY2RmJT4lZ2F0aGVyKGxpbmVhcjpRdWFkcmF0aWMsa2V5PSJQb2x5Iix2YWx1ZT0iQ29lZiIpJT4lDQogIGdncGxvdChhZXMoeD1TdGVwLHk9UG9seSxmaWxsPUNvZWYpKSsNCiAgZ2VvbV90aWxlKGNvbD0iYmxhY2siKSsNCiAgc2NhbGVfZmlsbF9ncmFkaWVudChsb3c9IndoaXRlIixoaWdoPSJibGFjayIpKw0KICB0aGVtZV9idygpDQpgYGANCg0KVGEgY8WpbmcgY8OzIHRo4buDIGzDoG0gbeG7mXQgcG9zdC1ob2MgdGVzdCB24bubaSBoaeG7h3UgY2jhu4luaCBCb25mZXJyb25pIDoNCg0KYGBge3IsbWVzc2FnZSA9IEZBTFNFLHdhcm5pbmc9RkFMU0V9DQpjb250cmFzdChsc20sInJldnBhaXJ3aXNlIixhZGp1c3Q9ImJvbmZlcnJvbmkiKQ0KDQpjZGY9Y29udHJhc3QobHNtLCJyZXZwYWlyd2lzZSIsYWRqdXN0PSJib25mZXJyb25pIiklPiUNCiAgY29lZigpJT4lDQogIGFzX3RpYmJsZSgpDQoNCmNvbG5hbWVzKGNkZik9YygiR3JvdXAiLCJFL0MiLCJGL0MiLCJGL0UiKQ0KDQpjZGYlPiVnYXRoZXIoYEUvQ2A6YEYvRWAsa2V5PSJQYWlycyIsdmFsdWU9IkNvZWYiKSU+JQ0KICBnZ3Bsb3QoYWVzKHg9R3JvdXAseT1QYWlycyxmaWxsPWFzLmZhY3RvcihDb2VmKSkpKw0KICBnZW9tX3RpbGUoY29sPSJibGFjayIpKw0KICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXM9YygiYmx1ZSIsIndoaXRlIiwicmVkIikpKw0KICB0aGVtZV9idygpDQoNCmBgYA0KDQpL4bq/dCBxdeG6oyBjaG8gdGjhuqV5IGPDsyBz4buxIGtow6FjIGJp4buHdCDDvSBuZ2jEqWEgduG7gSBnacOhIHRy4buLIFRoaWNrbmVzcyBnaeG7r2EgMiBi4buHbmggbMO9IEVtcGh5c2VtYSB2w6AgRmlicm9zaXMgc28gduG7m2kgbmjDs20gY2jhu6luZywgY+G7pSB0aOG7gzogQuG7h25oIG5ow6JuIGtow60gcGjhur8gdGjFqW5nIGPDsyBi4buBIGTDoHkgbcOgbmcgcGjhur8gbmFuZyBnaeG6o20gdHJ1bmcgYsOsbmggLTAuMjk4IMK1bSBzbyB24bubaSBuZ8aw4budaSBiw6xuaCB0aMaw4budbmcgKHA8MC4wMDAxKSAsIHRyb25nIGtoaSBi4buHbmggeMahIHBo4buVaSBsw6BtIHTEg25nIGLhu4EgZMOgeSBtw6BuZyB0cnVuZyBiw6xuaCArMC4yOTcgwrVtIChwPTAuMDAwMSkNCg0KIyBBTkNPVkEgdGhlbyBCQVlFUw0KDQpCw6J5IGdp4budLCBjaMO6bmcgdGEgc+G6vSB0aOG7sWMgaGnhu4duIEFOQ09WQSBi4bqxbmcgcGjGsMahbmcgcGjDoXAgQmF5ZXM6DQoNClBow6JuIHTDrWNoIEJheWVzIGNobyB0aGnhur90IGvhur8gQU5DT1ZBIMSRxrDhu6NjIGLhuq90IMSR4bqndSB24bubaSBnaeG6oyDEkeG7i25oOiBHacOhIHRy4buLIHF1YW4gc8OhdCBZaWsg4bufIG3hu5dpIGPDoSB0aOG7gyBpIHRyb25nIHBow6JuIG5ow7NtIFhrIGzDoCBr4bq/dCBxdeG6oyBj4bunYSBt4buZdCBiaeG6v24gbmfhuqt1IG5oacOqbiBZIGPDsyBwaMOibiBwaOG7kWkgY2h14bqpbiAoR2F1c3NpYW4pIMSRxrDhu6NjIHjDoWMgxJHhu4tuaCBi4bqxbmcgMiB0aGFtIHPhu5EgOiBNdSAodHJ1bmcgYsOsbmgpIHbDoCBTaWdtYSAoxJHhu5kgbOG7h2NoIGNodeG6qW4pLiANCg0KR2nDoSB0cuG7iyB0aGFtIHPhu5EgTXUgxJHGsOG7o2MgxrDhu5tjIHTDrW5oIGLhurFuZyBt4buZdCBtw7QgaMOsbmggdHV54bq/biB0w61uaCBjw7MgbuG7mWkgZHVuZzoNCg0KJCRcbXUgX3tpa30gPSBcYmV0YSBfe2N9QyArIFxiZXRhIF97MX1YX3sxfSArIFxiZXRhIF97Mn1YX3syfSArIFxiZXRhIF97M31YX3szfSQkDQoNCkdp4bqjIHRodXnhur90IHRp4buBbiDEkeG7i25oIChwcmlvcikgY2hvIGJldGEgdsOgIHNpZ21hIGfhu5NtOg0KDQokJFxiZXRhIFxzaW0gXG1hdGhpdHtOfSAoMCxcc2lnbWEgX3tcYmV0YSB9KSQkDQoNCiQkXHNpZ21hIFxzaW0gXG1hdGhpdHtDYXVjaHl9ICgwLDUpJCQNCg0KIVtdKGFuY292YTUucG5nKQ0KDQpUcsaw4bubYyBo4bq/dCB0YSB2aeG6v3QgbuG7mWkgZHVuZyBtw7QgaMOsbmggYuG6sW5nIFNUQU4gY29kZQ0KDQpgYGB7cixtZXNzYWdlID0gRkFMU0Usd2FybmluZz1GQUxTRX0NCiMgc3RhbmNvZGUNCg0KbW9kZWxfY29kZSA9ICINCi8vIGRhdGEgYmxvY2sNCg0KZGF0YSB7DQppbnQ8bG93ZXI9MT4gbjsgICAgLy8gc2FtcGxlIHNpemUNCmludDxsb3dlcj0xPiBuWDsgICAvLyBmYWN0b3IgbGV2ZWwNCnZlY3RvciBbbl0geTsgICAgICAvLyByZXNwb25zZSB2ZWN0b3IgWQ0KbWF0cml4IFtuLG5YXSBYOyAgIC8vIGRlc2lnbiBtYXRyaXggb2YgcHJlZGljdG9ycw0KfQ0KDQovLyBwYXJhIGJsb2NrDQoNCnBhcmFtZXRlcnMgew0KdmVjdG9yW25YXSBiZXRhOyAgIC8vIGJldGEgY29lZnMgPSB2ZWN0b3INCnJlYWw8bG93ZXI9MD4gc2lnbWE7ICAvLyBzaWdtYVkgPSByZWFsIG51bWJlcg0KfQ0KdHJhbnNmb3JtZWQgcGFyYW1ldGVycyB7IC8vIE11WSA9IHZlY3Rvcg0KdmVjdG9yW25dIG11OyAgICAgICAgIA0KDQptdSA9IFgqYmV0YTsgIC8vIEVzdGltYXRlIE11DQp9DQoNCi8vIE1vZGVsIGJsb2NrDQoNCm1vZGVsIHsNCg0KLy8gTGlrZWxpaG9vZCA6IHkgZm9sbG93cyBHYXVzc2lhbiBkaXN0LmxhdywgZGV0ZXJtaW5lZCBieSBzaWdtYSBhbmQgbXUNCg0KeX5ub3JtYWwobXUsc2lnbWEpOw0KDQovLyBQcmlvcnMgZm9yIGJldGEgYW5kIHNpZ21hDQoNCmJldGEgfiBub3JtYWwoMCwxMCk7DQpzaWdtYX5jYXVjaHkoMCw1KTsNCn0NCg0KLy8gU3VwcCB2YWx1ZXMgYmxvY2sNCg0KZ2VuZXJhdGVkIHF1YW50aXRpZXMgew0KdmVjdG9yW25dIGxvZ19saWs7DQoNCmZvciAoaSBpbiAxOm4pIHsNCmxvZ19saWtbaV0gPSBub3JtYWxfbHBkZih5W2ldIHwgbXVbaV0sIHNpZ21hKTsgDQp9DQp9DQoiDQpgYGANCg0KU1RBTiBjb2RlIMSRxrDhu6NjIGzGsHUgZMaw4bubaSBk4bqhbmcgY2h14buXaSBrw60gdOG7sSAoc3RyaW5nKSwNCg0KbW9kZWwudGV4dCA8LSDigJzigKYuLuKAnQ0KDQpN4buZdCBjaMawxqFuZyB0csOsbmggU1RBTiDEkcaw4bujYyBjaGlhIHRoZW8gY+G6pXUgdHLDumMgdOG7q25nIGto4buRaSAoYmxvY2spIHbDoCBt4buXaSBibG9jayBjw7MgdmFpIHRyw7Iga2jDoWMgbmhhdToNCg0KMSkgQmxvY2sgZGF0YTogVMOqbiBn4buNaSwgbG/huqFpIHbDoCBrw61jaCB0aMaw4bubYyBj4bunYSB04burbmcgcGjhuqduIHRyb25nIGThu68gbGnhu4d1IMSR4bqndSB2w6BvDQoNCiArIG4gbMOgIG3hu5l0IHPhu5Egbmd1ecOqbiBkxrDGoW5nIGNo4buJIGPhu6EgbeG6q3UNCg0KICsgblggbMOgIDEgc+G7kSBuZ3V5w6puIGTGsMahbmcsIGNo4buJIHPhu5EgcHJlZGljdG9ycyB0cm9uZyBkZXNpZ24gbWF0cml4IGPhu6dhIG3DtCBow6xuaCAobMawdSDDvToga2jDtG5nIGPDsyBpbnRlcmNlcHQsIERpYWdub3N0aWMgYuG7iyBjaGlhIHRow6BuaCAzIGR1bW15IHZhcmlhYmxlcykNCiANCiArICB5IGzDoCAgYmnhur9uIGvhur90IHF14bqjIChUaGlja25lc3MpLCAxIHZlY3RvciBz4buRIHRo4buxYyBjw7MgxJHhu5kgZMOgaSA9IG4NCg0KICsgIFggbMOgIDEgZGVzaWduIG1hdHJpeCAgY8OzIGvDrWNoIHRoxrDhu5tjIChuLG5YKSA6IGzGsHUgw707IHTDrW5oIGvhur8gdGjhu6thIGPhu6dhIFNUQU4gY29kZSwgYmnhur9uIHNhdSBz4butIGThu6VuZyB0aMO0bmcgdGluIGPhu6dhIGJp4bq/biB0csaw4bubYyDEkcOjIMSRxrDhu6NjIGtoYWkgYsOhbw0KDQoyKSBCbG9jayBwYXJhbWV0ZXI6DQoNCk5oxrAgdMOqbiBn4buNaSBj4bunYSBuw7MsIMSRw6J5IGzDoCBuxqFpIGtoYWkgYsOhbyB24buBIGPDoWMgdGhhbSBz4buRIG3DoCB0YSBj4bqnbiB0cm9uZyBtw7QgaMOsbmguIE5oxrAgxJHDoyB0csOsbmggYsOgeSwgdGEgY8OzIDIgbG/huqFpIHRoYW0gc+G7kTogYmV0YSAodHJ1bmcgYsOsbmggbeG7l2kgcGjDom4gbmjDs20sIGhheSB0aGFtIHPhu5EgaOG7k2kgcXV5IHRyb25nIG3DtCBow6xuaCksIGzDoCBt4buZdCB2ZWN0b3Igc+G7kSB0aOG7sWMgIGPDsyBrw61jaCB0aMaw4bubYyBuWCA9IHPhu5EgY+G7mXQgdHJvbmcgZGVzaWduIG1hdHJpeDsgY8OybiBzaWdtYSBsw6Agc2QgY+G7p2EgcmVzaWR1YWwsIGzDoCAxIHPhu5EgdGjhu7FjLg0KDQozKSBCbG9jayBUcmFuc2Zvcm1lZCBwYXJhbWV0ZXJzOg0KDQpCbG9jayBuw6B5IGtow7RuZyBi4bqvdCBideG7mWMgcGjhuqNpIGPDsyBjaG8gbeG7jWkgbcO0IGjDrG5oIHbDrCBuw7MgdMO5eSB0aHXhu5ljIHbDoG8gbeG7pWMgxJHDrWNoIGPhu6dhIG5nxrDhu51pIHZp4bq/dCBjb2RlIHbDoCDEkeG7mSBwaOG7qWMgdOG6oXAgY+G7p2EgbcO0IGjDrG5oLiBD4bqnbiBjaMO6IMO9IGzDoCBt4buNaSB0aGFtIHPhu5EgdOG6oW8gcmEgdHJvbmcgYmxvY2sgbsOgeSBjxaluZyBz4bq9IMSRxrDhu6NjIGzhuqV5IG3huqt1IGNobyBjaHXhu5dpIE1DTUMgdsOgIHh14bqldCByYSBr4bq/dCBxdeG6oyBtw7QgaMOsbmggbmjGsCBjw6FjIHRoYW0gc+G7kSB0cm9uZyBibG9jayBwYXJhbWV0ZXIuDQoNClRyb25nIGJsb2NrIG7DoHksIHRhIGtoYWkgYsOhbyB0aGFtIHPhu5EgTXUgbMOgIHRydW5nIGLDrG5oIGThu7EgYsOhbyBj4bunYSBZLCB4w6FjIMSR4buLbmggdOG7qyBiZXRhIHbDoCBkZXNpZ24gbWF0cml4IFguDQoNCjQpIEJsb2NrIE1vZGVsOiBUcm9uZyBibG9jayBuw6B5IHRhIHPhur0ga2hhaSBiw6FvIHbhu4EgdGjDoG5oIHBo4bqnbiBsaWtlbGlob29kIHbDoCBwcmlvcnMgKGdp4bqjIHRodXnhur90IHRp4buBbiDEkeG7i25oIHbhu4EgcGjDom4gcGjhu5FpIGPhu6dhIHThu6tuZyB0aGFtIHPhu5EpLg0KDQpUcm9uZyBtw7QgaMOsbmggbsOgeSwgbGlrZWxpaG9vZCBsw6AgeSB+IG5vcm1hbCAobXUsIHNpZ21hKTsgDQoNClRhIGPDsyAyIHRoYW0gc+G7kSBsw6AgYmV0YSB2w6AgU2lnbWEuIE7hur91IHRhIGtow7RuZyBraGFpIGLDoW8gcHJpb3IsIFNUQU4gc+G6vSBt4bq3YyDEkeG7i25oIGTDuW5nIHByaW9yIFVuaWZvcm0gY2hvIHRoYW0gc+G7kSDEkcOzLiDhu54gxJHDonkgdGEgZMO5bmcgcHJpb3Ig4oCcbm9uIGluZm9ybWF0aXZl4oCdIGzDoCBoYWxmLUNhdWNoeSBjaG8gc2lnbWEsIHbDoCAxIGdp4bqjIHRodXnhur90IHRp4buBbiDEkeG7i25oIHLhurFuZyBiZXRhIGPDsyBwaMOibiBwaOG7kWkgY2h14bqpbiwgZGFvIMSR4buZbmcgcXVhbmggdHJ1bmcgYsOsbmggPSAwIHbDoCBzZD0xMCkuDQoNCjUpIE5nb8OgaSByYSwgdGEgY8OybiBjw7MgdGjhu4MgZMO5bmcgdGjDqm0gYmxvY2sgdGjhu6kgNSDEkeG7gyBs4bqleSBt4bqrdSBjaG8gY8OhYyB0aGFtIHPhu5EgcGjhu6UgaGF5IHBow6FpIHNpbmggdOG7qyBjw6FjIHBhcmFtZXRlciBjw7Mgc+G6tW4gdHJvbmcgbcO0IGjDrG5oOyB0dXkgbmhpw6puIHZp4buHYyBuw6B5IGtow7RuZyB0aOG7sWMgc+G7sSBxdWFuIHRy4buNbmcsIG5o4bqldCBsw6Agdmnhu4djIHTDrW5oIHRvw6FuIG5oaeG7gXUgdGhhbSBz4buRIGLhurFuZyBjw6FjaCBs4bqleSBt4bqrdSBz4bq9IGzDoG0gcXV5IHRyw6xuaCB04bqhbyBNQ01DIHF1w6EgdOG6o2kgbeG7mXQgY8OhY2gga2jDtG5nIGPhuqduIHRoaeG6v3QuDQoNCg0KU2F1IGtoaSB2aeG6v3QgeG9uZyBTVEFOIGNvZGUsIHRhIHPhur0gdOG6oW8gZGVzaWduIG1hdHJpeCBjaG8gbcO0IGjDrG5oIHbDoCBkYXRhIGNobyBtw7QgaMOsbmggbMOgIDEgbGlzdA0KDQpgYGB7cixtZXNzYWdlID0gRkFMU0Usd2FybmluZz1GQUxTRX0NClhtYXQgPC0gbW9kZWwubWF0cml4KH5CTUkrRGlhZ25vc3RpYy0xLCBkYXRhKQ0KDQpkYXRhX2xpc3QgPC0gd2l0aChkYXRhLCBsaXN0KHkgPSBUaGlja25lc3MsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgWCA9IFhtYXQsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgblggPSBuY29sKFhtYXQpLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgIG4gPSBucm93KGRhdGEpKSkNCg0KZGF0YV9saXN0DQpgYGANCg0KVGEgxJHGsGEgZGF0YSB2w6BvIG3DtCBow6xuaCB2w6Aga8OtY2ggaG/huqF0IHF1eSB0csOsbmggbOG6pXkgbeG6q3UgTUNNQyB24bubaSBjw6FjIHTDuXkgY2jhu4luaCBUYSBz4bq9IGNo4bqheSAyIGNodeG7l2kgc29uZyBzb25nOiBUcsaw4bubYyBo4bq/dCBzYW1wbGVyIHPhur0gY2jhuqF5IDUwMCBsxrDhu6N0IGto4bufaSDEkeG7mW5nLCBraGkg4buVbiDEkeG7i25oIHRow6wga+G6v3QgcXXhuqMgTUNNQyBi4bqvdCDEkeG6p3UgxJHGsOG7o2MgZ2hpIGzhuqFpIDIwMDAgbMaw4bujdCBjaG8gbeG7l2kgY2h14buXaSxyw7p0IGfhu41uIDEvMiBrw61jaCB0aMaw4bubYyBt4buXaSBjaHXhu5dpIHbDoCBjaOG6oXkgc29uZyBzb25nIHRyw6puIHThu5FpIMSRYSA0IGNvcmVzIGPhu6dhIG3DoXkgdMOtbmguDQoNCmBgYHtyLG1lc3NhZ2UgPSBGQUxTRSx3YXJuaW5nPUZBTFNFfQ0KbGlicmFyeShyc3RhbikNCg0KcnN0YW5fb3B0aW9ucyhhdXRvX3dyaXRlID0gVFJVRSkNCm9wdGlvbnMobWMuY29yZXMgPSBwYXJhbGxlbDo6ZGV0ZWN0Q29yZXMoKSkNCg0Kc2V0LnNlZWQoMTIzKQ0KDQpmaXQ9IHN0YW4oZGF0YSA9IGRhdGFfbGlzdCwgDQogICAgICAgICAgbW9kZWxfY29kZSA9IG1vZGVsX2NvZGUgLCANCiAgICAgICAgICBjaGFpbnMgPSAyLA0KICAgICAgICAgIGl0ZXIgPSAyNTAwLCANCiAgICAgICAgICB3YXJtdXAgPSA1MDAsIA0KICAgICAgICAgIHRoaW4gPSAyKQ0KDQpgYGANCg0KTcO0IGjDrG5oIGNvbnZlcmdlIHLhuqV0IG5oYW5oLCBr4bq/dCBxdeG6oyB4deG6pXQgcmEgbMOgIG5o4buvbmcgY2h14buXaSBNQ01DIGNobyA0IHRoYW0gc+G7kSBiZXRhICh0xrDGoW5nIOG7qW5nIHRydW5nIGLDrG5oIGPhu6dhIDMgcGjDom4gbmjDs20gdsOgIGJldGEgY2hvIEJNSSksIDEgdGhhbSBz4buRIHNpZ21hIHbDoCAzOCBnacOhIHRy4buLIGThu7EgYsOhbyBjaG8gTXUgY+G7p2EgdOG7q25nIGPDoSB0aOG7gw0KDQpUYSB0w7NtIHThuq90IGvhur90IHF14bqjIGPhu6dhIDQgdGhhbSBz4buRIGJldGEgdsOgIHRoYW0gc+G7kSBzaWdtYSBuaMawIHNhdToNCg0KYGBge3IsbWVzc2FnZSA9IEZBTFNFLHdhcm5pbmc9RkFMU0V9DQpsaWJyYXJ5KGJyb29tKQ0KdGlkeU1DTUMoZml0LCBjb25mLmludCA9IFRSVUUsIGNvbmYubWV0aG9kID0gIkhQRGludGVydmFsIiwgcGFycyA9IGMoImJldGEiLCAic2lnbWEiKSkNCg0KYGBgDQoNClRhIGto4bqjbyBzw6F0IHRy4buxYyBxdWFuIHBow6JuIHBo4buRaSBo4bqtdSBuZ2hp4buHbSBj4bunYSBiZXRhIGNobyAzIHBow6JuIG5ow7NtDQoNCmBgYHtyLG1lc3NhZ2UgPSBGQUxTRSx3YXJuaW5nPUZBTFNFfQ0KcG9zdGRmPWZpdCU+JWFzLmRhdGEuZnJhbWUoKSU+JQ0KICBhc190aWJibGUoKSU+JS5bLGMoMTo1KV0lPiUNCiAgbXV0YXRlKC4sSXRlcmF0aW9uPWFzLm51bWVyaWMocmVwKGMoMToxMDAwKSwyKSksDQogICAgICAgICBDaGFpbj1hcy5mYWN0b3IocmVwKGMoMToyKSxlYWNoPTEwMDApKSkNCg0KY29sbmFtZXMocG9zdGRmKT1jKCJCTUkiLCJDb250cm9sIiwiRW1waHlzZW1hIiwiRmlicm9zaXMiLCJTaWdtYSIsIkl0ZXJhdGlvbiIsIkNoYWluIikNCg0KcG9zdGRmJFBzZXVkbz1mYWN0b3IocmVwKGMoMToxMCksZT1ucm93KHBvc3RkZikvMTApKQ0KDQpsaWJyYXJ5KGdncmlkZ2VzKQ0KDQpwb3N0ZGYlPiVnYXRoZXIoQ29udHJvbCxFbXBoeXNlbWEsRmlicm9zaXMsDQogICAgICAgICAgICAgICAga2V5PSJQcmVkaWN0b3JzIix2YWx1ZT0iRWZmZWN0IiklPiUNCiAgZ2dwbG90KCkrDQogIGdlb21fZGVuc2l0eV9yaWRnZXNfZ3JhZGllbnQoYWVzKHk9cmVvcmRlcihQcmVkaWN0b3JzLEVmZmVjdCksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHg9RWZmZWN0LGZpbGw9LS4ueC4uKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsaW5ldHlwZT0xLGNvbD0iYmxhY2siLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFscGhhPTAuNixzY2FsZT0xLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNob3cubGVnZW5kID0gRikrDQogIGdlb21fdmxpbmUoeGludGVyY2VwdD1hZGpfZGYkZml0WzFdLGNvbD0icmVkMyIsbGluZXR5cGU9MikrDQogIHRoZW1lX2J3KCkrDQogIHNjYWxlX3lfZGlzY3JldGUoIkZhY3RvcnMiKSsNCiAgc2NhbGVfeF9jb250aW51b3VzKCJFc3RpbWF0ZWQgVGhpY2tuZXNzIikrDQogIHNjYWxlX2ZpbGxfZ3JhZGllbnQobG93PSIjZjkwMDQ2IiwNCiAgICAgICAgICAgICAgICAgICAgICAgaGlnaD0iI2Y5Y2MwMCIpKw0KICBjb29yZF9mbGlwKCkNCg0KcG9zdGRmJT4lZ2F0aGVyKENvbnRyb2wsRW1waHlzZW1hLEZpYnJvc2lzLA0KICAgICAgICAgICAgICAgIGtleT0iUHJlZGljdG9ycyIsdmFsdWU9IkVmZmVjdCIpJT4lDQogIGdncGxvdCgpKw0KICBnZW9tX2RlbnNpdHlfcmlkZ2VzKGFlcyh5PXJlb3JkZXIoUHJlZGljdG9ycyxFZmZlY3QpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICB4PUVmZmVjdCxjb2w9UHNldWRvKSwNCiAgICAgICAgICAgICAgICAgICAgICBsaW5ldHlwZT0xLGZpbGw9InJlZCIsDQogICAgICAgICAgICAgICAgICAgICAgYWxwaGE9MC4wMyxzY2FsZT0xLA0KICAgICAgICAgICAgICAgICAgICAgIHNob3cubGVnZW5kID0gRikrDQogIGdlb21fdmxpbmUoeGludGVyY2VwdD1hZGpfZGYkZml0WzFdLGNvbD0icmVkMyIsbGluZXR5cGU9MikrDQogIHRoZW1lX2J3KCkrDQogIHNjYWxlX3lfZGlzY3JldGUoIkZhY3RvcnMiKSsNCiAgc2NhbGVfeF9jb250aW51b3VzKCJFc3RpbWF0ZWQgVGhpY2tuZXNzIikrDQogIGNvb3JkX2ZsaXAoKSsgDQogIHNjYWxlX2NvbG91cl9icmV3ZXIocGFsZXR0ZSA9ICJSZWRzIikNCmBgYA0KDQpIb+G6t2MgcGjhuqltIGNo4bqldCBj4bunYSBjw6FjIGNodeG7l2kgTUNNQzoNCg0KYGBge3IsbWVzc2FnZSA9IEZBTFNFLHdhcm5pbmc9RkFMU0V9DQpwMT1wb3N0ZGYlPiVnYXRoZXIoQk1JOlNpZ21hLGtleT0iRmFjdG9yIix2YWx1ZT0iRWZmZWN0IiklPiUNCiAgZ2dwbG90KGFlcyh4PUVmZmVjdCxmaWxsPUNoYWluLGNvbD1DaGFpbikpKw0KICBnZW9tX2RlbnNpdHkoYWxwaGE9MC4zLHNob3cubGVnZW5kID0gRikrDQogIGZhY2V0X3dyYXAofkZhY3RvcixuY29sPTEsc2NhbGU9ImZyZWVfeSIpKw0KICB0aGVtZV9idyg4KQ0KDQpwMj1wb3N0ZGYlPiVnYXRoZXIoQk1JOlNpZ21hLGtleT0iRmFjdG9yIix2YWx1ZT0iRWZmZWN0IiklPiUNCiAgZ2dwbG90KGFlcyh5PUVmZmVjdCx4PUl0ZXJhdGlvbixjb2w9Q2hhaW4pKSsNCiAgZ2VvbV9wYXRoKGFscGhhPTAuNSxzaG93LmxlZ2VuZCA9IEYpKw0KICBmYWNldF93cmFwKH5GYWN0b3IsbmNvbD0xLHNjYWxlPSJmcmVlX3kiKSsNCiAgdGhlbWVfYncoOCkNCg0KZ3JpZEV4dHJhOjpncmlkLmFycmFuZ2UocDIscDEsbmNvbD0yKQ0KDQpgYGANCg0KVOG7qyBtw7QgaMOsbmggQmF5ZXMsIHRhIGPDsyB0aOG7gyB0csOsbmggYsOgeSBNYXJnaW5hbCBlZmZlY3QgY+G7p2EgQk1JIMSRaeG7gXUga2nhu4duIGjDs2EgdGhlbyBwaMOibiBuaMOzbToNCg0KYGBge3IsbWVzc2FnZSA9IEZBTFNFLHdhcm5pbmc9RkFMU0V9DQpsaWJyYXJ5KGNvZGEpDQoNCm1jbWMgPSBhcy5tYXRyaXgoZml0KQ0KDQpuZXdkYXRhPWV4cGFuZC5ncmlkKEJNSSA9IHNlcShtaW4oZGF0YSRCTUkpLCBtYXgoZGF0YSRCTUkpLGxlbj0xMDApLA0KICAgICAgICAgICAgICAgICAgICBHcm91cCA9IGxldmVscyhkYXRhJERpYWdub3N0aWMpKQ0KDQpYbWF0MiA9IG1vZGVsLm1hdHJpeCh+Qk1JICsgR3JvdXAtMSwgbmV3ZGF0YSkNCmNvZWZzID0gbWNtY1ssIGMoImJldGFbMV0iLCAiYmV0YVsyXSIsICJiZXRhWzNdIiwgImJldGFbNF0iKV0NCnByZWQgPSBjb2VmcyAlKiUgdChYbWF0MikgDQoNCm5ld2RhdGEgPSBuZXdkYXRhICU+JSBjYmluZCh0aWR5TUNNQyhwcmVkLCBjb25mLmludCA9IFRSVUUsIGNvbmYubWV0aG9kID0gIkhQRGludGVydmFsIikpDQpgYGANCg0KDQpgYGB7cixtZXNzYWdlID0gRkFMU0Usd2FybmluZz1GQUxTRX0NCm5ld2RhdGElPiUNCiAgZ2dwbG90KGFlcyh4PUJNSSwNCiAgICAgICAgICAgICB5PWVzdGltYXRlLA0KICAgICAgICAgICAgIGZpbGw9R3JvdXAsDQogICAgICAgICAgICAgY29sPUdyb3VwKSkrDQogIGdlb21fcmliYm9uKGFlcyh5bWluID0gY29uZi5sb3csDQogICAgICAgICAgICAgICAgICB5bWF4ID0gY29uZi5oaWdoKSwgYWxwaGEgPSAwLjIsY29sPU5BKSsgDQogIGdlb21fbGluZSgpKw0KICBnZW9tX3Ntb290aChhbHBoYT0wLjUpKw0KICBzY2FsZV95X2NvbnRpbnVvdXMoIlRoaWNrbmVzcyIpKw0KICB0aGVtZV9idygpKw0KICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzPWMoIiMwMjFjZTUiLCIjMzUwMDkxIiwiI2Q4MDIxZSIpKSsNCiAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzPWMoIiMxNjc3ZmYiLCIjYTYxNmZmIiwiI2ZmMTY1NCIpKQ0KYGBgDQoNCk3DtCBow6xuaCBuw6B5IGPDsyBraOG6oyBuxINuZyBnaeG6o2kgdGjDrWNoIMSR4bq/biA4My40OCAlIHBoxrDGoW5nIHNhaSBj4bunYSBUaGlja25lc3MgKENpPTAuNzg1IMSR4bq/biAwLjg2NykNCg0KYGBge3IsbWVzc2FnZSA9IEZBTFNFLHdhcm5pbmc9RkFMU0V9DQptY21jIDwtIGFzLm1hdHJpeChmaXQpDQpYbWF0ID0gbW9kZWwubWF0cml4KH5CTUkrRGlhZ25vc3RpYy0xLGRhdGEpDQp3Y2ggPSBncmVwKCJiZXRhIiwgY29sbmFtZXMobWNtYykpDQpjb2VmcyA9IG1jbWNbLCB3Y2hdDQpwcmVkID0gY29lZnMgJSolIHQoWG1hdCkNCnJlc2lkID0gc3dlZXAocHJlZCwgMiwgZGF0YSRUaGlja25lc3MsICItIikNCnZhcl9mID0gYXBwbHkocHJlZCwgMSwgdmFyKQ0KdmFyX2UgPSBhcHBseShyZXNpZCwgMSwgdmFyKQ0KUjIgPSB2YXJfZi8odmFyX2YgKyB2YXJfZSkNCnRpZHlNQ01DKGFzLm1jbWMoUjIpLCBjb25mLmludCA9IFRSVUUsIGNvbmYubWV0aG9kID0gIkhQRGludGVydmFsIikNCg0KYGBgDQoNCg0KVGnhur9wIHRoZW8sIE5oaSBz4bq9IGzDoG0gMSBwaMOibiB0w61jaCBwb3N0LWhvYyB0aGVvIEJheWVzLiBO4buZaSBkdW5nIGPhu6dhIHF1eSB0csOsbmggbsOgeSBsw6AgdOG6oW8gcmEgY8OhYyBjaHXhu5dpIE1DTUMgY2hvIGtow6FjIGJp4buHdCBi4bqvdCBj4bq3cCB0deG6p24gdOG7sSBnaeG7r2EgMyBwaMOibiBuaMOzbQ0KDQpgYGB7cixtZXNzYWdlID0gRkFMU0Usd2FybmluZz1GQUxTRX0NCnBvc3Rob2NkZj1kYXRhLmZyYW1lKA0KICBDdnNFPXJlcChOQSwyMDAwKSwNCiAgQ3ZzRj1yZXAoTkEsMjAwMCksDQogIEV2c0Y9cmVwKE5BLDIwMDApDQopDQoNCmZvciAoaSBpbiAxOjIwMDApIHsNCiAgcG9zdGhvY2RmJEN2c0VbaV09cG9zdGRmJENvbnRyb2xbaV0tcG9zdGRmJEVtcGh5c2VtYVtpXQ0KICBwb3N0aG9jZGYkQ3ZzRltpXT1wb3N0ZGYkRmlicm9zaXNbaV0tcG9zdGRmJENvbnRyb2xbaV0NCiAgcG9zdGhvY2RmJEV2c0ZbaV09cG9zdGRmJEZpYnJvc2lzW2ldLXBvc3RkZiRFbXBoeXNlbWFbaV19DQpgYGANCg0KU2F1IMSRw7MgdGEgY8OzIHRo4buDIGto4bqjbyBzw6F0IG5o4buvbmcgY2h14buXaSBNQ01DIG7DoHkgdGhlbyBuaGnhu4F1IGPDoWNoLCBob+G6t2MgZMO5bmcgcGjhuqNuIG5naGnhu4dtICht4buZdCBsb+G6oWkgWi10ZXN0KSwgY8OzIHRo4buDIHh14bqldCByYSBnacOhIHRy4buLIHANCg0KYGBge3IsbWVzc2FnZSA9IEZBTFNFLHdhcm5pbmc9RkFMU0V9DQpsaWJyYXJ5KGNvZGEpDQptY21jcHZhbHVlIDwtIGZ1bmN0aW9uKHNhbXApIHsNCiAgaWYgKGxlbmd0aChkaW0oc2FtcCkpID09IDApIHsNCiAgICBzdGQgPC0gYmFja3NvbHZlKGNob2wodmFyKHNhbXApKSwgY2JpbmQoMCwgdChzYW1wKSkgLSBtZWFuKHNhbXApLA0KICAgICAgICAgICAgICAgICAgICAgdHJhbnNwb3NlID0gVFJVRSkNCiAgICBzcWRpc3QgPC0gY29sU3VtcyhzdGQgKiBzdGQpDQogICAgc3VtKHNxZGlzdFstMV0gPiBzcWRpc3RbMV0pL2xlbmd0aChzYW1wKQ0KICB9IGVsc2Ugew0KICAgIHN0ZCA8LSBiYWNrc29sdmUoY2hvbCh2YXIoc2FtcCkpLCBjYmluZCgwLCB0KHNhbXApKSAtIGNvbE1lYW5zKHNhbXApLA0KICAgICAgICAgICAgICAgICAgICAgdHJhbnNwb3NlID0gVFJVRSkNCiAgICBzcWRpc3QgPC0gY29sU3VtcyhzdGQgKiBzdGQpDQogICAgc3VtKHNxZGlzdFstMV0gPiBzcWRpc3RbMV0pL25yb3coc2FtcCkNCiAgfQ0KICANCn0NCg0KbWNtY3B2YWx1ZShwb3N0aG9jZGYkQ3ZzRSkNCm1jbWNwdmFsdWUocG9zdGhvY2RmJEN2c0YpDQptY21jcHZhbHVlKHBvc3Rob2NkZiRFdnNGKQ0KYGBgDQoNCkvhur90IHF14bqjIGNobyB0aOG6pXkgcF92YWx1ZSBjaG8gY+G6oyAzIGPhurdwIHNvIHPDoW5oIMSR4buBdSBy4bqldCB0aOG6pXAsIGNobyB0aOG6pXkga2jDoWMgYmnhu4d0IGPDsyDDvSBuZ2jEqWEgdGjhu5FuZyBrw6oNCg0KTeG7mXQgY8OhY2ggbMOgbSBraMOhYyBsw6Agc3V5IGRp4buFbiBi4bqxbmcgUk9QRSB2w6AgQ29tcFZhbCBuaMawIEtydXNjaGtlIMSRw6MgbMOgbSA6IEPDoWMgYuG6oW4geGVtIG5o4buvbmcgYsOgaSB0csaw4bubYyDEkeG7gyBoaeG7g3UgY8ahIGNo4bq/IGPhu6dhIHBow6lwIHN1eSBkaeG7hW4gbsOgeS4gDQoNCmBgYHtyLG1lc3NhZ2UgPSBGQUxTRSx3YXJuaW5nPUZBTFNFfQ0KSERJRj0gZnVuY3Rpb24oIHNhbXBsZVZlYyxjcmVkTWFzcz0wLjk3NSApIHsNCiAgc29ydGVkUHRzID0gc29ydCggc2FtcGxlVmVjICkNCiAgY2lJZHhJbmMgPSBjZWlsaW5nKCBjcmVkTWFzcyAqIGxlbmd0aCggc29ydGVkUHRzICkgKQ0KICBuQ0lzID0gbGVuZ3RoKCBzb3J0ZWRQdHMgKSAtIGNpSWR4SW5jDQogIGNpV2lkdGggPSByZXAoIDAgLCBuQ0lzICkNCiAgZm9yICggaSBpbiAxOm5DSXMgKSB7DQogICAgY2lXaWR0aFsgaSBdID0gc29ydGVkUHRzWyBpICsgY2lJZHhJbmMgXSAtIHNvcnRlZFB0c1sgaSBdDQogIH0NCiAgSERJbWluID0gc29ydGVkUHRzWyB3aGljaC5taW4oIGNpV2lkdGggKSBdDQogIEhESW1heCA9IHNvcnRlZFB0c1sgd2hpY2gubWluKCBjaVdpZHRoICkgKyBjaUlkeEluYyBdDQogIEhESWxpbSA9IGMoIEhESW1pbiAsIEhESW1heCApDQogIHJldHVybiggSERJbGltICkNCn0NCg0KU1VNSz1mdW5jdGlvbihwYXJhbVNhbXBsZVZlYyxjb21wVmFsPU5VTEwgLCBST1BFPU5VTEwgLCBjcmVkTWFzcz0wLjk3NSkgew0KICBtZWFuUGFyYW0gPSBtZWFuKCBwYXJhbVNhbXBsZVZlYyApDQogIG1lZGlhblBhcmFtID0gbWVkaWFuKCBwYXJhbVNhbXBsZVZlYyApDQogIGRyZXMgPSBkZW5zaXR5KCBwYXJhbVNhbXBsZVZlYyApDQogIG1vZGVQYXJhbSA9IGRyZXMkeFt3aGljaC5tYXgoZHJlcyR5KV0NCiAgaGRpTGltID0gSERJRiggcGFyYW1TYW1wbGVWZWMgLCBjcmVkTWFzcz1jcmVkTWFzcyApDQogIGlmICggIWlzLm51bGwoY29tcFZhbCkgKSB7DQogICAgcGNndENvbXBWYWwgPSAoIDEwMCAqIHN1bSggcGFyYW1TYW1wbGVWZWMgPiBjb21wVmFsICkgDQogICAgICAgICAgICAgICAgICAgIC8gbGVuZ3RoKCBwYXJhbVNhbXBsZVZlYyApICkNCiAgfSBlbHNlIHsNCiAgICBjb21wVmFsPU5BDQogICAgcGNndENvbXBWYWw9TkENCiAgfQ0KICBpZiAoICFpcy5udWxsKFJPUEUpICkgew0KICAgIHBjbHRSb3BlID0gKCAxMDAgKiBzdW0oIHBhcmFtU2FtcGxlVmVjIDwgUk9QRVsxXSApIA0KICAgICAgICAgICAgICAgICAvIGxlbmd0aCggcGFyYW1TYW1wbGVWZWMgKSApDQogICAgcGNndFJvcGUgPSAoIDEwMCAqIHN1bSggcGFyYW1TYW1wbGVWZWMgPiBST1BFWzJdICkgDQogICAgICAgICAgICAgICAgIC8gbGVuZ3RoKCBwYXJhbVNhbXBsZVZlYyApICkNCiAgICBwY2luUm9wZSA9IDEwMC0ocGNsdFJvcGUrcGNndFJvcGUpDQogIH0gZWxzZSB7IA0KICAgIFJPUEUgPSBjKE5BLE5BKQ0KICAgIHBjbHRSb3BlPU5BIA0KICAgIHBjZ3RSb3BlPU5BIA0KICAgIHBjaW5Sb3BlPU5BIA0KICB9ICANCiAgcmV0dXJuKCBjKCBNZWFuPW1lYW5QYXJhbSAsIE1lZGlhbj1tZWRpYW5QYXJhbSAsIE1vZGU9bW9kZVBhcmFtICwgDQogICAgICAgICAgICAgSERJbGV2ZWw9Y3JlZE1hc3MgLCBMTD1oZGlMaW1bMV0gLCBVTD1oZGlMaW1bMl0gLCANCiAgICAgICAgICAgICBDb21wVmFsPWNvbXBWYWwgLCBQY250R3RDb21wVmFsPXBjZ3RDb21wVmFsICwgDQogICAgICAgICAgICAgUk9QRWxvdz1ST1BFWzFdICwgUk9QRWhpZ2g9Uk9QRVsyXSAsDQogICAgICAgICAgICAgUGNudEx0Uk9QRT1wY2x0Um9wZSAsIFBjbnRJblJPUEU9cGNpblJvcGUgLCBQY250R3RST1BFPXBjZ3RSb3BlICkgKQ0KfQ0KDQpzdW1tYXJ5S3J1c2Noa2U9ZnVuY3Rpb24oTUNNQyxjb21wVmFsPU5VTEwsIHJvcGU9TlVMTCxjcmVkTWFzcz1OVUxMKXsNCiAgc3VtbWFyeUluZm8gPSBOVUxMDQogIHN1bW1hcnlJbmZvID0gY2JpbmQoc3VtbWFyeUluZm8sICJFc3RpbWF0ZWQiPSBTVU1LKE1DTUMsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbXBWYWw9Y29tcFZhbCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgUk9QRT1yb3BlLGNyZWRNYXNzPWNyZWRNYXNzKSkNCiAgcmV0dXJuKHN1bW1hcnlJbmZvKQ0KfQ0KDQpsb25ncG9zdD1wb3N0aG9jZGYlPiVnYXRoZXIoQ3ZzRSxDdnNGLEV2c0Ysa2V5PSJQYWlycyIsdmFsdWU9IkRpZmZlcmVuY2UiKQ0KDQpsb25ncG9zdCU+JQ0KICBzcGxpdCguJFBhaXJzKSU+JQ0KICBtYXAofnN1bW1hcnlLcnVzY2hrZShNQ01DID0gLiREaWZmZXJlbmNlLA0KICAgICAgICAgICAgICAgICAgICAgICBjb21wVmFsPTAuMCwNCiAgICAgICAgICAgICAgICAgICAgICAgcm9wZT1jKDAsMC4xKSwNCiAgICAgICAgICAgICAgICAgICAgICAgY3JlZE1hc3M9MC45NzUpDQogICklPiVhcy5kYXRhLmZyYW1lKCklPiVhc190aWJibGUoKS0+a3J1c2Noa2UNCg0KY29sbmFtZXMoa3J1c2Noa2UpPXVuaXF1ZShsb25ncG9zdCRQYWlycykNCg0Ka3J1c2Noa2UlPiVrbml0cjo6a2FibGUoKQ0KYGBgDQoNClTDs20gdOG6r3Q6IENvbXBWYWwgbMOgIDEgbmfGsOG7oW5nIHbDtCBuZ2jEqWEsIHRow60gZOG7pSBraMOhYyBiaeG7h3QgPSAwOyBST1BFIGzDoCBt4buZdCBraG/huqNuZyB2w7QgbmdoxKlhLCBn4buTbSAyIMSR4bqndSwgdGjDrSBk4bulIDAgdsOgICswLjEuIEvhur90IHF14bqjIGNobyB0aOG6pXkgMTAwJSBwaMOibiBwaOG7kWkgaOG6rXUgbmdoaeG7h20gY+G7p2Ega2jDoWMgYmnhu4d0IGzhu5tuIGjGoW4gMCBjaG8gMiBj4bq3cCBDb250cm9sIHZzIEVtcGh5c2VtYSB2w6AgQ29udHJvbCB2cyBGaWJyb3NpcywgOTkuOTUlIHBow6JuIGLhu5EgaOG6rXUgbmdoaeG7h20gbOG7m24gaMahbiAwIGNobyBj4bq3cCBFbXBoeXNlbWEgdnMgRmlicm9zaXMuDQoNClTGsMahbmcgdOG7sTogMTAwJSBwaMOibiBi4buRIGjhuq11IG5naGnhu4dtIG7hurFtIG5nb8OgaSBST1BFIGNobyAyIGPhurdwIEMgdnMgRSwgQ3ZzIEY7IHbDoCA5OC40NSUgbuG6sW0gbmdvw6BpIFJPUEUgY2hvIGPhurdwIEUgdnNGLg0KDQrEkMahbiBnaeG6o24gaMahbiBsw6AgbcO0IHThuqMgdHLhu7FjIHF1YW4gcGjDom4gcGjhu5FpIGjhuq11IG5naGnhu4dtIGPhu6dhIDMgY+G6t3Agc28gc8OhbmgsIG5nxrDhu51pIMSR4buNYyBjw7MgdGjhu4MgdOG7sSBj4bqjbSBuaOG6rW4gbeG7qWMgxJHhu5kga2jhuqMgdMOtbiBzbyB24bubaSBuZ8aw4buhbmcgMA0KDQpgYGB7cixtZXNzYWdlID0gRkFMU0Usd2FybmluZz1GQUxTRX0NCmxvbmdwb3N0JT4lDQogIGdncGxvdCgpKw0KICBnZW9tX2RlbnNpdHlfcmlkZ2VzX2dyYWRpZW50KGFlcyh5PXJlb3JkZXIoUGFpcnMsRGlmZmVyZW5jZSksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHg9RGlmZmVyZW5jZSxmaWxsPS0uLnguLiksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGluZXR5cGU9MSxjb2w9ImJsYWNrIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhbHBoYT0wLjYsc2NhbGU9MSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzaG93LmxlZ2VuZCA9IEYpKw0KICBnZW9tX3ZsaW5lKHhpbnRlcmNlcHQ9MCxjb2w9InJlZDMiLGxpbmV0eXBlPTIpKw0KICB0aGVtZV9idygpK3NjYWxlX3lfZGlzY3JldGUoIlBhaXJzIikrc2NhbGVfeF9jb250aW51b3VzKCJEaWZmZXJlbmNlIikrDQogIHNjYWxlX2ZpbGxfZ3JhZGllbnQobG93PSIjZjkwMDQ2IixoaWdoPSIjZjljYzAwIikrY29vcmRfZmxpcCgpDQoNCg0KYGBgDQoNCkN14buRaSBjw7luZyBsw6Agc3V5IGRp4buFbiBi4bqxbmcgQmF5ZXMgRmFjdG9yIGhheSB04buJIHRy4buNbmcgY2jhu6luZyBj4bupIHBo4bunIHF1eeG6v3QgY2hvIDEgbmfGsOG7oW5nIHbDtCBuZ2jEqWEgbmjhuqV0IMSR4buLbmggdOG7qyAwIMSR4bq/biAwLjcNCg0KYGBge3IsbWVzc2FnZSA9IEZBTFNFLHdhcm5pbmc9RkFMU0V9DQp0aHJlc2hvbGQ9cmVwKE5BLDMzKQ0KQmF5ZXNGYWN0b3I9cmVwKE5BLDMzKQ0KUGFpcj1yZXAoTkEsMzMpDQoNCnRocmVzPWMoMCwwLjA1LDAuMSwwLjE1LDAuMiwwLjI1LDAuMywwLjQsMC41LDAuNiwwLjcpDQoNCnBhaXJsZXY9dW5pcXVlKGxvbmdwb3N0JFBhaXJzKQ0KDQpuPTANCg0KZm9yKGkgaW4gKDE6Mykpew0KICBmb3IgKGogaW4gKDE6MTEpKXsNCiAgICB0ZW1wZGY9c3Vic2V0KGxvbmdwb3N0LFBhaXJzPT1wYWlybGV2W2ldKQ0KICAgIFBhaXJbbitqXT1wYWlybGV2W2ldDQogICAgdGhyPXRocmVzW2pdDQogICAgdGhyZXNob2xkW24ral09dGhyDQogICAgaHlwPXBhc3RlKCJEaWZmZXJlbmNlPiIsdGhyLHNlcD0iIikNCiAgICBiZj1icm1zOjpoeXBvdGhlc2lzKHRlbXBkZixoeXAsYWxwaGE9MC4wNSkNCiAgICBCYXllc0ZhY3RvcltuK2pdPWJmJGh5cG90aGVzaXMkRXZpZC5SYXRpbw0KICB9DQogIG49bisxMQ0KfQ0KDQp0ZW1wYmZkZj1jYmluZChQYWlyLHRocmVzaG9sZCxCYXllc0ZhY3RvciklPiVhc190aWJibGUoKQ0KdGVtcGJmZGYkdGhyZXNob2xkPWFzLm51bWVyaWModGVtcGJmZGYkdGhyZXNob2xkKQ0KdGVtcGJmZGYkQmF5ZXNGYWN0b3I9YXMubnVtZXJpYyh0ZW1wYmZkZiRCYXllc0ZhY3RvcikNCg0KdGVtcGJmZGYlPiUNCiAgZ2dwbG90KGFlcyh4PXRocmVzaG9sZCx5PUJheWVzRmFjdG9yLGZpbGw9QmF5ZXNGYWN0b3IpKSsNCiAgZ2VvbV9wYXRoKCkrDQogIGdlb21fcG9pbnQoc2hvdy5sZWdlbmQgPSBGLHNpemU9MyxzaGFwZT0yMSxjb2w9ImJsYWNrIikrDQogIHRoZW1lX2J3KDEwKSsNCiAgZ2VvbV90ZXh0KGFlcyhsYWJlbD1yb3VuZCh0ZW1wYmZkZiRCYXllc0ZhY3RvciwyKSksY29sPSJibGFjayIsc2hvdy5sZWdlbmQgPSBGLA0KICAgICAgICAgICAgYW5nbGUgPSA1MCxudWRnZV95PUJheWVzRmFjdG9yKzMwMCxudWRnZV94PTAsc2l6ZT0zKSsNCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID01MDAsbGluZXR5cGU9Mixjb2w9InJlZCIpKw0KICBmYWNldF93cmFwKH5QYWlyLG5jb2w9MSxzY2FsZXMgPSAiZnJlZSIpKw0KICAgIHNjYWxlX2ZpbGxfZ3JhZGllbnQobG93PSIjZjkwMDQ2IixoaWdoPSIjZjljYzAwIikNCg0KdGVtcGJmZGYlPiVrbml0cjo6a2FibGUoKQ0KYGBgDQoNCiMgRGnhu4VuIMSR4bqhdCB2xINuIGLhuqNuIGtob2EgaOG7jWMNCg0KUGjhuqduIG7DoHkgbmjhurFtIHRyw6xuaCBiw6B5IG3hu5l0IHbEg24gYuG6o24ga2hvYSBo4buNYyBt4bqrdSBjaG8gcGjDom4gdMOtY2ggQU5DT1ZBIHRoZW8gdHLGsOG7nW5nIHBow6FpIEJheWVzLiANCg0KUGjGsMahbmcgcGjDoXA6IE3hu5l0IG3DtCBow6xuaCBBTkNPVkEgxJHGsOG7o2MgdGjhu7FjIGhp4buHbiBuaOG6sW0ga2jhuqNvIHPDoXQgdGhheSDEkeG7lWkgY+G7p2EgYuG7gSBkw6B5IG3DoG5nIFBOTU0g4bufIGPDoWMgYuG7h25oIG5ow6JuIGtow60gcGjhur8gdGjFqW5nIHbDoCB4xqEgcGjhu5VpIHNvIHbhu5tpIG5ow7NtIGNo4bupbmcsIHNhdSBraGkgaGnhu4d1IGNo4buJbmggY2hvIGhp4buHdSDhu6luZyBj4bunYSBCTUkuIEPDoWMgdGhhbSBz4buRIHRyb25nIG3DtCBow6xuaCDEkcaw4bujYyB4w6FjIMSR4buLbmggYuG6sW5nIHBoxrDGoW5nIHBow6FwIEJheWVzIHbhu5tpIGdp4bqjIMSR4buLbmgga+G6v3QgcXXhuqMgY8OzIHBow6JuIGLhu5EgY2h14bqpbix2w6AgcHJpb3IgY2hvIE11IHbDoCBzaWdtYSBs4bqnbiBsxrDhu6N0IGzDoCBOb3JtYWwoMCwxMCkgdsOgIENhdWNoeSAoMCw1KS4gU+G7sSBraMOhYyBiaeG7h3QgZ2nhu69hIGPDoWMgcGjDom4gbmjDs20gxJHGsOG7o2Mga2nhu4NtIMSR4buLbmggYuG6sW5nIHBoxrDGoW5nIHBow6FwIHN1eSBkaeG7hW4gQmF5ZXMgdGhlbyBKb2huIEtydXNjaGtlIHbDoCBCYXllcyBGYWN0b3IuDQoNCkvhur90IHF14bqjOg0KDQpUaMSDbSBkw7IgZOG7ryBsaeG7h3UgY2hvIHRo4bqleSBi4buBIGTDoHkgbcOgbmcgcGjhur8gbmFuZyBjw7MgcXVhbiBo4buHIHR1eeG6v24gdMOtbmggduG7m2kgQk1JIG5oxrBuZyBraMO0bmcgY8OzIGhp4buHdSDhu6luZyB0xrDGoW5nIHTDoWMgZ2nhu69hIEJNSSB2w6AgcGjDom4gbmjDs20gYuG7h25oIGzDvS4gU2F1IGtoaSBoaeG7h3UgY2jhu4luaCBjaG8gQk1JLCBr4bq/dCBxdeG6oyBtw7QgaMOsbmggQU5DT1ZBIEJheWVzIGNobyB0aOG6pXkgY8OzIHPhu7EgdGhheSDEkeG7lWkgw70gbmdoxKlhIHbhu4EgYuG7gSBkw6B5IG3DoG5nIHBo4bq/IG5hbmcg4bufIGLhu4duaCBuaMOibiBjw7MgYuG7h25oIGjDtCBo4bqlcCBzbyB24bubaSBuaMOzbSBjaOG7qW5nLiBD4bulIHRo4buDLCBi4buHbmggS2jDrSBwaOG6vyB0aMWpbmcgbMOgbSBtw6BuZyBwaOG6vyBuYW5nIG3hu49uZyDEkWkgdHJ1bmcgYsOsbmggMC4zMiDCtW0gKEhESTotMC4yMCDEkeG6v24gMC40MSDCtW07IEJheWVzRmFjdG9yPjEwMCBjaG8gbmfGsOG7oW5nIHbDtCBoaeG7h3UgPSAtMC4yMCDCtW0pLCB0cm9uZyBraGkgYuG7h25oIHjGoSBwaOG7lWkgbMOgbSBtw6BuZyBwaOG6vyBuYW5nIGTDoHkgdGjDqm0gdHJ1bmcgYsOsbmggMC4yNCDCtW0gKEhESTowLjEwIMSR4bq/biAwLjM4IMK1bTsgQmF5ZXNmYWN0b3IgPTYzIHbDoCA0OTkgdMawxqFuZyDhu6luZyB24bubaSBuZ8aw4buhbmcgdsO0IGhp4buHdSA9ICswLjEgdsOgICswLjA1IMK1bSkuIA0KDQojIEvhur90IGx14bqtbg0KDQpEbyBo4bqndSBo4bq/dCBuaOG7r25nIGJp4bq/biBz4buRIHRyb25nIG5naGnDqm4gY+G7qXUgeSBo4buNYyDEkeG7gXUgY8OzIG3hu5FpIHTGsMahbmcgcXVhbiBwaOG7qWMgdOG6oXAsIHRoaeG6v3Qga+G6vyBBTkNPVkEgcuG6pXQgcXVhbiB0cuG7jW5nLiBUcm9uZyBiw6BpIHRo4buxYyBow6BuaCBuw6B5LCBjw6FjIGLhuqFuIMSRw6MgbMOgbSBxdWVuIHbhu5tpIG7DsyAobuG6v3UgY8OhYyBi4bqhbiBjaMawYSBiaeG6v3QpIHbDoCB0aOG7sWMgaGnhu4duIMSRxrDhu6NjIG3hu5l0IHBow6JuIHTDrWNoIEFOQ09WQSBi4bqxbmcgY+G6oyAyIHBoxrDGoW5nIHBow6FwIEZyZXF1ZW50aXN0IHbDoCBCYXllcy4gQuG6o24gY2jhuqV0IGPhu6dhIEFOQ09WQSBjxaluZyBuaMawIEFOT1ZBLCBsw6AgbeG7mXQgbcO0IGjDrG5oIGjhu5NpIHF1eSB0dXnhur9uIHTDrW5oIG5oxrBuZyBjw7MgeMOpdCB0aMOqbSBoaeG7h3Ug4bupbmcgY+G7p2EgaGnhu4dwIGJp4bq/biBz4buRIEMgbcOgIHRydW5nIGLDrG5oIMSRw6MgxJHGsOG7o2MgY2h1eeG7g24gduG7gSB6ZXJvLiBNw7QgaMOsbmggbsOgeSBjaG8gcGjDqXAgaGnhu4d1IGNo4buJbmggdMOhYyDEkeG7mW5nIHJpw6puZyBj4bunYSBDIMSR4buRaSB24bubaSBr4bq/dCBxdeG6oyDEkeG7gyDEkcOhbmggZ2nDoSBjaMOtbmggeMOhYyBoxqFuIHbhu4EgaGnhu4d1IOG7qW5nIGNow61uaCBj4bunYSBwaMOibiBuaMOzbSBYLiBT4buxIGhp4buHdSBjaOG7iW5oIG7DoHkgY8OzIHRo4buDIG1hbmcgbOG6oWkgbmhp4buBdSBi4bqldCBuZ+G7nSwgdGjhuq1tIGNow60gdGhheSDEkeG7lWkga+G6v3QgcXXhuqMgc3V5IGRp4buFbiB0aOG7kW5nIGvDqiBjaG8gWCB0cm9uZyBt4buZdCBz4buRIHRyxrDhu51uZyBo4bujcC4NCg0KQ2jDumMgY8OhYyBi4bqhbiB0aOG7sWMgaMOgbmggdnVpICEgDQo=