Dẫn nhập
Đây là một bài thực hành với tham vọng cao, đó là giới thiệu đến các bạn sinh viên Y khoa và bác sĩ về mô hình liên hợp. Đây là một công cụ tinh tế, phức tạp và hữu ích với nhiều ứng dụng lâm sàng; tuy nhiên nó gần như chỉ lưu hành nội bộ trong giới thống kê chuyên nghiệp (cho các hãng dược phẩm chẳng hạn), chứ không được phổ biến cho người thực hành lâm sàng.
Phương pháp này được phát triển khoảng đầu thập niên 1990,và đặc biệt dành riêng cho ngành Y khoa (tất cả dataset thực hành đều là clinical trial), vì nó cho phép kiểm chứng những giả thuyết phức tạp, khai thác thông tin hữu ích từ các nghiên cứu thử nghiệm thuốc.
Cho đến năm 2015, lý thuyết về Joint model đã hoàn thiện và có thể tiếp cận trong R bằng tới 3 cách : trường phái Bayes, ước lượng hợp lý cực đại (RE Maximum likelihood) và Hierarchical likelihood.
Trong bài này, Nhi sẽ giới thiệu trước cách tiếp cận dùng REML, cách làm Bayes sẽ được hướng dẫn vào 1 dịp khác. Trong bài Nhi chủ yếu dùng package JM của tác giả Dimitris Rizopoulos (2012-2016).
Mục tiêu của bài thực hành gồm có:
- Thăm dò dữ liệu của một Longitudinal và Time-Event data
- Dựng Joint model từ Cox-PH model và Mixed linear model
- Diễn giải kết quả mô hình
- Kiểm tra phẩm chất mô hình
- Ứng dụng mô hình để tiên lượng cho cá thể bệnh nhân
Do tính chất phức tạp của vấn đề, Nhi khuyến khích các bạn tìm đọc thêm tài liệu lý thuyết (bài này chỉ nhằm để thả thính…). Cụ thể là quyển sách :
Dataset AIDS
Trước hết ta tải 1 số package,dataset aids nằm trong package JM
library(JM) # package dựng Joint model
library(tidyverse) # data wrangling và ggplot
library(magrittr) # Toán tử pipe
library(ggkm) #Thăm dò Time event data
AIDS dataset có nguồn gốc từ một nghiên cứu (NC) thử nghiệm dược phẩm lâm sàng năm 1994 trên 467 bệnh nhân nhiễm HIV. Mục tiêu của NC này đó là so sánh hiệu quả điều trị giữa 2 loại thuốc antiretroviral khác nhau tên là Didanosine (mã = ddI) và zalcitabine (ddC). Outcome chính của NC này là biến cố tử vong với thời gian theo dõi kéo dài 2 năm. Tuy nhiên, NC còn có 1 outcome thứ hai là số lượng bạch cầu CD4 (gọi tắt là CD4). CD4 được khảo sát ở thời điểm đầu tiên, sau đó mỗi bệnh nhân được sắp xếp ngẫu nhiên vào 1 trong 2 phân nhóm trị liệu và CD4 tiếp tục được khảo sát định kỳ tại tháng thứ 2,6,12 và 18.
Vào lúc NC kết thúc, 188 bệnh nhân đã tử vong và người ta có tổng cộng 1405 giá trị CD4.
Như ta thấy, đây là một nghiên cứu có thiết kế phức tạp với 2 outcomes lần lượt là : 1 sự kiện (tử vong) và 1 biến định lượng (CD4) được theo dõi theo thời gian. Như vậy, mỗi bộ phận này tương ứng với một thiết kế nghiên cứu khác nhau : Longitudinal (cho CD4) và Time-Event (Survival) analysis.
Dĩ nhiên chúng ta có thể xử lý riêng từng bộ phận bằng 1 mô hình hồi quy hỗn hợp (Mixed model) (cho CD4) và 1 mô hình hồi quy Cox-PH (cho nguy cơ tử vong). Tuy nhiên, vấn đề không đơn giản như ta nghĩ. Để biết ta phải đương đầu với những khó khăn nào, Nhi sẽ thăm dò dữ liệu.
Thăm dò dữ liệu
Tổng số bệnh nhân là 467
aids$patient %>%
unique() %>%
length()
## [1] 467
Xem cấu trúc dữ liệu, thí dụ cho 1 bệnh nhân N°2
aids%>%filter(.$patient==2)%>%knitr::kable()
2 |
19 |
0 |
6.324555 |
0 |
ddI |
male |
noAIDS |
intolerance |
0 |
6 |
0 |
2 |
19 |
0 |
8.124038 |
6 |
ddI |
male |
noAIDS |
intolerance |
6 |
12 |
0 |
2 |
19 |
0 |
4.582576 |
12 |
ddI |
male |
noAIDS |
intolerance |
12 |
18 |
0 |
2 |
19 |
0 |
5.000000 |
18 |
ddI |
male |
noAIDS |
intolerance |
18 |
19 |
0 |
Thời gian theo dõi trung bình là 13.9 tháng, thấp nhất là khoảng 2 tuần, cao nhất là 21 tháng. Điều này chứng tỏ là tỉ lệ tử vong và mất mát dữ liệu khá cao (theo dự tính thời gian theo dõi tối đa 18 tháng.
summary(aids$Time)
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## 0.47 12.23 14.07 13.89 17.00 21.40
Phần 1: Diễn tiến CD4 theo thời gian (Thành tố Longitudinal)
Biểu đồ sau đây trình bày diễn tiến thay đổi của CD4 (ghi chú: đã được khai căn bậc 2) ở mỗi cá thể bệnh nhân và khuynh hướng trung bình giữa 2 phân nhóm điều trị, và giữa 2 nhóm có và không có tử vong.
Biểu đồ này cho chúng ta thấy 3 vấn đề:
Đầu tiên ta có thể thấy đó là không phải bất cứ bệnh nhân nào cũng có đủ 5 giá trị CD4. Vậy,có vấn đề thiếu sót dữ liệu và đây là vấn đề chung cho tất cả nghiên cứu lâm sàng. Nguyên nhân của sót dữ liệu có rất nhiều : bệnh nhân hủy bỏ cuộc hẹn, bệnh nhân rời bỏ nghiên cứu giữa chừng, mẫu bệnh phẩm bị thất lạc, thiết bị đo hư hỏng đúng vào lịch hẹn, mất hồ sơ, nhập liệu sai, và dĩ nhiên – bệnh nhân tử vong trước khi nghiên cứu kết thúc (Nhóm tử vong có số time point thấp , đa phần chấm dứt ở tháng thứ 6, là điểm hẹn thứ 3)
Một chi tiết khác, khó nhận ra hơn nhưng có thể tưởng tượng, đó là sai lệch ngẫu nhiên về thời điểm lấy mẫu CD4. Có thể bệnh nhân không đến đúng hẹn như kế hoạch, bác sĩ và họ có thể đã đặt hẹn ngẫu nhiên với sai lệch vài ngày đến 1-2 tuần trong cùng tháng, đơn vị mốc thời gian 2,6,12,18 do đó không chính xác tuyệt đối.
Vấn đề thứ 3 : Mỗi bệnh nhân là một « phân nhóm » đặc biệt trong nghiên cứu. Lộ trình diễn tiến của CD4 khác nhau giữa BN này và BN khác. Đó là nguyên nhân tại sao random effect và mixed model sẽ được sử dụng để xét đến yếu tố ngẫu nhiên cá thể.
library(colorRamps)
library(RColorBrewer)
mypalette = length(unique(aids$patient))
getPalette = colorRampPalette(brewer.pal(9, "Spectral"))
aids%>%mutate(.,Death=ifelse(.$death==1,"Death","Survived"))%>%
ggplot()+
geom_path(aes(x=obstime,y=CD4,color=patient),show.legend = F,alpha=0.5)+
geom_smooth(aes(x=as.numeric(obstime),y=CD4,fill=drug,linetype=drug),
color="black",
method="lm",formula = y ~ splines::bs(x,3),alpha=0.4)+
scale_color_manual(values = getPalette(mypalette))+
facet_wrap(~Death)+
theme_bw()

Có sự khác biệt rõ về khuynh hướng trung bình của CD4 giữa bệnh nhân sống sót và tử vong, cũng như giữa 2 phân nhóm điều trị, tuy nhiên theo thời gian CD4 giảm dần;
aids%>%filter(.$Time==.$stop)%>%
mutate(.,Death=ifelse(.$death==1,"Death","Censored"))%>%
ggplot()+
geom_boxplot(aes(x=Death,y=CD4,fill=Death),alpha=0.7)+
coord_flip()+
facet_wrap(~drug,scales="free",ncol=1)+
theme_bw()

aids%>%filter(.$Time==.$stop)%>%
mutate(.,Death=ifelse(.$death==1,"Death","Censored"))%>%
ggplot()+
geom_boxplot(aes(x=drug,y=CD4,fill=drug))+
coord_flip()+
facet_wrap(~Death,scales="free",ncol=1)+
theme_bw()

aids%>%
ggplot(aes(x=drug,y=Time,fill=drug))+
geom_boxplot(color="black")+
geom_violin(alpha=0.3)+
coord_flip()+
theme_bw()

Nếu muốn giải quyết độc lập biến số CD4 theo thời gian, ta có thể dùng mô hình Mixed linear model, bao gồm mô hình Random intercept và random slope như sau:
mypalette = length(unique(aids$patient))
getPalette = colorRampPalette(brewer.pal(9, "Spectral"))
rint=lme(CD4 ~ obstime,
random = ~ 1 | patient,
data = aids)
aids%>%mutate(.,Fitted=rint$fitted[,1]+rint$fitted[,2])%>%
ggplot()+
geom_smooth(aes(x=as.numeric(obstime),y=Fitted,color=patient),
method="lm",show.legend = F,se=F)+
geom_smooth(aes(x=as.numeric(obstime),y=Fitted),
method="lm",formula = y ~ x,show.legend = F,se=F,color="red")+
scale_color_grey()+
theme_bw()

rslope=lme(CD4 ~ obstime,
random = ~ obstime | patient,
data = aids)
aids%>%mutate(.,Fitted=rslope$fitted[,1]+rslope$fitted[,2])%>%
ggplot()+
geom_smooth(aes(x=as.numeric(obstime),y=Fitted,color=patient),
method="lm",show.legend = F,se=F)+
geom_smooth(aes(x=as.numeric(obstime),y=Fitted),
method="lm",formula = y ~ x,show.legend = F,se=F,color="red")+
scale_color_grey()+
theme_bw()

rslope=lme(CD4 ~ obstime,
random = ~ obstime | patient,
data = aids)
Liên hệ giữa diễn tiến CD4 và biến cố tử vong
Có sự liên hệ giữa CD4 và nguy cơ tử vong tại bất cứ thời điểm nào. Đây là một giả thuyết logic, vì lượng tế bào CD4 biểu thị cho khả năng miễn dịch của bệnh nhân. Suy giảm miễn dịch sẽ dẫn đến nhiễm trùng cơ hội và tăng nguy cơ tử vong.
binomial_smooth <- function(...) {
geom_smooth(method = "glm", method.args = list(family = "binomial"), ...)
}
aids%>%mutate(.,Death=ifelse(.$death==1,"Death","Survived"))%>%
ggplot(aes(x=CD4,y=death,fill=drug,linetype=drug))+
binomial_smooth(color="black",alpha=0.3)+
facet_wrap(~obstime,ncol=2)+
theme_bw()
## Warning: glm.fit: fitted probabilities numerically 0 or 1 occurred

mypalette = length(unique(aids$drug))
getPalette = colorRampPalette(brewer.pal(9, "Spectral"))
aids%>%mutate(.,Death=ifelse(.$death==1,"Death","Survived"))%>%
ggplot()+
geom_boxplot(aes(x=as.factor(obstime),y=CD4,fill=drug),alpha=0.7)+
scale_fill_manual(values = getPalette(mypalette))+
facet_wrap(~Death,ncol=1)+
coord_flip()+
theme_bw()

aids%>%mutate(.,Death=ifelse(.$death==1,"Death","Survived"))%>%
ggplot()+
geom_histogram(aes(x=CD4,y=..density..,fill=Death,bins=5,color=Death),alpha=0.5)+
scale_fill_manual(values = getPalette(mypalette))+
scale_color_manual(values = getPalette(mypalette))+
facet_grid(obstime~drug,scales = "free")+
theme_bw()
## Warning: Ignoring unknown aesthetics: bins
## `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.

Có sự liên hệ giữa CD4 và thời gian sống của bệnh nhân
mypalette = length(unique(aids$drug))
getPalette = colorRampPalette(brewer.pal(9, "Spectral"))
aids%>%
ggplot(aes(x=CD4,y=Time))+
geom_point(alpha=0.5,aes(color=drug))+
geom_smooth(aes(x=CD4,y=Time,fill=drug,linetype=drug),
color="black",
method="lm",formula = y ~ splines::bs(x,3),alpha=0.4)+
scale_color_manual(values = getPalette(mypalette))+
theme_bw()

mypalette = 12
getPalette = colorRampPalette(brewer.pal(9, "Spectral"))
aids%>%
ggplot(aes(x=CD4,y=Time))+
geom_boxplot(aes(group=cut_width(CD4,2.5),
fill=cut_width(CD4,2.5),
color=cut_width(CD4,2.5)),
alpha=0.5,
show.legend = F)+
geom_smooth(aes(x=CD4,y=Time,linetype=drug),
color="black",
method="lm",formula = y ~ splines::bs(x,3),alpha=0.4)+
scale_fill_manual(values = getPalette(mypalette))+
scale_color_manual(values = getPalette(mypalette))+
theme_bw()

Tác động của điều trị lên nguy cơ tử vong
Sau đây là 1 vài biểu đồ trình bày 3 hàm Survival, Nguy cơ tích lũy và CDF, cho thấy thay đổi của nguy cơ tử vong theo thời gian.
ggplot(aids,
aes(time = Time, status = death, fill = drug, color = drug))+
geom_km()+
geom_kmband()+
theme_bw()

ggplot(aids,
aes(time = Time, status = death, fill = drug, color = drug))+
geom_km(trans = "cumhaz")+
geom_kmband(trans = "cumhaz")+
theme_bw()+ ylab("Cumulative hazard")

ggplot(aids,
aes(time = Time, status = death, fill = drug, color = drug))+
geom_km(trans = "event")+
geom_kmband(trans = "event")+
theme_bw()+ ylab("Cumulative Events (CDF)")

library(survminer)
## Loading required package: ggpubr
## Warning: package 'ggpubr' was built under R version 3.4.1
library(survival)
fit <- survfit(Surv(Time, death) ~ drug, data = aids)
ggsurvplot(fit,
data = aids,
size=1,
conf.int = TRUE,
pval = TRUE,
risk.table = TRUE,
risk.table.col = "strata",
risk.table.height = 0.3,
ggtheme = theme_bw(),
risk.table.y.text.col = T,
risk.table.y.text = FALSE
)

fit <- survfit(Surv(Time, death) ~ drug, data = aids)
Một mô hình Cox đơn thuần không đủ khả năng để phát hiện sự khác biệt về hiệu ứng giữa 2 loại thuốc điều trị
coxphmod0<- coxph(Surv(Time, death) ~ drug, data = aids)
summary(coxphmod0)
## Call:
## coxph(formula = Surv(Time, death) ~ drug, data = aids)
##
## n= 1405, number of events= 412
##
## coef exp(coef) se(coef) z Pr(>|z|)
## drugddI 0.13022 1.13908 0.09859 1.321 0.187
##
## exp(coef) exp(-coef) lower .95 upper .95
## drugddI 1.139 0.8779 0.9389 1.382
##
## Concordance= 0.528 (se = 0.013 )
## Rsquare= 0.001 (max possible= 0.983 )
## Likelihood ratio test= 1.75 on 1 df, p=0.1865
## Wald test = 1.74 on 1 df, p=0.1866
## Score (logrank) test = 1.75 on 1 df, p=0.1863
ggcoxdiagnostics(coxphmod0)
## Warning in simpleLoess(y, x, w, span, degree = degree, parametric =
## parametric, : pseudoinverse used at -0.064417
## Warning in simpleLoess(y, x, w, span, degree = degree, parametric =
## parametric, : neighborhood radius 0.13087
## Warning in simpleLoess(y, x, w, span, degree = degree, parametric =
## parametric, : reciprocal condition number 5.5233e-030
## Warning in simpleLoess(y, x, w, span, degree = degree, parametric =
## parametric, : There are other near singularities as well. 0.017127
## Warning in predLoess(object$y, object$x, newx = if
## (is.null(newdata)) object$x else if (is.data.frame(newdata))
## as.matrix(model.frame(delete.response(terms(object)), : pseudoinverse used
## at -0.064417
## Warning in predLoess(object$y, object$x, newx = if
## (is.null(newdata)) object$x else if (is.data.frame(newdata))
## as.matrix(model.frame(delete.response(terms(object)), : neighborhood radius
## 0.13087
## Warning in predLoess(object$y, object$x, newx = if
## (is.null(newdata)) object$x else if (is.data.frame(newdata))
## as.matrix(model.frame(delete.response(terms(object)), : reciprocal
## condition number 5.5233e-030
## Warning in predLoess(object$y, object$x, newx = if
## (is.null(newdata)) object$x else if (is.data.frame(newdata))
## as.matrix(model.frame(delete.response(terms(object)), : There are other
## near singularities as well. 0.017127

ggforest(coxphmod0)
## Warning in .get_data(model, data = data): The `data` argument is not
## provided. Data will be extracted from model fit.
## Warning: Removed 1 rows containing missing values (geom_errorbar).

Khi biến số CD4 được đưa vào mô hình Cox, lúc này mọi thứ có vẻ khả quan hơn chút ít: Mô hình cho thấy CD4 thật sự có liên hệ với nguy cơ tử vong. sqrt(CD4) giảm 1 đơn vị làm tăng nguy cơ tử vong thêm 1.18 lần.
Tuy nhiên, mô hình này chưa tốt và độ phù hợp dữ liệu kém. Việc dùng trực tiếp CD4 như 1 hiệp biến số có thật sự đúng đắn chưa ? Câu trả lời là chưa ! Tuy CD4 có liên hệ với nguy cơ tử vong, nhưng liên hệ giữa chúng không thể được khảo sát hoàn toàn bởi mô hình Cox, vì mô hình Cox chỉ cho phép khảo sát 1 hiệp biến số độc lập, nằm bên ngoài thực thể bệnh lý (thí dụ phân nhóm điều trị, ô nhiễm môi trường, nhiễm trùng…) trong khi CD4 là một hiệp biến số nội tại, nó nằm ngay trong căn bệnh.
coxphmod<- coxph(Surv(Time, death) ~ drug+CD4, data = aids)
summary(coxphmod)
## Call:
## coxph(formula = Surv(Time, death) ~ drug + CD4, data = aids)
##
## n= 1405, number of events= 412
##
## coef exp(coef) se(coef) z Pr(>|z|)
## drugddI 0.21579 1.24085 0.09881 2.184 0.029 *
## CD4 -0.16292 0.84966 0.01463 -11.133 <2e-16 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## exp(coef) exp(-coef) lower .95 upper .95
## drugddI 1.2408 0.8059 1.0224 1.5060
## CD4 0.8497 1.1769 0.8256 0.8744
##
## Concordance= 0.683 (se = 0.015 )
## Rsquare= 0.114 (max possible= 0.983 )
## Likelihood ratio test= 169.6 on 2 df, p=0
## Wald test = 126 on 2 df, p=0
## Score (logrank) test = 138.6 on 2 df, p=0
ggcoxdiagnostics(coxphmod)

ggforest(coxphmod)
## Warning in .get_data(model, data = data): The `data` argument is not
## provided. Data will be extracted from model fit.
## Warning: Removed 1 rows containing missing values (geom_errorbar).

Mô hình liên hợp: tóm tắt
Mô hình liên hợp (Joint model) là một phương pháp thống kê cho phép hợp nhất hai bộ phận : sự biến thiên phụ thuộc thời gian của một biến định lượng (longitudinal response ; thí dụ marker trong 1 bệnh lý) và nguy cơ xảy ra một sự kiện theo thời gian (survival analysis), nhằm khảo sát mối liên hệ giữa chúng.
Một cách đơn giản tối đa, nguyên tắc của mô hình liên hợp có thể được mô tả như sau :
1 mô hình liên hợp có 2 bộ phận bên trong nó (như 2 bánh xe răng cưa) : Một là mô hình phân tích sự kiện (Cox-PH model), và 1 mô hình phân tích phương sai phụ thuộc thời gian với yếu tố ngẫu nhiên (Mixed model).
- Bộ phận Survival có mục tiêu đo lường mối liên hệ giữa diễn tiến của 1 biến số định lượng (marker M) và nguy cơ xảy ra 1 biến cố (E) theo thời gian. Lưu ý 1 chi tiết quan trọng, đó là giá trị của M tại thời điểm t là 1 giá trị Hư mà Thực (có thực, nhưng không thể hoặc chưa thể xác định được). Điều này mới nghe có vẻ kì cục, nhưng có thể hiểu được, thí dụ nếu thời điểm t rơi vào khoảng giữa 2 cuộc hẹn trong lịch, thì ta không thể nào biết marker khi đó có giá trị bao nhiêu, nhưng trong cơ thể của bệnh nhân khi đó vẫn diễn ra sự thay đổi. Do đó M được khảo sát như 1 bệnh sử tính đến thời điểm t.
Bộ phận survival có vai trò chủ chốt trong mô hình, vì nó cho phép xác định tham số đo lường hiệu ứng của longitudinal outcome M lên nguy cơ xảy ra E.
Trong phần survival còn có hàm nguy cơ gốc h0(t), có thể xem như cơ số cho hàm EXP đi sau. Hàm h0 trong mô hình Cox cũng có nhưng không được xác định.Trong Joint model, h0 phải được xác định, có thể hình dung là chia nhỏ khoảng thời gian khảo sát ra nhiều đoạn thường bằng Piece wise, B-spline, cubic splines … Việc làm này cũng cần phải cẩn trọng vì có nguy cơ gây overfitting. Do đó, có quy tắc đặt ra là giới hạn tổng số biến (bao gồm bậc của hàm spline và biến độc lập trong mô hình) từ 1/10 đến 1/20 tổng số sự kiện trong mẫu.
- Bộ phận thứ 2 là mô hình Longitudinal. Nhiệm vụ của nó là ước lượng càng chính xác càng tốt lịch sử diễn tiến của biến M (marker). Đây là móc nối giữa 2 mô hình bộ phận.
M được ước tính dựa trên : yếu tố thời gian, matrix mô hình, hiệu ứng cố định (beta) và hiệu ứng ngẫu nhiên (cá thể), và sai số ngẫu nhiên (giả định có phân phối chuẩn với trung bình =0 và phương sai sigma^2).
Mô hình longitudinal có bản chất là 1 linear mixed model có chứa biến số thời gian. Sau này, các tác giả còn mở rộng phần Longitidinal bằng cách đưa thêm vào các thành phần bất định khác.
Ta thử nhìn lại 1 lần nữa 2 quá trình song song: nguy cơ tử vong tích lũy theo thời gian, và CD4 thay đổi theo thời gian:
lp=aids%>%mutate(.,Death=ifelse(.$death==1,"Death","Survived"))%>%
ggplot()+
geom_smooth(aes(x=as.numeric(Time),y=CD4,fill=drug,linetype=drug),
color="black",alpha=0.4)+
scale_color_manual(values = getPalette(mypalette))+
theme_bw()
hz=ggplot(aids,
aes(time = Time, status = death, fill = drug, color = drug))+
geom_km(trans = "cumhaz")+
geom_kmband(trans = "cumhaz")+
theme_bw()+ ylab("Cumulative hazard")
gridExtra::grid.arrange(hz,lp,ncol=1)
## `geom_smooth()` using method = 'loess'

Dựng Joint model bằng package JM
Đầu tiên, ta dựng 2 mô hình Longitudinal và Cox-PH trong package JM:
# Tạo dataset cho mô hình Cox
aids%>%.[!duplicated(.$patient),]->aids.id
#mixed longitudinal submodel
longcomp=lme(CD4 ~ obstime+ obstime:drug,
random = ~ obstime | patient,
data = aids)
longcomp%>%summary()
## Linear mixed-effects model fit by REML
## Data: aids
## AIC BIC logLik
## 7147.575 7184.295 -3566.788
##
## Random effects:
## Formula: ~obstime | patient
## Structure: General positive-definite, Log-Cholesky parametrization
## StdDev Corr
## (Intercept) 4.5901582 (Intr)
## obstime 0.1738082 -0.155
## Residual 1.7497905
##
## Fixed effects: CD4 ~ obstime + obstime:drug
## Value Std.Error DF t-value p-value
## (Intercept) 7.188833 0.22215874 936 32.35899 0.0000
## obstime -0.163451 0.02080804 936 -7.85519 0.0000
## obstime:drugddI 0.028272 0.02970929 936 0.95163 0.3415
## Correlation:
## (Intr) obstim
## obstime -0.160
## obstime:drugddI 0.000 -0.682
##
## Standardized Within-Group Residuals:
## Min Q1 Med Q3 Max
## -4.32530054 -0.41785802 -0.04720642 0.40631129 4.32727623
##
## Number of Observations: 1405
## Number of Groups: 467
# Time event submodel (CoxPH)
coxcomp=coxph(Surv(Time, death) ~ drug,
data = aids.id,
x = TRUE)
coxcomp%>%summary()
## Call:
## coxph(formula = Surv(Time, death) ~ drug, data = aids.id, x = TRUE)
##
## n= 467, number of events= 188
##
## coef exp(coef) se(coef) z Pr(>|z|)
## drugddI 0.2102 1.2339 0.1462 1.437 0.151
##
## exp(coef) exp(-coef) lower .95 upper .95
## drugddI 1.234 0.8104 0.9264 1.643
##
## Concordance= 0.531 (se = 0.019 )
## Rsquare= 0.004 (max possible= 0.99 )
## Likelihood ratio test= 2.07 on 1 df, p=0.15
## Wald test = 2.07 on 1 df, p=0.1506
## Score (logrank) test = 2.07 on 1 df, p=0.1498
Hàm jointModel sẽ hợp nhất 2 mô hình bộ phận lại với nhau, với hàm nguy cơ gốc h0 được ước tính bằng phương pháp Spline, Proportional hazard và GH.Quy trình này sẽ mất khá nhiều thời gian, bạn nên kiên nhẫn chờ đến khi mô hình converged.
Package JM tương thích với nhiều hàm trong base R, thí dụ: summary( ) để xem nội dung model.
jointmod=jointModel(lmeObject=longcomp,
survObject=coxcomp,
timeVar = "obstime",
method = "spline-PH-GH")
summary(jointmod)
##
## Call:
## jointModel(lmeObject = longcomp, survObject = coxcomp, timeVar = "obstime",
## method = "spline-PH-GH")
##
## Data Descriptives:
## Longitudinal Process Event Process
## Number of Observations: 1405 Number of Events: 188 (40.3%)
## Number of Groups: 467
##
## Joint Model Summary:
## Longitudinal Process: Linear mixed-effects model
## Event Process: Relative risk model with spline-approximated
## baseline risk function
## Parameterization: Time-dependent
##
## log.Lik AIC BIC
## -4334.627 8705.254 8779.888
##
## Variance Components:
## StdDev Corr
## (Intercept) 4.5345 (Intr)
## obstime 0.1685 -0.0589
## Residual 1.8752
##
## Coefficients:
## Longitudinal Process
## Value Std.Err z-value p-value
## (Intercept) 7.1944 0.1346 53.4509 <0.0001
## obstime -0.1872 0.0211 -8.8893 <0.0001
## obstime:drugddI 0.0192 0.0272 0.7055 0.4805
##
## Event Process
## Value Std.Err z-value p-value
## drugddI 0.3520 0.1578 2.2299 0.0258
## Assoct -0.2957 0.0380 -7.7885 <0.0001
## bs1 -3.8251 0.5726 -6.6806 <0.0001
## bs2 -1.0674 0.5815 -1.8357 0.0664
## bs3 -3.9097 0.6275 -6.2310 <0.0001
## bs4 -1.2185 0.3767 -3.2351 0.0012
## bs5 -2.7493 0.4508 -6.0981 <0.0001
## bs6 -2.0172 0.7200 -2.8019 0.0051
## bs7 -2.7028 1.4141 -1.9113 0.0560
## bs8 -1.2243 2.7616 -0.4433 0.6575
## bs9 -6.7377 7.3450 -0.9173 0.3590
##
## Integration:
## method: Gauss-Hermite
## quadrature points: 15
##
## Optimization:
## Convergence: 0
Diễn giải kết quả Joint model
Đầu tiên, kết quả cho biết giá trị AIC, BIC và Log likelihood của model
Phần thứ nhất là Fixed effect, khảo sát sự thay đổi của CD4 theo thời gian (phần Longitudinal response), và liệu có sự tương tác với thuốc điều trị (nhóm DDi) hay không ?. Câu trả lời là : CD4 giảm theo thời gian, trung bình -0.187 đơn vị mỗi tháng, tương tác thuốc và thời gian không có ý nghĩa.
Phần thứ hai là khảo sát về biến cố tử vong (phần Survival). Những tham số hồi quy có thể được diễn giải như 1 mô hình Cox cổ điển. Phần này cho ta biết nhiều thông tin thú vị mà bản thân mô hình Cox trước đó không làm được. Đầu tiên, đó là yếu tố Association, hay liên hệ giữa bệnh sử CD4 và thuốc điều trị đối với nguy cơ tử vong :
Ta có thể diễn giải : tại bất cứ thời điểm nào, trên bệnh nhân AIDS sử dụng thuốc DDI , mỗi đơn vị CD4 giảm đi sẽ làm tăng nguy cơ tử vong lên 34% = exp(0.2957)-1
Mô hình sử dụng B-spline bậc 9 (cắt khoảng thời gian 21 tháng ra làm 9 đoạn), tuy nhiên kết quả chỉ có ý nghĩa thống kê tại ps1,ps3,ps4,bs5 và bs6
Suy diễn thống kê và kiểm tra mô hình
Ta có thể tính thủ công khoảng tin cậy 97.5% cho các tham số hồi quy (hiệu ứng) quan trọng
# Confidence intervals for the regression coefficients
confint(jointmod, parm = "Longitudinal")
## 2.5 % est. 97.5 %
## (Intercept) 6.93054678 7.19435267 7.45815857
## obstime -0.22851664 -0.18723420 -0.14595175
## obstime:drugddI -0.03412007 0.01919035 0.07250077
# Confidence intervals for the hazard ratios
confint(jointmod, parm = "Event")%>%exp()
## 2.5 % est. 97.5 %
## drugddI 1.0435252 1.4218586 1.9373579
## Assoct 0.6906397 0.7439959 0.8014741
Ta có thể làm 1 kiểm định Wald đa biến cho toàn bộ các tham số:
# Multivariate Wald test for simultaneously testing all
anova(jointmod,process="both")
##
## Marginal Wald Tests Table
##
## Longitudinal Process
## Chisq df Pr(>|Chi|)
## obstime 117.4799 2 <1e-04
## drug 0.4978 1 0.4805
## obstime:drug 0.4978 1 0.4805
##
## Event Process
## Chisq df Pr(>|Chi|)
## drug 4.9724 1 0.0258
## Assoct 60.6612 1 <1e-04
Để kiểm tra phần phân tích sự kiện, ta có thể làm 1 phân tích phương sai : ta dựng1 mô hình Cox tối giản rồi cũng làm joint model với nó, sau đó dùng ANOVA so sánh 2 mô hình joint model cũ và mới
#Anova for Event process
cox2=coxph(Surv(Time, death) ~ 1, data = aids.id, x = TRUE)
jointmod2=jointModel(lmeObject=longcomp,
survObject=cox2,
timeVar = "obstime",
method = "spline-PH-GH")
anova(jointmod2, jointmod)
##
## AIC BIC log.Lik LRT df p.value
## jointmod2 8708.33 8778.82 -4337.16
## jointmod 8705.25 8779.89 -4334.63 5.08 1 0.0243
Tiếp theo, ta có thể kiểm tra hiệu ứng liên hợp giữa Thuốc và CD4 bằng 1 scoretest
MLmod=update(longcomp, method = "ML")
pwc=piecewiseExp.ph(coxcomp)
init=list(betas = fixef(MLmod),
sigma = MLmod$sigma,
D = getVarCov(MLmod),
gammas = coef(pwc)[1], alpha = 0,
xi = exp(coef(pwc)[-1]))
JMScoreTest=jointModel(MLmod,
coxcomp,
timeVar = "obstime",
method = "spline-PH-GH",
init = init,
only.EM = TRUE)
score.vector=JMScoreTest$Score
inv.Hessian=vcov(JMScoreTest)
ScoreStat=c(t(score.vector) %*% inv.Hessian %*% score.vector)
cbind("Statistic" = ScoreStat, "df" = 1,
"p-value" = pchisq(ScoreStat, 1, lower.tail = FALSE))
## Statistic df p-value
## [1,] 0.8131939 1 0.3671777
Sai số và Marginal effect
plot(jointmod)




#Standardized residuals
resMarg=residuals(jointmod,
process = "Longitudinal",
type = "Marginal")
fitMarg=fitted(jointmod,
process = "Longitudinal",
type = "Marginal")
myColor=rev(RColorBrewer::brewer.pal(11, "Spectral"))
myColor_scale_fill=scale_fill_gradientn(colours = myColor)
myColor_scale_col=scale_color_gradientn(colours = rev(RColorBrewer::brewer.pal(11, "Spectral")))
aids%>%mutate(.,Fitted=fitMarg,Residual=resMarg)%>%
ggplot()+
geom_point(aes(x=Fitted,y=resMarg,color=Fitted))+
geom_smooth(aes(x=Fitted,y=resMarg),se=F,
method="lm",formula = y ~ splines::bs(x,3),color="black")+
geom_hline(yintercept = 0.0,color="blue",linetype=2)+
theme_bw()+
myColor_scale_col

aids%>%mutate(.,Fitted=fitMarg,Residual=resMarg)%>%
ggplot()+
geom_smooth(aes(x=obstime,y=fitMarg,color=patient),se=F,
method="lm",formula = y ~ x,show.legend = F)+
facet_wrap(~drug)+
theme_bw()

aids%>%mutate(.,Fitted=fitMarg,Residual=resMarg)%>%
ggplot()+
geom_smooth(aes(x=obstime,y=fitMarg,color=drug),se=F,
method="lm",formula = y ~ splines::bs(x))+
theme_bw()

# Event time marginal effect
EventRes=residuals(jointmod, process = "Event")
mit=fitted(jointmod, process = "Longitudinal",
type = "EventTime")
aids%>%mutate(.,MartingaleRes=EventRes,ssfvlo=mit)%>%
ggplot(aes(x=ssfvlo,y=MartingaleRes))+
geom_point(aes(color=ssfvlo),alpha=0.5,show.legend = F)+
geom_smooth(se=F,method="lm",formula = y ~ splines::bs(x),color="black")+
myColor_scale_col+
geom_hline(yintercept = 0.0,color="blue",linetype=2)+
theme_bw()+labs(y="Martingale residual",
x="Subject specific fitted values longitudinal outcome")

aids%>%mutate(.,MartingaleRes=EventRes,ssfvlo=mit)%>%
ggplot(aes(x=ssfvlo,y=MartingaleRes))+
geom_point(aes(color=drug),alpha=0.5,show.legend = F)+
geom_smooth(se=F,method="lm",formula = y ~ splines::bs(x),color="black")+
geom_hline(yintercept = 0.0,color="blue",linetype=2)+
theme_bw()+labs(y="Martingale residual",
x="Subject specific fitted values longitudinal outcome")+
facet_wrap(~drug)

#Residual of event CoxSnell
resCST=residuals(jointmod,
process = "Event",
type = "CoxSnell")
sfit=survfit(Surv(resCST,death)~1,data=aids.id)
plot(sfit, mark.time = FALSE, conf.int = TRUE,col=c("red","orange","red4"),
xlab = "Cox-Snell Residuals", ylab = "Survival Probability",
main = "Survival Function of Cox-Snell Residuals")
curve(exp(-x), from = 0, to = max(aids.id$Time), add = TRUE,
col = "blue", lwd = 2)

#Plot2
sfit2=survfit(Surv(resCST, death) ~ drug, data = aids.id)
plot(sfit2, mark.time = FALSE, conf.int = FALSE,col=c("red","blue"),
xlab = "Cox-Snell Residuals", ylab = "Survival Probability",
main = "Survival Function of Cox-Snell Residuals")
curve(exp(-x), from = 0,
to = max(aids.id$Time),
add = TRUE,
col = "purple", lwd = 2)

Ứng dụng: Tiên lượng cho cá thể
Một trong những ứng dụng tuyệt vời của Joint model là để tiên lượng sống còn cho cá thể từng bệnh nhân dựa vào bệnh sử của họ, thí dụ ở tháng thứ 6, dựa vào kết quả CD4 ở 3 lần trước, ta có thể tiên lượng khả năng sống của bệnh nhân đó cho tương lai nhiều tháng sau
Nhi chọn bất kì bệnh nhân nào (dĩ nhiên là phải censored) , thí dụ số 99
#Prediction with CI by MonteCarlo simulation
set.seed(123)
aids[aids$patient=="99",]
## patient Time death CD4 obstime drug gender prevOI AZT
## 292 99 13.03 0 4.898979 0 ddC male noAIDS intolerance
## 293 99 13.03 0 1.732051 6 ddC male noAIDS intolerance
## start stop event
## 292 0 6.00 0
## 293 6 13.03 0
#From the last time point
set.seed(333)
survPrbs=survfitJM(jointmod, idVar = "patient",
newdata = aids[aids$patient=="99",],
last.time = "obstime")
survPrbs
##
## Prediction of Conditional Probabilities for Event
## based on 200 Monte Carlo samples
##
## $`99`
## times Mean Median Lower Upper
## 1 6.0000 1.0000 1.0000 1.0000 1.0000
## 1 6.0368 0.9987 0.9988 0.9973 0.9995
## 2 6.6553 0.9771 0.9787 0.9519 0.9913
## 3 7.2738 0.9554 0.9584 0.9083 0.9833
## 4 7.8924 0.9323 0.9375 0.8667 0.9746
## 5 8.5109 0.9060 0.9131 0.8076 0.9644
## 6 9.1294 0.8750 0.8834 0.7434 0.9513
## 7 9.7479 0.8384 0.8472 0.6789 0.9402
## 8 10.3665 0.7965 0.8048 0.6113 0.9252
## 9 10.9850 0.7514 0.7615 0.5437 0.9066
## 10 11.6035 0.7070 0.7155 0.4800 0.8859
## 11 12.2221 0.6679 0.6745 0.4244 0.8685
## 12 12.8406 0.6369 0.6435 0.3738 0.8547
## 13 13.4591 0.6125 0.6175 0.3321 0.8434
## 14 14.0776 0.5895 0.5958 0.2965 0.8357
## 15 14.6962 0.5645 0.5691 0.2615 0.8236
## 16 15.3147 0.5361 0.5449 0.2142 0.8151
## 17 15.9332 0.5084 0.5176 0.1689 0.8069
## 18 16.5518 0.4824 0.4874 0.1304 0.7983
## 19 17.1703 0.4570 0.4567 0.1007 0.7872
## 20 17.7888 0.4330 0.4326 0.0810 0.7777
## 21 18.4074 0.4063 0.4015 0.0655 0.7657
## 22 19.0259 0.3764 0.3619 0.0460 0.7559
## 23 19.6444 0.3533 0.3424 0.0284 0.7470
## 24 20.2629 0.3365 0.3352 0.0133 0.7418
## 25 20.8815 0.3172 0.3147 0.0028 0.7398
## 26 21.5000 0.2430 0.2210 0.0000 0.6935
survPrbs[1]$summaries$`99`%>%as_tibble()->tdf
rbind(tdf,c(0,rep(1,4)))->tdf
tdf%>%
ggplot(aes(x=times))+
geom_ribbon(aes(ymin=Lower,ymax=Upper),alpha=0.5,fill="gold")+
geom_line(aes(y=Median),color="red3",size=1,linetype=1)+
geom_line(aes(y=Mean),color="blue",size=1,linetype=1)+
geom_line(aes(y=Lower),color="black",size=1,linetype=2)+
geom_line(aes(y=Upper),color="black",size=1,linetype=2)+
theme_bw()+
labs(title="Patient N°99",x="Time",y="Predicted survival probability",caption="Joint Model")

Hoặc bệnh nhân số 300
survPrbs3=survfitJM(jointmod, idVar = "patient",
newdata = aids[aids$patient=="300",],
last.time = "obstime")
survPrbs3
##
## Prediction of Conditional Probabilities for Event
## based on 200 Monte Carlo samples
##
## $`300`
## times Mean Median Lower Upper
## 1 2.0000 1.0000 1.0000 1.0000 1.0000
## 1 2.3256 0.9867 0.9874 0.9732 0.9950
## 2 2.9441 0.9587 0.9611 0.9176 0.9845
## 3 3.5626 0.9291 0.9336 0.8589 0.9730
## 4 4.1812 0.9003 0.9065 0.7991 0.9617
## 5 4.7997 0.8736 0.8814 0.7496 0.9509
## 6 5.4182 0.8493 0.8573 0.7018 0.9407
## 7 6.0368 0.8273 0.8367 0.6576 0.9322
## 8 6.6553 0.8067 0.8189 0.6159 0.9244
## 9 7.2738 0.7866 0.8027 0.5763 0.9171
## 10 7.8924 0.7656 0.7847 0.5309 0.9093
## 11 8.5109 0.7421 0.7640 0.4825 0.9007
## 12 9.1294 0.7147 0.7349 0.4287 0.8865
## 13 9.7479 0.6829 0.7086 0.3703 0.8717
## 14 10.3665 0.6471 0.6664 0.3116 0.8536
## 15 10.9850 0.6094 0.6279 0.2586 0.8391
## 16 11.6035 0.5734 0.5943 0.2101 0.8264
## 17 12.2221 0.5427 0.5619 0.1582 0.8154
## 18 12.8406 0.5193 0.5372 0.1248 0.8063
## 19 13.4591 0.5015 0.5188 0.1065 0.7985
## 20 14.0776 0.4854 0.4976 0.0915 0.7912
## 21 14.6962 0.4683 0.4774 0.0743 0.7837
## 22 15.3147 0.4492 0.4618 0.0565 0.7765
## 23 15.9332 0.4304 0.4417 0.0452 0.7689
## 24 16.5518 0.4124 0.4222 0.0361 0.7613
## 25 17.1703 0.3946 0.4035 0.0284 0.7542
## 26 17.7888 0.3784 0.3848 0.0222 0.7484
## 27 18.4074 0.3613 0.3648 0.0138 0.7441
## 28 19.0259 0.3423 0.3381 0.0079 0.7401
## 29 19.6444 0.3266 0.3227 0.0030 0.7347
## 30 20.2629 0.3132 0.3074 0.0019 0.7225
## 31 20.8815 0.2894 0.2959 0.0001 0.6893
## 32 21.5000 0.2337 0.2218 0.0000 0.6419
survPrbs3[1]$summaries$`300`%>%as_tibble()->tdf
rbind(tdf,c(0,rep(1,4)))->tdf
tdf%>%
ggplot(aes(x=times))+
geom_errorbar(aes(ymin=Lower,ymax=Upper,color=reorder(times,Median)),show.legend = F)+
geom_point(aes(y=Median,fill=reorder(times,Median)),shape=21,color="black",show.legend = F)+
theme_bw()+
labs(title="Patient N°300",x="Time",y="Predicted survival probability",caption="Joint Model")

Vì chứa cả 2 thành phần longitudinal và survival, một mô hình Joint model còn cho phép tiên lượng giá trị marker (CD4) theo thời gian:
Thí dụ ta chọn ngẫu nhiên bệnh nhân số 55. Ta có thể tiên lượng CD4 cho tương lai trong 4 hoàn cảnh khác nhau: ngay từ lần hẹn đầu tiên, vào thời điểm tháng thứ 2, 6, và 12
aids[aids$patient==55,]->newdat
longPreds=vector("list", nrow(newdat))
for (i in 1:nrow(newdat)) {
set.seed(123)
longPreds[[i]]=predict(jointmod,
newdata = newdat[1:i, ],
type = "Subject",
interval = "confidence",
returnData = TRUE,
idVar="patient")
longPreds[[i]]$FollowUp=round(max(newdat[1:i, "obstime"]), 1)
}
longPreds%<>%do.call(rbind,.)%>%as_tibble()
longPreds%>%
ggplot(aes(x=obstime))+
geom_ribbon(aes(ymin=low,ymax=upp),alpha=0.5,fill="gold")+
geom_line(aes(y=pred),color="red3",size=1)+
geom_line(aes(y=low),color="black",size=1,linetype=2)+
geom_line(aes(y=upp),color="black",size=1,linetype=2)+
theme_bw()+
facet_wrap(~FollowUp)+
labs(title="Patient N°55",x="Time",y="Predicted CD4",caption="Joint Model")
## Warning: Removed 1 rows containing missing values (geom_path).
## Warning: Removed 1 rows containing missing values (geom_path).

Cho bệnh nhân số 55 này, ta có thể kiểm tra độ chính xác của tiên lượng trong 4 hoàn cảnh: vào tháng thứ 2,6,12 và 18
# Performance evaluation
set.seed(123)
newdat$t0=as.numeric(newdat$obstime == 0)
rocaids= rocJM(jointmod,
dt = c(2,6,12,18),
data = newdat,
optThr="youden",
M=1000,burn.in=500,
idVar="patient")
plot(rocaids, legend = TRUE)

rocaids
##
## Areas under the time-dependent ROC curves
##
## Estimation: Monte Carlo (500 samples)
## Difference: absolute, lag = 1 (0)
## Thresholds range: (-1.41, 40.89)
##
## Case: 55
## Recorded time(s): 0, 2, 6, 12
## dt t + dt AUC Cut
## 2 14 0.7988 5.3541
## 6 18 0.8281 5.5233
## 12 24 0.8594 7.2154
## 18 30 0.8817 8.7383
Kết luận:
Trong bài này, các bạn đã làm quen với Joint model, một trong những công cụ cần thiết cho nghiên cứu lâm sàng.
Mô hình liên hợp (Joint model) cho phép chúng ta hợp nhất hai bộ phận tưởng chừng như độc lập trong một thử nghiệm lâm sàng, đó là bài toán Longitudinal analysis và bài toán Survival analysis. Việc hợp nhất này cho phép giải quyết hàng loạt câu hỏi, như :
Chứng minh về liên hệ giữa bệnh sử thông qua 1 marker và nguy cơ tử vong,
chứng minh 1 surrogate marker có thể dùng để đánh giá hiệu quả điều trị của thuốc,
tiên lượng diễn tiến của marker và nguy cơ tử vong cho từng bệnh nhân riêng biệt một khi đã biết bệnh sử của cá nhân này.
HY vọng bài viết tạo được cảm hứng cho các bạn bắt đầu tìm hiểu về phương pháp này.
Một bài khác sẽ được thực hiện để đi theo con đường Bayes.
Xin cảm ơn và hẹn gặp lại.
LS0tDQp0aXRsZTogIk3DtCBow6xuaCBsacOqbiBo4bujcCINCnN1YnRpdGxlOiAiUGjhuqduIDE6IFRyxrDhu51uZyBwaMOhaSBSRSBNYXhpbXVtIExpa2VsaWhvb2QiDQphdXRob3I6ICJMw6ogTmfhu41jIEto4bqjIE5oaSINCmRhdGU6ICIyNSBUaMOhbmcgOCAyMDE3Ig0Kb3V0cHV0Og0KICBodG1sX2RvY3VtZW50OiANCiAgICBjb2RlX2Rvd25sb2FkOiB0cnVlDQogICAgY29kZV9mb2xkaW5nOiBoaWRlDQogICAgbnVtYmVyX3NlY3Rpb25zOiB5ZXMNCiAgICB0aGVtZTogImRlZmF1bHQiDQogICAgdG9jOiBUUlVFDQogICAgdG9jX2Zsb2F0OiBUUlVFDQotLS0NCg0KYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9DQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUpDQoNCmxpYnJhcnkoSk0pDQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCmxpYnJhcnkobWFncml0dHIpDQpsaWJyYXJ5KGdna20pDQoNCmBgYA0KDQohW10oam9pbnRtMS5wbmcpDQoNCiMgROG6q24gbmjhuq1wDQoNCsSQw6J5IGzDoCBt4buZdCBiw6BpIHRo4buxYyBow6BuaCB24bubaSB0aGFtIHbhu41uZyBjYW8sIMSRw7MgbMOgIGdp4bubaSB0aGnhu4d1IMSR4bq/biBjw6FjIGLhuqFuIHNpbmggdmnDqm4gWSBraG9hIHbDoCBiw6FjIHPEqSB24buBIG3DtCBow6xuaCBsacOqbiBo4bujcC4gxJDDonkgbMOgIG3hu5l0IGPDtG5nIGPhu6UgdGluaCB04bq/LCBwaOG7qWMgdOG6oXAgdsOgIGjhu691IMOtY2ggduG7m2kgbmhp4buBdSDhu6luZyBk4bulbmcgbMOibSBzw6BuZzsgdHV5IG5oacOqbiBuw7MgZ+G6p24gbmjGsCBjaOG7iSBsxrB1IGjDoG5oIG7hu5lpIGLhu5kgdHJvbmcgZ2nhu5tpIHRo4buRbmcga8OqIGNodXnDqm4gbmdoaeG7h3AgKGNobyBjw6FjIGjDo25nIGTGsOG7o2MgcGjhuqltIGNo4bqzbmcgaOG6oW4pLCBjaOG7qSBraMO0bmcgxJHGsOG7o2MgcGjhu5UgYmnhur9uIGNobyBuZ8aw4budaSB0aOG7sWMgaMOgbmggbMOibSBzw6BuZy4NCg0KUGjGsMahbmcgcGjDoXAgbsOgeSDEkcaw4bujYyBwaMOhdCB0cmnhu4NuIGtob+G6o25nIMSR4bqndSB0aOG6rXAgbmnDqm4gMTk5MCx2w6AgxJHhurdjIGJp4buHdCBkw6BuaCByacOqbmcgY2hvIG5nw6BuaCBZIGtob2EgKHThuqV0IGPhuqMgZGF0YXNldCB0aOG7sWMgaMOgbmggxJHhu4F1IGzDoCBjbGluaWNhbCB0cmlhbCksIHbDrCBuw7MgY2hvIHBow6lwIGtp4buDbSBjaOG7qW5nIG5o4buvbmcgZ2nhuqMgdGh1eeG6v3QgcGjhu6ljIHThuqFwLCBraGFpIHRow6FjIHRow7RuZyB0aW4gaOG7r3Ugw61jaCB04burIGPDoWMgbmdoacOqbiBj4bupdSB0aOG7rSBuZ2hp4buHbSB0aHXhu5FjLiANCg0KQ2hvIMSR4bq/biBuxINtIDIwMTUsIGzDvSB0aHV54bq/dCB24buBIEpvaW50IG1vZGVsIMSRw6MgaG/DoG4gdGhp4buHbiB2w6AgY8OzIHRo4buDIHRp4bq/cCBj4bqtbiB0cm9uZyBSIGLhurFuZyB04bubaSAzIGPDoWNoIDogdHLGsOG7nW5nIHBow6FpIEJheWVzLCDGsOG7m2MgbMaw4bujbmcgaOG7o3AgbMO9IGPhu7FjIMSR4bqhaSAoUkUgTWF4aW11bSBsaWtlbGlob29kKSB2w6AgSGllcmFyY2hpY2FsIGxpa2VsaWhvb2QuIA0KDQpUcm9uZyBiw6BpIG7DoHksIE5oaSBz4bq9IGdp4bubaSB0aGnhu4d1IHRyxrDhu5tjIGPDoWNoIHRp4bq/cCBj4bqtbiBkw7luZyBSRU1MLCBjw6FjaCBsw6BtIEJheWVzIHPhur0gxJHGsOG7o2MgaMaw4bubbmcgZOG6q24gdsOgbyAxIGThu4twIGtow6FjLiBUcm9uZyBiw6BpIE5oaSBjaOG7pyB54bq/dSBkw7luZyBwYWNrYWdlIEpNIGPhu6dhIHTDoWMgZ2nhuqMgRGltaXRyaXMgUml6b3BvdWxvcyAoMjAxMi0yMDE2KS4NCg0KTeG7pWMgdGnDqnUgY+G7p2EgYsOgaSB0aOG7sWMgaMOgbmggZ+G7k20gY8OzOg0KDQoxKSBUaMSDbSBkw7IgZOG7ryBsaeG7h3UgY+G7p2EgbeG7mXQgTG9uZ2l0dWRpbmFsIHbDoCBUaW1lLUV2ZW50IGRhdGENCjIpIEThu7FuZyBKb2ludCBtb2RlbCB04burIENveC1QSCBtb2RlbCB2w6AgTWl4ZWQgbGluZWFyIG1vZGVsDQozKSBEaeG7hW4gZ2nhuqNpIGvhur90IHF14bqjIG3DtCBow6xuaA0KNCkgS2nhu4NtIHRyYSBwaOG6qW0gY2jhuqV0IG3DtCBow6xuaA0KNSkg4buobmcgZOG7pW5nIG3DtCBow6xuaCDEkeG7gyB0acOqbiBsxrDhu6NuZyBjaG8gY8OhIHRo4buDIGLhu4duaCBuaMOibg0KDQpEbyB0w61uaCBjaOG6pXQgcGjhu6ljIHThuqFwIGPhu6dhIHbhuqVuIMSR4buBLCBOaGkga2h1eeG6v24ga2jDrWNoIGPDoWMgYuG6oW4gdMOsbSDEkeG7jWMgdGjDqm0gdMOgaSBsaeG7h3UgbMO9IHRodXnhur90IChiw6BpIG7DoHkgY2jhu4kgbmjhurFtIMSR4buDIHRo4bqjIHRow61uaOKApikuIEPhu6UgdGjhu4MgbMOgIHF1eeG7g24gc8OhY2ggOg0KDQohW10oam9pbm1ib29rLnBuZykNCg0KIyBEYXRhc2V0IEFJRFMNCg0KVHLGsOG7m2MgaOG6v3QgdGEgdOG6o2kgMSBz4buRIHBhY2thZ2UsZGF0YXNldCBhaWRzIG7hurFtIHRyb25nIHBhY2thZ2UgSk0NCg0KYGBge3J9DQpsaWJyYXJ5KEpNKSAjIHBhY2thZ2UgZOG7sW5nIEpvaW50IG1vZGVsDQpsaWJyYXJ5KHRpZHl2ZXJzZSkgIyBkYXRhIHdyYW5nbGluZyB2w6AgZ2dwbG90DQpsaWJyYXJ5KG1hZ3JpdHRyKSAjIFRvw6FuIHThu60gcGlwZQ0KbGlicmFyeShnZ2ttKSAjVGjEg20gZMOyIFRpbWUgZXZlbnQgZGF0YQ0KYGBgDQoNCkFJRFMgZGF0YXNldCBjw7Mgbmd14buTbiBn4buRYyB04burIG3hu5l0IG5naGnDqm4gY+G7qXUgKE5DKSB0aOG7rSBuZ2hp4buHbSBkxrDhu6NjIHBo4bqpbSBsw6JtIHPDoG5nIG7Eg20gMTk5NCB0csOqbiA0NjcgYuG7h25oIG5ow6JuIG5oaeG7hW0gSElWLiBN4bulYyB0acOqdSBj4bunYSBOQyBuw6B5IMSRw7MgbMOgIHNvIHPDoW5oIGhp4buHdSBxdeG6oyDEkWnhu4F1IHRy4buLIGdp4buvYSAyIGxv4bqhaSB0aHXhu5FjIGFudGlyZXRyb3ZpcmFsIGtow6FjIG5oYXUgdMOqbiBsw6AgRGlkYW5vc2luZSAobcOjID0gZGRJKSB2w6AgemFsY2l0YWJpbmUgKGRkQykuIE91dGNvbWUgY2jDrW5oIGPhu6dhIE5DIG7DoHkgbMOgIGJp4bq/biBj4buRIHThu60gdm9uZyB24bubaSB0aOG7nWkgZ2lhbiB0aGVvIGTDtWkga8OpbyBkw6BpIDIgbsSDbS4gVHV5IG5oacOqbiwgTkMgY8OybiBjw7MgMSBvdXRjb21lIHRo4bupIGhhaSBsw6Agc+G7kSBsxrDhu6NuZyBi4bqhY2ggY+G6p3UgQ0Q0IChn4buNaSB04bqvdCBsw6AgQ0Q0KS4gQ0Q0IMSRxrDhu6NjIGto4bqjbyBzw6F0IOG7nyB0aOG7nWkgxJFp4buDbSDEkeG6p3UgdGnDqm4sIHNhdSDEkcOzIG3hu5dpIGLhu4duaCBuaMOibiDEkcaw4bujYyBz4bqvcCB44bq/cCBuZ+G6q3Ugbmhpw6puIHbDoG8gMSB0cm9uZyAyIHBow6JuIG5ow7NtIHRy4buLIGxp4buHdSB2w6AgQ0Q0IHRp4bq/cCB04bulYyDEkcaw4bujYyBraOG6o28gc8OhdCDEkeG7i25oIGvhu7MgdOG6oWkgdGjDoW5nIHRo4bupIDIsNiwxMiB2w6AgMTguIA0KDQpWw6BvIGzDumMgTkMga+G6v3QgdGjDumMsIDE4OCBi4buHbmggbmjDom4gxJHDoyB04butIHZvbmcgdsOgIG5nxrDhu51pIHRhIGPDsyB04buVbmcgY+G7mW5nIDE0MDUgZ2nDoSB0cuG7iyBDRDQuIA0KDQpOaMawIHRhIHRo4bqleSwgxJHDonkgbMOgIG3hu5l0IG5naGnDqm4gY+G7qXUgY8OzIHRoaeG6v3Qga+G6vyBwaOG7qWMgdOG6oXAgduG7m2kgMiBvdXRjb21lcyBs4bqnbiBsxrDhu6N0IGzDoCA6IDEgc+G7sSBraeG7h24gKHThu60gdm9uZykgdsOgIDEgYmnhur9uIMSR4buLbmggbMaw4bujbmcgKENENCkgxJHGsOG7o2MgdGhlbyBkw7VpIHRoZW8gdGjhu51pIGdpYW4uIE5oxrAgduG6rXksIG3hu5dpIGLhu5kgcGjhuq1uIG7DoHkgdMawxqFuZyDhu6luZyB24bubaSBt4buZdCB0aGnhur90IGvhur8gbmdoacOqbiBj4bupdSBraMOhYyBuaGF1IDogTG9uZ2l0dWRpbmFsIChjaG8gQ0Q0KSB2w6AgVGltZS1FdmVudCAoU3Vydml2YWwpIGFuYWx5c2lzLiANCg0KRMSpIG5oacOqbiBjaMO6bmcgdGEgY8OzIHRo4buDIHjhu60gbMO9IHJpw6puZyB04burbmcgYuG7mSBwaOG6rW4gYuG6sW5nIDEgbcO0IGjDrG5oIGjhu5NpIHF1eSBo4buXbiBo4bujcCAoTWl4ZWQgbW9kZWwpIChjaG8gQ0Q0KSB2w6AgMSBtw7QgaMOsbmggaOG7k2kgcXV5IENveC1QSCAoY2hvIG5ndXkgY8ahIHThu60gdm9uZykuIFR1eSBuaGnDqm4sIHbhuqVuIMSR4buBIGtow7RuZyDEkcahbiBnaeG6o24gbmjGsCB0YSBuZ2jEqS4gxJDhu4MgYmnhur90IHRhIHBo4bqjaSDEkcawxqFuZyDEkeG6p3UgduG7m2kgbmjhu69uZyBraMOzIGtoxINuIG7DoG8sIE5oaSBz4bq9IHRoxINtIGTDsiBk4buvIGxp4buHdS4NCg0KIyBUaMSDbSBkw7IgZOG7ryBsaeG7h3UNCg0KVOG7lW5nIHPhu5EgYuG7h25oIG5ow6JuIGzDoCA0NjcNCg0KYGBge3IsbWVzc2FnZSA9IEZBTFNFLHdhcm5pbmc9RkFMU0V9DQphaWRzJHBhdGllbnQgJT4lDQogIHVuaXF1ZSgpICU+JQ0KICBsZW5ndGgoKQ0KYGBgDQoNClhlbSBj4bqldSB0csO6YyBk4buvIGxp4buHdSwgdGjDrSBk4bulIGNobyAxIGLhu4duaCBuaMOibiBOwrAyDQoNCmBgYHtyLG1lc3NhZ2UgPSBGQUxTRSx3YXJuaW5nPUZBTFNFfQ0KYWlkcyU+JWZpbHRlciguJHBhdGllbnQ9PTIpJT4la25pdHI6OmthYmxlKCkNCmBgYA0KDQpUaOG7nWkgZ2lhbiB0aGVvIGTDtWkgdHJ1bmcgYsOsbmggbMOgIDEzLjkgdGjDoW5nLCB0aOG6pXAgbmjhuqV0IGzDoCBraG/huqNuZyAyIHR14bqnbiwgY2FvIG5o4bqldCBsw6AgMjEgdGjDoW5nLiDEkGnhu4F1IG7DoHkgY2jhu6luZyB04buPIGzDoCB04buJIGzhu4cgdOG7rSB2b25nIHbDoCBt4bqldCBtw6F0IGThu68gbGnhu4d1IGtow6EgY2FvICh0aGVvIGThu7EgdMOtbmggdGjhu51pIGdpYW4gdGhlbyBkw7VpIHThu5FpIMSRYSAxOCB0aMOhbmcuDQoNCmBgYHtyfQ0Kc3VtbWFyeShhaWRzJFRpbWUpDQoNCmBgYA0KDQoqUGjhuqduIDE6IERp4buFbiB0aeG6v24gQ0Q0IHRoZW8gdGjhu51pIGdpYW4gKFRow6BuaCB04buRIExvbmdpdHVkaW5hbCkqDQoNCkJp4buDdSDEkeG7kyBzYXUgxJHDonkgdHLDrG5oIGLDoHkgZGnhu4VuIHRp4bq/biB0aGF5IMSR4buVaSBj4bunYSBDRDQgKGdoaSBjaMO6OiDEkcOjIMSRxrDhu6NjIGtoYWkgY8SDbiBi4bqtYyAyKSDhu58gbeG7l2kgY8OhIHRo4buDIGLhu4duaCBuaMOibiB2w6Aga2h1eW5oIGjGsOG7m25nIHRydW5nIGLDrG5oIGdp4buvYSAyIHBow6JuIG5ow7NtIMSRaeG7gXUgdHLhu4ssIHbDoCBnaeG7r2EgMiBuaMOzbSBjw7MgdsOgIGtow7RuZyBjw7MgdOG7rSB2b25nLg0KDQpCaeG7g3UgxJHhu5MgbsOgeSBjaG8gY2jDum5nIHRhIHRo4bqleSAzIHbhuqVuIMSR4buBOg0KDQoxKSDEkOG6p3UgdGnDqm4gdGEgY8OzIHRo4buDIHRo4bqleSDEkcOzIGzDoCBraMO0bmcgcGjhuqNpIGLhuqV0IGPhu6kgYuG7h25oIG5ow6JuIG7DoG8gY8WpbmcgY8OzIMSR4bunIDUgZ2nDoSB0cuG7iyBDRDQuIFbhuq15LGPDsyB24bqlbiDEkeG7gSB0aGnhur91IHPDs3QgZOG7ryBsaeG7h3UgdsOgIMSRw6J5IGzDoCB24bqlbiDEkeG7gSBjaHVuZyBjaG8gdOG6pXQgY+G6oyBuZ2hpw6puIGPhu6l1IGzDom0gc8OgbmcuIE5ndXnDqm4gbmjDom4gY+G7p2Egc8OzdCBk4buvIGxp4buHdSBjw7MgcuG6pXQgbmhp4buBdSA6IGLhu4duaCBuaMOibiBo4buneSBi4buPIGN14buZYyBo4bq5biwgYuG7h25oIG5ow6JuIHLhu51pIGLhu48gbmdoacOqbiBj4bupdSBnaeG7r2EgY2jhu6tuZywgbeG6q3UgYuG7h25oIHBo4bqpbSBi4buLIHRo4bqldCBs4bqhYywgdGhp4bq/dCBi4buLIMSRbyBoxrAgaOG7j25nIMSRw7puZyB2w6BvIGzhu4tjaCBo4bq5biwgbeG6pXQgaOG7kyBzxqEsIG5o4bqtcCBsaeG7h3Ugc2FpLCB2w6AgZMSpIG5oacOqbiDigJMgYuG7h25oIG5ow6JuIHThu60gdm9uZyB0csaw4bubYyBraGkgbmdoacOqbiBj4bupdSBr4bq/dCB0aMO6YyAoTmjDs20gdOG7rSB2b25nIGPDsyBz4buRIHRpbWUgcG9pbnQgdGjhuqVwICwgxJFhIHBo4bqnbiBjaOG6pW0gZOG7qXQg4bufIHRow6FuZyB0aOG7qSA2LCBsw6AgxJFp4buDbSBo4bq5biB0aOG7qSAzKQ0KDQoyKSBN4buZdCBjaGkgdGnhur90IGtow6FjLCBraMOzIG5o4bqtbiByYSBoxqFuIG5oxrBuZyBjw7MgdGjhu4MgdMaw4bufbmcgdMaw4bujbmcsIMSRw7MgbMOgIHNhaSBs4buHY2ggbmfhuqt1IG5oacOqbiB24buBIHRo4budaSDEkWnhu4NtIGzhuqV5IG3huqt1IENENC4gQ8OzIHRo4buDIGLhu4duaCBuaMOibiBraMO0bmcgxJHhur9uIMSRw7puZyBo4bq5biBuaMawIGvhur8gaG/huqFjaCwgYsOhYyBzxKkgdsOgIGjhu40gY8OzIHRo4buDIMSRw6MgxJHhurd0IGjhurluIG5n4bqrdSBuaGnDqm4gIHbhu5tpIHNhaSBs4buHY2ggdsOgaSBuZ8OgeSDEkeG6v24gMS0yIHR14bqnbiB0cm9uZyBjw7luZyB0aMOhbmcsIMSRxqFuIHbhu4sgbeG7kWMgdGjhu51pIGdpYW4gMiw2LDEyLDE4IGRvIMSRw7Mga2jDtG5nIGNow61uaCB4w6FjIHR1eeG7h3QgxJHhu5FpLg0KDQozKSBW4bqlbiDEkeG7gSB0aOG7qSAzIDogTeG7l2kgYuG7h25oIG5ow6JuIGzDoCBt4buZdCDCqyBwaMOibiBuaMOzbSDCuyDEkeG6t2MgYmnhu4d0IHRyb25nIG5naGnDqm4gY+G7qXUuIEzhu5kgdHLDrG5oIGRp4buFbiB0aeG6v24gY+G7p2EgQ0Q0IGtow6FjIG5oYXUgZ2nhu69hIEJOIG7DoHkgdsOgIEJOIGtow6FjLiDEkMOzIGzDoCBuZ3V5w6puIG5ow6JuIHThuqFpIHNhbyByYW5kb20gZWZmZWN0IHbDoCBtaXhlZCBtb2RlbCBz4bq9IMSRxrDhu6NjIHPhu60gZOG7pW5nIMSR4buDIHjDqXQgxJHhur9uIHnhur91IHThu5Egbmfhuqt1IG5oacOqbiBjw6EgdGjhu4MuDQoNCmBgYHtyfQ0KbGlicmFyeShjb2xvclJhbXBzKQ0KbGlicmFyeShSQ29sb3JCcmV3ZXIpIA0KDQpteXBhbGV0dGUgPSBsZW5ndGgodW5pcXVlKGFpZHMkcGF0aWVudCkpDQpnZXRQYWxldHRlID0gY29sb3JSYW1wUGFsZXR0ZShicmV3ZXIucGFsKDksICJTcGVjdHJhbCIpKQ0KDQphaWRzJT4lbXV0YXRlKC4sRGVhdGg9aWZlbHNlKC4kZGVhdGg9PTEsIkRlYXRoIiwiU3Vydml2ZWQiKSklPiUNCiAgZ2dwbG90KCkrDQogIGdlb21fcGF0aChhZXMoeD1vYnN0aW1lLHk9Q0Q0LGNvbG9yPXBhdGllbnQpLHNob3cubGVnZW5kID0gRixhbHBoYT0wLjUpKw0KICBnZW9tX3Ntb290aChhZXMoeD1hcy5udW1lcmljKG9ic3RpbWUpLHk9Q0Q0LGZpbGw9ZHJ1ZyxsaW5ldHlwZT1kcnVnKSwNCiAgICAgICAgICAgICAgY29sb3I9ImJsYWNrIiwNCiAgICAgICAgICAgICAgbWV0aG9kPSJsbSIsZm9ybXVsYSA9IHkgfiBzcGxpbmVzOjpicyh4LDMpLGFscGhhPTAuNCkrDQogIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXMgPSBnZXRQYWxldHRlKG15cGFsZXR0ZSkpKw0KICBmYWNldF93cmFwKH5EZWF0aCkrDQogIHRoZW1lX2J3KCkNCmBgYA0KDQpDw7Mgc+G7sSBraMOhYyBiaeG7h3QgcsO1IHbhu4Ega2h1eW5oIGjGsOG7m25nIHRydW5nIGLDrG5oIGPhu6dhIENENCBnaeG7r2EgYuG7h25oIG5ow6JuIHPhu5FuZyBzw7N0IHbDoCB04butIHZvbmcsIGPFqW5nIG5oxrAgZ2nhu69hIDIgcGjDom4gbmjDs20gxJFp4buBdSB0cuG7iywgdHV5IG5oacOqbiB0aGVvIHRo4budaSBnaWFuIENENCBnaeG6o20gZOG6p247DQoNCmBgYHtyfQ0KYWlkcyU+JWZpbHRlciguJFRpbWU9PS4kc3RvcCklPiUNCiAgbXV0YXRlKC4sRGVhdGg9aWZlbHNlKC4kZGVhdGg9PTEsIkRlYXRoIiwiQ2Vuc29yZWQiKSklPiUNCiAgZ2dwbG90KCkrDQogIGdlb21fYm94cGxvdChhZXMoeD1EZWF0aCx5PUNENCxmaWxsPURlYXRoKSxhbHBoYT0wLjcpKw0KICBjb29yZF9mbGlwKCkrDQogIGZhY2V0X3dyYXAofmRydWcsc2NhbGVzPSJmcmVlIixuY29sPTEpKw0KICB0aGVtZV9idygpDQpgYGANCg0KYGBge3J9DQphaWRzJT4lZmlsdGVyKC4kVGltZT09LiRzdG9wKSU+JQ0KICBtdXRhdGUoLixEZWF0aD1pZmVsc2UoLiRkZWF0aD09MSwiRGVhdGgiLCJDZW5zb3JlZCIpKSU+JQ0KICBnZ3Bsb3QoKSsNCiAgZ2VvbV9ib3hwbG90KGFlcyh4PWRydWcseT1DRDQsZmlsbD1kcnVnKSkrDQogIGNvb3JkX2ZsaXAoKSsNCiAgZmFjZXRfd3JhcCh+RGVhdGgsc2NhbGVzPSJmcmVlIixuY29sPTEpKw0KICB0aGVtZV9idygpDQpgYGANCg0KYGBge3J9DQphaWRzJT4lDQogIGdncGxvdChhZXMoeD1kcnVnLHk9VGltZSxmaWxsPWRydWcpKSsNCiAgZ2VvbV9ib3hwbG90KGNvbG9yPSJibGFjayIpKw0KICBnZW9tX3Zpb2xpbihhbHBoYT0wLjMpKw0KICBjb29yZF9mbGlwKCkrDQogIHRoZW1lX2J3KCkNCg0KYGBgDQoNCk7hur91IG114buRbiBnaeG6o2kgcXV54bq/dCDEkeG7mWMgbOG6rXAgYmnhur9uIHPhu5EgQ0Q0IHRoZW8gdGjhu51pIGdpYW4sIHRhIGPDsyB0aOG7gyBkw7luZyBtw7QgaMOsbmggTWl4ZWQgbGluZWFyIG1vZGVsLCBiYW8gZ+G7k20gbcO0IGjDrG5oIFJhbmRvbSBpbnRlcmNlcHQgdsOgIHJhbmRvbSBzbG9wZSBuaMawIHNhdToNCg0KYGBge3J9DQpteXBhbGV0dGUgPSBsZW5ndGgodW5pcXVlKGFpZHMkcGF0aWVudCkpDQpnZXRQYWxldHRlID0gY29sb3JSYW1wUGFsZXR0ZShicmV3ZXIucGFsKDksICJTcGVjdHJhbCIpKQ0KDQpyaW50PWxtZShDRDQgfiBvYnN0aW1lLCANCiAgICAgICAgIHJhbmRvbSA9IH4gMSB8IHBhdGllbnQsDQogICAgICAgICBkYXRhID0gYWlkcykNCg0KYWlkcyU+JW11dGF0ZSguLEZpdHRlZD1yaW50JGZpdHRlZFssMV0rcmludCRmaXR0ZWRbLDJdKSU+JQ0KICBnZ3Bsb3QoKSsNCiAgZ2VvbV9zbW9vdGgoYWVzKHg9YXMubnVtZXJpYyhvYnN0aW1lKSx5PUZpdHRlZCxjb2xvcj1wYXRpZW50KSwNCiAgICAgICAgICAgICAgbWV0aG9kPSJsbSIsc2hvdy5sZWdlbmQgPSBGLHNlPUYpKw0KICBnZW9tX3Ntb290aChhZXMoeD1hcy5udW1lcmljKG9ic3RpbWUpLHk9Rml0dGVkKSwNCiAgICAgICAgICAgICAgbWV0aG9kPSJsbSIsZm9ybXVsYSA9IHkgfiB4LHNob3cubGVnZW5kID0gRixzZT1GLGNvbG9yPSJyZWQiKSsNCiAgc2NhbGVfY29sb3JfZ3JleSgpKw0KICB0aGVtZV9idygpDQoNCnJzbG9wZT1sbWUoQ0Q0IH4gb2JzdGltZSwgDQogICAgICAgICAgIHJhbmRvbSA9IH4gb2JzdGltZSB8IHBhdGllbnQsDQogICAgICAgICAgICAgICAgICBkYXRhID0gYWlkcykNCg0KYWlkcyU+JW11dGF0ZSguLEZpdHRlZD1yc2xvcGUkZml0dGVkWywxXStyc2xvcGUkZml0dGVkWywyXSklPiUNCiAgZ2dwbG90KCkrDQogIGdlb21fc21vb3RoKGFlcyh4PWFzLm51bWVyaWMob2JzdGltZSkseT1GaXR0ZWQsY29sb3I9cGF0aWVudCksDQogICAgICAgICAgICAgIG1ldGhvZD0ibG0iLHNob3cubGVnZW5kID0gRixzZT1GKSsNCiAgZ2VvbV9zbW9vdGgoYWVzKHg9YXMubnVtZXJpYyhvYnN0aW1lKSx5PUZpdHRlZCksDQogICAgICAgICAgICAgIG1ldGhvZD0ibG0iLGZvcm11bGEgPSB5IH4geCxzaG93LmxlZ2VuZCA9IEYsc2U9Rixjb2xvcj0icmVkIikrDQogIHNjYWxlX2NvbG9yX2dyZXkoKSsNCiAgdGhlbWVfYncoKQ0KDQpyc2xvcGU9bG1lKENENCB+IG9ic3RpbWUsIA0KICAgICAgICAgICByYW5kb20gPSB+IG9ic3RpbWUgfCBwYXRpZW50LA0KICAgICAgICAgICBkYXRhID0gYWlkcykNCmBgYA0KDQoqKkxpw6puIGjhu4cgZ2nhu69hIGRp4buFbiB0aeG6v24gQ0Q0IHbDoCBiaeG6v24gY+G7kSB04butIHZvbmcqKg0KDQpDw7Mgc+G7sSBsacOqbiBo4buHIGdp4buvYSBDRDQgdsOgIG5ndXkgY8ahIHThu60gdm9uZyB04bqhaSBi4bqldCBj4bupIHRo4budaSDEkWnhu4NtIG7DoG8uIMSQw6J5IGzDoCBt4buZdCBnaeG6oyB0aHV54bq/dCBsb2dpYywgdsOsIGzGsOG7o25nIHThur8gYsOgbyBDRDQgYmnhu4N1IHRo4buLIGNobyBraOG6oyBuxINuZyBtaeG7hW4gZOG7i2NoIGPhu6dhIGLhu4duaCBuaMOibi4gU3V5IGdp4bqjbSBtaeG7hW4gZOG7i2NoIHPhur0gZOG6q24gxJHhur9uIG5oaeG7hW0gdHLDuW5nIGPGoSBo4buZaSB2w6AgdMSDbmcgbmd1eSBjxqEgdOG7rSB2b25nLiANCg0KYGBge3J9DQpiaW5vbWlhbF9zbW9vdGggPC0gZnVuY3Rpb24oLi4uKSB7DQogIGdlb21fc21vb3RoKG1ldGhvZCA9ICJnbG0iLCBtZXRob2QuYXJncyA9IGxpc3QoZmFtaWx5ID0gImJpbm9taWFsIiksIC4uLikNCn0NCg0KYWlkcyU+JW11dGF0ZSguLERlYXRoPWlmZWxzZSguJGRlYXRoPT0xLCJEZWF0aCIsIlN1cnZpdmVkIikpJT4lDQogIGdncGxvdChhZXMoeD1DRDQseT1kZWF0aCxmaWxsPWRydWcsbGluZXR5cGU9ZHJ1ZykpKw0KICBiaW5vbWlhbF9zbW9vdGgoY29sb3I9ImJsYWNrIixhbHBoYT0wLjMpKw0KICBmYWNldF93cmFwKH5vYnN0aW1lLG5jb2w9MikrDQogIHRoZW1lX2J3KCkNCg0KYGBgDQoNCmBgYHtyfQ0KbXlwYWxldHRlID0gbGVuZ3RoKHVuaXF1ZShhaWRzJGRydWcpKQ0KZ2V0UGFsZXR0ZSA9IGNvbG9yUmFtcFBhbGV0dGUoYnJld2VyLnBhbCg5LCAiU3BlY3RyYWwiKSkNCg0KYWlkcyU+JW11dGF0ZSguLERlYXRoPWlmZWxzZSguJGRlYXRoPT0xLCJEZWF0aCIsIlN1cnZpdmVkIikpJT4lDQogIGdncGxvdCgpKw0KICBnZW9tX2JveHBsb3QoYWVzKHg9YXMuZmFjdG9yKG9ic3RpbWUpLHk9Q0Q0LGZpbGw9ZHJ1ZyksYWxwaGE9MC43KSsNCiAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gZ2V0UGFsZXR0ZShteXBhbGV0dGUpKSsNCiAgZmFjZXRfd3JhcCh+RGVhdGgsbmNvbD0xKSsNCiAgY29vcmRfZmxpcCgpKw0KICB0aGVtZV9idygpDQoNCmBgYA0KDQpgYGB7cn0NCmFpZHMlPiVtdXRhdGUoLixEZWF0aD1pZmVsc2UoLiRkZWF0aD09MSwiRGVhdGgiLCJTdXJ2aXZlZCIpKSU+JQ0KICBnZ3Bsb3QoKSsNCiAgZ2VvbV9oaXN0b2dyYW0oYWVzKHg9Q0Q0LHk9Li5kZW5zaXR5Li4sZmlsbD1EZWF0aCxiaW5zPTUsY29sb3I9RGVhdGgpLGFscGhhPTAuNSkrDQogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IGdldFBhbGV0dGUobXlwYWxldHRlKSkrDQogIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXMgPSBnZXRQYWxldHRlKG15cGFsZXR0ZSkpKw0KICBmYWNldF9ncmlkKG9ic3RpbWV+ZHJ1ZyxzY2FsZXMgPSAiZnJlZSIpKw0KICB0aGVtZV9idygpDQpgYGANCg0KQ8OzIHPhu7EgbGnDqm4gaOG7hyBnaeG7r2EgQ0Q0IHbDoCB0aOG7nWkgZ2lhbiBz4buRbmcgY+G7p2EgYuG7h25oIG5ow6JuDQoNCmBgYHtyfQ0KbXlwYWxldHRlID0gbGVuZ3RoKHVuaXF1ZShhaWRzJGRydWcpKQ0KZ2V0UGFsZXR0ZSA9IGNvbG9yUmFtcFBhbGV0dGUoYnJld2VyLnBhbCg5LCAiU3BlY3RyYWwiKSkNCg0KYWlkcyU+JQ0KICBnZ3Bsb3QoYWVzKHg9Q0Q0LHk9VGltZSkpKw0KICBnZW9tX3BvaW50KGFscGhhPTAuNSxhZXMoY29sb3I9ZHJ1ZykpKw0KICBnZW9tX3Ntb290aChhZXMoeD1DRDQseT1UaW1lLGZpbGw9ZHJ1ZyxsaW5ldHlwZT1kcnVnKSwNCiAgICAgICAgICAgICAgY29sb3I9ImJsYWNrIiwNCiAgICAgICAgICAgICAgbWV0aG9kPSJsbSIsZm9ybXVsYSA9IHkgfiBzcGxpbmVzOjpicyh4LDMpLGFscGhhPTAuNCkrDQogIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXMgPSBnZXRQYWxldHRlKG15cGFsZXR0ZSkpKw0KICB0aGVtZV9idygpDQpgYGANCg0KYGBge3J9DQpteXBhbGV0dGUgPSAxMg0KZ2V0UGFsZXR0ZSA9IGNvbG9yUmFtcFBhbGV0dGUoYnJld2VyLnBhbCg5LCAiU3BlY3RyYWwiKSkNCg0KYWlkcyU+JQ0KICBnZ3Bsb3QoYWVzKHg9Q0Q0LHk9VGltZSkpKw0KICBnZW9tX2JveHBsb3QoYWVzKGdyb3VwPWN1dF93aWR0aChDRDQsMi41KSwNCiAgICAgICAgICAgICAgICAgICBmaWxsPWN1dF93aWR0aChDRDQsMi41KSwNCiAgICAgICAgICAgICAgIGNvbG9yPWN1dF93aWR0aChDRDQsMi41KSksDQogICAgICAgICAgICAgICBhbHBoYT0wLjUsDQogICAgICAgICAgICAgICBzaG93LmxlZ2VuZCA9IEYpKw0KICBnZW9tX3Ntb290aChhZXMoeD1DRDQseT1UaW1lLGxpbmV0eXBlPWRydWcpLA0KICAgICAgICAgICAgICBjb2xvcj0iYmxhY2siLA0KICAgICAgICAgICAgICBtZXRob2Q9ImxtIixmb3JtdWxhID0geSB+IHNwbGluZXM6OmJzKHgsMyksYWxwaGE9MC40KSsNCiAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gZ2V0UGFsZXR0ZShteXBhbGV0dGUpKSsNCiAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcyA9IGdldFBhbGV0dGUobXlwYWxldHRlKSkrDQogIHRoZW1lX2J3KCkNCg0KYGBgDQoNCioqVMOhYyDEkeG7mW5nIGPhu6dhIMSRaeG7gXUgdHLhu4sgbMOqbiBuZ3V5IGPGoSB04butIHZvbmcqKg0KDQpTYXUgxJHDonkgbMOgIDEgdsOgaSBiaeG7g3UgxJHhu5MgdHLDrG5oIGLDoHkgMyBow6BtIFN1cnZpdmFsLCBOZ3V5IGPGoSB0w61jaCBsxal5IHbDoCBDREYsIGNobyB0aOG6pXkgdGhheSDEkeG7lWkgY+G7p2Egbmd1eSBjxqEgdOG7rSB2b25nIHRoZW8gdGjhu51pIGdpYW4uIA0KDQoNCmBgYHtyfQ0KZ2dwbG90KGFpZHMsIA0KICAgICAgIGFlcyh0aW1lID0gVGltZSwgc3RhdHVzID0gZGVhdGgsIGZpbGwgPSBkcnVnLCBjb2xvciA9IGRydWcpKSsgDQogIGdlb21fa20oKSsgDQogIGdlb21fa21iYW5kKCkrDQogIHRoZW1lX2J3KCkNCg0KZ2dwbG90KGFpZHMsIA0KICAgICAgIGFlcyh0aW1lID0gVGltZSwgc3RhdHVzID0gZGVhdGgsIGZpbGwgPSBkcnVnLCBjb2xvciA9IGRydWcpKSsgDQogIGdlb21fa20odHJhbnMgPSAiY3VtaGF6IikrIA0KICBnZW9tX2ttYmFuZCh0cmFucyA9ICJjdW1oYXoiKSsNCiAgdGhlbWVfYncoKSsgeWxhYigiQ3VtdWxhdGl2ZSBoYXphcmQiKQ0KDQpnZ3Bsb3QoYWlkcywgDQogICAgICAgYWVzKHRpbWUgPSBUaW1lLCBzdGF0dXMgPSBkZWF0aCwgZmlsbCA9IGRydWcsIGNvbG9yID0gZHJ1ZykpKyANCiAgZ2VvbV9rbSh0cmFucyA9ICJldmVudCIpKyANCiAgZ2VvbV9rbWJhbmQodHJhbnMgPSAiZXZlbnQiKSsNCiAgdGhlbWVfYncoKSsgeWxhYigiQ3VtdWxhdGl2ZSBFdmVudHMgKENERikiKQ0KYGBgDQoNCg0KYGBge3J9DQpsaWJyYXJ5KHN1cnZtaW5lcikNCmxpYnJhcnkoc3Vydml2YWwpDQoNCmZpdCA8LSBzdXJ2Zml0KFN1cnYoVGltZSwgZGVhdGgpIH4gZHJ1ZywgZGF0YSA9IGFpZHMpDQoNCmdnc3VydnBsb3QoZml0LCANCiAgICAgICAgICAgZGF0YSA9IGFpZHMsDQogICAgICAgICAgIHNpemU9MSwNCiAgICAgICAgICAgY29uZi5pbnQgPSBUUlVFLA0KICAgICAgICAgICBwdmFsID0gVFJVRSwNCiAgICAgICAgICAgcmlzay50YWJsZSA9IFRSVUUsIA0KICAgICAgICAgICByaXNrLnRhYmxlLmNvbCA9ICJzdHJhdGEiLA0KICAgICAgICAgICByaXNrLnRhYmxlLmhlaWdodCA9IDAuMywNCiAgICAgICAgICAgZ2d0aGVtZSA9IHRoZW1lX2J3KCksDQogICAgICAgICAgIHJpc2sudGFibGUueS50ZXh0LmNvbCA9IFQsIA0KICAgICAgICAgICByaXNrLnRhYmxlLnkudGV4dCA9IEZBTFNFDQogICAgICAgICAgICkNCg0KZml0IDwtIHN1cnZmaXQoU3VydihUaW1lLCBkZWF0aCkgfiBkcnVnLCBkYXRhID0gYWlkcykNCg0KYGBgDQoNCk3hu5l0IG3DtCBow6xuaCBDb3ggxJHGoW4gdGh14bqnbiBraMO0bmcgxJHhu6cga2jhuqMgbsSDbmcgxJHhu4MgcGjDoXQgaGnhu4duIHPhu7Ega2jDoWMgYmnhu4d0IHbhu4EgaGnhu4d1IOG7qW5nIGdp4buvYSAyIGxv4bqhaSB0aHXhu5FjIMSRaeG7gXUgdHLhu4sNCg0KYGBge3J9DQpjb3hwaG1vZDA8LSBjb3hwaChTdXJ2KFRpbWUsIGRlYXRoKSB+IGRydWcsIGRhdGEgPSBhaWRzKQ0KDQpzdW1tYXJ5KGNveHBobW9kMCkNCg0KZ2djb3hkaWFnbm9zdGljcyhjb3hwaG1vZDApDQoNCmdnZm9yZXN0KGNveHBobW9kMCkNCmBgYA0KDQpLaGkgYmnhur9uIHPhu5EgQ0Q0IMSRxrDhu6NjIMSRxrBhIHbDoG8gbcO0IGjDrG5oIENveCwgbMO6YyBuw6B5IG3hu41pIHRo4bupIGPDsyB24bq7IGto4bqjIHF1YW4gaMahbiBjaMO6dCDDrXQ6IE3DtCBow6xuaCBjaG8gdGjhuqV5IENENCB0aOG6rXQgc+G7sSBjw7MgbGnDqm4gaOG7hyB24bubaSBuZ3V5IGPGoSB04butIHZvbmcuIHNxcnQoQ0Q0KSBnaeG6o20gMSDEkcahbiB24buLIGzDoG0gdMSDbmcgbmd1eSBjxqEgdOG7rSB2b25nIHRow6ptIDEuMTggbOG6p24uDQoNClR1eSBuaGnDqm4sIG3DtCBow6xuaCBuw6B5IGNoxrBhIHThu5F0IHbDoCDEkeG7mSBwaMO5IGjhu6NwIGThu68gbGnhu4d1IGvDqW0uIFZp4buHYyBkw7luZyB0cuG7sWMgdGnhur9wIENENCBuaMawIDEgaGnhu4dwIGJp4bq/biBz4buRIGPDsyB0aOG6rXQgc+G7sSDEkcO6bmcgxJHhuq9uIGNoxrBhID8gQ8OidSB0cuG6oyBs4budaSBsw6AgY2jGsGEgISBUdXkgQ0Q0IGPDsyBsacOqbiBo4buHIHbhu5tpIG5ndXkgY8ahIHThu60gdm9uZywgbmjGsG5nIGxpw6puIGjhu4cgZ2nhu69hIGNow7puZyBraMO0bmcgdGjhu4MgxJHGsOG7o2Mga2jhuqNvIHPDoXQgaG/DoG4gdG/DoG4gYuG7n2kgbcO0IGjDrG5oIENveCwgdsOsIG3DtCBow6xuaCBDb3ggY2jhu4kgY2hvIHBow6lwIGto4bqjbyBzw6F0IDEgaGnhu4dwIGJp4bq/biBz4buRIMSR4buZYyBs4bqtcCwgbuG6sW0gYsOqbiBuZ2/DoGkgdGjhu7FjIHRo4buDIGLhu4duaCBsw70gKHRow60gZOG7pSBwaMOibiBuaMOzbSDEkWnhu4F1IHRy4buLLCDDtCBuaGnhu4VtIG3DtGkgdHLGsOG7nW5nLCBuaGnhu4VtIHRyw7luZ+KApikgdHJvbmcga2hpIENENCBsw6AgbeG7mXQgaGnhu4dwIGJp4bq/biBz4buRIG7hu5lpIHThuqFpLCBuw7MgbuG6sW0gbmdheSB0cm9uZyBjxINuIGLhu4duaC4NCg0KYGBge3J9DQpjb3hwaG1vZDwtIGNveHBoKFN1cnYoVGltZSwgZGVhdGgpIH4gZHJ1ZytDRDQsIGRhdGEgPSBhaWRzKQ0KDQpzdW1tYXJ5KGNveHBobW9kKQ0KDQpnZ2NveGRpYWdub3N0aWNzKGNveHBobW9kKQ0KDQpnZ2ZvcmVzdChjb3hwaG1vZCkNCmBgYA0KDQojIE3DtCBow6xuaCBsacOqbiBo4bujcDogdMOzbSB04bqvdCANCg0KTcO0IGjDrG5oIGxpw6puIGjhu6NwIChKb2ludCBtb2RlbCkgbMOgIG3hu5l0IHBoxrDGoW5nIHBow6FwIHRo4buRbmcga8OqIGNobyBwaMOpcCBo4bujcCBuaOG6pXQgaGFpIGLhu5kgcGjhuq1uIDogc+G7sSBiaeG6v24gdGhpw6puIHBo4bulIHRodeG7mWMgdGjhu51pIGdpYW4gY+G7p2EgbeG7mXQgYmnhur9uIMSR4buLbmggbMaw4bujbmcgKGxvbmdpdHVkaW5hbCByZXNwb25zZSA7IHRow60gZOG7pSBtYXJrZXIgdHJvbmcgMSBi4buHbmggbMO9KSB2w6Agbmd1eSBjxqEgeOG6o3kgcmEgbeG7mXQgc+G7sSBraeG7h24gdGhlbyB0aOG7nWkgZ2lhbiAoc3Vydml2YWwgYW5hbHlzaXMpLCBuaOG6sW0ga2jhuqNvIHPDoXQgbeG7kWkgbGnDqm4gaOG7hyBnaeG7r2EgY2jDum5nLiANCg0KIVtdKGpvaW50bTMucG5nKQ0KDQoNCk3hu5l0IGPDoWNoIMSRxqFuIGdp4bqjbiB04buRaSDEkWEsIG5ndXnDqm4gdOG6r2MgY+G7p2EgbcO0IGjDrG5oIGxpw6puIGjhu6NwIGPDsyB0aOG7gyDEkcaw4bujYyBtw7QgdOG6oyBuaMawIHNhdSA6DQoNCjEgbcO0IGjDrG5oIGxpw6puIGjhu6NwIGPDsyAyIGLhu5kgcGjhuq1uIGLDqm4gdHJvbmcgbsOzIChuaMawIDIgYsOhbmggeGUgcsSDbmcgY8awYSkgOiBN4buZdCBsw6AgbcO0IGjDrG5oIHBow6JuIHTDrWNoIHPhu7Ega2nhu4duIChDb3gtUEggbW9kZWwpLCB2w6AgMSBtw7QgaMOsbmggcGjDom4gdMOtY2ggcGjGsMahbmcgc2FpIHBo4bulIHRodeG7mWMgdGjhu51pIGdpYW4gduG7m2kgeeG6v3UgdOG7kSBuZ+G6q3Ugbmhpw6puIChNaXhlZCBtb2RlbCkuIA0KDQoxKSBC4buZIHBo4bqtbiBTdXJ2aXZhbCBjw7MgbeG7pWMgdGnDqnUgxJFvIGzGsOG7nW5nIG3hu5FpIGxpw6puIGjhu4cgZ2nhu69hIGRp4buFbiB0aeG6v24gY+G7p2EgMSBiaeG6v24gc+G7kSDEkeG7i25oIGzGsOG7o25nIChtYXJrZXIgTSkgdsOgIG5ndXkgY8ahIHjhuqN5IHJhIDEgYmnhur9uIGPhu5EgKEUpIHRoZW8gdGjhu51pIGdpYW4uIEzGsHUgw70gMSBjaGkgdGnhur90IHF1YW4gdHLhu41uZywgxJHDsyBsw6AgZ2nDoSB0cuG7iyBj4bunYSBNIHThuqFpIHRo4budaSDEkWnhu4NtIHQgbMOgIDEgZ2nDoSB0cuG7iyBIxrAgbcOgIFRo4buxYyAoY8OzIHRo4buxYywgbmjGsG5nIGtow7RuZyB0aOG7gyBob+G6t2MgY2jGsGEgdGjhu4MgeMOhYyDEkeG7i25oIMSRxrDhu6NjKS4gxJBp4buBdSBuw6B5IG3hu5tpIG5naGUgY8OzIHbhursga8OsIGPhu6VjLCBuaMawbmcgY8OzIHRo4buDIGhp4buDdSDEkcaw4bujYywgdGjDrSBk4bulIG7hur91IHRo4budaSDEkWnhu4NtIHQgcsahaSB2w6BvIGtob+G6o25nIGdp4buvYSAyIGN14buZYyBo4bq5biB0cm9uZyBs4buLY2gsIHRow6wgdGEga2jDtG5nIHRo4buDIG7DoG8gYmnhur90IG1hcmtlciBraGkgxJHDsyBjw7MgZ2nDoSB0cuG7iyBiYW8gbmhpw6p1LCBuaMawbmcgdHJvbmcgY8ahIHRo4buDIGPhu6dhIGLhu4duaCBuaMOibiBraGkgxJHDsyB24bqrbiBkaeG7hW4gcmEgc+G7sSB0aGF5IMSR4buVaS4gRG8gxJHDsyBNIMSRxrDhu6NjIGto4bqjbyBzw6F0IG5oxrAgMSBi4buHbmggc+G7rSB0w61uaCDEkeG6v24gdGjhu51pIMSRaeG7g20gdC4gDQoNCkLhu5kgcGjhuq1uIHN1cnZpdmFsIGPDsyB2YWkgdHLDsiBjaOG7pyBjaOG7kXQgIHRyb25nIG3DtCBow6xuaCwgdsOsIG7DsyBjaG8gcGjDqXAgeMOhYyDEkeG7i25oIHRoYW0gc+G7kSDEkW8gbMaw4budbmcgaGnhu4d1IOG7qW5nIGPhu6dhIGxvbmdpdHVkaW5hbCBvdXRjb21lIE0gbMOqbiBuZ3V5IGPGoSB44bqjeSByYSBFLiANCg0KVHJvbmcgcGjhuqduIHN1cnZpdmFsIGPDsm4gY8OzIGjDoG0gIG5ndXkgY8ahIGfhu5FjIGgwKHQpLCBjw7MgdGjhu4MgeGVtIG5oxrAgY8ahIHPhu5EgY2hvIGjDoG0gRVhQIMSRaSBzYXUuIEjDoG0gaDAgdHJvbmcgbcO0IGjDrG5oIENveCBjxaluZyBjw7MgbmjGsG5nIGtow7RuZyDEkcaw4bujYyB4w6FjIMSR4buLbmguVHJvbmcgSm9pbnQgbW9kZWwsIGgwIHBo4bqjaSDEkcaw4bujYyB4w6FjIMSR4buLbmgsIGPDsyB0aOG7gyBow6xuaCBkdW5nIGzDoCBjaGlhIG5o4buPIGtob+G6o25nIHRo4budaSBnaWFuIGto4bqjbyBzw6F0IHJhIG5oaeG7gXUgxJFv4bqhbiB0aMaw4budbmcgYuG6sW5nIFBpZWNlIHdpc2UsIEItc3BsaW5lLCBjdWJpYyBzcGxpbmVzIOKApiBWaeG7h2MgbMOgbSBuw6B5IGPFqW5nIGPhuqduIHBo4bqjaSBj4bqpbiB0cuG7jW5nIHbDrCBjw7Mgbmd1eSBjxqEgZ8OieSBvdmVyZml0dGluZy4gRG8gxJHDsywgY8OzIHF1eSB04bqvYyDEkeG6t3QgcmEgbMOgIGdp4bubaSBo4bqhbiB04buVbmcgc+G7kSBiaeG6v24gKGJhbyBn4buTbSBi4bqtYyBj4bunYSBow6BtIHNwbGluZSB2w6AgYmnhur9uIMSR4buZYyBs4bqtcCB0cm9uZyBtw7QgaMOsbmgpIHThu6sgMS8xMCDEkeG6v24gMS8yMCB04buVbmcgc+G7kSBz4buxIGtp4buHbiB0cm9uZyBt4bqrdS4NCg0KMikgQuG7mSBwaOG6rW4gdGjhu6kgMiBsw6AgbcO0IGjDrG5oIExvbmdpdHVkaW5hbC4gTmhp4buHbSB24bulIGPhu6dhIG7DsyBsw6AgxrDhu5tjIGzGsOG7o25nIGPDoG5nIGNow61uaCB4w6FjIGPDoG5nIHThu5F0IGzhu4tjaCBz4butIGRp4buFbiB0aeG6v24gY+G7p2EgYmnhur9uIE0gKG1hcmtlcikuIMSQw6J5IGzDoCBtw7NjIG7hu5FpIGdp4buvYSAyIG3DtCBow6xuaCBi4buZIHBo4bqtbi4gDQoNCk0gxJHGsOG7o2MgxrDhu5tjIHTDrW5oIGThu7FhIHRyw6puIDogeeG6v3UgdOG7kSB0aOG7nWkgZ2lhbiwgbWF0cml4IG3DtCBow6xuaCwgaGnhu4d1IOG7qW5nIGPhu5EgxJHhu4tuaCAoYmV0YSkgdsOgIGhp4buHdSDhu6luZyBuZ+G6q3Ugbmhpw6puIChjw6EgdGjhu4MpLCB2w6Agc2FpIHPhu5Egbmfhuqt1IG5oacOqbiAoZ2nhuqMgxJHhu4tuaCBjw7MgcGjDom4gcGjhu5FpIGNodeG6qW4gduG7m2kgdHJ1bmcgYsOsbmggPTAgdsOgIHBoxrDGoW5nIHNhaSBzaWdtYV4yKS4NCg0KTcO0IGjDrG5oIGxvbmdpdHVkaW5hbCBjw7MgYuG6o24gY2jhuqV0IGzDoCAxIGxpbmVhciBtaXhlZCBtb2RlbCBjw7MgY2jhu6lhIGJp4bq/biBz4buRIHRo4budaSBnaWFuLiBTYXUgbsOgeSwgY8OhYyB0w6FjIGdp4bqjIGPDsm4gbeG7nyBy4buZbmcgcGjhuqduIExvbmdpdGlkaW5hbCBi4bqxbmcgY8OhY2ggxJHGsGEgdGjDqm0gdsOgbyBjw6FjIHRow6BuaCBwaOG6p24gYuG6pXQgxJHhu4tuaCBraMOhYy4NCg0KVGEgdGjhu60gbmjDrG4gbOG6oWkgMSBs4bqnbiBu4buvYSAyIHF1w6EgdHLDrG5oIHNvbmcgc29uZzogbmd1eSBjxqEgdOG7rSB2b25nIHTDrWNoIGzFqXkgdGhlbyB0aOG7nWkgZ2lhbiwgdsOgIENENCB0aGF5IMSR4buVaSB0aGVvIHRo4budaSBnaWFuOg0KDQpgYGB7cn0NCmxwPWFpZHMlPiVtdXRhdGUoLixEZWF0aD1pZmVsc2UoLiRkZWF0aD09MSwiRGVhdGgiLCJTdXJ2aXZlZCIpKSU+JQ0KICBnZ3Bsb3QoKSsNCiAgZ2VvbV9zbW9vdGgoYWVzKHg9YXMubnVtZXJpYyhUaW1lKSx5PUNENCxmaWxsPWRydWcsbGluZXR5cGU9ZHJ1ZyksDQogICAgICAgICAgICAgIGNvbG9yPSJibGFjayIsYWxwaGE9MC40KSsNCiAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcyA9IGdldFBhbGV0dGUobXlwYWxldHRlKSkrDQogIHRoZW1lX2J3KCkNCg0KaHo9Z2dwbG90KGFpZHMsIA0KICAgICAgIGFlcyh0aW1lID0gVGltZSwgc3RhdHVzID0gZGVhdGgsIGZpbGwgPSBkcnVnLCBjb2xvciA9IGRydWcpKSsgDQogIGdlb21fa20odHJhbnMgPSAiY3VtaGF6IikrIA0KICBnZW9tX2ttYmFuZCh0cmFucyA9ICJjdW1oYXoiKSsNCiAgdGhlbWVfYncoKSsgeWxhYigiQ3VtdWxhdGl2ZSBoYXphcmQiKQ0KDQpncmlkRXh0cmE6OmdyaWQuYXJyYW5nZShoeixscCxuY29sPTEpDQpgYGANCg0KIyBE4buxbmcgSm9pbnQgbW9kZWwgYuG6sW5nIHBhY2thZ2UgSk0NCg0KxJDhuqd1IHRpw6puLCB0YSBk4buxbmcgMiBtw7QgaMOsbmggTG9uZ2l0dWRpbmFsIHbDoCBDb3gtUEggdHJvbmcgcGFja2FnZSBKTToNCg0KYGBge3J9DQojIFThuqFvIGRhdGFzZXQgY2hvIG3DtCBow6xuaCBDb3gNCg0KYWlkcyU+JS5bIWR1cGxpY2F0ZWQoLiRwYXRpZW50KSxdLT5haWRzLmlkDQoNCiNtaXhlZCBsb25naXR1ZGluYWwgc3VibW9kZWwNCg0KbG9uZ2NvbXA9bG1lKENENCB+IG9ic3RpbWUrIG9ic3RpbWU6ZHJ1ZywNCiAgICAgICAgICAgICByYW5kb20gPSB+IG9ic3RpbWUgfCBwYXRpZW50LCANCiAgICAgICAgICAgICBkYXRhID0gYWlkcykNCg0KbG9uZ2NvbXAlPiVzdW1tYXJ5KCkNCg0KIyBUaW1lIGV2ZW50IHN1Ym1vZGVsIChDb3hQSCkNCg0KY294Y29tcD1jb3hwaChTdXJ2KFRpbWUsIGRlYXRoKSB+IGRydWcsIA0KICAgICAgICAgICAgICBkYXRhID0gYWlkcy5pZCwgDQogICAgICAgICAgICAgIHggPSBUUlVFKQ0KDQpjb3hjb21wJT4lc3VtbWFyeSgpDQpgYGANCg0KSMOgbSBqb2ludE1vZGVsIHPhur0gaOG7o3AgbmjhuqV0IDIgbcO0IGjDrG5oIGLhu5kgcGjhuq1uIGzhuqFpIHbhu5tpIG5oYXUsIHbhu5tpIGjDoG0gbmd1eSBjxqEgZ+G7kWMgaDAgxJHGsOG7o2MgxrDhu5tjIHTDrW5oIGLhurFuZyBwaMawxqFuZyBwaMOhcCBTcGxpbmUsIFByb3BvcnRpb25hbCBoYXphcmQgdsOgIEdILlF1eSB0csOsbmggbsOgeSBz4bq9IG3huqV0IGtow6Egbmhp4buBdSB0aOG7nWkgZ2lhbiwgYuG6oW4gbsOqbiBracOqbiBuaOG6q24gY2jhu50gxJHhur9uIGtoaSBtw7QgaMOsbmggY29udmVyZ2VkLg0KDQpQYWNrYWdlIEpNIHTGsMahbmcgdGjDrWNoIHbhu5tpIG5oaeG7gXUgaMOgbSB0cm9uZyBiYXNlIFIsIHRow60gZOG7pTogc3VtbWFyeSggKSDEkeG7gyB4ZW0gbuG7mWkgZHVuZyBtb2RlbC4NCg0KYGBge3J9DQpqb2ludG1vZD1qb2ludE1vZGVsKGxtZU9iamVjdD1sb25nY29tcCwgDQogICAgICAgICAgICAgICAgICAgIHN1cnZPYmplY3Q9Y294Y29tcCwNCiAgICAgICAgICAgICAgICAgICAgICAgICB0aW1lVmFyID0gIm9ic3RpbWUiLA0KICAgICAgICAgICAgICAgICAgICAgICAgIG1ldGhvZCA9ICJzcGxpbmUtUEgtR0giKQ0KDQpzdW1tYXJ5KGpvaW50bW9kKQ0KYGBgDQoNCioqRGnhu4VuIGdp4bqjaSBr4bq/dCBxdeG6oyBKb2ludCBtb2RlbCoqDQoNCsSQ4bqndSB0acOqbiwga+G6v3QgcXXhuqMgY2hvIGJp4bq/dCBnacOhIHRy4buLIEFJQywgQklDIHbDoCBMb2cgbGlrZWxpaG9vZCBj4bunYSBtb2RlbA0KDQpQaOG6p24gdGjhu6kgbmjhuqV0IGzDoCBGaXhlZCBlZmZlY3QsIGto4bqjbyBzw6F0IHPhu7EgdGhheSDEkeG7lWkgY+G7p2EgQ0Q0IHRoZW8gdGjhu51pIGdpYW4gKHBo4bqnbiBMb25naXR1ZGluYWwgcmVzcG9uc2UpLCB2w6AgbGnhu4d1IGPDsyBz4buxIHTGsMahbmcgdMOhYyB24bubaSB0aHXhu5FjIMSRaeG7gXUgdHLhu4sgKG5ow7NtIEREaSkgaGF5IGtow7RuZyA/LiBDw6J1IHRy4bqjIGzhu51pIGzDoCA6IENENCBnaeG6o20gdGhlbyB0aOG7nWkgZ2lhbiwgdHJ1bmcgYsOsbmggLTAuMTg3IMSRxqFuIHbhu4sgbeG7l2kgdGjDoW5nLCB0xrDGoW5nIHTDoWMgdGh14buRYyB2w6AgdGjhu51pIGdpYW4ga2jDtG5nIGPDsyDDvSBuZ2jEqWEuDQoNClBo4bqnbiB0aOG7qSBoYWkgbMOgIGto4bqjbyBzw6F0IHbhu4EgYmnhur9uIGPhu5EgdOG7rSB2b25nIChwaOG6p24gU3Vydml2YWwpLiBOaOG7r25nIHRoYW0gc+G7kSBo4buTaSBxdXkgY8OzIHRo4buDIMSRxrDhu6NjIGRp4buFbiBnaeG6o2kgbmjGsCAxIG3DtCBow6xuaCBDb3ggY+G7lSDEkWnhu4NuLiBQaOG6p24gbsOgeSBjaG8gdGEgYmnhur90IG5oaeG7gXUgdGjDtG5nIHRpbiB0aMO6IHbhu4sgbcOgIGLhuqNuIHRow6JuIG3DtCBow6xuaCBDb3ggdHLGsOG7m2MgxJHDsyBraMO0bmcgbMOgbSDEkcaw4bujYy4gxJDhuqd1IHRpw6puLCDEkcOzIGzDoCB54bq/dSB04buRIEFzc29jaWF0aW9uLCBoYXkgbGnDqm4gaOG7hyBnaeG7r2EgYuG7h25oIHPhu60gQ0Q0IHbDoCB0aHXhu5FjIMSRaeG7gXUgdHLhu4sgxJHhu5FpIHbhu5tpIG5ndXkgY8ahIHThu60gdm9uZyA6DQoNClRhIGPDsyB0aOG7gyBkaeG7hW4gZ2nhuqNpIDogdOG6oWkgYuG6pXQgY+G7qSB0aOG7nWkgxJFp4buDbSBuw6BvLCB0csOqbiBi4buHbmggbmjDom4gQUlEUyBz4butIGThu6VuZyB0aHXhu5FjIERESSAsIG3hu5dpIMSRxqFuIHbhu4sgQ0Q0IGdp4bqjbSDEkWkgc+G6vSBsw6BtIHTEg25nIG5ndXkgY8ahIHThu60gdm9uZyBsw6puIDM0JSA9ICBleHAoMC4yOTU3KS0xDQoNCk3DtCBow6xuaCBz4butIGThu6VuZyBCLXNwbGluZSBi4bqtYyA5IChj4bqvdCBraG/huqNuZyB0aOG7nWkgZ2lhbiAyMSB0aMOhbmcgcmEgbMOgbSA5IMSRb+G6oW4pLCB0dXkgbmhpw6puIGvhur90IHF14bqjIGNo4buJIGPDsyDDvSBuZ2jEqWEgdGjhu5FuZyBrw6ogdOG6oWkgcHMxLHBzMyxwczQsYnM1IHbDoCBiczYNCg0KIyBTdXkgZGnhu4VuIHRo4buRbmcga8OqIHbDoCBraeG7g20gdHJhIG3DtCBow6xuaA0KDQpUYSBjw7MgdGjhu4MgdMOtbmggdGjhu6cgY8O0bmcga2hv4bqjbmcgdGluIGPhuq15IDk3LjUlIGNobyBjw6FjIHRoYW0gc+G7kSBo4buTaSBxdXkgKGhp4buHdSDhu6luZykgcXVhbiB0cuG7jW5nDQoNCmBgYHtyfQ0KIyBDb25maWRlbmNlIGludGVydmFscyBmb3IgdGhlIHJlZ3Jlc3Npb24gY29lZmZpY2llbnRzDQpjb25maW50KGpvaW50bW9kLCBwYXJtID0gIkxvbmdpdHVkaW5hbCIpDQoNCiMgQ29uZmlkZW5jZSBpbnRlcnZhbHMgZm9yIHRoZSBoYXphcmQgcmF0aW9zIA0KDQpjb25maW50KGpvaW50bW9kLCBwYXJtID0gIkV2ZW50IiklPiVleHAoKQ0KYGBgDQoNClRhIGPDsyB0aOG7gyBsw6BtIDEga2nhu4NtIMSR4buLbmggV2FsZCDEkWEgYmnhur9uIGNobyB0b8OgbiBi4buZIGPDoWMgdGhhbSBz4buROg0KDQpgYGB7cn0NCiMgTXVsdGl2YXJpYXRlIFdhbGQgdGVzdCBmb3Igc2ltdWx0YW5lb3VzbHkgdGVzdGluZyBhbGwgDQoNCmFub3ZhKGpvaW50bW9kLHByb2Nlc3M9ImJvdGgiKQ0KYGBgDQoNCsSQ4buDIGtp4buDbSB0cmEgcGjhuqduIHBow6JuIHTDrWNoIHPhu7Ega2nhu4duLCB0YSBjw7MgdGjhu4MgbMOgbSAxIHBow6JuIHTDrWNoIHBoxrDGoW5nIHNhaSA6IHRhIGThu7FuZzEgbcO0IGjDrG5oIENveCB04buRaSBnaeG6o24gcuG7k2kgY8WpbmcgbMOgbSBqb2ludCBtb2RlbCB24bubaSBuw7MsIHNhdSDEkcOzIGTDuW5nIEFOT1ZBIHNvIHPDoW5oIDIgbcO0IGjDrG5oIGpvaW50IG1vZGVsIGPFqSB2w6AgbeG7m2kgDQoNCmBgYHtyLG1lc3NhZ2UgPSBGQUxTRSx3YXJuaW5nPUZBTFNFfQ0KI0Fub3ZhIGZvciBFdmVudCBwcm9jZXNzDQoNCmNveDI9Y294cGgoU3VydihUaW1lLCBkZWF0aCkgfiAxLCBkYXRhID0gYWlkcy5pZCwgeCA9IFRSVUUpDQoNCmpvaW50bW9kMj1qb2ludE1vZGVsKGxtZU9iamVjdD1sb25nY29tcCwgDQogICAgICAgICAgICAgICAgICAgIHN1cnZPYmplY3Q9Y294MiwNCiAgICAgICAgICAgICAgICAgICAgdGltZVZhciA9ICJvYnN0aW1lIiwNCiAgICAgICAgICAgICAgICAgICAgbWV0aG9kID0gInNwbGluZS1QSC1HSCIpDQoNCmFub3ZhKGpvaW50bW9kMiwgam9pbnRtb2QpDQoNCmBgYA0KDQpUaeG6v3AgdGhlbywgdGEgY8OzIHRo4buDIGtp4buDbSB0cmEgaGnhu4d1IOG7qW5nIGxpw6puIGjhu6NwIGdp4buvYSBUaHXhu5FjIHbDoCBDRDQgYuG6sW5nIDEgc2NvcmV0ZXN0IA0KDQpgYGB7cixtZXNzYWdlID0gRkFMU0Usd2FybmluZz1GQUxTRX0NCk1MbW9kPXVwZGF0ZShsb25nY29tcCwgbWV0aG9kID0gIk1MIikNCg0KcHdjPXBpZWNld2lzZUV4cC5waChjb3hjb21wKQ0KDQppbml0PWxpc3QoYmV0YXMgPSBmaXhlZihNTG1vZCksIA0KICAgICAgICAgICAgIHNpZ21hID0gTUxtb2Qkc2lnbWEsDQogICAgICAgICAgICAgRCA9IGdldFZhckNvdihNTG1vZCksDQogICAgICAgICAgICAgZ2FtbWFzID0gY29lZihwd2MpWzFdLCBhbHBoYSA9IDAsDQogICAgICAgICAgICAgeGkgPSBleHAoY29lZihwd2MpWy0xXSkpDQoNCkpNU2NvcmVUZXN0PWpvaW50TW9kZWwoTUxtb2QsIA0KICAgICAgICAgICAgICAgICAgICAgICBjb3hjb21wLA0KICAgICAgICAgICAgICAgICAgICAgICAgICB0aW1lVmFyID0gIm9ic3RpbWUiLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgbWV0aG9kID0gInNwbGluZS1QSC1HSCIsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICBpbml0ID0gaW5pdCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgb25seS5FTSA9IFRSVUUpDQoNCnNjb3JlLnZlY3Rvcj1KTVNjb3JlVGVzdCRTY29yZQ0KDQppbnYuSGVzc2lhbj12Y292KEpNU2NvcmVUZXN0KQ0KDQpTY29yZVN0YXQ9Yyh0KHNjb3JlLnZlY3RvcikgJSolIGludi5IZXNzaWFuICUqJSBzY29yZS52ZWN0b3IpDQoNCmNiaW5kKCJTdGF0aXN0aWMiID0gU2NvcmVTdGF0LCAiZGYiID0gMSwNCiAgICAgICJwLXZhbHVlIiA9IHBjaGlzcShTY29yZVN0YXQsIDEsIGxvd2VyLnRhaWwgPSBGQUxTRSkpDQpgYGANCg0KKlNhaSBz4buRIHbDoCBNYXJnaW5hbCBlZmZlY3QqDQoNCmBgYHtyfQ0KcGxvdChqb2ludG1vZCkNCmBgYA0KDQoNCmBgYHtyLG1lc3NhZ2UgPSBGQUxTRSx3YXJuaW5nPUZBTFNFfQ0KI1N0YW5kYXJkaXplZCByZXNpZHVhbHMNCg0KcmVzTWFyZz1yZXNpZHVhbHMoam9pbnRtb2QsIA0KICAgICAgICAgICAgICAgICAgcHJvY2VzcyA9ICJMb25naXR1ZGluYWwiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgdHlwZSA9ICJNYXJnaW5hbCIpDQoNCmZpdE1hcmc9Zml0dGVkKGpvaW50bW9kLCANCiAgICAgICAgICAgICAgICAgIHByb2Nlc3MgPSAiTG9uZ2l0dWRpbmFsIiwNCiAgICAgICAgICAgICAgICAgIHR5cGUgPSAiTWFyZ2luYWwiKQ0KDQpteUNvbG9yPXJldihSQ29sb3JCcmV3ZXI6OmJyZXdlci5wYWwoMTEsICJTcGVjdHJhbCIpKQ0KbXlDb2xvcl9zY2FsZV9maWxsPXNjYWxlX2ZpbGxfZ3JhZGllbnRuKGNvbG91cnMgPSBteUNvbG9yKQ0KbXlDb2xvcl9zY2FsZV9jb2w9c2NhbGVfY29sb3JfZ3JhZGllbnRuKGNvbG91cnMgPSByZXYoUkNvbG9yQnJld2VyOjpicmV3ZXIucGFsKDExLCAiU3BlY3RyYWwiKSkpDQoNCmFpZHMlPiVtdXRhdGUoLixGaXR0ZWQ9Zml0TWFyZyxSZXNpZHVhbD1yZXNNYXJnKSU+JQ0KICBnZ3Bsb3QoKSsNCiAgZ2VvbV9wb2ludChhZXMoeD1GaXR0ZWQseT1yZXNNYXJnLGNvbG9yPUZpdHRlZCkpKw0KICBnZW9tX3Ntb290aChhZXMoeD1GaXR0ZWQseT1yZXNNYXJnKSxzZT1GLA0KICAgICAgICAgICAgICBtZXRob2Q9ImxtIixmb3JtdWxhID0geSB+IHNwbGluZXM6OmJzKHgsMyksY29sb3I9ImJsYWNrIikrDQogIGdlb21faGxpbmUoeWludGVyY2VwdCA9IDAuMCxjb2xvcj0iYmx1ZSIsbGluZXR5cGU9MikrDQogIHRoZW1lX2J3KCkrDQogIG15Q29sb3Jfc2NhbGVfY29sDQoNCmFpZHMlPiVtdXRhdGUoLixGaXR0ZWQ9Zml0TWFyZyxSZXNpZHVhbD1yZXNNYXJnKSU+JQ0KICBnZ3Bsb3QoKSsNCiAgZ2VvbV9zbW9vdGgoYWVzKHg9b2JzdGltZSx5PWZpdE1hcmcsY29sb3I9cGF0aWVudCksc2U9RiwNCiAgICAgICAgICAgICAgbWV0aG9kPSJsbSIsZm9ybXVsYSA9IHkgfiB4LHNob3cubGVnZW5kID0gRikrDQogIGZhY2V0X3dyYXAofmRydWcpKw0KICB0aGVtZV9idygpDQoNCg0KYWlkcyU+JW11dGF0ZSguLEZpdHRlZD1maXRNYXJnLFJlc2lkdWFsPXJlc01hcmcpJT4lDQogIGdncGxvdCgpKw0KICBnZW9tX3Ntb290aChhZXMoeD1vYnN0aW1lLHk9Zml0TWFyZyxjb2xvcj1kcnVnKSxzZT1GLA0KICAgICAgICAgICAgICBtZXRob2Q9ImxtIixmb3JtdWxhID0geSB+IHNwbGluZXM6OmJzKHgpKSsNCiAgdGhlbWVfYncoKQ0KYGBgDQoNCmBgYHtyfQ0KIyBFdmVudCB0aW1lIG1hcmdpbmFsIGVmZmVjdA0KDQpFdmVudFJlcz1yZXNpZHVhbHMoam9pbnRtb2QsIHByb2Nlc3MgPSAiRXZlbnQiKQ0KbWl0PWZpdHRlZChqb2ludG1vZCwgcHJvY2VzcyA9ICJMb25naXR1ZGluYWwiLA0KICAgICAgICAgICAgICAgdHlwZSA9ICJFdmVudFRpbWUiKQ0KDQoNCmFpZHMlPiVtdXRhdGUoLixNYXJ0aW5nYWxlUmVzPUV2ZW50UmVzLHNzZnZsbz1taXQpJT4lDQogIGdncGxvdChhZXMoeD1zc2Z2bG8seT1NYXJ0aW5nYWxlUmVzKSkrDQogIGdlb21fcG9pbnQoYWVzKGNvbG9yPXNzZnZsbyksYWxwaGE9MC41LHNob3cubGVnZW5kID0gRikrDQogIGdlb21fc21vb3RoKHNlPUYsbWV0aG9kPSJsbSIsZm9ybXVsYSA9IHkgfiBzcGxpbmVzOjpicyh4KSxjb2xvcj0iYmxhY2siKSsNCiAgbXlDb2xvcl9zY2FsZV9jb2wrDQogIGdlb21faGxpbmUoeWludGVyY2VwdCA9IDAuMCxjb2xvcj0iYmx1ZSIsbGluZXR5cGU9MikrDQogIHRoZW1lX2J3KCkrbGFicyh5PSJNYXJ0aW5nYWxlIHJlc2lkdWFsIiwNCiAgICAgICAgICAgICAgICAgIHg9IlN1YmplY3Qgc3BlY2lmaWMgZml0dGVkIHZhbHVlcyBsb25naXR1ZGluYWwgb3V0Y29tZSIpDQoNCmFpZHMlPiVtdXRhdGUoLixNYXJ0aW5nYWxlUmVzPUV2ZW50UmVzLHNzZnZsbz1taXQpJT4lDQogIGdncGxvdChhZXMoeD1zc2Z2bG8seT1NYXJ0aW5nYWxlUmVzKSkrDQogIGdlb21fcG9pbnQoYWVzKGNvbG9yPWRydWcpLGFscGhhPTAuNSxzaG93LmxlZ2VuZCA9IEYpKw0KICBnZW9tX3Ntb290aChzZT1GLG1ldGhvZD0ibG0iLGZvcm11bGEgPSB5IH4gc3BsaW5lczo6YnMoeCksY29sb3I9ImJsYWNrIikrDQogIGdlb21faGxpbmUoeWludGVyY2VwdCA9IDAuMCxjb2xvcj0iYmx1ZSIsbGluZXR5cGU9MikrDQogIHRoZW1lX2J3KCkrbGFicyh5PSJNYXJ0aW5nYWxlIHJlc2lkdWFsIiwNCiAgICAgICAgICAgICAgICAgIHg9IlN1YmplY3Qgc3BlY2lmaWMgZml0dGVkIHZhbHVlcyBsb25naXR1ZGluYWwgb3V0Y29tZSIpKw0KICBmYWNldF93cmFwKH5kcnVnKQ0KYGBgDQoNCmBgYHtyfQ0KI1Jlc2lkdWFsIG9mIGV2ZW50IENveFNuZWxsDQoNCnJlc0NTVD1yZXNpZHVhbHMoam9pbnRtb2QsIA0KICAgICAgICAgICAgICAgICBwcm9jZXNzID0gIkV2ZW50IiwNCiAgICAgICAgICAgICAgICAgICAgdHlwZSA9ICJDb3hTbmVsbCIpDQoNCnNmaXQ9c3VydmZpdChTdXJ2KHJlc0NTVCxkZWF0aCl+MSxkYXRhPWFpZHMuaWQpDQoNCnBsb3Qoc2ZpdCwgbWFyay50aW1lID0gRkFMU0UsIGNvbmYuaW50ID0gVFJVRSxjb2w9YygicmVkIiwib3JhbmdlIiwicmVkNCIpLA0KICAgICB4bGFiID0gIkNveC1TbmVsbCBSZXNpZHVhbHMiLCB5bGFiID0gIlN1cnZpdmFsIFByb2JhYmlsaXR5IiwNCiAgICAgbWFpbiA9ICJTdXJ2aXZhbCBGdW5jdGlvbiBvZiBDb3gtU25lbGwgUmVzaWR1YWxzIikNCg0KY3VydmUoZXhwKC14KSwgZnJvbSA9IDAsIHRvID0gbWF4KGFpZHMuaWQkVGltZSksIGFkZCA9IFRSVUUsDQogICAgICAgIGNvbCA9ICJibHVlIiwgbHdkID0gMikNCg0KDQojUGxvdDINCg0Kc2ZpdDI9c3VydmZpdChTdXJ2KHJlc0NTVCwgZGVhdGgpIH4gZHJ1ZywgZGF0YSA9IGFpZHMuaWQpDQoNCnBsb3Qoc2ZpdDIsIG1hcmsudGltZSA9IEZBTFNFLCBjb25mLmludCA9IEZBTFNFLGNvbD1jKCJyZWQiLCJibHVlIiksDQogICAgICAgeGxhYiA9ICJDb3gtU25lbGwgUmVzaWR1YWxzIiwgeWxhYiA9ICJTdXJ2aXZhbCBQcm9iYWJpbGl0eSIsDQogICAgICAgbWFpbiA9ICJTdXJ2aXZhbCBGdW5jdGlvbiBvZiBDb3gtU25lbGwgUmVzaWR1YWxzIikNCg0KY3VydmUoZXhwKC14KSwgZnJvbSA9IDAsIA0KICAgICAgdG8gPSBtYXgoYWlkcy5pZCRUaW1lKSwgDQogICAgICBhZGQgPSBUUlVFLA0KICAgICAgY29sID0gInB1cnBsZSIsIGx3ZCA9IDIpDQoNCmBgYA0KDQoj4buobmcgZOG7pW5nOiBUacOqbiBsxrDhu6NuZyBjaG8gY8OhIHRo4buDDQoNCk3hu5l0IHRyb25nIG5o4buvbmcg4bupbmcgZOG7pW5nIHR1eeG7h3QgduG7nWkgY+G7p2EgSm9pbnQgbW9kZWwgbMOgIMSR4buDIHRpw6puIGzGsOG7o25nIHPhu5FuZyBjw7JuIGNobyBjw6EgdGjhu4MgdOG7q25nIGLhu4duaCBuaMOibiBk4buxYSB2w6BvIGLhu4duaCBz4butIGPhu6dhIGjhu40sIHRow60gZOG7pSDhu58gdGjDoW5nIHRo4bupIDYsIGThu7FhIHbDoG8ga+G6v3QgcXXhuqMgQ0Q0IOG7nyAzIGzhuqduIHRyxrDhu5tjLCB0YSBjw7MgdGjhu4MgdGnDqm4gbMaw4bujbmcga2jhuqMgbsSDbmcgc+G7kW5nIGPhu6dhIGLhu4duaCBuaMOibiDEkcOzIGNobyB0xrDGoW5nIGxhaSBuaGnhu4F1IHRow6FuZyBzYXUNCg0KTmhpIGNo4buNbiBi4bqldCBrw6wgYuG7h25oIG5ow6JuIG7DoG8gKGTEqSBuaGnDqm4gbMOgIHBo4bqjaSBjZW5zb3JlZCkgLCB0aMOtIGThu6Ugc+G7kSA5OQ0KDQpgYGB7cn0NCiNQcmVkaWN0aW9uIHdpdGggQ0kgYnkgTW9udGVDYXJsbyBzaW11bGF0aW9uDQoNCnNldC5zZWVkKDEyMykNCg0KYWlkc1thaWRzJHBhdGllbnQ9PSI5OSIsXQ0KDQojRnJvbSB0aGUgbGFzdCB0aW1lIHBvaW50DQoNCnNldC5zZWVkKDMzMykNCg0Kc3VydlByYnM9c3VydmZpdEpNKGpvaW50bW9kLCBpZFZhciA9ICJwYXRpZW50IiwNCiAgICAgICAgICAgICAgICAgICBuZXdkYXRhID0gYWlkc1thaWRzJHBhdGllbnQ9PSI5OSIsXSwNCiAgICAgICAgICAgICAgICAgICBsYXN0LnRpbWUgPSAib2JzdGltZSIpDQoNCnN1cnZQcmJzDQoNCnN1cnZQcmJzWzFdJHN1bW1hcmllcyRgOTlgJT4lYXNfdGliYmxlKCktPnRkZg0KcmJpbmQodGRmLGMoMCxyZXAoMSw0KSkpLT50ZGYNCiAgdGRmJT4lDQogIGdncGxvdChhZXMoeD10aW1lcykpKw0KICBnZW9tX3JpYmJvbihhZXMoeW1pbj1Mb3dlcix5bWF4PVVwcGVyKSxhbHBoYT0wLjUsZmlsbD0iZ29sZCIpKw0KICBnZW9tX2xpbmUoYWVzKHk9TWVkaWFuKSxjb2xvcj0icmVkMyIsc2l6ZT0xLGxpbmV0eXBlPTEpKw0KICBnZW9tX2xpbmUoYWVzKHk9TWVhbiksY29sb3I9ImJsdWUiLHNpemU9MSxsaW5ldHlwZT0xKSsNCiAgZ2VvbV9saW5lKGFlcyh5PUxvd2VyKSxjb2xvcj0iYmxhY2siLHNpemU9MSxsaW5ldHlwZT0yKSsNCiAgZ2VvbV9saW5lKGFlcyh5PVVwcGVyKSxjb2xvcj0iYmxhY2siLHNpemU9MSxsaW5ldHlwZT0yKSsNCiAgdGhlbWVfYncoKSsNCiAgbGFicyh0aXRsZT0iUGF0aWVudCBOwrA5OSIseD0iVGltZSIseT0iUHJlZGljdGVkIHN1cnZpdmFsIHByb2JhYmlsaXR5IixjYXB0aW9uPSJKb2ludCBNb2RlbCIpDQoNCmBgYA0KDQpIb+G6t2MgYuG7h25oIG5ow6JuIHPhu5EgMzAwDQoNCmBgYHtyfQ0Kc3VydlByYnMzPXN1cnZmaXRKTShqb2ludG1vZCwgaWRWYXIgPSAicGF0aWVudCIsDQogICAgICAgICAgICAgICAgICAgICAgbmV3ZGF0YSA9IGFpZHNbYWlkcyRwYXRpZW50PT0iMzAwIixdLA0KICAgICAgICAgICAgICAgICAgICAgIGxhc3QudGltZSA9ICJvYnN0aW1lIikNCiAgDQogIHN1cnZQcmJzMw0KICANCiAgc3VydlByYnMzWzFdJHN1bW1hcmllcyRgMzAwYCU+JWFzX3RpYmJsZSgpLT50ZGYNCiAgcmJpbmQodGRmLGMoMCxyZXAoMSw0KSkpLT50ZGYNCiAgdGRmJT4lDQogICAgZ2dwbG90KGFlcyh4PXRpbWVzKSkrDQogICAgZ2VvbV9lcnJvcmJhcihhZXMoeW1pbj1Mb3dlcix5bWF4PVVwcGVyLGNvbG9yPXJlb3JkZXIodGltZXMsTWVkaWFuKSksc2hvdy5sZWdlbmQgPSBGKSsNCiAgICBnZW9tX3BvaW50KGFlcyh5PU1lZGlhbixmaWxsPXJlb3JkZXIodGltZXMsTWVkaWFuKSksc2hhcGU9MjEsY29sb3I9ImJsYWNrIixzaG93LmxlZ2VuZCA9IEYpKw0KICAgIHRoZW1lX2J3KCkrDQogICAgbGFicyh0aXRsZT0iUGF0aWVudCBOwrAzMDAiLHg9IlRpbWUiLHk9IlByZWRpY3RlZCBzdXJ2aXZhbCBwcm9iYWJpbGl0eSIsY2FwdGlvbj0iSm9pbnQgTW9kZWwiKQ0KICANCg0KICANCiAgDQpgYGANCg0KVsOsIGNo4bupYSBj4bqjIDIgdGjDoG5oIHBo4bqnbiBsb25naXR1ZGluYWwgdsOgIHN1cnZpdmFsLCBt4buZdCBtw7QgaMOsbmggSm9pbnQgbW9kZWwgY8OybiBjaG8gcGjDqXAgdGnDqm4gbMaw4bujbmcgZ2nDoSB0cuG7iyBtYXJrZXIgKENENCkgdGhlbyB0aOG7nWkgZ2lhbjoNCg0KVGjDrSBk4bulIHRhIGNo4buNbiBuZ+G6q3Ugbmhpw6puIGLhu4duaCBuaMOibiBz4buRIDU1LiBUYSBjw7MgdGjhu4MgdGnDqm4gbMaw4bujbmcgQ0Q0IGNobyB0xrDGoW5nIGxhaSB0cm9uZyA0IGhvw6BuIGPhuqNuaCBraMOhYyBuaGF1OiBuZ2F5IHThu6sgbOG6p24gaOG6uW4gxJHhuqd1IHRpw6puLCB2w6BvIHRo4budaSDEkWnhu4NtIHRow6FuZyB0aOG7qSAyLCA2LCB2w6AgMTINCg0KYGBge3J9DQphaWRzW2FpZHMkcGF0aWVudD09NTUsXS0+bmV3ZGF0DQoNCmxvbmdQcmVkcz12ZWN0b3IoImxpc3QiLCBucm93KG5ld2RhdCkpDQoNCmZvciAoaSBpbiAxOm5yb3cobmV3ZGF0KSkgew0Kc2V0LnNlZWQoMTIzKQ0KICAgIGxvbmdQcmVkc1tbaV1dPXByZWRpY3Qoam9pbnRtb2QsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgbmV3ZGF0YSA9IG5ld2RhdFsxOmksIF0sDQogICAgICAgICAgICAgICAgICAgICAgICAgICB0eXBlID0gIlN1YmplY3QiLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgIGludGVydmFsID0gImNvbmZpZGVuY2UiLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgIHJldHVybkRhdGEgPSBUUlVFLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgaWRWYXI9InBhdGllbnQiKQ0KICAgIGxvbmdQcmVkc1tbaV1dJEZvbGxvd1VwPXJvdW5kKG1heChuZXdkYXRbMTppLCAib2JzdGltZSJdKSwgMSkNCiAgfQ0KDQpsb25nUHJlZHMlPD4lZG8uY2FsbChyYmluZCwuKSU+JWFzX3RpYmJsZSgpDQoNCmxvbmdQcmVkcyU+JQ0KICBnZ3Bsb3QoYWVzKHg9b2JzdGltZSkpKw0KICBnZW9tX3JpYmJvbihhZXMoeW1pbj1sb3cseW1heD11cHApLGFscGhhPTAuNSxmaWxsPSJnb2xkIikrDQogIGdlb21fbGluZShhZXMoeT1wcmVkKSxjb2xvcj0icmVkMyIsc2l6ZT0xKSsNCiAgZ2VvbV9saW5lKGFlcyh5PWxvdyksY29sb3I9ImJsYWNrIixzaXplPTEsbGluZXR5cGU9MikrDQogIGdlb21fbGluZShhZXMoeT11cHApLGNvbG9yPSJibGFjayIsc2l6ZT0xLGxpbmV0eXBlPTIpKw0KICB0aGVtZV9idygpKw0KICBmYWNldF93cmFwKH5Gb2xsb3dVcCkrDQogIGxhYnModGl0bGU9IlBhdGllbnQgTsKwNTUiLHg9IlRpbWUiLHk9IlByZWRpY3RlZCBDRDQiLGNhcHRpb249IkpvaW50IE1vZGVsIikNCg0KYGBgDQoNCkNobyBi4buHbmggbmjDom4gc+G7kSA1NSBuw6B5LCB0YSBjw7MgdGjhu4Mga2nhu4NtIHRyYSDEkeG7mSBjaMOtbmggeMOhYyBj4bunYSB0acOqbiBsxrDhu6NuZyB0cm9uZyA0IGhvw6BuIGPhuqNuaDogdsOgbyB0aMOhbmcgdGjhu6kgMiw2LDEyIHbDoCAxOA0KDQpgYGB7cn0NCiMgUGVyZm9ybWFuY2UgZXZhbHVhdGlvbg0KDQpzZXQuc2VlZCgxMjMpDQoNCm5ld2RhdCR0MD1hcy5udW1lcmljKG5ld2RhdCRvYnN0aW1lID09IDApDQoNCnJvY2FpZHM9IHJvY0pNKGpvaW50bW9kLCANCiAgICAgICAgICAgICAgIGR0ID0gYygyLDYsMTIsMTgpLCANCiAgICAgICAgICAgICAgIGRhdGEgPSBuZXdkYXQsIA0KICAgICAgICAgICAgICAgb3B0VGhyPSJ5b3VkZW4iLA0KICAgICAgICAgICAgICAgTT0xMDAwLGJ1cm4uaW49NTAwLA0KICAgICAgICAgICAgICAgaWRWYXI9InBhdGllbnQiKQ0KDQpwbG90KHJvY2FpZHMsIGxlZ2VuZCA9IFRSVUUpDQoNCnJvY2FpZHMNCmBgYA0KDQojS+G6v3QgbHXhuq1uOg0KDQpUcm9uZyBiw6BpIG7DoHksIGPDoWMgYuG6oW4gxJHDoyBsw6BtIHF1ZW4gduG7m2kgSm9pbnQgbW9kZWwsIG3hu5l0IHRyb25nIG5o4buvbmcgY8O0bmcgY+G7pSBj4bqnbiB0aGnhur90IGNobyBuZ2hpw6puIGPhu6l1IGzDom0gc8OgbmcuDQoNCk3DtCBow6xuaCBsacOqbiBo4bujcCAoSm9pbnQgbW9kZWwpIGNobyBwaMOpcCBjaMO6bmcgdGEgaOG7o3AgbmjhuqV0IGhhaSBi4buZIHBo4bqtbiB0xrDhu59uZyBjaOG7q25nIG5oxrAgxJHhu5ljIGzhuq1wIHRyb25nIG3hu5l0IHRo4butIG5naGnhu4dtIGzDom0gc8OgbmcsIMSRw7MgbMOgIGLDoGkgdG/DoW4gTG9uZ2l0dWRpbmFsIGFuYWx5c2lzIHbDoCBiw6BpIHRvw6FuIFN1cnZpdmFsIGFuYWx5c2lzLiBWaeG7h2MgaOG7o3AgbmjhuqV0IG7DoHkgY2hvIHBow6lwIGdp4bqjaSBxdXnhur90IGjDoG5nIGxv4bqhdCBjw6J1IGjhu49pLCBuaMawIDogDQoNCisgQ2jhu6luZyBtaW5oIHbhu4EgbGnDqm4gaOG7hyBnaeG7r2EgYuG7h25oIHPhu60gdGjDtG5nIHF1YSAxIG1hcmtlciB2w6Agbmd1eSBjxqEgdOG7rSB2b25nLA0KDQorIGNo4bupbmcgbWluaCAgMSBzdXJyb2dhdGUgbWFya2VyIGPDsyB0aOG7gyBkw7luZyDEkeG7gyDEkcOhbmggZ2nDoSBoaeG7h3UgcXXhuqMgxJFp4buBdSB0cuG7iyBj4bunYSB0aHXhu5FjLCANCg0KKyB0acOqbiBsxrDhu6NuZyBkaeG7hW4gdGnhur9uIGPhu6dhIG1hcmtlciB2w6Agbmd1eSBjxqEgdOG7rSB2b25nIGNobyB04burbmcgYuG7h25oIG5ow6JuIHJpw6puZyBiaeG7h3QgbeG7mXQga2hpIMSRw6MgYmnhur90IGLhu4duaCBz4butIGPhu6dhIGPDoSBuaMOibiBuw6B5LiANCg0KSFkgduG7jW5nIGLDoGkgdmnhur90IHThuqFvIMSRxrDhu6NjIGPhuqNtIGjhu6luZyBjaG8gY8OhYyBi4bqhbiBi4bqvdCDEkeG6p3UgdMOsbSBoaeG7g3UgduG7gSBwaMawxqFuZyBwaMOhcCBuw6B5Lg0KDQpN4buZdCBiw6BpIGtow6FjIHPhur0gxJHGsOG7o2MgdGjhu7FjIGhp4buHbiDEkeG7gyDEkWkgdGhlbyBjb24gxJHGsOG7nW5nIEJheWVzLg0KDQoqKlhpbiBj4bqjbSDGoW4gdsOgIGjhurluIGfhurdwIGzhuqFpLioqDQo=