1 Giới thiệu

So sánh giá trị một đại lượng giữa nhiều phân nhóm độc lập là một vấn đề thường gặp trong nghiên cứu y học lâm sàng. Giải pháp thường quy là phân tích phương sai một yếu tố (đơn biến) (One-way ANOVA). Tuy nhiên, ANOVA cổ điển lại đặt ra giả định về tính phân phối chuẩn của dữ liệu và giả định này gần như không phù hợp với thực tiễn, bởi vì hầu hết những đại lượng sinh lý bệnh học trong tự nhiên đều có phân phối lệch dương.

Ở đây chúng tôi giới thiệu một giải pháp linh hoạt và phổ quát hơn cho bài toán so sánh giá trị trung bình của biến định lượng giữa nhiều phân nhóm độc lập, đó là mô hình tuyến tính tỗng quát (Generalized linear model, GLM) với phân phối Gamma.

2 Công cụ cần thiết:

Quy trình phân tích cần những công cụ sau đây:

  • Hệ sinh thái tidyverse: với thư viện ggplot2 cho đồ họa thống kê và dplyr để thao tác dữ liệu

  • Thư viện ggridges : để vẽ biểu đổ mật độ phân phối riêng cho từng phân nhóm (ggplot2 cơ bản không hỗ trợ loại biểu đồ này).

  • Thư viện fitdistrplus cho phép kiểm tra tính phù hợp giữa dữ liệu thực nghiệm và một quy luật phân phối xác suất lý thuyết, như Gaussian hoặc Gamma.

  • Thư viện gamlss: cho phép khớp các mô hình hồi quy cho nhiều họ phân phối khác nhau, bao gồm Gamma.

  • Thư viện broom: cho phép thao tác trên kết quả mô hình hồi quy gamlss.

  • Thư viện marginaleffects: dùng cho phân tích hậu kiểm (post-hoc analysis) trên kết quả mô hình hồi quy gamlss

  • Dữ liệu đầu vào có cấu trúc gồm 1 biến kết quả Y thuộc loại định lượng liên tục (kiểu dữ liệu double, numeric, int) và 1 biến phân nhóm X thuộc loại định tính rời rạc (định dạng character hoặc factor). Lưu ý, phân phối Gamma có miền xác định là tập số thực dương R+, nên trong dữ liệu Y không được có giá trị < 0 hoặc = 0.

# Thao tác dữ liệu và đồ họa
library(tidyverse)
library(ggridges)

# Kiểm tra quy luật phân phối
library(fitdistrplus)

# Mô hình GLM
library(broom)
library(gamlss)

library(marginaleffects)

Thí dụ minh họa sử dụng dataset “Five_protocols.csv” từ một nghiên cứu có thực trên 1005 phụ nữ được thực hiện thụ tinh nhân tạo. Mục tiêu đặt ra là khảo sát giá trị hormone LH ở ngày trigger (biến LH) giữa 5 loại phác đồ kích thích buồng trứng (biến Protocol), gồm:

  • Mild = mild stimulation sử dụng Letrozole,
  • GnRH_ant = Phác đồ đối vận GnRH,
  • Fol_long = Follicular long acting,
  • PPOS: Progestin-Primed Ovarian Stimulation,
  • Lut_short = Lutean phase short acting.

3 Chuẩn bị dữ liệu

Dữ liệu được lưu ở định dạng .csv với dấu ngăn cách là “;” và dấu thập phân là “.”

  1. Ta dùng hàm read.csv để tải dữ liệu vào R và lưu trong dataframe df.

  2. Dùng hàm na.omit() để loại bỏ những đơn vị quan sát bị thiếu sót dữ liệu (missing value)

  3. Chuyển Protocol từ 1 biến kiểu kí tự (character) thành factor, để tương thích với quy trình phân tích.

df <- read.csv('Five_protocols.csv', 
               sep = ';', 
               dec= '.', 
               fileEncoding = 'UTF-8-BOM')%>%na.omit()

df$Protocol = as.factor(df$Protocol)

df%>%sample_frac(0.1)%>%head()%>%knitr::kable()
Protocol Age BMI LH P
Lut_short 40 23.00 3.26 0.05
Lut_short 30 22.70 1.97 0.69
GnRH_ant 30 24.20 1.55 0.19
Fol_long 36 20.20 0.69 0.56
Lut_short 36 20.80 2.95 0.38
PPOS 39 20.05 1.87 0.60

Một cách mặc định, biến rời rạc loại factor sẽ được phân cấp theo thứ tự alphabet, mỗi bậc giá trị sẽ tương ứng với số thứ tự. Trong trường hợp này, thứ tự các loại protocol là:

1 = Fol_long (sẽ được xem là nhóm tham chiếu khi phân tích hồi quy)

2 = GnRH_ant

3 = Lut_short

4 = Mild

5 = PPOS

levels(df$Protocol)
## [1] "Fol_long"  "GnRH_ant"  "Lut_short" "Mild"      "PPOS"

4 Kế hoạch phân tích

Một quy trình phân tích gồm 4 bước sẽ được tiến hành nhằm giải đáp câu hỏi nghiên cứu đã đặt ra:

  1. Đặc tính phân bố của giá trị LH ở 5 nhóm phác đồ sẽ được mô tả bằng trung bình, độ lệch chuẩn, trung vị và bách phân vị 5-95, đồng thời trình bày trực quan bằng biểu đồ mật độ phân bố (Kernel density plot) hoặc Boxplot, violin plot.

  2. Dữ liệu LH sẽ được khớp lần lượt với 2 quy luật phân phối Gamma và Gaussian, sau đó đối chiếu đồ thị hàm PDF và CDF để xác nhận tính phù hợp tốt hơn của phân phối Gamma so với Gaussian.

  3. Mô hình hồi quy tuyến tính tổng quát với phân phối Gamma sẽ được áp dụng để khảo sát giá trị trung bình LH giữa 5 phác đồ.

  4. Hậu kiểm so sánh bắt cặp tuần tự nhằm kiểm tra một loạt giả định về sự khác biệt của giá trị LH giữa các nhóm phác đồ.

Suy diễn thống kê dựa trên kiểm định giả thuyết vô hiệu với ngưỡng ý nghĩa thống kê p = 0.05. Hiệu chỉnh Bonferroni được áp dụng khi kiểm tra hàng loạt giả định.

5 Bước 1: Phân tích mô tả

Mục tiêu của công đoạn này nhằm thăm dò và mô tả sơ khởi về đặc tính phân phối của LH giữa các nhóm phác đồ khác nhau. Nhận định rút ra từ phân tích mô tả này cho phép đặt ra những giả thuyết cần chứng minh và đề xuất hướng phân tích phù hợp.

  1. Dựng bảng thống kê mô tả
  • Sử dụng hàm group_by của thư viện dplyr để phân chia dữ liệu thành 5 phần, tương ứng 5 bậc giá trị của biến Protocol

  • Áp dụng hàm summarize của thư viện dplyr để tính mean, sd, median, p5, p95, min và max của LH cho mỗi nhóm nhỏ

  • Dùng hàm arrange của dplyr để Xếp thứ tự theo giá trị trung bình LH tăng dần

Kết quả như sau:

df%>% 
  group_by(Protocol)%>%
  summarize(n = n(),
            mean = sprintf("%0.2f", mean(LH)),
            sd = sprintf("%0.2f",sd(LH)),
            median = sprintf("%0.2f", median(LH)),
            p5 = sprintf("%0.2f", quantile(LH, 0.05)),
            p95 = sprintf("%0.2f", quantile(LH, 0.95)),
            min= sprintf("%0.2f", min(LH)),
            max = sprintf("%0.2f", max(LH)),
            )%>%
    arrange(mean)%>%
    knitr::kable()
Protocol n mean sd median p5 p95 min max
Fol_long 261 1.37 1.64 0.87 0.13 4.18 0.10 12.72
Mild 75 12.02 8.45 8.70 2.52 27.65 1.81 36.90
Lut_short 220 2.50 1.65 2.12 0.75 4.92 0.17 14.51
PPOS 204 3.65 3.90 2.41 0.65 10.68 0.09 29.15
GnRH_ant 245 7.78 6.01 6.05 1.55 19.06 0.41 45.02

Theo kết quả này, có sự khác biệt về giá trị LH trung bình giữa 5 phác đồ. Một cách cụ thể, LH trung bình có khuynh hướng tăng dần theo thứ tự:

Fol_long < Short < Mild < < PPOS < GnRH_ant < Mild

  1. So sánh trực quan bằng biểu đồ

Ta sẽ sử dụng 2 loại biểu đồ cho phép so sánh đặc điểm phân bố của LH giữa 5 phác đồ, đó là Biểu đồ mật độ phân phối 1 chiều (Density plot) hoặc biểu đồ Boxplot

Đầu tiên, ta tạo dataframe med_df trình bày giá trị trung bình của LH giữa 5 phân nhóm, sử dụng kết hợp 2 hàm group_by và summarize_at. Dataframe này sẽ được dùng để vẽ biểu đồ tuyến kí và định vị trung bình LH cho mỗi nhóm.

Tiếp theo, ta lần lượt dùng các hàm:

  • geom_density_ridges (data = df) để vẽ 5 biểu đồ density plot cho LH ở 5 nhóm, màu nền theo nhóm phác đồ. Lưu ý rằng hàm geom_density_ridges mặc định trục x = biến định lượng, y = phân nhóm.

  • geom_path cho med_df để vẽ biểu đồ tuyến kí nối các điểm giá trị LH trung bình;

  • geom_point cho med_df để chấm 5 điểm giá trị LH trung bình

Lưu ý rằng trục X luôn là LH, y luôn là Protocol cho 3 hàm trên

Sau đó, dùng hàm coord_flip() để xoay trục 90°, như vậy trục x và y sẽ hoán chuyển cho nhau, hình ảnh sẽ biểu diễn theo chiều dọc thay vì chiều ngang.

Hàm scale_x_continuous cho phép tùy chỉnh thang đo trục X, ở đây giới hạn limits từ -0.5 đến 40, mốc thang đo 5 đon vị

med_df = df %>% 
  group_by(Protocol)%>%
  summarize_at('LH', mean)

ggplot()+
  geom_density_ridges(data = df,
                      aes(y = Protocol, 
                          x = LH,
                          fill = Protocol),
                      scale = 0.8,
                      alpha = 0.5,
                      show.legend = F)+
  geom_path(data = med_df,
            aes(y = Protocol, x = LH, group = 1),
            color = 'black',
            size = 1,
            alpha = 0.5)+
  geom_point(data = med_df,
            aes(y = Protocol, x = LH),
            color = 'black',
            size = 2.5,
            alpha = 0.9)+
  coord_flip()+
  scale_x_continuous(limits = c(-0.5,40), 
                     breaks = seq(0,40,by = 5))+
  labs(x = 'LH level (mIU/mL)', y = 'Protocols')+
  theme_bw(10)

Kết quả thu được là 5 biểu đồ mật độ phân bố như trong hình. Các biểu đồ này có bản chất là đồ thị hàm probability desity (PDF) của biến LH, trục Y tương ứng với thang đo của LH). Chúng mô tả đặc tính phân phối của LH trong 5 nhóm phác đồ (trục X). Các chấm màu đen tương ứng với giá trị trung bình LH ở mỗi nhóm, được nối với nhau bằng các đoạn thẳng thể hiện khuynh hướng tăng/giảm của giá trị này.

Dựa vào hình ảnh này, ta có thể rút ra được hai thông tin: thứ nhất, có sự khác biệt rõ nét, không chỉ về giá trị trung bình nhưng còn về đặc tính phân phối của LH, thí dụ độ phân tán, độ lệch… giữa 5 nhóm. Thứ hai là ở mỗi phân nhóm, LH chắc chắn không thể phù hợp với quy luật phân phối chuẩn (Gaussian). Phân phối của LH không đối xứng, thậm chí có độ lệch rất cao ở các nhóm GnRh_ant, Mild và PPOS.

Ta có thể làm tương tự để tạo ra biểu đổ boxplot, chỉ cần thay hàm geom_density_ridges bằng hàm geom_boxplot. Lưu ý rằng với dạng biểu đồ boxplot mặc định thì trục x chỉ Protocol, còn trục y cho LH, ta vẫn dùng hàm coord_flip() để xoay trục, trình bày theo phương ngang, vì khả năng so sánh bằng thị giác sẽ tốt hơn theo chiều ngang so với chiều dọc.

ggplot()+
  geom_boxplot(data = df,
                      aes(x = Protocol, 
                          y = LH,
                          fill = Protocol),
                      alpha = 0.5,
                      show.legend = F)+
  geom_point(data = med_df,
            aes(x = Protocol, y = LH),
            color = 'black',
            shape = 18,
            size = 5,
            alpha = 0.9)+
  scale_y_continuous(limits = c(-0.5,40), 
                     breaks = seq(0,40,by = 5))+
  labs(y = 'LH level (mIU/mL)', x = 'Protocols')+
  coord_flip()+
  theme_bw(10)

Biểu đồ boxplot trình bày các trị số thống kê mô tả đại diện gồm trung vị, tứ phân vị (phân vị thứ 25, 50, 75), các chấm tròn nhỏ là những giá trị vượt ra khỏi khoảng tứ phân vị. Chấm màu đen lớn nhất là vị trí của trung bình. Tuy không mô tả hình ảnh của toàn thể mật độ phân phối, biểu đồ Boxplot vừa đủ để cho ta thấy cùng những thông tin về sự tương phản rõ nét giữa 5 phân nhóm, ta cũng cảm nhận được LH không có phân phối chuẩn (trung vị không trùng với trung bình, 2 đầu boxplot không cân xứng, những giá trị cao lệch hẳn về chiều dương của thang đo LH…).

6 Bước 2: Biện luận về tính phù hợp của phân phối Gamma

Mục tiêu của công đoạn này nhằm chứng minh rằng để ước lượng giá trị LH thì phân phối Gamma phù hợp hơn so với phân phối Gaussian.

Về mặt lý thuyết, ta hoàn toàn có thể chọn quy luật phân phối Gamma cho biến LH vì những lý do sau: Thang đo LH có giá trị >0, phân phối của LH có hình ảnh bất đối xứng và lệch dương.

