1 Dẫn nhập: Mô hình giá trị tham chiếu

Trong thực hành lâm sàng, kết quả các xét nghiệm, thí dụ chức năng hô hấp (Pulmonary function test, PFT) chỉ là những con số vô nghĩa nếu không có giá trị tham chiếu (Reference values). Giá trị tham chiếu thường được ước lượng từ một mô hình hồi quy dựa trên dữ liệu từ quần thể người khỏe mạnh. Mô hình này thường có nguyên tắc chung là : dựa vào một hay nhiều biến số đầu vào (predictors) dễ đo đạc, độc lập với nhau nhưng có liên hệ chặt chẽ với kết quả cần dự báo (outcome), thí dụ Chủng tộc, Giới tính, các chỉ số nhân trắc (trọng lượng, chiều cao, BMI…).Nhiệm vụ của nhà thống kê là xác định hàm f cho phép ước lượng Outcome Y theo những predictors này.

\[Outcome Y \sim f(predictors...)\]

Khi áp dụng vào chẩn đoán, kết quả xét nghiệm (Observed, true value) của một cá thể bất kì sẽ được so sánh với giá trị dự báo (Predicted value) từ mô hình (hàm f), ý nghĩa của giá trị dự báo này là trung bình của Outcome Y trong một quần thể giả định có cùng đặc tính nhân trắc học, chủng tộc và giới tính với cá thể này. Do tuổi được dùng như predictor, mô hình f này đồng thời cũng đã mô phỏng sự thay đổi của Y dưới tác động của quy luật sinh lý bình thường (tăng trưởng, già lão, thoái hóa tự nhiên), nên người bác sĩ có thể loại trừ những yếu tố này khỏi biện luận chẩn đoán để có thể tập trung vào yếu tố nghi ngờ là có sự tăng/giảm bất thường của Y do nguyên nhân bệnh lý.

2 Nhược điểm của phương pháp thủ công

Trong hàng chục năm qua, Y giới tạo ra những mô hình giá trị tham chiếu theo cách hoàn toàn thủ công. Đầu tiên, xét nghiệm được thực hiện trên một mẫu với kích thước từ vài trăm đến hàng ngàn cá thể, được giả định là đại diện cho quần thể người khỏe mạnh. Dữ liệu sau đó phải được nhập thủ công vào máy tính và tích tụ theo thời gian cho đến khi đạt cỡ mẫu mong muốn. Thậm chí khi những nghiên cứu loại này được tổ chức ở cấp quốc gia, quá trình này thường rất chậm, và có thể kéo dài từ 1-3 năm tùy theo cỡ mẫu.

Sau khi có dữ liệu, một chuyên viên thống kê sẽ bắt đầu dựng mô hình. Đồng hành với lịch sử phát triển của ngành Thống kê, độ phức tạp của hàm f dần dần tăng lên – từ một hằng số trung bình đến mô hình tuyến tính đơn biến, rồi đa biến, rồi đến mô hình đa thức, phi tuyến tính. Vào năm 2018 này, đỉnh cao nhất về sự tinh tế của hàm f là mô hình 3 tham số LMS kết hợp với hàm spline và hàm đa thức. Tuy nhiên, có một điều không thay đổi, đó là việc dựng mô hình hoàn toàn thủ công, thường bằng phương pháp Step-wise. Quy trình này có thể kéo dài vài tháng hoặc lâu hơn.

Sau khi có mô hình, các tác giả sẽ công bố nó dưới dạng 1 bài báo khoa học trên tạp chí. Quy trình bình duyệt cho bài báo có thể dài từ 1 đến 6 tháng nữa cho đến khi nó thực sự được công bố. Sau khi xuất bản, bài báo sẽ được chuyển qua ngành công nghiệp thiết bị xét nghiệm, nơi các kỹ sư phần mềm sẽ đưa mô hình f vào code của phần mềm (PC software hay firmware). Công đoạn này có thể mất thêm vài tháng nữa.

Như vậy, từ khi ý tưởng về mô hình được để xuất cho đến khi nó thực sự được áp dụng trên lâm sàng thường phải mất từ 2-5 năm.

3 Ý tưởng về thiết bị xét nghiệm thông minh

Bây giờ, bạn thử tưởng tượng về một quy trình mới cho phép một cái máy xét nghiệm có khả năng tự thu thập dữ liệu một cách liên tục và ở thời gian thực, học từ dữ liệu này ngày này qua ngày khác và tự tìm ra quy luật (hàm f) tối ưu nhất cho phép ước lượng giá trị tham chiếu rồi áp dụng cho chính nó mà không cần một sự can thiệp nào từ con người ? Hơn nữa, cái máy còn có khả năng tự khắc phục những sai sót của mô hình khi tiếp xúc với dữ liệu mới và cập nhật mô hình để việc dự báo luôn chính xác nhất có thể ?

Đây có lẽ là hình thức sơ khai nhất của « trí tuệ nhân tạo », nhưng không phải là một chuyện viễn tưởng. Trong bài thực hành sau đây, Nhi sẽ thực hiện một thí nghiệm để minh họa cho ý tưởng này. Vào cuối bài, các bạn sẽ thấy rằng ý tưởng xây dựng giá trị tham chiếu tự động không những khả thi, mà còn tốt hơn so với cách làm mô hình thủ công, thậm chí với công cụ tốt nhất hiện nay là mô hình LMS.

4 Thí nghiệm minh họa

Trong thí dụ này, Nhi đặt vấn đề giả định như sau : Một khoa xét nghiệm chức năng hô hấp tại quốc gia C sở hữu một database kết quả thể tích phế nang (AV) và hệ số khuếch tán khí CO (DLCO) trên 627 người bình thường. Chưa có giá trị tham chiếu cho AV và DLCO ở người dân của quốc gia C. Trưởng khoa giao nhiệm vụ cho phòng Thống kê bệnh viện để dựng mô hình dự báo giá trị tham chiếu AV và DLCO cho riêng Nam và Nữ.Chuyên viên thống kê khá tự tin cho biết anh ta biết làm mô hình Lamnda Mu Sigma (LMS), một phương pháp tối tân nhất cho mô hình tham chiếu hiện được Hội hô hấp Châu Âu, Hoa kỳ và WHO áp dụng.

Cùng lúc đó, một cô gái làm ở phòng quản lý dữ liệu của bệnh viện muốn thử nghiệm một ý tưởng khác : Tôi sẽ áp dụng một Deep neural network (mạng thần kinh nhân tạo sâu) với Tensorflow để tạo ra một framework nhằm thực hiện cùng mục tiêu : Ước lượng giá trị AV và DLCO dựa vào dữ liệu đầu vào là 3 biến : Tuổi, Giới tính, Chiều cao. Hãy cho tôi cơ hội làm thử, rồi chúng ta sẽ so sánh hiệu quả giữa cách của tôi và cách của anh.

Một cuộc thi đấu bắt đầu …

Trước hết, dữ liệu được chia ngẫu nhiên ra 2 phần: một phần dùng để dựng mô hình (n=500) chung cho cả 2 phương pháp LMS và Deep learning, một phần nhỏ hơn (n=127) sẽ được dùng làm dữ liệu kiểm định phẩm chất và so sánh 2 mô hình.

Lưu ý rằng dữ liệu kiểm định sẽ được dấu kín, cả 2 phía đều không biết gì về nội dung của dữ liệu này cho đến khi nó được dùng để so sánh mô hình của họ.

5 Thăm dò dữ liệu

library(tidyverse)

ndf<-read.csv("DLCOkeras.csv",sep=";")

ndf<-ndf%>%dplyr::select(-Group)%>%filter(DLCO>11.5,DLCO<55)

# original train/test data
idx=caret::createDataPartition(y=ndf$Sex, p=499/627,list=FALSE)
trainset=ndf[idx,]
testset=ndf[-idx,]

trainset%>%head()%>%knitr::kable()
Sex Age Height AV DLCO
1 F 20 173.0 6.00 29.2
2 F 22 165.0 4.76 26.3
3 F 22 168.6 5.40 27.9
4 F 23 164.0 5.36 25.4
5 F 24 170.0 5.77 29.3
8 F 25 155.0 4.61 26.6

Dữ liệu gồm 5 biến, 2 biến kết quả là AV và DLCO, 3 predictors là Sex, Age và Height. Trước hết, ta biết chắc chắn rằng AV hoặc DLCO phải liên hệ với Sex,Age và Height theo một kiểu nào đó, thí dụ: nữ giới thường có giá trị thấp hơn Nam giới, người già suy giảm giá trị do hiện tượng lão hóa sinh lý, dung tích phổi thì tỉ lệ thuận với chiều cao…

Tuy nhiên, vấn đề khó khăn đó là: những mối liên hệ này là phi tuyến tính như biểu đồ bên dưới:

# Data visualisation

trainset%>%gather(AV,DLCO,key="Outcome",value="Value")%>%
  ggplot(aes(x=Age,y=Value))+
  geom_point(aes(col=Sex),alpha=0.2)+
  geom_smooth(aes(fill=Sex,col=Sex),alpha=0.5)+
  theme_bw()+
  facet_grid(Outcome~Sex,scales = "free")+
  scale_fill_manual(values=c("red","blue"))+
  scale_color_manual(values=c("red3","blue3"))

trainset%>%gather(AV,DLCO,key="Outcome",value="Value")%>%
  ggplot(aes(x=Height,y=Value))+
  geom_point(aes(col=Sex),alpha=0.2)+
  geom_smooth(aes(fill=Sex,col=Sex),alpha=0.5)+
  theme_bw()+
  facet_grid(Outcome~Sex,scales = "free")+
  scale_fill_manual(values=c("red","blue"))+
  scale_color_manual(values=c("red3","blue3"))

Một mô hình hồi quy tốt phải có khả năng mô phỏng được quy luật phi tuyến tính này theo tuổi và/hoặc chiều cao. Ta sẽ theo dõi xem anh chuyên viên thống kê và chị chuyên viên data science xử lý vấn đề như thế nào nhé :

6 Mô hình LMS thủ công bằng Step-wise

Theo truyền thống, chúng ta cần phải dựng một mô hình riêng cho mỗi giới tính (M, F), do đó quy trình bắt đầu bằng việc chia dữ liệu gốc thành 2 phần cho riêng Nam và Nữ:

# trainset for LMS
trainLMS_M<-trainset%>%filter(Sex=="M")
trainLMS_F<-trainset%>%filter(Sex!="M")

# testset for LMS
testLMS_M<-testset%>%filter(Sex=="M")
testLMS_F<-testset%>%filter(Sex!="M")

Phương pháp mô hình 3 tham số (Lambda-Mu-Sigma, LMS) do 2 tác giả Rigby RA. và Stasinopoulos DM tạo ra vào năm 2001 để ước lượng một biến số ngẫu nhiên, liên tục và không âm, có quy luật biến thiên phi tuyến tính theo thời gian (Tuổi).LMS linh hoạt vì không dựa vào phân phối Gaussian như đa số mô hình thống kê khác. LMS cho phép tái hiện quỹ tích phức tạp của Y.

LMS có bản chất là một dạng đặc biệt thuộc họ mô hình GAM (Generalised additive model).Nền tảng của LMS model là họ phân phối Box-Cox-Cole-Green (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).

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 trong mô hình dự báo dung AV hoặc DLCO…

Từ mô hình LMS, ta có thể ước tính : giá trị trung bình lý thuyết của Outcome, 2 ngưỡng cao nhất và thấp nhất.

\[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 ]\] 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). Chính những hàm polynomial và smoothing này 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. Tuy nhiên đây cũng có thể trở thành nhược điểm vì nếu quá tham lam, ta sẽ tạo ra mô hình bị overfit, nhất là khi dữ liệu trainset hỗn tạp và có số lượng lớn. Mô hình overfit chỉ hoạt động tốt trên chính dữ liệu dùng để tạo ra nó, nhưng sẽ phạm sai lầm khi áp dụng trên thực tế với dữ liệu mới.

Mô hình LMS được dựng bằng package gamlss trong R. Ta sẽ sử dụng một chiến thuật đơn giản đó là Backward Step-wise (triệt thoái tự động, dựa vào BIC). Thí dụ ta có đến 3 mô hình con là L,M,S; và mỗi mô hình dùng 1 đa thức bậc cao bao gồm smoothing spline, và tất cả tổ hợp có thể giữa chúng phải được kiểm tra bằng câu hỏi: Có cần mô hình bậc 2,3,4 cho Age hoặc Height không cho Mu/Sigma/Nu ?

Các biến trong formula sẽ bị triệt tiêu dần đến khi mô hình có AIC tối ưu hiện ra.

Sau đây lần lượt là 4 mô hình cho Nam/nữ, AV và DLCO:

# LMS

library(gamlss)
nC<-detectCores()

# DLCO for Male

mm1=gamlss(data=trainLMS_M[,-1],
          DLCO~1,
          sigma.formula = DLCO~1,
          nu.formula = DLCO~1,
          family=BCCG(mu.link="log"),
          trace=FALSE,
          parallel="multicore",
          ncpus = nC)

mm2=stepGAICAll.A(mm1,scope=list(lower=~1, 
                                upper=~poly(Age,3)+poly(Height,3)+pb(Age)),
                 sigma.scope = list(lower=~1, 
                                    upper=~poly(Age,3)+poly(Height,3)+pb(Age)),
                 nu.scope = list(lower=~1,upper=~poly(Age,3)+poly(Height,3)),
                 k=log(length(trainLMS_M[,-1])),
                 trace=FALSE,
                 parallel="multicore",
                 method=RS(1000),
                 ncpus = nC
)
## --------------------------------------------------- 
## Start:  AIC= 1665.44 
##  DLCO ~ 1 
## 
## --------------------------------------------------- 
## Start:  AIC= 1548.02 
##  ~1 
## 
## --------------------------------------------------- 
## Start:  AIC= 1546.01 
##  ~1 
## 
## --------------------------------------------------- 
## Start:  AIC= 1536.22 
##  ~poly(Height, 3) 
## 
## --------------------------------------------------- 
## Start:  AIC= 1536.22 
##  DLCO ~ poly(Age, 3) + poly(Height, 3) 
## 
## ---------------------------------------------------
modDL_M=mm2

summary(modDL_M)
## ******************************************************************
## Family:  c("BCCG", "Box-Cox-Cole-Green") 
## 
## Call:  
## gamlss(formula = DLCO ~ poly(Age, 3) + poly(Height, 3), sigma.formula = ~poly(Height,  
##     3), nu.formula = ~poly(Height, 3) + poly(Age, 3), family = BCCG(mu.link = "log"),  
##     data = trainLMS_M[, -1], trace = FALSE, parallel = "multicore",  
##     ncpus = nC) 
## 
## Fitting method: RS() 
## 
## ------------------------------------------------------------------
## Mu link function:  log
## Mu Coefficients:
##                   Estimate Std. Error t value Pr(>|t|)    
## (Intercept)       3.533025   0.008146 433.696  < 2e-16 ***
## poly(Age, 3)1    -1.533083   0.121116 -12.658  < 2e-16 ***
## poly(Age, 3)2    -0.542061   0.112982  -4.798 2.78e-06 ***
## poly(Age, 3)3    -0.071340   0.101436  -0.703    0.483    
## poly(Height, 3)1  0.838180   0.124046   6.757 1.01e-10 ***
## poly(Height, 3)2 -0.098194   0.082379  -1.192    0.234    
## poly(Height, 3)3 -0.054692   0.078729  -0.695    0.488    
## ---
## 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)      -1.89192    0.04446 -42.558  < 2e-16 ***
## poly(Height, 3)1 -0.97254    0.70711  -1.375   0.1703    
## poly(Height, 3)2  1.83223    0.70711   2.591   0.0101 *  
## poly(Height, 3)3  2.87401    0.70711   4.064 6.46e-05 ***
## ---
## 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.7268     0.3211   2.264 0.024451 *  
## poly(Height, 3)1  17.1680     4.7256   3.633 0.000341 ***
## poly(Height, 3)2  44.6022     4.4419  10.041  < 2e-16 ***
## poly(Height, 3)3  13.1565     3.5889   3.666 0.000302 ***
## poly(Age, 3)1     21.4322     4.7096   4.551 8.41e-06 ***
## poly(Age, 3)2    -16.6489     4.7888  -3.477 0.000600 ***
## poly(Age, 3)3     19.7044     4.6986   4.194 3.83e-05 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## ------------------------------------------------------------------
## No. of observations in the fit:  253 
## Degrees of Freedom for the fit:  18
##       Residual Deg. of Freedom:  235 
##                       at cycle:  20 
##  
## Global Deviance:     1511.266 
##             AIC:     1547.266 
##             SBC:     1610.867 
## ******************************************************************
# DLCO for female

mf1=gamlss(data=trainLMS_F[,-1],
           DLCO~1,
           sigma.formula = DLCO~1,
           nu.formula = DLCO~1,
           family=BCCG(mu.link="log"),
           trace=FALSE,
           parallel="multicore",
           ncpus = nC)

mf2=stepGAICAll.A(mf1,scope=list(lower=~1, 
                                 upper=~poly(Age,3)+poly(Height,3)+pb(Age)),
                  sigma.scope = list(lower=~1, 
                                     upper=~poly(Age,3)+pb(Age)),
                  nu.scope = list(lower=~1,upper=~Age),
                  k=log(length(trainLMS_F[,-1])),
                  trace=FALSE,
                  parallel="multicore",
                  method=RS(10000),
                  ncpus = nC
)
## --------------------------------------------------- 
## Start:  AIC= 1549.39 
##  DLCO ~ 1 
## 
## --------------------------------------------------- 
## Start:  AIC= 1375.57 
##  ~1 
## 
## --------------------------------------------------- 
## Start:  AIC= 1375.57 
##  ~1 
## 
## --------------------------------------------------- 
## Start:  AIC= 1375.57 
##  ~1 
## 
## --------------------------------------------------- 
## Start:  AIC= 1375.57 
##  DLCO ~ poly(Age, 3) + poly(Height, 3) 
## 
## ---------------------------------------------------
modDL_F=mf2

summary(modDL_F)
## ******************************************************************
## Family:  c("BCCG", "Box-Cox-Cole-Green") 
## 
## Call:  
## gamlss(formula = DLCO ~ poly(Age, 3) + poly(Height, 3), sigma.formula = ~1,  
##     nu.formula = ~1, family = BCCG(mu.link = "log"), data = trainLMS_F[,  
##         -1], trace = FALSE, parallel = "multicore", ncpus = nC) 
## 
## Fitting method: RS() 
## 
## ------------------------------------------------------------------
## Mu link function:  log
## Mu Coefficients:
##                   Estimate Std. Error t value Pr(>|t|)    
## (Intercept)       3.191868   0.009995 319.337  < 2e-16 ***
## poly(Age, 3)1    -2.103494   0.167392 -12.566  < 2e-16 ***
## poly(Age, 3)2    -0.760809   0.159128  -4.781 3.04e-06 ***
## poly(Age, 3)3    -0.014776   0.159795  -0.092   0.9264    
## poly(Height, 3)1  0.835098   0.170029   4.911 1.67e-06 ***
## poly(Height, 3)2 -0.138109   0.157733  -0.876   0.3821    
## poly(Height, 3)3  0.261908   0.158388   1.654   0.0995 .  
## ---
## 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) -1.84908    0.04499   -41.1   <2e-16 ***
## ---
## 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.2648     0.3056   0.866    0.387
## 
## ------------------------------------------------------------------
## No. of observations in the fit:  247 
## Degrees of Freedom for the fit:  9
##       Residual Deg. of Freedom:  238 
##                       at cycle:  4 
##  
## Global Deviance:     1363.093 
##             AIC:     1381.093 
##             SBC:     1412.677 
## ******************************************************************
# VA for Male

mm1=gamlss(data=trainLMS_M[,-1],
          AV~1,
          sigma.formula = AV~1,
          nu.formula = AV~1,
          family=BCCG(mu.link="log"),
          trace=FALSE,
          parallel="multicore",
          ncpus = nC)

mm2=stepGAICAll.A(mm1, scope=list(lower=~1, 
                                upper=~poly(Age,3)+poly(Height,3)+pb(Age)),
                 sigma.scope = list(lower=~1, 
                                    upper=~poly(Age,3)+poly(Height,3)+pb(Age)),
                 nu.scope = list(lower=~1, 
                                    upper=~poly(Age,3)+poly(Height,3)+pb(Age)),
                 k=log(length(trainLMS_M[,-1])),
                 trace=FALSE,
                 parallel="multicore",
                 method=RS(10000),
                 ncpus = nC
)
## --------------------------------------------------- 
## Start:  AIC= 694.71 
##  AV ~ 1 
## 
## --------------------------------------------------- 
## Start:  AIC= 559.96 
##  ~1 
## 
## --------------------------------------------------- 
## Start:  AIC= 559.38 
##  ~1 
## 
## --------------------------------------------------- 
## Start:  AIC= 549.6 
##  ~pb(Age) 
## 
## --------------------------------------------------- 
## Start:  AIC= 549.42 
##  AV ~ poly(Height, 3) + pb(Age) 
## 
## ---------------------------------------------------
modAV_M=mm2

