Giới thiệu về mô hình Lambda-Mu-Sigma
Thân chào các bạn, trong bài thực hành số 7 cho package GAMLSS này, Nhi sẽ giới thiệu với các bạn một ứng dụng thiết thực và độc đáo của Gamlss vào Y học lâm sàng, đó là phương pháp mô hình 3 tham số (Lambda-Mu-Sigma, LMS)
Phương pháp LMS do 2 tác giả Rigby RA. và Stasinopoulos DM thiết kế vào năm 2001 như giải pháp chuyên dụng để ước lượng một biến số ngẫu nhiên, liên tục và không âm (thường là đại lượng sinh học) đặc trưng bởi quy luật biến thiên phi tuyến tính theo thời gian (cụ thể là Tuổi).
LMS trở thành một công cụ ưu thế trong Y học chính vì 3 lý do :
Tính linh hoạt : LMS không yêu cầu Y phải có phân phối bình thường (Gaussian).
Hữu ích cho chẩn đoán lâm sàng : Từ giá trị của tham số L,M,S chúng ta có thể ước tính chính xác 2 ngưỡng Upper limit và Lower limit của Y.
Cũng từ L,M,S, ta có thể ước tính Z-score (thang chuẩn hóa) cho Y. Ngưỡng ULN,LLN và Z-score được ứng dụng trong việc diễn giải kết quả xét nghiệm của nhiều chỉ số sinh học.
- LMS Cho phép mô hình hóa một cách liên tục sự biến thiên theo quỹ đạo phức tạp, phi tuyến tính của Y.
Một số ứng dụng của LMS trên thực tế như :
Mô hình tăng trưởng ở trẻ em (Cân nặng, chiều cao, BMI…).
Mô hình ước tính giá trị « bình thường » của các xét nghiệm y khoa, thí dụ : dung tích phổi, hàm lượng Hb, huyết áp, các marker sinh lý, sinh hóa, huyết học, miễn dịch …
Tất cả những bài toán này đều có liên quan đến yếu tố Thời gian (Tuổi).
Hình sau đây trình bày đồ thị của 4x3 mô hình ước tính giá trị lý thuyết của 3 đại lượng sinh lý hô hấp là FEV1, FVC, FEV1/FVC riêng cho Nam/Nữ và cho 4 chủng tộc khác nhau. Tất cả chúng đều được dựng nên nhờ phương pháp LMS và sử dụng package gamlss, dựa vào hàng trăm ngàn kết quả hô hấp ký trên người bình thường từ 3-95 tuổi.
Các mô hình này do nhóm Global Lung function initiative (GLI) công bố vào năm 2012.
Trong một thí dụ khác, một nhóm tác giả Hà Lan khảo sát chỉ số Gripstrength ở nam và nữ, từ trẻ em đến người già:
LMS có bản chất là một thể đặc biệt thuộc họ mô hình GAM :
- Nền tảng của LMS model là họ phân phối Box-Cox-Cole-Green (BCCG), một quy luật áp dụng để mô tả biến số ngẫu nhiên Y nằm trong khoảng từ 0 đến dương vô cùng. Họ phân phối này do Cole và Green thiết lập năm 1992 từ phương pháp hoán chuyển Box-Cox. Phân phối BCCG được xác định bởi 3 tham số, bao gồm vị trí trung tâm hoặc trung bình (Mu), Scale (Sigma) và Skewness (Nu).
Hình sau đây minh họa đồ thị hàm PDF của một biến X có phân phối BCCG(mu=5,sigma=0.5 và nu=1):
Trước hết, giả sử ta có 1 biến số sau hoán chuyển Z, có phân phối chuẩn với Mu=0 và Sigma (SD) = 1,
Thì biến số ngẫu nhiên Y tuân theo quy luật BCCG nếu :
Khi Nu khác 0 :
\[Z = \frac{1}{\sigma \nu }\left [ \left ( \frac{Y}{\mu }\right )^{\nu } -1\right ]\] Khi Nu=0 :
\[Z = \frac{1}{\sigma }log\left ( \frac{Y}{\mu } \right )\] 2) LMS là một distributional model, tức một tập hợp 3 mô hình riêng biệt M,S,L cho 3 tham số : Mu (vị trị trung tâm), Sigma (Scale) và Lambda (Skewness), tương ứng với 3 tham số Mu (link function = log), Sigma (link=log) và Nu (link = identity) của quy luật phân phối Box-Cox-Cole-Green.
Một mô hình hồi quy GAMLSS với biến kết quả Outcome là Y tuân theo quy luật BCCG :
\[Y \sim BCCG(\mu ,\sigma ,\nu )\] Mỗi tham số theta (thí dụ Mu, Sigma, Nu, Zero_inflated,…) có thể được xem như một outcome thứ cấp, và có thể được mô hình hóa riêng biệt bởi một link function f với nội dung mô hình lần lượt như sau :
\[\mu \sim ln(\beta X + BSpline(x))\] \[\sigma \sim ln(\beta X + BSpline(x))\] \[\nu \sim identity(\beta X + BSpline(x))\] Với : Mu, sigma và Nu lần lượt là 3 tham số của phân phối BCCG, Beta đại diện cho tham số hồi quy tương ứng với một model matrix X, là tập hợp của nhiều biến predictor trong mô hình, thí dụ Tuổi, Chiều cao, cân nặng trong mô hình dự báo dung tích phổi…
- Mỗi mô hình M,L,S là một mô hình GAM (generalized additive model), nên matrix X cho phép tích hợp thêm Smoothing splines (các hàm điều hòa/hiệu chỉnh) và hàm đa thức (Polynomial). Thí dụ nếu ta áp dụng hàm poly(Age,2) thì X sẽ bao gồm Age và Age^2).Hàm Spline phổ biến nhất là B_spline. Các bạn đọc lại những bài trước để rõ hơn về design matrix và các thành phần đa thức bậc cao này.
Chính những hàm polynomial và smoothing đó cho phép đồ thị LMS model uốn lượn mềm mại theo những hình dạng phức tạp nhất có thể và tăng tính chính xác của mô hình.
Minh họa: Ngưỡng giá trị bình thường của Dung tích phổi
Trong bài, Nhi sẽ tái hiện lại một quy trình dựng mô hình dự báo giá trị lý thuyết cho một đại lượng sinh lý (Dung tích phổi: Total Lung capacity) theo Tuổi và Chiều cao ở Nam giới, chủng tộc Caucassian.
Trước hết chúng ta tải dữ liệu vào R:
library(tidyverse)
df=read.csv("https://raw.githubusercontent.com/kinokoberuji/R-Tutorials/master/LMSmodelGAMLSS.csv", sep=";")%>%as_tibble()
head(df)%>%knitr::kable()
7.017265 |
18 |
177.5 |
M |
7.648100 |
18 |
181.0 |
M |
6.737513 |
18 |
176.0 |
M |
5.949827 |
22 |
171.0 |
M |
7.389043 |
22 |
180.0 |
M |
7.048908 |
23 |
185.0 |
M |
Đây là một nghiên cứu dịch tễ học điển hình. Mục tiêu của chúng ta là tạo ra một mô hình tiên lượng cho phép ước tính : giá trị trung bình lý thuyết của TLC, 2 ngưỡng cao nhất và thấp nhất của khoảng TLC bình thường. Nếu ta biết giá trị TLC thực tế của đối tượng thì ta có thể tính Z-score.
\[LLN = Exp \left [ ln(\mu)+\frac{ln(1-1.645*\nu \sigma)}{\nu}\right ] \]
\[ULN = Exp \left [ ln(\mu)+\frac{ln(1+1.645*\nu \sigma)}{\nu}\right ]\]
\[Z_{score}=\frac{\mu^{\nu -1}}{\nu \sigma }\]
\[\%predicted = \frac{100*Y}{\mu }\]
Giải thích thêm một chút:
Xét nghiệm chức năng hô hấp là một trong những tiêu chuẩn quan trọng để chẩn đoán xác định các bệnh lý về hô hấp. Trong thực hành lâm sàng, kết quả xét nghiệm đo dung tích phổi được diễn giải theo 3 cách:
Có giá trị TLC đo được (Y), bác sĩ so sánh giá trị này với giá trị trung bình dự báo (mean predicted)- chính là Mu trong mô hình LMS. Ngưỡng cắt chẩn đoán thường là 80%, nếu %pred<80, ta có thể kết luận TLC giảm bất thường.
Bác sĩ so sánh TLC đo được (Y) với 2 giá trị LLN và ULN mà mô hình LMS tính ra. Nếu Y < LLN cho thấy TLC giảm bất thường, nếu Y > ULN cho thấy TLC tăng cao bất thường.
Từ Y,L,M và S bác sĩ tính được Z-score, nếu Z-score < -1.645 cho thấy TLC giảm bất thường, nếu Z-score < -1.9 hoặc < -2 thì gần như chắc chắn là có sự suy giảm dung tích phổi bệnh lý.
Hình dưới đây minh họa cho một báo cáo xét nghiệm hô hấp ký với 3 chỉ số chính là FVC, FEV1 và FEV1/FVC, mô hình GLI 2012 cho phép tính được LLN, Z-score và %Pred cho bệnh nhân. Kết quả cho thấy FEV1/FVC < LLN và Z-score < -3, gợi ý hội chứng tắc nghẽn và bệnh lý nghi ngờ là Hen phế quản vì bệnh nhân có hồi phục sau khi dùng thuốc dãn phế quản.
Trở lại với thí dụ của chúng ta, ta sẽ bắt đầu bằng việc thăm dò dữ liệu
Bước 1: Thăm dò dữ liệu
Bước thăm dò này rất quan trọng, nội dung của nó gồm:
Xác định phạm vi áp dụng của mô hình: Giới hạn Min/Max của mỗi predictor(Chiều cao, Tuổi) cũng chính là giới hạn của mô hình. Thí dụ nếu người cao tuổi nhất là 75 tuổi thì bạn không thể dùng mô hình để dự báo LLN, Mu, Z_score cho ông già 90 tuổi. Tương tự, nếu chiều cao thấp nhất trong data là 1m60 thì bạn không thể áp dụng mô hình cho người thấp 1m50. Khi bạn cố tình làm trái với quy ước này, đó là sự ngoại suy và kết quả không thể tin cậy. Phần cuối bài Nhi sẽ minh họa về ngoại suy.
Phát hiện outliers
Phác họa quỹ đạo của mô hình trên biểu đồ, và thăm dò hiệu ứng của các hàm đa thức
Nhi sử dụng package tableone để làm thống kê mô tả: Ở nam giới, phạm vi của chiều cao là 158-200 cm, còn Tuổi là 18-85.
library(tableone)
vars=c("Age","Height","TLC")
sumtab=CreateContTable(vars,
strata = "Sex",
funcNames=c("n","mean","median","sd","min","max"),
test=T,
data = df)
summary(sumtab,
digits = 3)
## Sex: F
## n mean median sd min max
## Age 239 44.75 43.0 17.53 18.00 87.00
## Height 239 163.97 163.0 7.17 147.00 182.00
## TLC 239 5.31 5.3 0.86 3.18 7.58
## --------------------------------------------------------
## Sex: M
## n mean median sd min max
## Age 243 43.51 43.00 16.083 18.00 85.00
## Height 243 176.72 176.00 7.515 158.00 200.00
## TLC 243 7.07 7.05 0.969 4.61 9.82
##
## p-values
## pNormal pNonNormal
## Age 4.176566e-01 6.521788e-01
## Height 1.023777e-60 1.036753e-49
## TLC 1.859500e-70 2.072124e-55
##
## Standardize mean differences
## 1 vs 2
## Age 0.07387375
## Height 1.73666068
## TLC 1.92412053
Density curves cho thấy biến kết quả TLC có phân phối gần như bình thường ở cả Nam và Nữ:
df%>%gather(TLC:Height,key="Variable",value="Value")%>%
ggplot()+
geom_density(aes(x=Value,fill=Sex),alpha=0.6)+
facet_wrap(~Variable,ncol=2,scales="free")+
theme_bw()

Ta tách riêng ra một dataset dành cho Nam giới:
dfM=filter(df,Sex=="M")%>%dplyr::select(-Sex)
TLC có liên hệ phi tuyến tính với Chiều cao và tuổi, như đồ thị hàm loess smoothing cho thấy:
dfM%>%gather(Height,Age,key="Predictor",value="Value")%>%
ggplot(aes(x=Value,y=TLC))+
geom_point(shape=21,col="red",fill="red",alpha=0.3)+
geom_smooth(alpha=0.5,fill="red",col="red4")+
facet_wrap(~Predictor,ncol=2,scales="free")+
theme_bw()

Nếu ta ước tính TLC theo Age và Height bằng một mô hình tuyến tính,đồ thị và mặt phẳng hồi quy sẽ như thế này:
dfM%>%gather(Height,Age,key="Predictor",value="Value")%>%
ggplot(aes(x=Value,y=TLC))+
geom_point(shape=21,col="black",fill="grey80",alpha=0.4)+
geom_smooth(alpha=0.5,fill="blue",col="blue",method="lm")+
facet_wrap(~Predictor,ncol=2,scales="free")+
theme_bw()

library(mgcv)
fit <- gam(TLC~Age+Height,
data=dfM)
vis.gam(fit,view=c("Age","Height"),theta=50,phi=30,n.grid=30,color="topo",ticktype = "detailed")

Nếu áp dụng một hàm đa thức bậc 3 cho Height và bậc 2 cho Age,một linkfunction=log cho TLC, đồ thị và mặt phẳng hồi quy sẽ như sau:
dfM%>%ggplot(aes(x=Height,y=log(TLC)))+
geom_point(alpha=0.3)+
geom_smooth(method=glm,
formula = y ~ poly(x,3),alpha=0.3,fill="violet",col="purple")+
theme_bw()+ggtitle("ln(TLC)~Height^3+Height^2+Height")

dfM%>%ggplot(aes(x=Age,y=log(TLC)))+
geom_point(alpha=0.3)+
geom_smooth(method=glm,
formula = y ~ poly(x,2),alpha=0.3,fill="violet",col="purple")+
theme_bw()+ggtitle("ln(TLC)~Age^2+Age")

fit <- gam(log(TLC)~Age+poly(Age,2)+poly(Height,3),
data=dfM)
vis.gam(fit,view=c("Age","Height"),theta=60,phi=25,n.grid=30,color="topo",ticktype = "detailed")