Tuy nhiên, ta có thể dựa vào chứng cứ xác thực hơn bằng quy trình sau:

  1. Đầu tiên, ta dùng hàm fitfist để lần lượt khớp dữ liệu LH với 2 quy luật phân phối Gaussian và Gamma,

  2. sau đó đối chiếu hình ảnh đồ thị hàm PDF và CDF so với dữ liệu thực nghiệm, bằng 2 hàm denscomp và cdfcomp:

fit_gam = fitdist(df$LH, 'gamma', optim.method = 'BFGS')
fit_gauss = fitdist(df$LH, 'norm', optim.method = 'BFGS')

denscomp(list(fit_gauss, fit_gam), 
         legendtext = c("Gaussian", "Gamma"),
         plotstyle = 'ggplot',
         fitcol = c('#03a9fc','#fc036f'),
         dempcol = c('grey'),
         fitlty = 1,
         fitlwd = 1)+
  labs(x = 'LH on trigger day (mUI/mL)')+ 
  theme_bw()

Chú thích: Biểu đồ trên cho phép đối chiếu đồ thị hàm PDF (mật độ xác suất) của 2 phân phối Gaussian (màu xanh), Gamma (màu đỏ) với hình ảnh biểu đồ tần suất trên thực nghiệm (màu xám).

cdfcomp(list(fit_gauss, fit_gam), 
        legendtext = c("Gaussian", "Gamma"),
        plotstyle = 'ggplot',
        fitcol = c('#03a9fc','#fc036f'),
        fitlty = 1,
        datacol = 'grey')+
  labs(x = 'LH on trigger day (mUI/mL)')+ 
  theme_bw()

Chú thích: Biểu đồ trên cho phép đối chiếu đồ thị hàm CDF (mật độ xác suất tích lũy) của 2 phân phối Gaussian (màu xanh), Gamma (màu đỏ) với hàm CDF trên thực nghiệm (màu xám).

Cả 2 kết quả này cho thấy phân phối Gamma thích hợp hơn so với phân phối Gaussian để ước lượng giá trị LH, vì hàm PDF và CDF của phân phối Gamma phù hợp hơn với dữ liệu thực nghiệm.

Lợi ích của việc chọn quy luật phân phối đúng cho biến kết quả mô hình hồi quy, đó là nó cho phép ước lượng và suy luận thống kê chính xác hơn, phù hợp thực tế hơn.

7 Bước 3: Dựng mô hình hồi quy với phân phối Gamma

Như đã trình bày trong kế hoạch phân tích, công cụ thống kê chính mà ta sử dụng là một mô hình hồi quy tuyến tính tổng quát (GLM). Hệ thống mô hình GLM do 2 nhà thống kê John Nelder và Robert Wedderburn thiết lập vào năm 1972 cho phép ước lượng giá trị của biến kết quả theo nhiều quy luật phân phối đa dạng.

Một cách tổng quát, mô hình GLM ước lượng giá trị trung bình (tham số \(\mu\)) của biến ngẫu nhiên Y, điều kiện hóa theo một tập biến X thông qua một hàm liên kết \(g\) và một mô hình hồi tuyến tính \(X\beta\).

\[E(Y|X) = \mu_{Y} \sim g^{-1}(X\beta)\] Trong bài toán hiện thời, biến kết quả Y là giá trị của LH ngày trigger, được giả định là một biến ngẫu nhiên theo quy luật phân phối Gamma với 2 tham số \(\mu\)\(\sigma\)

Hàm mật độ phân phối (PDF) của phân phối Gamma được xác định như sau:

\[f(y|\mu ,\sigma)=\frac{1}{\left ( \sigma ^{2} \mu \right )^{1/\sigma^{2}}}\frac{y^{\frac{1}{\sigma ^{2}}-1}e^{-y/(\sigma ^{2}\mu )}}{\Gamma \left ( 1/\sigma^{2} \right )}\]

\(\mu\) có ý nghĩa như trọng tâm (central tendency) hay giá trị trung bình của biến kết quả cần ước lượng, còn giá trị của \(\sigma\) tương ứng với mức độ phân tán và xác định hình ảnh của hàm mật độ phân phối. Một khi có mu và sigma, có thể ước tính độ lệch chuẩn (và phương sai) của LH theo công thức:

\[SD = \sigma * \mu\]

Phân tích hồi quy Gamma dùng thư viện gamlss. Đầu tiên, ta khớp một mô hình hồi quy Gamma chỉ chứa Intercept. Mô hình này tương ứng với việc ước lượng giá trị trung bình của LH cho toàn bộ mẫu khảo sát, không phân biệt nhóm phác đồ nào.

Ta dùng hàm gamlss, với công thức formula = LH ~ 1, và family = GA

# Mô hình chỉ có intercept

m0 = gamlss(formula = LH ~ 1,
             data=df,
             family = GA,
             trace=F,
             parallel="multicore",
             ncpus = nC)

Ta sẽ dùng mô hình cơ bản này để làm tham chiếu và so sánh với mô hình chính trong phân tích, nhằm chứng minh rằng yếu tố chia nhóm theo phác đồ thực sự có ảnh hưởng đến giá trị trung bình LH.

Nội dung mô hình cơ bản (chỉ chứa Intercept, ước lượng LH trung bình cho toàn quần thể) có nội dung như sau:

summary(m0)
## ******************************************************************
## Family:  c("GA", "Gamma") 
## 
## Call:  gamlss(formula = LH ~ 1, family = GA, data = df, trace = F,  
##     parallel = "multicore", ncpus = nC) 
## 
## Fitting method: RS() 
## 
## ------------------------------------------------------------------
## Mu link function:  log
## Mu Coefficients:
##             Estimate Std. Error t value Pr(>|t|)    
## (Intercept)  1.49042    0.03189   46.73   <2e-16 ***
## ---
## 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)  0.01101    0.01960   0.561    0.575
## 
## ------------------------------------------------------------------
## No. of observations in the fit:  1005 
## Degrees of Freedom for the fit:  2
##       Residual Deg. of Freedom:  1003 
##                       at cycle:  2 
##  
## Global Deviance:     5005.425 
##             AIC:     5009.425 
##             SBC:     5019.251 
## ******************************************************************

gamlss dựng 2 mô hình hồi quy riêng cho \(\mu\)\(\sigma\), cả 2 mô hình đều sử dụng hàm liên kết là logarit, vì vậy thang đo của các hệ số hồi quy hiện thời là log(LH), để đưa về thang đo nguyên thủy (mUI/mL) ta phải dùng hàm exponential.

Mô hình thứ nhất cho biết giá trị trung bình của LH cho toàn quần thể \(\mu = exp(1.49042) = 4.438959\)

Ta có thể đối chiếu với kết quả ước lượng từ dữ liệu thực nghiệm, và thấy rằng Intercept của mô hình m0 cơ bản rất phù hợp với giá trị trung bình của LH

mean(df$LH)
## [1] 4.438955
  1. Tiếp theo, ta dựng một mô hình khác với 1 biến độc lập là phân nhóm Protocol. Mô hình này cho phép ước lượng giá trị trung bình LH ở 5 phân nhóm Protocol khác nhau, hay nói cách khác, khảo sát liên hệ giữa biến protocol và giá trị trung bình LH (hay hiệu ứng của Protocol đối với giá trị trung bình của LH).

\[\mu_{LH} \sim \beta_0*Long + \beta_1*GnRHant + \beta_2*Short + \beta_3*Mild + \beta_4*PPOS\]

Mô hình này có công thức cho tham số chính (trung bình của phân phối Gamma) là LH ~ Protocol; và cho tham số sigma: sigma.formula = ~ Protocol.

# Mô hình ANOVA 1 biến

m1 = gamlss(formula = LH ~ Protocol,
            sigma.formula = ~ Protocol,
            data=df,
            family = GA,
            trace=F,
            parallel="multicore",
            ncpus = nC)

Ở đây ta ước lượng đồng thời cả 2 tham số của phân phối Gamma là mu (trung bình) và sigma (tham số kiểu hình) theo biến Protocol. Vì ta không chỉ quan tâm đến việc Protocol sẽ làm thay đổi giá trị trung bình LH (trọng tâm của phân phối Gamma, tham số \(\mu\)), mà còn muốn biết liệu phân loại Protocol có tác động gây thay đổi hình ảnh (độ phân tán, độ lệch) của phân phối Gamma (tham số \(\sigma\)) hay không ?

Nội dung của mô hình chính (ước lượng LH theo phác đồ) có nội dung như sau

summary(m1)
## ******************************************************************
## Family:  c("GA", "Gamma") 
## 
## Call:  gamlss(formula = LH ~ Protocol, sigma.formula = ~Protocol,  
##     family = GA, data = df, trace = F, parallel = "multicore",      ncpus = nC) 
## 
## 
## Fitting method: RS() 
## 
## ------------------------------------------------------------------
## Mu link function:  log
## Mu Coefficients:
##                   Estimate Std. Error t value Pr(>|t|)    
## (Intercept)        0.31235    0.05756   5.426 7.23e-08 ***
## ProtocolGnRH_ant   1.73978    0.07409  23.481  < 2e-16 ***
## ProtocolLut_short  0.60523    0.06878   8.800  < 2e-16 ***
## ProtocolMild       2.17429    0.09639  22.556  < 2e-16 ***
## ProtocolPPOS       0.98292    0.08235  11.935  < 2e-16 ***
## ---
## 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)       -0.07264    0.03903  -1.861 0.063003 .  
## ProtocolGnRH_ant  -0.24182    0.05717  -4.230 2.56e-05 ***
## ProtocolLut_short -0.51025    0.05988  -8.521  < 2e-16 ***
## ProtocolMild      -0.32842    0.08573  -3.831 0.000136 ***
## ProtocolPPOS      -0.10027    0.05946  -1.686 0.092072 .  
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## ------------------------------------------------------------------
## No. of observations in the fit:  1005 
## Degrees of Freedom for the fit:  10
##       Residual Deg. of Freedom:  995 
##                       at cycle:  2 
##  
## Global Deviance:     4275.111 
##             AIC:     4295.111 
##             SBC:     4344.238 
## ******************************************************************

Mô hình m1 này cho phép ước lượng giá trị trung bình của LH ở mỗi phân nhóm Protocol. Lưu ý rằng phác đồ Fol_long được chọn làm tham chiếu, vì vậy Intercept trong mô hình tương ứng với trung bình của log(LH) trong phân nhóm Protocol = Fol_long.

Ta có thể đối chiếu với kết quả thống kê mô tả ban đầu:

df%>% 
  group_by(Protocol)%>%
  summarize('mean' = mean(LH)) %>% knitr::kable()
Protocol mean
Fol_long 1.366628
GnRH_ant 7.784449
Lut_short 2.503227
Mild 12.020800
PPOS 3.651961

exp(Intercept) tương ứng với trung bình của LH trong phác đồ Follicular long acting, đơn vị mUI/mL: \(exp(0.31235) = 1.366633\), ta thấy giá trị này tương đồng với kết quả mean LH ở nhóm Fol_Long

Giá trị trung bình LH nhóm GnRH_ant được tính bằng : exp(Intercept + ProtocolGnRH_ant): \(exp(0.31235 + 1.73978) = 7.784464\)

Cứ thế, ta có thể ước lượng được trung bình của log(LH) hoặc LH ở cả 5 phân nhóm.

8 Bước 4: Suy diễn thống kê

Dựa vào 2 mô hình tuyến tính tổng quát này, ta có thể thực hiện một số suy diễn thống kê như sau:

Đầu tiên, khi so sánh mô hình chỉ chứa intercept và mô hình có thêm biến Protocol bằng một kiểm định Likelihood ratio, ta có thể biết liệu phân nhóm phác đồ thực sự có hiệu ứng ý nghĩa đối với LH hay không ? (câu hỏi này tương tự với kiểm định F theo Fisher trong phân tích phương sai cổ điển)

LR.test(m0,m1)
##  Likelihood Ratio Test for nested GAMLSS models. 
##  (No check whether the models are nested is performed). 
##  
##        Null model: deviance= 5005.425 with  2 deg. of freedom 
##  Altenative model: deviance= 4275.111 with  10 deg. of freedom 
##  
##  LRT = 730.3143 with 8 deg. of freedom and p-value= 0

Kết quả cho ra một giá trị p cực kì thấp, như vậy ta có thể khẳng định rằng phân nhóm phác đồ thực sự có tác động làm thay đổi LH.

Nói cách khác, có sự khác biệt ý nghĩa về giá trị LH giữa các nhóm phác đồ khác nhau. Tuy nhiên ta chưa thể biết rõ sự khác biệt này là ở nhóm nào so với nhóm nào.

Nội dung hệ số hồi quy trong mô hình cho phép ta kiểm định về sự khác biệt của LH trung bình ở phác đồ PPOS, GnRH, Luteal short acting và Mild (Letrozole) so với nhóm tham chiếu là Follicular long acting.

Dựa vào kết quả thô của mô hình, có thể thấy tất cả hệ số hồi quy đều >0 và có ý nghĩa thống kê, cho thấy giá trị trung bình LH ở 4 phác đồ còn lại đều cao hơn so với phác đồ tham chiếu (Longacting).

Tuy nhiên, ta sẽ không suy diễn trực tiếp dựa vào hệ số hồi quy, mà tiến hành một phân tích hậu kiểm với nội dung so sánh bắt cặp tuần tự giữa 5 loại phác đồ. Trong khi làm thống kê mô tả, ta có cảm nhận về khuynh hướng tăng dần của giá trị LH trung bình theo thứ tự: Fol_long < Short < PPOS < GnRH_ant < Mild

Ở đây ta sẽ kiểm tra lại kết quả này.

Để tiến hành phân tích hậu kiểm này, ta dùng hàm avg_comparisons của thư viện marginalseffect với tùy chỉnh variables = list(Protocol = ‘pairwise’), sau đó hiệu chỉnh giá trị p bằng hàm p.adjust trong R, với phương pháp hiệu chỉnh Bonferroni.

Kết quả của hàm này là một bảng như sau:

pw_comp = avg_comparisons(m1,
                          what = 'mu',
                variables = list(Protocol = 'pairwise'))

pw_comp$adj_p = p.adjust(pw_comp$p.value, method = 'bonferroni')