summary(modAV_M)
## ******************************************************************
## Family:  c("BCCG", "Box-Cox-Cole-Green") 
## 
## Call:  
## gamlss(formula = AV ~ poly(Height, 3) + pb(Age), sigma.formula = ~1,  
##     nu.formula = ~poly(Height, 3) + pb(Age), family = BCCG(mu.link = "log"),  
##     data = trainLMS_M[, -1], trace = FALSE, parallel = "multicore",  
##     ncpus = nC) 
## 
## Fitting method: RS() 
## 
## ------------------------------------------------------------------
## Mu link function:  log
## Mu Coefficients:
##                    Estimate Std. Error t value Pr(>|t|)    
## (Intercept)       1.9021994  0.0144106 132.000  < 2e-16 ***
## poly(Height, 3)1  1.4046132  0.0783293  17.932  < 2e-16 ***
## poly(Height, 3)2 -0.0772345  0.0362332  -2.132  0.03403 *  
## poly(Height, 3)3 -0.3997339  0.0397826 -10.048  < 2e-16 ***
## pb(Age)           0.0009241  0.0003098   2.983  0.00314 ** 
## ---
## 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.26702    0.04446  -50.99   <2e-16 ***
## ---
## 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)        2.08298    1.31278   1.587   0.1139    
## poly(Height, 3)1  55.36866    7.46891   7.413 1.94e-12 ***
## poly(Height, 3)2 -15.75267    7.29528  -2.159   0.0318 *  
## poly(Height, 3)3  68.13854    7.30389   9.329  < 2e-16 ***
## pb(Age)           -0.04297    0.02934  -1.465   0.1443    
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## ------------------------------------------------------------------
## 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 may not be reliable. 
## ------------------------------------------------------------------
## No. of observations in the fit:  253 
## Degrees of Freedom for the fit:  13.11931
##       Residual Deg. of Freedom:  239.8807 
##                       at cycle:  12 
##  
## Global Deviance:     531.2316 
##             AIC:     557.4702 
##             SBC:     603.8259 
## ******************************************************************
# AV for Female

mf1=gamlss(data=trainLMS_F[,-1],
           AV~1,
           sigma.formula = AV~1,
           nu.formula = AV~1,
           family=BCCG(mu.link="log"),
           trace=FALSE,
           parallel="multicore",
           ncpus = nC)

mf2=stepGAICAll.A(mm1, scope=list(lower=~1, 
                                  upper=~poly(Age,3)+poly(Height,3)+pb(Age)),
                  sigma.scope = list(lower=~1, 
                                     upper=~poly(Age,3)+poly(Height,3)+pb(Age)),
                  nu.scope = list(lower=~1, 
                                  upper=~poly(Age,3)+poly(Height,3)+pb(Age)),
                  k=log(length(trainLMS_F[,-1])),
                  trace=FALSE,
                  parallel="multicore",
                  method=RS(10000),
                  ncpus = nC
)
## --------------------------------------------------- 
## Start:  AIC= 694.71 
##  AV ~ 1 
## 
## --------------------------------------------------- 
## Start:  AIC= 559.96 
##  ~1 
## 
## --------------------------------------------------- 
## Start:  AIC= 559.38 
##  ~1 
## 
## --------------------------------------------------- 
## Start:  AIC= 549.6 
##  ~pb(Age) 
## 
## --------------------------------------------------- 
## Start:  AIC= 549.42 
##  AV ~ poly(Height, 3) + pb(Age) 
## 
## ---------------------------------------------------
modAV_F=mf2

summary(modAV_F)
## ******************************************************************
## Family:  c("BCCG", "Box-Cox-Cole-Green") 
## 
## Call:  
## gamlss(formula = AV ~ poly(Height, 3) + pb(Age), sigma.formula = ~1,  
##     nu.formula = ~poly(Height, 3) + pb(Age), family = BCCG(mu.link = "log"),  
##     data = trainLMS_M[, -1], trace = FALSE, parallel = "multicore",  
##     ncpus = nC) 
## 
## Fitting method: RS() 
## 
## ------------------------------------------------------------------
## Mu link function:  log
## Mu Coefficients:
##                    Estimate Std. Error t value Pr(>|t|)    
## (Intercept)       1.9021994  0.0144106 132.000  < 2e-16 ***
## poly(Height, 3)1  1.4046132  0.0783293  17.932  < 2e-16 ***
## poly(Height, 3)2 -0.0772345  0.0362332  -2.132  0.03403 *  
## poly(Height, 3)3 -0.3997339  0.0397826 -10.048  < 2e-16 ***
## pb(Age)           0.0009241  0.0003098   2.983  0.00314 ** 
## ---
## 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.26702    0.04446  -50.99   <2e-16 ***
## ---
## 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)        2.08298    1.31278   1.587   0.1139    
## poly(Height, 3)1  55.36866    7.46891   7.413 1.94e-12 ***
## poly(Height, 3)2 -15.75267    7.29528  -2.159   0.0318 *  
## poly(Height, 3)3  68.13854    7.30389   9.329  < 2e-16 ***
## pb(Age)           -0.04297    0.02934  -1.465   0.1443    
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## ------------------------------------------------------------------
## 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 may not be reliable. 
## ------------------------------------------------------------------
## No. of observations in the fit:  253 
## Degrees of Freedom for the fit:  13.11931
##       Residual Deg. of Freedom:  239.8807 
##                       at cycle:  12 
##  
## Global Deviance:     531.2316 
##             AIC:     557.4702 
##             SBC:     603.8259 
## ******************************************************************

7 Mô hình mạng thần kinh nhân tạo sâu (Deep learning)

Cùng lúc này, cô chuyên viên Data science đang giải quyết vấn đề theo một hướng đi hoàn toàn khác với cách làm truyền thống. Sau đây là những phân tích của cá nhân cô ấy:

  1. Chúng ta cắt bài toán hồi quy này ra thành nhiều mảnh nhỏ (mô hình riêng cho Nam, Nữ, AV, DLCO…) để làm gì cho khó khăn ? Tại sao ta không tạo ra 1 mô hình DUY NHẤT cho phép ước lượng đồng thời cả 2 outcomes (AV, DLCO),và cho tất cả mọi cá thể bất kể giới tính ?

  2. Ta đã xác định về dữ liệu đầu vào, sẽ gồm 3 biến: Sex, Age, Height, ta cũng đã xác định sẽ có 2 outcomes là AV và DLCO. Như vậy mục tiêu của ta là tạo ra một nền tảng cho phép xuất ra 2 giá trị AV và DLCO gần nhất so với giá trị thực tế. Ta còn muốn rằng nền tảng này sẽ cho phép cái máy tự nó thích nghi với dữ liệu đầu vào mới, mà không cần phải tháo dỡ hệ thống ra rồi lắp ráp lại từ đầu như phương pháp Step-wise thủ công. Điều này giống như việc thiết kế một bệnh viện để nó hoạt động trong 10-20 năm, đầu vào tiếp nhận bệnh nhân, đầu ra bệnh nhân lành bệnh. Hệ thống bên trong bệnh viện (phòng ốc, hành lang, y cụ, nhân viên y tế) là cố định, nhưng có khả năng thích ứng với nhiều bệnh lý khác nhau, chữa nhiều bệnh đồng thời. Như vậy khi gặp bệnh khó chữa, vấn đề là hệ thống phải thích nghi chứ ta không thể đập bỏ bệnh viện và xây lại một cái mới.

Hệ thống đó chính là một mạng neuron nhân tạo có cấu trúc gồm 3 phần:

Lớp đầu tiên gồm 3 perceptron, có chức năng nhận dữ liệu đầu vào, tương ứng 3 biến là Sex (giá trị nhị phân), Age và Height.

Tín hiệu đi qua mỗi neuron sẽ được kích hoạt bằng một hàm để điều chỉnh trọng số của tín hiệu xuất ra qua những neuron tiếp theo nằm trong lớp sâu (hidden layers). Mạng neuron này sẽ có 2 hay 3 lớp sâu như vậy, mỗi lớp chứa vài chục đến vài trăm nodes. Trước khi dẫn đến lớp cuối cùng, tín hiệu sẽ đi qua một lớp có vai trò hiệu chính sử dụng 1 hàm l2 regularisation.

Lớp cuối cùng là đầu ra của mạng neuron, sẽ gồm 2 nodes tương ứng với 2 outcomes là DLCO và AV.

Cấu trúc này sẽ được “huấn luyện” bằng từng đoạn dữ liệu trong trainset và kiểm định qua hàng trăm lượt, cho đến khi đạt trạng thái tối ưu. Quá trình huấn luyện này có mục tiêu là giảm thiểu một hàm mất mát (loss function) và tiêu chí về sai số chẩn đoán, thí dụ MAE hay RMSE…

Sau khi huấn luyện xong, ta có trong tay 1 mô hình Deep neural network có khả năng ước lượng đồng thời AV và DLCO từ 3 biến đầu vào.

Công cụ được sử dụng là package keras, giao thức cho phép R tương tác với Tensofflow của Python (bạn cần install trước một gói Python thí dụ Anaconda để có thể sử dụng keras trong R).

Giao thức Keras tiếp nhận một cấu trúc dữ liệu đặc biệt gọi là các tensor. Ta có thể hình dung về tensors như khái niệm tổng quát để gọi tên những đối tượng dữ liệu mà ta từng biết trong R theo số chiều thông tin (dimension). Thí dụ vector là 1D tensor, còn matrix gồm nhiều features, nhiều instances được gọi là 2D tensor. Trong R, tensor được lưu dưới dạng array, do đó trước hết ta cần hoán chuyển những dataframe hiện có thành array. Keras tiếp nhận riêng tensors cho tập features và cho biến kết quả nên ta phải tách riêng 2 phần này từ tập train và test

Cần lưu ý là Neural network chỉ hoạt động tối ưu khi dữ liệu đầu vào được chuẩn hó, và nó chỉ tiếp nhận dữ liệu số - nên các predictor như Sex phải được chuyển thành dummy variable hay binary variable (giá trị 0/1)

Việc chuẩn hóa này được thực hiện trên cả 2 tập trainset và testset, tuy nhiên chỉ dựa vào mean và sd của trainset để đảm bảo tính bí mật của testset. Package recipes cho phép ta tạo 1 hàm chuẩn hóa như vậy

library(recipes)

# Recipe for standardisation

dlcorec<-recipe(trainset,AV+DLCO~Sex+Age+Height)%>%
  add_role(Sex,new_role = "stratify")%>%
  step_center(all_predictors())%>%
  step_scale(all_predictors())

# Standardised trainset+testset

std_func<-prep(dlcorec,training=trainset,retain=F)
std_test<-bake(std_func, newdata=testset)
std_train<-bake(std_func, newdata=trainset)

# Predictors to tensors

train_data<-std_train%>%
  dplyr::select(-DLCO,-AV)%>%
  mutate(Sex=as.numeric(trainset$Sex)-1)%>%
  as.matrix()%>%
  as.array.default(dimnames=NULL)  

test_data<-std_test%>%
  dplyr::select(-DLCO,-AV)%>%
  mutate(Sex=as.numeric(testset$Sex)-1)%>%
  as.matrix()%>%
  as.array.default(dimnames=NULL)  

# Outcomes to tensors
train_targets<-trainset%>%
  dplyr::select(AV,DLCO)%>%
  as.matrix()%>%
  as.array.default(dimnames=NULL)  

test_targets<-testset%>%
  dplyr::select(AV,DLCO)%>%
  as.matrix()%>%
  as.array.default(dimnames=NULL)

Ta thiết kế mạng neuron trong keras: mạng này có 1 lớp đầu vào , 4 lớp sâu dùng hàm rectified linear unit (relu), 1 lớp hiệu chỉnh với L2 reg, cuối cùng là lớp kết quả gồm 2 neuron. Hàm mất mát là MAE, tiêu chí huấn luyện là MSE

# Design a deep neural net

# ANN

library(keras)

build_model <- function() {
  model <- keras_model_sequential() %>% 
    layer_dense(units = 128, activation = "relu", 
                input_shape = dim(train_data)[[2]]) %>% 
    layer_dense(units = 128, activation = "relu") %>%
    layer_dense(units = 128, activation = "relu") %>%
    layer_dense(units =128, kernel_regularizer = regularizer_l2(0.001))%>%   
    layer_dense(units = 2)
    
  model %>% compile(
    optimizer = "rmsprop",
    loss = "mae",
    metric= "msle"
  )
}

# Train ANN on whole data.

model <- build_model()

model %>% fit(train_data, train_targets,
              epochs = 200, batch_size = 30,verbose=0,
              validation_split = 0.1) -> dnnmod

summary(model)
## ___________________________________________________________________________
## Layer (type)                     Output Shape                  Param #     
## ===========================================================================
## dense_1 (Dense)                  (None, 128)                   512         
## ___________________________________________________________________________
## dense_2 (Dense)                  (None, 128)                   16512       
## ___________________________________________________________________________
## dense_3 (Dense)                  (None, 128)                   16512       
## ___________________________________________________________________________
## dense_4 (Dense)                  (None, 128)                   16512       
## ___________________________________________________________________________
## dense_5 (Dense)                  (None, 2)                     258         
## ===========================================================================
## Total params: 50,306
## Trainable params: 50,306
## Non-trainable params: 0
## ___________________________________________________________________________
plot(dnnmod)+theme_bw()

Sau khi huấn luyện 200 lượt với tỉ lệ kiểm định 30%, sử dụng ngẫu nhiên 30 trường hợp mỗi lượt, ta có kết quả sau cùng trong object dnnmod.

Phần lớn thời gian được sử dụng là để thiết kế cấu trúc mạng neuron nhưng công việc này tương đối nhẹ nhàng so với quy trình step wise thủ công mà phòng thống kê đang làm.

8 So sánh hiệu quả 2 phương pháp

Đây là thời điểm quyết định, hai mô hình LMS (phái thống kê) và Deep neural network (phái Machine learning) đã hoàn tất. Ta sẽ kiểm tra độ chính xác của chúng trên tập testset.

Bên cạnh đó, một mô hình tuyến tính đơn giản cho 2 chỉ số DLCO và AV được ERS đề xuất năm 2017 sẽ được dùng làm nhóm chứng,chúng ta hy vọng rằng có sự khác biệt giữa 2 mô hình mới và mô hình tuyến tính, cũng như có sự tương phản rõ nét về hiệu năng giữa 2 phương pháp Deep learning và LMS theo GAMLSS.

# Validating DLCO prediction on testset

y_pred=predict(model,test_data)

library(mlr)

# Metrics to be estimated
mets=list(mse,mae,medae,rmse)

dlco.task= mlr::makeRegrTask(id = "DLCO", data=testset, target = "DLCO")
dlco.lrn = makeLearner("regr.glm")
dummydlco=mlr::train(dlco.lrn,dlco.task)
dumpredDNNdlco=predict(dummydlco,dlco.task)

# Implement y_pred to dummy
dumpredDNNdlco$data$response<-y_pred[,2]


# Validating VA prediction on testset

va.task= mlr::makeRegrTask(id = "VA", data=testset, target = "AV")
va.lrn = makeLearner("regr.glm")
dummyva=mlr::train(va.lrn,va.task)
dumpredDNNva=predict(dummyva,va.task)

# Implement y_pred to dummy
dumpredDNNva$data$response<-y_pred[,1]

# Validating DLCO by LMS

pred_DL_M=predict(modDL_M,newdata=testLMS_M[,-1],type="response")
pred_DL_F=predict(modDL_F,newdata=testLMS_F[,-1],type="response")
pred_AV_M=predict(modAV_M,newdata=testLMS_M[,-1],type="response")
## new prediction 
## New way of prediction in pb()  (starting from GAMLSS version 5.0-3)
pred_AV_F=predict(modAV_F,newdata=testLMS_F[,-1],type="response")
## new prediction 
## New way of prediction in pb()  (starting from GAMLSS version 5.0-3)
test_DL_LMS=rbind(testLMS_M,testLMS_F)%>%
  mutate(predDL=c(pred_DL_M,pred_DL_F),
         predAV=c(pred_AV_M,pred_AV_F))

# Dummy LMS
dumpredLMS_DL=predict(dummydlco,dlco.task)
dumpredLMS_AV=predict(dummyva,va.task)

# Implement

dumpredLMS_DL$data$response<-test_DL_LMS$predDL
dumpredLMS_AV$data$response<-test_DL_LMS$predAV

            
# ERS 2017 linear model:

# DLCO
# Male: (0.3*HEIGHT)-(0.002*(AGE^2))+(5.47*1)-17.75    RSD=4.4377
# Female: (0.3*HEIGHT)-(0.002*(AGE^2))+(5.47*0)-17.75  RSD=4.7377

#AV
# male: 0.082*HEIGHT+0.72*1-8.2   RSD=0.766
# Female: 0.082*HEIGHT+0.72*0-8.2 RSD=0.766

predERSmod=function(newdata){
  Sex=as.numeric(newdata$Sex)-1
  Age=newdata$Age
  Height=newdata$Height
  rsdDL=4.4377
  rsdAV=0.766
  predERS=list(Mu_DL=(0.3*Height)-(0.002*(Age^2))+(5.47*Sex)-17.75,
               LLN_DL=(0.3*Height)-(0.002*(Age^2))+(5.47*Sex)-17.75-1.645*rsdDL,
               ULN_DL=(0.3*Height)-(0.002*(Age^2))+(5.47*Sex)-17.75+1.645*rsdDL,
               Mu_AV=0.082*Height+0.72*Sex-8.2,
                LLN_AV=0.082*Height+0.72*Sex-8.2-1.645*rsdAV,
                ULN_AV=0.082*Height+0.72*Sex-8.2+1.645*rsdAV)
return(predERS)
}

predERS=predERSmod(testset)

# Dummy ERS
dumpredERS_DL=predict(dummydlco,dlco.task)
dumpredERS_AV=predict(dummyva,va.task)

# Implement

dumpredERS_DL$data$response<-predERS$Mu_DL
dumpredERS_AV$data$response<-predERS$Mu_AV

# Compare DLCO
pe1=performance(dumpredLMS_DL,measures =mets)
pe2=performance(dumpredDNNdlco,measures =mets)
pe3=performance(dumpredERS_DL,measures =mets)

# Compare AV
pe4=performance(dumpredLMS_AV,measures =mets)
pe5=performance(dumpredDNNva,measures =mets)
pe6=performance(dumpredERS_AV,measures =mets)

verdict=data_frame(Outcome=c("DLCO","DLCO","DLCO","AV","AV","AV"),
                   Method=rep(c("Step_wise_LMS","Deep_NN","ERS_linear"),2),
                   MSE=c(pe1[[1]],pe2[[1]],pe3[[1]],pe4[[1]],pe5[[1]],pe6[[1]]),
                   MAE=c(pe1[[2]],pe2[[2]],pe3[[2]],pe4[[2]],pe5[[2]],pe6[[2]]),
                   MEDAE=c(pe1[[3]],pe2[[3]],pe3[[3]],pe4[[3]],pe5[[3]],pe6[[3]]),
                   RMSE=c(pe1[[4]],pe2[[4]],pe3[[4]],pe4[[4]],pe5[[4]],pe6[[4]])
                   )

verdict%>%knitr::kable()
Outcome Method MSE MAE MEDAE RMSE
DLCO Step_wise_LMS 123.9756040 9.8277355 9.607005 11.1344333
DLCO Deep_NN 21.2717903 3.5261171 2.852706 4.6121351
DLCO ERS_linear 22.9205832 4.0352126 3.838000 4.7875446
AV Step_wise_LMS 2.8022101 1.4087842 1.304354 1.6739803
AV Deep_NN 0.5301520 0.5686747 0.454293 0.7281154
AV ERS_linear 0.5583332 0.6060008 0.536000 0.7472170

Kết quả kiểm định cho thấy một điều bất ngờ, đó là một mô hình phức tạp như LMS lại có mức độ chính xác kém hơn so với một mô hình tuyến tính đơn giản (ERS). Mô hình Deep learning có hiệu quả tương đương, thậm chí có thể tốt hơn so với mô hình của ERS. Kết quả được trình bày bằng biểu đồ:

verdict%>%gather(MSE:RMSE,key="Metric",value="Score")%>%
  ggplot(aes(x=Method,y=Score,fill=Method))+
  geom_bar(stat="identity",position="dodge",alpha=0.6,col="black")+
  theme_bw()+coord_flip()+
  facet_grid(Metric~Outcome,scales="free")

Độ chính xác của mô hình Deep learning và ERS có thể được quan sát trên sự chồng lắp biểu đồ tuyến kí giữa dữ liệu ước tính bởi mô hình và dữ liệu thực tế tron tập kiểm định như sau:

# LLN and ULN

# rsd for DLCO

(trainset$DLCO-as.vector(y_pred[,2]))%>%
  density()%>%
  plot(col="red")

rsd_DL=sd(trainset$DLCO-as.vector(y_pred[,2]))

# rsd for AV

(trainset$AV-as.vector(y_pred[,1]))%>%
  density()%>%
  plot(col="blue")

rsd_AV=sd(trainset$AV-as.vector(y_pred[,1]))


# LLN and ULN by LMS

