1 Đặt vấn đề

Có một số chuyên đề nghiên cứu đặc biệt trong ngành Y-Dược nhắm đến mục tiêu là khảo sát những đặc tính “động học”, thí dụ khả năng hấp thu/thải trừ một dược chất sau khi đưa vào cơ thể (Dược động học),sự chuyển biến nồng độ của một biomarker nội sinh trong luồng khí thở (volatile organic compound, VOC), hay sự phân bố của một chất khí trong phổi sau khi hít vào (khí động học), sự suy giảm một chức năng sinh lý do quá trình lão hóa …

Những bài toán này đều có đặc điểm chung là biến số kết quả được mô tả bởi một quy luật (hệ thống) động học phi tuyến tính phụ thuộc thời gian. Phương pháp hồi quy tuyến tính truyền thống không thể giải quyết được một hệ thống tương tự. Do đó, nghiên cứu sinh bắt buộc phải dùng những mô hình phi tuyến tính (non-linear models), thậm chí phức tạp hơn là mô hình phân cấp (hierarchical model) hoặc nhiều ngăn (compartimental model).

Trong bài thực hành hôm nay (thuộc Project Bayes for Vietnam), Nhi sẽ hướng dẫn các bạn dựng các mô hình phân cấp-phi tuyến tính theo trường phái Bayes, nhờ package “brms” của Paul Bürkner. Hai thí dụ minh họa sẽ được trình bày, một rất đơn giản dùng dữ liệu mô phỏng, một phức tạp hơn và có thực, liên quan đến mô hình Dược động học 1 ngăn (PK model).

2 Khởi động với một thí dụ đơn giản

Giả sử ta muốn ước lượng giá trị của một đại lượng y cho cá thể (i) tại một thời điểm ti bất kì trong khoảng thời gian T, ta cần một mô hình có dạng:

\[y_i \sim f(t_j ,\phi) + e_{ij} \quad , \quad 0\leq j \leq T\]

Trong đó Phi là một vector chứa các tham số hồi quy (parameters) của mô hình, còn e là sai số, j là số điểm khảo sát trong thí nghiệm, i là 1 cá thể được khảo sát (bệnh nhân, con vật). y là kết quả, được mô tả như biến số ngẫu nhiên và tuân theo quy luật phân bố giả định (thí dụ Gaussian). Mô hình được xác định bởi bản chất của hàm f với thời điểm tj là biến số (predictor).

Đặc điểm chung của các mô hình phi tuyến tính, đó là hàm f có thể chứa trong nó một hay nhiều hàm phi tuyến tính, thí dụ Exponential. Công thức mô hình với Phi và tj bị chi phối bởi hàm Exp này.

Trong thí dụ đơn giản nhất, giả định hàm f có nội dung như sau:

\[f = b_0 e^{-b1*t}\]

library(tidyverse)

my_theme <- function(base_size =8, base_family = "sans"){
  theme_bw(base_size = base_size, base_family = base_family) +
    theme(
      panel.grid.major = element_line(color = "gray"),
      panel.grid.minor = element_blank(),
      panel.background = element_rect(fill = NA),
      strip.background = element_rect(fill = "#3c0477", color = "#3c0477", size =0.5),
      strip.text = element_text(face = "bold", size = 8, color = "white"),
      legend.position = "bottom",
      legend.justification = "center",
      legend.background = element_blank()
    )
}

Ta sẽ mô phỏng một dữ liệu dựa theo hàm f đơn giản như trên, với giả định tham số b0=150 và b1=0.08 và thời gian T dài 60 giây

t<-c(1:60)
se=rnorm(60,2,0.5)
out <- rnorm(60,mean=150*exp(-0.08*t),se)

set.seed(1206)
df=data_frame(Time=as.numeric(t),Outcome=out)

head(df)%>%knitr::kable()
Time Outcome
1 142.67188
2 128.95137
3 114.52035
4 108.35965
5 100.55330
6 92.32339
ggplot(df,aes(Time,Outcome))+
  geom_smooth(col="red4",fill="red",alpha=0.5)+
  geom_point()+my_theme(10)

Hàm f này mô tả một sự suy giảm theo thời gian của Outcome, theo quy luật hàm exp(-b1*t).

Nhi sẽ dùng package brms để dựng một mô hình nhằm xác định phân bố hậu nghiệm của b0 và b1. Nhưng trước hết, Nhi phải cân nhắc về giả thuyết tiền định (prior) cho Outcome, b0 và b1.

Ta sẽ phác thảo mô hình trên giấy:

Đầu tiên ta giả định Outcome là 1 biến ngẫu nhiên có phân bố Gaussian, :

\[yi \sim N(\mu,\sigma)\] Sau đó ta phác thảo nội dung hàm f cho phép xác định tham số µ:

\[\mu_{j} \sim b0*exp(-b1*Time_j)\] Dựa vào biểu đồ, ta có thể phán đoán là b0 > 100 nhưng nhỏ hơn 200, do đó prior cho nó có thể là 1 phân bố Gaussian có trung bình = 150 và sd =50

Tương tự, ta dự đoán b1 có giá trị rất thấp, gần 0 nhưng không rõ là bao nhiêu, một prior gamma có thể thích hợp cho dự đoán này

curve(dnorm(x, 150,20), from = 0,300,main="b0 ~ N(150,20)",
      xlab="", ylab="", bty="n",col="red")

curve(dgamma(x,2,3), from = 0,5, main="b1~Gamma(2,3)",col="blue",
      xlab="", ylab="", bty="n")

library(brms)
library(rstan)

rstan_options(auto_write = TRUE)
options(mc.cores = parallel::detectCores())

prior1 <- c(prior(normal(150,20), nlpar="beta0"),
  prior(gamma(2,3), nlpar = "beta1",lb=0))

prior1
##             prior class coef group resp dpar nlpar     bound
## 1 normal(150, 20)     b                      beta0          
## 2     gamma(2, 3)     b                      beta1 <lower=0>

Sau khi thiết lập giả định về prior, ta viết công thức cho mô hình phi tuyến tính trong brms. Lưu ý là kể từ 2017, brms cho phép viết công thức (formula) theo nhiều phong cách khác nhau, trước và trong khi code cho hàm brm. Nhi thích chuẩn bị công thức Trước khi viết tùy chỉnh cho hàm brm, vì cách làm việc theo công đoạn này cho phép hạn chế sai sót và tổ chức tốt hơn quy trình.

Có một điều thú vị, đó là brms cho phép khai báo thêm những tham số / hằng số khác ngoài những biến có sẵn trong dữ liệu trong khi viết công thức, điều này dẫn đến một khả năng thích nghi hết sức uyển chuyển của mô hình Bayes, cho phép ta mở rộng mô hình với nhiều thành phần phong phú, để mô tả một giả thuyết (hàm) bất kì, chứ không chỉ giới hạn ở các biến từ dữ liệu.

Thí dụ ta có thể viết công thức :

“Outcome ~ beta0exp(-beta1Time)”

Để khai báo 2 tham số b0, b1. Sau đó ta đưa công thức này vào hàm bf,đồng thời khai báo thêm nội dung rằng b0 và b1 sẽ được khảo sát như hằng số (Intercept) trong mô hình, sau cùng lưu ý đổi tùy chỉnh nl=TRUE để khẳng định đây là mô hình phi tuyến tính.

Sau khi đóng công thức, ta khai báo dữ liệu là df, và prior là prior_mod.

Cuối cùng là chế độ lấy mẫu cho chuỗi MCMC, Nhi cho sampler chạy 4 chuỗi ngắn 4x1000 lượt sau 500 lượt khởi động.

Ta có thể cho thi hành mô hình fit1

fml1<- Outcome ~ beta0*(exp(-beta1*Time))

fit1 <- brm(bf(fml1,
               beta0+beta1~1,
               nl = TRUE),
            data = df,
            family = brmsfamily("gaussian", link_sigma = "log"),
            prior = prior1,
            control = list(adapt_delta = 0.90, max_treedepth=20),
          seed = 1234, iter = 1500,chains = 4)

Mô hình converge rất nhanh vì đơn giản, ta có thể tóm tắt phân bố hậu nghiệm của 2 hằng số beta0 và beta1 cũng như sigma của Outcome