pw_comp %>% dplyr::select(-c(1,2,6,7)) %>%knitr::kable()
contrast estimate std.error conf.low conf.high adj_p
GnRH_ant - Fol_long 6.417821 0.3715848 5.6895277 7.146114 0.0000000
Lut_short - Fol_long 1.136599 0.1227487 0.8960158 1.377182 0.0000000
Lut_short - GnRH_ant -5.281222 0.3751863 -6.0165734 -4.545870 0.0000000
Mild - Fol_long 10.654172 0.9328177 8.8258825 12.482461 0.0000000
Mild - GnRH_ant 4.236351 0.9979211 2.2804617 6.192240 0.0002184
Mild - Lut_short 9.517573 0.9342582 7.6864603 11.348685 0.0000000
PPOS - Fol_long 2.285332 0.2290351 1.8364319 2.734233 0.0000000
PPOS - GnRH_ant -4.132488 0.4220836 -4.9597569 -3.305220 0.0000000
PPOS - Lut_short 1.148734 0.2348331 0.6884692 1.608998 0.0000100
PPOS - Mild -8.368839 0.9540588 -10.2387601 -6.498918 0.0000000

Cột contrast trình bày các cặp so sánh mà ta cần kiểm nghiệm, thí dụ GnRH_ant - Fol_long;

Cột estimate trình bày giá trị khác biệt trung bình về LH giữa 2 nhóm phác đồ, thí dụ GnRHant cao hơn Long acting 6.4178 mUI/mL.

2 cột conf.low conf.high trình bày ngưỡng dưới và trên khoảng tin cậy 95% của khác biệt, KTC95% có ý nghĩa thống kê khi không chứa giá trị 0.

Cột adj_p trình bày giá trị p sau hiệu chỉnh Bonferroni,

Kết quả cho thấy toàn bộ 10 giả thuyết đều có ý nghĩa thống kê. Một cách tổng quát, giá trị LH trung bình tăng dần theo thứ tự 5 phác đồ:

Fol_long < Short < PPOS < GnRH_ant < Mild

Như vậy phác đồ Mild stimulation với Letrozole có LH trung bình ngày trigger cao nhất, phác đồ Follicular long acting có LH trung bình thấp nhất, phác đồ PPOS ở vị trí trung gian, với LH cao hơn so với 2 phác đồ Long và Short.

Cuối cùng, kết quả của mô hình hồi quy có thể trình bày trực quan như sau:

m1%>%augment()%>%mutate(UL= .fitted +.se.fit*1.96,
                     LL=.fitted-.se.fit*1.96)%>%
  group_by(Protocol)%>%
  summarize_at('.fitted', median) -> m1_sum

augment(m1)%>%mutate(UL= .fitted +.se.fit*1.96,
                     LL=.fitted-.se.fit*1.96)%>%
  ggplot()+
  geom_jitter(aes(y = Protocol, 
                  x = LH,
                  col = Protocol),
              scale = 0.8,
              alpha = 0.3,
              width = 0.01,
              show.legend = F)+
  geom_density_ridges(aes(y = Protocol, 
                          x = LH,
                          fill = Protocol),
                      scale = 0.8,
                      alpha = 0.3,
                      show.legend = F)+
  geom_errorbar(aes(y=Protocol, 
                    xmin=exp(LL), 
                    xmax=exp(UL)),
                width=0.1,
                size=1) + 
  geom_path(data = m1_sum,
            aes(y=Protocol, 
                x=exp(.fitted),
                group = 1))+
  geom_point(aes(y=Protocol, 
                 x=exp(.fitted)), 
             size=3)+
  scale_x_continuous(breaks = seq(0,40,by = 5))+
  labs(x = 'LH level on trigger day (mIU/mL)', y = 'Protocols')+
  coord_flip()+
  theme_bw()  

9 Kết luận

Ta thấy mô hình GLM với phân phối Gamma là một giải pháp phổ quát và hiệu quả cho hầu hết những tình huống nghiên cứu y học lâm sàng. Mô hình GLM Gamma có thể thay thế hoàn toàn phân tích phương sai (ANOVA) cổ điển và cả mô hình hồi quy tuyến tính thông thường.