Bước 2: Dựng mô hình LMS
Bây giờ chúng ta qua bước 2, dựng mô hình LMS trong gamlss. Trước hết, ta sẽ dùng caret để chia dữ liệu thành 2 phần, 20% dành cho kiểm định và 80% để dựng mô hình:
library(caret)
set.seed(123)
idM=createDataPartition(y=dfM$TLC, p=0.8,list=FALSE)
trainM=dfM[idM,]
testM=dfM[-idM,]
Chiến lược 1: Triệt thoái tự động dựa vào BIC và AIC
gamlss cung cấp 2 giải pháp rất đơn giản để thăm dò và chọn lọc mô hình tối ưu. Cả 2 đều dựa vào tiêu chí AIC hoặc BIC, nhưng khác nhau về cách tái chọn mẫu / kiểm định.
Cách thứ nhất không dùng tái chọn mẫu, các mô hình được dựng và kiểm tra tuần tự trên CÙNG một mẫu duy nhất (thí dụ trainM). Các biến số trong formula sẽ bị triệt tiêu dần đến khi mô hình có AIC tối ưu hiện ra.
Quy trình Stepwise này thực ra phức tạp hơn ta tưởng, vì ở đây ta có đến 3 mô hình con là L,M,S; và một công thức với nhiều biến predictor, bao gồm smoothing spline, như vậy tất cả tổ hợp có thể giữa chúng phải được kiểm tra, bao gồm 2 câu hỏi: Có cần mô hình cho Sigma/Nu hay không ? và: Nếu cần, thì nội dung mô hình là gì ?
Trong trường hợp này Nhi muốn chọn lọc mô hình L,M,S tối ưu dựa vào 3 giả định:hàm đa thức bậc 3 cho Height , bậc 2 cho Age, kèm hoặc không kèm Spline cho Age, công thức này sẽ lần lượt test cho Mu và Sigma, Nu được mặc định là hằng số.
library(gamlss)
nC<-detectCores()
m1=gamlss(data=trainM,
TLC~1,
sigma.formula = TLC~1,
nu.formula = TLC~1,
family=BCCG(mu.link="log"),
trace=FALSE,
parallel="multicore",
ncpus = nC)
m2=stepGAICAll.A(m1, scope=list(lower=~1,
upper=~poly(Age,2)+poly(Height,3)+pb(Age)),
sigma.scope = list(lower=~1,
upper=~poly(Age,2)+poly(Height,3)+pb(Age)),
k=log(length(trainM)),
trace=FALSE,
parallel="multicore",
ncpus = nC
)
## ---------------------------------------------------
## Start: AIC= 546.27
## TLC ~ 1
##
## ---------------------------------------------------
## Start: AIC= 437.74
## ~1
##
## ---------------------------------------------------
## Start: AIC= 435.34
## ~1
##
## ---------------------------------------------------
## Start: AIC= 435.34
## ~pb(Age)
##
## ---------------------------------------------------
## Start: AIC= 435.34
## TLC ~ poly(Height, 3) + pb(Age)
##
## ---------------------------------------------------
Kết quả Stepwise như sau:
Mô hình cho Mu có công thức: Mu ~ poly(Height,3)+Age+ MuSpline.
Mô hình cho Sigma chỉ gồm Sigma ~ Age + SigmSplin.
Nu = hằng số.
summary(m2)
## ******************************************************************
## Family: c("BCCG", "Box-Cox-Cole-Green")
##
## Call:
## gamlss(formula = TLC ~ poly(Height, 3) + pb(Age), sigma.formula = ~pb(Age),
## nu.formula = ~1, family = BCCG(mu.link = "log"),
## data = trainM, trace = FALSE, parallel = "multicore", ncpus = nC)
##
##
## Fitting method: RS()
##
## ------------------------------------------------------------------
## Mu link function: log
## Mu Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 1.9041367 0.0222386 85.623 < 2e-16 ***
## poly(Height, 3)1 1.1785602 0.1076800 10.945 < 2e-16 ***
## poly(Height, 3)2 -0.3753507 0.1063796 -3.528 0.000527 ***
## poly(Height, 3)3 -0.1604175 0.1038782 -1.544 0.124224
## pb(Age) 0.0009752 0.0005334 1.828 0.069093 .
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## ------------------------------------------------------------------
## Sigma link function: log
## Sigma Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) -2.568038 0.155515 -16.51 <2e-16 ***
## pb(Age) 0.006680 0.003356 1.99 0.048 *
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## ------------------------------------------------------------------
## Nu link function: identity
## Nu Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 0.05722 0.59892 0.096 0.924
##
## ------------------------------------------------------------------
## NOTE: Additive smoothing terms exist in the formulas:
## i) Std. Error for smoothers are for the linear effect only.
## ii) Std. Error for the linear terms maybe are not accurate.
## ------------------------------------------------------------------
## No. of observations in the fit: 195
## Degrees of Freedom for the fit: 9.53894
## Residual Deg. of Freedom: 185.4611
## at cycle: 5
##
## Global Deviance: 424.8601
## AIC: 443.938
## SBC: 475.1589
## ******************************************************************
Rsq(m2,type="both")
## $CoxSnell
## [1] 0.45431
##
## $CraggUhler
## [1] 0.484216
plot(m2)

## ******************************************************************
## Summary of the Quantile Residuals
## mean = -0.000254358
## variance = 1.005155
## coef. of skewness = -0.01853313
## coef. of kurtosis = 3.01777
## Filliben correlation coefficient = 0.9963444
## ******************************************************************
Dù R2 khá thấp, nhưng mô hình tối ưu mà Stepwise chọn được không cho thấy vấn đề gì nghiêm trọng.
Chiến lược 2: Kiểm chứng chéo K block
Cách chọn lọc mô hình thứ hai phức tạp hơn, thường dùng để kiểm định kết quả của Stepwise. Nó có bản chất là một quy trình K-folds cross validation.
Nguyên tắc lựa chọn mô hình vẫn dựa trên tiêu chí AIC và BIC, tuy nhiên ta phải xác định trước những mô hình nào cần được kiểm tra, thí dụ ta có 3 mô hình M1, M2, M3. Quy trình crossvalidation sẽ được áp dụng cho mỗi mô hình, và tính AIC trung bình. Sau đó ta so sánh AIC này giữa 3 mô hình, mô hình nào có AIC thấp nhất sẽ được chọn.
Mỗi mô hình sẽ được dựng trên (k-1) block dữ liệu và kiểm định độc lập trên block còn lại, quy trình này lặp lại K lần, như vậy mỗi một block trong số K block đều được dùng làm tập kiểm định 1 lần và đều tham gia vào việc huấn luyện mô hình.
Thí dụ, Nhi đặt mô hình g1 với công thức tối ưu như trên:
TLCpoly(Height,3)+Age+pb(Age), sigma.formula=TLCAge+pb(Age),
Sau đó thay đổi công thức để tạo ra thêm 3 mô hình g2,g3,g4 đơn giản hơn, thí dụ giảm bậc đa thức của Height xuống còn 2, hay tăng bậc đa thức cho Age từ 1 lên 2, hay bỏ hẳn Age và chỉ giữ lại Height.
rand1 <- sample (5,nrow(trainM),replace=TRUE)
g1=gamlssCV(rand=rand1,
data=trainM,
TLC~poly(Height,3)+Age+pb(Age),
sigma.formula=TLC~Age+pb(Age),
family=BCCG(mu.link="log"),
trace=FALSE,
parallel="multicore",
ncpus = nC
)
## fold 1
## new prediction
## New way of prediction in pb() (starting from GAMLSS version 5.0-3)
## new prediction
## New way of prediction in pb() (starting from GAMLSS version 5.0-3)
## fold 2
## new prediction
## New way of prediction in pb() (starting from GAMLSS version 5.0-3)
## new prediction
## New way of prediction in pb() (starting from GAMLSS version 5.0-3)
## fold 3
## new prediction
## New way of prediction in pb() (starting from GAMLSS version 5.0-3)
## new prediction
## New way of prediction in pb() (starting from GAMLSS version 5.0-3)
## fold 4
## new prediction
## New way of prediction in pb() (starting from GAMLSS version 5.0-3)
## new prediction
## New way of prediction in pb() (starting from GAMLSS version 5.0-3)
## fold 5
## new prediction
## New way of prediction in pb() (starting from GAMLSS version 5.0-3)
## new prediction
## New way of prediction in pb() (starting from GAMLSS version 5.0-3)
g2=gamlssCV(rand=rand1,
data=trainM,
TLC~poly(Height,3)+poly(Age,2)+pb(Age),
sigma.formula=TLC~poly(Age,2)+pb(Age),
family=BCCG(mu.link="log"),
trace=FALSE,
parallel="multicore",
ncpus = nC
)
## fold 1
## new prediction
## New way of prediction in pb() (starting from GAMLSS version 5.0-3)
## new prediction
## New way of prediction in pb() (starting from GAMLSS version 5.0-3)
## fold 2
## new prediction
## New way of prediction in pb() (starting from GAMLSS version 5.0-3)
## new prediction
## New way of prediction in pb() (starting from GAMLSS version 5.0-3)
## fold 3
## new prediction
## New way of prediction in pb() (starting from GAMLSS version 5.0-3)
## new prediction
## New way of prediction in pb() (starting from GAMLSS version 5.0-3)
## fold 4
## new prediction
## New way of prediction in pb() (starting from GAMLSS version 5.0-3)
## new prediction
## New way of prediction in pb() (starting from GAMLSS version 5.0-3)
## fold 5
## new prediction
## New way of prediction in pb() (starting from GAMLSS version 5.0-3)
## new prediction
## New way of prediction in pb() (starting from GAMLSS version 5.0-3)
g3=gamlssCV(rand=rand1,
data=trainM,
TLC~poly(Age,2)+pb(Age),
sigma.formula=TLC~Age+pb(Age),
family=BCCG(mu.link="log"),
trace=FALSE,
parallel="multicore",
ncpus = nC)
## fold 1
## new prediction
## New way of prediction in pb() (starting from GAMLSS version 5.0-3)
## new prediction
## New way of prediction in pb() (starting from GAMLSS version 5.0-3)
## fold 2
## new prediction
## New way of prediction in pb() (starting from GAMLSS version 5.0-3)
## new prediction
## New way of prediction in pb() (starting from GAMLSS version 5.0-3)
## fold 3
## new prediction
## New way of prediction in pb() (starting from GAMLSS version 5.0-3)
## new prediction
## New way of prediction in pb() (starting from GAMLSS version 5.0-3)
## fold 4
## new prediction
## New way of prediction in pb() (starting from GAMLSS version 5.0-3)
## new prediction
## New way of prediction in pb() (starting from GAMLSS version 5.0-3)
## fold 5
## new prediction
## New way of prediction in pb() (starting from GAMLSS version 5.0-3)
## new prediction
## New way of prediction in pb() (starting from GAMLSS version 5.0-3)
g4=gamlssCV(rand=rand1,
data=trainM,
TLC~poly(Height,3)+pb(Age),
sigma.formula=TLC~Age+pb(Age),
family=BCCG(mu.link="log"),
trace=FALSE,
parallel="multicore",
ncpus = nC
)
## fold 1
## new prediction
## New way of prediction in pb() (starting from GAMLSS version 5.0-3)
## new prediction
## New way of prediction in pb() (starting from GAMLSS version 5.0-3)
## fold 2
## new prediction
## New way of prediction in pb() (starting from GAMLSS version 5.0-3)
## new prediction
## New way of prediction in pb() (starting from GAMLSS version 5.0-3)
## fold 3
## new prediction
## New way of prediction in pb() (starting from GAMLSS version 5.0-3)
## new prediction
## New way of prediction in pb() (starting from GAMLSS version 5.0-3)
## fold 4
## new prediction
## New way of prediction in pb() (starting from GAMLSS version 5.0-3)
## new prediction
## New way of prediction in pb() (starting from GAMLSS version 5.0-3)
## fold 5
## new prediction
## New way of prediction in pb() (starting from GAMLSS version 5.0-3)
## new prediction
## New way of prediction in pb() (starting from GAMLSS version 5.0-3)
CV(g1,g2,g3,g4)
## val[o.val]
## g1 462.3335
## g4 462.3335
## g2 470.9797
## g3 601.6367
Kết quả của CV khẳng định rằng mô hình G1 vẫn là tối ưu
Bước 3: Biện luận mô hình
Sau khi có mô hình tối ưu, đây là lúc ta khai thác nó, trước tiên Nhi muốn kiểm tra lại mô hình này trên testset, một cách độc lập, và tính những chỉ số như R2,rmse; mse…
library(mlr)
regr.task= mlr::makeRegrTask(id = "dfM", data=testM, target = "TLC")
regr.lrn = makeLearner("regr.glm")
dummy=mlr::train(regr.lrn,regr.task)
dumpred=predict(dummy,regr.task)
dumpred$data$response<-predict(m2,newdata=testM,type="response")
## new prediction
## New way of prediction in pb() (starting from GAMLSS version 5.0-3)
mets=list(rsq,mse,rmse)
performance(dumpred,measures =mets)
## rsq mse rmse
## 0.3998107 0.5183984 0.7199989
Tiếp theo, Nhi dùng hàm predictAll để ước tính cả 3 tham số L,M,S trong mô hình. Chỗ lắt léo duy nhất ở đây đó là Nu trong BCCG chính là Lambda trong method LMS:
lms=predictAll(m2,newdata=dfM,type="response")
## new prediction
## New way of prediction in pb() (starting from GAMLSS version 5.0-3)
## new prediction
## New way of prediction in pb() (starting from GAMLSS version 5.0-3)
pdf=dfM%>%mutate(Mu=lms$mu,
LLN=exp(log(lms$mu)+(log(1-1.645*lms$nu*lms$sigma))/lms$nu),
ULN=exp(log(lms$mu)+(log(1+1.645*lms$nu*lms$sigma))/lms$nu),
Z_score=((TLC/lms$mu)^(lms$nu)-1)/(lms$nu*lms$sigma),
pcPred=100*TLC/lms$mu,
cutof80=0.8*lms$mu)
pdf%>%head()%>%knitr::kable()
7.017265 |
18 |
177.5 |
6.831976 |
5.922563 |
7.871907 |
0.3096632 |
102.71210 |
5.465580 |
7.648100 |
18 |
181.0 |
7.129516 |
6.180497 |
8.214737 |
0.8135273 |
107.27377 |
5.703613 |
6.737513 |
18 |
176.0 |
6.692085 |
5.801294 |
7.710723 |
0.0782444 |
100.67883 |
5.353668 |
5.949827 |
22 |
171.0 |
6.276478 |
5.419907 |
7.259549 |
-0.6007951 |
94.79563 |
5.021183 |
7.389043 |
22 |
180.0 |
7.128793 |
6.155904 |
8.245360 |
0.4040904 |
103.65068 |
5.703035 |
7.048908 |
23 |
185.0 |
7.491865 |
6.463038 |
8.673720 |
-0.6803729 |
94.08750 |
5.993492 |
Sau đó, Nhi tạo ra 2 biến mới là ToleranceZscore và Tolerance%pred, để kiểm tra liệu trong 243 người bình thường này, liệu mô hình có phân loại nhầm ai đó thành bệnh lý hay không ? Như vậy Z-score không được thấp hơn -1.645 còn %Pred không được thấp hơn 80%
pdf$ToleranceZscore=if_else(pdf$Z_score<(-1.645),
"Unacceptable",
"Acceptable")
pdf$TolerancePcPred=if_else(pdf$pcPred<80,
"Unacceptable",
"Acceptable")
Ta sẽ nhìn kết quả này một cách trực quan như sau:
pdf%>%ggplot(aes(x=Age))+
geom_point(aes(y=TLC),color="grey80")+
geom_smooth(aes(y=ULN),se=F,color="blue",linetype=2)+
geom_smooth(aes(y=LLN),se=F,color="red",linetype=2)+
geom_smooth(aes(y=Mu),se=F,color="black")+
geom_smooth(aes(y=cutof80),se=F,color="orange")+
geom_point(aes(y=Mu,color=ToleranceZscore),size=2,alpha=0.6)+
scale_color_brewer(palette = "Set1",direction = -1)+
theme_bw()+ggtitle("Mu,%pred,LLN and ULN for Males")