summary(fit1)
##  Family: gaussian 
##   Links: mu = identity; sigma = identity 
## Formula: Outcome ~ beta0 * (exp(-beta1 * Time)) 
##          beta0 ~ 1
##          beta1 ~ 1
##    Data: df (Number of observations: 60) 
## Samples: 4 chains, each with iter = 1500; warmup = 750; thin = 1;
##          total post-warmup samples = 3000
## 
## Population-Level Effects: 
##                 Estimate Est.Error l-95% CI u-95% CI Eff.Sample Rhat
## beta0_Intercept   151.84      1.43   149.05   154.59       1201 1.00
## beta1_Intercept     0.08      0.00     0.08     0.08       1415 1.00
## 
## Family Specific Parameters: 
##       Estimate Est.Error l-95% CI u-95% CI Eff.Sample Rhat
## sigma     2.33      0.22     1.95     2.79       1845 1.00
## 
## Samples were drawn using sampling(NUTS). For each parameter, Eff.Sample 
## is a crude measure of effective sample size, and Rhat is the potential 
## scale reduction factor on split chains (at convergence, Rhat = 1).
plot(fit1, ask = FALSE,theme=my_theme(8))

Như ta thấy, mô hình đã xác định beta0 =150.98 (từ 148.41 đến 153.74), beta1=0.08 và sigma = 2.21 (1.85 đến 2.67), kết quả này rất phù hợp với các tham số ta đã dùng để mô phỏng dữ liệu.

theme_set(my_theme())

marginal_effects(fit1)

3 Một thí dụ phức tạp hơn: Mô hình PK 1 ngăn

Tiếp theo chúng ta sẽ thử sức với một trường hợp có độ khó cao hơn, đó là một nghiên cứu dược động học của Theophylline : Vào năm 1994, Boeckmann, Sheiner và Beal đã công bố dữ liệu này từ kết quả thực nghiệm của BS. Robert Upton. Thuốc Theophylline được dùng trên 12 người theo đường uống, sau đó nồng độ Theophilline trong máu được khảo sát liên tục qua 11 thời điểm trong 25 giờ sau đó. Dữ liệu này sau đó được Pinheiro, J. C.và Bates, D. M. sử dụng để minh họa cho mô hình phi tuyến tính có hiệu ứng ngẫu nhiên vào năm 2000 (Mixed-effects Models in S and S-PLUS, Springer).

Ta tải dữ liệu và bắt đầu khảo sát nó

dat<-read.csv("http://vincentarelbundock.github.io/Rdatasets/csv/datasets/Theoph.csv")


head(dat)%>%knitr::kable()
X Subject Wt Dose Time conc
1 1 79.6 4.02 0.00 0.74
2 1 79.6 4.02 0.25 2.84
3 1 79.6 4.02 0.57 6.57
4 1 79.6 4.02 1.12 10.50
5 1 79.6 4.02 2.02 9.66
6 1 79.6 4.02 3.82 8.58

Dữ liệu gồm 5 biến, Subject để định danh cá thể, Wt chỉ cân năng (kg),Dose là liều thuốc uống (mg/kg), Time chỉ thời điểm khảo sát và conc là biến số kết quả cần nghiên cứu có đơn vị mg/L.

Đầu tiên, chúng ta sẽ tính lại Liều thuốc thực sự được đưa vào cơ thể bằng cách nhân cho thể trọng:

Khảo sát trực quan cho thấy noèéng độ Theophylline thay đổi theo quy luật như sau qua 11 thời điểm:

library(RColorBrewer)

coledges=colorRampPalette(c("red","blue","purple"))
mycol=(coledges(12))

sum_df=dat%>%group_by(Time)%>%
  summarise_at("conc",median)

dat%>%ggplot()+
  geom_line(aes(x=Time,y=conc,col=factor(Subject)),alpha=0.5)+
  geom_smooth(data=sum_df,aes(x=sum_df$Time,y=sum_df$conc),col="red4",fill="red",size=1,alpha=0.2)+scale_color_manual(values=mycol)

Để đơn giản hóa, chúng ta sẽ sử dụng mô hình dược động học 1 ngăn (One Compartment PK model), theo đó Lượng thuốc ban đầu (Dose) được giả định phân bố ngay tức khắc và đồng đều trong cơ thể - được xem như 1 ngăn chứa duy nhất với thể tích V (L), Nồng độ thuốc trong máu ở bất cứ thời điểm nào sau đó sẽ được xác định bởi 2 hằng số hấp thu (ka) và thải trừ (ke).

Giả định conc = biến ngẫu nhiên Y được xác định bằng phân bố Gaussian với 2 tham số Mu và sigma.

\[Y_{ij} \sim N(\mu_{ij},\sigma_{ij})\]

Cho mỗi cá thể i và thời điểm tj, giá trị Muij được ước lượng bằng 1 hàm f có nội dung :

\[\mu_{ij} \sim f(t_{j},\phi) = \frac{Dose*k_{a}}{V*(k_{a}-k_{e})}\left ( e^{-k_{e}*t_{j}} - e^{-k_{a}*t_{j}} \right )\]

Như vậy mục tiêu của chúng ta là xây dựng một mô hình cho phép xác định đồng thời giá trị của 2 hằng số ka và ke.

Nhi sẽ làm việc này theo từng bước:

  1. Đầu tiên, Nhi đưa hàm f vào công thức mô hình phi tuyến tính. Hàm f này có nội dung gần giống như trên, tuy nhiên với giả định là Nhi chỉ muốn tính ka và ke (2 hằng số hấp thu/thải trừ) nhưng không quan tâm đến Volume, Nhi không nhân liều thuốc với cân nặng nhưng giữ nguyên giá trị Dose và nhân cho 1 biến tên là InV = 1/V.
fml <- conc~Dose*ka*InV/(ka-ke)*(exp(-ke*Time)-exp(-ka*Time))

fml
## conc ~ Dose * ka * InV/(ka - ke) * (exp(-ke * Time) - exp(-ka * 
##     Time))
  1. Sau đó, Nhi đưa công thức này vào mô hình qua hàm bf, sau đó phát triển thêm 3 công thức nữa để ước tính InV ~ 1|Subject, ka và ke đều là hằng số (Intercept).

  2. Sau đó Nhi áp dụng prior cho conc là N(Mu,sigma) với link function = log cho sigma và Identity cho Mu.

  3. Tiếp theo, là bước quan trọng nhất, Nhi khai báo 3 prior cho 3 tham số InV, ka và ke. Có thể dự đoán là giá trị của chúng đều rất thấp, có thể nằm giữa 0 và 2; nên Nhi chọn prior là Gamma()

curve(dgamma(x, 1, 5), from = 0, 5, main="Gamma priors",
      xlab="", ylab="", bty="n",col="red")
curve(dgamma(x, 2,5), add = TRUE,col="blue")
curve(dgamma(x, 3,5), add=TRUE,col="green")
text(x = c(0.1,0.2,0.6), y = c(4,2,1.2), labels = c("InV", "ke", "ka"),col=c("red","blue","green"))

  1. Sau cùng, Ta mô tả chế độ lấy mẫu cho sampler, bao gồm 2 chuỗi, mỗi chuỗi 1000 lượt
dat$Subject=as.factor(dat$Subject)

bpkmod <- brm(bf(fml,
             InV ~ 1|Subject,
             ka + ke ~ 1,
             nl = TRUE),
          data = dat, 
          family = brmsfamily("gaussian", link_sigma = "log"),
          prior = c(prior(gamma(1,5), nlpar = "InV", lb=0),
                    prior(gamma(3,5), nlpar = "ka", lb=0),
                    prior(gamma(2,5), nlpar = "ke", lb=0)),
          control = list(adapt_delta = 0.999, max_treedepth=30),
          seed = 1206, iter = 2000,warmup = 1000,chains = 2)

Mô hình converge sau vài phút và đây là kết quả phân bố hậu nghiệm và chuỗi MCMC của các hằng số trong mô hình. Ta quan tâm đến ka và ke. Giá trị của 2 hằng số này lần lượt là 1.5 và 0.08

plot(bpkmod, N = 5, ask = FALSE,theme=my_theme(8))

summary(bpkmod)
##  Family: gaussian 
##   Links: mu = identity; sigma = identity 
## Formula: conc ~ Dose * ka * InV/(ka - ke) * (exp(-ke * Time) - exp(-ka * Time)) 
##          InV ~ 1 | Subject
##          ka ~ 1
##          ke ~ 1
##    Data: dat (Number of observations: 132) 
## Samples: 2 chains, each with iter = 2000; warmup = 1000; thin = 1;
##          total post-warmup samples = 2000
## 
## Group-Level Effects: 
## ~Subject (Number of levels: 12) 
##                   Estimate Est.Error l-95% CI u-95% CI Eff.Sample Rhat
## sd(InV_Intercept)     0.49      0.15     0.28     0.86        178 1.00
## 
## Population-Level Effects: 
##               Estimate Est.Error l-95% CI u-95% CI Eff.Sample Rhat
## InV_Intercept     1.97      0.18     1.56     2.28        281 1.00
## ka_Intercept      1.52      0.13     1.28     1.80       1028 1.00
## ke_Intercept      0.08      0.01     0.07     0.09       1126 1.00
## 
## Family Specific Parameters: 
##       Estimate Est.Error l-95% CI u-95% CI Eff.Sample Rhat
## sigma     1.16      0.08     1.02     1.31       1097 1.00
## 
## Samples were drawn using sampling(NUTS). For each parameter, Eff.Sample 
## is a crude measure of effective sample size, and Rhat is the potential 
## scale reduction factor on split chains (at convergence, Rhat = 1).