LS0tDQp0aXRsZTogIk3DtCBow6xuaCBo4buTaSBxdXkgR2FtbWEiDQphdXRob3I6ICJCUy4gTmfhu41jIENow6J1LCBCUy4gS2jhuqMgTmhpIg0KZGF0ZTogIjI3IFRow6FuZyAyIG7Eg20gMjAyMyINCm91dHB1dDoNCiAgaHRtbF9kb2N1bWVudDoNCiAgICBjb2RlX2Rvd25sb2FkOiB5ZXMNCiAgICBjb2RlX2ZvbGRpbmc6IGhpZGUNCiAgICBudW1iZXJfc2VjdGlvbnM6IHllcw0KICAgIHRoZW1lOiBkZWZhdWx0DQogICAgdG9jOiB5ZXMNCiAgICB0b2NfZmxvYXQ6IHllcw0KICAgIGRldjogc3ZnDQogIHdvcmRfZG9jdW1lbnQ6DQogICAgdG9jOiB5ZXMNCiAgcGRmX2RvY3VtZW50OiANCiAgICB0b2M6IHllcw0KICAgIGxhdGV4X2VuZ2luZTogbHVhbGF0ZXgNCiAgICBrZWVwX3RleDogeWVzDQotLS0NCg0KYGBge3Igc2V0dXAsaW5jbHVkZT1GQUxTRX0NCmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvID0gVFJVRSwgbWVzc2FnZSA9IEZBTFNFLHdhcm5pbmc9RkFMU0UpDQpgYGANCg0KIVtdKEdhbW1hX0dMTS5wbmcpDQoNCiMgR2nhu5tpIHRoaeG7h3UNCg0KU28gc8OhbmggZ2nDoSB0cuG7iyBt4buZdCDEkeG6oWkgbMaw4bujbmcgZ2nhu69hIG5oaeG7gXUgcGjDom4gbmjDs20gxJHhu5ljIGzhuq1wIGzDoCBt4buZdCB24bqlbiDEkeG7gSB0aMaw4budbmcgZ+G6t3AgdHJvbmcgbmdoacOqbiBj4bupdSB5IGjhu41jIGzDom0gc8OgbmcuIEdp4bqjaSBwaMOhcCB0aMaw4budbmcgcXV5IGzDoCBwaMOibiB0w61jaCBwaMawxqFuZyBzYWkgbeG7mXQgeeG6v3UgdOG7kSAoxJHGoW4gYmnhur9uKSAoT25lLXdheSBBTk9WQSkuIFR1eSBuaGnDqm4sIEFOT1ZBIGPhu5UgxJFp4buDbiBs4bqhaSDEkeG6t3QgcmEgZ2nhuqMgxJHhu4tuaCB24buBIHTDrW5oIHBow6JuIHBo4buRaSBjaHXhuqluIGPhu6dhIGThu68gbGnhu4d1IHbDoCBnaeG6oyDEkeG7i25oIG7DoHkgZ+G6p24gbmjGsCBraMO0bmcgcGjDuSBo4bujcCB24bubaSB0aOG7sWMgdGnhu4VuLCBi4bufaSB2w6wgaOG6p3UgaOG6v3Qgbmjhu69uZyDEkeG6oWkgbMaw4bujbmcgc2luaCBsw70gYuG7h25oIGjhu41jIHRyb25nIHThu7Egbmhpw6puIMSR4buBdSBjw7MgcGjDom4gcGjhu5FpIGzhu4djaCBkxrDGoW5nLg0KDQrhu54gxJHDonkgY2jDum5nIHTDtGkgZ2nhu5tpIHRoaeG7h3UgbeG7mXQgZ2nhuqNpIHBow6FwIGxpbmggaG/huqF0IHbDoCBwaOG7lSBxdcOhdCBoxqFuIGNobyBiw6BpIHRvw6FuIHNvIHPDoW5oIGdpw6EgdHLhu4sgdHJ1bmcgYsOsbmggY+G7p2EgYmnhur9uIMSR4buLbmggbMaw4bujbmcgZ2nhu69hIG5oaeG7gXUgcGjDom4gbmjDs20gxJHhu5ljIGzhuq1wLCDEkcOzIGzDoCBtw7QgaMOsbmggdHV54bq/biB0w61uaCB04buXbmcgcXXDoXQgKEdlbmVyYWxpemVkIGxpbmVhciBtb2RlbCwgR0xNKSB24bubaSBwaMOibiBwaOG7kWkgR2FtbWEuDQoNCiMgQ8O0bmcgY+G7pSBj4bqnbiB0aGnhur90Og0KDQpRdXkgdHLDrG5oIHBow6JuIHTDrWNoIGPhuqduIG5o4buvbmcgY8O0bmcgY+G7pSBzYXUgxJHDonk6DQoNCisgSOG7hyBzaW5oIHRow6FpIHRpZHl2ZXJzZTogduG7m2kgdGjGsCB2aeG7h24gZ2dwbG90MiBjaG8gxJHhu5MgaOG7jWEgdGjhu5FuZyBrw6ogdsOgIGRwbHlyIMSR4buDIHRoYW8gdMOhYyBk4buvIGxp4buHdQ0KDQorIFRoxrAgdmnhu4duIGdncmlkZ2VzIDogxJHhu4MgduG6vSBiaeG7g3UgxJHhu5UgbeG6rXQgxJHhu5kgcGjDom4gcGjhu5FpIHJpw6puZyBjaG8gdOG7q25nIHBow6JuIG5ow7NtIChnZ3Bsb3QyIGPGoSBi4bqjbiBraMO0bmcgaOG7lyB0cuG7oyBsb+G6oWkgYmnhu4N1IMSR4buTIG7DoHkpLg0KDQorIFRoxrAgdmnhu4duIGZpdGRpc3RycGx1cyBjaG8gcGjDqXAga2nhu4NtIHRyYSB0w61uaCBwaMO5IGjhu6NwIGdp4buvYSBk4buvIGxp4buHdSB0aOG7sWMgbmdoaeG7h20gdsOgIG3hu5l0IHF1eSBsdeG6rXQgcGjDom4gcGjhu5FpIHjDoWMgc3XhuqV0IGzDvSB0aHV54bq/dCwgbmjGsCBHYXVzc2lhbiBob+G6t2MgR2FtbWEuDQoNCisgVGjGsCB2aeG7h24gZ2FtbHNzOiBjaG8gcGjDqXAga2jhu5twIGPDoWMgbcO0IGjDrG5oIGjhu5NpIHF1eSBjaG8gbmhp4buBdSBo4buNIHBow6JuIHBo4buRaSBraMOhYyBuaGF1LCBiYW8gZ+G7k20gR2FtbWEuDQoNCisgVGjGsCB2aeG7h24gYnJvb206IGNobyBwaMOpcCB0aGFvIHTDoWMgdHLDqm4ga+G6v3QgcXXhuqMgbcO0IGjDrG5oIGjhu5NpIHF1eSBnYW1sc3MuDQoNCisgVGjGsCB2aeG7h24gbWFyZ2luYWxlZmZlY3RzOiBkw7luZyBjaG8gcGjDom4gdMOtY2ggaOG6rXUga2nhu4NtIChwb3N0LWhvYyBhbmFseXNpcykgdHLDqm4ga+G6v3QgcXXhuqMgbcO0IGjDrG5oIGjhu5NpIHF1eSBnYW1sc3MNCg0KKyBE4buvIGxp4buHdSDEkeG6p3UgdsOgbyBjw7MgY+G6pXUgdHLDumMgZ+G7k20gMSBiaeG6v24ga+G6v3QgcXXhuqMgWSB0aHXhu5ljIGxv4bqhaSDEkeG7i25oIGzGsOG7o25nIGxpw6puIHThu6VjIChraeG7g3UgZOG7ryBsaeG7h3UgZG91YmxlLCBudW1lcmljLCBpbnQpIHbDoCAxIGJp4bq/biBwaMOibiBuaMOzbSBYIHRodeG7mWMgbG/huqFpIMSR4buLbmggdMOtbmggcuG7nWkgcuG6oWMgKMSR4buLbmggZOG6oW5nIGNoYXJhY3RlciBob+G6t2MgZmFjdG9yKS4gTMawdSDDvSwgcGjDom4gcGjhu5FpIEdhbW1hIGPDsyBtaeG7gW4geMOhYyDEkeG7i25oIGzDoCB04bqtcCBz4buRIHRo4buxYyBkxrDGoW5nIFIrLCBuw6puIHRyb25nIGThu68gbGnhu4d1IFkga2jDtG5nIMSRxrDhu6NjIGPDsyBnacOhIHRy4buLIDwgMCBob+G6t2MgPSAwLg0KDQpgYGB7cixtZXNzYWdlID0gRkFMU0Usd2FybmluZz1GQUxTRX0NCiMgVGhhbyB0w6FjIGThu68gbGnhu4d1IHbDoCDEkeG7kyBo4buNYQ0KbGlicmFyeSh0aWR5dmVyc2UpDQpsaWJyYXJ5KGdncmlkZ2VzKQ0KDQojIEtp4buDbSB0cmEgcXV5IGx14bqtdCBwaMOibiBwaOG7kWkNCmxpYnJhcnkoZml0ZGlzdHJwbHVzKQ0KDQojIE3DtCBow6xuaCBHTE0NCmxpYnJhcnkoYnJvb20pDQpsaWJyYXJ5KGdhbWxzcykNCg0KbGlicmFyeShtYXJnaW5hbGVmZmVjdHMpDQpgYGANCg0KVGjDrSBk4bulIG1pbmggaOG7jWEgc+G7rSBk4bulbmcgZGF0YXNldCAiRml2ZV9wcm90b2NvbHMuY3N2IiB04burIG3hu5l0IG5naGnDqm4gY+G7qXUgY8OzIHRo4buxYyB0csOqbiAxMDA1IHBo4bulIG7hu68gxJHGsOG7o2MgdGjhu7FjIGhp4buHbiB0aOG7pSB0aW5oIG5ow6JuIHThuqFvLiBN4bulYyB0acOqdSDEkeG6t3QgcmEgbMOgIGto4bqjbyBzw6F0IGdpw6EgdHLhu4sgaG9ybW9uZSBMSCDhu58gbmfDoHkgdHJpZ2dlciAoYmnhur9uIExIKSBnaeG7r2EgNSBsb+G6oWkgcGjDoWMgxJHhu5Mga8OtY2ggdGjDrWNoIGJ14buTbmcgdHLhu6luZyAoYmnhur9uIFByb3RvY29sKSwgZ+G7k206IA0KDQorIE1pbGQgPSBtaWxkIHN0aW11bGF0aW9uIHPhu60gZOG7pW5nIExldHJvem9sZSwgDQorIEduUkhfYW50ID0gUGjDoWMgxJHhu5MgxJHhu5FpIHbhuq1uIEduUkgsIA0KKyBGb2xfbG9uZyA9IEZvbGxpY3VsYXIgbG9uZyBhY3RpbmcsIA0KKyBQUE9TOiBQcm9nZXN0aW4tUHJpbWVkIE92YXJpYW4gU3RpbXVsYXRpb24sIA0KKyBMdXRfc2hvcnQgPSBMdXRlYW4gcGhhc2Ugc2hvcnQgYWN0aW5nLg0KDQojIENodeG6qW4gYuG7iyBk4buvIGxp4buHdQ0KDQpE4buvIGxp4buHdSDEkcaw4bujYyBsxrB1IOG7nyDEkeG7i25oIGThuqFuZyAuY3N2IHbhu5tpIGThuqV1IG5nxINuIGPDoWNoIGzDoCAiOyIgdsOgIGThuqV1IHRo4bqtcCBwaMOibiBsw6AgIi4iDQoNCjEpIFRhIGTDuW5nIGjDoG0gcmVhZC5jc3YgxJHhu4MgdOG6o2kgZOG7ryBsaeG7h3UgdsOgbyBSIHbDoCBsxrB1IHRyb25nIGRhdGFmcmFtZSBkZi4NCg0KMikgRMO5bmcgaMOgbSBuYS5vbWl0KCkgxJHhu4MgbG/huqFpIGLhu48gbmjhu69uZyDEkcahbiB24buLIHF1YW4gc8OhdCBi4buLIHRoaeG6v3Ugc8OzdCBk4buvIGxp4buHdSAobWlzc2luZyB2YWx1ZSkNCg0KMykgQ2h1eeG7g24gUHJvdG9jb2wgdOG7qyAxIGJp4bq/biBraeG7g3Uga8OtIHThu7EgKGNoYXJhY3RlcikgdGjDoG5oIGZhY3RvciwgxJHhu4MgdMawxqFuZyB0aMOtY2ggduG7m2kgcXV5IHRyw6xuaCBwaMOibiB0w61jaC4NCg0KYGBge3IsbWVzc2FnZSA9IEZBTFNFLHdhcm5pbmc9RkFMU0V9DQpkZiA8LSByZWFkLmNzdignRml2ZV9wcm90b2NvbHMuY3N2JywgDQogICAgICAgICAgICAgICBzZXAgPSAnOycsIA0KICAgICAgICAgICAgICAgZGVjPSAnLicsIA0KICAgICAgICAgICAgICAgZmlsZUVuY29kaW5nID0gJ1VURi04LUJPTScpJT4lbmEub21pdCgpDQoNCmRmJFByb3RvY29sID0gYXMuZmFjdG9yKGRmJFByb3RvY29sKQ0KDQpkZiU+JXNhbXBsZV9mcmFjKDAuMSklPiVoZWFkKCklPiVrbml0cjo6a2FibGUoKQ0KYGBgDQoNCk3hu5l0IGPDoWNoIG3hurdjIMSR4buLbmgsIGJp4bq/biBy4budaSBy4bqhYyBsb+G6oWkgZmFjdG9yIHPhur0gxJHGsOG7o2MgcGjDom4gY+G6pXAgdGhlbyB0aOG7qSB04buxIGFscGhhYmV0LCBt4buXaSBi4bqtYyBnacOhIHRy4buLIHPhur0gdMawxqFuZyDhu6luZyB24bubaSBz4buRIHRo4bupIHThu7EuIFRyb25nIHRyxrDhu51uZyBo4bujcCBuw6B5LCB0aOG7qSB04buxIGPDoWMgbG/huqFpIHByb3RvY29sIGzDoDoNCg0KMSA9IEZvbF9sb25nIChz4bq9IMSRxrDhu6NjIHhlbSBsw6AgbmjDs20gdGhhbSBjaGnhur91IGtoaSBwaMOibiB0w61jaCBo4buTaSBxdXkpDQoNCjIgPSBHblJIX2FudA0KDQozID0gTHV0X3Nob3J0DQoNCjQgPSBNaWxkDQoNCjUgPSBQUE9TDQoNCmBgYHtyfQ0KbGV2ZWxzKGRmJFByb3RvY29sKQ0KYGBgDQojIEvhur8gaG/huqFjaCBwaMOibiB0w61jaA0KDQpN4buZdCBxdXkgdHLDrG5oIHBow6JuIHTDrWNoIGfhu5NtIDQgYsaw4bubYyBz4bq9IMSRxrDhu6NjIHRp4bq/biBow6BuaCBuaOG6sW0gZ2nhuqNpIMSRw6FwIGPDonUgaOG7j2kgbmdoacOqbiBj4bupdSDEkcOjIMSR4bq3dCByYToNCg0KMSkgxJDhurdjIHTDrW5oIHBow6JuIGLhu5EgY+G7p2EgZ2nDoSB0cuG7iyBMSCDhu58gNSBuaMOzbSBwaMOhYyDEkeG7kyBz4bq9IMSRxrDhu6NjIG3DtCB04bqjIGLhurFuZyB0cnVuZyBiw6xuaCwgxJHhu5kgbOG7h2NoIGNodeG6qW4sIHRydW5nIHbhu4sgdsOgIGLDoWNoIHBow6JuIHbhu4sgNS05NSwgxJHhu5NuZyB0aOG7nWkgdHLDrG5oIGLDoHkgdHLhu7FjIHF1YW4gYuG6sW5nIGJp4buDdSDEkeG7kyBt4bqtdCDEkeG7mSBwaMOibiBi4buRIChLZXJuZWwgZGVuc2l0eSBwbG90KSBob+G6t2MgQm94cGxvdCwgdmlvbGluIHBsb3QuDQoNCjIpIEThu68gbGnhu4d1IExIIHPhur0gxJHGsOG7o2Mga2jhu5twIGzhuqduIGzGsOG7o3QgduG7m2kgMiBxdXkgbHXhuq10IHBow6JuIHBo4buRaSBHYW1tYSB2w6AgR2F1c3NpYW4sIHNhdSDEkcOzIMSR4buRaSBjaGnhur91IMSR4buTIHRo4buLIGjDoG0gUERGIHbDoCBDREYgxJHhu4MgeMOhYyBuaOG6rW4gdMOtbmggcGjDuSBo4bujcCB04buRdCBoxqFuIGPhu6dhIHBow6JuIHBo4buRaSBHYW1tYSBzbyB24bubaSBHYXVzc2lhbi4NCg0KMykgTcO0IGjDrG5oIGjhu5NpIHF1eSB0dXnhur9uIHTDrW5oIHThu5VuZyBxdcOhdCB24bubaSBwaMOibiBwaOG7kWkgR2FtbWEgc+G6vSDEkcaw4bujYyDDoXAgZOG7pW5nIMSR4buDIGto4bqjbyBzw6F0IGdpw6EgdHLhu4sgdHJ1bmcgYsOsbmggTEggZ2nhu69hIDUgcGjDoWMgxJHhu5MuDQoNCjQpIEjhuq11IGtp4buDbSBzbyBzw6FuaCBi4bqvdCBj4bq3cCB0deG6p24gdOG7sSBuaOG6sW0ga2nhu4NtIHRyYSBt4buZdCBsb+G6oXQgZ2nhuqMgxJHhu4tuaCB24buBIHPhu7Ega2jDoWMgYmnhu4d0IGPhu6dhIGdpw6EgdHLhu4sgTEggZ2nhu69hIGPDoWMgbmjDs20gcGjDoWMgxJHhu5MuDQoNClN1eSBkaeG7hW4gdGjhu5FuZyBrw6ogZOG7sWEgdHLDqm4ga2nhu4NtIMSR4buLbmggZ2nhuqMgdGh1eeG6v3QgdsO0IGhp4buHdSB24bubaSBuZ8aw4buhbmcgw70gbmdoxKlhIHRo4buRbmcga8OqIHAgPSAwLjA1LiBIaeG7h3UgY2jhu4luaCBCb25mZXJyb25pIMSRxrDhu6NjIMOhcCBk4bulbmcga2hpIGtp4buDbSB0cmEgaMOgbmcgbG/huqF0IGdp4bqjIMSR4buLbmguDQoNCiMgQsaw4bubYyAxOiBQaMOibiB0w61jaCBtw7QgdOG6ow0KDQpN4bulYyB0acOqdSBj4bunYSBjw7RuZyDEkW/huqFuIG7DoHkgbmjhurFtIHRoxINtIGTDsiB2w6AgbcO0IHThuqMgc8ahIGto4bufaSB24buBIMSR4bq3YyB0w61uaCBwaMOibiBwaOG7kWkgY+G7p2EgTEggZ2nhu69hIGPDoWMgbmjDs20gcGjDoWMgxJHhu5Mga2jDoWMgbmhhdS4gTmjhuq1uIMSR4buLbmggcsO6dCByYSB04burIHBow6JuIHTDrWNoIG3DtCB04bqjIG7DoHkgY2hvIHBow6lwIMSR4bq3dCByYSBuaOG7r25nIGdp4bqjIHRodXnhur90IGPhuqduIGNo4bupbmcgbWluaCB2w6AgxJHhu4EgeHXhuqV0IGjGsOG7m25nIHBow6JuIHTDrWNoIHBow7kgaOG7o3AuDQoNCjEpIEThu7FuZyBi4bqjbmcgdGjhu5FuZyBrw6ogbcO0IHThuqMNCg0KKyBT4butIGThu6VuZyBow6BtIGdyb3VwX2J5IGPhu6dhIHRoxrAgdmnhu4duIGRwbHlyIMSR4buDIHBow6JuIGNoaWEgZOG7ryBsaeG7h3UgdGjDoG5oIDUgcGjhuqduLCB0xrDGoW5nIOG7qW5nIDUgYuG6rWMgZ2nDoSB0cuG7iyBj4bunYSBiaeG6v24gUHJvdG9jb2wgDQoNCisgw4FwIGThu6VuZyBow6BtIHN1bW1hcml6ZSBj4bunYSB0aMawIHZp4buHbiBkcGx5ciDEkeG7gyB0w61uaCBtZWFuLCBzZCwgbWVkaWFuLCBwNSwgcDk1LCBtaW4gdsOgIG1heCBj4bunYSBMSCBjaG8gbeG7l2kgbmjDs20gbmjhu48NCg0KKyBEw7luZyBow6BtIGFycmFuZ2UgY+G7p2EgZHBseXIgxJHhu4MgWOG6v3AgdGjhu6kgdOG7sSB0aGVvIGdpw6EgdHLhu4sgdHJ1bmcgYsOsbmggTEggdMSDbmcgZOG6p24NCg0KS+G6v3QgcXXhuqMgbmjGsCBzYXU6DQoNCmBgYHtyfQ0KZGYlPiUgDQogIGdyb3VwX2J5KFByb3RvY29sKSU+JQ0KICBzdW1tYXJpemUobiA9IG4oKSwNCiAgICAgICAgICAgIG1lYW4gPSBzcHJpbnRmKCIlMC4yZiIsIG1lYW4oTEgpKSwNCiAgICAgICAgICAgIHNkID0gc3ByaW50ZigiJTAuMmYiLHNkKExIKSksDQogICAgICAgICAgICBtZWRpYW4gPSBzcHJpbnRmKCIlMC4yZiIsIG1lZGlhbihMSCkpLA0KICAgICAgICAgICAgcDUgPSBzcHJpbnRmKCIlMC4yZiIsIHF1YW50aWxlKExILCAwLjA1KSksDQogICAgICAgICAgICBwOTUgPSBzcHJpbnRmKCIlMC4yZiIsIHF1YW50aWxlKExILCAwLjk1KSksDQogICAgICAgICAgICBtaW49IHNwcmludGYoIiUwLjJmIiwgbWluKExIKSksDQogICAgICAgICAgICBtYXggPSBzcHJpbnRmKCIlMC4yZiIsIG1heChMSCkpLA0KICAgICAgICAgICAgKSU+JQ0KICAgIGFycmFuZ2UobWVhbiklPiUNCiAgICBrbml0cjo6a2FibGUoKQ0KYGBgDQoNClRoZW8ga+G6v3QgcXXhuqMgbsOgeSwgY8OzIHPhu7Ega2jDoWMgYmnhu4d0IHbhu4EgZ2nDoSB0cuG7iyBMSCB0cnVuZyBiw6xuaCBnaeG7r2EgNSBwaMOhYyDEkeG7ky4gTeG7mXQgY8OhY2ggY+G7pSB0aOG7gywgTEggdHJ1bmcgYsOsbmggY8OzIGtodXluaCBoxrDhu5tuZyB0xINuZyBk4bqnbiB0aGVvIHRo4bupIHThu7E6DQoNCkZvbF9sb25nIDwgU2hvcnQgPCBNaWxkIDwgPCBQUE9TIDwgR25SSF9hbnQgPCBNaWxkDQoNCjIpIFNvIHPDoW5oIHRy4buxYyBxdWFuIGLhurFuZyBiaeG7g3UgxJHhu5MNCg0KVGEgc+G6vSBz4butIGThu6VuZyAyIGxv4bqhaSBiaeG7g3UgxJHhu5MgY2hvIHBow6lwIHNvIHPDoW5oIMSR4bq3YyDEkWnhu4NtIHBow6JuIGLhu5EgY+G7p2EgTEggZ2nhu69hIDUgcGjDoWMgxJHhu5MsIMSRw7MgbMOgIEJp4buDdSDEkeG7kyBt4bqtdCDEkeG7mSBwaMOibiBwaOG7kWkgMSBjaGnhu4F1IChEZW5zaXR5IHBsb3QpIGhv4bq3YyBiaeG7g3UgxJHhu5MgQm94cGxvdA0KDQrEkOG6p3UgdGnDqm4sIHRhIHThuqFvIGRhdGFmcmFtZSBtZWRfZGYgdHLDrG5oIGLDoHkgZ2nDoSB0cuG7iyB0cnVuZyBiw6xuaCBj4bunYSBMSCBnaeG7r2EgNSBwaMOibiBuaMOzbSwgc+G7rSBk4bulbmcga+G6v3QgaOG7o3AgMiBow6BtIGdyb3VwX2J5IHbDoCBzdW1tYXJpemVfYXQuIERhdGFmcmFtZSBuw6B5IHPhur0gxJHGsOG7o2MgZMO5bmcgxJHhu4MgduG6vSBiaeG7g3UgxJHhu5MgdHV54bq/biBrw60gdsOgIMSR4buLbmggduG7iyB0cnVuZyBiw6xuaCBMSCBjaG8gbeG7l2kgbmjDs20uDQoNClRp4bq/cCB0aGVvLCB0YSBs4bqnbiBsxrDhu6N0IGTDuW5nIGPDoWMgaMOgbToNCg0KKyBnZW9tX2RlbnNpdHlfcmlkZ2VzIChkYXRhID0gZGYpIMSR4buDIHbhur0gNSBiaeG7g3UgxJHhu5MgZGVuc2l0eSBwbG90IGNobyBMSCDhu58gNSBuaMOzbSwgbcOgdSBu4buBbiB0aGVvIG5ow7NtIHBow6FjIMSR4buTLiBMxrB1IMO9IHLhurFuZyBow6BtIGdlb21fZGVuc2l0eV9yaWRnZXMgbeG6t2MgxJHhu4tuaCB0cuG7pWMgeCA9IGJp4bq/biDEkeG7i25oIGzGsOG7o25nLCB5ID0gcGjDom4gbmjDs20uDQoNCisgZ2VvbV9wYXRoIGNobyBtZWRfZGYgxJHhu4MgduG6vSBiaeG7g3UgxJHhu5MgdHV54bq/biBrw60gbuG7kWkgY8OhYyDEkWnhu4NtIGdpw6EgdHLhu4sgTEggdHJ1bmcgYsOsbmg7DQoNCisgZ2VvbV9wb2ludCBjaG8gbWVkX2RmIMSR4buDIGNo4bqlbSA1IMSRaeG7g20gZ2nDoSB0cuG7iyBMSCB0cnVuZyBiw6xuaA0KDQpMxrB1IMO9IHLhurFuZyB0cuG7pWMgWCBsdcO0biBsw6AgTEgsIHkgbHXDtG4gbMOgIFByb3RvY29sIGNobyAzIGjDoG0gdHLDqm4NCg0KU2F1IMSRw7MsIGTDuW5nIGjDoG0gY29vcmRfZmxpcCgpIMSR4buDIHhvYXkgdHLhu6VjIDkwwrAsIG5oxrAgduG6rXkgdHLhu6VjIHggdsOgIHkgc+G6vSBob8OhbiBjaHV54buDbiBjaG8gbmhhdSwgaMOsbmgg4bqjbmggc+G6vSBiaeG7g3UgZGnhu4VuIHRoZW8gY2hp4buBdSBk4buNYyB0aGF5IHbDrCBjaGnhu4F1IG5nYW5nLg0KDQpIw6BtIHNjYWxlX3hfY29udGludW91cyBjaG8gcGjDqXAgdMO5eSBjaOG7iW5oIHRoYW5nIMSRbyB0cuG7pWMgWCwg4bufIMSRw6J5IGdp4bubaSBo4bqhbiBsaW1pdHMgdOG7qyAtMC41IMSR4bq/biA0MCwgbeG7kWMgdGhhbmcgxJFvIDUgxJFvbiB24buLDQoNCmBgYHtyLG1lc3NhZ2UgPSBGQUxTRSx3YXJuaW5nPUZBTFNFfQ0KbWVkX2RmID0gZGYgJT4lIA0KICBncm91cF9ieShQcm90b2NvbCklPiUNCiAgc3VtbWFyaXplX2F0KCdMSCcsIG1lYW4pDQoNCmdncGxvdCgpKw0KICBnZW9tX2RlbnNpdHlfcmlkZ2VzKGRhdGEgPSBkZiwNCiAgICAgICAgICAgICAgICAgICAgICBhZXMoeSA9IFByb3RvY29sLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgeCA9IExILA0KICAgICAgICAgICAgICAgICAgICAgICAgICBmaWxsID0gUHJvdG9jb2wpLA0KICAgICAgICAgICAgICAgICAgICAgIHNjYWxlID0gMC44LA0KICAgICAgICAgICAgICAgICAgICAgIGFscGhhID0gMC41LA0KICAgICAgICAgICAgICAgICAgICAgIHNob3cubGVnZW5kID0gRikrDQogIGdlb21fcGF0aChkYXRhID0gbWVkX2RmLA0KICAgICAgICAgICAgYWVzKHkgPSBQcm90b2NvbCwgeCA9IExILCBncm91cCA9IDEpLA0KICAgICAgICAgICAgY29sb3IgPSAnYmxhY2snLA0KICAgICAgICAgICAgc2l6ZSA9IDEsDQogICAgICAgICAgICBhbHBoYSA9IDAuNSkrDQogIGdlb21fcG9pbnQoZGF0YSA9IG1lZF9kZiwNCiAgICAgICAgICAgIGFlcyh5ID0gUHJvdG9jb2wsIHggPSBMSCksDQogICAgICAgICAgICBjb2xvciA9ICdibGFjaycsDQogICAgICAgICAgICBzaXplID0gMi41LA0KICAgICAgICAgICAgYWxwaGEgPSAwLjkpKw0KICBjb29yZF9mbGlwKCkrDQogIHNjYWxlX3hfY29udGludW91cyhsaW1pdHMgPSBjKC0wLjUsNDApLCANCiAgICAgICAgICAgICAgICAgICAgIGJyZWFrcyA9IHNlcSgwLDQwLGJ5ID0gNSkpKw0KICBsYWJzKHggPSAnTEggbGV2ZWwgKG1JVS9tTCknLCB5ID0gJ1Byb3RvY29scycpKw0KICB0aGVtZV9idygxMCkNCmBgYA0KDQpL4bq/dCBxdeG6oyB0aHUgxJHGsOG7o2MgbMOgIDUgYmnhu4N1IMSR4buTIG3huq10IMSR4buZIHBow6JuIGLhu5EgbmjGsCB0cm9uZyBow6xuaC4gQ8OhYyBiaeG7g3UgxJHhu5MgbsOgeSBjw7MgYuG6o24gY2jhuqV0IGzDoCDEkeG7kyB0aOG7iyBow6BtIHByb2JhYmlsaXR5IGRlc2l0eSAoUERGKSBj4bunYSBiaeG6v24gTEgsIHRy4bulYyBZIHTGsMahbmcg4bupbmcgduG7m2kgdGhhbmcgxJFvIGPhu6dhIExIKS4gQ2jDum5nIG3DtCB04bqjIMSR4bq3YyB0w61uaCBwaMOibiBwaOG7kWkgY+G7p2EgTEggdHJvbmcgNSBuaMOzbSBwaMOhYyDEkeG7kyAodHLhu6VjIFgpLiBDw6FjIGNo4bqlbSBtw6B1IMSRZW4gdMawxqFuZyDhu6luZyB24bubaSBnacOhIHRy4buLIHRydW5nIGLDrG5oIExIIOG7nyBt4buXaSBuaMOzbSwgxJHGsOG7o2MgbuG7kWkgduG7m2kgbmhhdSBi4bqxbmcgY8OhYyDEkW/huqFuIHRo4bqzbmcgdGjhu4MgaGnhu4duIGtodXluaCBoxrDhu5tuZyB0xINuZy9naeG6o20gY+G7p2EgZ2nDoSB0cuG7iyBuw6B5Lg0KDQpE4buxYSB2w6BvIGjDrG5oIOG6o25oIG7DoHksIHRhIGPDsyB0aOG7gyByw7p0IHJhIMSRxrDhu6NjIGhhaSB0aMO0bmcgdGluOiB0aOG7qSBuaOG6pXQsIGPDsyBz4buxIGtow6FjIGJp4buHdCByw7UgbsOpdCwga2jDtG5nIGNo4buJIHbhu4EgZ2nDoSB0cuG7iyB0cnVuZyBiw6xuaCBuaMawbmcgY8OybiB24buBIMSR4bq3YyB0w61uaCBwaMOibiBwaOG7kWkgY+G7p2EgTEgsIHRow60gZOG7pSDEkeG7mSBwaMOibiB0w6FuLCDEkeG7mSBs4buHY2guLi4gZ2nhu69hIDUgbmjDs20uIFRo4bupIGhhaSBsw6Ag4bufIG3hu5dpIHBow6JuIG5ow7NtLCBMSCBjaOG6r2MgY2jhuq9uIGtow7RuZyB0aOG7gyBwaMO5IGjhu6NwIHbhu5tpIHF1eSBsdeG6rXQgcGjDom4gcGjhu5FpIGNodeG6qW4gKEdhdXNzaWFuKS4gUGjDom4gcGjhu5FpIGPhu6dhIExIIGtow7RuZyDEkeG7kWkgeOG7qW5nLCB0aOG6rW0gY2jDrSBjw7MgxJHhu5kgbOG7h2NoIHLhuqV0IGNhbyDhu58gY8OhYyBuaMOzbSBHblJoX2FudCwgTWlsZCB2w6AgUFBPUy4NCg0KVGEgY8OzIHRo4buDIGzDoG0gdMawxqFuZyB04buxIMSR4buDIHThuqFvIHJhIGJp4buDdSDEkeG7lSBib3hwbG90LCBjaOG7iSBj4bqnbiB0aGF5IGjDoG0gZ2VvbV9kZW5zaXR5X3JpZGdlcyBi4bqxbmcgaMOgbSBnZW9tX2JveHBsb3QuIEzGsHUgw70gcuG6sW5nIHbhu5tpIGThuqFuZyBiaeG7g3UgxJHhu5MgYm94cGxvdCBt4bq3YyDEkeG7i25oIHRow6wgdHLhu6VjIHggY2jhu4kgUHJvdG9jb2wsIGPDsm4gdHLhu6VjIHkgY2hvIExILCB0YSB24bqrbiBkw7luZyBow6BtICBjb29yZF9mbGlwKCkgxJHhu4MgeG9heSB0cuG7pWMsIHRyw6xuaCBiw6B5IHRoZW8gcGjGsMahbmcgbmdhbmcsIHbDrCBraOG6oyBuxINuZyBzbyBzw6FuaCBi4bqxbmcgdGjhu4sgZ2nDoWMgc+G6vSB04buRdCBoxqFuIHRoZW8gY2hp4buBdSBuZ2FuZyBzbyB24bubaSBjaGnhu4F1IGThu41jLg0KDQpgYGB7cn0NCmdncGxvdCgpKw0KICBnZW9tX2JveHBsb3QoZGF0YSA9IGRmLA0KICAgICAgICAgICAgICAgICAgICAgIGFlcyh4ID0gUHJvdG9jb2wsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICB5ID0gTEgsDQogICAgICAgICAgICAgICAgICAgICAgICAgIGZpbGwgPSBQcm90b2NvbCksDQogICAgICAgICAgICAgICAgICAgICAgYWxwaGEgPSAwLjUsDQogICAgICAgICAgICAgICAgICAgICAgc2hvdy5sZWdlbmQgPSBGKSsNCiAgZ2VvbV9wb2ludChkYXRhID0gbWVkX2RmLA0KICAgICAgICAgICAgYWVzKHggPSBQcm90b2NvbCwgeSA9IExIKSwNCiAgICAgICAgICAgIGNvbG9yID0gJ2JsYWNrJywNCiAgICAgICAgICAgIHNoYXBlID0gMTgsDQogICAgICAgICAgICBzaXplID0gNSwNCiAgICAgICAgICAgIGFscGhhID0gMC45KSsNCiAgc2NhbGVfeV9jb250aW51b3VzKGxpbWl0cyA9IGMoLTAuNSw0MCksIA0KICAgICAgICAgICAgICAgICAgICAgYnJlYWtzID0gc2VxKDAsNDAsYnkgPSA1KSkrDQogIGxhYnMoeSA9ICdMSCBsZXZlbCAobUlVL21MKScsIHggPSAnUHJvdG9jb2xzJykrDQogIGNvb3JkX2ZsaXAoKSsNCiAgdGhlbWVfYncoMTApDQpgYGANCg0KDQpCaeG7g3UgxJHhu5MgYm94cGxvdCB0csOsbmggYsOgeSBjw6FjIHRy4buLIHPhu5EgdGjhu5FuZyBrw6ogbcO0IHThuqMgxJHhuqFpIGRp4buHbiBn4buTbSB0cnVuZyB24buLLCB04bupIHBow6JuIHbhu4sgKHBow6JuIHbhu4sgdGjhu6kgMjUsIDUwLCA3NSksIGPDoWMgY2jhuqVtIHRyw7JuIG5o4buPIGzDoCBuaOG7r25nIGdpw6EgdHLhu4sgdsaw4bujdCByYSBraOG7j2kga2hv4bqjbmcgdOG7qSBwaMOibiB24buLLiBDaOG6pW0gbcOgdSDEkWVuIGzhu5tuIG5o4bqldCBsw6AgduG7iyB0csOtIGPhu6dhIHRydW5nIGLDrG5oLiBUdXkga2jDtG5nIG3DtCB04bqjIGjDrG5oIOG6o25oIGPhu6dhIHRvw6BuIHRo4buDIG3huq10IMSR4buZIHBow6JuIHBo4buRaSwgYmnhu4N1IMSR4buTIEJveHBsb3QgduG7q2EgxJHhu6cgxJHhu4MgY2hvIHRhIHRo4bqleSBjw7luZyBuaOG7r25nIHRow7RuZyB0aW4gduG7gSBz4buxIHTGsMahbmcgcGjhuqNuIHLDtSBuw6l0IGdp4buvYSA1IHBow6JuIG5ow7NtLCB0YSBjxaluZyBj4bqjbSBuaOG6rW4gxJHGsOG7o2MgTEgga2jDtG5nIGPDsyBwaMOibiBwaOG7kWkgY2h14bqpbiAodHJ1bmcgduG7iyBraMO0bmcgdHLDuW5nIHbhu5tpIHRydW5nIGLDrG5oLCAyIMSR4bqndSBib3hwbG90IGtow7RuZyBjw6JuIHjhu6luZywgbmjhu69uZyBnacOhIHRy4buLIGNhbyBs4buHY2ggaOG6s24gduG7gSBjaGnhu4F1IGTGsMahbmcgY+G7p2EgdGhhbmcgxJFvIExILi4uKS4NCg0KIyBCxrDhu5tjIDI6IEJp4buHbiBsdeG6rW4gduG7gSB0w61uaCBwaMO5IGjhu6NwIGPhu6dhIHBow6JuIHBo4buRaSBHYW1tYQ0KDQpN4bulYyB0acOqdSBj4bunYSBjw7RuZyDEkW/huqFuIG7DoHkgbmjhurFtIGNo4bupbmcgbWluaCBy4bqxbmcgxJHhu4MgxrDhu5tjIGzGsOG7o25nIGdpw6EgdHLhu4sgTEggdGjDrCBwaMOibiBwaOG7kWkgR2FtbWEgcGjDuSBo4bujcCBoxqFuIHNvIHbhu5tpIHBow6JuIHBo4buRaSBHYXVzc2lhbi4gDQoNClbhu4EgbeG6t3QgbMO9IHRodXnhur90LCB0YSBob8OgbiB0b8OgbiBjw7MgdGjhu4MgY2jhu41uIHF1eSBsdeG6rXQgcGjDom4gcGjhu5FpIEdhbW1hIGNobyBiaeG6v24gTEggdsOsIG5o4buvbmcgbMO9IGRvIHNhdTogVGhhbmcgxJFvIExIIGPDsyBnacOhIHRy4buLID4wLCBwaMOibiBwaOG7kWkgY+G7p2EgTEggY8OzIGjDrG5oIOG6o25oIGLhuqV0IMSR4buRaSB44bupbmcgdsOgIGzhu4djaCBkxrDGoW5nLiANCg0KVHV5IG5oacOqbiwgdGEgY8OzIHRo4buDIGThu7FhIHbDoG8gY2jhu6luZyBj4bupIHjDoWMgdGjhu7FjIGjGoW4gYuG6sW5nIHF1eSB0csOsbmggc2F1Og0KDQoxKSDEkOG6p3UgdGnDqm4sIHRhIGTDuW5nIGjDoG0gZml0ZmlzdCDEkeG7gyBs4bqnbiBsxrDhu6N0IGto4bubcCBk4buvIGxp4buHdSBMSCB24bubaSAyIHF1eSBsdeG6rXQgcGjDom4gcGjhu5FpIEdhdXNzaWFuIHbDoCBHYW1tYSwgDQoNCjIpIHNhdSDEkcOzIMSR4buRaSBjaGnhur91IGjDrG5oIOG6o25oIMSR4buTIHRo4buLIGjDoG0gUERGIHbDoCBDREYgc28gduG7m2kgZOG7ryBsaeG7h3UgdGjhu7FjIG5naGnhu4dtLCBi4bqxbmcgMiBow6BtIGRlbnNjb21wIHbDoCBjZGZjb21wOg0KDQpgYGB7cixtZXNzYWdlID0gRkFMU0Usd2FybmluZz1GQUxTRX0NCmZpdF9nYW0gPSBmaXRkaXN0KGRmJExILCAnZ2FtbWEnLCBvcHRpbS5tZXRob2QgPSAnQkZHUycpDQpmaXRfZ2F1c3MgPSBmaXRkaXN0KGRmJExILCAnbm9ybScsIG9wdGltLm1ldGhvZCA9ICdCRkdTJykNCg0KZGVuc2NvbXAobGlzdChmaXRfZ2F1c3MsIGZpdF9nYW0pLCANCiAgICAgICAgIGxlZ2VuZHRleHQgPSBjKCJHYXVzc2lhbiIsICJHYW1tYSIpLA0KICAgICAgICAgcGxvdHN0eWxlID0gJ2dncGxvdCcsDQogICAgICAgICBmaXRjb2wgPSBjKCcjMDNhOWZjJywnI2ZjMDM2ZicpLA0KICAgICAgICAgZGVtcGNvbCA9IGMoJ2dyZXknKSwNCiAgICAgICAgIGZpdGx0eSA9IDEsDQogICAgICAgICBmaXRsd2QgPSAxKSsNCiAgbGFicyh4ID0gJ0xIIG9uIHRyaWdnZXIgZGF5IChtVUkvbUwpJykrIA0KICB0aGVtZV9idygpDQpgYGANCg0KQ2jDuiB0aMOtY2g6IEJp4buDdSDEkeG7kyB0csOqbiBjaG8gcGjDqXAgxJHhu5FpIGNoaeG6v3UgxJHhu5MgdGjhu4sgaMOgbSBQREYgKG3huq10IMSR4buZIHjDoWMgc3XhuqV0KSBj4bunYSAyIHBow6JuIHBo4buRaSBHYXVzc2lhbiAobcOgdSB4YW5oKSwgR2FtbWEgKG3DoHUgxJHhu48pIHbhu5tpIGjDrG5oIOG6o25oIGJp4buDdSDEkeG7kyB04bqnbiBzdeG6pXQgdHLDqm4gdGjhu7FjIG5naGnhu4dtIChtw6B1IHjDoW0pLg0KDQpgYGB7cixtZXNzYWdlID0gRkFMU0Usd2FybmluZz1GQUxTRX0NCmNkZmNvbXAobGlzdChmaXRfZ2F1c3MsIGZpdF9nYW0pLCANCiAgICAgICAgbGVnZW5kdGV4dCA9IGMoIkdhdXNzaWFuIiwgIkdhbW1hIiksDQogICAgICAgIHBsb3RzdHlsZSA9ICdnZ3Bsb3QnLA0KICAgICAgICBmaXRjb2wgPSBjKCcjMDNhOWZjJywnI2ZjMDM2ZicpLA0KICAgICAgICBmaXRsdHkgPSAxLA0KICAgICAgICBkYXRhY29sID0gJ2dyZXknKSsNCiAgbGFicyh4ID0gJ0xIIG9uIHRyaWdnZXIgZGF5IChtVUkvbUwpJykrIA0KICB0aGVtZV9idygpDQpgYGANCg0KQ2jDuiB0aMOtY2g6IEJp4buDdSDEkeG7kyB0csOqbiBjaG8gcGjDqXAgxJHhu5FpIGNoaeG6v3UgxJHhu5MgdGjhu4sgaMOgbSBDREYgKG3huq10IMSR4buZIHjDoWMgc3XhuqV0IHTDrWNoIGzFqXkpIGPhu6dhIDIgcGjDom4gcGjhu5FpIEdhdXNzaWFuIChtw6B1IHhhbmgpLCBHYW1tYSAobcOgdSDEkeG7jykgduG7m2kgaMOgbSBDREYgdHLDqm4gdGjhu7FjIG5naGnhu4dtIChtw6B1IHjDoW0pLg0KDQpD4bqjIDIga+G6v3QgcXXhuqMgbsOgeSBjaG8gdGjhuqV5IHBow6JuIHBo4buRaSBHYW1tYSB0aMOtY2ggaOG7o3AgaMahbiBzbyB24bubaSBwaMOibiBwaOG7kWkgR2F1c3NpYW4gxJHhu4MgxrDhu5tjIGzGsOG7o25nIGdpw6EgdHLhu4sgTEgsIHbDrCBow6BtIFBERiB2w6AgQ0RGIGPhu6dhIHBow6JuIHBo4buRaSBHYW1tYSBwaMO5IGjhu6NwIGjGoW4gduG7m2kgZOG7ryBsaeG7h3UgdGjhu7FjIG5naGnhu4dtLg0KDQpM4bujaSDDrWNoIGPhu6dhIHZp4buHYyBjaOG7jW4gcXV5IGx14bqtdCBwaMOibiBwaOG7kWkgxJHDum5nIGNobyBiaeG6v24ga+G6v3QgcXXhuqMgbcO0IGjDrG5oIGjhu5NpIHF1eSwgxJHDsyBsw6AgbsOzIGNobyBwaMOpcCDGsOG7m2MgbMaw4bujbmcgdsOgIHN1eSBsdeG6rW4gdGjhu5FuZyBrw6ogY2jDrW5oIHjDoWMgaMahbiwgcGjDuSBo4bujcCB0aOG7sWMgdOG6vyBoxqFuLiANCg0KIyBCxrDhu5tjIDM6IEThu7FuZyBtw7QgaMOsbmggaOG7k2kgcXV5IHbhu5tpIHBow6JuIHBo4buRaSBHYW1tYQ0KDQpOaMawIMSRw6MgdHLDrG5oIGLDoHkgdHJvbmcga+G6vyBob+G6oWNoIHBow6JuIHTDrWNoLCBjw7RuZyBj4bulIHRo4buRbmcga8OqIGNow61uaCBtw6AgdGEgc+G7rSBk4bulbmcgbMOgIG3hu5l0IG3DtCBow6xuaCBo4buTaSBxdXkgdHV54bq/biB0w61uaCB04buVbmcgcXXDoXQgKEdMTSkuIEjhu4cgdGjhu5FuZyBtw7QgaMOsbmggR0xNIGRvIDIgbmjDoCB0aOG7kW5nIGvDqiBKb2huIE5lbGRlciB2w6AgUm9iZXJ0IFdlZGRlcmJ1cm4gdGhp4bq/dCBs4bqtcCB2w6BvIG7Eg20gMTk3MiBjaG8gcGjDqXAgxrDhu5tjIGzGsOG7o25nIGdpw6EgdHLhu4sgY+G7p2EgYmnhur9uIGvhur90IHF14bqjIHRoZW8gbmhp4buBdSBxdXkgbHXhuq10IHBow6JuIHBo4buRaSDEkWEgZOG6oW5nLg0KDQpN4buZdCBjw6FjaCB04buVbmcgcXXDoXQsIG3DtCBow6xuaCBHTE0gxrDhu5tjIGzGsOG7o25nIGdpw6EgdHLhu4sgdHJ1bmcgYsOsbmggKHRoYW0gc+G7kSAkXG11JCkgY+G7p2EgYmnhur9uIG5n4bqrdSBuaGnDqm4gWSwgxJFp4buBdSBraeG7h24gaMOzYSB0aGVvIG3hu5l0IHThuq1wIGJp4bq/biBYIHRow7RuZyBxdWEgbeG7mXQgaMOgbSBsacOqbiBr4bq/dCAkZyQgdsOgIG3hu5l0IG3DtCBow6xuaCBo4buTaSB0dXnhur9uIHTDrW5oICRYXGJldGEkLg0KDQokJEUoWXxYKSA9IFxtdV97WX0gXHNpbSBnXnstMX0oWFxiZXRhKSQkDQpUcm9uZyBiw6BpIHRvw6FuIGhp4buHbiB0aOG7nWksIGJp4bq/biBr4bq/dCBxdeG6oyBZIGzDoCBnacOhIHRy4buLIGPhu6dhIExIIG5nw6B5IHRyaWdnZXIsIMSRxrDhu6NjIGdp4bqjIMSR4buLbmggbMOgIG3hu5l0IGJp4bq/biBuZ+G6q3Ugbmhpw6puIHRoZW8gcXV5IGx14bqtdCBwaMOibiBwaOG7kWkgR2FtbWEgduG7m2kgMiB0aGFtIHPhu5EgJFxtdSQgdsOgICRcc2lnbWEkDQoNCkjDoG0gbeG6rXQgxJHhu5kgcGjDom4gcGjhu5FpIChQREYpIGPhu6dhIHBow6JuIHBo4buRaSBHYW1tYSDEkcaw4bujYyB4w6FjIMSR4buLbmggbmjGsCBzYXU6DQoNCiQkZih5fFxtdSAsXHNpZ21hKT1cZnJhY3sxfXtcbGVmdCAoIFxzaWdtYSBeezJ9IFxtdSBccmlnaHQgKV57MS9cc2lnbWFeezJ9fX1cZnJhY3t5XntcZnJhY3sxfXtcc2lnbWEgXnsyfX0tMX1lXnsteS8oXHNpZ21hIF57Mn1cbXUgKX19e1xHYW1tYSBcbGVmdCAoIDEvXHNpZ21hXnsyfSBccmlnaHQgKX0kJA0KDQokXG11JCBjw7Mgw70gbmdoxKlhIG5oxrAgdHLhu41uZyB0w6JtIChjZW50cmFsIHRlbmRlbmN5KSBoYXkgZ2nDoSB0cuG7iyB0cnVuZyBiw6xuaCBj4bunYSBiaeG6v24ga+G6v3QgcXXhuqMgY+G6p24gxrDhu5tjIGzGsOG7o25nLCBjw7JuIGdpw6EgdHLhu4sgY+G7p2EgJFxzaWdtYSQgdMawxqFuZyDhu6luZyB24bubaSBt4bupYyDEkeG7mSBwaMOibiB0w6FuIHbDoCB4w6FjIMSR4buLbmggaMOsbmgg4bqjbmggY+G7p2EgaMOgbSBt4bqtdCDEkeG7mSBwaMOibiBwaOG7kWkuIE3hu5l0IGtoaSBjw7MgbXUgdsOgIHNpZ21hLCBjw7MgdGjhu4MgxrDhu5tjIHTDrW5oIMSR4buZIGzhu4djaCBjaHXhuqluICh2w6AgcGjGsMahbmcgc2FpKSBj4bunYSBMSCB0aGVvIGPDtG5nIHRo4bupYzoNCg0KJCRTRCA9IFxzaWdtYSAqIFxtdSQkDQoNClBow6JuIHTDrWNoIGjhu5NpIHF1eSBHYW1tYSBkw7luZyB0aMawIHZp4buHbiBnYW1sc3MuIMSQ4bqndSB0acOqbiwgdGEga2jhu5twIG3hu5l0IG3DtCBow6xuaCBo4buTaSBxdXkgR2FtbWEgY2jhu4kgY2jhu6lhIEludGVyY2VwdC4gTcO0IGjDrG5oIG7DoHkgdMawxqFuZyDhu6luZyB24bubaSB2aeG7h2MgxrDhu5tjIGzGsOG7o25nIGdpw6EgdHLhu4sgdHJ1bmcgYsOsbmggY+G7p2EgTEggY2hvIHRvw6BuIGLhu5kgbeG6q3Uga2jhuqNvIHPDoXQsIGtow7RuZyBwaMOibiBiaeG7h3QgbmjDs20gcGjDoWMgxJHhu5MgbsOgby4NCg0KVGEgZMO5bmcgaMOgbSBnYW1sc3MsIHbhu5tpIGPDtG5nIHRo4bupYyBmb3JtdWxhID0gTEggfiAxLCB2w6AgZmFtaWx5ID0gR0EgDQoNCmBgYHtyLCByZXN1bHRzPSdoaWRlJ30NCiMgTcO0IGjDrG5oIGNo4buJIGPDsyBpbnRlcmNlcHQNCg0KbTAgPSBnYW1sc3MoZm9ybXVsYSA9IExIIH4gMSwNCiAgICAgICAgICAgICBkYXRhPWRmLA0KICAgICAgICAgICAgIGZhbWlseSA9IEdBLA0KICAgICAgICAgICAgIHRyYWNlPUYsDQogICAgICAgICAgICAgcGFyYWxsZWw9Im11bHRpY29yZSIsDQogICAgICAgICAgICAgbmNwdXMgPSBuQykNCmBgYA0KDQpUYSBz4bq9IGTDuW5nIG3DtCBow6xuaCBjxqEgYuG6o24gbsOgeSDEkeG7gyBsw6BtIHRoYW0gY2hp4bq/dSB2w6Agc28gc8OhbmggduG7m2kgbcO0IGjDrG5oIGNow61uaCB0cm9uZyBwaMOibiB0w61jaCwgbmjhurFtIGNo4bupbmcgbWluaCBy4bqxbmcgeeG6v3UgdOG7kSBjaGlhIG5ow7NtIHRoZW8gcGjDoWMgxJHhu5MgdGjhu7FjIHPhu7EgY8OzIOG6o25oIGjGsOG7n25nIMSR4bq/biBnacOhIHRy4buLIHRydW5nIGLDrG5oIExILg0KDQpO4buZaSBkdW5nIG3DtCBow6xuaCBjxqEgYuG6o24gKGNo4buJIGNo4bupYSBJbnRlcmNlcHQsIMaw4bubYyBsxrDhu6NuZyBMSCB0cnVuZyBiw6xuaCBjaG8gdG/DoG4gcXXhuqduIHRo4buDKSBjw7MgbuG7mWkgZHVuZyBuaMawIHNhdToNCg0KYGBge3J9DQpzdW1tYXJ5KG0wKQ0KYGBgDQpnYW1sc3MgZOG7sW5nIDIgbcO0IGjDrG5oIGjhu5NpIHF1eSByacOqbmcgY2hvICRcbXUkIHbDoCAkXHNpZ21hJCwgY+G6oyAyIG3DtCBow6xuaCDEkeG7gXUgc+G7rSBk4bulbmcgaMOgbSBsacOqbiBr4bq/dCBsw6AgbG9nYXJpdCwgdsOsIHbhuq15IHRoYW5nIMSRbyBj4bunYSBjw6FjIGjhu4cgc+G7kSBo4buTaSBxdXkgaGnhu4duIHRo4budaSBsw6AgbG9nKExIKSwgxJHhu4MgxJHGsGEgduG7gSB0aGFuZyDEkW8gbmd1ecOqbiB0aOG7p3kgKG1VSS9tTCkgdGEgcGjhuqNpIGTDuW5nIGjDoG0gZXhwb25lbnRpYWwuDQoNCk3DtCBow6xuaCB0aOG7qSBuaOG6pXQgY2hvIGJp4bq/dCBnacOhIHRy4buLIHRydW5nIGLDrG5oIGPhu6dhIExIIGNobyB0b8OgbiBxdeG6p24gdGjhu4MgJFxtdSA9IGV4cCgxLjQ5MDQyKSA9IDQuNDM4OTU5JCANCg0KVGEgY8OzIHRo4buDIMSR4buRaSBjaGnhur91IHbhu5tpIGvhur90IHF14bqjIMaw4bubYyBsxrDhu6NuZyB04burIGThu68gbGnhu4d1IHRo4buxYyBuZ2hp4buHbSwgdsOgIHRo4bqleSBy4bqxbmcgSW50ZXJjZXB0IGPhu6dhIG3DtCBow6xuaCBtMCBjxqEgYuG6o24gcuG6pXQgcGjDuSBo4bujcCB24bubaSBnacOhIHRy4buLIHRydW5nIGLDrG5oIGPhu6dhIExIDQoNCmBgYHtyfQ0KbWVhbihkZiRMSCkNCmBgYA0KDQoyKSBUaeG6v3AgdGhlbywgdGEgZOG7sW5nIG3hu5l0IG3DtCBow6xuaCBraMOhYyB24bubaSAxIGJp4bq/biDEkeG7mWMgbOG6rXAgbMOgIHBow6JuIG5ow7NtIFByb3RvY29sLiBNw7QgaMOsbmggbsOgeSBjaG8gcGjDqXAgxrDhu5tjIGzGsOG7o25nIGdpw6EgdHLhu4sgdHJ1bmcgYsOsbmggTEgg4bufIDUgcGjDom4gbmjDs20gUHJvdG9jb2wga2jDoWMgbmhhdSwgaGF5IG7Ds2kgY8OhY2gga2jDoWMsIGto4bqjbyBzw6F0IGxpw6puIGjhu4cgZ2nhu69hIGJp4bq/biBwcm90b2NvbCB2w6AgZ2nDoSB0cuG7iyB0cnVuZyBiw6xuaCBMSCAoaGF5IGhp4buHdSDhu6luZyBj4bunYSBQcm90b2NvbCDEkeG7kWkgduG7m2kgZ2nDoSB0cuG7iyB0cnVuZyBiw6xuaCBj4bunYSBMSCkuDQoNCiQkXG11X3tMSH0gXHNpbSBcYmV0YV8wKkxvbmcgKyBcYmV0YV8xKkduUkhhbnQgKyBcYmV0YV8yKlNob3J0ICsgXGJldGFfMypNaWxkICsgXGJldGFfNCpQUE9TJCQNCg0KTcO0IGjDrG5oIG7DoHkgY8OzIGPDtG5nIHRo4bupYyBjaG8gdGhhbSBz4buRIGNow61uaCAodHJ1bmcgYsOsbmggY+G7p2EgcGjDom4gcGjhu5FpIEdhbW1hKSBsw6AgTEggfiBQcm90b2NvbDsgdsOgIGNobyB0aGFtIHPhu5Egc2lnbWE6IHNpZ21hLmZvcm11bGEgPSB+IFByb3RvY29sLg0KDQpgYGB7ciwgcmVzdWx0cz0naGlkZSd9DQojIE3DtCBow6xuaCBBTk9WQSAxIGJp4bq/bg0KDQptMSA9IGdhbWxzcyhmb3JtdWxhID0gTEggfiBQcm90b2NvbCwNCiAgICAgICAgICAgIHNpZ21hLmZvcm11bGEgPSB+IFByb3RvY29sLA0KICAgICAgICAgICAgZGF0YT1kZiwNCiAgICAgICAgICAgIGZhbWlseSA9IEdBLA0KICAgICAgICAgICAgdHJhY2U9RiwNCiAgICAgICAgICAgIHBhcmFsbGVsPSJtdWx0aWNvcmUiLA0KICAgICAgICAgICAgbmNwdXMgPSBuQykNCmBgYA0KDQrhu54gxJHDonkgdGEgxrDhu5tjIGzGsOG7o25nIMSR4buTbmcgdGjhu51pIGPhuqMgMiB0aGFtIHPhu5EgY+G7p2EgcGjDom4gcGjhu5FpIEdhbW1hIGzDoCBtdSAodHJ1bmcgYsOsbmgpIHbDoCBzaWdtYSAodGhhbSBz4buRIGtp4buDdSBow6xuaCkgdGhlbyBiaeG6v24gUHJvdG9jb2wuIFbDrCB0YSBraMO0bmcgY2jhu4kgcXVhbiB0w6JtIMSR4bq/biB2aeG7h2MgUHJvdG9jb2wgc+G6vSBsw6BtIHRoYXkgxJHhu5VpIGdpw6EgdHLhu4sgdHJ1bmcgYsOsbmggTEggKHRy4buNbmcgdMOibSBj4bunYSBwaMOibiBwaOG7kWkgR2FtbWEsIHRoYW0gc+G7kSAkXG11JCksIG3DoCBjw7JuIG114buRbiBiaeG6v3QgbGnhu4d1IHBow6JuIGxv4bqhaSBQcm90b2NvbCBjw7MgdMOhYyDEkeG7mW5nIGfDonkgdGhheSDEkeG7lWkgaMOsbmgg4bqjbmggKMSR4buZIHBow6JuIHTDoW4sIMSR4buZIGzhu4djaCkgY+G7p2EgcGjDom4gcGjhu5FpIEdhbW1hICh0aGFtIHPhu5EgJFxzaWdtYSQpIGhheSBraMO0bmcgPw0KDQpO4buZaSBkdW5nIGPhu6dhIG3DtCBow6xuaCBjaMOtbmggKMaw4bubYyBsxrDhu6NuZyBMSCB0aGVvIHBow6FjIMSR4buTKSBjw7MgbuG7mWkgZHVuZyBuaMawIHNhdQ0KDQpgYGB7cn0NCnN1bW1hcnkobTEpDQpgYGANCg0KTcO0IGjDrG5oIG0xIG7DoHkgY2hvIHBow6lwIMaw4bubYyBsxrDhu6NuZyBnacOhIHRy4buLIHRydW5nIGLDrG5oIGPhu6dhIExIIOG7nyBt4buXaSBwaMOibiBuaMOzbSBQcm90b2NvbC4gTMawdSDDvSBy4bqxbmcgcGjDoWMgxJHhu5MgRm9sX2xvbmcgxJHGsOG7o2MgY2jhu41uIGzDoG0gdGhhbSBjaGnhur91LCB2w6wgduG6rXkgSW50ZXJjZXB0IHRyb25nIG3DtCBow6xuaCB0xrDGoW5nIOG7qW5nIHbhu5tpIHRydW5nIGLDrG5oIGPhu6dhIGxvZyhMSCkgdHJvbmcgcGjDom4gbmjDs20gUHJvdG9jb2wgPSBGb2xfbG9uZy4NCg0KVGEgY8OzIHRo4buDIMSR4buRaSBjaGnhur91IHbhu5tpIGvhur90IHF14bqjIHRo4buRbmcga8OqIG3DtCB04bqjIGJhbiDEkeG6p3U6DQoNCmBgYHtyfQ0KZGYlPiUgDQogIGdyb3VwX2J5KFByb3RvY29sKSU+JQ0KICBzdW1tYXJpemUoJ21lYW4nID0gbWVhbihMSCkpICU+JSBrbml0cjo6a2FibGUoKQ0KYGBgDQoNCmV4cChJbnRlcmNlcHQpIHTGsMahbmcg4bupbmcgduG7m2kgdHJ1bmcgYsOsbmggY+G7p2EgTEggdHJvbmcgcGjDoWMgxJHhu5MgRm9sbGljdWxhciBsb25nIGFjdGluZywgxJHGoW4gduG7iyBtVUkvbUw6ICRleHAoMC4zMTIzNSkgPSAxLjM2NjYzMyQsIHRhIHRo4bqleSBnacOhIHRy4buLIG7DoHkgdMawxqFuZyDEkeG7k25nIHbhu5tpIGvhur90IHF14bqjIG1lYW4gTEgg4bufIG5ow7NtIEZvbF9Mb25nDQoNCkdpw6EgdHLhu4sgdHJ1bmcgYsOsbmggTEggbmjDs20gR25SSF9hbnQgxJHGsOG7o2MgdMOtbmggYuG6sW5nIDogZXhwKEludGVyY2VwdCArIFByb3RvY29sR25SSF9hbnQpOiAkZXhwKDAuMzEyMzUgKyAxLjczOTc4KSA9IDcuNzg0NDY0JCANCi4uLg0KDQpD4bupIHRo4bq/LCB0YSBjw7MgdGjhu4MgxrDhu5tjIGzGsOG7o25nIMSRxrDhu6NjIHRydW5nIGLDrG5oIGPhu6dhIGxvZyhMSCkgaG/hurdjIExIIOG7nyBj4bqjIDUgcGjDom4gbmjDs20uDQoNCiMgQsaw4bubYyA0OiBTdXkgZGnhu4VuIHRo4buRbmcga8OqDQoNCkThu7FhIHbDoG8gMiBtw7QgaMOsbmggdHV54bq/biB0w61uaCB04buVbmcgcXXDoXQgbsOgeSwgdGEgY8OzIHRo4buDIHRo4buxYyBoaeG7h24gbeG7mXQgc+G7kSBzdXkgZGnhu4VuIHRo4buRbmcga8OqIG5oxrAgc2F1Og0KDQrEkOG6p3UgdGnDqm4sIGtoaSBzbyBzw6FuaCBtw7QgaMOsbmggY2jhu4kgY2jhu6lhIGludGVyY2VwdCB2w6AgbcO0IGjDrG5oIGPDsyB0aMOqbSBiaeG6v24gUHJvdG9jb2wgYuG6sW5nIG3hu5l0IGtp4buDbSDEkeG7i25oIExpa2VsaWhvb2QgcmF0aW8sIHRhIGPDsyB0aOG7gyBiaeG6v3QgbGnhu4d1IHBow6JuIG5ow7NtIHBow6FjIMSR4buTIHRo4buxYyBz4buxIGPDsyBoaeG7h3Ug4bupbmcgw70gbmdoxKlhIMSR4buRaSB24bubaSBMSCBoYXkga2jDtG5nID8gKGPDonUgaOG7j2kgbsOgeSB0xrDGoW5nIHThu7EgduG7m2kga2nhu4NtIMSR4buLbmggRiB0aGVvIEZpc2hlciB0cm9uZyBwaMOibiB0w61jaCBwaMawxqFuZyBzYWkgY+G7lSDEkWnhu4NuKQ0KDQpgYGB7cn0NCkxSLnRlc3QobTAsbTEpDQpgYGANCg0KS+G6v3QgcXXhuqMgY2hvIHJhIG3hu5l0IGdpw6EgdHLhu4sgcCBj4buxYyBrw6wgdGjhuqVwLCBuaMawIHbhuq15IHRhIGPDsyB0aOG7gyBraOG6s25nIMSR4buLbmggcuG6sW5nIHBow6JuIG5ow7NtIHBow6FjIMSR4buTIHRo4buxYyBz4buxIGPDsyB0w6FjIMSR4buZbmcgbMOgbSB0aGF5IMSR4buVaSBMSC4NCg0KTsOzaSBjw6FjaCBraMOhYywgY8OzIHPhu7Ega2jDoWMgYmnhu4d0IMO9IG5naMSpYSB24buBIGdpw6EgdHLhu4sgTEggZ2nhu69hIGPDoWMgbmjDs20gcGjDoWMgxJHhu5Mga2jDoWMgbmhhdS4gVHV5IG5oacOqbiB0YSBjaMawYSB0aOG7gyBiaeG6v3QgcsO1IHPhu7Ega2jDoWMgYmnhu4d0IG7DoHkgbMOgIOG7nyBuaMOzbSBuw6BvIHNvIHbhu5tpIG5ow7NtIG7DoG8uIA0KDQpO4buZaSBkdW5nIGjhu4cgc+G7kSBo4buTaSBxdXkgdHJvbmcgbcO0IGjDrG5oIGNobyBwaMOpcCB0YSBraeG7g20gxJHhu4tuaCB24buBIHPhu7Ega2jDoWMgYmnhu4d0IGPhu6dhIExIIHRydW5nIGLDrG5oIOG7nyBwaMOhYyDEkeG7kyBQUE9TLCBHblJILCBMdXRlYWwgc2hvcnQgYWN0aW5nIHbDoCBNaWxkIChMZXRyb3pvbGUpIHNvIHbhu5tpIG5ow7NtIHRoYW0gY2hp4bq/dSBsw6AgRm9sbGljdWxhciBsb25nIGFjdGluZy4gDQoNCkThu7FhIHbDoG8ga+G6v3QgcXXhuqMgdGjDtCBj4bunYSBtw7QgaMOsbmgsIGPDsyB0aOG7gyB0aOG6pXkgdOG6pXQgY+G6oyBo4buHIHPhu5EgaOG7k2kgcXV5IMSR4buBdSA+MCB2w6AgY8OzIMO9IG5naMSpYSB0aOG7kW5nIGvDqiwgY2hvIHRo4bqleSBnacOhIHRy4buLIHRydW5nIGLDrG5oIExIIOG7nyA0IHBow6FjIMSR4buTIGPDsm4gbOG6oWkgxJHhu4F1IGNhbyBoxqFuIHNvIHbhu5tpIHBow6FjIMSR4buTIHRoYW0gY2hp4bq/dSAoTG9uZ2FjdGluZykuDQoNClR1eSBuaGnDqm4sIHRhIHPhur0ga2jDtG5nIHN1eSBkaeG7hW4gdHLhu7FjIHRp4bq/cCBk4buxYSB2w6BvIGjhu4cgc+G7kSBo4buTaSBxdXksIG3DoCB0aeG6v24gaMOgbmggbeG7mXQgcGjDom4gdMOtY2ggaOG6rXUga2nhu4NtIHbhu5tpIG7hu5lpIGR1bmcgc28gc8OhbmggYuG6r3QgY+G6t3AgdHXhuqduIHThu7EgZ2nhu69hIDUgbG/huqFpIHBow6FjIMSR4buTLiBUcm9uZyBraGkgbMOgbSB0aOG7kW5nIGvDqiBtw7QgdOG6oywgdGEgY8OzIGPhuqNtIG5o4bqtbiB24buBIGtodXluaCBoxrDhu5tuZyB0xINuZyBk4bqnbiBj4bunYSBnacOhIHRy4buLIExIIHRydW5nIGLDrG5oIHRoZW8gdGjhu6kgdOG7sTogRm9sX2xvbmcgPCBTaG9ydCA8ICBQUE9TIDwgR25SSF9hbnQgPCBNaWxkDQoNCuG7niDEkcOieSB0YSBz4bq9IGtp4buDbSB0cmEgbOG6oWkga+G6v3QgcXXhuqMgbsOgeS4NCg0KxJDhu4MgdGnhur9uIGjDoG5oIHBow6JuIHTDrWNoIGjhuq11IGtp4buDbSBuw6B5LCB0YSBkw7luZyBow6BtIGF2Z19jb21wYXJpc29ucyBj4bunYSB0aMawIHZp4buHbiBtYXJnaW5hbHNlZmZlY3QgduG7m2kgdMO5eSBjaOG7iW5oIHZhcmlhYmxlcyA9IGxpc3QoUHJvdG9jb2wgPSAncGFpcndpc2UnKSwgc2F1IMSRw7MgaGnhu4d1IGNo4buJbmggZ2nDoSB0cuG7iyBwIGLhurFuZyBow6BtIHAuYWRqdXN0IHRyb25nIFIsIHbhu5tpIHBoxrDGoW5nIHBow6FwIGhp4buHdSBjaOG7iW5oIEJvbmZlcnJvbmkuDQoNCkvhur90IHF14bqjIGPhu6dhIGjDoG0gbsOgeSBsw6AgbeG7mXQgYuG6o25nIG5oxrAgc2F1Og0KDQpgYGB7cn0NCnB3X2NvbXAgPSBhdmdfY29tcGFyaXNvbnMobTEsDQogICAgICAgICAgICAgICAgICAgICAgICAgIHdoYXQgPSAnbXUnLA0KICAgICAgICAgICAgICAgIHZhcmlhYmxlcyA9IGxpc3QoUHJvdG9jb2wgPSAncGFpcndpc2UnKSkNCg0KcHdfY29tcCRhZGpfcCA9IHAuYWRqdXN0KHB3X2NvbXAkcC52YWx1ZSwgbWV0aG9kID0gJ2JvbmZlcnJvbmknKQ0KDQpwd19jb21wICU+JSBkcGx5cjo6c2VsZWN0KC1jKDEsMiw2LDcpKSAlPiVrbml0cjo6a2FibGUoKQ0KYGBgDQoNCkPhu5l0IGNvbnRyYXN0IHRyw6xuaCBiw6B5IGPDoWMgY+G6t3Agc28gc8OhbmggbcOgIHRhIGPhuqduIGtp4buDbSBuZ2hp4buHbSwgdGjDrSBk4bulIEduUkhfYW50IC0gRm9sX2xvbmc7DQoNCkPhu5l0IGVzdGltYXRlIHRyw6xuaCBiw6B5IGdpw6EgdHLhu4sga2jDoWMgYmnhu4d0IHRydW5nIGLDrG5oIHbhu4EgTEggZ2nhu69hIDIgbmjDs20gcGjDoWMgxJHhu5MsIHRow60gZOG7pSBHblJIYW50IGNhbyBoxqFuIExvbmcgYWN0aW5nIDYuNDE3OCBtVUkvbUwuDQoNCjIgY+G7mXQgY29uZi5sb3cJY29uZi5oaWdoCXRyw6xuaCBiw6B5IG5nxrDhu6FuZyBkxrDhu5tpIHbDoCB0csOqbiBraG/huqNuZyB0aW4gY+G6rXkgOTUlIGPhu6dhIGtow6FjIGJp4buHdCwgS1RDOTUlIGPDsyDDvSBuZ2jEqWEgdGjhu5FuZyBrw6oga2hpIGtow7RuZyBjaOG7qWEgZ2nDoSB0cuG7iyAwLg0KDQpD4buZdCBhZGpfcCB0csOsbmggYsOgeSBnacOhIHRy4buLIHAgc2F1IGhp4buHdSBjaOG7iW5oIEJvbmZlcnJvbmksIA0KDQpL4bq/dCBxdeG6oyBjaG8gdGjhuqV5IHRvw6BuIGLhu5kgMTAgZ2nhuqMgdGh1eeG6v3QgxJHhu4F1IGPDsyDDvSBuZ2jEqWEgdGjhu5FuZyBrw6ouIE3hu5l0IGPDoWNoIHThu5VuZyBxdcOhdCwgZ2nDoSB0cuG7iyBMSCB0cnVuZyBiw6xuaCB0xINuZyBk4bqnbiB0aGVvIHRo4bupIHThu7EgNSBwaMOhYyDEkeG7kzoNCg0KRm9sX2xvbmcgPCBTaG9ydCA8IFBQT1MgPCBHblJIX2FudCA8IE1pbGQNCg0KTmjGsCB24bqteSBwaMOhYyDEkeG7kyBNaWxkIHN0aW11bGF0aW9uIHbhu5tpIExldHJvem9sZSBjw7MgTEggdHJ1bmcgYsOsbmggbmfDoHkgdHJpZ2dlciBjYW8gbmjhuqV0LCBwaMOhYyDEkeG7kyBGb2xsaWN1bGFyIGxvbmcgYWN0aW5nIGPDsyBMSCB0cnVuZyBiw6xuaCB0aOG6pXAgbmjhuqV0LCBwaMOhYyDEkeG7kyBQUE9TIOG7nyB24buLIHRyw60gdHJ1bmcgZ2lhbiwgduG7m2kgTEggY2FvIGjGoW4gc28gduG7m2kgMiBwaMOhYyDEkeG7kyBMb25nIHbDoCBTaG9ydC4NCg0KQ3Xhu5FpIGPDuW5nLCBr4bq/dCBxdeG6oyBj4bunYSBtw7QgaMOsbmggaOG7k2kgcXV5IGPDsyB0aOG7gyB0csOsbmggYsOgeSB0cuG7sWMgcXVhbiBuaMawIHNhdToNCg0KYGBge3J9DQptMSU+JWF1Z21lbnQoKSU+JW11dGF0ZShVTD0gLmZpdHRlZCArLnNlLmZpdCoxLjk2LA0KICAgICAgICAgICAgICAgICAgICAgTEw9LmZpdHRlZC0uc2UuZml0KjEuOTYpJT4lDQogIGdyb3VwX2J5KFByb3RvY29sKSU+JQ0KICBzdW1tYXJpemVfYXQoJy5maXR0ZWQnLCBtZWRpYW4pIC0+IG0xX3N1bQ0KDQphdWdtZW50KG0xKSU+JW11dGF0ZShVTD0gLmZpdHRlZCArLnNlLmZpdCoxLjk2LA0KICAgICAgICAgICAgICAgICAgICAgTEw9LmZpdHRlZC0uc2UuZml0KjEuOTYpJT4lDQogIGdncGxvdCgpKw0KICBnZW9tX2ppdHRlcihhZXMoeSA9IFByb3RvY29sLCANCiAgICAgICAgICAgICAgICAgIHggPSBMSCwNCiAgICAgICAgICAgICAgICAgIGNvbCA9IFByb3RvY29sKSwNCiAgICAgICAgICAgICAgc2NhbGUgPSAwLjgsDQogICAgICAgICAgICAgIGFscGhhID0gMC4zLA0KICAgICAgICAgICAgICB3aWR0aCA9IDAuMDEsDQogICAgICAgICAgICAgIHNob3cubGVnZW5kID0gRikrDQogIGdlb21fZGVuc2l0eV9yaWRnZXMoYWVzKHkgPSBQcm90b2NvbCwgDQogICAgICAgICAgICAgICAgICAgICAgICAgIHggPSBMSCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgZmlsbCA9IFByb3RvY29sKSwNCiAgICAgICAgICAgICAgICAgICAgICBzY2FsZSA9IDAuOCwNCiAgICAgICAgICAgICAgICAgICAgICBhbHBoYSA9IDAuMywNCiAgICAgICAgICAgICAgICAgICAgICBzaG93LmxlZ2VuZCA9IEYpKw0KICBnZW9tX2Vycm9yYmFyKGFlcyh5PVByb3RvY29sLCANCiAgICAgICAgICAgICAgICAgICAgeG1pbj1leHAoTEwpLCANCiAgICAgICAgICAgICAgICAgICAgeG1heD1leHAoVUwpKSwNCiAgICAgICAgICAgICAgICB3aWR0aD0wLjEsDQogICAgICAgICAgICAgICAgc2l6ZT0xKSArIA0KICBnZW9tX3BhdGgoZGF0YSA9IG0xX3N1bSwNCiAgICAgICAgICAgIGFlcyh5PVByb3RvY29sLCANCiAgICAgICAgICAgICAgICB4PWV4cCguZml0dGVkKSwNCiAgICAgICAgICAgICAgICBncm91cCA9IDEpKSsNCiAgZ2VvbV9wb2ludChhZXMoeT1Qcm90b2NvbCwgDQogICAgICAgICAgICAgICAgIHg9ZXhwKC5maXR0ZWQpKSwgDQogICAgICAgICAgICAgc2l6ZT0zKSsNCiAgc2NhbGVfeF9jb250aW51b3VzKGJyZWFrcyA9IHNlcSgwLDQwLGJ5ID0gNSkpKw0KICBsYWJzKHggPSAnTEggbGV2ZWwgb24gdHJpZ2dlciBkYXkgKG1JVS9tTCknLCB5ID0gJ1Byb3RvY29scycpKw0KICBjb29yZF9mbGlwKCkrDQogIHRoZW1lX2J3KCkgIA0KYGBgDQoNCiMgS+G6v3QgbHXhuq1uDQoNClRhIHRo4bqleSBtw7QgaMOsbmggR0xNIHbhu5tpIHBow6JuIHBo4buRaSBHYW1tYSBsw6AgbeG7mXQgZ2nhuqNpIHBow6FwIHBo4buVIHF1w6F0IHbDoCBoaeG7h3UgcXXhuqMgY2hvIGjhuqd1IGjhur90IG5o4buvbmcgdMOsbmggaHXhu5FuZyBuZ2hpw6puIGPhu6l1IHkgaOG7jWMgbMOibSBzw6BuZy4gTcO0IGjDrG5oIEdMTSBHYW1tYSBjw7MgdGjhu4MgdGhheSB0aOG6vyBob8OgbiB0b8OgbiBwaMOibiB0w61jaCBwaMawxqFuZyBzYWkgKEFOT1ZBKSBj4buVIMSRaeG7g24gdsOgIGPhuqMgbcO0IGjDrG5oIGjhu5NpIHF1eSB0dXnhur9uIHTDrW5oIHRow7RuZyB0aMaw4budbmcu