Trên hình vẽ: màu đen là Mu, Xanh là ULN, đỏ là LLN, màu cam là ngưỡng 80% của Mu
pdf%>%ggplot(aes(x=Height))+
geom_point(aes(y=TLC),color="grey80")+
geom_smooth(aes(y=ULN),se=F,color="blue",linetype=2)+
geom_smooth(aes(y=LLN),se=F,color="red",linetype=2)+
geom_smooth(aes(y=Mu),se=F,color="black")+
geom_smooth(aes(y=cutof80),se=F,color="orange")+
geom_point(aes(y=Mu,color=ToleranceZscore),size=2,alpha=0.6)+
scale_color_brewer(palette = "Set1",direction = -1)+
theme_bw()+ggtitle("Mean predicted,LLN and ULN for Males")

Ta có thể kết luận rằng mô hình đã thể hiện khá trung thành khuynh hướng biến thiên của TLC theo Age và Height. Chỉ có 1 số ít cá thể bị phân loại nhầm trên thực tế.
Khi chồng lắp 2 phân phối của Mu (màu cam) và của TLC thực tế (màu đỏ),ta thấy mô hình cho phép ước tính chính xác vị trí trung tâm của TLC trong mẫu, nhưng có một phần nào đó TLC đã bị đánh giá thấp hơn thực tế.
pdf%>%ggplot()+
geom_density(aes(x=Mu),color="orange",linetype=1,fill="orange",alpha=0.5)+
geom_density(aes(x=TLC),color="red",linetype=2,fill="red",alpha=0.2)+
theme_bw()

Khi chuyển cả 2 thành thang đo Z-score, thì Z-score ước tính bởi mô hình hoàn toàn tương hợp với Z-score của TLC trên thực tế.
pdf%>%ggplot()+
geom_density(aes(x=Z_score),color="red",linetype=1,fill="red",alpha=0.3)+
geom_density(aes(x=scale(TLC)),color="blue",linetype=1,fill="blue",alpha=0.3)+
theme_bw()+ggtitle("Predicted (Red) vs Truth (Blue)")

Bước 4: Ngoại suy
Cuối cùng, Nhi tò mò muốn biết liệu ta có thể ngoại suy mô hình này cho những người già > 85 tuổi và những thiếu niên < 18 tuổi hay không ? Để làm việc này Nhi tạo ra dữ liệu mô phỏng:
oldmendf=filter(dfM,Age==max(dfM$Age))
boydf=filter(dfM,Age==min(dfM$Age))
extdf=data_frame(Height=rnorm(oldmendf$Height-5,n=200,sd=10),
Age=rnorm(92,5,n=200))
expred=predictAll(m2,
newdata=extdf,
type="response")
## new prediction
## New way of prediction in pb() (starting from GAMLSS version 5.0-3)
## new prediction
## New way of prediction in pb() (starting from GAMLSS version 5.0-3)
extdf=extdf%>%mutate(Mu=expred$mu,
LLN=exp(log(expred$mu)+
(log(1-1.645*expred$nu*expred$sigma))/expred$nu),
ULN=exp(log(expred$mu)+
(log(1+1.645*expred$nu*expred$sigma))/expred$nu),
cutof80=0.8*expred$mu)
youngdf=data_frame(Height=rnorm(mean(boydf$Height)-10,n=200,sd=10),
Age=rnorm(15,3,n=200))
ypred=predictAll(m2,
newdata=youngdf,
type="response")
## new prediction
## New way of prediction in pb() (starting from GAMLSS version 5.0-3)
## new prediction
## New way of prediction in pb() (starting from GAMLSS version 5.0-3)
ydf=youngdf%>%mutate(Mu=ypred$mu,
LLN=exp(log(ypred$mu)+
(log(1-1.645*ypred$nu*ypred$sigma))/ypred$nu),
ULN=exp(log(ypred$mu)+
(log(1+1.645*ypred$nu*ypred$sigma))/ypred$nu),
cutof80=0.8*ypred$mu)
extdf=extdf%>%mutate(Mu=expred$mu,
LLN=exp(log(expred$mu)+
(log(1-1.645*expred$nu*expred$sigma))/expred$nu),
ULN=exp(log(expred$mu)+
(log(1+1.645*expred$nu*expred$sigma))/expred$nu),
cutof80=0.8*expred$mu)
extradf=rbind(extdf,ydf,
dplyr::select(pdf,colnames(extdf)))
extradf=extradf%>%mutate(Extra=if_else(Age<18 | Age>85,"Extrapolation","True"))
extradf%>%ggplot(aes(x=Age))+
geom_smooth(aes(y=ULN),se=F,color="blue",linetype=2)+
geom_smooth(aes(y=LLN),se=F,color="red",linetype=2)+
geom_smooth(aes(y=Mu),se=F,color="black")+
geom_smooth(aes(y=cutof80),se=F,color="orange")+
geom_point(aes(y=Mu,col=Extra,fill=Extra),shape=21,size=2,alpha=0.5,show.legend = F)+
geom_vline(xintercept = c(18,85),col=c("blue4","red4"),linetype=1)+
scale_y_continuous("TLC")+
scale_color_brewer(palette = "Set1")+
scale_fill_brewer(palette = "Set1")+
theme_bw()+ggtitle("Extrapolation for <18 and >85")