Kết quả ka, ke này tương đương với cách làm cổ điển như trong website sau đây: http://sia.webpopix.org/nlme.html

Ta có thể kiểm tra xem mô hình có ước tính chính xác diễn tiến của biến conc theo thời gian hay không ? Trong hình dưới đây: Màu xanh chỉ giá trị thực tế, màu đỏ là giá trị mô hình ước tính.

pred_df=predict(bpkmod)%>%as.data.frame()

dat$Pred=pred_df$Estimate

post_df=bpkmod$fit%>%as.matrix()

ggplot(data=dat)+
  geom_point(aes(x=Time,y=conc),col="pink")+
  geom_line(aes(x=Time,y=conc),col="blue",linetype=2)+
  geom_line(aes(x=Time,y=Pred),col="red")+
  facet_wrap(~Subject,ncol=3,scales="free")+my_theme()

4 Kết luận

Package brms hỗ trợ thực hiện mô hình phi tuyến tính, phân cấp với cấu trúc mô hình có thể mở rộng thoải mái. Trong bài này chúng ta chỉ mới sử dụng mô hình 1 ngăn, và chỉ quan tâm đến hằng số; tuy nhiên các mô hình nhiều ngăn cũng có thể được giải quyết theo cùng cách làm này. Ta cũng có thể đưa thêm nhiều tham số vào mô hình và chúng có liên hệ với dữ liệu: thí dụ sử dụng cân nặng của bệnh nhân trong các mô hình dược động học, hay các chỉ số nhân trắc về hệ hô hấp cho các mô hình khí động học.

Vấn đề khó khăn nhất khi sử dụng phương pháp Bayes là xác định prior phù hợp. Với mô hình phi tuyến tính thì prior có vai trò cực kì quan trọng, như các bạn đã thấy trong bài.

Chúc các bạn thực hành vui. Hẹn gặp lại trong một bài khác về Bayes.