pred_DL_M_all=predictAll(modDL_M,newdata=testLMS_M[,-1])
pred_DL_F_all=predictAll(modDL_F,newdata=testLMS_F[,-1])
pred_AV_M_all=predictAll(modAV_M,newdata=testLMS_M[,-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)
pred_AV_F_all=predictAll(modAV_F,newdata=testLMS_F[,-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)
# DLCO
MuDL=c(pred_DL_M_all$mu , pred_DL_F_all$mu)
sigmaDL=c(pred_DL_M_all$sigma , pred_DL_F_all$sigma)
NuDL=c(pred_DL_M_all$nu , pred_DL_F_all$nu)
LLN_DL_LMS=exp(log(MuDL)+(log(1-1.645*NuDL*sigmaDL))/NuDL)
ULN_DL_LMS=exp(log(MuDL)+(log(1+1.645*NuDL*sigmaDL))/NuDL)

# AV
MuAV=c(pred_AV_M_all$mu , pred_AV_F_all$mu)
sigmaAV=c(pred_AV_M_all$sigma , pred_AV_F_all$sigma)
NuAV=c(pred_AV_M_all$nu , pred_AV_F_all$nu)
LLN_AV_LMS=exp(log(MuAV)+(log(1-1.645*NuAV*sigmaAV))/NuAV)
ULN_AV_LMS=exp(log(MuAV)+(log(1+1.645*NuAV*sigmaAV))/NuAV)

test_all<-testset%>%mutate(Mu_AV_ANN=as.vector(y_pred[,1]),
                          LLN_AV_ANN=qnorm(p=0.2,
                                           mean=as.vector(y_pred[,1]),
                                           sd=rsd_AV),
                          ULN_AV_ANN=qnorm(p=0.8,
                                           mean=as.vector(y_pred[,1]),
                                           sd=rsd_AV),
                          Mu_DL_ANN=as.vector(y_pred[,2]),
                          LLN_DL_ANN=qnorm(p=0.2,
                                           mean=as.vector(y_pred[,2]),
                                           sd=rsd_DL),
                          ULN_DL_ANN=qnorm(p=0.8,
                                           mean=as.vector(y_pred[,2]),
                                           sd=rsd_DL),
                          Mu_DL_LMS=MuDL,
                          LLN_DL_LMS=LLN_DL_LMS,
                          ULN_DL_LMS=ULN_DL_LMS,
                          Mu_AV_LMS=MuAV,
                          LLN_AV_LMS=LLN_AV_LMS,
                          ULN_AV_LMS=ULN_AV_LMS,
                          Mu_DL_ERS=predERS$Mu_DL,
                          LLN_DL_ERS=predERS$LLN_DL,
                          ULN_DL_ERS=predERS$ULN_DL,
                          Mu_AV_ERS=predERS$Mu_AV,
                          LLN_AV_ERS=predERS$LLN_AV,
                          ULN_AV_ERS=predERS$ULN_AV
                          )
                          
p1=test_all%>%ggplot(aes(x=Age))+
  geom_point(aes(y=AV),alpha=0.3,col="black")+
  geom_smooth(aes(y=Mu_AV_ANN),fill="red",col="red3",alpha=0.2)+
  geom_smooth(aes(y=AV),fill="green",col="green4",alpha=0.2)+
  geom_smooth(aes(y=LLN_AV_ANN),fill="red",col="red3",se=F,linetype=2)+
  geom_smooth(aes(y=ULN_AV_ANN),fill="red",col="red3",se=F,linetype=2)+
  theme_bw()+ggtitle("Deep neural network")+scale_y_continuous(limits = c(2,9))

p2=test_all%>%ggplot(aes(x=Age))+
  geom_point(aes(y=AV),alpha=0.3,col="black")+
  geom_smooth(aes(y=Mu_AV_LMS),fill="blue",col="blue3",alpha=0.2)+
  geom_smooth(aes(y=AV),fill="green",col="green3",alpha=0.2)+
  geom_smooth(aes(y=LLN_AV_LMS),fill="blue",col="blue3",se=F,linetype=2)+
  geom_smooth(aes(y=ULN_AV_LMS),fill="blue",col="blue3",se=F,linetype=2)+
  theme_bw()+ggtitle("LMS")+scale_y_continuous(limits = c(2,9))

p3=test_all%>%ggplot(aes(x=Age))+
  geom_point(aes(y=AV),alpha=0.3,col="black")+
  geom_smooth(aes(y=Mu_AV_ERS),fill="purple",col="blue3",alpha=0.2)+
  geom_smooth(aes(y=AV),fill="green",col="green3",alpha=0.2)+
  geom_smooth(aes(y=LLN_AV_ERS),fill="purple",col="purple",se=F,linetype=2)+
  geom_smooth(aes(y=ULN_AV_ERS),fill="purple",col="purple",se=F,linetype=2)+
  theme_bw()+ggtitle("ERS")+scale_y_continuous(limits = c(2,9))

p4=test_all%>%ggplot(aes(x=Age))+
  geom_point(aes(y=DLCO),alpha=0.3,col="black")+
  geom_smooth(aes(y=DLCO),fill="green",col="green4",alpha=0.2)+
  geom_smooth(aes(y=Mu_DL_ANN),fill="red",col="red3",alpha=0.2)+
  geom_smooth(aes(y=LLN_DL_ANN),fill="red",col="red3",se=F,linetype=2)+
  geom_smooth(aes(y=ULN_DL_ANN),fill="red",col="red3",se=F,linetype=2)+
  theme_bw()+scale_y_continuous(limits = c(5,50))+ggtitle("Deep neural network")

p5=test_all%>%ggplot(aes(x=Age))+
  geom_point(aes(y=DLCO),alpha=0.3,col="black")+
  geom_smooth(aes(y=DLCO),fill="green",col="green4",alpha=0.2)+
  geom_smooth(aes(y=Mu_DL_LMS),fill="blue",col="blue3",alpha=0.2)+
  geom_smooth(aes(y=LLN_DL_LMS),fill="blue",col="blue3",se=F,linetype=2)+
  geom_smooth(aes(y=ULN_DL_LMS),fill="blue",col="blue3",se=F,linetype=2)+
  theme_bw()+scale_y_continuous(limits = c(5,50))+ggtitle("LMS")

p6=test_all%>%ggplot(aes(x=Age))+
  geom_point(aes(y=DLCO),alpha=0.3,col="black")+
  geom_smooth(aes(y=DLCO),fill="green",col="green4",alpha=0.2)+
  geom_smooth(aes(y=Mu_DL_ERS),fill="violet",col="purple",alpha=0.2)+
  geom_smooth(aes(y=LLN_DL_ERS),fill="violet",col="purple",se=F,linetype=2)+
  geom_smooth(aes(y=ULN_DL_ERS),fill="violet",col="purple",se=F,linetype=2)+
  theme_bw()+scale_y_continuous(limits = c(5,50))+ggtitle("ERS")

gridExtra::grid.arrange(p1,p2,p3,ncol=2)

gridExtra::grid.arrange(p4,p5,p6,ncol=2)

9 Bàn luận

Sự phổ biến và phát triển của các phương pháp Machine learning mà giải thuật hiện đại nhất là Deep learning hay mạng thần kinh nhân tạo đã cung cấp một giải pháp hoàn toàn mới cho bài toán xây dựng giá trị tham chiếu cho xét nghiệm y khoa. Trong thí dụ ngắn này, Nhi đã chứng tỏ rằng Deep learning với cấu trúc mạng neuro phù hợp sẽ mạnh hơn nhiều so với những mô hình tuyến tính cổ điển, thậm chí những mô hình phức tạp nhất như LMS với Splines.

Ta cùng nhìn lại 2 giải pháp để so sánh giữa chúng:

  1. Những ưu thế của Deep learning so với mô hình LMS:
  • Deep neural network cho phép giải quyết cùng lúc nhiều outcomes trong một mô hình duy nhất, LMS không thể làm được điều này.

  • Cấu trúc của mạng neuron chỉ cần thiết kế 1 lần, nhưng có thể tái sử dụng nhiều lần , và việc thiết kế có tính thứ bậc. Trái lại, phương pháp step-wise luôn phá bỏ cấu trúc mô hình cũ và thay thế bằng cấu trúc mới khi thay đổi dữ liệu đầu vào. Việc thăm dò, thử, sai và thử lại những tổ hợp của bậc đa thức trong mô hình LMS bằng Stepwise cũng khó khăn hơn nhiều so với việc dựng mạng neuron.

  1. Những nhược điểm của Deep learning so với LMS:
  • Mô hình Deep neural network là một hộp đen (blackbox), ta không thể nhìn thấy cơ chế bên trong của nó. Tuy nhiên, điều này không thực sự quan trọng. Trên thực tế các mô hình LMS cũng là 1 dạng blackbox, vì không có bác sĩ nào có khả năng tính toán thủ công từ mô hình, nhất là hàm splines sẽ tạo ra những matrices phức tạp và hoàn toàn ẩn. Một khi đã đưa vào software, người ta không quan tâm đến nội dung của model nữa.

  • Mô hình Deep neural net chưa cho phép ước tính phân vị một cách chính xác như LMS. Trong thí dụ này, Nhi ước tính LLN và ULN dựa vào giả định là residual error có phân bố chuẩn, nên LLN được tính từ hàm quantile với Mean= predicted và sd = sd của residual error.

Một câu hỏi thú vị nữa: Tại sao ta không dùng Deep learning để đưa ra chẩn đoán luôn (phân biệt các bệnh lý với người bình thường) mà lại phải đi vòng qua mô hình hồi quy ? Câu trả lời đó là : (1) bài toán multiclass, multilabel classification luôn phức tạp hơn nhiều so với 1 bài toán hồi quy, và (2) Thu thập dữ liệu của bệnh nhân với chẩn đoán xác định thì khó khăn hơn rất nhiều so với chỉ thu thập dữ liệu trên người khỏe mạnh, (3) Đọc kết quả xét nghiệm dựa vào giá trị tham chiếu vẫn còn là phương pháp thực hành lâm sàng quy ước, các bác sĩ không thấy dễ chịu với ý tưởng cái máy thay thế vai trò của họ hoàn toàn.

Có thể trong một tương lai gần, máy tính sẽ thay thế cho vai trò của chuyên viên thống kê, vì trí tuệ nhân tạo mang lại ưu thế quá lớn so với các làm việc thủ công. Thay vì phải chờ 3-5 năm để có một mô hình, một cái máy có thể làm việc này hàng tuần, thậm chí hằng ngày, với độ chính xác cao hơn nhiều.

Tài liệu tham khảo

  1. Rigby RA, Stasinopoulos DM. Generalized additive models for location, scale and shape (with discussion). Appl Statist 2005; 54: 507-554.

  2. Francois Chollet, J. J. Allaire. Deep Learning with R. Publisher: Manning Publications. 2018. ISBN: 9781617295546

LS0tDQp0aXRsZTogIlRlbnNvcmZsb3cgRGVlcCBsZWFybmluZyINCnN1YnRpdGxlOiAi4buobmcgZOG7pW5nIEjhu5NpIHF1eSINCmF1dGhvcjogIkzDqiBOZ+G7jWMgS2jhuqMgTmhpIg0KZGF0ZTogIjE1IFRow6FuZyAwNSAyMDE4Ig0Kb3V0cHV0Og0KICBodG1sX2RvY3VtZW50OiANCiAgICBjb2RlX2Rvd25sb2FkOiB0cnVlDQogICAgY29kZV9mb2xkaW5nOiBoaWRlDQogICAgbnVtYmVyX3NlY3Rpb25zOiB5ZXMNCiAgICB0aGVtZTogImRlZmF1bHQiDQogICAgdG9jOiBUUlVFDQogICAgdG9jX2Zsb2F0OiBUUlVFDQotLS0NCg0KYGBge3Igc2V0dXAsaW5jbHVkZT1GQUxTRX0NCmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvID0gVFJVRSkNCmxpYnJhcnkodGlkeXZlcnNlKQ0KYGBgDQoNCiFbXShrZXJhc3ZzZ2FtbHNzLnBuZykNCg0KIyBE4bqrbiBuaOG6rXA6IE3DtCBow6xuaCBnacOhIHRy4buLIHRoYW0gY2hp4bq/dQ0KDQpUcm9uZyB0aOG7sWMgaMOgbmggbMOibSBzw6BuZywga+G6v3QgcXXhuqMgY8OhYyB4w6l0IG5naGnhu4dtLCB0aMOtIGThu6UgY2jhu6ljIG7Eg25nIGjDtCBo4bqlcCAoUHVsbW9uYXJ5IGZ1bmN0aW9uIHRlc3QsIFBGVCkgY2jhu4kgbMOgIG5o4buvbmcgY29uIHPhu5EgdsO0IG5naMSpYSBu4bq/dSBraMO0bmcgY8OzIGdpw6EgdHLhu4sgdGhhbSBjaGnhur91IChSZWZlcmVuY2UgdmFsdWVzKS4gR2nDoSB0cuG7iyB0aGFtIGNoaeG6v3UgdGjGsOG7nW5nIMSRxrDhu6NjIMaw4bubYyBsxrDhu6NuZyB04burIG3hu5l0IG3DtCBow6xuaCBo4buTaSBxdXkgZOG7sWEgdHLDqm4gZOG7ryBsaeG7h3UgdOG7qyBxdeG6p24gdGjhu4MgbmfGsOG7nWkga2jhu49lIG3huqFuaC4gTcO0IGjDrG5oIG7DoHkgdGjGsOG7nW5nIGPDsyBuZ3V5w6puIHThuq9jIGNodW5nIGzDoCA6IGThu7FhIHbDoG8gbeG7mXQgaGF5IG5oaeG7gXUgYmnhur9uIHPhu5EgxJHhuqd1IHbDoG8gKHByZWRpY3RvcnMpIGThu4UgxJFvIMSR4bqhYywgxJHhu5ljIGzhuq1wIHbhu5tpIG5oYXUgbmjGsG5nIGPDsyBsacOqbiBo4buHIGNo4bq3dCBjaOG6vSB24bubaSBr4bq/dCBxdeG6oyBj4bqnbiBk4buxIGLDoW8gKG91dGNvbWUpLCB0aMOtIGThu6UgQ2jhu6duZyB04buZYywgR2nhu5tpIHTDrW5oLCBjw6FjIGNo4buJIHPhu5EgbmjDom4gdHLhuq9jICh0cuG7jW5nIGzGsOG7o25nLCBjaGnhu4F1IGNhbywgQk1J4oCmKS5OaGnhu4dtIHbhu6UgY+G7p2EgbmjDoCB0aOG7kW5nIGvDqiBsw6AgeMOhYyDEkeG7i25oIGjDoG0gZiBjaG8gcGjDqXAgxrDhu5tjIGzGsOG7o25nIE91dGNvbWUgWSB0aGVvIG5o4buvbmcgcHJlZGljdG9ycyBuw6B5LiANCg0KJCRPdXRjb21lIFkgXHNpbSAgZihwcmVkaWN0b3JzLi4uKSQkDQoNCktoaSDDoXAgZOG7pW5nIHbDoG8gY2jhuqluIMSRb8Ohbiwga+G6v3QgcXXhuqMgeMOpdCBuZ2hp4buHbSAoT2JzZXJ2ZWQsIHRydWUgdmFsdWUpIGPhu6dhIG3hu5l0IGPDoSB0aOG7gyBi4bqldCBrw6wgc+G6vSDEkcaw4bujYyBzbyBzw6FuaCB24bubaSBnacOhIHRy4buLIGThu7EgYsOhbyAoUHJlZGljdGVkIHZhbHVlKSB04burIG3DtCBow6xuaCAoaMOgbSBmKSwgw70gbmdoxKlhIGPhu6dhIGdpw6EgdHLhu4sgZOG7sSBiw6FvIG7DoHkgbMOgIHRydW5nIGLDrG5oIGPhu6dhIE91dGNvbWUgWSB0cm9uZyBt4buZdCBxdeG6p24gdGjhu4MgZ2nhuqMgxJHhu4tuaCBjw7MgY8O5bmcgxJHhurdjIHTDrW5oIG5ow6JuIHRy4bqvYyBo4buNYywgY2jhu6duZyB04buZYyB2w6AgZ2nhu5tpIHTDrW5oIHbhu5tpIGPDoSB0aOG7gyBuw6B5LiBEbyB0deG7lWkgxJHGsOG7o2MgZMO5bmcgbmjGsCBwcmVkaWN0b3IsIG3DtCBow6xuaCBmIG7DoHkgxJHhu5NuZyB0aOG7nWkgY8WpbmcgxJHDoyBtw7QgcGjhu49uZyBz4buxIHRoYXkgxJHhu5VpIGPhu6dhIFkgZMaw4bubaSB0w6FjIMSR4buZbmcgY+G7p2EgcXV5IGx14bqtdCBzaW5oIGzDvSBiw6xuaCB0aMaw4budbmcgKHTEg25nIHRyxrDhu59uZywgZ2nDoCBsw6NvLCB0aG/DoWkgaMOzYSB04buxIG5oacOqbiksIG7Dqm4gbmfGsOG7nWkgYsOhYyBzxKkgY8OzIHRo4buDIGxv4bqhaSB0cuG7qyBuaOG7r25nIHnhur91IHThu5EgbsOgeSBraOG7j2kgYmnhu4duIGx14bqtbiBjaOG6qW4gxJFvw6FuIMSR4buDIGPDsyB0aOG7gyB04bqtcCB0cnVuZyB2w6BvIHnhur91IHThu5EgbmdoaSBuZ+G7nSBsw6AgY8OzIHPhu7EgdMSDbmcvZ2nhuqNtIGLhuqV0IHRoxrDhu51uZyBj4bunYSBZIGRvIG5ndXnDqm4gbmjDom4gYuG7h25oIGzDvS4NCg0KDQojIE5oxrDhu6NjIMSRaeG7g20gY+G7p2EgcGjGsMahbmcgcGjDoXAgdGjhu6cgY8O0bmcNCg0KVHJvbmcgaMOgbmcgY2jhu6VjIG7Eg20gcXVhLCBZIGdp4bubaSB04bqhbyByYSBuaOG7r25nIG3DtCBow6xuaCBnacOhIHRy4buLIHRoYW0gY2hp4bq/dSB0aGVvIGPDoWNoIGhvw6BuIHRvw6BuIHRo4bunIGPDtG5nLiDEkOG6p3UgdGnDqm4sIHjDqXQgbmdoaeG7h20gxJHGsOG7o2MgdGjhu7FjIGhp4buHbiB0csOqbiBt4buZdCBt4bqrdSB24bubaSBrw61jaCB0aMaw4bubYyB04burIHbDoGkgdHLEg20gxJHhur9uIGjDoG5nIG5nw6BuIGPDoSB0aOG7gywgxJHGsOG7o2MgZ2nhuqMgxJHhu4tuaCBsw6AgxJHhuqFpIGRp4buHbiBjaG8gcXXhuqduIHRo4buDIG5nxrDhu51pIGto4buPZSBt4bqhbmguIEThu68gbGnhu4d1IHNhdSDEkcOzIHBo4bqjaSDEkcaw4bujYyBuaOG6rXAgdGjhu6cgY8O0bmcgdsOgbyBtw6F5IHTDrW5oIHbDoCB0w61jaCB04bulIHRoZW8gdGjhu51pIGdpYW4gY2hvIMSR4bq/biBraGkgxJHhuqF0IGPhu6EgbeG6q3UgbW9uZyBtdeG7kW4uIFRo4bqtbSBjaMOtIGtoaSBuaOG7r25nIG5naGnDqm4gY+G7qXUgbG/huqFpIG7DoHkgxJHGsOG7o2MgdOG7lSBjaOG7qWMg4bufIGPhuqVwIHF14buRYyBnaWEsIHF1w6EgdHLDrG5oIG7DoHkgdGjGsOG7nW5nIHLhuqV0IGNo4bqtbSwgdsOgIGPDsyB0aOG7gyBrw6lvIGTDoGkgdOG7qyAxLTMgbsSDbSB0w7l5IHRoZW8gY+G7oSBt4bqrdS4NCg0KU2F1IGtoaSBjw7MgZOG7ryBsaeG7h3UsIG3hu5l0IGNodXnDqm4gdmnDqm4gdGjhu5FuZyBrw6ogc+G6vSBi4bqvdCDEkeG6p3UgZOG7sW5nIG3DtCBow6xuaC4gxJDhu5NuZyBow6BuaCB24bubaSBs4buLY2ggc+G7rSBwaMOhdCB0cmnhu4NuIGPhu6dhIG5nw6BuaCBUaOG7kW5nIGvDqiwgxJHhu5kgcGjhu6ljIHThuqFwIGPhu6dhIGjDoG0gZiBk4bqnbiBk4bqnbiB0xINuZyBsw6puIOKAkyB04burIG3hu5l0IGjhurFuZyBz4buRIHRydW5nIGLDrG5oIMSR4bq/biBtw7QgaMOsbmggdHV54bq/biB0w61uaCDEkcahbiBiaeG6v24sIHLhu5NpIMSRYSBiaeG6v24sIHLhu5NpIMSR4bq/biBtw7QgaMOsbmggxJFhIHRo4bupYywgcGhpIHR1eeG6v24gdMOtbmguIFbDoG8gbsSDbSAyMDE4IG7DoHksIMSR4buJbmggY2FvIG5o4bqldCB24buBIHPhu7EgdGluaCB04bq/IGPhu6dhIGjDoG0gZiBsw6AgbcO0IGjDrG5oIDMgdGhhbSBz4buRIExNUyBr4bq/dCBo4bujcCB24bubaSBow6BtIHNwbGluZSB2w6AgaMOgbSDEkWEgdGjhu6ljLiBUdXkgbmhpw6puLCBjw7MgbeG7mXQgxJFp4buBdSBraMO0bmcgdGhheSDEkeG7lWksIMSRw7MgbMOgIHZp4buHYyBk4buxbmcgbcO0IGjDrG5oIGhvw6BuIHRvw6BuIHRo4bunIGPDtG5nLCB0aMaw4budbmcgYuG6sW5nIHBoxrDGoW5nIHBow6FwIFN0ZXAtd2lzZS4gUXV5IHRyw6xuaCBuw6B5IGPDsyB0aOG7gyBrw6lvIGTDoGkgdsOgaSB0aMOhbmcgaG/hurdjIGzDonUgaMahbi4gDQoNClNhdSBraGkgY8OzIG3DtCBow6xuaCwgY8OhYyB0w6FjIGdp4bqjIHPhur0gY8O0bmcgYuG7kSBuw7MgZMaw4bubaSBk4bqhbmcgMSBiw6BpIGLDoW8ga2hvYSBo4buNYyB0csOqbiB04bqhcCBjaMOtLiBRdXkgdHLDrG5oIGLDrG5oIGR1eeG7h3QgY2hvIGLDoGkgYsOhbyBjw7MgdGjhu4MgZMOgaSB04burIDEgxJHhur9uIDYgdGjDoW5nIG7hu69hIGNobyDEkeG6v24ga2hpIG7DsyB0aOG7sWMgc+G7sSDEkcaw4bujYyBjw7RuZyBi4buRLiBTYXUga2hpIHh14bqldCBi4bqjbiwgYsOgaSBiw6FvIHPhur0gxJHGsOG7o2MgY2h1eeG7g24gcXVhIG5nw6BuaCBjw7RuZyBuZ2hp4buHcCB0aGnhur90IGLhu4sgeMOpdCBuZ2hp4buHbSwgbsahaSBjw6FjIGvhu7kgc8awIHBo4bqnbiBt4buBbSBz4bq9IMSRxrBhIG3DtCBow6xuaCBmIHbDoG8gY29kZSBj4bunYSBwaOG6p24gbeG7gW0gKFBDIHNvZnR3YXJlIGhheSBmaXJtd2FyZSkuIEPDtG5nIMSRb+G6oW4gbsOgeSBjw7MgdGjhu4MgbeG6pXQgdGjDqm0gdsOgaSB0aMOhbmcgbuG7r2EuDQoNCiFbXShyZWZlcmVuY2V2YWx1ZXMxLnBuZykNCg0KTmjGsCB24bqteSwgdOG7qyBraGkgw70gdMaw4bufbmcgduG7gSBtw7QgaMOsbmggxJHGsOG7o2MgxJHhu4MgeHXhuqV0IGNobyDEkeG6v24ga2hpIG7DsyB0aOG7sWMgc+G7sSDEkcaw4bujYyDDoXAgZOG7pW5nIHRyw6puIGzDom0gc8OgbmcgdGjGsOG7nW5nIHBo4bqjaSBt4bqldCB04burIDItNSBuxINtLg0KDQojIMOdIHTGsOG7n25nIHbhu4EgdGhp4bq/dCBi4buLIHjDqXQgbmdoaeG7h20gdGjDtG5nIG1pbmgNCg0KQsOieSBnaeG7nSwgYuG6oW4gdGjhu60gdMaw4bufbmcgdMaw4bujbmcgduG7gSBt4buZdCBxdXkgdHLDrG5oIG3hu5tpIGNobyBwaMOpcCBt4buZdCBjw6FpIG3DoXkgeMOpdCBuZ2hp4buHbSBjw7Mga2jhuqMgbsSDbmcgdOG7sSB0aHUgdGjhuq1wIGThu68gbGnhu4d1IG3hu5l0IGPDoWNoIGxpw6puIHThu6VjIHbDoCDhu58gdGjhu51pIGdpYW4gdGjhu7FjLCBo4buNYyB04burIGThu68gbGnhu4d1IG7DoHkgbmfDoHkgbsOgeSBxdWEgbmfDoHkga2jDoWMgdsOgIHThu7EgdMOsbSByYSBxdXkgbHXhuq10IChow6BtIGYpIHThu5FpIMawdSBuaOG6pXQgY2hvIHBow6lwIMaw4bubYyBsxrDhu6NuZyBnacOhIHRy4buLIHRoYW0gY2hp4bq/dSBy4buTaSDDoXAgZOG7pW5nIGNobyBjaMOtbmggbsOzIG3DoCBraMO0bmcgY+G6p24gbeG7mXQgc+G7sSBjYW4gdGhp4buHcCBuw6BvIHThu6sgY29uIG5nxrDhu51pID8gSMahbiBu4buvYSwgY8OhaSBtw6F5IGPDsm4gY8OzIGto4bqjIG7Eg25nIHThu7Ega2jhuq9jIHBo4bulYyBuaOG7r25nIHNhaSBzw7N0IGPhu6dhIG3DtCBow6xuaCBraGkgdGnhur9wIHjDumMgduG7m2kgZOG7ryBsaeG7h3UgbeG7m2kgdsOgIGPhuq1wIG5o4bqtdCBtw7QgaMOsbmggxJHhu4Mgdmnhu4djIGThu7EgYsOhbyBsdcO0biBjaMOtbmggeMOhYyBuaOG6pXQgY8OzIHRo4buDID8NCg0KxJDDonkgY8OzIGzhur0gbMOgIGjDrG5oIHRo4bupYyBzxqEga2hhaSBuaOG6pXQgY+G7p2EgwqsgdHLDrSB0deG7hyBuaMOibiB04bqhbyDCuywgbmjGsG5nIGtow7RuZyBwaOG6o2kgbMOgIG3hu5l0IGNodXnhu4duIHZp4buFbiB0xrDhu59uZy4gVHJvbmcgYsOgaSB0aOG7sWMgaMOgbmggc2F1IMSRw6J5LCBOaGkgc+G6vSB0aOG7sWMgaGnhu4duIG3hu5l0IHRow60gbmdoaeG7h20gxJHhu4MgbWluaCBo4buNYSBjaG8gw70gdMaw4bufbmcgbsOgeS4gVsOgbyBjdeG7kWkgYsOgaSwgY8OhYyBi4bqhbiBz4bq9IHRo4bqleSBy4bqxbmcgw70gdMaw4bufbmcgeMOieSBk4buxbmcgZ2nDoSB0cuG7iyB0aGFtIGNoaeG6v3UgdOG7sSDEkeG7mW5nIGtow7RuZyBuaOG7r25nIGto4bqjIHRoaSwgbcOgIGPDsm4gdOG7kXQgaMahbiBzbyB24bubaSBjw6FjaCBsw6BtIG3DtCBow6xuaCB0aOG7pyBjw7RuZywgdGjhuq1tIGNow60gduG7m2kgY8O0bmcgY+G7pSB04buRdCBuaOG6pXQgaGnhu4duIG5heSBsw6AgbcO0IGjDrG5oIExNUy4gDQoNCiFbXShSZWZ2YWx1ZXNBSS5wbmcpDQoNCiMgVGjDrSBuZ2hp4buHbSBtaW5oIGjhu41hDQoNClRyb25nIHRow60gZOG7pSBuw6B5LCBOaGkgxJHhurd0IHbhuqVuIMSR4buBIGdp4bqjIMSR4buLbmggbmjGsCBzYXUgOiBN4buZdCBraG9hIHjDqXQgbmdoaeG7h20gY2jhu6ljIG7Eg25nIGjDtCBo4bqlcCB04bqhaSBxdeG7kWMgZ2lhIEMgc+G7nyBo4buvdSBt4buZdCBkYXRhYmFzZSBr4bq/dCBxdeG6oyB0aOG7gyB0w61jaCBwaOG6vyBuYW5nIChBVikgdsOgIGjhu4cgc+G7kSBraHXhur9jaCB0w6FuIGtow60gQ08gKERMQ08pIHRyw6puIDYyNyBuZ8aw4budaSBiw6xuaCB0aMaw4budbmcuIENoxrBhIGPDsyBnacOhIHRy4buLIHRoYW0gY2hp4bq/dSBjaG8gQVYgdsOgIERMQ08g4bufIG5nxrDhu51pIGTDom4gY+G7p2EgcXXhu5FjIGdpYSBDLiBUcsaw4bufbmcga2hvYSBnaWFvIG5oaeG7h20gduG7pSBjaG8gcGjDsm5nIFRo4buRbmcga8OqIGLhu4duaCB2aeG7h24gxJHhu4MgZOG7sW5nIG3DtCBow6xuaCBk4buxIGLDoW8gZ2nDoSB0cuG7iyB0aGFtIGNoaeG6v3UgQVYgdsOgIERMQ08gY2hvIHJpw6puZyBOYW0gdsOgIE7hu68uQ2h1ecOqbiB2acOqbiB0aOG7kW5nIGvDqiBraMOhIHThu7EgdGluIGNobyBiaeG6v3QgYW5oIHRhIGJp4bq/dCBsw6BtIG3DtCBow6xuaCBMYW1uZGEgTXUgU2lnbWEgKExNUyksIG3hu5l0IHBoxrDGoW5nIHBow6FwIHThu5FpIHTDom4gbmjhuqV0IGNobyBtw7QgaMOsbmggdGhhbSBjaGnhur91IGhp4buHbiDEkcaw4bujYyBI4buZaSBow7QgaOG6pXAgQ2jDonUgw4J1LCBIb2Ega+G7syB2w6AgV0hPIMOhcCBk4bulbmcuIA0KDQpDw7luZyBsw7pjIMSRw7MsIG3hu5l0IGPDtCBnw6FpIGzDoG0g4bufIHBow7JuZyBxdeG6o24gbMO9IGThu68gbGnhu4d1IGPhu6dhIGLhu4duaCB2aeG7h24gbXXhu5FuIHRo4butIG5naGnhu4dtIG3hu5l0IMO9IHTGsOG7n25nIGtow6FjIDogVMO0aSBz4bq9IMOhcCBk4bulbmcgbeG7mXQgRGVlcCBuZXVyYWwgbmV0d29yayAobeG6oW5nIHRo4bqnbiBraW5oIG5ow6JuIHThuqFvIHPDonUpIHbhu5tpIFRlbnNvcmZsb3cgxJHhu4MgIHThuqFvIHJhIG3hu5l0IGZyYW1ld29yayBuaOG6sW0gdGjhu7FjIGhp4buHbiBjw7luZyBt4bulYyB0acOqdSA6IMav4bubYyBsxrDhu6NuZyBnacOhIHRy4buLIEFWIHbDoCBETENPIGThu7FhIHbDoG8gZOG7ryBsaeG7h3UgxJHhuqd1IHbDoG8gbMOgIDMgYmnhur9uIDogVHXhu5VpLCBHaeG7m2kgdMOtbmgsIENoaeG7gXUgY2FvLiBIw6N5IGNobyB0w7RpIGPGoSBo4buZaSBsw6BtIHRo4butLCBy4buTaSBjaMO6bmcgdGEgc+G6vSBzbyBzw6FuaCBoaeG7h3UgcXXhuqMgZ2nhu69hIGPDoWNoIGPhu6dhIHTDtGkgdsOgIGPDoWNoIGPhu6dhIGFuaC4NCg0KTeG7mXQgY3Xhu5ljIHRoaSDEkeG6pXUgYuG6r3QgxJHhuqd1IC4uLg0KDQpUcsaw4bubYyBo4bq/dCwgZOG7ryBsaeG7h3UgxJHGsOG7o2MgY2hpYSBuZ+G6q3Ugbmhpw6puIHJhIDIgcGjhuqduOiBt4buZdCBwaOG6p24gZMO5bmcgxJHhu4MgZOG7sW5nIG3DtCBow6xuaCAobj01MDApIGNodW5nIGNobyBj4bqjIDIgcGjGsMahbmcgcGjDoXAgTE1TIHbDoCBEZWVwIGxlYXJuaW5nLCBt4buZdCBwaOG6p24gbmjhu48gaMahbiAobj0xMjcpIHPhur0gxJHGsOG7o2MgZMO5bmcgbMOgbSBk4buvIGxp4buHdSBraeG7g20gxJHhu4tuaCBwaOG6qW0gY2jhuqV0IHbDoCBzbyBzw6FuaCAyIG3DtCBow6xuaC4NCg0KTMawdSDDvSBy4bqxbmcgZOG7ryBsaeG7h3Uga2nhu4NtIMSR4buLbmggc+G6vSDEkcaw4bujYyBk4bqldSBrw61uLCBj4bqjIDIgcGjDrWEgxJHhu4F1IGtow7RuZyBiaeG6v3QgZ8OsIHbhu4EgbuG7mWkgZHVuZyBj4bunYSBk4buvIGxp4buHdSBuw6B5IGNobyDEkeG6v24ga2hpIG7DsyDEkcaw4bujYyBkw7luZyDEkeG7gyBzbyBzw6FuaCBtw7QgaMOsbmggY+G7p2EgaOG7jS4NCg0KIyBUaMSDbSBkw7IgZOG7ryBsaeG7h3UgDQoNCmBgYHtyLG1lc3NhZ2UgPSBGQUxTRSx3YXJuaW5nPUZBTFNFfQ0KbGlicmFyeSh0aWR5dmVyc2UpDQoNCm5kZjwtcmVhZC5jc3YoIkRMQ09rZXJhcy5jc3YiLHNlcD0iOyIpDQoNCm5kZjwtbmRmJT4lZHBseXI6OnNlbGVjdCgtR3JvdXApJT4lZmlsdGVyKERMQ08+MTEuNSxETENPPDU1KQ0KDQojIG9yaWdpbmFsIHRyYWluL3Rlc3QgZGF0YQ0KaWR4PWNhcmV0OjpjcmVhdGVEYXRhUGFydGl0aW9uKHk9bmRmJFNleCwgcD00OTkvNjI3LGxpc3Q9RkFMU0UpDQp0cmFpbnNldD1uZGZbaWR4LF0NCnRlc3RzZXQ9bmRmWy1pZHgsXQ0KDQp0cmFpbnNldCU+JWhlYWQoKSU+JWtuaXRyOjprYWJsZSgpDQpgYGANCg0KROG7ryBsaeG7h3UgZ+G7k20gNSBiaeG6v24sIDIgYmnhur9uIGvhur90IHF14bqjIGzDoCBBViB2w6AgRExDTywgMyBwcmVkaWN0b3JzIGzDoCBTZXgsIEFnZSB2w6AgSGVpZ2h0LiBUcsaw4bubYyBo4bq/dCwgdGEgYmnhur90IGNo4bqvYyBjaOG6r24gcuG6sW5nIEFWIGhv4bq3YyBETENPIHBo4bqjaSBsacOqbiBo4buHIHbhu5tpIFNleCxBZ2UgdsOgIEhlaWdodCB0aGVvIG3hu5l0IGtp4buDdSBuw6BvIMSRw7MsIHRow60gZOG7pTogbuG7ryBnaeG7m2kgdGjGsOG7nW5nIGPDsyBnacOhIHRy4buLIHRo4bqlcCBoxqFuIE5hbSBnaeG7m2ksIG5nxrDhu51pIGdpw6Agc3V5IGdp4bqjbSBnacOhIHRy4buLIGRvIGhp4buHbiB0xrDhu6NuZyBsw6NvIGjDs2Egc2luaCBsw70sIGR1bmcgdMOtY2ggcGjhu5VpIHRow6wgdOG7iSBs4buHIHRodeG6rW4gduG7m2kgY2hp4buBdSBjYW8uLi4NCg0KVHV5IG5oacOqbiwgduG6pW4gxJHhu4Ega2jDsyBraMSDbiDEkcOzIGzDoDogbmjhu69uZyBt4buRaSBsacOqbiBo4buHIG7DoHkgbMOgIHBoaSB0dXnhur9uIHTDrW5oIG5oxrAgYmnhu4N1IMSR4buTIGLDqm4gZMaw4bubaToNCg0KYGBge3IsbWVzc2FnZSA9IEZBTFNFLHdhcm5pbmc9RkFMU0V9DQojIERhdGEgdmlzdWFsaXNhdGlvbg0KDQp0cmFpbnNldCU+JWdhdGhlcihBVixETENPLGtleT0iT3V0Y29tZSIsdmFsdWU9IlZhbHVlIiklPiUNCiAgZ2dwbG90KGFlcyh4PUFnZSx5PVZhbHVlKSkrDQogIGdlb21fcG9pbnQoYWVzKGNvbD1TZXgpLGFscGhhPTAuMikrDQogIGdlb21fc21vb3RoKGFlcyhmaWxsPVNleCxjb2w9U2V4KSxhbHBoYT0wLjUpKw0KICB0aGVtZV9idygpKw0KICBmYWNldF9ncmlkKE91dGNvbWV+U2V4LHNjYWxlcyA9ICJmcmVlIikrDQogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcz1jKCJyZWQiLCJibHVlIikpKw0KICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzPWMoInJlZDMiLCJibHVlMyIpKQ0KDQp0cmFpbnNldCU+JWdhdGhlcihBVixETENPLGtleT0iT3V0Y29tZSIsdmFsdWU9IlZhbHVlIiklPiUNCiAgZ2dwbG90KGFlcyh4PUhlaWdodCx5PVZhbHVlKSkrDQogIGdlb21fcG9pbnQoYWVzKGNvbD1TZXgpLGFscGhhPTAuMikrDQogIGdlb21fc21vb3RoKGFlcyhmaWxsPVNleCxjb2w9U2V4KSxhbHBoYT0wLjUpKw0KICB0aGVtZV9idygpKw0KICBmYWNldF9ncmlkKE91dGNvbWV+U2V4LHNjYWxlcyA9ICJmcmVlIikrDQogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcz1jKCJyZWQiLCJibHVlIikpKw0KICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzPWMoInJlZDMiLCJibHVlMyIpKQ0KYGBgDQoNCk3hu5l0IG3DtCBow6xuaCBo4buTaSBxdXkgdOG7kXQgcGjhuqNpIGPDsyBraOG6oyBuxINuZyBtw7QgcGjhu49uZyDEkcaw4bujYyBxdXkgbHXhuq10IHBoaSB0dXnhur9uIHTDrW5oIG7DoHkgdGhlbyB0deG7lWkgdsOgL2hv4bq3YyBjaGnhu4F1IGNhby4gVGEgc+G6vSB0aGVvIGTDtWkgeGVtIGFuaCBjaHV5w6puIHZpw6puIHRo4buRbmcga8OqIHbDoCBjaOG7iyBjaHV5w6puIHZpw6puIGRhdGEgc2NpZW5jZSB44butIGzDvSB24bqlbiDEkeG7gSBuaMawIHRo4bq/IG7DoG8gbmjDqSA6DQoNCiMgTcO0IGjDrG5oIExNUyB0aOG7pyBjw7RuZyBi4bqxbmcgU3RlcC13aXNlDQoNClRoZW8gdHJ1eeG7gW4gdGjhu5FuZywgY2jDum5nIHRhIGPhuqduIHBo4bqjaSBk4buxbmcgbeG7mXQgbcO0IGjDrG5oIHJpw6puZyBjaG8gbeG7l2kgZ2nhu5tpIHTDrW5oIChNLCBGKSwgZG8gxJHDsyBxdXkgdHLDrG5oIGLhuq90IMSR4bqndSBi4bqxbmcgdmnhu4djIGNoaWEgZOG7ryBsaeG7h3UgZ+G7kWMgdGjDoG5oIDIgcGjhuqduIGNobyByacOqbmcgTmFtIHbDoCBO4buvOg0KDQpgYGB7cixtZXNzYWdlID0gRkFMU0Usd2FybmluZz1GQUxTRX0NCiMgdHJhaW5zZXQgZm9yIExNUw0KdHJhaW5MTVNfTTwtdHJhaW5zZXQlPiVmaWx0ZXIoU2V4PT0iTSIpDQp0cmFpbkxNU19GPC10cmFpbnNldCU+JWZpbHRlcihTZXghPSJNIikNCg0KIyB0ZXN0c2V0IGZvciBMTVMNCnRlc3RMTVNfTTwtdGVzdHNldCU+JWZpbHRlcihTZXg9PSJNIikNCnRlc3RMTVNfRjwtdGVzdHNldCU+JWZpbHRlcihTZXghPSJNIikNCmBgYA0KDQpQaMawxqFuZyBwaMOhcCBtw7QgaMOsbmggMyB0aGFtIHPhu5EgKExhbWJkYS1NdS1TaWdtYSwgTE1TKSBkbyAyIHTDoWMgZ2nhuqMgUmlnYnkgUkEuIHbDoCBTdGFzaW5vcG91bG9zIERNIHThuqFvIHJhIHbDoG8gbsSDbSAyMDAxIMSR4buDIMaw4bubYyBsxrDhu6NuZyBt4buZdCBiaeG6v24gc+G7kSBuZ+G6q3Ugbmhpw6puLCBsacOqbiB04bulYyB2w6Aga2jDtG5nIMOibSwgY8OzIHF1eSBsdeG6rXQgYmnhur9uIHRoacOqbiBwaGkgdHV54bq/biB0w61uaCB0aGVvIHRo4budaSBnaWFuIChUdeG7lWkpLkxNUyBsaW5oIGhv4bqhdCB2w6wga2jDtG5nIGThu7FhIHbDoG8gcGjDom4gcGjhu5FpIEdhdXNzaWFuIG5oxrAgxJFhIHPhu5EgbcO0IGjDrG5oIHRo4buRbmcga8OqIGtow6FjLiBMTVMgY2hvIHBow6lwIHTDoWkgaGnhu4duIHF14bu5IHTDrWNoIHBo4bupYyB04bqhcCBj4bunYSBZLg0KDQpMTVMgY8OzIGLhuqNuIGNo4bqldCBsw6AgbeG7mXQgZOG6oW5nIMSR4bq3YyBiaeG7h3QgdGh14buZYyBo4buNIG3DtCBow6xuaCBHQU0gKEdlbmVyYWxpc2VkIGFkZGl0aXZlIG1vZGVsKS5O4buBbiB04bqjbmcgY+G7p2EgTE1TIG1vZGVsIGzDoCBo4buNIHBow6JuIHBo4buRaSBCb3gtQ294LUNvbGUtR3JlZW4gKEJDQ0cpLCDEkcaw4bujYyB4w6FjIMSR4buLbmggYuG7n2kgMyB0aGFtIHPhu5EsIGJhbyBn4buTbSB24buLIHRyw60gdHJ1bmcgdMOibSBob+G6t2MgdHJ1bmcgYsOsbmggKE11KSwgU2NhbGUgKFNpZ21hKSB2w6AgU2tld25lc3MgKE51KS4NCg0KTE1TIGzDoCBt4buZdCBkaXN0cmlidXRpb25hbCBtb2RlbCwgdOG7qWMgbeG7mXQgdOG6rXAgaOG7o3AgMyBtw7QgaMOsbmggcmnDqm5nIGJp4buHdCBNLFMsTCBjaG8gMyB0aGFtIHPhu5EgOiBNdSAoduG7iyB0cuG7iyB0cnVuZyB0w6JtKSwgU2lnbWEgKFNjYWxlKSB2w6AgTGFtYmRhIChTa2V3bmVzcyksIHTGsMahbmcg4bupbmcgduG7m2kgMyB0aGFtIHPhu5EgTXUgKGxpbmsgZnVuY3Rpb24gPSBsb2cpLCBTaWdtYSAobGluaz1sb2cpIHbDoCBOdSAobGluayA9IGlkZW50aXR5KSBj4bunYSBxdXkgbHXhuq10IHBow6JuIHBo4buRaSBCb3gtQ294LUNvbGUtR3JlZW4uIA0KDQpN4buZdCBtw7QgaMOsbmggaOG7k2kgcXV5IEdBTUxTUyB24bubaSBiaeG6v24ga+G6v3QgcXXhuqMgT3V0Y29tZSBsw6AgWSB0dcOibiB0aGVvIHF1eSBsdeG6rXQgQkNDRyA6DQoNCiQkWSBcc2ltIEJDQ0coXG11ICxcc2lnbWEgLFxudSApJCQNCg0KTeG7l2kgdGhhbSBz4buRIHRoZXRhICh0aMOtIGThu6UgTXUsIFNpZ21hLCBOdSwgWmVyb19pbmZsYXRlZCzigKYpIGPDsyB0aOG7gyDEkcaw4bujYyB4ZW0gbmjGsCBt4buZdCBvdXRjb21lIHRo4bupIGPhuqVwLCB2w6AgY8OzIHRo4buDIMSRxrDhu6NjIG3DtCBow6xuaCBow7NhIHJpw6puZyBiaeG7h3QgYuG7n2kgbeG7mXQgbGluayBmdW5jdGlvbiBmICB24bubaSBu4buZaSBkdW5nIG3DtCBow6xuaCBs4bqnbiBsxrDhu6N0IG5oxrAgc2F1IDoNCg0KJCRcbXUgXHNpbSBsbihcYmV0YSBYICsgQlNwbGluZSh4KSkkJA0KDQokJFxzaWdtYSBcc2ltIGxuKFxiZXRhIFggKyBCU3BsaW5lKHgpKSQkDQoNCiQkXG51IFxzaW0gaWRlbnRpdHkoXGJldGEgWCArIEJTcGxpbmUoeCkpJCQNCg0KVuG7m2kgOiBNdSwgc2lnbWEgdsOgIE51IGzhuqduIGzGsOG7o3QgbMOgIDMgdGhhbSBz4buRIGPhu6dhIHBow6JuIHBo4buRaSBCQ0NHLCANCkJldGEgxJHhuqFpIGRp4buHbiBjaG8gdGhhbSBz4buRIGjhu5NpIHF1eSB0xrDGoW5nIOG7qW5nIHbhu5tpIG3hu5l0IG1vZGVsIG1hdHJpeCBYLCBsw6AgdOG6rXAgaOG7o3AgY+G7p2Egbmhp4buBdSBiaeG6v24gcHJlZGljdG9yIHRyb25nIG3DtCBow6xuaCwgdGjDrSBk4bulIFR14buVaSwgQ2hp4buBdSBjYW8gdHJvbmcgbcO0IGjDrG5oIGThu7EgYsOhbyBkdW5nIEFWIGhv4bq3YyBETENPLi4uDQoNClThu6sgbcO0IGjDrG5oIExNUywgdGEgY8OzIHRo4buDIMaw4bubYyB0w61uaCA6IGdpw6EgdHLhu4sgdHJ1bmcgYsOsbmggbMO9IHRodXnhur90IGPhu6dhIE91dGNvbWUsIDIgbmfGsOG7oW5nIGNhbyBuaOG6pXQgdsOgIHRo4bqlcCBuaOG6pXQuIA0KDQokJExMTiA9IEV4cCBcbGVmdCBbIGxuKFxtdSkrXGZyYWN7bG4oMS0xLjY0NSpcbnUgXHNpZ21hKX17XG51fVxyaWdodCBdICQkDQoNCiQkVUxOID0gRXhwIFxsZWZ0IFsgbG4oXG11KStcZnJhY3tsbigxKzEuNjQ1KlxudSBcc2lnbWEpfXtcbnV9XHJpZ2h0IF0kJA0KTeG7l2kgbcO0IGjDrG5oIE0sTCxTIGzDoCBt4buZdCBtw7QgaMOsbmggR0FNIChnZW5lcmFsaXplZCBhZGRpdGl2ZSBtb2RlbCksIG7Dqm4gbWF0cml4IFggY2hvIHBow6lwIHTDrWNoIGjhu6NwIHRow6ptIFNtb290aGluZyBzcGxpbmVzIChjw6FjIGjDoG0gxJFp4buBdSBow7JhL2hp4buHdSBjaOG7iW5oKSB2w6AgaMOgbSDEkWEgdGjhu6ljIChQb2x5bm9taWFsKS4gQ2jDrW5oIG5o4buvbmcgaMOgbSBwb2x5bm9taWFsIHbDoCBzbW9vdGhpbmcgbsOgeSBjaG8gcGjDqXAgxJHhu5MgdGjhu4sgTE1TIG1vZGVsIHXhu5FuIGzGsOG7o24gbeG7gW0gbeG6oWkgdGhlbyBuaOG7r25nIGjDrG5oIGThuqFuZyBwaOG7qWMgdOG6oXAgbmjhuqV0IGPDsyB0aOG7gyB2w6AgdMSDbmcgdMOtbmggY2jDrW5oIHjDoWMgY+G7p2EgbcO0IGjDrG5oLiBUdXkgbmhpw6puIMSRw6J5IGPFqW5nIGPDsyB0aOG7gyB0cuG7nyB0aMOgbmggbmjGsOG7o2MgxJFp4buDbSB2w6wgbuG6v3UgcXXDoSB0aGFtIGxhbSwgdGEgc+G6vSB04bqhbyByYSBtw7QgaMOsbmggYuG7iyBvdmVyZml0LCBuaOG6pXQgbMOgIGtoaSBk4buvIGxp4buHdSB0cmFpbnNldCBo4buXbiB04bqhcCB2w6AgY8OzIHPhu5EgbMaw4bujbmcgbOG7m24uIE3DtCBow6xuaCBvdmVyZml0IGNo4buJIGhv4bqhdCDEkeG7mW5nIHThu5F0IHRyw6puIGNow61uaCBk4buvIGxp4buHdSBkw7luZyDEkeG7gyB04bqhbyByYSBuw7MsIG5oxrBuZyBz4bq9IHBo4bqhbSBzYWkgbOG6p20ga2hpIMOhcCBk4bulbmcgdHLDqm4gdGjhu7FjIHThur8gduG7m2kgZOG7ryBsaeG7h3UgbeG7m2kuDQoNCk3DtCBow6xuaCBMTVMgxJHGsOG7o2MgZOG7sW5nIGLhurFuZyBwYWNrYWdlIGdhbWxzcyB0cm9uZyBSLiBUYSBz4bq9IHPhu60gZOG7pW5nIG3hu5l0IGNoaeG6v24gdGh14bqtdCDEkcahbiBnaeG6o24gxJHDsyBsw6AgQmFja3dhcmQgU3RlcC13aXNlICh0cmnhu4d0IHRob8OhaSB04buxIMSR4buZbmcsIGThu7FhIHbDoG8gQklDKS4gIFRow60gZOG7pSB0YSBjw7MgxJHhur9uIDMgbcO0IGjDrG5oIGNvbiBsw6AgTCxNLFM7IHbDoCBt4buXaSBtw7QgaMOsbmggZMO5bmcgMSDEkWEgdGjhu6ljIGLhuq1jIGNhbyBiYW8gZ+G7k20gc21vb3RoaW5nIHNwbGluZSwgdsOgIHThuqV0IGPhuqMgdOG7lSBo4bujcCBjw7MgdGjhu4MgZ2nhu69hIGNow7puZyBwaOG6o2kgxJHGsOG7o2Mga2nhu4NtIHRyYSBi4bqxbmcgY8OidSBo4buPaTogQ8OzIGPhuqduIG3DtCBow6xuaCBi4bqtYyAyLDMsNCBjaG8gQWdlIGhv4bq3YyBIZWlnaHQga2jDtG5nIGNobyBNdS9TaWdtYS9OdSA/IA0KDQpDw6FjIGJp4bq/biB0cm9uZyBmb3JtdWxhIHPhur0gYuG7iyB0cmnhu4d0IHRpw6p1IGThuqduICDEkeG6v24ga2hpIG3DtCBow6xuaCBjw7MgQUlDIHThu5FpIMawdSBoaeG7h24gcmEuIA0KDQpTYXUgxJHDonkgbOG6p24gbMaw4bujdCBsw6AgNCBtw7QgaMOsbmggY2hvIE5hbS9u4buvLCBBViB2w6AgRExDTzoNCg0KYGBge3IsbWVzc2FnZSA9IEZBTFNFLHdhcm5pbmc9RkFMU0V9DQoNCiMgTE1TDQoNCmxpYnJhcnkoZ2FtbHNzKQ0KbkM8LWRldGVjdENvcmVzKCkNCg0KIyBETENPIGZvciBNYWxlDQoNCm1tMT1nYW1sc3MoZGF0YT10cmFpbkxNU19NWywtMV0sDQogICAgICAgICAgRExDT34xLA0KICAgICAgICAgIHNpZ21hLmZvcm11bGEgPSBETENPfjEsDQogICAgICAgICAgbnUuZm9ybXVsYSA9IERMQ09+MSwNCiAgICAgICAgICBmYW1pbHk9QkNDRyhtdS5saW5rPSJsb2ciKSwNCiAgICAgICAgICB0cmFjZT1GQUxTRSwNCiAgICAgICAgICBwYXJhbGxlbD0ibXVsdGljb3JlIiwNCiAgICAgICAgICBuY3B1cyA9IG5DKQ0KDQptbTI9c3RlcEdBSUNBbGwuQShtbTEsc2NvcGU9bGlzdChsb3dlcj1+MSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHVwcGVyPX5wb2x5KEFnZSwzKStwb2x5KEhlaWdodCwzKStwYihBZ2UpKSwNCiAgICAgICAgICAgICAgICAgc2lnbWEuc2NvcGUgPSBsaXN0KGxvd2VyPX4xLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHVwcGVyPX5wb2x5KEFnZSwzKStwb2x5KEhlaWdodCwzKStwYihBZ2UpKSwNCiAgICAgICAgICAgICAgICAgbnUuc2NvcGUgPSBsaXN0KGxvd2VyPX4xLHVwcGVyPX5wb2x5KEFnZSwzKStwb2x5KEhlaWdodCwzKSksDQogICAgICAgICAgICAgICAgIGs9bG9nKGxlbmd0aCh0cmFpbkxNU19NWywtMV0pKSwNCiAgICAgICAgICAgICAgICAgdHJhY2U9RkFMU0UsDQogICAgICAgICAgICAgICAgIHBhcmFsbGVsPSJtdWx0aWNvcmUiLA0KICAgICAgICAgICAgICAgICBtZXRob2Q9UlMoMTAwMCksDQogICAgICAgICAgICAgICAgIG5jcHVzID0gbkMNCikNCg0KbW9kRExfTT1tbTINCg0Kc3VtbWFyeShtb2RETF9NKQ0KDQojIERMQ08gZm9yIGZlbWFsZQ0KDQptZjE9Z2FtbHNzKGRhdGE9dHJhaW5MTVNfRlssLTFdLA0KICAgICAgICAgICBETENPfjEsDQogICAgICAgICAgIHNpZ21hLmZvcm11bGEgPSBETENPfjEsDQogICAgICAgICAgIG51LmZvcm11bGEgPSBETENPfjEsDQogICAgICAgICAgIGZhbWlseT1CQ0NHKG11Lmxpbms9ImxvZyIpLA0KICAgICAgICAgICB0cmFjZT1GQUxTRSwNCiAgICAgICAgICAgcGFyYWxsZWw9Im11bHRpY29yZSIsDQogICAgICAgICAgIG5jcHVzID0gbkMpDQoNCm1mMj1zdGVwR0FJQ0FsbC5BKG1mMSxzY29wZT1saXN0KGxvd2VyPX4xLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHVwcGVyPX5wb2x5KEFnZSwzKStwb2x5KEhlaWdodCwzKStwYihBZ2UpKSwNCiAgICAgICAgICAgICAgICAgIHNpZ21hLnNjb3BlID0gbGlzdChsb3dlcj1+MSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdXBwZXI9fnBvbHkoQWdlLDMpK3BiKEFnZSkpLA0KICAgICAgICAgICAgICAgICAgbnUuc2NvcGUgPSBsaXN0KGxvd2VyPX4xLHVwcGVyPX5BZ2UpLA0KICAgICAgICAgICAgICAgICAgaz1sb2cobGVuZ3RoKHRyYWluTE1TX0ZbLC0xXSkpLA0KICAgICAgICAgICAgICAgICAgdHJhY2U9RkFMU0UsDQogICAgICAgICAgICAgICAgICBwYXJhbGxlbD0ibXVsdGljb3JlIiwNCiAgICAgICAgICAgICAgICAgIG1ldGhvZD1SUygxMDAwMCksDQogICAgICAgICAgICAgICAgICBuY3B1cyA9IG5DDQopDQoNCm1vZERMX0Y9bWYyDQoNCnN1bW1hcnkobW9kRExfRikNCg0KIyBWQSBmb3IgTWFsZQ0KDQptbTE9Z2FtbHNzKGRhdGE9dHJhaW5MTVNfTVssLTFdLA0KICAgICAgICAgIEFWfjEsDQogICAgICAgICAgc2lnbWEuZm9ybXVsYSA9IEFWfjEsDQogICAgICAgICAgbnUuZm9ybXVsYSA9IEFWfjEsDQogICAgICAgICAgZmFtaWx5PUJDQ0cobXUubGluaz0ibG9nIiksDQogICAgICAgICAgdHJhY2U9RkFMU0UsDQogICAgICAgICAgcGFyYWxsZWw9Im11bHRpY29yZSIsDQogICAgICAgICAgbmNwdXMgPSBuQykNCg0KbW0yPXN0ZXBHQUlDQWxsLkEobW0xLCBzY29wZT1saXN0KGxvd2VyPX4xLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdXBwZXI9fnBvbHkoQWdlLDMpK3BvbHkoSGVpZ2h0LDMpK3BiKEFnZSkpLA0KICAgICAgICAgICAgICAgICBzaWdtYS5zY29wZSA9IGxpc3QobG93ZXI9fjEsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdXBwZXI9fnBvbHkoQWdlLDMpK3BvbHkoSGVpZ2h0LDMpK3BiKEFnZSkpLA0KICAgICAgICAgICAgICAgICBudS5zY29wZSA9IGxpc3QobG93ZXI9fjEsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdXBwZXI9fnBvbHkoQWdlLDMpK3BvbHkoSGVpZ2h0LDMpK3BiKEFnZSkpLA0KICAgICAgICAgICAgICAgICBrPWxvZyhsZW5ndGgodHJhaW5MTVNfTVssLTFdKSksDQogICAgICAgICAgICAgICAgIHRyYWNlPUZBTFNFLA0KICAgICAgICAgICAgICAgICBwYXJhbGxlbD0ibXVsdGljb3JlIiwNCiAgICAgICAgICAgICAgICAgbWV0aG9kPVJTKDEwMDAwKSwNCiAgICAgICAgICAgICAgICAgbmNwdXMgPSBuQw0KKQ0KDQptb2RBVl9NPW1tMg0KDQpzdW1tYXJ5KG1vZEFWX00pDQoNCiMgQVYgZm9yIEZlbWFsZQ0KDQptZjE9Z2FtbHNzKGRhdGE9dHJhaW5MTVNfRlssLTFdLA0KICAgICAgICAgICBBVn4xLA0KICAgICAgICAgICBzaWdtYS5mb3JtdWxhID0gQVZ+MSwNCiAgICAgICAgICAgbnUuZm9ybXVsYSA9IEFWfjEsDQogICAgICAgICAgIGZhbWlseT1CQ0NHKG11Lmxpbms9ImxvZyIpLA0KICAgICAgICAgICB0cmFjZT1GQUxTRSwNCiAgICAgICAgICAgcGFyYWxsZWw9Im11bHRpY29yZSIsDQogICAgICAgICAgIG5jcHVzID0gbkMpDQoNCm1mMj1zdGVwR0FJQ0FsbC5BKG1tMSwgc2NvcGU9bGlzdChsb3dlcj1+MSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdXBwZXI9fnBvbHkoQWdlLDMpK3BvbHkoSGVpZ2h0LDMpK3BiKEFnZSkpLA0KICAgICAgICAgICAgICAgICAgc2lnbWEuc2NvcGUgPSBsaXN0KGxvd2VyPX4xLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB1cHBlcj1+cG9seShBZ2UsMykrcG9seShIZWlnaHQsMykrcGIoQWdlKSksDQogICAgICAgICAgICAgICAgICBudS5zY29wZSA9IGxpc3QobG93ZXI9fjEsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHVwcGVyPX5wb2x5KEFnZSwzKStwb2x5KEhlaWdodCwzKStwYihBZ2UpKSwNCiAgICAgICAgICAgICAgICAgIGs9bG9nKGxlbmd0aCh0cmFpbkxNU19GWywtMV0pKSwNCiAgICAgICAgICAgICAgICAgIHRyYWNlPUZBTFNFLA0KICAgICAgICAgICAgICAgICAgcGFyYWxsZWw9Im11bHRpY29yZSIsDQogICAgICAgICAgICAgICAgICBtZXRob2Q9UlMoMTAwMDApLA0KICAgICAgICAgICAgICAgICAgbmNwdXMgPSBuQw0KKQ0KDQptb2RBVl9GPW1mMg0KDQpzdW1tYXJ5KG1vZEFWX0YpDQoNCmBgYA0KDQojIE3DtCBow6xuaCBt4bqhbmcgdGjhuqduIGtpbmggbmjDom4gdOG6oW8gc8OidSAoRGVlcCBsZWFybmluZykNCg0KQ8O5bmcgbMO6YyBuw6B5LCBjw7QgY2h1ecOqbiB2acOqbiBEYXRhIHNjaWVuY2UgxJFhbmcgZ2nhuqNpIHF1eeG6v3QgduG6pW4gxJHhu4EgdGhlbyBt4buZdCBoxrDhu5tuZyDEkWkgaG/DoG4gdG/DoG4ga2jDoWMgduG7m2kgY8OhY2ggbMOgbSB0cnV54buBbiB0aOG7kW5nLiBTYXUgxJHDonkgbMOgIG5o4buvbmcgcGjDom4gdMOtY2ggY+G7p2EgY8OhIG5ow6JuIGPDtCDhuqV5Og0KDQoxKSBDaMO6bmcgdGEgY+G6r3QgYsOgaSB0b8OhbiBo4buTaSBxdXkgbsOgeSByYSB0aMOgbmggbmhp4buBdSBt4bqjbmggbmjhu48gKG3DtCBow6xuaCByacOqbmcgY2hvIE5hbSwgTuG7rywgQVYsIERMQ08uLi4pIMSR4buDIGzDoG0gZ8OsIGNobyBraMOzIGtoxINuID8gVOG6oWkgc2FvIHRhIGtow7RuZyB04bqhbyByYSAxIG3DtCBow6xuaCBEVVkgTkjhuqRUIGNobyBwaMOpcCDGsOG7m2MgbMaw4bujbmcgxJHhu5NuZyB0aOG7nWkgY+G6oyAyIG91dGNvbWVzIChBViwgRExDTyksdsOgIGNobyB04bqldCBj4bqjIG3hu41pIGPDoSB0aOG7gyBi4bqldCBr4buDIGdp4bubaSB0w61uaCA/DQoNCjIpIFRhIMSRw6MgeMOhYyDEkeG7i25oIHbhu4EgZOG7ryBsaeG7h3UgxJHhuqd1IHbDoG8sIHPhur0gZ+G7k20gMyBiaeG6v246IFNleCwgQWdlLCBIZWlnaHQsIHRhIGPFqW5nIMSRw6MgeMOhYyDEkeG7i25oIHPhur0gY8OzIDIgb3V0Y29tZXMgbMOgIEFWIHbDoCBETENPLiBOaMawIHbhuq15IG3hu6VjIHRpw6p1IGPhu6dhIHRhIGzDoCB04bqhbyByYSBt4buZdCBu4buBbiB04bqjbmcgY2hvIHBow6lwIHh14bqldCByYSAyIGdpw6EgdHLhu4sgQVYgdsOgIERMQ08gZ+G6p24gbmjhuqV0IHNvIHbhu5tpIGdpw6EgdHLhu4sgdGjhu7FjIHThur8uIFRhIGPDsm4gbXXhu5FuIHLhurFuZyBu4buBbiB04bqjbmcgbsOgeSBz4bq9IGNobyBwaMOpcCBjw6FpIG3DoXkgdOG7sSBuw7MgdGjDrWNoIG5naGkgduG7m2kgZOG7ryBsaeG7h3UgxJHhuqd1IHbDoG8gbeG7m2ksIG3DoCBraMO0bmcgY+G6p24gcGjhuqNpIHRow6FvIGThu6EgaOG7hyB0aOG7kW5nIHJhIHLhu5NpIGzhuq9wIHLDoXAgbOG6oWkgdOG7qyDEkeG6p3UgbmjGsCBwaMawxqFuZyBwaMOhcCBTdGVwLXdpc2UgdGjhu6cgY8O0bmcuIMSQaeG7gXUgbsOgeSBnaeG7kW5nIG5oxrAgdmnhu4djIHRoaeG6v3Qga+G6vyBt4buZdCBi4buHbmggdmnhu4duIMSR4buDIG7DsyBob+G6oXQgxJHhu5luZyB0cm9uZyAxMC0yMCBuxINtLCDEkeG6p3UgdsOgbyB0aeG6v3Agbmjhuq1uIGLhu4duaCBuaMOibiwgxJHhuqd1IHJhIGLhu4duaCBuaMOibiBsw6BuaCBi4buHbmguIEjhu4cgdGjhu5FuZyBiw6puIHRyb25nIGLhu4duaCB2aeG7h24gKHBow7JuZyDhu5FjLCBow6BuaCBsYW5nLCB5IGPhu6UsIG5ow6JuIHZpw6puIHkgdOG6vykgbMOgIGPhu5EgxJHhu4tuaCwgbmjGsG5nIGPDsyBraOG6oyBuxINuZyB0aMOtY2gg4bupbmcgduG7m2kgbmhp4buBdSBi4buHbmggbMO9IGtow6FjIG5oYXUsIGNo4buvYSBuaGnhu4F1IGLhu4duaCDEkeG7k25nIHRo4budaS4gTmjGsCB24bqteSBraGkgZ+G6t3AgIGLhu4duaCBraMOzIGNo4buvYSwgduG6pW4gxJHhu4EgbMOgIGjhu4cgdGjhu5FuZyBwaOG6o2kgdGjDrWNoIG5naGkgY2jhu6kgdGEga2jDtG5nIHRo4buDIMSR4bqtcCBi4buPIGLhu4duaCB2aeG7h24gdsOgIHjDonkgbOG6oWkgbeG7mXQgY8OhaSBt4bubaS4NCg0KSOG7hyB0aOG7kW5nIMSRw7MgY2jDrW5oIGzDoCBt4buZdCBt4bqhbmcgbmV1cm9uIG5ow6JuIHThuqFvIGPDsyBj4bqldSB0csO6YyBn4buTbSAzIHBo4bqnbjoNCg0KIVtdKEFOTkRMQ09BVi5wbmcpDQoNCkzhu5twIMSR4bqndSB0acOqbiBn4buTbSAzIHBlcmNlcHRyb24sIGPDsyBjaOG7qWMgbsSDbmcgbmjhuq1uIGThu68gbGnhu4d1IMSR4bqndSB2w6BvLCB0xrDGoW5nIOG7qW5nIDMgYmnhur9uIGzDoCBTZXggKGdpw6EgdHLhu4sgbmjhu4sgcGjDom4pLCBBZ2UgdsOgIEhlaWdodC4NCg0KVMOtbiBoaeG7h3UgxJFpIHF1YSBt4buXaSBuZXVyb24gc+G6vSDEkcaw4bujYyBrw61jaCBob+G6oXQgYuG6sW5nIG3hu5l0IGjDoG0gxJHhu4MgxJFp4buBdSBjaOG7iW5oIHRy4buNbmcgc+G7kSBj4bunYSB0w61uIGhp4buHdSB4deG6pXQgcmEgcXVhIG5o4buvbmcgbmV1cm9uIHRp4bq/cCB0aGVvIG7hurFtIHRyb25nIGzhu5twIHPDonUgKGhpZGRlbiBsYXllcnMpLiBN4bqhbmcgbmV1cm9uIG7DoHkgc+G6vSBjw7MgMiBoYXkgMyBs4bubcCBzw6J1IG5oxrAgduG6rXksIG3hu5dpIGzhu5twIGNo4bupYSB2w6BpIGNo4bulYyDEkeG6v24gdsOgaSB0csSDbSBub2Rlcy4gVHLGsOG7m2Mga2hpIGThuqtuIMSR4bq/biBs4bubcCBjdeG7kWkgY8O5bmcsIHTDrW4gaGnhu4d1IHPhur0gxJFpIHF1YSBt4buZdCBs4bubcCBjw7MgdmFpIHRyw7IgaGnhu4d1IGNow61uaCBz4butIGThu6VuZyAxIGjDoG0gbDIgcmVndWxhcmlzYXRpb24uIA0KDQpM4bubcCBjdeG7kWkgY8O5bmcgbMOgIMSR4bqndSByYSBj4bunYSBt4bqhbmcgbmV1cm9uLCBz4bq9IGfhu5NtIDIgbm9kZXMgdMawxqFuZyDhu6luZyB24bubaSAyIG91dGNvbWVzIGzDoCBETENPIHbDoCBBVi4NCg0KQ+G6pXUgdHLDumMgbsOgeSBz4bq9IMSRxrDhu6NjICJodeG6pW4gbHV54buHbiIgYuG6sW5nIHThu6tuZyDEkW/huqFuIGThu68gbGnhu4d1IHRyb25nIHRyYWluc2V0IHbDoCBraeG7g20gxJHhu4tuaCBxdWEgaMOgbmcgdHLEg20gbMaw4bujdCwgY2hvIMSR4bq/biBraGkgxJHhuqF0IHRy4bqhbmcgdGjDoWkgdOG7kWkgxrB1LiBRdcOhIHRyw6xuaCBodeG6pW4gbHV54buHbiBuw6B5IGPDsyBt4bulYyB0acOqdSBsw6AgZ2nhuqNtIHRoaeG7g3UgbeG7mXQgaMOgbSBt4bqldCBtw6F0IChsb3NzIGZ1bmN0aW9uKSB2w6AgdGnDqnUgY2jDrSB24buBIHNhaSBz4buRIGNo4bqpbiDEkW/DoW4sIHRow60gZOG7pSBNQUUgaGF5IFJNU0UuLi4NCg0KU2F1IGtoaSBodeG6pW4gbHV54buHbiB4b25nLCB0YSBjw7MgdHJvbmcgdGF5IDEgbcO0IGjDrG5oIERlZXAgbmV1cmFsIG5ldHdvcmsgY8OzIGto4bqjIG7Eg25nIMaw4bubYyBsxrDhu6NuZyDEkeG7k25nIHRo4budaSBBViB2w6AgRExDTyB04burIDMgYmnhur9uIMSR4bqndSB2w6BvLg0KDQpDw7RuZyBj4bulIMSRxrDhu6NjIHPhu60gZOG7pW5nIGzDoCBwYWNrYWdlIGtlcmFzLCBnaWFvIHRo4bupYyBjaG8gcGjDqXAgUiB0xrDGoW5nIHTDoWMgduG7m2kgVGVuc29mZmxvdyBj4bunYSBQeXRob24gKGLhuqFuIGPhuqduIGluc3RhbGwgdHLGsOG7m2MgbeG7mXQgZ8OzaSBQeXRob24gdGjDrSBk4bulIEFuYWNvbmRhIMSR4buDIGPDsyB0aOG7gyBz4butIGThu6VuZyBrZXJhcyB0cm9uZyBSKS4NCg0KR2lhbyB0aOG7qWMgS2VyYXMgdGnhur9wIG5o4bqtbiBt4buZdCBj4bqldSB0csO6YyBk4buvIGxp4buHdSDEkeG6t2MgYmnhu4d0IGfhu41pIGzDoCBjw6FjIHRlbnNvci4gVGEgY8OzIHRo4buDIGjDrG5oIGR1bmcgduG7gSB0ZW5zb3JzIG5oxrAga2jDoWkgbmnhu4dtIHThu5VuZyBxdcOhdCDEkeG7gyBn4buNaSB0w6puIG5o4buvbmcgxJHhu5FpIHTGsOG7o25nIGThu68gbGnhu4d1IG3DoCB0YSB04burbmcgYmnhur90IHRyb25nIFIgdGhlbyBz4buRIGNoaeG7gXUgdGjDtG5nIHRpbiAoZGltZW5zaW9uKS4gVGjDrSBk4bulIHZlY3RvciBsw6AgMUQgdGVuc29yLCBjw7JuIG1hdHJpeCBn4buTbSBuaGnhu4F1IGZlYXR1cmVzLCBuaGnhu4F1IGluc3RhbmNlcyDEkcaw4bujYyBn4buNaSBsw6AgMkQgdGVuc29yLiBUcm9uZyBSLCB0ZW5zb3IgxJHGsOG7o2MgbMawdSBkxrDhu5tpIGThuqFuZyBhcnJheSwgZG8gxJHDsyB0csaw4bubYyBo4bq/dCB0YSBj4bqnbiBob8OhbiBjaHV54buDbiBuaOG7r25nIGRhdGFmcmFtZSBoaeG7h24gY8OzIHRow6BuaCBhcnJheS4gS2VyYXMgdGnhur9wIG5o4bqtbiByacOqbmcgdGVuc29ycyBjaG8gdOG6rXAgZmVhdHVyZXMgdsOgIGNobyBiaeG6v24ga+G6v3QgcXXhuqMgbsOqbiB0YSBwaOG6o2kgdMOhY2ggcmnDqm5nIDIgcGjhuqduIG7DoHkgdOG7qyB04bqtcCB0cmFpbiB2w6AgdGVzdA0KDQpD4bqnbiBsxrB1IMO9IGzDoCBOZXVyYWwgbmV0d29yayBjaOG7iSBob+G6oXQgxJHhu5luZyB04buRaSDGsHUga2hpIGThu68gbGnhu4d1IMSR4bqndSB2w6BvIMSRxrDhu6NjIGNodeG6qW4gaMOzLCB2w6AgbsOzIGNo4buJIHRp4bq/cCBuaOG6rW4gZOG7ryBsaeG7h3Ugc+G7kSAtIG7Dqm4gY8OhYyBwcmVkaWN0b3IgbmjGsCBTZXggcGjhuqNpIMSRxrDhu6NjIGNodXnhu4NuIHRow6BuaCBkdW1teSB2YXJpYWJsZSBoYXkgYmluYXJ5IHZhcmlhYmxlIChnacOhIHRy4buLIDAvMSkNCg0KVmnhu4djIGNodeG6qW4gaMOzYSBuw6B5IMSRxrDhu6NjIHRo4buxYyBoaeG7h24gdHLDqm4gY+G6oyAyIHThuq1wIHRyYWluc2V0IHbDoCB0ZXN0c2V0LCB0dXkgbmhpw6puIGNo4buJIGThu7FhIHbDoG8gbWVhbiB2w6Agc2QgY+G7p2EgdHJhaW5zZXQgxJHhu4MgxJHhuqNtIGLhuqNvIHTDrW5oIGLDrSBt4bqtdCBj4bunYSB0ZXN0c2V0LiBQYWNrYWdlIHJlY2lwZXMgY2hvIHBow6lwIHRhIHThuqFvIDEgaMOgbSBjaHXhuqluIGjDs2EgbmjGsCB24bqteQ0KDQpgYGB7cixtZXNzYWdlID0gRkFMU0Usd2FybmluZz1GQUxTRX0NCmxpYnJhcnkocmVjaXBlcykNCg0KIyBSZWNpcGUgZm9yIHN0YW5kYXJkaXNhdGlvbg0KDQpkbGNvcmVjPC1yZWNpcGUodHJhaW5zZXQsQVYrRExDT35TZXgrQWdlK0hlaWdodCklPiUNCiAgYWRkX3JvbGUoU2V4LG5ld19yb2xlID0gInN0cmF0aWZ5IiklPiUNCiAgc3RlcF9jZW50ZXIoYWxsX3ByZWRpY3RvcnMoKSklPiUNCiAgc3RlcF9zY2FsZShhbGxfcHJlZGljdG9ycygpKQ0KDQojIFN0YW5kYXJkaXNlZCB0cmFpbnNldCt0ZXN0c2V0DQoNCnN0ZF9mdW5jPC1wcmVwKGRsY29yZWMsdHJhaW5pbmc9dHJhaW5zZXQscmV0YWluPUYpDQpzdGRfdGVzdDwtYmFrZShzdGRfZnVuYywgbmV3ZGF0YT10ZXN0c2V0KQ0Kc3RkX3RyYWluPC1iYWtlKHN0ZF9mdW5jLCBuZXdkYXRhPXRyYWluc2V0KQ0KDQojIFByZWRpY3RvcnMgdG8gdGVuc29ycw0KDQp0cmFpbl9kYXRhPC1zdGRfdHJhaW4lPiUNCiAgZHBseXI6OnNlbGVjdCgtRExDTywtQVYpJT4lDQogIG11dGF0ZShTZXg9YXMubnVtZXJpYyh0cmFpbnNldCRTZXgpLTEpJT4lDQogIGFzLm1hdHJpeCgpJT4lDQogIGFzLmFycmF5LmRlZmF1bHQoZGltbmFtZXM9TlVMTCkgIA0KDQp0ZXN0X2RhdGE8LXN0ZF90ZXN0JT4lDQogIGRwbHlyOjpzZWxlY3QoLURMQ08sLUFWKSU+JQ0KICBtdXRhdGUoU2V4PWFzLm51bWVyaWModGVzdHNldCRTZXgpLTEpJT4lDQogIGFzLm1hdHJpeCgpJT4lDQogIGFzLmFycmF5LmRlZmF1bHQoZGltbmFtZXM9TlVMTCkgIA0KDQojIE91dGNvbWVzIHRvIHRlbnNvcnMNCnRyYWluX3RhcmdldHM8LXRyYWluc2V0JT4lDQogIGRwbHlyOjpzZWxlY3QoQVYsRExDTyklPiUNCiAgYXMubWF0cml4KCklPiUNCiAgYXMuYXJyYXkuZGVmYXVsdChkaW1uYW1lcz1OVUxMKSAgDQoNCnRlc3RfdGFyZ2V0czwtdGVzdHNldCU+JQ0KICBkcGx5cjo6c2VsZWN0KEFWLERMQ08pJT4lDQogIGFzLm1hdHJpeCgpJT4lDQogIGFzLmFycmF5LmRlZmF1bHQoZGltbmFtZXM9TlVMTCkNCg0KYGBgDQoNClRhIHRoaeG6v3Qga+G6vyBt4bqhbmcgbmV1cm9uIHRyb25nIGtlcmFzOiBt4bqhbmcgbsOgeSBjw7MgMSBs4bubcCDEkeG6p3UgdsOgbyAsIDQgbOG7m3Agc8OidSBkw7luZyBow6BtIHJlY3RpZmllZCBsaW5lYXIgdW5pdCAocmVsdSksIDEgbOG7m3AgaGnhu4d1IGNo4buJbmggduG7m2kgTDIgcmVnLCBjdeG7kWkgY8O5bmcgbMOgIGzhu5twIGvhur90IHF14bqjIGfhu5NtIDIgbmV1cm9uLiBIw6BtIG3huqV0IG3DoXQgbMOgIE1BRSwgdGnDqnUgY2jDrSBodeG6pW4gbHV54buHbiBsw6AgTVNFIA0KDQpgYGB7cixtZXNzYWdlID0gRkFMU0Usd2FybmluZz1GQUxTRX0NCiMgRGVzaWduIGEgZGVlcCBuZXVyYWwgbmV0DQoNCiMgQU5ODQoNCmxpYnJhcnkoa2VyYXMpDQoNCmJ1aWxkX21vZGVsIDwtIGZ1bmN0aW9uKCkgew0KICBtb2RlbCA8LSBrZXJhc19tb2RlbF9zZXF1ZW50aWFsKCkgJT4lIA0KICAgIGxheWVyX2RlbnNlKHVuaXRzID0gMTI4LCBhY3RpdmF0aW9uID0gInJlbHUiLCANCiAgICAgICAgICAgICAgICBpbnB1dF9zaGFwZSA9IGRpbSh0cmFpbl9kYXRhKVtbMl1dKSAlPiUgDQogICAgbGF5ZXJfZGVuc2UodW5pdHMgPSAxMjgsIGFjdGl2YXRpb24gPSAicmVsdSIpICU+JQ0KICAgIGxheWVyX2RlbnNlKHVuaXRzID0gMTI4LCBhY3RpdmF0aW9uID0gInJlbHUiKSAlPiUNCiAgICBsYXllcl9kZW5zZSh1bml0cyA9MTI4LCBrZXJuZWxfcmVndWxhcml6ZXIgPSByZWd1bGFyaXplcl9sMigwLjAwMSkpJT4lICAgDQogICAgbGF5ZXJfZGVuc2UodW5pdHMgPSAyKQ0KICAgIA0KICBtb2RlbCAlPiUgY29tcGlsZSgNCiAgICBvcHRpbWl6ZXIgPSAicm1zcHJvcCIsDQogICAgbG9zcyA9ICJtYWUiLA0KICAgIG1ldHJpYz0gIm1zbGUiDQogICkNCn0NCg0KIyBUcmFpbiBBTk4gb24gd2hvbGUgZGF0YS4NCg0KbW9kZWwgPC0gYnVpbGRfbW9kZWwoKQ0KDQptb2RlbCAlPiUgZml0KHRyYWluX2RhdGEsIHRyYWluX3RhcmdldHMsDQogICAgICAgICAgICAgIGVwb2NocyA9IDIwMCwgYmF0Y2hfc2l6ZSA9IDMwLHZlcmJvc2U9MCwNCiAgICAgICAgICAgICAgdmFsaWRhdGlvbl9zcGxpdCA9IDAuMSkgLT4gZG5ubW9kDQoNCnN1bW1hcnkobW9kZWwpDQoNCnBsb3QoZG5ubW9kKSt0aGVtZV9idygpDQpgYGANCg0KU2F1IGtoaSBodeG6pW4gbHV54buHbiAyMDAgbMaw4bujdCB24bubaSB04buJIGzhu4cga2nhu4NtIMSR4buLbmggMzAlLCBz4butIGThu6VuZyBuZ+G6q3Ugbmhpw6puIDMwIHRyxrDhu51uZyBo4bujcCBt4buXaSBsxrDhu6N0LCB0YSBjw7Mga+G6v3QgcXXhuqMgc2F1IGPDuW5nIHRyb25nIG9iamVjdCBkbm5tb2QuDQoNClBo4bqnbiBs4bubbiB0aOG7nWkgZ2lhbiDEkcaw4bujYyBz4butIGThu6VuZyBsw6AgxJHhu4MgdGhp4bq/dCBr4bq/IGPhuqV1IHRyw7pjIG3huqFuZyBuZXVyb24gbmjGsG5nIGPDtG5nIHZp4buHYyBuw6B5IHTGsMahbmcgxJHhu5FpIG5o4bq5IG5ow6BuZyBzbyB24bubaSBxdXkgdHLDrG5oIHN0ZXAgd2lzZSB0aOG7pyBjw7RuZyBtw6AgcGjDsm5nIHRo4buRbmcga8OqIMSRYW5nIGzDoG0uDQoNCiMgU28gc8OhbmggaGnhu4d1IHF14bqjIDIgcGjGsMahbmcgcGjDoXANCg0KxJDDonkgbMOgIHRo4budaSDEkWnhu4NtIHF1eeG6v3QgxJHhu4tuaCwgaGFpIG3DtCBow6xuaCBMTVMgKHBow6FpIHRo4buRbmcga8OqKSB2w6AgRGVlcCBuZXVyYWwgbmV0d29yayAocGjDoWkgTWFjaGluZSBsZWFybmluZykgxJHDoyBob8OgbiB04bqldC4gVGEgc+G6vSBraeG7g20gdHJhIMSR4buZIGNow61uaCB4w6FjIGPhu6dhIGNow7puZyB0csOqbiB04bqtcCB0ZXN0c2V0Lg0KDQpCw6puIGPhuqFuaCDEkcOzLCBt4buZdCBtw7QgaMOsbmggdHV54bq/biB0w61uaCDEkcahbiBnaeG6o24gY2hvIDIgY2jhu4kgc+G7kSBETENPIHbDoCBBViDEkcaw4bujYyBFUlMgxJHhu4EgeHXhuqV0IG7Eg20gMjAxNyBz4bq9IMSRxrDhu6NjIGTDuW5nIGzDoG0gbmjDs20gY2jhu6luZyxjaMO6bmcgdGEgaHkgduG7jW5nIHLhurFuZyBjw7Mgc+G7sSBraMOhYyBiaeG7h3QgZ2nhu69hIDIgbcO0IGjDrG5oIG3hu5tpIHbDoCBtw7QgaMOsbmggdHV54bq/biB0w61uaCwgY8WpbmcgbmjGsCBjw7Mgc+G7sSB0xrDGoW5nIHBo4bqjbiByw7UgbsOpdCB24buBIGhp4buHdSBuxINuZyBnaeG7r2EgMiBwaMawxqFuZyBwaMOhcCBEZWVwIGxlYXJuaW5nIHbDoCBMTVMgdGhlbyBHQU1MU1MuDQoNCmBgYHtyLG1lc3NhZ2UgPSBGQUxTRSx3YXJuaW5nPUZBTFNFfQ0KIyBWYWxpZGF0aW5nIERMQ08gcHJlZGljdGlvbiBvbiB0ZXN0c2V0DQoNCnlfcHJlZD1wcmVkaWN0KG1vZGVsLHRlc3RfZGF0YSkNCg0KbGlicmFyeShtbHIpDQoNCiMgTWV0cmljcyB0byBiZSBlc3RpbWF0ZWQNCm1ldHM9bGlzdChtc2UsbWFlLG1lZGFlLHJtc2UpDQoNCmRsY28udGFzaz0gbWxyOjptYWtlUmVnclRhc2soaWQgPSAiRExDTyIsIGRhdGE9dGVzdHNldCwgdGFyZ2V0ID0gIkRMQ08iKQ0KZGxjby5scm4gPSBtYWtlTGVhcm5lcigicmVnci5nbG0iKQ0KZHVtbXlkbGNvPW1scjo6dHJhaW4oZGxjby5scm4sZGxjby50YXNrKQ0KZHVtcHJlZEROTmRsY289cHJlZGljdChkdW1teWRsY28sZGxjby50YXNrKQ0KDQojIEltcGxlbWVudCB5X3ByZWQgdG8gZHVtbXkNCmR1bXByZWRETk5kbGNvJGRhdGEkcmVzcG9uc2U8LXlfcHJlZFssMl0NCg0KDQojIFZhbGlkYXRpbmcgVkEgcHJlZGljdGlvbiBvbiB0ZXN0c2V0DQoNCnZhLnRhc2s9IG1scjo6bWFrZVJlZ3JUYXNrKGlkID0gIlZBIiwgZGF0YT10ZXN0c2V0LCB0YXJnZXQgPSAiQVYiKQ0KdmEubHJuID0gbWFrZUxlYXJuZXIoInJlZ3IuZ2xtIikNCmR1bW15dmE9bWxyOjp0cmFpbih2YS5scm4sdmEudGFzaykNCmR1bXByZWRETk52YT1wcmVkaWN0KGR1bW15dmEsdmEudGFzaykNCg0KIyBJbXBsZW1lbnQgeV9wcmVkIHRvIGR1bW15DQpkdW1wcmVkRE5OdmEkZGF0YSRyZXNwb25zZTwteV9wcmVkWywxXQ0KDQojIFZhbGlkYXRpbmcgRExDTyBieSBMTVMNCg0KcHJlZF9ETF9NPXByZWRpY3QobW9kRExfTSxuZXdkYXRhPXRlc3RMTVNfTVssLTFdLHR5cGU9InJlc3BvbnNlIikNCnByZWRfRExfRj1wcmVkaWN0KG1vZERMX0YsbmV3ZGF0YT10ZXN0TE1TX0ZbLC0xXSx0eXBlPSJyZXNwb25zZSIpDQpwcmVkX0FWX009cHJlZGljdChtb2RBVl9NLG5ld2RhdGE9dGVzdExNU19NWywtMV0sdHlwZT0icmVzcG9uc2UiKQ0KcHJlZF9BVl9GPXByZWRpY3QobW9kQVZfRixuZXdkYXRhPXRlc3RMTVNfRlssLTFdLHR5cGU9InJlc3BvbnNlIikNCg0KdGVzdF9ETF9MTVM9cmJpbmQodGVzdExNU19NLHRlc3RMTVNfRiklPiUNCiAgbXV0YXRlKHByZWRETD1jKHByZWRfRExfTSxwcmVkX0RMX0YpLA0KICAgICAgICAgcHJlZEFWPWMocHJlZF9BVl9NLHByZWRfQVZfRikpDQoNCiMgRHVtbXkgTE1TDQpkdW1wcmVkTE1TX0RMPXByZWRpY3QoZHVtbXlkbGNvLGRsY28udGFzaykNCmR1bXByZWRMTVNfQVY9cHJlZGljdChkdW1teXZhLHZhLnRhc2spDQoNCiMgSW1wbGVtZW50DQoNCmR1bXByZWRMTVNfREwkZGF0YSRyZXNwb25zZTwtdGVzdF9ETF9MTVMkcHJlZERMDQpkdW1wcmVkTE1TX0FWJGRhdGEkcmVzcG9uc2U8LXRlc3RfRExfTE1TJHByZWRBVg0KDQogICAgICAgICAgICANCiMgRVJTIDIwMTcgbGluZWFyIG1vZGVsOg0KDQojIERMQ08NCiMgTWFsZTogKDAuMypIRUlHSFQpLSgwLjAwMiooQUdFXjIpKSsoNS40NyoxKS0xNy43NSAgICBSU0Q9NC40Mzc3DQojIEZlbWFsZTogKDAuMypIRUlHSFQpLSgwLjAwMiooQUdFXjIpKSsoNS40NyowKS0xNy43NSAgUlNEPTQuNzM3Nw0KDQojQVYNCiMgbWFsZTogMC4wODIqSEVJR0hUKzAuNzIqMS04LjIgICBSU0Q9MC43NjYNCiMgRmVtYWxlOiAwLjA4MipIRUlHSFQrMC43MiowLTguMiBSU0Q9MC43NjYNCg0KcHJlZEVSU21vZD1mdW5jdGlvbihuZXdkYXRhKXsNCiAgU2V4PWFzLm51bWVyaWMobmV3ZGF0YSRTZXgpLTENCiAgQWdlPW5ld2RhdGEkQWdlDQogIEhlaWdodD1uZXdkYXRhJEhlaWdodA0KICByc2RETD00LjQzNzcNCiAgcnNkQVY9MC43NjYNCiAgcHJlZEVSUz1saXN0KE11X0RMPSgwLjMqSGVpZ2h0KS0oMC4wMDIqKEFnZV4yKSkrKDUuNDcqU2V4KS0xNy43NSwNCiAgICAgICAgICAgICAgIExMTl9ETD0oMC4zKkhlaWdodCktKDAuMDAyKihBZ2VeMikpKyg1LjQ3KlNleCktMTcuNzUtMS42NDUqcnNkREwsDQogICAgICAgICAgICAgICBVTE5fREw9KDAuMypIZWlnaHQpLSgwLjAwMiooQWdlXjIpKSsoNS40NypTZXgpLTE3Ljc1KzEuNjQ1KnJzZERMLA0KICAgICAgICAgICAgICAgTXVfQVY9MC4wODIqSGVpZ2h0KzAuNzIqU2V4LTguMiwNCiAgICAgICAgICAgICAgICBMTE5fQVY9MC4wODIqSGVpZ2h0KzAuNzIqU2V4LTguMi0xLjY0NSpyc2RBViwNCiAgICAgICAgICAgICAgICBVTE5fQVY9MC4wODIqSGVpZ2h0KzAuNzIqU2V4LTguMisxLjY0NSpyc2RBVikNCnJldHVybihwcmVkRVJTKQ0KfQ0KDQpwcmVkRVJTPXByZWRFUlNtb2QodGVzdHNldCkNCg0KIyBEdW1teSBFUlMNCmR1bXByZWRFUlNfREw9cHJlZGljdChkdW1teWRsY28sZGxjby50YXNrKQ0KZHVtcHJlZEVSU19BVj1wcmVkaWN0KGR1bW15dmEsdmEudGFzaykNCg0KIyBJbXBsZW1lbnQNCg0KZHVtcHJlZEVSU19ETCRkYXRhJHJlc3BvbnNlPC1wcmVkRVJTJE11X0RMDQpkdW1wcmVkRVJTX0FWJGRhdGEkcmVzcG9uc2U8LXByZWRFUlMkTXVfQVYNCg0KIyBDb21wYXJlIERMQ08NCnBlMT1wZXJmb3JtYW5jZShkdW1wcmVkTE1TX0RMLG1lYXN1cmVzID1tZXRzKQ0KcGUyPXBlcmZvcm1hbmNlKGR1bXByZWRETk5kbGNvLG1lYXN1cmVzID1tZXRzKQ0KcGUzPXBlcmZvcm1hbmNlKGR1bXByZWRFUlNfREwsbWVhc3VyZXMgPW1ldHMpDQoNCiMgQ29tcGFyZSBBVg0KcGU0PXBlcmZvcm1hbmNlKGR1bXByZWRMTVNfQVYsbWVhc3VyZXMgPW1ldHMpDQpwZTU9cGVyZm9ybWFuY2UoZHVtcHJlZEROTnZhLG1lYXN1cmVzID1tZXRzKQ0KcGU2PXBlcmZvcm1hbmNlKGR1bXByZWRFUlNfQVYsbWVhc3VyZXMgPW1ldHMpDQoNCnZlcmRpY3Q9ZGF0YV9mcmFtZShPdXRjb21lPWMoIkRMQ08iLCJETENPIiwiRExDTyIsIkFWIiwiQVYiLCJBViIpLA0KICAgICAgICAgICAgICAgICAgIE1ldGhvZD1yZXAoYygiU3RlcF93aXNlX0xNUyIsIkRlZXBfTk4iLCJFUlNfbGluZWFyIiksMiksDQogICAgICAgICAgICAgICAgICAgTVNFPWMocGUxW1sxXV0scGUyW1sxXV0scGUzW1sxXV0scGU0W1sxXV0scGU1W1sxXV0scGU2W1sxXV0pLA0KICAgICAgICAgICAgICAgICAgIE1BRT1jKHBlMVtbMl1dLHBlMltbMl1dLHBlM1tbMl1dLHBlNFtbMl1dLHBlNVtbMl1dLHBlNltbMl1dKSwNCiAgICAgICAgICAgICAgICAgICBNRURBRT1jKHBlMVtbM11dLHBlMltbM11dLHBlM1tbM11dLHBlNFtbM11dLHBlNVtbM11dLHBlNltbM11dKSwNCiAgICAgICAgICAgICAgICAgICBSTVNFPWMocGUxW1s0XV0scGUyW1s0XV0scGUzW1s0XV0scGU0W1s0XV0scGU1W1s0XV0scGU2W1s0XV0pDQogICAgICAgICAgICAgICAgICAgKQ0KDQp2ZXJkaWN0JT4la25pdHI6OmthYmxlKCkNCiAgICAgICAgICAgICAgICAgICANCmBgYA0KDQpL4bq/dCBxdeG6oyBraeG7g20gxJHhu4tuaCBjaG8gdGjhuqV5IG3hu5l0IMSRaeG7gXUgYuG6pXQgbmfhu50sIMSRw7MgbMOgIG3hu5l0IG3DtCBow6xuaCBwaOG7qWMgdOG6oXAgbmjGsCBMTVMgbOG6oWkgY8OzIG3hu6ljIMSR4buZIGNow61uaCB4w6FjIGvDqW0gaMahbiBzbyB24bubaSBt4buZdCBtw7QgaMOsbmggdHV54bq/biB0w61uaCDEkcahbiBnaeG6o24gKEVSUykuIE3DtCBow6xuaCBEZWVwIGxlYXJuaW5nIGPDsyBoaeG7h3UgcXXhuqMgdMawxqFuZyDEkcawxqFuZywgdGjhuq1tIGNow60gY8OzIHRo4buDIHThu5F0IGjGoW4gc28gduG7m2kgbcO0IGjDrG5oIGPhu6dhIEVSUy4gS+G6v3QgcXXhuqMgxJHGsOG7o2MgdHLDrG5oIGLDoHkgYuG6sW5nIGJp4buDdSDEkeG7kzogDQoNCmBgYHtyLG1lc3NhZ2UgPSBGQUxTRSx3YXJuaW5nPUZBTFNFfQ0KdmVyZGljdCU+JWdhdGhlcihNU0U6Uk1TRSxrZXk9Ik1ldHJpYyIsdmFsdWU9IlNjb3JlIiklPiUNCiAgZ2dwbG90KGFlcyh4PU1ldGhvZCx5PVNjb3JlLGZpbGw9TWV0aG9kKSkrDQogIGdlb21fYmFyKHN0YXQ9ImlkZW50aXR5Iixwb3NpdGlvbj0iZG9kZ2UiLGFscGhhPTAuNixjb2w9ImJsYWNrIikrDQogIHRoZW1lX2J3KCkrY29vcmRfZmxpcCgpKw0KICBmYWNldF9ncmlkKE1ldHJpY35PdXRjb21lLHNjYWxlcz0iZnJlZSIpDQpgYGANCsSQ4buZIGNow61uaCB4w6FjIGPhu6dhIG3DtCBow6xuaCBEZWVwIGxlYXJuaW5nIHbDoCBFUlMgY8OzIHRo4buDIMSRxrDhu6NjIHF1YW4gc8OhdCB0csOqbiBz4buxIGNo4buTbmcgbOG6r3AgYmnhu4N1IMSR4buTIHR1eeG6v24ga8OtIGdp4buvYSBk4buvIGxp4buHdSDGsOG7m2MgdMOtbmggYuG7n2kgbcO0IGjDrG5oIHbDoCBk4buvIGxp4buHdSB0aOG7sWMgdOG6vyB0cm9uIHThuq1wIGtp4buDbSDEkeG7i25oIG5oxrAgc2F1Og0KDQpgYGB7cixtZXNzYWdlID0gRkFMU0Usd2FybmluZz1GQUxTRX0NCiMgTExOIGFuZCBVTE4NCg0KIyByc2QgZm9yIERMQ08NCg0KKHRyYWluc2V0JERMQ08tYXMudmVjdG9yKHlfcHJlZFssMl0pKSU+JQ0KICBkZW5zaXR5KCklPiUNCiAgcGxvdChjb2w9InJlZCIpDQoNCnJzZF9ETD1zZCh0cmFpbnNldCRETENPLWFzLnZlY3Rvcih5X3ByZWRbLDJdKSkNCg0KIyByc2QgZm9yIEFWDQoNCih0cmFpbnNldCRBVi1hcy52ZWN0b3IoeV9wcmVkWywxXSkpJT4lDQogIGRlbnNpdHkoKSU+JQ0KICBwbG90KGNvbD0iYmx1ZSIpDQoNCnJzZF9BVj1zZCh0cmFpbnNldCRBVi1hcy52ZWN0b3IoeV9wcmVkWywxXSkpDQoNCg0KIyBMTE4gYW5kIFVMTiBieSBMTVMNCg0KcHJlZF9ETF9NX2FsbD1wcmVkaWN0QWxsKG1vZERMX00sbmV3ZGF0YT10ZXN0TE1TX01bLC0xXSkNCnByZWRfRExfRl9hbGw9cHJlZGljdEFsbChtb2RETF9GLG5ld2RhdGE9dGVzdExNU19GWywtMV0pDQpwcmVkX0FWX01fYWxsPXByZWRpY3RBbGwobW9kQVZfTSxuZXdkYXRhPXRlc3RMTVNfTVssLTFdKQ0KcHJlZF9BVl9GX2FsbD1wcmVkaWN0QWxsKG1vZEFWX0YsbmV3ZGF0YT10ZXN0TE1TX0ZbLC0xXSkNCg0KIyBETENPDQpNdURMPWMocHJlZF9ETF9NX2FsbCRtdSAsIHByZWRfRExfRl9hbGwkbXUpDQpzaWdtYURMPWMocHJlZF9ETF9NX2FsbCRzaWdtYSAsIHByZWRfRExfRl9hbGwkc2lnbWEpDQpOdURMPWMocHJlZF9ETF9NX2FsbCRudSAsIHByZWRfRExfRl9hbGwkbnUpDQpMTE5fRExfTE1TPWV4cChsb2coTXVETCkrKGxvZygxLTEuNjQ1Kk51REwqc2lnbWFETCkpL051REwpDQpVTE5fRExfTE1TPWV4cChsb2coTXVETCkrKGxvZygxKzEuNjQ1Kk51REwqc2lnbWFETCkpL051REwpDQoNCiMgQVYNCk11QVY9YyhwcmVkX0FWX01fYWxsJG11ICwgcHJlZF9BVl9GX2FsbCRtdSkNCnNpZ21hQVY9YyhwcmVkX0FWX01fYWxsJHNpZ21hICwgcHJlZF9BVl9GX2FsbCRzaWdtYSkNCk51QVY9YyhwcmVkX0FWX01fYWxsJG51ICwgcHJlZF9BVl9GX2FsbCRudSkNCkxMTl9BVl9MTVM9ZXhwKGxvZyhNdUFWKSsobG9nKDEtMS42NDUqTnVBVipzaWdtYUFWKSkvTnVBVikNClVMTl9BVl9MTVM9ZXhwKGxvZyhNdUFWKSsobG9nKDErMS42NDUqTnVBVipzaWdtYUFWKSkvTnVBVikNCg0KdGVzdF9hbGw8LXRlc3RzZXQlPiVtdXRhdGUoTXVfQVZfQU5OPWFzLnZlY3Rvcih5X3ByZWRbLDFdKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgTExOX0FWX0FOTj1xbm9ybShwPTAuMiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtZWFuPWFzLnZlY3Rvcih5X3ByZWRbLDFdKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzZD1yc2RfQVYpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICBVTE5fQVZfQU5OPXFub3JtKHA9MC44LA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1lYW49YXMudmVjdG9yKHlfcHJlZFssMV0pLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNkPXJzZF9BViksDQogICAgICAgICAgICAgICAgICAgICAgICAgIE11X0RMX0FOTj1hcy52ZWN0b3IoeV9wcmVkWywyXSksDQogICAgICAgICAgICAgICAgICAgICAgICAgIExMTl9ETF9BTk49cW5vcm0ocD0wLjIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbWVhbj1hcy52ZWN0b3IoeV9wcmVkWywyXSksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2Q9cnNkX0RMKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgVUxOX0RMX0FOTj1xbm9ybShwPTAuOCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtZWFuPWFzLnZlY3Rvcih5X3ByZWRbLDJdKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzZD1yc2RfREwpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICBNdV9ETF9MTVM9TXVETCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgTExOX0RMX0xNUz1MTE5fRExfTE1TLA0KICAgICAgICAgICAgICAgICAgICAgICAgICBVTE5fRExfTE1TPVVMTl9ETF9MTVMsDQogICAgICAgICAgICAgICAgICAgICAgICAgIE11X0FWX0xNUz1NdUFWLA0KICAgICAgICAgICAgICAgICAgICAgICAgICBMTE5fQVZfTE1TPUxMTl9BVl9MTVMsDQogICAgICAgICAgICAgICAgICAgICAgICAgIFVMTl9BVl9MTVM9VUxOX0FWX0xNUywNCiAgICAgICAgICAgICAgICAgICAgICAgICAgTXVfRExfRVJTPXByZWRFUlMkTXVfREwsDQogICAgICAgICAgICAgICAgICAgICAgICAgIExMTl9ETF9FUlM9cHJlZEVSUyRMTE5fREwsDQogICAgICAgICAgICAgICAgICAgICAgICAgIFVMTl9ETF9FUlM9cHJlZEVSUyRVTE5fREwsDQogICAgICAgICAgICAgICAgICAgICAgICAgIE11X0FWX0VSUz1wcmVkRVJTJE11X0FWLA0KICAgICAgICAgICAgICAgICAgICAgICAgICBMTE5fQVZfRVJTPXByZWRFUlMkTExOX0FWLA0KICAgICAgICAgICAgICAgICAgICAgICAgICBVTE5fQVZfRVJTPXByZWRFUlMkVUxOX0FWDQogICAgICAgICAgICAgICAgICAgICAgICAgICkNCiAgICAgICAgICAgICAgICAgICAgICAgICAgDQpwMT10ZXN0X2FsbCU+JWdncGxvdChhZXMoeD1BZ2UpKSsNCiAgZ2VvbV9wb2ludChhZXMoeT1BViksYWxwaGE9MC4zLGNvbD0iYmxhY2siKSsNCiAgZ2VvbV9zbW9vdGgoYWVzKHk9TXVfQVZfQU5OKSxmaWxsPSJyZWQiLGNvbD0icmVkMyIsYWxwaGE9MC4yKSsNCiAgZ2VvbV9zbW9vdGgoYWVzKHk9QVYpLGZpbGw9ImdyZWVuIixjb2w9ImdyZWVuNCIsYWxwaGE9MC4yKSsNCiAgZ2VvbV9zbW9vdGgoYWVzKHk9TExOX0FWX0FOTiksZmlsbD0icmVkIixjb2w9InJlZDMiLHNlPUYsbGluZXR5cGU9MikrDQogIGdlb21fc21vb3RoKGFlcyh5PVVMTl9BVl9BTk4pLGZpbGw9InJlZCIsY29sPSJyZWQzIixzZT1GLGxpbmV0eXBlPTIpKw0KICB0aGVtZV9idygpK2dndGl0bGUoIkRlZXAgbmV1cmFsIG5ldHdvcmsiKStzY2FsZV95X2NvbnRpbnVvdXMobGltaXRzID0gYygyLDkpKQ0KDQpwMj10ZXN0X2FsbCU+JWdncGxvdChhZXMoeD1BZ2UpKSsNCiAgZ2VvbV9wb2ludChhZXMoeT1BViksYWxwaGE9MC4zLGNvbD0iYmxhY2siKSsNCiAgZ2VvbV9zbW9vdGgoYWVzKHk9TXVfQVZfTE1TKSxmaWxsPSJibHVlIixjb2w9ImJsdWUzIixhbHBoYT0wLjIpKw0KICBnZW9tX3Ntb290aChhZXMoeT1BViksZmlsbD0iZ3JlZW4iLGNvbD0iZ3JlZW4zIixhbHBoYT0wLjIpKw0KICBnZW9tX3Ntb290aChhZXMoeT1MTE5fQVZfTE1TKSxmaWxsPSJibHVlIixjb2w9ImJsdWUzIixzZT1GLGxpbmV0eXBlPTIpKw0KICBnZW9tX3Ntb290aChhZXMoeT1VTE5fQVZfTE1TKSxmaWxsPSJibHVlIixjb2w9ImJsdWUzIixzZT1GLGxpbmV0eXBlPTIpKw0KICB0aGVtZV9idygpK2dndGl0bGUoIkxNUyIpK3NjYWxlX3lfY29udGludW91cyhsaW1pdHMgPSBjKDIsOSkpDQoNCnAzPXRlc3RfYWxsJT4lZ2dwbG90KGFlcyh4PUFnZSkpKw0KICBnZW9tX3BvaW50KGFlcyh5PUFWKSxhbHBoYT0wLjMsY29sPSJibGFjayIpKw0KICBnZW9tX3Ntb290aChhZXMoeT1NdV9BVl9FUlMpLGZpbGw9InB1cnBsZSIsY29sPSJibHVlMyIsYWxwaGE9MC4yKSsNCiAgZ2VvbV9zbW9vdGgoYWVzKHk9QVYpLGZpbGw9ImdyZWVuIixjb2w9ImdyZWVuMyIsYWxwaGE9MC4yKSsNCiAgZ2VvbV9zbW9vdGgoYWVzKHk9TExOX0FWX0VSUyksZmlsbD0icHVycGxlIixjb2w9InB1cnBsZSIsc2U9RixsaW5ldHlwZT0yKSsNCiAgZ2VvbV9zbW9vdGgoYWVzKHk9VUxOX0FWX0VSUyksZmlsbD0icHVycGxlIixjb2w9InB1cnBsZSIsc2U9RixsaW5ldHlwZT0yKSsNCiAgdGhlbWVfYncoKStnZ3RpdGxlKCJFUlMiKStzY2FsZV95X2NvbnRpbnVvdXMobGltaXRzID0gYygyLDkpKQ0KDQpwND10ZXN0X2FsbCU+JWdncGxvdChhZXMoeD1BZ2UpKSsNCiAgZ2VvbV9wb2ludChhZXMoeT1ETENPKSxhbHBoYT0wLjMsY29sPSJibGFjayIpKw0KICBnZW9tX3Ntb290aChhZXMoeT1ETENPKSxmaWxsPSJncmVlbiIsY29sPSJncmVlbjQiLGFscGhhPTAuMikrDQogIGdlb21fc21vb3RoKGFlcyh5PU11X0RMX0FOTiksZmlsbD0icmVkIixjb2w9InJlZDMiLGFscGhhPTAuMikrDQogIGdlb21fc21vb3RoKGFlcyh5PUxMTl9ETF9BTk4pLGZpbGw9InJlZCIsY29sPSJyZWQzIixzZT1GLGxpbmV0eXBlPTIpKw0KICBnZW9tX3Ntb290aChhZXMoeT1VTE5fRExfQU5OKSxmaWxsPSJyZWQiLGNvbD0icmVkMyIsc2U9RixsaW5ldHlwZT0yKSsNCiAgdGhlbWVfYncoKStzY2FsZV95X2NvbnRpbnVvdXMobGltaXRzID0gYyg1LDUwKSkrZ2d0aXRsZSgiRGVlcCBuZXVyYWwgbmV0d29yayIpDQoNCnA1PXRlc3RfYWxsJT4lZ2dwbG90KGFlcyh4PUFnZSkpKw0KICBnZW9tX3BvaW50KGFlcyh5PURMQ08pLGFscGhhPTAuMyxjb2w9ImJsYWNrIikrDQogIGdlb21fc21vb3RoKGFlcyh5PURMQ08pLGZpbGw9ImdyZWVuIixjb2w9ImdyZWVuNCIsYWxwaGE9MC4yKSsNCiAgZ2VvbV9zbW9vdGgoYWVzKHk9TXVfRExfTE1TKSxmaWxsPSJibHVlIixjb2w9ImJsdWUzIixhbHBoYT0wLjIpKw0KICBnZW9tX3Ntb290aChhZXMoeT1MTE5fRExfTE1TKSxmaWxsPSJibHVlIixjb2w9ImJsdWUzIixzZT1GLGxpbmV0eXBlPTIpKw0KICBnZW9tX3Ntb290aChhZXMoeT1VTE5fRExfTE1TKSxmaWxsPSJibHVlIixjb2w9ImJsdWUzIixzZT1GLGxpbmV0eXBlPTIpKw0KICB0aGVtZV9idygpK3NjYWxlX3lfY29udGludW91cyhsaW1pdHMgPSBjKDUsNTApKStnZ3RpdGxlKCJMTVMiKQ0KDQpwNj10ZXN0X2FsbCU+JWdncGxvdChhZXMoeD1BZ2UpKSsNCiAgZ2VvbV9wb2ludChhZXMoeT1ETENPKSxhbHBoYT0wLjMsY29sPSJibGFjayIpKw0KICBnZW9tX3Ntb290aChhZXMoeT1ETENPKSxmaWxsPSJncmVlbiIsY29sPSJncmVlbjQiLGFscGhhPTAuMikrDQogIGdlb21fc21vb3RoKGFlcyh5PU11X0RMX0VSUyksZmlsbD0idmlvbGV0Iixjb2w9InB1cnBsZSIsYWxwaGE9MC4yKSsNCiAgZ2VvbV9zbW9vdGgoYWVzKHk9TExOX0RMX0VSUyksZmlsbD0idmlvbGV0Iixjb2w9InB1cnBsZSIsc2U9RixsaW5ldHlwZT0yKSsNCiAgZ2VvbV9zbW9vdGgoYWVzKHk9VUxOX0RMX0VSUyksZmlsbD0idmlvbGV0Iixjb2w9InB1cnBsZSIsc2U9RixsaW5ldHlwZT0yKSsNCiAgdGhlbWVfYncoKStzY2FsZV95X2NvbnRpbnVvdXMobGltaXRzID0gYyg1LDUwKSkrZ2d0aXRsZSgiRVJTIikNCg0KZ3JpZEV4dHJhOjpncmlkLmFycmFuZ2UocDEscDIscDMsbmNvbD0yKQ0KDQpncmlkRXh0cmE6OmdyaWQuYXJyYW5nZShwNCxwNSxwNixuY29sPTIpDQoNCmBgYA0KDQojIELDoG4gbHXhuq1uDQoNClPhu7EgcGjhu5UgYmnhur9uIHbDoCBwaMOhdCB0cmnhu4NuIGPhu6dhIGPDoWMgcGjGsMahbmcgcGjDoXAgTWFjaGluZSBsZWFybmluZyBtw6AgZ2nhuqNpIHRodeG6rXQgaGnhu4duIMSR4bqhaSBuaOG6pXQgbMOgIERlZXAgbGVhcm5pbmcgaGF5IG3huqFuZyB0aOG6p24ga2luaCBuaMOibiB04bqhbyDEkcOjIGN1bmcgY+G6pXAgbeG7mXQgZ2nhuqNpIHBow6FwIGhvw6BuIHRvw6BuIG3hu5tpIGNobyBiw6BpIHRvw6FuIHjDonkgZOG7sW5nIGdpw6EgdHLhu4sgdGhhbSBjaGnhur91IGNobyB4w6l0IG5naGnhu4dtIHkga2hvYS4gVHJvbmcgdGjDrSBk4bulIG5n4bqvbiBuw6B5LCBOaGkgxJHDoyBjaOG7qW5nIHThu48gcuG6sW5nIERlZXAgbGVhcm5pbmcgduG7m2kgY+G6pXUgdHLDumMgbeG6oW5nIG5ldXJvIHBow7kgaOG7o3Agc+G6vSBt4bqhbmggaMahbiBuaGnhu4F1IHNvIHbhu5tpIG5o4buvbmcgbcO0IGjDrG5oIHR1eeG6v24gdMOtbmggY+G7lSDEkWnhu4NuLCB0aOG6rW0gY2jDrSBuaOG7r25nIG3DtCBow6xuaCBwaOG7qWMgdOG6oXAgbmjhuqV0IG5oxrAgTE1TIHbhu5tpIFNwbGluZXMuDQoNClRhIGPDuW5nIG5ow6xuIGzhuqFpIDIgZ2nhuqNpIHBow6FwIMSR4buDIHNvIHPDoW5oIGdp4buvYSBjaMO6bmc6DQoNCjEpIE5o4buvbmcgxrB1IHRo4bq/IGPhu6dhIERlZXAgbGVhcm5pbmcgc28gduG7m2kgbcO0IGjDrG5oIExNUzoNCg0KKyBEZWVwIG5ldXJhbCBuZXR3b3JrIGNobyBwaMOpcCBnaeG6o2kgcXV54bq/dCBjw7luZyBsw7pjIG5oaeG7gXUgb3V0Y29tZXMgdHJvbmcgbeG7mXQgbcO0IGjDrG5oIGR1eSBuaOG6pXQsIExNUyBraMO0bmcgdGjhu4MgbMOgbSDEkcaw4bujYyDEkWnhu4F1IG7DoHkuIA0KDQorIEPhuqV1IHRyw7pjIGPhu6dhIG3huqFuZyBuZXVyb24gY2jhu4kgY+G6p24gdGhp4bq/dCBr4bq/IDEgbOG6p24sIG5oxrBuZyBjw7MgdGjhu4MgdMOhaSBz4butIGThu6VuZyBuaGnhu4F1IGzhuqduICwgdsOgIHZp4buHYyB0aGnhur90IGvhur8gY8OzIHTDrW5oIHRo4bupIGLhuq1jLiBUcsOhaSBs4bqhaSwgcGjGsMahbmcgcGjDoXAgc3RlcC13aXNlIGx1w7RuIHBow6EgYuG7jyBj4bqldSB0csO6YyBtw7QgaMOsbmggY8WpIHbDoCB0aGF5IHRo4bq/IGLhurFuZyBj4bqldSB0csO6YyBt4bubaSBraGkgdGhheSDEkeG7lWkgZOG7ryBsaeG7h3UgxJHhuqd1IHbDoG8uIFZp4buHYyB0aMSDbSBkw7IsIHRo4butLCBzYWkgdsOgIHRo4butIGzhuqFpIG5o4buvbmcgdOG7lSBo4bujcCBj4bunYSBi4bqtYyDEkWEgdGjhu6ljIHRyb25nIG3DtCBow6xuaCBMTVMgYuG6sW5nIFN0ZXB3aXNlIGPFqW5nIGtow7Mga2jEg24gaMahbiBuaGnhu4F1IHNvIHbhu5tpIHZp4buHYyBk4buxbmcgbeG6oW5nIG5ldXJvbi4NCg0KMikgTmjhu69uZyBuaMaw4bujYyDEkWnhu4NtIGPhu6dhIERlZXAgbGVhcm5pbmcgc28gduG7m2kgTE1TOg0KDQorIE3DtCBow6xuaCBEZWVwIG5ldXJhbCBuZXR3b3JrIGzDoCBt4buZdCBo4buZcCDEkWVuIChibGFja2JveCksIHRhIGtow7RuZyB0aOG7gyBuaMOsbiB0aOG6pXkgY8ahIGNo4bq/IGLDqm4gdHJvbmcgY+G7p2EgbsOzLiBUdXkgbmhpw6puLCDEkWnhu4F1IG7DoHkga2jDtG5nIHRo4buxYyBz4buxIHF1YW4gdHLhu41uZy4gVHLDqm4gdGjhu7FjIHThur8gY8OhYyBtw7QgaMOsbmggTE1TIGPFqW5nIGzDoCAxIGThuqFuZyBibGFja2JveCwgdsOsIGtow7RuZyBjw7MgYsOhYyBzxKkgbsOgbyBjw7Mga2jhuqMgbsSDbmcgdMOtbmggdG/DoW4gdGjhu6cgY8O0bmcgdOG7qyBtw7QgaMOsbmgsIG5o4bqldCBsw6AgaMOgbSBzcGxpbmVzIHPhur0gdOG6oW8gcmEgbmjhu69uZyBtYXRyaWNlcyBwaOG7qWMgdOG6oXAgdsOgIGhvw6BuIHRvw6BuIOG6qW4uIE3hu5l0IGtoaSDEkcOjIMSRxrBhIHbDoG8gc29mdHdhcmUsIG5nxrDhu51pIHRhIGtow7RuZyBxdWFuIHTDom0gxJHhur9uIG7hu5lpIGR1bmcgY+G7p2EgbW9kZWwgbuG7r2EuDQoNCisgTcO0IGjDrG5oIERlZXAgbmV1cmFsIG5ldCBjaMawYSBjaG8gcGjDqXAgxrDhu5tjIHTDrW5oIHBow6JuIHbhu4sgbeG7mXQgY8OhY2ggY2jDrW5oIHjDoWMgbmjGsCBMTVMuIFRyb25nIHRow60gZOG7pSBuw6B5LCBOaGkgxrDhu5tjIHTDrW5oIExMTiB2w6AgVUxOIGThu7FhIHbDoG8gZ2nhuqMgxJHhu4tuaCBsw6AgcmVzaWR1YWwgZXJyb3IgY8OzIHBow6JuIGLhu5EgY2h14bqpbiwgbsOqbiBMTE4gxJHGsOG7o2MgdMOtbmggdOG7qyBow6BtIHF1YW50aWxlIHbhu5tpIE1lYW49IHByZWRpY3RlZCB2w6Agc2QgPSBzZCBj4bunYSByZXNpZHVhbCBlcnJvci4NCg0KTeG7mXQgY8OidSBo4buPaSB0aMO6IHbhu4sgbuG7r2E6IFThuqFpIHNhbyB0YSBraMO0bmcgZMO5bmcgRGVlcCBsZWFybmluZyDEkeG7gyDEkcawYSByYSBjaOG6qW4gxJFvw6FuIGx1w7RuIChwaMOibiBiaeG7h3QgY8OhYyBi4buHbmggbMO9IHbhu5tpIG5nxrDhu51pIGLDrG5oIHRoxrDhu51uZykgbcOgIGzhuqFpIHBo4bqjaSDEkWkgdsOybmcgcXVhIG3DtCBow6xuaCBo4buTaSBxdXkgPyBDw6J1IHRy4bqjIGzhu51pIMSRw7MgbMOgIDogKDEpIGLDoGkgdG/DoW4gbXVsdGljbGFzcywgbXVsdGlsYWJlbCBjbGFzc2lmaWNhdGlvbiBsdcO0biBwaOG7qWMgdOG6oXAgaMahbiBuaGnhu4F1IHNvIHbhu5tpIDEgYsOgaSB0b8OhbiBo4buTaSBxdXksIHbDoCAoMikgVGh1IHRo4bqtcCBk4buvIGxp4buHdSBj4bunYSBi4buHbmggbmjDom4gduG7m2kgY2jhuqluIMSRb8OhbiB4w6FjIMSR4buLbmggdGjDrCBraMOzIGtoxINuIGjGoW4gcuG6pXQgbmhp4buBdSBzbyB24bubaSBjaOG7iSB0aHUgdGjhuq1wIGThu68gbGnhu4d1IHRyw6puIG5nxrDhu51pIGto4buPZSBt4bqhbmgsICgzKSDEkOG7jWMga+G6v3QgcXXhuqMgeMOpdCBuZ2hp4buHbSBk4buxYSB2w6BvIGdpw6EgdHLhu4sgdGhhbSBjaGnhur91IHbhuqtuIGPDsm4gbMOgIHBoxrDGoW5nIHBow6FwIHRo4buxYyBow6BuaCBsw6JtIHPDoG5nIHF1eSDGsOG7m2MsIGPDoWMgYsOhYyBzxKkga2jDtG5nIHRo4bqleSBk4buFIGNo4buLdSB24bubaSDDvSB0xrDhu59uZyBjw6FpIG3DoXkgdGhheSB0aOG6vyB2YWkgdHLDsiBj4bunYSBo4buNIGhvw6BuIHRvw6BuLg0KDQpDw7MgdGjhu4MgdHJvbmcgbeG7mXQgdMawxqFuZyBsYWkgZ+G6p24sIG3DoXkgdMOtbmggc+G6vSB0aGF5IHRo4bq/IGNobyB2YWkgdHLDsiBj4bunYSBjaHV5w6puIHZpw6puIHRo4buRbmcga8OqLCB2w6wgdHLDrSB0deG7hyBuaMOibiB04bqhbyBtYW5nIGzhuqFpIMawdSB0aOG6vyBxdcOhIGzhu5tuIHNvIHbhu5tpIGPDoWMgbMOgbSB2aeG7h2MgdGjhu6cgY8O0bmcuIFRoYXkgdsOsIHBo4bqjaSBjaOG7nSAzLTUgbsSDbSDEkeG7gyBjw7MgbeG7mXQgbcO0IGjDrG5oLCBt4buZdCBjw6FpIG3DoXkgY8OzIHRo4buDIGzDoG0gdmnhu4djIG7DoHkgaMOgbmcgdHXhuqduLCB0aOG6rW0gY2jDrSBo4bqxbmcgbmfDoHksIHbhu5tpIMSR4buZIGNow61uaCB4w6FjIGNhbyBoxqFuIG5oaeG7gXUuDQoNCioqVMOgaSBsaeG7h3UgdGhhbSBraOG6o28qKiANCg0KMSkgUmlnYnkgUkEsIFN0YXNpbm9wb3Vsb3MgRE0uIEdlbmVyYWxpemVkIGFkZGl0aXZlIG1vZGVscyBmb3IgbG9jYXRpb24sIHNjYWxlIGFuZCBzaGFwZSAod2l0aCBkaXNjdXNzaW9uKS4gQXBwbCBTdGF0aXN0IDIwMDU7IDU0OiA1MDctNTU0Lg0KDQoyKSBGcmFuY29pcyBDaG9sbGV0LCBKLiBKLiBBbGxhaXJlLiBEZWVwIExlYXJuaW5nIHdpdGggUi4gUHVibGlzaGVyOiBNYW5uaW5nIFB1YmxpY2F0aW9ucy4gMjAxOC4gSVNCTjogOTc4MTYxNzI5NTU0Ng0K