Kết quả mô phỏng cho phép dự báo kết quả ngoại suy mô hình cho những đối tượng ngoài giới hạn Tuổi trong mẫu khảo sát. Đây chỉ là kết quả mô phỏng, với giả định chiều cao không biến đổi quá lớn và có phân phối chuẩn. Trên thực tế những thiếu niên từ 14-17 tuổi có thể tăng trưởng chiều cao nhiều hơn, tương tự người già > 85 tuổi có khuynh hướng giảm chiều cao.
Tổng kết
Bài thực hành đến đây là kết thúc. Nhi đã chuyển đến các bạn toàn bộ nguyên lý đằng sau mô hình LMS và những bí quyết để xây dựng nó trong gamlss. Những điều này chưa bao giờ được giải thích đầy đủ bởi các tổ chức như GLI và các nhóm nghiên cứu khác. Bây giờ thì họ không còn độc quyền nữa. Nếu có đủ dữ liệu tốt trong tay, các bạn hoàn toàn có thể làm được những mô hình tăng trưởng và mô hình ước tính ngưỡng giá trị chẩn đoán cho xét nghiệm sinh lý, sinh hóa cho người Việt.
Cảm ơn các bạn và hẹn gặp lại trong một tutorial khác.
Tài liệu tham khảo
Rigby RA, Stasinopoulos DM. Generalized additive models for location, scale and shape (with discussion). Appl Statist 2005; 54: 507-554. Stanojevic S, Wade A, Stocks J, et al. Reference ranges for spirometry across all ages. A new approach. Am J Respir Crit Care Med 2008; 177: 253–260.
Quanjer PH, Stanojevic S, Cole TJ, et al. and the ERS Global Lung Function Initiative. Multi-ethnic reference values for spirometry for the 3-95 years age range: the Global Lung Function 2012 equations. Eur Respir J 2012; 40: 1324-1343.
Sandercock G., Voss C., Cohen D., Taylor M., and Stasinopoulos D. M. (2012) Centile curves and normative values for the twenty metre shuttle-run test in English schoolchildren. Journal of Sports Sciences, DOI:10.1080/02640414.2012.660185
WHO Multicentre Growth Reference Study Group (2007) WHO Child Growth Standards: Head circumference-for-age, arm circumference-for-age, triceps circumference-for-age and subscapular skinford-for-age: Methods and development. Geneva: World Health Organization. Available from: http://www.who.int/childgrowth/standards/second_set/technical_report_2/en/.
Rigby RA, Stasinopoulos DM. Smooth centile curves for skew and kurtotic data modelled using the Box-Cox power exponential distribution. Stat Med. 2004;23:3053-76.
LS0tDQp0aXRsZTogIkjGsOG7m25nIGThuqtuIEdBTUxTUyBiw6BpIDciDQpzdWJ0aXRsZTogIk3DtCBow6xuaCBMYW1iZGEtTXUtU2lnbWEgKExNUykiDQphdXRob3I6ICJMw6ogTmfhu41jIEto4bqjIE5oaSINCmRhdGU6ICIxNSBUaMOhbmcgMTIgMjAxNyINCm91dHB1dDoNCiAgaHRtbF9kb2N1bWVudDogDQogICAgY29kZV9kb3dubG9hZDogdHJ1ZQ0KICAgIGNvZGVfZm9sZGluZzogaGlkZQ0KICAgIG51bWJlcl9zZWN0aW9uczogeWVzDQogICAgdGhlbWU6ICJkZWZhdWx0Ig0KICAgIHRvYzogVFJVRQ0KICAgIHRvY19mbG9hdDogVFJVRQ0KLS0tDQoNCmBgYHtyIHNldHVwLGluY2x1ZGU9RkFMU0V9DQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUpDQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCmxpYnJhcnkocGFuZGVyKQ0KYGBgDQoNCiFbXShMTVNnYW1sc3MxLnBuZykNCg0KIyBHaeG7m2kgdGhp4buHdSB24buBIG3DtCBow6xuaCBMYW1iZGEtTXUtU2lnbWENCg0KVGjDom4gY2jDoG8gY8OhYyBi4bqhbiwgdHJvbmcgYsOgaSB0aOG7sWMgaMOgbmggc+G7kSA3IGNobyBwYWNrYWdlIEdBTUxTUyBuw6B5LCBOaGkgc+G6vSBnaeG7m2kgdGhp4buHdSB24bubaSBjw6FjIGLhuqFuIG3hu5l0IOG7qW5nIGThu6VuZyB0aGnhur90IHRo4buxYyB2w6AgxJHhu5ljIMSRw6FvIGPhu6dhIEdhbWxzcyB2w6BvIFkgaOG7jWMgbMOibSBzw6BuZywgxJHDsyBsw6AgcGjGsMahbmcgcGjDoXAgbcO0IGjDrG5oIDMgdGhhbSBz4buRIChMYW1iZGEtTXUtU2lnbWEsIExNUykNCg0KUGjGsMahbmcgcGjDoXAgTE1TIGRvIDIgdMOhYyBnaeG6oyBSaWdieSBSQS4gdsOgIFN0YXNpbm9wb3Vsb3MgRE0gdGhp4bq/dCBr4bq/IHbDoG8gbsSDbSAyMDAxIG5oxrAgZ2nhuqNpIHBow6FwIGNodXnDqm4gZOG7pW5nIMSR4buDIMaw4bubYyBsxrDhu6NuZyBt4buZdCBiaeG6v24gc+G7kSBuZ+G6q3Ugbmhpw6puLCBsacOqbiB04bulYyB2w6Aga2jDtG5nIMOibSAodGjGsOG7nW5nIGzDoCDEkeG6oWkgbMaw4bujbmcgc2luaCBo4buNYykgxJHhurdjIHRyxrBuZyBi4bufaSBxdXkgbHXhuq10IGJp4bq/biB0aGnDqm4gcGhpIHR1eeG6v24gdMOtbmggdGhlbyB0aOG7nWkgZ2lhbiAoY+G7pSB0aOG7gyBsw6AgVHXhu5VpKS4gDQoNCkxNUyB0cuG7nyB0aMOgbmggbeG7mXQgY8O0bmcgY+G7pSDGsHUgdGjhur8gdHJvbmcgWSBo4buNYyBjaMOtbmggdsOsIDMgbMO9IGRvIDoNCg0KMSkgVMOtbmggbGluaCBob+G6oXQgOiBMTVMga2jDtG5nIHnDqnUgY+G6p3UgWSBwaOG6o2kgY8OzIHBow6JuIHBo4buRaSBiw6xuaCB0aMaw4budbmcgKEdhdXNzaWFuKS4NCg0KMikgSOG7r3Ugw61jaCBjaG8gY2jhuqluIMSRb8OhbiBsw6JtIHPDoG5nIDogVOG7qyBnacOhIHRy4buLIGPhu6dhIHRoYW0gc+G7kSAgTCxNLFMgY2jDum5nIHRhIGPDsyB0aOG7gyDGsOG7m2MgdMOtbmggY2jDrW5oIHjDoWMgMiBuZ8aw4buhbmcgVXBwZXIgbGltaXQgdsOgIExvd2VyIGxpbWl0IGPhu6dhIFkuDQoNCkPFqW5nIHThu6sgTCxNLFMsIHRhIGPDsyB0aOG7gyDGsOG7m2MgdMOtbmggWi1zY29yZSAodGhhbmcgY2h14bqpbiBow7NhKSBjaG8gWS4gTmfGsOG7oW5nIFVMTixMTE4gdsOgIFotc2NvcmUgxJHGsOG7o2Mg4bupbmcgZOG7pW5nIHRyb25nIHZp4buHYyBkaeG7hW4gZ2nhuqNpIGvhur90IHF14bqjIHjDqXQgbmdoaeG7h20gY+G7p2Egbmhp4buBdSBjaOG7iSBz4buRIHNpbmggaOG7jWMuDQoNCjMpIExNUyBDaG8gcGjDqXAgbcO0IGjDrG5oIGjDs2EgbeG7mXQgY8OhY2ggbGnDqm4gdOG7pWMgc+G7sSBiaeG6v24gdGhpw6puIHRoZW8gcXXhu7kgxJHhuqFvIHBo4bupYyB04bqhcCwgcGhpIHR1eeG6v24gdMOtbmggY+G7p2EgWS4NCg0KTeG7mXQgc+G7kSDhu6luZyBk4bulbmcgY+G7p2EgTE1TIHRyw6puIHRo4buxYyB04bq/IG5oxrAgOg0KDQorIE3DtCBow6xuaCB0xINuZyB0csaw4bufbmcg4bufIHRy4bq7IGVtIChDw6JuIG7hurduZywgY2hp4buBdSBjYW8sIEJNSeKApikuDQoNCisgTcO0IGjDrG5oIMaw4bubYyB0w61uaCBnacOhIHRy4buLIMKrIGLDrG5oIHRoxrDhu51uZyDCuyBj4bunYSBjw6FjIHjDqXQgbmdoaeG7h20geSBraG9hLCB0aMOtIGThu6UgOiBkdW5nIHTDrWNoIHBo4buVaSwgaMOgbSBsxrDhu6NuZyBIYiwgaHV54bq/dCDDoXAsIGPDoWMgbWFya2VyIHNpbmggbMO9LCBzaW5oIGjDs2EsIGh1eeG6v3QgaOG7jWMsIG1p4buFbiBk4buLY2ggLi4uDQoNClThuqV0IGPhuqMgbmjhu69uZyBiw6BpIHRvw6FuIG7DoHkgxJHhu4F1IGPDsyBsacOqbiBxdWFuIMSR4bq/biB54bq/dSB04buRIFRo4budaSBnaWFuIChUdeG7lWkpLg0KDQpIw6xuaCBzYXUgxJHDonkgdHLDrG5oIGLDoHkgxJHhu5MgdGjhu4sgY+G7p2EgNHgzIG3DtCBow6xuaCDGsOG7m2MgdMOtbmggZ2nDoSB0cuG7iyBsw70gdGh1eeG6v3QgY+G7p2EgMyDEkeG6oWkgbMaw4bujbmcgc2luaCBsw70gaMO0IGjhuqVwIGzDoCBGRVYxLCBGVkMsIEZFVjEvRlZDIHJpw6puZyBjaG8gTmFtL07hu68gdsOgIGNobyA0IGNo4bunbmcgdOG7mWMga2jDoWMgbmhhdS4gVOG6pXQgY+G6oyBjaMO6bmcgxJHhu4F1IMSRxrDhu6NjIGThu7FuZyBuw6puIG5o4budIHBoxrDGoW5nIHBow6FwIExNUyB2w6Agc+G7rSBk4bulbmcgcGFja2FnZSBnYW1sc3MsIGThu7FhIHbDoG8gaMOgbmcgdHLEg20gbmfDoG4ga+G6v3QgcXXhuqMgaMO0IGjhuqVwIGvDvSB0csOqbiBuZ8aw4budaSBiw6xuaCB0aMaw4budbmcgdOG7qyAzLTk1IHR14buVaS4NCg0KQ8OhYyBtw7QgaMOsbmggbsOgeSBkbyBuaMOzbSBHbG9iYWwgTHVuZyBmdW5jdGlvbiBpbml0aWF0aXZlIChHTEkpIGPDtG5nIGLhu5EgdsOgbyBuxINtIDIwMTIuIA0KDQohW10oTE1TZ2FtbHNzMi5wbmcpDQoNClRyb25nIG3hu5l0IHRow60gZOG7pSBraMOhYywgbeG7mXQgbmjDs20gdMOhYyBnaeG6oyBIw6AgTGFuIGto4bqjbyBzw6F0IGNo4buJIHPhu5EgR3JpcHN0cmVuZ3RoIOG7nyBuYW0gdsOgIG7hu68sIHThu6sgdHLhursgZW0gxJHhur9uIG5nxrDhu51pIGdpw6A6DQoNCiFbXShMTVNnYW1sc3M1LnBuZykNCg0KTE1TIGPDsyBi4bqjbiBjaOG6pXQgbMOgIG3hu5l0IHRo4buDIMSR4bq3YyBiaeG7h3QgdGh14buZYyBo4buNIG3DtCBow6xuaCBHQU0gOg0KDQoxKSBO4buBbiB04bqjbmcgY+G7p2EgTE1TIG1vZGVsIGzDoCBo4buNIHBow6JuIHBo4buRaSBCb3gtQ294LUNvbGUtR3JlZW4gKEJDQ0cpLCBt4buZdCBxdXkgbHXhuq10IMOhcCBk4bulbmcgxJHhu4MgbcO0IHThuqMgYmnhur9uIHPhu5Egbmfhuqt1IG5oacOqbiBZIG7hurFtIHRyb25nIGtob+G6o25nIHThu6sgMCDEkeG6v24gZMawxqFuZyB2w7QgY8O5bmcuIEjhu40gcGjDom4gcGjhu5FpIG7DoHkgZG8gQ29sZSB2w6AgR3JlZW4gdGhp4bq/dCBs4bqtcCBuxINtIDE5OTIgdOG7qyBwaMawxqFuZyBwaMOhcCBob8OhbiBjaHV54buDbiBCb3gtQ294LiBQaMOibiBwaOG7kWkgQkNDRyDEkcaw4bujYyB4w6FjIMSR4buLbmggYuG7n2kgMyB0aGFtIHPhu5EsIGJhbyBn4buTbSB24buLIHRyw60gdHJ1bmcgdMOibSBob+G6t2MgdHJ1bmcgYsOsbmggKE11KSwgU2NhbGUgKFNpZ21hKSB2w6AgU2tld25lc3MgKE51KS4gDQoNCkjDrG5oIHNhdSDEkcOieSBtaW5oIGjhu41hIMSR4buTIHRo4buLIGjDoG0gUERGIGPhu6dhIG3hu5l0IGJp4bq/biBYIGPDsyBwaMOibiBwaOG7kWkgQkNDRyhtdT01LHNpZ21hPTAuNSB2w6AgbnU9MSk6DQoNCiFbXShMTVNnYW1sc3M2LnBuZykNCg0KVHLGsOG7m2MgaOG6v3QsIGdp4bqjIHPhu60gdGEgY8OzIDEgYmnhur9uIHPhu5Egc2F1IGhvw6FuIGNodXnhu4NuIFosIGPDsyBwaMOibiBwaOG7kWkgY2h14bqpbiB24bubaSBNdT0wIHbDoCBTaWdtYSAoU0QpID0gMSwNCg0KVGjDrCBiaeG6v24gc+G7kSBuZ+G6q3Ugbmhpw6puIFkgdHXDom4gdGhlbyBxdXkgbHXhuq10IEJDQ0cgbuG6v3UgOg0KDQpLaGkgTnUga2jDoWMgMCA6DQoNCiQkWiA9IFxmcmFjezF9e1xzaWdtYSBcbnUgfVxsZWZ0IFsgXGxlZnQgKCBcZnJhY3tZfXtcbXUgfVxyaWdodCApXntcbnUgfSAtMVxyaWdodCBdJCQNCktoaSBOdT0wIDoNCg0KJCRaID0gXGZyYWN7MX17XHNpZ21hIH1sb2dcbGVmdCAoIFxmcmFje1l9e1xtdSB9IFxyaWdodCApJCQNCjIpIExNUyBsw6AgbeG7mXQgZGlzdHJpYnV0aW9uYWwgbW9kZWwsIHThu6ljIG3hu5l0IHThuq1wIGjhu6NwIDMgbcO0IGjDrG5oIHJpw6puZyBiaeG7h3QgTSxTLEwgY2hvIDMgdGhhbSBz4buRIDogTXUgKHbhu4sgdHLhu4sgdHJ1bmcgdMOibSksIFNpZ21hIChTY2FsZSkgdsOgIExhbWJkYSAoU2tld25lc3MpLCB0xrDGoW5nIOG7qW5nIHbhu5tpIDMgdGhhbSBz4buRIE11IChsaW5rIGZ1bmN0aW9uID0gbG9nKSwgU2lnbWEgKGxpbms9bG9nKSB2w6AgTnUgKGxpbmsgPSBpZGVudGl0eSkgY+G7p2EgcXV5IGx14bqtdCBwaMOibiBwaOG7kWkgQm94LUNveC1Db2xlLUdyZWVuLiANCg0KTeG7mXQgbcO0IGjDrG5oIGjhu5NpIHF1eSBHQU1MU1MgduG7m2kgYmnhur9uIGvhur90IHF14bqjIE91dGNvbWUgbMOgIFkgdHXDom4gdGhlbyBxdXkgbHXhuq10IEJDQ0cgOg0KDQokJFkgXHNpbSBCQ0NHKFxtdSAsXHNpZ21hICxcbnUgKSQkDQpN4buXaSB0aGFtIHPhu5EgdGhldGEgKHRow60gZOG7pSBNdSwgU2lnbWEsIE51LCBaZXJvX2luZmxhdGVkLOKApikgY8OzIHRo4buDIMSRxrDhu6NjIHhlbSBuaMawIG3hu5l0IG91dGNvbWUgdGjhu6kgY+G6pXAsIHbDoCBjw7MgdGjhu4MgxJHGsOG7o2MgbcO0IGjDrG5oIGjDs2EgcmnDqm5nIGJp4buHdCBi4bufaSBt4buZdCBsaW5rIGZ1bmN0aW9uIGYgIHbhu5tpIG7hu5lpIGR1bmcgbcO0IGjDrG5oIGzhuqduIGzGsOG7o3QgbmjGsCBzYXUgOg0KDQokJFxtdSBcc2ltIGxuKFxiZXRhIFggKyBCU3BsaW5lKHgpKSQkDQokJFxzaWdtYSBcc2ltIGxuKFxiZXRhIFggKyBCU3BsaW5lKHgpKSQkDQokJFxudSBcc2ltIGlkZW50aXR5KFxiZXRhIFggKyBCU3BsaW5lKHgpKSQkDQpW4bubaSA6IE11LCBzaWdtYSB2w6AgTnUgbOG6p24gbMaw4bujdCBsw6AgMyB0aGFtIHPhu5EgY+G7p2EgcGjDom4gcGjhu5FpIEJDQ0csIA0KQmV0YSDEkeG6oWkgZGnhu4duIGNobyB0aGFtIHPhu5EgaOG7k2kgcXV5IHTGsMahbmcg4bupbmcgduG7m2kgbeG7mXQgbW9kZWwgbWF0cml4IFgsIGzDoCB04bqtcCBo4bujcCBj4bunYSBuaGnhu4F1IGJp4bq/biBwcmVkaWN0b3IgdHJvbmcgbcO0IGjDrG5oLCB0aMOtIGThu6UgVHXhu5VpLCBDaGnhu4F1IGNhbywgY8OibiBu4bq3bmcgdHJvbmcgbcO0IGjDrG5oIGThu7EgYsOhbyBkdW5nIHTDrWNoIHBo4buVaS4uLg0KDQozKSBN4buXaSBtw7QgaMOsbmggTSxMLFMgbMOgIG3hu5l0IG3DtCBow6xuaCBHQU0gKGdlbmVyYWxpemVkIGFkZGl0aXZlIG1vZGVsKSwgbsOqbiBtYXRyaXggWCBjaG8gcGjDqXAgdMOtY2ggaOG7o3AgdGjDqm0gU21vb3RoaW5nIHNwbGluZXMgKGPDoWMgaMOgbSDEkWnhu4F1IGjDsmEvaGnhu4d1IGNo4buJbmgpIHbDoCBow6BtIMSRYSB0aOG7qWMgKFBvbHlub21pYWwpLiBUaMOtIGThu6UgbuG6v3UgdGEgw6FwIGThu6VuZyBow6BtIHBvbHkoQWdlLDIpIHRow6wgWCBz4bq9IGJhbyBn4buTbSBBZ2UgdsOgIEFnZV4yKS5Iw6BtIFNwbGluZSBwaOG7lSBiaeG6v24gbmjhuqV0IGzDoCBCX3NwbGluZS4gQ8OhYyBi4bqhbiDEkeG7jWMgbOG6oWkgbmjhu69uZyBiw6BpIHRyxrDhu5tjIMSR4buDIHLDtSBoxqFuIHbhu4EgZGVzaWduIG1hdHJpeCB2w6AgY8OhYyB0aMOgbmggcGjhuqduIMSRYSB0aOG7qWMgYuG6rWMgY2FvIG7DoHkuDQoNCkNow61uaCBuaOG7r25nIGjDoG0gcG9seW5vbWlhbCB2w6Agc21vb3RoaW5nIMSRw7MgY2hvIHBow6lwIMSR4buTIHRo4buLIExNUyBtb2RlbCB14buRbiBsxrDhu6NuIG3hu4FtIG3huqFpIHRoZW8gbmjhu69uZyBow6xuaCBk4bqhbmcgcGjhu6ljIHThuqFwIG5o4bqldCBjw7MgdGjhu4MgdsOgIHTEg25nIHTDrW5oIGNow61uaCB4w6FjIGPhu6dhIG3DtCBow6xuaC4NCg0KIyBNaW5oIGjhu41hOiBOZ8aw4buhbmcgZ2nDoSB0cuG7iyBiw6xuaCB0aMaw4budbmcgY+G7p2EgRHVuZyB0w61jaCBwaOG7lWkNCg0KVHJvbmcgYsOgaSwgTmhpIHPhur0gdMOhaSBoaeG7h24gbOG6oWkgbeG7mXQgcXV5IHRyw6xuaCBk4buxbmcgbcO0IGjDrG5oIGThu7EgYsOhbyBnacOhIHRy4buLIGzDvSB0aHV54bq/dCBjaG8gbeG7mXQgxJHhuqFpIGzGsOG7o25nIHNpbmggbMO9IChEdW5nIHTDrWNoIHBo4buVaTogVG90YWwgTHVuZyBjYXBhY2l0eSkgdGhlbyBUdeG7lWkgdsOgIENoaeG7gXUgY2FvIOG7nyBOYW0gZ2nhu5tpLCBjaOG7p25nIHThu5ljIENhdWNhc3NpYW4uIA0KDQpUcsaw4bubYyBo4bq/dCBjaMO6bmcgdGEgdOG6o2kgZOG7ryBsaeG7h3UgdsOgbyBSOg0KDQpgYGB7cixtZXNzYWdlID0gRkFMU0Usd2FybmluZz1GQUxTRX0NCmxpYnJhcnkodGlkeXZlcnNlKQ0KDQpkZj1yZWFkLmNzdigiaHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL2tpbm9rb2JlcnVqaS9SLVR1dG9yaWFscy9tYXN0ZXIvTE1TbW9kZWxHQU1MU1MuY3N2Iiwgc2VwPSI7IiklPiVhc190aWJibGUoKQ0KDQpoZWFkKGRmKSU+JWtuaXRyOjprYWJsZSgpDQpgYGANCg0KxJDDonkgbMOgIG3hu5l0IG5naGnDqm4gY+G7qXUgZOG7i2NoIHThu4UgaOG7jWMgxJFp4buDbiBow6xuaC4gTeG7pWMgdGnDqnUgY+G7p2EgY2jDum5nIHRhIGzDoCB04bqhbyByYSBt4buZdCBtw7QgaMOsbmggdGnDqm4gbMaw4bujbmcgY2hvIHBow6lwIMaw4bubYyB0w61uaCA6IGdpw6EgdHLhu4sgdHJ1bmcgYsOsbmggbMO9IHRodXnhur90IGPhu6dhIFRMQywgMiBuZ8aw4buhbmcgY2FvIG5o4bqldCB2w6AgdGjhuqVwIG5o4bqldCBj4bunYSBraG/huqNuZyBUTEMgYsOsbmggdGjGsOG7nW5nLiBO4bq/dSB0YSBiaeG6v3QgZ2nDoSB0cuG7iyBUTEMgdGjhu7FjIHThur8gY+G7p2EgxJHhu5FpIHTGsOG7o25nIHRow6wgdGEgY8OzIHRo4buDIHTDrW5oIFotc2NvcmUuDQoNCiQkTExOID0gRXhwIFxsZWZ0IFsgbG4oXG11KStcZnJhY3tsbigxLTEuNjQ1KlxudSBcc2lnbWEpfXtcbnV9XHJpZ2h0IF0gJCQNCg0KJCRVTE4gPSBFeHAgXGxlZnQgWyBsbihcbXUpK1xmcmFje2xuKDErMS42NDUqXG51IFxzaWdtYSl9e1xudX1ccmlnaHQgXSQkDQoNCiQkWl97c2NvcmV9PVxmcmFje1xtdV57XG51IC0xfX17XG51IFxzaWdtYSB9JCQNCg0KJCRcJXByZWRpY3RlZCA9IFxmcmFjezEwMCpZfXtcbXUgfSQkDQoNCkdp4bqjaSB0aMOtY2ggdGjDqm0gbeG7mXQgY2jDunQ6IA0KDQpYw6l0IG5naGnhu4dtIGNo4bupYyBuxINuZyBow7QgaOG6pXAgbMOgIG3hu5l0IHRyb25nIG5o4buvbmcgdGnDqnUgY2h14bqpbiBxdWFuIHRy4buNbmcgxJHhu4MgY2jhuqluIMSRb8OhbiB4w6FjIMSR4buLbmggY8OhYyBi4buHbmggbMO9IHbhu4EgaMO0IGjhuqVwLiBUcm9uZyB0aOG7sWMgaMOgbmggbMOibSBzw6BuZywga+G6v3QgcXXhuqMgeMOpdCBuZ2hp4buHbSDEkW8gZHVuZyB0w61jaCBwaOG7lWkgxJHGsOG7o2MgZGnhu4VuIGdp4bqjaSB0aGVvIDMgY8OhY2g6DQoNCjEpIEPDsyBnacOhIHRy4buLIFRMQyDEkW8gxJHGsOG7o2MgKFkpLCBiw6FjIHPEqSBzbyBzw6FuaCBnacOhIHRy4buLIG7DoHkgduG7m2kgZ2nDoSB0cuG7iyB0cnVuZyBiw6xuaCBk4buxIGLDoW8gKG1lYW4gcHJlZGljdGVkKS0gY2jDrW5oIGzDoCBNdSB0cm9uZyBtw7QgaMOsbmggTE1TLiBOZ8aw4buhbmcgY+G6r3QgY2jhuqluIMSRb8OhbiB0aMaw4budbmcgbMOgIDgwJSwgbuG6v3UgJXByZWQ8ODAsIHRhIGPDsyB0aOG7gyBr4bq/dCBsdeG6rW4gVExDIGdp4bqjbSBi4bqldCB0aMaw4budbmcuDQoNCjIpIELDoWMgc8SpIHNvIHPDoW5oIFRMQyDEkW8gxJHGsOG7o2MgKFkpIHbhu5tpIDIgZ2nDoSB0cuG7iyBMTE4gdsOgIFVMTiBtw6AgbcO0IGjDrG5oIExNUyB0w61uaCByYS4gTuG6v3UgWSA8IExMTiBjaG8gdGjhuqV5IFRMQyBnaeG6o20gYuG6pXQgdGjGsOG7nW5nLCBu4bq/dSBZID4gVUxOIGNobyB0aOG6pXkgVExDIHTEg25nIGNhbyBi4bqldCB0aMaw4budbmcuDQoNCjMpIFThu6sgWSxMLE0gdsOgIFMgYsOhYyBzxKkgdMOtbmggxJHGsOG7o2MgWi1zY29yZSwgbuG6v3UgWi1zY29yZSA8IC0xLjY0NSBjaG8gdGjhuqV5IFRMQyBnaeG6o20gYuG6pXQgdGjGsOG7nW5nLCBu4bq/dSBaLXNjb3JlIDwgLTEuOSBob+G6t2MgPCAtMiB0aMOsIGfhuqduIG5oxrAgY2jhuq9jIGNo4bqvbiBsw6AgY8OzIHPhu7Egc3V5IGdp4bqjbSBkdW5nIHTDrWNoIHBo4buVaSBi4buHbmggbMO9LiANCg0KSMOsbmggZMaw4bubaSDEkcOieSBtaW5oIGjhu41hIGNobyBt4buZdCBiw6FvIGPDoW8geMOpdCBuZ2hp4buHbSBow7QgaOG6pXAga8O9IHbhu5tpIDMgY2jhu4kgc+G7kSBjaMOtbmggbMOgIEZWQywgRkVWMSB2w6AgRkVWMS9GVkMsIG3DtCBow6xuaCBHTEkgMjAxMiBjaG8gcGjDqXAgdMOtbmggxJHGsOG7o2MgTExOLCBaLXNjb3JlIHbDoCAlUHJlZCBjaG8gYuG7h25oIG5ow6JuLiBL4bq/dCBxdeG6oyBjaG8gdGjhuqV5IEZFVjEvRlZDIDwgTExOIHbDoCBaLXNjb3JlIDwgLTMsIGfhu6NpIMO9IGjhu5lpIGNo4bupbmcgdOG6r2Mgbmdo4bq9biB2w6AgYuG7h25oIGzDvSBuZ2hpIG5n4budIGzDoCBIZW4gcGjhur8gcXXhuqNuIHbDrCBi4buHbmggbmjDom4gY8OzIGjhu5NpIHBo4bulYyBzYXUga2hpIGTDuW5nIHRodeG7kWMgZMOjbiBwaOG6vyBxdeG6o24uDQoNCiFbXShMTVNnYW1sc3MzLnBuZykNCg0KVHLhu58gbOG6oWkgduG7m2kgdGjDrSBk4bulIGPhu6dhIGNow7puZyB0YSwgdGEgc+G6vSBi4bqvdCDEkeG6p3UgYuG6sW5nIHZp4buHYyB0aMSDbSBkw7IgZOG7ryBsaeG7h3UNCg0KIyBCxrDhu5tjIDE6IFRoxINtIGTDsiBk4buvIGxp4buHdSANCg0KQsaw4bubYyB0aMSDbSBkw7IgbsOgeSBy4bqldCBxdWFuIHRy4buNbmcsIG7hu5lpIGR1bmcgY+G7p2EgbsOzIGfhu5NtOg0KDQorIFjDoWMgxJHhu4tuaCBwaOG6oW0gdmkgw6FwIGThu6VuZyBj4bunYSBtw7QgaMOsbmg6IEdp4bubaSBo4bqhbiBNaW4vTWF4IGPhu6dhIG3hu5dpIHByZWRpY3RvcihDaGnhu4F1IGNhbywgVHXhu5VpKSBjxaluZyBjaMOtbmggbMOgIGdp4bubaSBo4bqhbiBj4bunYSBtw7QgaMOsbmguIFRow60gZOG7pSBu4bq/dSBuZ8aw4budaSBjYW8gdHXhu5VpIG5o4bqldCBsw6AgNzUgdHXhu5VpIHRow6wgYuG6oW4ga2jDtG5nIHRo4buDIGTDuW5nIG3DtCBow6xuaCDEkeG7gyBk4buxIGLDoW8gTExOLCBNdSwgWl9zY29yZSBjaG8gw7RuZyBnacOgIDkwIHR14buVaS4gVMawxqFuZyB04buxLCBu4bq/dSBjaGnhu4F1IGNhbyB0aOG6pXAgbmjhuqV0IHRyb25nIGRhdGEgbMOgIDFtNjAgdGjDrCBi4bqhbiBraMO0bmcgdGjhu4Mgw6FwIGThu6VuZyBtw7QgaMOsbmggY2hvIG5nxrDhu51pIHRo4bqlcCAxbTUwLiBLaGkgYuG6oW4gY+G7kSB0w6xuaCBsw6BtIHRyw6FpIHbhu5tpIHF1eSDGsOG7m2MgbsOgeSwgxJHDsyBsw6Agc+G7sSBuZ2/huqFpIHN1eSB2w6Aga+G6v3QgcXXhuqMga2jDtG5nIHRo4buDIHRpbiBj4bqteS4gUGjhuqduIGN14buRaSBiw6BpIE5oaSBz4bq9IG1pbmggaOG7jWEgduG7gSBuZ2/huqFpIHN1eS4NCg0KKyBQaMOhdCBoaeG7h24gb3V0bGllcnMNCg0KKyBQaMOhYyBo4buNYSBxdeG7uSDEkeG6oW8gY+G7p2EgbcO0IGjDrG5oIHRyw6puIGJp4buDdSDEkeG7kywgdsOgIHRoxINtIGTDsiBoaeG7h3Ug4bupbmcgY+G7p2EgY8OhYyBow6BtIMSRYSB0aOG7qWMNCg0KTmhpIHPhu60gZOG7pW5nIHBhY2thZ2UgdGFibGVvbmUgxJHhu4MgbMOgbSB0aOG7kW5nIGvDqiBtw7QgdOG6ozog4bueIG5hbSBnaeG7m2ksIHBo4bqhbSB2aSBj4bunYSBjaGnhu4F1IGNhbyBsw6AgMTU4LTIwMCBjbSwgY8OybiBUdeG7lWkgbMOgIDE4LTg1Lg0KDQpgYGB7cixtZXNzYWdlID0gRkFMU0Usd2FybmluZz1GQUxTRX0NCmxpYnJhcnkodGFibGVvbmUpDQoNCnZhcnM9YygiQWdlIiwiSGVpZ2h0IiwiVExDIikNCg0Kc3VtdGFiPUNyZWF0ZUNvbnRUYWJsZSh2YXJzLCANCiAgICAgICAgICAgICAgICAgICAgICBzdHJhdGEgPSAiU2V4IiwNCiAgICAgICAgICAgICAgICAgICAgICBmdW5jTmFtZXM9YygibiIsIm1lYW4iLCJtZWRpYW4iLCJzZCIsIm1pbiIsIm1heCIpLA0KICAgICAgICAgICAgICAgICAgICAgIHRlc3Q9VCwNCiAgICAgICAgICAgICAgICAgICAgICBkYXRhID0gZGYpDQoNCnN1bW1hcnkoc3VtdGFiLA0KICAgICAgICBkaWdpdHMgPSAzKQ0KDQpgYGANCg0KRGVuc2l0eSBjdXJ2ZXMgY2hvIHRo4bqleSBiaeG6v24ga+G6v3QgcXXhuqMgVExDIGPDsyBwaMOibiBwaOG7kWkgZ+G6p24gbmjGsCBiw6xuaCB0aMaw4budbmcg4bufIGPhuqMgTmFtIHbDoCBO4buvOg0KDQpgYGB7cixtZXNzYWdlID0gRkFMU0Usd2FybmluZz1GQUxTRX0NCmRmJT4lZ2F0aGVyKFRMQzpIZWlnaHQsa2V5PSJWYXJpYWJsZSIsdmFsdWU9IlZhbHVlIiklPiUNCiAgZ2dwbG90KCkrDQogIGdlb21fZGVuc2l0eShhZXMoeD1WYWx1ZSxmaWxsPVNleCksYWxwaGE9MC42KSsNCiAgZmFjZXRfd3JhcCh+VmFyaWFibGUsbmNvbD0yLHNjYWxlcz0iZnJlZSIpKw0KICB0aGVtZV9idygpDQoNCmBgYA0KDQpUYSB0w6FjaCByacOqbmcgcmEgbeG7mXQgZGF0YXNldCBkw6BuaCBjaG8gTmFtIGdp4bubaToNCg0KYGBge3J9DQpkZk09ZmlsdGVyKGRmLFNleD09Ik0iKSU+JWRwbHlyOjpzZWxlY3QoLVNleCkNCmBgYA0KDQpUTEMgY8OzIGxpw6puIGjhu4cgcGhpIHR1eeG6v24gdMOtbmggduG7m2kgQ2hp4buBdSBjYW8gdsOgIHR14buVaSwgbmjGsCDEkeG7kyB0aOG7iyBow6BtIGxvZXNzIHNtb290aGluZyBjaG8gdGjhuqV5Og0KDQpgYGB7cixtZXNzYWdlID0gRkFMU0Usd2FybmluZz1GQUxTRX0NCmRmTSU+JWdhdGhlcihIZWlnaHQsQWdlLGtleT0iUHJlZGljdG9yIix2YWx1ZT0iVmFsdWUiKSU+JQ0KICBnZ3Bsb3QoYWVzKHg9VmFsdWUseT1UTEMpKSsNCiAgZ2VvbV9wb2ludChzaGFwZT0yMSxjb2w9InJlZCIsZmlsbD0icmVkIixhbHBoYT0wLjMpKw0KICBnZW9tX3Ntb290aChhbHBoYT0wLjUsZmlsbD0icmVkIixjb2w9InJlZDQiKSsNCiAgZmFjZXRfd3JhcCh+UHJlZGljdG9yLG5jb2w9MixzY2FsZXM9ImZyZWUiKSsNCiAgdGhlbWVfYncoKQ0KYGBgDQoNCk7hur91IHRhIMaw4bubYyB0w61uaCBUTEMgdGhlbyBBZ2UgdsOgIEhlaWdodCBi4bqxbmcgbeG7mXQgbcO0IGjDrG5oIHR1eeG6v24gdMOtbmgsxJHhu5MgdGjhu4sgdsOgIG3hurd0IHBo4bqzbmcgaOG7k2kgcXV5IHPhur0gbmjGsCB0aOG6vyBuw6B5Og0KDQpgYGB7cixtZXNzYWdlID0gRkFMU0Usd2FybmluZz1GQUxTRX0NCmRmTSU+JWdhdGhlcihIZWlnaHQsQWdlLGtleT0iUHJlZGljdG9yIix2YWx1ZT0iVmFsdWUiKSU+JQ0KICBnZ3Bsb3QoYWVzKHg9VmFsdWUseT1UTEMpKSsNCiAgZ2VvbV9wb2ludChzaGFwZT0yMSxjb2w9ImJsYWNrIixmaWxsPSJncmV5ODAiLGFscGhhPTAuNCkrDQogIGdlb21fc21vb3RoKGFscGhhPTAuNSxmaWxsPSJibHVlIixjb2w9ImJsdWUiLG1ldGhvZD0ibG0iKSsNCiAgZmFjZXRfd3JhcCh+UHJlZGljdG9yLG5jb2w9MixzY2FsZXM9ImZyZWUiKSsNCiAgdGhlbWVfYncoKQ0KYGBgDQoNCmBgYHtyLG1lc3NhZ2UgPSBGQUxTRSx3YXJuaW5nPUZBTFNFfQ0KbGlicmFyeShtZ2N2KQ0KDQpmaXQgPC0gZ2FtKFRMQ35BZ2UrSGVpZ2h0LA0KICAgICAgICAgICBkYXRhPWRmTSkNCg0KdmlzLmdhbShmaXQsdmlldz1jKCJBZ2UiLCJIZWlnaHQiKSx0aGV0YT01MCxwaGk9MzAsbi5ncmlkPTMwLGNvbG9yPSJ0b3BvIix0aWNrdHlwZSA9ICJkZXRhaWxlZCIpIA0KDQpgYGANCg0KTuG6v3Ugw6FwIGThu6VuZyBt4buZdCBow6BtIMSRYSB0aOG7qWMgYuG6rWMgMyBjaG8gSGVpZ2h0IHbDoCBi4bqtYyAyIGNobyBBZ2UsbeG7mXQgbGlua2Z1bmN0aW9uPWxvZyBjaG8gVExDLCDEkeG7kyB0aOG7iyB2w6AgbeG6t3QgcGjhurNuZyBo4buTaSBxdXkgc+G6vSBuaMawIHNhdToNCg0KYGBge3IsbWVzc2FnZSA9IEZBTFNFLHdhcm5pbmc9RkFMU0V9DQpkZk0lPiVnZ3Bsb3QoYWVzKHg9SGVpZ2h0LHk9bG9nKFRMQykpKSsNCiAgZ2VvbV9wb2ludChhbHBoYT0wLjMpKw0KICBnZW9tX3Ntb290aChtZXRob2Q9Z2xtLA0KICAgICAgICAgICAgICBmb3JtdWxhID0geSB+IHBvbHkoeCwzKSxhbHBoYT0wLjMsZmlsbD0idmlvbGV0Iixjb2w9InB1cnBsZSIpKw0KICB0aGVtZV9idygpK2dndGl0bGUoImxuKFRMQyl+SGVpZ2h0XjMrSGVpZ2h0XjIrSGVpZ2h0IikNCg0KYGBgDQoNCmBgYHtyLG1lc3NhZ2UgPSBGQUxTRSx3YXJuaW5nPUZBTFNFfQ0KZGZNJT4lZ2dwbG90KGFlcyh4PUFnZSx5PWxvZyhUTEMpKSkrDQogIGdlb21fcG9pbnQoYWxwaGE9MC4zKSsNCiAgZ2VvbV9zbW9vdGgobWV0aG9kPWdsbSwNCiAgICAgICAgICAgICAgZm9ybXVsYSA9IHkgfiBwb2x5KHgsMiksYWxwaGE9MC4zLGZpbGw9InZpb2xldCIsY29sPSJwdXJwbGUiKSsNCiAgdGhlbWVfYncoKStnZ3RpdGxlKCJsbihUTEMpfkFnZV4yK0FnZSIpDQoNCmBgYA0KDQpgYGB7cixtZXNzYWdlID0gRkFMU0Usd2FybmluZz1GQUxTRX0NCg0KZml0IDwtIGdhbShsb2coVExDKX5BZ2UrcG9seShBZ2UsMikrcG9seShIZWlnaHQsMyksDQogICAgICAgICAgIGRhdGE9ZGZNKQ0KDQp2aXMuZ2FtKGZpdCx2aWV3PWMoIkFnZSIsIkhlaWdodCIpLHRoZXRhPTYwLHBoaT0yNSxuLmdyaWQ9MzAsY29sb3I9InRvcG8iLHRpY2t0eXBlID0gImRldGFpbGVkIikgDQoNCmBgYA0KDQojIELGsOG7m2MgMjogROG7sW5nIG3DtCBow6xuaCBMTVMNCg0KQsOieSBnaeG7nSBjaMO6bmcgdGEgcXVhIGLGsOG7m2MgMiwgZOG7sW5nIG3DtCBow6xuaCBMTVMgdHJvbmcgZ2FtbHNzLiBUcsaw4bubYyBo4bq/dCwgdGEgc+G6vSBkw7luZyBjYXJldCDEkeG7gyBjaGlhIGThu68gbGnhu4d1IHRow6BuaCAyIHBo4bqnbiwgMjAlIGTDoG5oIGNobyBraeG7g20gxJHhu4tuaCB2w6AgODAlIMSR4buDIGThu7FuZyBtw7QgaMOsbmg6DQoNCmBgYHtyLG1lc3NhZ2UgPSBGQUxTRSx3YXJuaW5nPUZBTFNFfQ0KbGlicmFyeShjYXJldCkNCg0Kc2V0LnNlZWQoMTIzKQ0KDQppZE09Y3JlYXRlRGF0YVBhcnRpdGlvbih5PWRmTSRUTEMsIHA9MC44LGxpc3Q9RkFMU0UpDQp0cmFpbk09ZGZNW2lkTSxdDQp0ZXN0TT1kZk1bLWlkTSxdDQoNCmBgYA0KDQojIyBDaGnhur9uIGzGsOG7o2MgMTogVHJp4buHdCB0aG/DoWkgdOG7sSDEkeG7mW5nIGThu7FhIHbDoG8gQklDIHbDoCBBSUMNCg0KZ2FtbHNzIGN1bmcgY+G6pXAgMiBnaeG6o2kgcGjDoXAgcuG6pXQgxJHGoW4gZ2nhuqNuIMSR4buDIHRoxINtIGTDsiB2w6AgY2jhu41uIGzhu41jIG3DtCBow6xuaCB04buRaSDGsHUuIEPhuqMgMiDEkeG7gXUgZOG7sWEgdsOgbyB0acOqdSBjaMOtIEFJQyBob+G6t2MgQklDLCBuaMawbmcga2jDoWMgbmhhdSB24buBIGPDoWNoIHTDoWkgY2jhu41uIG3huqt1IC8ga2nhu4NtIMSR4buLbmguDQoNCkPDoWNoIHRo4bupIG5o4bqldCBraMO0bmcgZMO5bmcgdMOhaSBjaOG7jW4gbeG6q3UsIGPDoWMgbcO0IGjDrG5oIMSRxrDhu6NjIGThu7FuZyB2w6Aga2nhu4NtIHRyYSB0deG6p24gdOG7sSB0csOqbiBDw5lORyBt4buZdCBt4bqrdSBkdXkgbmjhuqV0ICh0aMOtIGThu6UgdHJhaW5NKS4gQ8OhYyBiaeG6v24gc+G7kSB0cm9uZyBmb3JtdWxhIHPhur0gYuG7iyB0cmnhu4d0IHRpw6p1IGThuqduICDEkeG6v24ga2hpIG3DtCBow6xuaCBjw7MgQUlDIHThu5FpIMawdSBoaeG7h24gcmEuIA0KDQpRdXkgdHLDrG5oIFN0ZXB3aXNlIG7DoHkgdGjhu7FjIHJhIHBo4bupYyB04bqhcCBoxqFuIHRhIHTGsOG7n25nLCB2w6wg4bufIMSRw6J5IHRhIGPDsyDEkeG6v24gMyBtw7QgaMOsbmggY29uIGzDoCBMLE0sUzsgdsOgIG3hu5l0IGPDtG5nIHRo4bupYyB24bubaSBuaGnhu4F1IGJp4bq/biBwcmVkaWN0b3IsIGJhbyBn4buTbSBzbW9vdGhpbmcgc3BsaW5lLCBuaMawIHbhuq15IHThuqV0IGPhuqMgdOG7lSBo4bujcCBjw7MgdGjhu4MgZ2nhu69hIGNow7puZyBwaOG6o2kgxJHGsOG7o2Mga2nhu4NtIHRyYSwgYmFvIGfhu5NtIDIgY8OidSBo4buPaTogQ8OzIGPhuqduIG3DtCBow6xuaCBjaG8gU2lnbWEvTnUgaGF5IGtow7RuZyA/IHbDoDogTuG6v3UgY+G6p24sIHRow6wgbuG7mWkgZHVuZyBtw7QgaMOsbmggbMOgIGfDrCA/DQoNClRyb25nIHRyxrDhu51uZyBo4bujcCBuw6B5IE5oaSBtdeG7kW4gY2jhu41uIGzhu41jIG3DtCBow6xuaCBMLE0sUyB04buRaSDGsHUgZOG7sWEgdsOgbyAzIGdp4bqjIMSR4buLbmg6aMOgbSDEkWEgdGjhu6ljIGLhuq1jIDMgY2hvIEhlaWdodCAsIGLhuq1jIDIgY2hvIEFnZSwga8OobSBob+G6t2Mga2jDtG5nIGvDqG0gU3BsaW5lIGNobyBBZ2UsIGPDtG5nIHRo4bupYyBuw6B5IHPhur0gbOG6p24gbMaw4bujdCB0ZXN0IGNobyBNdSB2w6AgU2lnbWEsIE51IMSRxrDhu6NjIG3hurdjIMSR4buLbmggbMOgIGjhurFuZyBz4buRLg0KDQpgYGB7cixtZXNzYWdlID0gRkFMU0Usd2FybmluZz1GQUxTRX0NCg0KbGlicmFyeShnYW1sc3MpDQpuQzwtZGV0ZWN0Q29yZXMoKQ0KDQptMT1nYW1sc3MoZGF0YT10cmFpbk0sDQogICAgICAgICAgVExDfjEsDQogICAgICAgICAgc2lnbWEuZm9ybXVsYSA9IFRMQ34xLA0KICAgICAgICAgIG51LmZvcm11bGEgPSBUTEN+MSwNCiAgICAgICAgICBmYW1pbHk9QkNDRyhtdS5saW5rPSJsb2ciKSwNCiAgICAgICAgICB0cmFjZT1GQUxTRSwNCiAgICAgICAgICBwYXJhbGxlbD0ibXVsdGljb3JlIiwNCiAgICAgICAgICBuY3B1cyA9IG5DKQ0KDQptMj1zdGVwR0FJQ0FsbC5BKG0xLCBzY29wZT1saXN0KGxvd2VyPX4xLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdXBwZXI9fnBvbHkoQWdlLDIpK3BvbHkoSGVpZ2h0LDMpK3BiKEFnZSkpLA0KICAgICAgICAgICAgICAgICBzaWdtYS5zY29wZSA9IGxpc3QobG93ZXI9fjEsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB1cHBlcj1+cG9seShBZ2UsMikrcG9seShIZWlnaHQsMykrcGIoQWdlKSksDQogICAgICAgICAgICAgICAgIGs9bG9nKGxlbmd0aCh0cmFpbk0pKSwNCiAgICAgICAgICAgICAgICAgdHJhY2U9RkFMU0UsDQogICAgICAgICAgICAgICAgIHBhcmFsbGVsPSJtdWx0aWNvcmUiLA0KICAgICAgICAgICAgICAgICBuY3B1cyA9IG5DDQogICAgICAgICAgICAgICkNCmBgYA0KDQpL4bq/dCBxdeG6oyBTdGVwd2lzZSBuaMawIHNhdToNCg0KTcO0IGjDrG5oIGNobyBNdSBjw7MgY8O0bmcgdGjhu6ljOiBNdSB+IHBvbHkoSGVpZ2h0LDMpK0FnZSsgTXVTcGxpbmUuDQoNCk3DtCBow6xuaCBjaG8gU2lnbWEgY2jhu4kgZ+G7k20gU2lnbWEgfiBBZ2UgKyBTaWdtU3BsaW4uDQoNCk51ID0gaOG6sW5nIHPhu5EuDQoNCmBgYHtyLG1lc3NhZ2UgPSBGQUxTRSx3YXJuaW5nPUZBTFNFfQ0KDQpzdW1tYXJ5KG0yKQ0KUnNxKG0yLHR5cGU9ImJvdGgiKQ0KDQpgYGANCg0KYGBge3IsbWVzc2FnZSA9IEZBTFNFLHdhcm5pbmc9RkFMU0V9DQpwbG90KG0yKQ0KDQpgYGANCg0KRMO5IFIyIGtow6EgdGjhuqVwLCBuaMawbmcgbcO0IGjDrG5oIHThu5FpIMawdSBtw6AgU3RlcHdpc2UgY2jhu41uIMSRxrDhu6NjIGtow7RuZyBjaG8gdGjhuqV5IHbhuqVuIMSR4buBIGfDrCBuZ2hpw6ptIHRy4buNbmcuDQoNCiMjIENoaeG6v24gbMaw4bujYyAyOiBLaeG7g20gY2jhu6luZyBjaMOpbyBLIGJsb2NrDQoNCkPDoWNoIGNo4buNbiBs4buNYyBtw7QgaMOsbmggdGjhu6kgaGFpIHBo4bupYyB04bqhcCBoxqFuLCB0aMaw4budbmcgZMO5bmcgxJHhu4Mga2nhu4NtIMSR4buLbmgga+G6v3QgcXXhuqMgY+G7p2EgU3RlcHdpc2UuIE7DsyBjw7MgYuG6o24gY2jhuqV0IGzDoCBt4buZdCBxdXkgdHLDrG5oIEstZm9sZHMgY3Jvc3MgdmFsaWRhdGlvbi4NCg0KTmd1ecOqbiB04bqvYyBs4buxYSBjaOG7jW4gbcO0IGjDrG5oIHbhuqtuIGThu7FhIHRyw6puIHRpw6p1IGNow60gQUlDIHbDoCBCSUMsIHR1eSBuaGnDqm4gdGEgcGjhuqNpIHjDoWMgxJHhu4tuaCB0csaw4bubYyBuaOG7r25nIG3DtCBow6xuaCBuw6BvIGPhuqduIMSRxrDhu6NjIGtp4buDbSB0cmEsIHRow60gZOG7pSB0YSBjw7MgMyBtw7QgaMOsbmggTTEsIE0yLCBNMy4gUXV5IHRyw6xuaCBjcm9zc3ZhbGlkYXRpb24gc+G6vSDEkcaw4bujYyDDoXAgZOG7pW5nIGNobyBt4buXaSBtw7QgaMOsbmgsIHbDoCB0w61uaCBBSUMgdHJ1bmcgYsOsbmguIFNhdSDEkcOzIHRhIHNvIHPDoW5oIEFJQyBuw6B5IGdp4buvYSAzIG3DtCBow6xuaCwgbcO0IGjDrG5oIG7DoG8gY8OzIEFJQyB0aOG6pXAgbmjhuqV0IHPhur0gxJHGsOG7o2MgY2jhu41uLg0KDQpN4buXaSBtw7QgaMOsbmggc+G6vSDEkcaw4bujYyBk4buxbmcgdHLDqm4gKGstMSkgYmxvY2sgZOG7ryBsaeG7h3UgdsOgIGtp4buDbSDEkeG7i25oIMSR4buZYyBs4bqtcCB0csOqbiBibG9jayBjw7JuIGzhuqFpLCBxdXkgdHLDrG5oIG7DoHkgbOG6t3AgbOG6oWkgSyBs4bqnbiwgbmjGsCB24bqteSBt4buXaSBt4buZdCBibG9jayB0cm9uZyBz4buRIEsgYmxvY2sgxJHhu4F1IMSRxrDhu6NjIGTDuW5nIGzDoG0gdOG6rXAga2nhu4NtIMSR4buLbmggMSBs4bqnbiB2w6AgxJHhu4F1IHRoYW0gZ2lhIHbDoG8gdmnhu4djIGh14bqlbiBsdXnhu4duIG3DtCBow6xuaC4NCg0KVGjDrSBk4bulLCBOaGkgxJHhurd0IG3DtCBow6xuaCBnMSB24bubaSBjw7RuZyB0aOG7qWMgdOG7kWkgxrB1IG5oxrAgdHLDqm46DQoNClRMQ35wb2x5KEhlaWdodCwzKStBZ2UrcGIoQWdlKSwNCiAgICAgICAgIHNpZ21hLmZvcm11bGE9VExDfkFnZStwYihBZ2UpLA0KDQpTYXUgxJHDsyB0aGF5IMSR4buVaSBjw7RuZyB0aOG7qWMgxJHhu4MgdOG6oW8gcmEgdGjDqm0gMyBtw7QgaMOsbmggZzIsZzMsZzQgxJHGoW4gZ2nhuqNuIGjGoW4sIHRow60gZOG7pSBnaeG6o20gYuG6rWMgxJFhIHRo4bupYyBj4bunYSBIZWlnaHQgeHXhu5FuZyBjw7JuIDIsIGhheSB0xINuZyBi4bqtYyDEkWEgdGjhu6ljIGNobyBBZ2UgdOG7qyAxIGzDqm4gMiwgaGF5IGLhu48gaOG6s24gQWdlIHbDoCBjaOG7iSBnaeG7ryBs4bqhaSBIZWlnaHQuDQogICAgICAgICAgICANCmBgYHtyLG1lc3NhZ2UgPSBGQUxTRSx3YXJuaW5nPUZBTFNFfQ0KDQpyYW5kMSA8LSBzYW1wbGUgKDUsbnJvdyh0cmFpbk0pLHJlcGxhY2U9VFJVRSkNCg0KZzE9Z2FtbHNzQ1YocmFuZD1yYW5kMSwNCiAgICAgICAgICAgIGRhdGE9dHJhaW5NLA0KICAgICAgICAgICAgVExDfnBvbHkoSGVpZ2h0LDMpK0FnZStwYihBZ2UpLA0KICAgICAgICAgICAgc2lnbWEuZm9ybXVsYT1UTEN+QWdlK3BiKEFnZSksDQogICAgICAgICAgICBmYW1pbHk9QkNDRyhtdS5saW5rPSJsb2ciKSwNCiAgICAgICAgICAgIHRyYWNlPUZBTFNFLA0KICAgICAgICAgICAgcGFyYWxsZWw9Im11bHRpY29yZSIsDQogICAgICAgICAgICBuY3B1cyA9IG5DDQogICAgICAgICAgICApDQoNCmcyPWdhbWxzc0NWKHJhbmQ9cmFuZDEsDQogICAgICAgICAgICBkYXRhPXRyYWluTSwNCiAgICAgICAgICAgIFRMQ35wb2x5KEhlaWdodCwzKStwb2x5KEFnZSwyKStwYihBZ2UpLA0KICAgICAgICAgICAgc2lnbWEuZm9ybXVsYT1UTEN+cG9seShBZ2UsMikrcGIoQWdlKSwNCiAgICAgICAgICAgIGZhbWlseT1CQ0NHKG11Lmxpbms9ImxvZyIpLA0KICAgICAgICAgICAgdHJhY2U9RkFMU0UsDQogICAgICAgICAgICBwYXJhbGxlbD0ibXVsdGljb3JlIiwNCiAgICAgICAgICAgIG5jcHVzID0gbkMNCikNCg0KZzM9Z2FtbHNzQ1YocmFuZD1yYW5kMSwNCiAgICAgICAgICAgIGRhdGE9dHJhaW5NLA0KICAgICAgICAgICAgVExDfnBvbHkoQWdlLDIpK3BiKEFnZSksDQogICAgICAgICAgICBzaWdtYS5mb3JtdWxhPVRMQ35BZ2UrcGIoQWdlKSwNCiAgICAgICAgICAgIGZhbWlseT1CQ0NHKG11Lmxpbms9ImxvZyIpLA0KICAgICAgICAgICAgdHJhY2U9RkFMU0UsDQogICAgICAgICAgICBwYXJhbGxlbD0ibXVsdGljb3JlIiwNCiAgICAgICAgICAgIG5jcHVzID0gbkMpDQogICAgICAgICAgICANCmc0PWdhbWxzc0NWKHJhbmQ9cmFuZDEsDQogICAgICAgICAgICBkYXRhPXRyYWluTSwNCiAgICAgICAgICAgIFRMQ35wb2x5KEhlaWdodCwzKStwYihBZ2UpLA0KICAgICAgICAgICAgc2lnbWEuZm9ybXVsYT1UTEN+QWdlK3BiKEFnZSksDQogICAgICAgICAgICBmYW1pbHk9QkNDRyhtdS5saW5rPSJsb2ciKSwNCiAgICAgICAgICAgIHRyYWNlPUZBTFNFLA0KICAgICAgICAgICAgcGFyYWxsZWw9Im11bHRpY29yZSIsDQogICAgICAgICAgICBuY3B1cyA9IG5DDQopDQoNCkNWKGcxLGcyLGczLGc0KQ0KDQpgYGANCg0KS+G6v3QgcXXhuqMgY+G7p2EgQ1Yga2jhurNuZyDEkeG7i25oIHLhurFuZyBtw7QgaMOsbmggRzEgduG6q24gbMOgIHThu5FpIMawdQ0KDQojIELGsOG7m2MgMzogQmnhu4duIGx14bqtbiBtw7QgaMOsbmgNCg0KU2F1IGtoaSBjw7MgbcO0IGjDrG5oIHThu5FpIMawdSwgxJHDonkgbMOgIGzDumMgdGEga2hhaSB0aMOhYyBuw7MsIHRyxrDhu5tjIHRpw6puIE5oaSBtdeG7kW4ga2nhu4NtIHRyYSBs4bqhaSBtw7QgaMOsbmggbsOgeSB0csOqbiB0ZXN0c2V0LCBt4buZdCBjw6FjaCDEkeG7mWMgbOG6rXAsIHbDoCB0w61uaCBuaOG7r25nIGNo4buJIHPhu5EgbmjGsCBSMixybXNlOyBtc2UuLi4NCg0KYGBge3IsbWVzc2FnZSA9IEZBTFNFLHdhcm5pbmc9RkFMU0V9DQpsaWJyYXJ5KG1scikNCg0KcmVnci50YXNrPSBtbHI6Om1ha2VSZWdyVGFzayhpZCA9ICJkZk0iLCBkYXRhPXRlc3RNLCB0YXJnZXQgPSAiVExDIikNCnJlZ3IubHJuID0gbWFrZUxlYXJuZXIoInJlZ3IuZ2xtIikNCg0KZHVtbXk9bWxyOjp0cmFpbihyZWdyLmxybixyZWdyLnRhc2spDQoNCmR1bXByZWQ9cHJlZGljdChkdW1teSxyZWdyLnRhc2spDQpkdW1wcmVkJGRhdGEkcmVzcG9uc2U8LXByZWRpY3QobTIsbmV3ZGF0YT10ZXN0TSx0eXBlPSJyZXNwb25zZSIpDQoNCm1ldHM9bGlzdChyc3EsbXNlLHJtc2UpDQoNCnBlcmZvcm1hbmNlKGR1bXByZWQsbWVhc3VyZXMgPW1ldHMpDQpgYGANCg0KVGnhur9wIHRoZW8sIE5oaSBkw7luZyBow6BtIHByZWRpY3RBbGwgxJHhu4MgxrDhu5tjIHTDrW5oIGPhuqMgMyB0aGFtIHPhu5EgTCxNLFMgdHJvbmcgbcO0IGjDrG5oLiBDaOG7lyBs4bqvdCBsw6lvIGR1eSBuaOG6pXQg4bufIMSRw6J5IMSRw7MgbMOgIE51IHRyb25nIEJDQ0cgY2jDrW5oIGzDoCBMYW1iZGEgdHJvbmcgbWV0aG9kIExNUzoNCg0KYGBge3IsbWVzc2FnZSA9IEZBTFNFLHdhcm5pbmc9RkFMU0V9DQpsbXM9cHJlZGljdEFsbChtMixuZXdkYXRhPWRmTSx0eXBlPSJyZXNwb25zZSIpDQoNCnBkZj1kZk0lPiVtdXRhdGUoTXU9bG1zJG11LA0KICAgICAgICAgICAgICAgICBMTE49ZXhwKGxvZyhsbXMkbXUpKyhsb2coMS0xLjY0NSpsbXMkbnUqbG1zJHNpZ21hKSkvbG1zJG51KSwNCiAgICAgICAgICAgICAgICAgVUxOPWV4cChsb2cobG1zJG11KSsobG9nKDErMS42NDUqbG1zJG51KmxtcyRzaWdtYSkpL2xtcyRudSksDQogICAgICAgICAgICAgICAgIFpfc2NvcmU9KChUTEMvbG1zJG11KV4obG1zJG51KS0xKS8obG1zJG51KmxtcyRzaWdtYSksDQogICAgICAgICAgICAgICAgIHBjUHJlZD0xMDAqVExDL2xtcyRtdSwNCiAgICAgICAgICAgICAgICAgY3V0b2Y4MD0wLjgqbG1zJG11KQ0KDQpwZGYlPiVoZWFkKCklPiVrbml0cjo6a2FibGUoKQ0KYGBgDQoNClNhdSDEkcOzLCBOaGkgdOG6oW8gcmEgMiBiaeG6v24gbeG7m2kgbMOgIFRvbGVyYW5jZVpzY29yZSB2w6AgVG9sZXJhbmNlJXByZWQsIMSR4buDIGtp4buDbSB0cmEgbGnhu4d1IHRyb25nIDI0MyBuZ8aw4budaSBiw6xuaCB0aMaw4budbmcgbsOgeSwgbGnhu4d1IG3DtCBow6xuaCBjw7MgcGjDom4gbG/huqFpIG5o4bqnbSBhaSDEkcOzIHRow6BuaCBi4buHbmggbMO9IGhheSBraMO0bmcgPyBOaMawIHbhuq15IFotc2NvcmUga2jDtG5nIMSRxrDhu6NjIHRo4bqlcCBoxqFuIC0xLjY0NSBjw7JuICVQcmVkIGtow7RuZyDEkcaw4bujYyB0aOG6pXAgaMahbiA4MCUNCg0KYGBge3IsbWVzc2FnZSA9IEZBTFNFLHdhcm5pbmc9RkFMU0V9DQpwZGYkVG9sZXJhbmNlWnNjb3JlPWlmX2Vsc2UocGRmJFpfc2NvcmU8KC0xLjY0NSksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAiVW5hY2NlcHRhYmxlIiwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAiQWNjZXB0YWJsZSIpDQoNCnBkZiRUb2xlcmFuY2VQY1ByZWQ9aWZfZWxzZShwZGYkcGNQcmVkPDgwLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIlVuYWNjZXB0YWJsZSIsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIkFjY2VwdGFibGUiKQ0KYGBgDQoNClRhIHPhur0gbmjDrG4ga+G6v3QgcXXhuqMgbsOgeSBt4buZdCBjw6FjaCB0cuG7sWMgcXVhbiBuaMawIHNhdToNCg0KYGBge3IsbWVzc2FnZSA9IEZBTFNFLHdhcm5pbmc9RkFMU0V9DQpwZGYlPiVnZ3Bsb3QoYWVzKHg9QWdlKSkrDQogIGdlb21fcG9pbnQoYWVzKHk9VExDKSxjb2xvcj0iZ3JleTgwIikrDQogIGdlb21fc21vb3RoKGFlcyh5PVVMTiksc2U9Rixjb2xvcj0iYmx1ZSIsbGluZXR5cGU9MikrDQogIGdlb21fc21vb3RoKGFlcyh5PUxMTiksc2U9Rixjb2xvcj0icmVkIixsaW5ldHlwZT0yKSsNCiAgZ2VvbV9zbW9vdGgoYWVzKHk9TXUpLHNlPUYsY29sb3I9ImJsYWNrIikrDQogIGdlb21fc21vb3RoKGFlcyh5PWN1dG9mODApLHNlPUYsY29sb3I9Im9yYW5nZSIpKw0KICBnZW9tX3BvaW50KGFlcyh5PU11LGNvbG9yPVRvbGVyYW5jZVpzY29yZSksc2l6ZT0yLGFscGhhPTAuNikrDQogIHNjYWxlX2NvbG9yX2JyZXdlcihwYWxldHRlID0gIlNldDEiLGRpcmVjdGlvbiA9IC0xKSsNCiAgdGhlbWVfYncoKStnZ3RpdGxlKCJNdSwlcHJlZCxMTE4gYW5kIFVMTiBmb3IgTWFsZXMiKQ0KDQpgYGANCg0KVHLDqm4gaMOsbmggduG6vTogbcOgdSDEkWVuIGzDoCBNdSwgWGFuaCBsw6AgVUxOLCDEkeG7jyBsw6AgTExOLCBtw6B1IGNhbSBsw6AgbmfGsOG7oW5nIDgwJSBj4bunYSBNdQ0KDQpgYGB7cixtZXNzYWdlID0gRkFMU0Usd2FybmluZz1GQUxTRX0NCnBkZiU+JWdncGxvdChhZXMoeD1IZWlnaHQpKSsNCiAgZ2VvbV9wb2ludChhZXMoeT1UTEMpLGNvbG9yPSJncmV5ODAiKSsNCiAgZ2VvbV9zbW9vdGgoYWVzKHk9VUxOKSxzZT1GLGNvbG9yPSJibHVlIixsaW5ldHlwZT0yKSsNCiAgZ2VvbV9zbW9vdGgoYWVzKHk9TExOKSxzZT1GLGNvbG9yPSJyZWQiLGxpbmV0eXBlPTIpKw0KICBnZW9tX3Ntb290aChhZXMoeT1NdSksc2U9Rixjb2xvcj0iYmxhY2siKSsNCiAgZ2VvbV9zbW9vdGgoYWVzKHk9Y3V0b2Y4MCksc2U9Rixjb2xvcj0ib3JhbmdlIikrDQogIGdlb21fcG9pbnQoYWVzKHk9TXUsY29sb3I9VG9sZXJhbmNlWnNjb3JlKSxzaXplPTIsYWxwaGE9MC42KSsNCiAgc2NhbGVfY29sb3JfYnJld2VyKHBhbGV0dGUgPSAiU2V0MSIsZGlyZWN0aW9uID0gLTEpKw0KICB0aGVtZV9idygpK2dndGl0bGUoIk1lYW4gcHJlZGljdGVkLExMTiBhbmQgVUxOIGZvciBNYWxlcyIpDQpgYGANCg0KVGEgY8OzIHRo4buDIGvhur90IGx14bqtbiBy4bqxbmcgbcO0IGjDrG5oIMSRw6MgdGjhu4MgaGnhu4duIGtow6EgdHJ1bmcgdGjDoG5oIGtodXluaCBoxrDhu5tuZyBiaeG6v24gdGhpw6puIGPhu6dhIFRMQyB0aGVvIEFnZSB2w6AgSGVpZ2h0LiBDaOG7iSBjw7MgMSBz4buRIMOtdCBjw6EgdGjhu4MgYuG7iyBwaMOibiBsb+G6oWkgbmjhuqdtIHRyw6puIHRo4buxYyB04bq/Lg0KDQpLaGkgY2jhu5NuZyBs4bqvcCAyIHBow6JuIHBo4buRaSBj4bunYSBNdSAobcOgdSBjYW0pIHbDoCBj4bunYSBUTEMgdGjhu7FjIHThur8gKG3DoHUgxJHhu48pLHRhIHRo4bqleSBtw7QgaMOsbmggY2hvIHBow6lwIMaw4bubYyB0w61uaCBjaMOtbmggeMOhYyB24buLIHRyw60gdHJ1bmcgdMOibSBj4bunYSBUTEMgdHJvbmcgbeG6q3UsIG5oxrBuZyBjw7MgbeG7mXQgcGjhuqduIG7DoG8gxJHDsyBUTEMgxJHDoyBi4buLIMSRw6FuaCBnacOhIHRo4bqlcCBoxqFuIHRo4buxYyB04bq/LiANCg0KYGBge3IsbWVzc2FnZSA9IEZBTFNFLHdhcm5pbmc9RkFMU0V9DQpwZGYlPiVnZ3Bsb3QoKSsNCiAgZ2VvbV9kZW5zaXR5KGFlcyh4PU11KSxjb2xvcj0ib3JhbmdlIixsaW5ldHlwZT0xLGZpbGw9Im9yYW5nZSIsYWxwaGE9MC41KSsNCiAgZ2VvbV9kZW5zaXR5KGFlcyh4PVRMQyksY29sb3I9InJlZCIsbGluZXR5cGU9MixmaWxsPSJyZWQiLGFscGhhPTAuMikrDQogIHRoZW1lX2J3KCkNCmBgYA0KDQpLaGkgY2h1eeG7g24gY+G6oyAyIHRow6BuaCB0aGFuZyDEkW8gWi1zY29yZSwgdGjDrCBaLXNjb3JlIMaw4bubYyB0w61uaCBi4bufaSBtw7QgaMOsbmggaG/DoG4gdG/DoG4gIHTGsMahbmcgaOG7o3AgduG7m2kgWi1zY29yZSBj4bunYSBUTEMgdHLDqm4gdGjhu7FjIHThur8uDQoNCmBgYHtyfQ0KcGRmJT4lZ2dwbG90KCkrDQogIGdlb21fZGVuc2l0eShhZXMoeD1aX3Njb3JlKSxjb2xvcj0icmVkIixsaW5ldHlwZT0xLGZpbGw9InJlZCIsYWxwaGE9MC4zKSsNCiAgZ2VvbV9kZW5zaXR5KGFlcyh4PXNjYWxlKFRMQykpLGNvbG9yPSJibHVlIixsaW5ldHlwZT0xLGZpbGw9ImJsdWUiLGFscGhhPTAuMykrDQogIHRoZW1lX2J3KCkrZ2d0aXRsZSgiUHJlZGljdGVkIChSZWQpIHZzIFRydXRoIChCbHVlKSIpDQpgYGANCg0KDQojIELGsOG7m2MgNDogTmdv4bqhaSBzdXkNCg0KQ3Xhu5FpIGPDuW5nLCBOaGkgdMOyIG3DsiBtdeG7kW4gYmnhur90IGxp4buHdSB0YSBjw7MgdGjhu4Mgbmdv4bqhaSBzdXkgbcO0IGjDrG5oIG7DoHkgY2hvIG5o4buvbmcgbmfGsOG7nWkgZ2nDoCA+IDg1IHR14buVaSB2w6Agbmjhu69uZyB0aGnhur91IG5pw6puIDwgMTggdHXhu5VpIGhheSBraMO0bmcgPyDEkOG7gyBsw6BtIHZp4buHYyBuw6B5IE5oaSB04bqhbyByYSBk4buvIGxp4buHdSBtw7QgcGjhu49uZzoNCg0KYGBge3IsbWVzc2FnZSA9IEZBTFNFLHdhcm5pbmc9RkFMU0V9DQpvbGRtZW5kZj1maWx0ZXIoZGZNLEFnZT09bWF4KGRmTSRBZ2UpKQ0KYm95ZGY9ZmlsdGVyKGRmTSxBZ2U9PW1pbihkZk0kQWdlKSkNCg0KZXh0ZGY9ZGF0YV9mcmFtZShIZWlnaHQ9cm5vcm0ob2xkbWVuZGYkSGVpZ2h0LTUsbj0yMDAsc2Q9MTApLA0KICAgICAgICAgICAgICAgICBBZ2U9cm5vcm0oOTIsNSxuPTIwMCkpDQoNCmV4cHJlZD1wcmVkaWN0QWxsKG0yLA0KICAgICAgICAgICAgICAgICAgbmV3ZGF0YT1leHRkZiwNCiAgICAgICAgICAgICAgICAgIHR5cGU9InJlc3BvbnNlIikNCg0KZXh0ZGY9ZXh0ZGYlPiVtdXRhdGUoTXU9ZXhwcmVkJG11LA0KICAgICAgICAgICAgICAgICBMTE49ZXhwKGxvZyhleHByZWQkbXUpKw0KICAgICAgICAgICAgICAgICAgICAgICAgICAgKGxvZygxLTEuNjQ1KmV4cHJlZCRudSpleHByZWQkc2lnbWEpKS9leHByZWQkbnUpLA0KICAgICAgICAgICAgICAgICBVTE49ZXhwKGxvZyhleHByZWQkbXUpKw0KICAgICAgICAgICAgICAgICAgICAgICAgICAgKGxvZygxKzEuNjQ1KmV4cHJlZCRudSpleHByZWQkc2lnbWEpKS9leHByZWQkbnUpLA0KICAgICAgICAgICAgICAgICBjdXRvZjgwPTAuOCpleHByZWQkbXUpDQoNCnlvdW5nZGY9ZGF0YV9mcmFtZShIZWlnaHQ9cm5vcm0obWVhbihib3lkZiRIZWlnaHQpLTEwLG49MjAwLHNkPTEwKSwNCiAgICAgICAgICAgICAgICAgQWdlPXJub3JtKDE1LDMsbj0yMDApKQ0KDQp5cHJlZD1wcmVkaWN0QWxsKG0yLA0KICAgICAgICAgICAgICAgICAgbmV3ZGF0YT15b3VuZ2RmLA0KICAgICAgICAgICAgICAgICAgdHlwZT0icmVzcG9uc2UiKQ0KDQp5ZGY9eW91bmdkZiU+JW11dGF0ZShNdT15cHJlZCRtdSwNCiAgICAgICAgICAgICAgICAgICAgIExMTj1leHAobG9nKHlwcmVkJG11KSsNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAobG9nKDEtMS42NDUqeXByZWQkbnUqeXByZWQkc2lnbWEpKS95cHJlZCRudSksDQogICAgICAgICAgICAgICAgICAgICBVTE49ZXhwKGxvZyh5cHJlZCRtdSkrDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKGxvZygxKzEuNjQ1KnlwcmVkJG51KnlwcmVkJHNpZ21hKSkveXByZWQkbnUpLA0KICAgICAgICAgICAgICAgICAgICAgY3V0b2Y4MD0wLjgqeXByZWQkbXUpDQoNCmV4dGRmPWV4dGRmJT4lbXV0YXRlKE11PWV4cHJlZCRtdSwNCiAgICAgICAgICAgICAgICAgICAgIExMTj1leHAobG9nKGV4cHJlZCRtdSkrDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKGxvZygxLTEuNjQ1KmV4cHJlZCRudSpleHByZWQkc2lnbWEpKS9leHByZWQkbnUpLA0KICAgICAgICAgICAgICAgICAgICAgVUxOPWV4cChsb2coZXhwcmVkJG11KSsNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAobG9nKDErMS42NDUqZXhwcmVkJG51KmV4cHJlZCRzaWdtYSkpL2V4cHJlZCRudSksDQogICAgICAgICAgICAgICAgICAgICBjdXRvZjgwPTAuOCpleHByZWQkbXUpDQoNCmV4dHJhZGY9cmJpbmQoZXh0ZGYseWRmLA0KICAgICAgICAgICAgICBkcGx5cjo6c2VsZWN0KHBkZixjb2xuYW1lcyhleHRkZikpKQ0KDQpleHRyYWRmPWV4dHJhZGYlPiVtdXRhdGUoRXh0cmE9aWZfZWxzZShBZ2U8MTggfCBBZ2U+ODUsIkV4dHJhcG9sYXRpb24iLCJUcnVlIikpDQoNCmV4dHJhZGYlPiVnZ3Bsb3QoYWVzKHg9QWdlKSkrDQogIGdlb21fc21vb3RoKGFlcyh5PVVMTiksc2U9Rixjb2xvcj0iYmx1ZSIsbGluZXR5cGU9MikrDQogIGdlb21fc21vb3RoKGFlcyh5PUxMTiksc2U9Rixjb2xvcj0icmVkIixsaW5ldHlwZT0yKSsNCiAgZ2VvbV9zbW9vdGgoYWVzKHk9TXUpLHNlPUYsY29sb3I9ImJsYWNrIikrDQogIGdlb21fc21vb3RoKGFlcyh5PWN1dG9mODApLHNlPUYsY29sb3I9Im9yYW5nZSIpKw0KICBnZW9tX3BvaW50KGFlcyh5PU11LGNvbD1FeHRyYSxmaWxsPUV4dHJhKSxzaGFwZT0yMSxzaXplPTIsYWxwaGE9MC41LHNob3cubGVnZW5kID0gRikrDQogIGdlb21fdmxpbmUoeGludGVyY2VwdCA9IGMoMTgsODUpLGNvbD1jKCJibHVlNCIsInJlZDQiKSxsaW5ldHlwZT0xKSsNCiAgc2NhbGVfeV9jb250aW51b3VzKCJUTEMiKSsNCiAgc2NhbGVfY29sb3JfYnJld2VyKHBhbGV0dGUgPSAiU2V0MSIpKw0KICBzY2FsZV9maWxsX2JyZXdlcihwYWxldHRlID0gIlNldDEiKSsNCiAgdGhlbWVfYncoKStnZ3RpdGxlKCJFeHRyYXBvbGF0aW9uIGZvciA8MTggYW5kID44NSIpDQpgYGANCg0KS+G6v3QgcXXhuqMgbcO0IHBo4buPbmcgY2hvIHBow6lwIGThu7EgYsOhbyBr4bq/dCBxdeG6oyBuZ2/huqFpIHN1eSBtw7QgaMOsbmggY2hvIG5o4buvbmcgxJHhu5FpIHTGsOG7o25nIG5nb8OgaSBnaeG7m2kgaOG6oW4gVHXhu5VpIHRyb25nIG3huqt1IGto4bqjbyBzw6F0LiDEkMOieSBjaOG7iSBsw6Aga+G6v3QgcXXhuqMgbcO0IHBo4buPbmcsIHbhu5tpIGdp4bqjIMSR4buLbmggY2hp4buBdSBjYW8ga2jDtG5nIGJp4bq/biDEkeG7lWkgcXXDoSBs4bubbiB2w6AgY8OzIHBow6JuIHBo4buRaSBjaHXhuqluLiBUcsOqbiB0aOG7sWMgdOG6vyBuaOG7r25nIHRoaeG6v3UgbmnDqm4gdOG7qyAxNC0xNyB0deG7lWkgY8OzIHRo4buDIHTEg25nIHRyxrDhu59uZyBjaGnhu4F1IGNhbyBuaGnhu4F1IGjGoW4sIHTGsMahbmcgdOG7sSBuZ8aw4budaSBnacOgID4gODUgdHXhu5VpIGPDsyBraHV5bmggaMaw4bubbmcgZ2nhuqNtIGNoaeG7gXUgY2FvLg0KDQojIFThu5VuZyBr4bq/dA0KDQpCw6BpIHRo4buxYyBow6BuaCDEkeG6v24gxJHDonkgbMOgIGvhur90IHRow7pjLiBOaGkgxJHDoyBjaHV54buDbiDEkeG6v24gY8OhYyBi4bqhbiB0b8OgbiBi4buZIG5ndXnDqm4gbMO9IMSR4bqxbmcgc2F1IG3DtCBow6xuaCBMTVMgdsOgIG5o4buvbmcgYsOtIHF1eeG6v3QgxJHhu4MgeMOieSBk4buxbmcgbsOzIHRyb25nIGdhbWxzcy4gTmjhu69uZyDEkWnhu4F1IG7DoHkgY2jGsGEgYmFvIGdp4budIMSRxrDhu6NjIGdp4bqjaSB0aMOtY2ggxJHhuqd5IMSR4bunIGLhu59pIGPDoWMgdOG7lSBjaOG7qWMgbmjGsCBHTEkgdsOgIGPDoWMgbmjDs20gbmdoacOqbiBj4bupdSBraMOhYy4gQsOieSBnaeG7nSB0aMOsIGjhu40ga2jDtG5nIGPDsm4gxJHhu5ljIHF1eeG7gW4gbuG7r2EuIE7hur91IGPDsyDEkeG7pyBk4buvIGxp4buHdSB04buRdCB0cm9uZyB0YXksIGPDoWMgYuG6oW4gaG/DoG4gdG/DoG4gY8OzIHRo4buDIGzDoG0gxJHGsOG7o2Mgbmjhu69uZyBtw7QgaMOsbmggdMSDbmcgdHLGsOG7n25nIHbDoCBtw7QgaMOsbmggxrDhu5tjIHTDrW5oIG5nxrDhu6FuZyBnacOhIHRy4buLIGNo4bqpbiDEkW/DoW4gY2hvIHjDqXQgbmdoaeG7h20gc2luaCBsw70sIHNpbmggaMOzYSBjaG8gbmfGsOG7nWkgVmnhu4d0Lg0KDQpD4bqjbSDGoW4gY8OhYyBi4bqhbiB2w6AgaOG6uW4gZ+G6t3AgbOG6oWkgdHJvbmcgbeG7mXQgdHV0b3JpYWwga2jDoWMuDQoNCioqVMOgaSBsaeG7h3UgdGhhbSBraOG6o28qKiANCg0KMSkgUmlnYnkgUkEsIFN0YXNpbm9wb3Vsb3MgRE0uIEdlbmVyYWxpemVkIGFkZGl0aXZlIG1vZGVscyBmb3IgbG9jYXRpb24sIHNjYWxlIGFuZCBzaGFwZSAod2l0aCBkaXNjdXNzaW9uKS4gQXBwbCBTdGF0aXN0IDIwMDU7IDU0OiA1MDctNTU0Lg0KU3Rhbm9qZXZpYyBTLCBXYWRlIEEsIFN0b2NrcyBKLCBldCBhbC4gUmVmZXJlbmNlIHJhbmdlcyBmb3Igc3Bpcm9tZXRyeSBhY3Jvc3MgYWxsIGFnZXMuIEEgbmV3IGFwcHJvYWNoLiBBbSBKIFJlc3BpciBDcml0IENhcmUgTWVkIDIwMDg7IDE3NzogMjUz4oCTMjYwLg0KDQoyKSBRdWFuamVyIFBILCBTdGFub2pldmljIFMsIENvbGUgVEosIGV0IGFsLiBhbmQgdGhlIEVSUyBHbG9iYWwgTHVuZyBGdW5jdGlvbiBJbml0aWF0aXZlLiBNdWx0aS1ldGhuaWMgcmVmZXJlbmNlIHZhbHVlcyBmb3Igc3Bpcm9tZXRyeSBmb3IgdGhlIDMtOTUgeWVhcnMgYWdlIHJhbmdlOiB0aGUgR2xvYmFsIEx1bmcgRnVuY3Rpb24gMjAxMiBlcXVhdGlvbnMuIEV1ciBSZXNwaXIgSiAyMDEyOyA0MDogMTMyNC0xMzQzLg0KDQozKSBTYW5kZXJjb2NrIEcuLCBWb3NzIEMuLCBDb2hlbiBELiwgVGF5bG9yIE0uLCBhbmQgU3Rhc2lub3BvdWxvcyBELiBNLiAoMjAxMikgQ2VudGlsZSBjdXJ2ZXMgYW5kIG5vcm1hdGl2ZSB2YWx1ZXMgZm9yIHRoZSB0d2VudHkgbWV0cmUgc2h1dHRsZS1ydW4gdGVzdCBpbiBFbmdsaXNoIHNjaG9vbGNoaWxkcmVuLiBKb3VybmFsIG9mIFNwb3J0cyBTY2llbmNlcywgRE9JOjEwLjEwODAvMDI2NDA0MTQuMjAxMi42NjAxODUNCg0KNCkgV0hPIE11bHRpY2VudHJlIEdyb3d0aCBSZWZlcmVuY2UgU3R1ZHkgR3JvdXAgKDIwMDcpIFdITyBDaGlsZCBHcm93dGggU3RhbmRhcmRzOiBIZWFkIGNpcmN1bWZlcmVuY2UtZm9yLWFnZSwgYXJtIGNpcmN1bWZlcmVuY2UtZm9yLWFnZSwgdHJpY2VwcyBjaXJjdW1mZXJlbmNlLWZvci1hZ2UgYW5kIHN1YnNjYXB1bGFyIHNraW5mb3JkLWZvci1hZ2U6IE1ldGhvZHMgYW5kIGRldmVsb3BtZW50LiBHZW5ldmE6IFdvcmxkIEhlYWx0aCBPcmdhbml6YXRpb24uDQpBdmFpbGFibGUgIGZyb206ICBodHRwOi8vd3d3Lndoby5pbnQvY2hpbGRncm93dGgvc3RhbmRhcmRzL3NlY29uZF9zZXQvdGVjaG5pY2FsX3JlcG9ydF8yL2VuLy4gIA0KDQo1KSBSaWdieSBSQSwgU3Rhc2lub3BvdWxvcyBETS4gU21vb3RoIGNlbnRpbGUgY3VydmVzIGZvciBza2V3IGFuZCBrdXJ0b3RpYyBkYXRhIG1vZGVsbGVkIHVzaW5nIHRoZSBCb3gtQ294IHBvd2VyIGV4cG9uZW50aWFsIGRpc3RyaWJ1dGlvbi4gU3RhdCBNZWQuIDIwMDQ7MjM6MzA1My03Ni4NCg==