LS0tDQp0aXRsZTogIk3DtCBow6xuaCBwaMOibiBj4bqlcCBwaGkgdHV54bq/biB0w61uaCBCQVlFUyINCmF1dGhvcjogIkzDqiBOZ+G7jWMgS2jhuqMgTmhpIg0KZGF0ZTogIjEyIFRow6FuZyA2IDIwMTgiDQpvdXRwdXQ6DQogIGh0bWxfZG9jdW1lbnQ6IA0KICAgIGNvZGVfZG93bmxvYWQ6IHRydWUNCiAgICBjb2RlX2ZvbGRpbmc6IGhpZGUNCiAgICBudW1iZXJfc2VjdGlvbnM6IHllcw0KICAgIHRoZW1lOiAiZGVmYXVsdCINCiAgICB0b2M6IFRSVUUNCiAgICB0b2NfZmxvYXQ6IFRSVUUNCi0tLQ0KDQpgYGB7ciBzZXR1cCxpbmNsdWRlPUZBTFNFfQ0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGVjaG8gPSBUUlVFKQ0KYGBgDQoNCiFbXShQS21vZGVsMS5wbmcpDQoNCiMgxJDhurd0IHbhuqVuIMSR4buBDQoNCkPDsyBt4buZdCBz4buRIGNodXnDqm4gxJHhu4EgbmdoacOqbiBj4bupdSDEkeG6t2MgYmnhu4d0IHRyb25nIG5nw6BuaCBZLUTGsOG7o2Mgbmjhuq9tIMSR4bq/biBt4bulYyB0acOqdSBsw6Aga2jhuqNvIHPDoXQgbmjhu69uZyDEkeG6t2MgdMOtbmggIsSR4buZbmcgaOG7jWMiLCB0aMOtIGThu6Uga2jhuqMgbsSDbmcgaOG6pXAgdGh1L3Ro4bqjaSB0cuG7qyBt4buZdCBkxrDhu6NjIGNo4bqldCBzYXUga2hpIMSRxrBhIHbDoG8gY8ahIHRo4buDIChExrDhu6NjIMSR4buZbmcgaOG7jWMpLHPhu7EgY2h1eeG7g24gYmnhur9uIG7hu5NuZyDEkeG7mSBj4bunYSBt4buZdCBiaW9tYXJrZXIgIG7hu5lpIHNpbmggdHJvbmcgbHXhu5NuZyBraMOtIHRo4bufICh2b2xhdGlsZSBvcmdhbmljIGNvbXBvdW5kLCBWT0MpLCBoYXkgc+G7sSBwaMOibiBi4buRIGPhu6dhIG3hu5l0IGNo4bqldCBraMOtIHRyb25nIHBo4buVaSBzYXUga2hpIGjDrXQgdsOgbyAoa2jDrSDEkeG7mW5nIGjhu41jKSwgc+G7sSBzdXkgZ2nhuqNtIG3hu5l0IGNo4bupYyBuxINuZyBzaW5oIGzDvSBkbyBxdcOhIHRyw6xuaCBsw6NvIGjDs2EgLi4uIA0KDQpOaOG7r25nIGLDoGkgdG/DoW4gbsOgeSDEkeG7gXUgY8OzIMSR4bq3YyDEkWnhu4NtIGNodW5nIGzDoCBiaeG6v24gc+G7kSBr4bq/dCBxdeG6oyDEkcaw4bujYyBtw7QgdOG6oyBi4bufaSBt4buZdCBxdXkgbHXhuq10ICho4buHIHRo4buRbmcpIMSR4buZbmcgaOG7jWMgcGhpIHR1eeG6v24gdMOtbmggcGjhu6UgdGh14buZYyB0aOG7nWkgZ2lhbi4gUGjGsMahbmcgcGjDoXAgaOG7k2kgcXV5IHR1eeG6v24gdMOtbmggdHJ1eeG7gW4gdGjhu5FuZyBraMO0bmcgdGjhu4MgZ2nhuqNpIHF1eeG6v3QgxJHGsOG7o2MgbeG7mXQgaOG7hyB0aOG7kW5nIHTGsMahbmcgdOG7sS4gRG8gxJHDsywgbmdoacOqbiBj4bupdSBzaW5oIGLhuq90IGJ14buZYyBwaOG6o2kgZMO5bmcgbmjhu69uZyBtw7QgaMOsbmggcGhpIHR1eeG6v24gdMOtbmggKG5vbi1saW5lYXIgbW9kZWxzKSwgdGjhuq1tIGNow60gcGjhu6ljIHThuqFwIGjGoW4gbMOgIG3DtCBow6xuaCBwaMOibiBj4bqlcCAoaGllcmFyY2hpY2FsIG1vZGVsKSBob+G6t2Mgbmhp4buBdSBuZ8SDbiAoY29tcGFydGltZW50YWwgbW9kZWwpLg0KDQpUcm9uZyBiw6BpIHRo4buxYyBow6BuaCBow7RtIG5heSAodGh14buZYyBQcm9qZWN0IEJheWVzIGZvciBWaWV0bmFtKSwgTmhpIHPhur0gaMaw4bubbmcgZOG6q24gY8OhYyBi4bqhbiBk4buxbmcgY8OhYyBtw7QgaMOsbmggcGjDom4gY+G6pXAtcGhpIHR1eeG6v24gdMOtbmggdGhlbyB0csaw4budbmcgcGjDoWkgQmF5ZXMsIG5o4budIHBhY2thZ2UgImJybXMiIGPhu6dhIFBhdWwgQsO8cmtuZXIuIEhhaSB0aMOtIGThu6UgbWluaCBo4buNYSBz4bq9IMSRxrDhu6NjIHRyw6xuaCBiw6B5LCBt4buZdCBy4bqldCDEkcahbiBnaeG6o24gZMO5bmcgZOG7ryBsaeG7h3UgbcO0IHBo4buPbmcsIG3hu5l0IHBo4bupYyB04bqhcCBoxqFuIHbDoCBjw7MgdGjhu7FjLCBsacOqbiBxdWFuIMSR4bq/biBtw7QgaMOsbmggRMaw4bujYyDEkeG7mW5nIGjhu41jIDEgbmfEg24gKFBLIG1vZGVsKS4NCg0KIyBLaOG7n2kgxJHhu5luZyB24bubaSBt4buZdCB0aMOtIGThu6UgxJHGoW4gZ2nhuqNuDQoNCkdp4bqjIHPhu60gdGEgbXXhu5FuIMaw4bubYyBsxrDhu6NuZyBnacOhIHRy4buLIGPhu6dhIG3hu5l0IMSR4bqhaSBsxrDhu6NuZyB5IGNobyBjw6EgdGjhu4MgKGkpIHThuqFpIG3hu5l0IHRo4budaSDEkWnhu4NtIHRpIGLhuqV0IGvDrCB0cm9uZyBraG/huqNuZyB0aOG7nWkgZ2lhbiBULCB0YSBj4bqnbiBt4buZdCBtw7QgaMOsbmggY8OzIGThuqFuZzogDQoNCg0KJCR5X2kgXHNpbSBmKHRfaiAsXHBoaSkgKyBlX3tpan0gXHF1YWQgLCBccXVhZCAwXGxlcSBqIFxsZXEgVCQkDQoNClRyb25nIMSRw7MgUGhpIGzDoCBt4buZdCB2ZWN0b3IgY2jhu6lhIGPDoWMgdGhhbSBz4buRIGjhu5NpIHF1eSAocGFyYW1ldGVycykgY+G7p2EgbcO0IGjDrG5oLCBjw7JuIGUgbMOgIHNhaSBz4buRLCBqIGzDoCBz4buRIMSRaeG7g20ga2jhuqNvIHPDoXQgdHJvbmcgdGjDrSBuZ2hp4buHbSwgaSBsw6AgMSBjw6EgdGjhu4MgxJHGsOG7o2Mga2jhuqNvIHPDoXQgKGLhu4duaCBuaMOibiwgY29uIHbhuq10KS4geSBsw6Aga+G6v3QgcXXhuqMsIMSRxrDhu6NjIG3DtCB04bqjIG5oxrAgYmnhur9uIHPhu5Egbmfhuqt1IG5oacOqbiB2w6AgdHXDom4gdGhlbyBxdXkgbHXhuq10IHBow6JuIGLhu5EgIGdp4bqjIMSR4buLbmggKHRow60gZOG7pSBHYXVzc2lhbikuIE3DtCBow6xuaCDEkcaw4bujYyB4w6FjIMSR4buLbmggYuG7n2kgYuG6o24gY2jhuqV0IGPhu6dhIGjDoG0gZiB24bubaSB0aOG7nWkgxJFp4buDbSB0aiBsw6AgYmnhur9uIHPhu5EgKHByZWRpY3RvcikuDQoNCsSQ4bq3YyDEkWnhu4NtIGNodW5nIGPhu6dhIGPDoWMgbcO0IGjDrG5oIHBoaSB0dXnhur9uIHTDrW5oLCDEkcOzIGzDoCBow6BtIGYgY8OzIHRo4buDIGNo4bupYSB0cm9uZyBuw7MgbeG7mXQgaGF5IG5oaeG7gXUgaMOgbSBwaGkgdHV54bq/biB0w61uaCwgdGjDrSBk4bulIEV4cG9uZW50aWFsLiBDw7RuZyB0aOG7qWMgbcO0IGjDrG5oIHbhu5tpIFBoaSB2w6AgdGogYuG7iyBjaGkgcGjhu5FpIGLhu59pIGjDoG0gRXhwIG7DoHkuDQoNClRyb25nIHRow60gZOG7pSDEkcahbiBnaeG6o24gbmjhuqV0LCBnaeG6oyDEkeG7i25oIGjDoG0gZiBjw7MgbuG7mWkgZHVuZyBuaMawIHNhdToNCg0KJCRmID0gYl8wIGVeey1iMSp0fSQkDQoNCmBgYHtyLG1lc3NhZ2UgPSBGQUxTRSx3YXJuaW5nPUZBTFNFfQ0KbGlicmFyeSh0aWR5dmVyc2UpDQoNCm15X3RoZW1lIDwtIGZ1bmN0aW9uKGJhc2Vfc2l6ZSA9OCwgYmFzZV9mYW1pbHkgPSAic2FucyIpew0KICB0aGVtZV9idyhiYXNlX3NpemUgPSBiYXNlX3NpemUsIGJhc2VfZmFtaWx5ID0gYmFzZV9mYW1pbHkpICsNCiAgICB0aGVtZSgNCiAgICAgIHBhbmVsLmdyaWQubWFqb3IgPSBlbGVtZW50X2xpbmUoY29sb3IgPSAiZ3JheSIpLA0KICAgICAgcGFuZWwuZ3JpZC5taW5vciA9IGVsZW1lbnRfYmxhbmsoKSwNCiAgICAgIHBhbmVsLmJhY2tncm91bmQgPSBlbGVtZW50X3JlY3QoZmlsbCA9IE5BKSwNCiAgICAgIHN0cmlwLmJhY2tncm91bmQgPSBlbGVtZW50X3JlY3QoZmlsbCA9ICIjM2MwNDc3IiwgY29sb3IgPSAiIzNjMDQ3NyIsIHNpemUgPTAuNSksDQogICAgICBzdHJpcC50ZXh0ID0gZWxlbWVudF90ZXh0KGZhY2UgPSAiYm9sZCIsIHNpemUgPSA4LCBjb2xvciA9ICJ3aGl0ZSIpLA0KICAgICAgbGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIsDQogICAgICBsZWdlbmQuanVzdGlmaWNhdGlvbiA9ICJjZW50ZXIiLA0KICAgICAgbGVnZW5kLmJhY2tncm91bmQgPSBlbGVtZW50X2JsYW5rKCkNCiAgICApDQp9DQpgYGANCg0KVGEgc+G6vSBtw7QgcGjhu49uZyBt4buZdCBk4buvIGxp4buHdSBk4buxYSB0aGVvIGjDoG0gZiDEkcahbiBnaeG6o24gbmjGsCB0csOqbiwgduG7m2kgZ2nhuqMgxJHhu4tuaCB0aGFtIHPhu5EgYjA9MTUwIHbDoCBiMT0wLjA4IHbDoCB0aOG7nWkgZ2lhbiBUIGTDoGkgNjAgZ2nDonkNCg0KYGBge3IsbWVzc2FnZSA9IEZBTFNFLHdhcm5pbmc9RkFMU0V9DQoNCnQ8LWMoMTo2MCkNCnNlPXJub3JtKDYwLDIsMC41KQ0Kb3V0IDwtIHJub3JtKDYwLG1lYW49MTUwKmV4cCgtMC4wOCp0KSxzZSkNCg0Kc2V0LnNlZWQoMTIwNikNCmRmPWRhdGFfZnJhbWUoVGltZT1hcy5udW1lcmljKHQpLE91dGNvbWU9b3V0KQ0KDQpoZWFkKGRmKSU+JWtuaXRyOjprYWJsZSgpDQpgYGANCg0KYGBge3IsbWVzc2FnZSA9IEZBTFNFLHdhcm5pbmc9RkFMU0V9DQpnZ3Bsb3QoZGYsYWVzKFRpbWUsT3V0Y29tZSkpKw0KICBnZW9tX3Ntb290aChjb2w9InJlZDQiLGZpbGw9InJlZCIsYWxwaGE9MC41KSsNCiAgZ2VvbV9wb2ludCgpK215X3RoZW1lKDEwKQ0KYGBgDQoNCkjDoG0gZiBuw6B5IG3DtCB04bqjIG3hu5l0IHPhu7Egc3V5IGdp4bqjbSB0aGVvIHRo4budaSBnaWFuIGPhu6dhIE91dGNvbWUsIHRoZW8gcXV5IGx14bqtdCBow6BtIGV4cCgtYjEqdCkuDQoNCk5oaSBz4bq9IGTDuW5nIHBhY2thZ2UgYnJtcyDEkeG7gyBk4buxbmcgbeG7mXQgbcO0IGjDrG5oIG5o4bqxbSB4w6FjIMSR4buLbmggcGjDom4gYuG7kSBo4bqtdSBuZ2hp4buHbSBj4bunYSBiMCB2w6AgYjEuIE5oxrBuZyB0csaw4bubYyBo4bq/dCwgTmhpIHBo4bqjaSBjw6JuIG5o4bqvYyB24buBIGdp4bqjIHRodXnhur90IHRp4buBbiDEkeG7i25oIChwcmlvcikgY2hvIE91dGNvbWUsIGIwIHbDoCBiMS4NCg0KVGEgc+G6vSBwaMOhYyB0aOG6o28gbcO0IGjDrG5oIHRyw6puIGdp4bqleTogDQoNCsSQ4bqndSB0acOqbiB0YSBnaeG6oyDEkeG7i25oIE91dGNvbWUgbMOgIDEgYmnhur9uIG5n4bqrdSBuaGnDqm4gY8OzIHBow6JuIGLhu5EgR2F1c3NpYW4sIDoNCg0KJCR5aSBcc2ltIE4oXG11LFxzaWdtYSkkJA0KU2F1IMSRw7MgdGEgcGjDoWMgdGjhuqNvIG7hu5lpIGR1bmcgaMOgbSBmIGNobyBwaMOpcCB4w6FjIMSR4buLbmggdGhhbSBz4buRIMK1Og0KDQoNCiQkXG11X3tqfSBcc2ltIGIwKmV4cCgtYjEqVGltZV9qKSQkDQpE4buxYSB2w6BvIGJp4buDdSDEkeG7kywgdGEgY8OzIHRo4buDIHBow6FuIMSRb8OhbiBsw6AgYjAgPiAxMDAgbmjGsG5nIG5o4buPIGjGoW4gMjAwLCBkbyDEkcOzIHByaW9yIGNobyBuw7MgY8OzIHRo4buDIGzDoCAxIHBow6JuIGLhu5EgR2F1c3NpYW4gY8OzIHRydW5nIGLDrG5oID0gMTUwIHbDoCBzZCA9NTANCg0KVMawxqFuZyB04buxLCB0YSBk4buxIMSRb8OhbiBiMSBjw7MgZ2nDoSB0cuG7iyBy4bqldCB0aOG6pXAsIGfhuqduIDAgbmjGsG5nIGtow7RuZyByw7UgbMOgIGJhbyBuaGnDqnUsIG3hu5l0IHByaW9yIGdhbW1hIGPDsyB0aOG7gyB0aMOtY2ggaOG7o3AgY2hvIGThu7EgxJFvw6FuIG7DoHkgDQoNCmBgYHtyLG1lc3NhZ2UgPSBGQUxTRSx3YXJuaW5nPUZBTFNFfQ0KY3VydmUoZG5vcm0oeCwgMTUwLDIwKSwgZnJvbSA9IDAsMzAwLG1haW49ImIwIH4gTigxNTAsMjApIiwNCiAgICAgIHhsYWI9IiIsIHlsYWI9IiIsIGJ0eT0ibiIsY29sPSJyZWQiKQ0KYGBgDQoNCmBgYHtyLG1lc3NhZ2UgPSBGQUxTRSx3YXJuaW5nPUZBTFNFfQ0KY3VydmUoZGdhbW1hKHgsMiwzKSwgZnJvbSA9IDAsNSwgbWFpbj0iYjF+R2FtbWEoMiwzKSIsY29sPSJibHVlIiwNCiAgICAgIHhsYWI9IiIsIHlsYWI9IiIsIGJ0eT0ibiIpDQpgYGANCg0KYGBge3IsbWVzc2FnZSA9IEZBTFNFLHdhcm5pbmc9RkFMU0V9DQoNCmxpYnJhcnkoYnJtcykNCmxpYnJhcnkocnN0YW4pDQoNCnJzdGFuX29wdGlvbnMoYXV0b193cml0ZSA9IFRSVUUpDQpvcHRpb25zKG1jLmNvcmVzID0gcGFyYWxsZWw6OmRldGVjdENvcmVzKCkpDQoNCnByaW9yMSA8LSBjKHByaW9yKG5vcm1hbCgxNTAsMjApLCBubHBhcj0iYmV0YTAiKSwNCiAgcHJpb3IoZ2FtbWEoMiwzKSwgbmxwYXIgPSAiYmV0YTEiLGxiPTApKQ0KDQpwcmlvcjENCmBgYA0KDQpTYXUga2hpIHRoaeG6v3QgbOG6rXAgZ2nhuqMgxJHhu4tuaCB24buBIHByaW9yLCB0YSB2aeG6v3QgY8O0bmcgdGjhu6ljIGNobyBtw7QgaMOsbmggcGhpIHR1eeG6v24gdMOtbmggdHJvbmcgYnJtcy4gTMawdSDDvSBsw6Aga+G7gyB04burIDIwMTcsIGJybXMgY2hvIHBow6lwIHZp4bq/dCBjw7RuZyB0aOG7qWMgKGZvcm11bGEpIHRoZW8gbmhp4buBdSBwaG9uZyBjw6FjaCBraMOhYyBuaGF1LCB0csaw4bubYyB2w6AgdHJvbmcga2hpIGNvZGUgY2hvIGjDoG0gYnJtLiBOaGkgdGjDrWNoIGNodeG6qW4gYuG7iyBjw7RuZyB0aOG7qWMgVHLGsOG7m2Mga2hpIHZp4bq/dCB0w7l5IGNo4buJbmggY2hvIGjDoG0gYnJtLCB2w6wgY8OhY2ggbMOgbSB2aeG7h2MgdGhlbyBjw7RuZyDEkW/huqFuIG7DoHkgY2hvIHBow6lwIGjhuqFuIGNo4bq/IHNhaSBzw7N0IHbDoCB04buVIGNo4bupYyB04buRdCBoxqFuIHF1eSB0csOsbmguDQoNCkPDsyBt4buZdCDEkWnhu4F1IHRow7ogduG7iywgxJHDsyBsw6AgYnJtcyBjaG8gcGjDqXAga2hhaSBiw6FvIHRow6ptIG5o4buvbmcgdGhhbSBz4buRIC8gaOG6sW5nIHPhu5Ega2jDoWMgbmdvw6BpIG5o4buvbmcgYmnhur9uIGPDsyBz4bq1biB0cm9uZyBk4buvIGxp4buHdSB0cm9uZyBraGkgdmnhur90IGPDtG5nIHRo4bupYywgxJFp4buBdSBuw6B5IGThuqtuIMSR4bq/biBt4buZdCBraOG6oyBuxINuZyB0aMOtY2ggbmdoaSBo4bq/dCBz4bupYyB1eeG7g24gY2h1eeG7g24gY+G7p2EgbcO0IGjDrG5oIEJheWVzLCBjaG8gcGjDqXAgdGEgbeG7nyBy4buZbmcgbcO0IGjDrG5oIHbhu5tpIG5oaeG7gXUgdGjDoG5oIHBo4bqnbiBwaG9uZyBwaMO6LCDEkeG7gyBtw7QgdOG6oyBt4buZdCBnaeG6oyB0aHV54bq/dCAoaMOgbSkgYuG6pXQga8OsLCBjaOG7qSBraMO0bmcgY2jhu4kgZ2nhu5tpIGjhuqFuIOG7nyBjw6FjIGJp4bq/biB04burIGThu68gbGnhu4d1Lg0KDQpUaMOtIGThu6UgdGEgY8OzIHRo4buDIHZp4bq/dCBjw7RuZyB0aOG7qWMgOg0KDQoiT3V0Y29tZSAgfiBiZXRhMCpleHAoLWJldGExKlRpbWUpIg0KDQrEkOG7gyBraGFpIGLDoW8gMiB0aGFtIHPhu5EgYjAsIGIxLiBTYXUgxJHDsyB0YSDEkcawYSBjw7RuZyB0aOG7qWMgbsOgeSB2w6BvIGjDoG0gYmYsxJHhu5NuZyB0aOG7nWkga2hhaSBiw6FvIHRow6ptIG7hu5lpIGR1bmcgcuG6sW5nIGIwIHbDoCBiMSBz4bq9IMSRxrDhu6NjIGto4bqjbyBzw6F0IG5oxrAgaOG6sW5nIHPhu5EgKEludGVyY2VwdCkgdHJvbmcgbcO0IGjDrG5oLCBzYXUgY8O5bmcgbMawdSDDvSDEkeG7lWkgdMO5eSBjaOG7iW5oIG5sPVRSVUUgxJHhu4Mga2jhurNuZyDEkeG7i25oIMSRw6J5IGzDoCBtw7QgaMOsbmggcGhpIHR1eeG6v24gdMOtbmguICANCg0KU2F1IGtoaSDEkcOzbmcgY8O0bmcgdGjhu6ljLCB0YSBraGFpIGLDoW8gZOG7ryBsaeG7h3UgbMOgIGRmLCB2w6AgcHJpb3IgbMOgIHByaW9yX21vZC4NCg0KQ3Xhu5FpIGPDuW5nIGzDoCBjaOG6vyDEkeG7mSBs4bqleSBt4bqrdSBjaG8gY2h14buXaSBNQ01DLCBOaGkgY2hvIHNhbXBsZXIgY2jhuqF5IDQgY2h14buXaSBuZ+G6r24gNHgxMDAwIGzGsOG7o3Qgc2F1IDUwMCBsxrDhu6N0IGto4bufaSDEkeG7mW5nLg0KDQpUYSBjw7MgdGjhu4MgY2hvIHRoaSBow6BuaCBtw7QgaMOsbmggZml0MQ0KDQpgYGB7cixtZXNzYWdlID0gRkFMU0Usd2FybmluZz1GQUxTRX0NCg0KZm1sMTwtIE91dGNvbWUgfiBiZXRhMCooZXhwKC1iZXRhMSpUaW1lKSkNCg0KZml0MSA8LSBicm0oYmYoZm1sMSwNCiAgICAgICAgICAgICAgIGJldGEwK2JldGExfjEsDQogICAgICAgICAgICAgICBubCA9IFRSVUUpLA0KICAgICAgICAgICAgZGF0YSA9IGRmLA0KICAgICAgICAgICAgZmFtaWx5ID0gYnJtc2ZhbWlseSgiZ2F1c3NpYW4iLCBsaW5rX3NpZ21hID0gImxvZyIpLA0KICAgICAgICAgICAgcHJpb3IgPSBwcmlvcjEsDQogICAgICAgICAgICBjb250cm9sID0gbGlzdChhZGFwdF9kZWx0YSA9IDAuOTAsIG1heF90cmVlZGVwdGg9MjApLA0KICAgICAgICAgIHNlZWQgPSAxMjM0LCBpdGVyID0gMTUwMCxjaGFpbnMgPSA0KQ0KYGBgDQoNCk3DtCBow6xuaCBjb252ZXJnZSBy4bqldCBuaGFuaCB2w6wgxJHGoW4gZ2nhuqNuLCB0YSBjw7MgdGjhu4MgdMOzbSB04bqvdCBwaMOibiBi4buRIGjhuq11IG5naGnhu4dtIGPhu6dhIDIgaOG6sW5nIHPhu5EgYmV0YTAgdsOgIGJldGExIGPFqW5nIG5oxrAgc2lnbWEgY+G7p2EgT3V0Y29tZQ0KDQpgYGB7cixtZXNzYWdlID0gRkFMU0Usd2FybmluZz1GQUxTRX0NCnN1bW1hcnkoZml0MSkNCg0KcGxvdChmaXQxLCBhc2sgPSBGQUxTRSx0aGVtZT1teV90aGVtZSg4KSkNCg0KYGBgDQoNCk5oxrAgdGEgdGjhuqV5LCBtw7QgaMOsbmggxJHDoyB4w6FjIMSR4buLbmggYmV0YTAgPTE1MC45OCAodOG7qyAxNDguNDEgxJHhur9uIDE1My43NCksIGJldGExPTAuMDggdsOgIHNpZ21hID0gMi4yMSAoMS44NSDEkeG6v24gMi42NyksIGvhur90IHF14bqjIG7DoHkgcuG6pXQgcGjDuSBo4bujcCB24bubaSBjw6FjIHRoYW0gc+G7kSB0YSDEkcOjIGTDuW5nIMSR4buDIG3DtCBwaOG7j25nIGThu68gbGnhu4d1Lg0KDQpgYGB7cixtZXNzYWdlID0gRkFMU0Usd2FybmluZz1GQUxTRX0NCg0KdGhlbWVfc2V0KG15X3RoZW1lKCkpDQoNCm1hcmdpbmFsX2VmZmVjdHMoZml0MSkNCmBgYA0KDQojIE3hu5l0IHRow60gZOG7pSBwaOG7qWMgdOG6oXAgaMahbjogTcO0IGjDrG5oIFBLIDEgbmfEg24NCg0KVGnhur9wIHRoZW8gY2jDum5nIHRhIHPhur0gdGjhu60gc+G7qWMgduG7m2kgbeG7mXQgdHLGsOG7nW5nIGjhu6NwIGPDsyDEkeG7mSBraMOzIGNhbyBoxqFuLCDEkcOzIGzDoCBt4buZdCBuZ2hpw6puIGPhu6l1IGTGsOG7o2MgxJHhu5luZyBo4buNYyBj4bunYSBUaGVvcGh5bGxpbmUgOiBWw6BvIG7Eg20gMTk5NCwgQm9lY2ttYW5uLCBTaGVpbmVyIHbDoCBCZWFsIMSRw6MgY8O0bmcgYuG7kSBk4buvIGxp4buHdSBuw6B5IHThu6sga+G6v3QgcXXhuqMgdGjhu7FjIG5naGnhu4dtIGPhu6dhIEJTLiBSb2JlcnQgVXB0b24uIFRodeG7kWMgVGhlb3BoeWxsaW5lIMSRxrDhu6NjIGTDuW5nIHRyw6puIDEyIG5nxrDhu51pIHRoZW8gxJHGsOG7nW5nIHXhu5FuZywgc2F1IMSRw7MgbuG7k25nIMSR4buZIFRoZW9waGlsbGluZSAgdHJvbmcgbcOhdSDEkcaw4bujYyBraOG6o28gc8OhdCBsacOqbiB04bulYyBxdWEgMTEgdGjhu51pIMSRaeG7g20gdHJvbmcgMjUgZ2nhu50gc2F1IMSRw7MuIEThu68gbGnhu4d1IG7DoHkgc2F1IMSRw7MgxJHGsOG7o2MgUGluaGVpcm8sIEouIEMudsOgIEJhdGVzLCBELiBNLiBz4butIGThu6VuZyDEkeG7gyBtaW5oIGjhu41hIGNobyBtw7QgaMOsbmggcGhpIHR1eeG6v24gdMOtbmggY8OzIGhp4buHdSDhu6luZyBuZ+G6q3Ugbmhpw6puIHbDoG8gbsSDbSAyMDAwIChNaXhlZC1lZmZlY3RzIE1vZGVscyBpbiBTIGFuZCBTLVBMVVMsIFNwcmluZ2VyKS4gDQoNClRhIHThuqNpIGThu68gbGnhu4d1IHbDoCBi4bqvdCDEkeG6p3Uga2jhuqNvIHPDoXQgbsOzDQoNCmBgYHtyLG1lc3NhZ2UgPSBGQUxTRSx3YXJuaW5nPUZBTFNFfQ0KZGF0PC1yZWFkLmNzdigiaHR0cDovL3ZpbmNlbnRhcmVsYnVuZG9jay5naXRodWIuaW8vUmRhdGFzZXRzL2Nzdi9kYXRhc2V0cy9UaGVvcGguY3N2IikNCg0KDQpoZWFkKGRhdCklPiVrbml0cjo6a2FibGUoKQ0KYGBgDQoNCkThu68gbGnhu4d1IGfhu5NtIDUgYmnhur9uLCBTdWJqZWN0IMSR4buDIMSR4buLbmggZGFuaCBjw6EgdGjhu4MsIFd0IGNo4buJIGPDom4gbsSDbmcgKGtnKSxEb3NlIGzDoCBsaeG7gXUgdGh14buRYyB14buRbmcgKG1nL2tnKSwgVGltZSBjaOG7iSB0aOG7nWkgxJFp4buDbSBraOG6o28gc8OhdCB2w6AgY29uYyBsw6AgYmnhur9uIHPhu5Ega+G6v3QgcXXhuqMgY+G6p24gbmdoacOqbiBj4bupdSBjw7MgxJHGoW4gduG7iyBtZy9MLg0KDQrEkOG6p3UgdGnDqm4sIGNow7puZyB0YSBz4bq9IHTDrW5oIGzhuqFpIExp4buBdSB0aHXhu5FjIHRo4buxYyBz4buxIMSRxrDhu6NjIMSRxrBhIHbDoG8gY8ahIHRo4buDIGLhurFuZyBjw6FjaCBuaMOibiBjaG8gdGjhu4MgdHLhu41uZzoNCg0KS2jhuqNvIHPDoXQgdHLhu7FjIHF1YW4gY2hvIHRo4bqleSBub8Oow6luZyDEkeG7mSBUaGVvcGh5bGxpbmUgdGhheSDEkeG7lWkgdGhlbyBxdXkgbHXhuq10IG5oxrAgc2F1IHF1YSAxMSB0aOG7nWkgxJFp4buDbToNCg0KYGBge3IsbWVzc2FnZSA9IEZBTFNFLHdhcm5pbmc9RkFMU0V9DQpsaWJyYXJ5KFJDb2xvckJyZXdlcikNCg0KY29sZWRnZXM9Y29sb3JSYW1wUGFsZXR0ZShjKCJyZWQiLCJibHVlIiwicHVycGxlIikpDQpteWNvbD0oY29sZWRnZXMoMTIpKQ0KDQpzdW1fZGY9ZGF0JT4lZ3JvdXBfYnkoVGltZSklPiUNCiAgc3VtbWFyaXNlX2F0KCJjb25jIixtZWRpYW4pDQoNCmRhdCU+JWdncGxvdCgpKw0KICBnZW9tX2xpbmUoYWVzKHg9VGltZSx5PWNvbmMsY29sPWZhY3RvcihTdWJqZWN0KSksYWxwaGE9MC41KSsNCiAgZ2VvbV9zbW9vdGgoZGF0YT1zdW1fZGYsYWVzKHg9c3VtX2RmJFRpbWUseT1zdW1fZGYkY29uYyksY29sPSJyZWQ0IixmaWxsPSJyZWQiLHNpemU9MSxhbHBoYT0wLjIpK3NjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXM9bXljb2wpDQpgYGANCg0KxJDhu4MgxJHGoW4gZ2nhuqNuIGjDs2EsIGNow7puZyB0YSBz4bq9IHPhu60gZOG7pW5nIG3DtCBow6xuaCBkxrDhu6NjIMSR4buZbmcgaOG7jWMgMSBuZ8SDbiAoT25lIENvbXBhcnRtZW50IFBLIG1vZGVsKSwgdGhlbyDEkcOzIEzGsOG7o25nIHRodeG7kWMgYmFuIMSR4bqndSAoRG9zZSkgxJHGsOG7o2MgZ2nhuqMgxJHhu4tuaCBwaMOibiBi4buRIG5nYXkgdOG7qWMga2jhuq9jIHbDoCDEkeG7k25nIMSR4buBdSB0cm9uZyBjxqEgdGjhu4MgLSDEkcaw4bujYyB4ZW0gbmjGsCAxIG5nxINuIGNo4bupYSBkdXkgbmjhuqV0IHbhu5tpIHRo4buDIHTDrWNoIFYgKEwpLCBO4buTbmcgxJHhu5kgdGh14buRYyB0cm9uZyBtw6F1IOG7nyBi4bqldCBj4bupIHRo4budaSDEkWnhu4NtIG7DoG8gc2F1IMSRw7Mgc+G6vSDEkcaw4bujYyB4w6FjIMSR4buLbmggYuG7n2kgMiBo4bqxbmcgc+G7kSBo4bqlcCB0aHUgKGthKSB2w6AgdGjhuqNpIHRy4burIChrZSkuDQoNCiFbXShQS21vZGVsMi5wbmcpDQoNCkdp4bqjIMSR4buLbmggY29uYyA9IGJp4bq/biBuZ+G6q3Ugbmhpw6puIFkgxJHGsOG7o2MgeMOhYyDEkeG7i25oIGLhurFuZyBwaMOibiBi4buRIEdhdXNzaWFuIHbhu5tpIDIgdGhhbSBz4buRIE11IHbDoCBzaWdtYS4NCg0KJCRZX3tpan0gXHNpbSBOKFxtdV97aWp9LFxzaWdtYV97aWp9KSQkDQoNCkNobyBt4buXaSBjw6EgdGjhu4MgaSB2w6AgdGjhu51pIMSRaeG7g20gdGosIGdpw6EgdHLhu4sgTXVpaiDEkcaw4bujYyDGsOG7m2MgbMaw4bujbmcgYuG6sW5nIDEgaMOgbSBmIGPDsyBu4buZaSBkdW5nIDoNCg0KJCRcbXVfe2lqfSBcc2ltIGYodF97an0sXHBoaSkgPSBcZnJhY3tEb3NlKmtfe2F9fXtWKihrX3thfS1rX3tlfSl9XGxlZnQgKCBlXnsta197ZX0qdF97an19IC0gZV57LWtfe2F9KnRfe2p9fSBccmlnaHQgKSQkDQoNCk5oxrAgduG6rXkgbeG7pWMgdGnDqnUgY+G7p2EgY2jDum5nIHRhIGzDoCB4w6J5IGThu7FuZyBt4buZdCBtw7QgaMOsbmggY2hvIHBow6lwIHjDoWMgxJHhu4tuaCDEkeG7k25nIHRo4budaSBnacOhIHRy4buLIGPhu6dhIDIgaOG6sW5nIHPhu5Ega2EgdsOgIGtlLg0KDQpOaGkgc+G6vSBsw6BtIHZp4buHYyBuw6B5IHRoZW8gdOG7q25nIGLGsOG7m2M6DQoNCjEpIMSQ4bqndSB0acOqbiwgTmhpIMSRxrBhIGjDoG0gZiB2w6BvIGPDtG5nIHRo4bupYyBtw7QgaMOsbmggcGhpIHR1eeG6v24gdMOtbmguIEjDoG0gIGYgbsOgeSBjw7MgbuG7mWkgZHVuZyBn4bqnbiBnaeG7kW5nIG5oxrAgdHLDqm4sIHR1eSBuaGnDqm4gduG7m2kgZ2nhuqMgxJHhu4tuaCBsw6AgTmhpIGNo4buJIG114buRbiB0w61uaCBrYSB2w6Aga2UgKDIgaOG6sW5nIHPhu5EgaOG6pXAgdGh1L3Ro4bqjaSB0cuG7qykgbmjGsG5nIGtow7RuZyBxdWFuIHTDom0gxJHhur9uIFZvbHVtZSwgTmhpIGtow7RuZyBuaMOibiBsaeG7gXUgdGh14buRYyB24bubaSBjw6JuIG7hurduZyBuaMawbmcgZ2nhu68gbmd1ecOqbiBnacOhIHRy4buLIERvc2UgdsOgIG5ow6JuIGNobyAxIGJp4bq/biB0w6puIGzDoCBJblYgPSAxL1YuDQoNCmBgYHtyLG1lc3NhZ2UgPSBGQUxTRSx3YXJuaW5nPUZBTFNFfQ0KZm1sIDwtIGNvbmN+RG9zZSprYSpJblYvKGthLWtlKSooZXhwKC1rZSpUaW1lKS1leHAoLWthKlRpbWUpKQ0KDQpmbWwNCmBgYA0KDQoyKSBTYXUgxJHDsywgTmhpIMSRxrBhIGPDtG5nIHRo4bupYyBuw6B5IHbDoG8gbcO0IGjDrG5oIHF1YSBow6BtIGJmLCBzYXUgxJHDsyBwaMOhdCB0cmnhu4NuIHRow6ptIDMgY8O0bmcgdGjhu6ljIG7hu69hIMSR4buDIMaw4bubYyB0w61uaCBJblYgfiAxfFN1YmplY3QsIGthIHbDoCBrZSDEkeG7gXUgbMOgIGjhurFuZyBz4buRIChJbnRlcmNlcHQpLg0KDQozKSBTYXUgxJHDsyBOaGkgw6FwIGThu6VuZyBwcmlvciBjaG8gY29uYyBsw6AgTihNdSxzaWdtYSkgduG7m2kgbGluayBmdW5jdGlvbiA9IGxvZyBjaG8gc2lnbWEgdsOgIElkZW50aXR5IGNobyBNdS4NCg0KNCkgVGnhur9wIHRoZW8sIGzDoCBixrDhu5tjIHF1YW4gdHLhu41uZyBuaOG6pXQsIE5oaSBraGFpIGLDoW8gMyBwcmlvciBjaG8gMyB0aGFtIHPhu5EgSW5WLCBrYSB2w6Aga2UuIEPDsyB0aOG7gyBk4buxIMSRb8OhbiBsw6AgZ2nDoSB0cuG7iyBj4bunYSBjaMO6bmcgxJHhu4F1IHLhuqV0IHRo4bqlcCwgY8OzIHRo4buDIG7hurFtIGdp4buvYSAwIHbDoCAyOyBuw6puIE5oaSBjaOG7jW4gcHJpb3IgbMOgIEdhbW1hKCkgDQoNCmBgYHtyfQ0KY3VydmUoZGdhbW1hKHgsIDEsIDUpLCBmcm9tID0gMCwgNSwgbWFpbj0iR2FtbWEgcHJpb3JzIiwNCiAgICAgIHhsYWI9IiIsIHlsYWI9IiIsIGJ0eT0ibiIsY29sPSJyZWQiKQ0KY3VydmUoZGdhbW1hKHgsIDIsNSksIGFkZCA9IFRSVUUsY29sPSJibHVlIikNCmN1cnZlKGRnYW1tYSh4LCAzLDUpLCBhZGQ9VFJVRSxjb2w9ImdyZWVuIikNCnRleHQoeCA9IGMoMC4xLDAuMiwwLjYpLCB5ID0gYyg0LDIsMS4yKSwgbGFiZWxzID0gYygiSW5WIiwgImtlIiwgImthIiksY29sPWMoInJlZCIsImJsdWUiLCJncmVlbiIpKQ0KYGBgDQoNCjUpIFNhdSBjw7luZywgVGEgbcO0IHThuqMgY2jhur8gxJHhu5kgbOG6pXkgbeG6q3UgY2hvIHNhbXBsZXIsIGJhbyBn4buTbSAyIGNodeG7l2ksIG3hu5dpIGNodeG7l2kgMTAwMCBsxrDhu6N0DQoNCmBgYHtyLG1lc3NhZ2UgPSBGQUxTRSx3YXJuaW5nPUZBTFNFfQ0KZGF0JFN1YmplY3Q9YXMuZmFjdG9yKGRhdCRTdWJqZWN0KQ0KDQpicGttb2QgPC0gYnJtKGJmKGZtbCwNCiAgICAgICAgICAgICBJblYgfiAxfFN1YmplY3QsDQogICAgICAgICAgICAga2EgKyBrZSB+IDEsDQogICAgICAgICAgICAgbmwgPSBUUlVFKSwNCiAgICAgICAgICBkYXRhID0gZGF0LCANCiAgICAgICAgICBmYW1pbHkgPSBicm1zZmFtaWx5KCJnYXVzc2lhbiIsIGxpbmtfc2lnbWEgPSAibG9nIiksDQogICAgICAgICAgcHJpb3IgPSBjKHByaW9yKGdhbW1hKDEsNSksIG5scGFyID0gIkluViIsIGxiPTApLA0KICAgICAgICAgICAgICAgICAgICBwcmlvcihnYW1tYSgzLDUpLCBubHBhciA9ICJrYSIsIGxiPTApLA0KICAgICAgICAgICAgICAgICAgICBwcmlvcihnYW1tYSgyLDUpLCBubHBhciA9ICJrZSIsIGxiPTApKSwNCiAgICAgICAgICBjb250cm9sID0gbGlzdChhZGFwdF9kZWx0YSA9IDAuOTk5LCBtYXhfdHJlZWRlcHRoPTMwKSwNCiAgICAgICAgICBzZWVkID0gMTIwNiwgaXRlciA9IDIwMDAsd2FybXVwID0gMTAwMCxjaGFpbnMgPSAyKQ0KYGBgDQoNCk3DtCBow6xuaCBjb252ZXJnZSBzYXUgdsOgaSBwaMO6dCB2w6AgxJHDonkgbMOgIGvhur90IHF14bqjIHBow6JuIGLhu5EgaOG6rXUgbmdoaeG7h20gdsOgIGNodeG7l2kgTUNNQyBj4bunYSBjw6FjIGjhurFuZyBz4buRIHRyb25nIG3DtCBow6xuaC4gVGEgcXVhbiB0w6JtIMSR4bq/biBrYSB2w6Aga2UuIEdpw6EgdHLhu4sgY+G7p2EgMiBo4bqxbmcgc+G7kSBuw6B5IGzhuqduIGzGsOG7o3QgbMOgIDEuNSB2w6AgMC4wOA0KDQpgYGB7cixtZXNzYWdlID0gRkFMU0Usd2FybmluZz1GQUxTRX0NCnBsb3QoYnBrbW9kLCBOID0gNSwgYXNrID0gRkFMU0UsdGhlbWU9bXlfdGhlbWUoOCkpDQoNCmBgYA0KDQpgYGB7cixtZXNzYWdlID0gRkFMU0Usd2FybmluZz1GQUxTRX0NCnN1bW1hcnkoYnBrbW9kKQ0KYGBgDQoNCg0KS+G6v3QgcXXhuqMga2EsIGtlIG7DoHkgdMawxqFuZyDEkcawxqFuZyB24bubaSBjw6FjaCBsw6BtIGPhu5UgxJFp4buDbiBuaMawIHRyb25nIHdlYnNpdGUgc2F1IMSRw6J5OiA8aHR0cDovL3NpYS53ZWJwb3BpeC5vcmcvbmxtZS5odG1sPg0KDQpUYSBjw7MgdGjhu4Mga2nhu4NtIHRyYSB4ZW0gbcO0IGjDrG5oIGPDsyDGsOG7m2MgdMOtbmggY2jDrW5oIHjDoWMgZGnhu4VuIHRp4bq/biBj4bunYSBiaeG6v24gY29uYyB0aGVvIHRo4budaSBnaWFuIGhheSBraMO0bmcgPyBUcm9uZyBow6xuaCBkxrDhu5tpIMSRw6J5OiBNw6B1IHhhbmggY2jhu4kgZ2nDoSB0cuG7iyB0aOG7sWMgdOG6vywgbcOgdSDEkeG7jyBsw6AgZ2nDoSB0cuG7iyBtw7QgaMOsbmggxrDhu5tjIHTDrW5oLg0KDQpgYGB7cixtZXNzYWdlID0gRkFMU0Usd2FybmluZz1GQUxTRX0NCnByZWRfZGY9cHJlZGljdChicGttb2QpJT4lYXMuZGF0YS5mcmFtZSgpDQoNCmRhdCRQcmVkPXByZWRfZGYkRXN0aW1hdGUNCg0KcG9zdF9kZj1icGttb2QkZml0JT4lYXMubWF0cml4KCkNCg0KZ2dwbG90KGRhdGE9ZGF0KSsNCiAgZ2VvbV9wb2ludChhZXMoeD1UaW1lLHk9Y29uYyksY29sPSJwaW5rIikrDQogIGdlb21fbGluZShhZXMoeD1UaW1lLHk9Y29uYyksY29sPSJibHVlIixsaW5ldHlwZT0yKSsNCiAgZ2VvbV9saW5lKGFlcyh4PVRpbWUseT1QcmVkKSxjb2w9InJlZCIpKw0KICBmYWNldF93cmFwKH5TdWJqZWN0LG5jb2w9MyxzY2FsZXM9ImZyZWUiKStteV90aGVtZSgpDQpgYGANCg0KIyBL4bq/dCBsdeG6rW4NCg0KUGFja2FnZSBicm1zIGjhu5cgdHLhu6MgdGjhu7FjIGhp4buHbiBtw7QgaMOsbmggcGhpIHR1eeG6v24gdMOtbmgsIHBow6JuIGPhuqVwIHbhu5tpIGPhuqV1IHRyw7pjIG3DtCBow6xuaCBjw7MgdGjhu4MgbeG7nyBy4buZbmcgdGhv4bqjaSBtw6FpLiBUcm9uZyBiw6BpIG7DoHkgY2jDum5nIHRhIGNo4buJIG3hu5tpIHPhu60gZOG7pW5nIG3DtCBow6xuaCAxIG5nxINuLCB2w6AgY2jhu4kgcXVhbiB0w6JtIMSR4bq/biBo4bqxbmcgc+G7kTsgdHV5IG5oacOqbiBjw6FjIG3DtCBow6xuaCBuaGnhu4F1IG5nxINuIGPFqW5nIGPDsyB0aOG7gyDEkcaw4bujYyBnaeG6o2kgcXV54bq/dCB0aGVvIGPDuW5nIGPDoWNoIGzDoG0gbsOgeS4gVGEgY8WpbmcgY8OzIHRo4buDIMSRxrBhIHRow6ptIG5oaeG7gXUgdGhhbSBz4buRIHbDoG8gbcO0IGjDrG5oIHbDoCBjaMO6bmcgY8OzIGxpw6puIGjhu4cgduG7m2kgZOG7ryBsaeG7h3U6IHRow60gZOG7pSBz4butIGThu6VuZyBjw6JuIG7hurduZyBj4bunYSBi4buHbmggbmjDom4gdHJvbmcgY8OhYyBtw7QgaMOsbmggZMaw4bujYyDEkeG7mW5nIGjhu41jLCBoYXkgY8OhYyBjaOG7iSBz4buRIG5ow6JuIHRy4bqvYyB24buBIGjhu4cgaMO0IGjhuqVwIGNobyBjw6FjIG3DtCBow6xuaCBraMOtIMSR4buZbmcgaOG7jWMuDQoNClbhuqVuIMSR4buBIGtow7Mga2jEg24gbmjhuqV0IGtoaSBz4butIGThu6VuZyBwaMawxqFuZyBwaMOhcCBCYXllcyBsw6AgeMOhYyDEkeG7i25oIHByaW9yIHBow7kgaOG7o3AuIFbhu5tpIG3DtCBow6xuaCBwaGkgdHV54bq/biB0w61uaCB0aMOsIHByaW9yIGPDsyB2YWkgdHLDsiBj4buxYyBrw6wgcXVhbiB0cuG7jW5nLCBuaMawIGPDoWMgYuG6oW4gxJHDoyB0aOG6pXkgdHJvbmcgYsOgaS4NCg0KQ2jDumMgY8OhYyBi4bqhbiB0aOG7sWMgaMOgbmggdnVpLiBI4bq5biBn4bq3cCBs4bqhaSB0cm9uZyBt4buZdCBiw6BpIGtow6FjIHbhu4EgQmF5ZXMu