Đặt vấn đề
Trong y văn, đặc tính phân phối của các đại lượng được nghiên cứu
thường chỉ được mô tả một cách giản lược dưới hình thức trung bình (độ
lệch chuẩn) hoặc trung vị (khoảng tứ phân vị). Những thông tin này chỉ
cung cấp khái niệm về khuynh hướng trung tâm và mưc độ phân tán của dữ
liệu, nhưng không đủ để tái hiện lại hình ảnh chính xác của phân phối
thực tế.
Trên thực tế, các đại lượng y sinh học thường không khớp với phân
phối chuẩn, nhưng có những đặc điểm như thang đo chỉ bao gồm giá trị
>0, lệch phải, và đuôi dài. Ngoài ta, cơ chế tạo nên giá trị của các
thông số này thường tương đồng với tiến trình tích lũy theo quy luật
phân phối Gamma; do đó quy luật phân phối xác suất Gamma có thể phù hợp
hơn để ước lượng chúng.
Trong một số hoàn cảnh, nhà khoa học có nhu cầu mô phỏng lại chính
xác dữ liệu của một đại lượng dựa theo thông tin mô tả từ y văn, với
tiêu chí càng gần với thực tế càng tốt. Việc mô phỏng này có rất nhiều
ứng dụng thực tiễn, bao gồm ước tính cỡ mẫu, tối ưu hóa hiệu suất của
một suy luận thống kê, thiết kế analysis plan, phân tích độ nhạy, phân
tích tổng hợp, ước tính chi phí/hiệu quả của can thiệp điều trị, tối ưu
hóa chính sách y tế, giảng dạy và học tập v.v.
Tuy nhiên, mô phỏng chính xác phân phối Gamma khi chỉ có trong tay
thông tin về trung vị và khoảng tứ phân vị hay trung bình và độ lệch
chuẩn là một bài toán không hề đơn giản, vì phân phối Gamma là một phân
phối xác suất phức tạp được xác định bởi hệ tham số hoàn toàn khác so
với phân phối chuẩn, và các hàm PDF, CDF và quantile của nó không có
dạng đóng.
Bài thực hành sau sẽ cung cấp 2 giải pháp hiệu quả cho bài toán
này.
Thí dụ minh họa
library(tidyverse)
library(fitdistrplus)
library(nleqslv)
Trong một nghiên cứu, người ta định lượng hormone LH (luteinizing
hormone) vào ngày trigger trong một chu kì kích thích buồng trứng. Phân
phối của dữ liệu thực nghiệm có hình ảnh như sau:
# Load data
df <- readxl::read_xlsx("Data_Qianwen_Xi.xlsx")%>%
filter(Protocol == "PPOS")%>%
dplyr::select(LH_TD)%>%
na.omit()
# KDE plot
df %>% ggplot()+
geom_density(aes(x = LH_TD),
fill ="red",
alpha = 0.6)+
theme_bw()
Có nhiều cơ sở cho thấy quy luật phân phối Gamma có thể phù hợp với
dữ liệu này, bao gồm:
Lượng hormone là kết quả của quá trình tăng trưởng của các tế bào chế
tiết, và được tích lũy theo thời gian tương ứng với tiến trình tăng
trưởng theo quy luật hàm mũ;
Đại lượng sinh lý này không thể âm và có khuynh hướng lệch phải chứ
không đối xứng;
Giả định này có thể được kiểm nghiệm một cách trực quan bằng cách
khớp một phân phối Gamma với dữ liệu thực nghiệm bằng package
fitdistrplus. Kết quả cho thấy quả thực phân phối Gamma khớp tốt với dữ
liệu thực tế, với các tham số ước lượng là \(k
= 3.010749\) , \(\theta =
1.623632\) và \(\beta = 1/\theta =
0.61590\) .
# Fit gamma
fit_gamma <- fitdist(df$LH_TD, "gamma")
# value of params
fit_gamma$estimate
## shape rate
## 3.010749 1.623632
# Comparative plots with legend
df%>%ggplot(aes(x=LH_TD))+
geom_density(aes(y = after_stat(density)),
fill = "gold",
color = NA,
linetype = 1,
alpha = 0.6) +
stat_function(fun = dgamma,
args = list(shape = fit_gamma$estimate[1],
rate = fit_gamma$estimate[2]),
linetype = 2,
color = "black") +
theme_bw()
Tuy nhiên, trong bài báo, người ta chỉ báo cáo mean(sd) hoặc
median(IQR) của giá trị LH như sau:
df %>%
summarise(median = median(LH_TD),
iqr = IQR(LH_TD),
mean = mean(LH_TD),
sd = sd(LH_TD))%>%
knitr::kable()
1.72
1.46
1.854264
1.042271
Phân tích bài toán
Phân phối Gamma là một phân phối xác suất liên tục, thường được tham
số hóa bởi hai tham số: \(k\) (shape,
hình dạng của phân phối, ảnh hưởng đến độ lệch và độ nhọn), \(\theta\) (scale) và \(\beta = 1/\theta\) (rate) (ảnh hưởng đến độ
phân tán của phân phối), với hàm mật độ xác suất:
\[f(x; k, \theta) = \frac{1}{\Gamma(k)
\theta^k} x^{k-1} e^{-x/\theta}, \quad x > 0, , k > 0, , \theta
> 0\] Trong đó \(\Gamma(k)\)
là hàm Gamma, một phiên bản tổng quát của giai thừa cho các số thực.
Mục tiêu của bài toán hiện thời là xác định các tham số \(k\) và \(\theta\) (hoặc \(\beta\) ), sao cho phân phối Gamma tạo ra có
trung vị và khoảng tứ phân vị, hoặc trung bình và độ lệch chuẩn khớp với
các giá trị được báo cáo từ y văn.
Trường hợp biết trung
bình và độ lệch chuẩn
Trong trường hợp biết trung bình và độ lệch chuẩn, giải pháp tương
đối đơn giản vì các tham số của phân phối Gamma có thể được ước lượng
dựa vào công thức tính giá trị kỳ vọng và phương sai của phân phối
Gamma:
\[E(X) = k\theta, \quad Var(X) =
k\theta^2\] Từ đó:
\[k = \frac{\mu^2}{\sigma^2}, \quad \theta
= \frac{\sigma^2}{\mu}, \quad \beta = 1/\theta\] Ta viết hai
function để mô phỏng dữ liệu từ trung bình và độ lệch chuẩn
param_from_mean_sd <- function(mu, sigma) {
shape <- (mu^2)/(sigma^2)
rate <- mu/sigma^2
scale <- 1/rate
return(list(shape = shape,
rate = rate,
scale = scale))
}
# Simulate data from mean and sd
sim_gamma_from_mean_sd <- function(mu, sigma, n) {
params <- param_from_mean_sd(mu, sigma)
sim_data <- rgamma(n,
shape = params[['shape']],
rate = params[['rate']])
return(sim_data)
}
Theo kết quả mô tả ở trên, trung bình và độ lệch chuẩn của giá trị LH
là 1.854264 và 1.042271, ta có thể mô phỏng dữ liệu từ thông tin
này.
Kết quả cho thấy dữ liệu mô phỏng (màu xanh) khá gần với dữ liệu thực
tế (màu đỏ)
set.seed(123)
sim_x <- sim_gamma_from_mean_sd(1.854264, 1.042271,1000)
df %>% ggplot()+
geom_density(aes(x = LH_TD, y = after_stat(density)),
fill = "red",
color = NA,
linetype = 1,
alpha = 0.3) +
geom_density(data = tibble(sim_x),
aes(x = sim_x),
fill ="blue",
color = NA,
alpha = 0.3)+
theme_bw()
Trường hợp biết trung
vị và khoảng tứ phân vị
Trường hợp này thực ra là khó hơn nhiều, vì 2 thông số này không cho
phép ước lượng trực tiếp các tham số của phân phối Gamma.
Giải pháp 1: khớp
phân vị
Bài toán này có thể giải bằng phương pháp “khớp phân vị” (quantiles
matching). Đầu tiên, ta tính trung vị, và IQR (bách phân vị thứ 25 và
75) từ một giá trị \(k\) và \(\theta\) bất kì, cách làm là sử dụng nghịch
đảo của hàm CDF, hay hàm quantile của phân phối Gamma.
Như vậy, ta tạo ra một hệ phương trình phi tuyến 2 ẩn:
\[\begin{cases} qgamma(q=0.5, shape = k,
scale = \theta) - median_{k,\theta} = 0 \\ qgamma(q=0.75, shape = k,
scale = \theta) - qgamma(q=0.25, shape = k, scale = \theta) -
IQR_{k,\theta} = 0\end{cases}\]
Vì qgamma là một hàm phi tuyến phức tạp (không có nghiệm dạng đóng),
ta phải sử dụng thuật toán số học để giải hệ phương trình \(F(k,θ)=0\)
Package nleqslv trong R cung cấp phương pháp giải hệ phương trình phi
tuyến dựa trên các thuật toán như Newton-Raphson hoặc Broyden. Các bước
cơ bản của thuật toán:
Khởi tạo giá trị ban đầu: Chọn một điểm khởi đầu \(x_0 = [k_0,\theta_0]\) gần với nghiệm thực
tế để đảm bảo hội tụ.
Sử dụng phương pháp lặp để cập nhật giá trị \(x_i\) từ \(x_{i-1}\) , với công thức: \(x_i = x_{i-1} - J^{-1}F(x_{i-1})\) , trong
đó \(J\) là ma trận Jacobian của hàm
\(F\) .
Lặp lại quá trình trên cho đến khi hội tụ, tức là \(F(x_i)\) đủ nhỏ.
Sau khi giải hệ phương trình, ta thu được giá trị \(k\) và \(\theta\) tối ưu, từ đó ta có thể tạo ra một
mẫu dữ liệu tuân theo phân phối Gamma với các tham số này.
Ta viết một function để triển khai phương pháp này:
quantile_matching_nleqslv <- function(m, iqr) {
sys_fun <- function(x) {
k <- x[1]
theta <- x[2]
eq1 <- qgamma(0.5, shape = k, scale = theta) - m
eq2 <- qgamma(0.75, shape = k,scale = theta) - qgamma(0.25, shape = k, scale = theta) - iqr
return(c(eq1, eq2))
}
init_k <- 1
init_theta <- m / qgamma(0.5, shape = init_k, scale = 1)
x_init <- c(init_k, init_theta)
sol <- nleqslv(x_init, sys_fun)
if (sol$termcd != 1) warning("nleqslv did not converge.")
return(list(shape = sol$x[1],
rate = 1/sol$x[2],
scale = sol$x[2]))
}
Áp dụng function này cho trung vị và IQR của dữ liệu LH, kết quả là
ta cũng thu được kết quả khá gần với thực tế:
set.seed(123)
params <- quantile_matching_nleqslv(1.72, 1.46)
sim_x <- rgamma(1000,
shape = params[['shape']],
rate = params[['rate']])
df %>% ggplot()+
geom_density(aes(x = LH_TD, y = after_stat(density)),
fill = "red",
color = NA,
linetype = 1,
alpha = 0.3) +
geom_density(data = tibble(sim_x),
aes(x = sim_x),
fill ="blue",
color = NA,
alpha = 0.3)+
theme_bw()
Giải pháp khác: khớp
tỷ số
Một phương pháp khác giản lược hơn để giải bài toán này là “khớp tỷ
số” (ratio matching).
Phương pháp này tận dụng đặc tính của phân phối Gamma: tỷ số giữa
median và IQR phụ thuộc duy nhất vào tham số \(k\) (shape) khi \(\theta\) được chuẩn hóa (scale = 1). Khi so
sánh tỷ số này với tỷ số được cung cấp từ y văn, ta tạo ra một phương
trình phi tuyến với một ẩn số là \(k\) :
\[f(k) = \frac{median_k}{IQR_k} -
\frac{median_{true}}{IQR_{true}}\] Như vậy ta đã giản lược bài
toán từ hệ 2 phương trình xuống còn 1 phương trình và một ẩn, tuy nhiên
phương trình này cũng không có công thức nghiệm đóng, nó chỉ có thể được
giải bằng phương pháp số học.
Ví dụ, ta có thể dùng thuật toán uniroot trong R với cơ chế như
sau:
Cung cấp một khoảng ước lượng chứa nghiệm \([l,u]\) , với \(l\) và \(u\) là hai giá trị của k, và ta biết rằng
nghiệm k nằm trong khoảng này.
Chia đôi tuần tự khoảng này thành nhiều khoảng nhỏ hơn, rồi tính
giá trị của hàm \(f(m)\) tại giữa mỗi
khoảng, với \(m = (l+u)/2\) . Nếu \(f(m)\) có dấu trái với \(f(l)\) , ta chuyển \(u\) thành \(m\) , ngược lại, ta chuyển \(l\) thành \(m\) .
Lặp lại quá trình trên cho đến khi khoảng \([l,u]\) đủ nhỏ, và ta coi \(m\) là nghiệm của phương trình.
Sau khi giải được \(k\) , ta có thể
dùng \(k\) và tỷ số \(\frac{median_k}{IQR_k}\) để giải hệ phương
trình ban đầu, từ đó tìm ra \(\theta\)
hoặc \(\beta\) .
Ta viết R function để triển khai giải pháp này. Kết quả của 3 tham số
hoàn toàn giống với cách làm thứ nhất, và dữ liệu mô phỏng ra cũng gần
với thực tế:
# Quantile matching using uniroot
quantile_match_uniroot <- function(m,iqr){
f <- function(k){
med_k <- qgamma(0.5, shape = k, scale = 1)
iqr_k <- qgamma(0.75, shape = k, scale = 1) - qgamma(0.25, shape = k, scale = 1)
ratio_k <- med_k/iqr_k
target_ratio <- m/iqr
return(ratio_k - target_ratio)
}
alpha_initial <- (1.35 * m / iqr)^2
lower_k <- max(0.1, alpha_initial - 5)
upper_k <- alpha_initial + 5
k_sol <- uniroot(f,
lower = lower_k,
upper = upper_k)$root
theta_sol <- m / qgamma(0.5, shape = k_sol, scale = 1)
return(list(shape = k_sol,
rate = 1/theta_sol,
scale = theta_sol))
}
solved_d <- quantile_match_uniroot(1.72, 1.46)
# Simulate data with the solved k and theta
sim_x <- rgamma(1000,
shape = solved_d[['shape']],
rate = solved_d[['rate']])
# compared 2 KDE
df %>% ggplot()+
geom_density(aes(x = LH_TD, y = after_stat(density)),
fill = "red",
color = NA,
linetype = 1,
alpha = 0.3) +
geom_density(data = tibble(sim_x),
aes(x = sim_x),
fill ="blue",
color = NA,
alpha = 0.3)+
theme_bw()
Kết luận
Qua bài thực hành này, chúng ta đã có thể mô phỏng phân phối Gamma từ
thông tin mô tả trong y văn, thông qua 2 phương pháp: khớp trung bình và
độ lệch chuẩn, và khớp trung vị và khoảng tứ phân vị. Cả hai phương pháp
đều cho kết quả khá gần với dữ liệu thực tế, và có thể áp dụng trong
nhiều tình huống thực tiễn khác nhau.
LS0tDQp0aXRsZTogIk3DtCBwaOG7j25nIHBow6JuIHBo4buRaSBHYW1tYSB04burIHkgdsSDbiINCmF1dGhvcjogIkJTLiBMw6ogTmfhu41jIEto4bqjIE5oaSINCmRhdGU6ICIxMiBUaMOhbmcgMyBuxINtIDIwMjUiDQpvdXRwdXQ6DQogIGh0bWxfZG9jdW1lbnQ6DQogICAgY29kZV9kb3dubG9hZDogeWVzDQogICAgY29kZV9mb2xkaW5nOiBoaWRlDQogICAgbnVtYmVyX3NlY3Rpb25zOiB5ZXMNCiAgICB0aGVtZTogZGVmYXVsdA0KICAgIHRvYzogeWVzDQogICAgdG9jX2Zsb2F0OiB5ZXMNCiAgICBkZXY6IHN2Zw0KICB3b3JkX2RvY3VtZW50Og0KICAgIHRvYzogeWVzDQogIHBkZl9kb2N1bWVudDogDQogICAgdG9jOiB5ZXMNCiAgICBsYXRleF9lbmdpbmU6IGx1YWxhdGV4DQogICAga2VlcF90ZXg6IHllcw0KLS0tDQoNCiFbXShHYW1tYVNpbS5wbmcpDQoNCmBgYHtyIHNldHVwLGluY2x1ZGU9RkFMU0V9DQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFQsIHdhcm5pbmcgPSBGLCBtZXNzYWdlID0gRikNCmBgYA0KDQojIMSQ4bq3dCB24bqlbiDEkeG7gQ0KDQpUcm9uZyB5IHbEg24sIMSR4bq3YyB0w61uaCBwaMOibiBwaOG7kWkgY+G7p2EgY8OhYyDEkeG6oWkgbMaw4bujbmcgxJHGsOG7o2MgbmdoacOqbiBj4bupdSB0aMaw4budbmcgY2jhu4kgxJHGsOG7o2MgbcO0IHThuqMgbeG7mXQgY8OhY2ggZ2nhuqNuIGzGsOG7o2MgZMaw4bubaSBow6xuaCB0aOG7qWMgdHJ1bmcgYsOsbmggKMSR4buZIGzhu4djaCBjaHXhuqluKSBob+G6t2MgdHJ1bmcgduG7iyAoa2hv4bqjbmcgdOG7qSBwaMOibiB24buLKS4gTmjhu69uZyB0aMO0bmcgdGluIG7DoHkgY2jhu4kgY3VuZyBj4bqlcCBraMOhaSBuaeG7h20gduG7gSBraHV5bmggaMaw4bubbmcgdHJ1bmcgdMOibSB2w6AgbcawYyDEkeG7mSBwaMOibiB0w6FuIGPhu6dhIGThu68gbGnhu4d1LCBuaMawbmcga2jDtG5nIMSR4bunIMSR4buDIHTDoWkgaGnhu4duIGzhuqFpIGjDrG5oIOG6o25oIGNow61uaCB4w6FjIGPhu6dhIHBow6JuIHBo4buRaSB0aOG7sWMgdOG6vy4gDQoNClRyw6puIHRo4buxYyB04bq/LCBjw6FjIMSR4bqhaSBsxrDhu6NuZyB5IHNpbmggaOG7jWMgdGjGsOG7nW5nIGtow7RuZyBraOG7m3AgduG7m2kgcGjDom4gcGjhu5FpIGNodeG6qW4sIG5oxrBuZyBjw7Mgbmjhu69uZyDEkeG6t2MgxJFp4buDbSBuaMawIHRoYW5nIMSRbyBjaOG7iSBiYW8gZ+G7k20gZ2nDoSB0cuG7iyA+MCwgbOG7h2NoIHBo4bqjaSwgdsOgIMSRdcO0aSBkw6BpLiBOZ2/DoGkgdGEsIGPGoSBjaOG6vyB04bqhbyBuw6puIGdpw6EgdHLhu4sgY+G7p2EgY8OhYyB0aMO0bmcgc+G7kSBuw6B5IHRoxrDhu51uZyB0xrDGoW5nIMSR4buTbmcgduG7m2kgdGnhur9uIHRyw6xuaCB0w61jaCBsxal5IHRoZW8gcXV5IGx14bqtdCBwaMOibiBwaOG7kWkgR2FtbWE7IGRvIMSRw7MgcXV5IGx14bqtdCBwaMOibiBwaOG7kWkgeMOhYyBzdeG6pXQgR2FtbWEgY8OzIHRo4buDIHBow7kgaOG7o3AgaMahbiDEkeG7gyDGsOG7m2MgbMaw4bujbmcgY2jDum5nLg0KDQpUcm9uZyBt4buZdCBz4buRIGhvw6BuIGPhuqNuaCwgbmjDoCBraG9hIGjhu41jIGPDsyBuaHUgY+G6p3UgbcO0IHBo4buPbmcgbOG6oWkgY2jDrW5oIHjDoWMgZOG7ryBsaeG7h3UgY+G7p2EgbeG7mXQgxJHhuqFpIGzGsOG7o25nIGThu7FhIHRoZW8gdGjDtG5nIHRpbiBtw7QgdOG6oyB04burIHkgdsSDbiwgduG7m2kgdGnDqnUgY2jDrSBjw6BuZyBn4bqnbiB24bubaSB0aOG7sWMgdOG6vyBjw6BuZyB04buRdC4gVmnhu4djIG3DtCBwaOG7j25nIG7DoHkgY8OzIHLhuqV0IG5oaeG7gXUg4bupbmcgZOG7pW5nIHRo4buxYyB0aeG7hW4sIGJhbyBn4buTbSDGsOG7m2MgdMOtbmggY+G7oSBt4bqrdSwgdOG7kWkgxrB1IGjDs2EgaGnhu4d1IHN14bqldCBj4bunYSBt4buZdCBzdXkgbHXhuq1uIHRo4buRbmcga8OqLCB0aGnhur90IGvhur8gYW5hbHlzaXMgcGxhbiwgcGjDom4gdMOtY2ggxJHhu5kgbmjhuqF5LCBwaMOibiB0w61jaCB04buVbmcgaOG7o3AsIMaw4bubYyB0w61uaCBjaGkgcGjDrS9oaeG7h3UgcXXhuqMgY+G7p2EgY2FuIHRoaeG7h3AgxJFp4buBdSB0cuG7iywgdOG7kWkgxrB1IGjDs2EgY2jDrW5oIHPDoWNoIHkgdOG6vywgZ2nhuqNuZyBk4bqheSB2w6AgaOG7jWMgdOG6rXAgdi52Lg0KDQpUdXkgbmhpw6puLCBtw7QgcGjhu49uZyBjaMOtbmggeMOhYyBwaMOibiBwaOG7kWkgR2FtbWEga2hpIGNo4buJIGPDsyB0cm9uZyB0YXkgdGjDtG5nIHRpbiB24buBIHRydW5nIHbhu4sgdsOgIGtob+G6o25nIHThu6kgcGjDom4gduG7iyBoYXkgdHJ1bmcgYsOsbmggdsOgIMSR4buZIGzhu4djaCBjaHXhuqluIGzDoCBt4buZdCBiw6BpIHRvw6FuIGtow7RuZyBo4buBIMSRxqFuIGdp4bqjbiwgdsOsIHBow6JuIHBo4buRaSBHYW1tYSBsw6AgbeG7mXQgcGjDom4gcGjhu5FpIHjDoWMgc3XhuqV0IHBo4bupYyB04bqhcCDEkcaw4bujYyB4w6FjIMSR4buLbmggYuG7n2kgaOG7hyB0aGFtIHPhu5EgaG/DoG4gdG/DoG4ga2jDoWMgc28gduG7m2kgcGjDom4gcGjhu5FpIGNodeG6qW4sIHbDoCBjw6FjIGjDoG0gUERGLCBDREYgdsOgIHF1YW50aWxlIGPhu6dhIG7DsyBraMO0bmcgY8OzIGThuqFuZyDEkcOzbmcuDQoNCkLDoGkgdGjhu7FjIGjDoG5oIHNhdSBz4bq9IGN1bmcgY+G6pXAgMiBnaeG6o2kgcGjDoXAgaGnhu4d1IHF14bqjIGNobyBiw6BpIHRvw6FuIG7DoHkuDQoNCiMgVGjDrSBk4bulIG1pbmggaOG7jWEgDQoNCmBgYHtyfQ0KbGlicmFyeSh0aWR5dmVyc2UpDQpsaWJyYXJ5KGZpdGRpc3RycGx1cykNCmxpYnJhcnkobmxlcXNsdikNCmBgYA0KDQpUcm9uZyBt4buZdCBuZ2hpw6puIGPhu6l1LCBuZ8aw4budaSB0YSDEkeG7i25oIGzGsOG7o25nIGhvcm1vbmUgTEggKGx1dGVpbml6aW5nIGhvcm1vbmUpIHbDoG8gbmfDoHkgdHJpZ2dlciB0cm9uZyBt4buZdCBjaHUga8OsIGvDrWNoIHRow61jaCBideG7k25nIHRy4bupbmcuIFBow6JuIHBo4buRaSBj4bunYSBk4buvIGxp4buHdSB0aOG7sWMgbmdoaeG7h20gY8OzIGjDrG5oIOG6o25oIG5oxrAgc2F1Og0KDQpgYGB7cn0NCiMgTG9hZCBkYXRhDQpkZiA8LSByZWFkeGw6OnJlYWRfeGxzeCgiRGF0YV9RaWFud2VuX1hpLnhsc3giKSU+JQ0KICBmaWx0ZXIoUHJvdG9jb2wgPT0gIlBQT1MiKSU+JQ0KICBkcGx5cjo6c2VsZWN0KExIX1REKSU+JQ0KICBuYS5vbWl0KCkNCg0KIyBLREUgcGxvdA0KZGYgJT4lIGdncGxvdCgpKw0KICBnZW9tX2RlbnNpdHkoYWVzKHggPSBMSF9URCksIA0KICAgICAgICAgICAgICAgZmlsbCA9InJlZCIsDQogICAgICAgICAgICAgICBhbHBoYSA9IDAuNikrDQogIHRoZW1lX2J3KCkNCmBgYA0KDQpDw7Mgbmhp4buBdSBjxqEgc+G7nyBjaG8gdGjhuqV5IHF1eSBsdeG6rXQgcGjDom4gcGjhu5FpIEdhbW1hIGPDsyB0aOG7gyBwaMO5IGjhu6NwIHbhu5tpIGThu68gbGnhu4d1IG7DoHksIGJhbyBn4buTbToNCg0KTMaw4bujbmcgaG9ybW9uZSBsw6Aga+G6v3QgcXXhuqMgY+G7p2EgcXXDoSB0csOsbmggdMSDbmcgdHLGsOG7n25nIGPhu6dhIGPDoWMgdOG6vyBiw6BvIGNo4bq/IHRp4bq/dCwgdsOgIMSRxrDhu6NjIHTDrWNoIGzFqXkgdGhlbyB0aOG7nWkgZ2lhbiB0xrDGoW5nIOG7qW5nIHbhu5tpIHRp4bq/biB0csOsbmggdMSDbmcgdHLGsOG7n25nIHRoZW8gcXV5IGx14bqtdCBow6BtIG3FqTsNCg0KxJDhuqFpIGzGsOG7o25nIHNpbmggbMO9IG7DoHkga2jDtG5nIHRo4buDIMOibSB2w6AgY8OzIGtodXluaCBoxrDhu5tuZyBs4buHY2ggcGjhuqNpIGNo4bupIGtow7RuZyDEkeG7kWkgeOG7qW5nOw0KDQpHaeG6oyDEkeG7i25oIG7DoHkgY8OzIHRo4buDIMSRxrDhu6NjIGtp4buDbSBuZ2hp4buHbSBt4buZdCBjw6FjaCB0cuG7sWMgcXVhbiBi4bqxbmcgY8OhY2gga2jhu5twIG3hu5l0IHBow6JuIHBo4buRaSBHYW1tYSB24bubaSBk4buvIGxp4buHdSB0aOG7sWMgbmdoaeG7h20gYuG6sW5nIHBhY2thZ2UgZml0ZGlzdHJwbHVzLiBL4bq/dCBxdeG6oyBjaG8gdGjhuqV5IHF14bqjIHRo4buxYyBwaMOibiBwaOG7kWkgR2FtbWEga2jhu5twIHThu5F0IHbhu5tpIGThu68gbGnhu4d1IHRo4buxYyB04bq/LCB24bubaSBjw6FjIHRoYW0gc+G7kSDGsOG7m2MgbMaw4bujbmcgbMOgICRrID0gMy4wMTA3NDkkLCAkXHRoZXRhID0gMS42MjM2MzIkIHbDoCAkXGJldGEgPSAxL1x0aGV0YSA9IDAuNjE1OTAkLg0KDQoNCmBgYHtyfQ0KIyBGaXQgZ2FtbWENCmZpdF9nYW1tYSA8LSBmaXRkaXN0KGRmJExIX1RELCAiZ2FtbWEiKQ0KDQojIHZhbHVlIG9mIHBhcmFtcw0KZml0X2dhbW1hJGVzdGltYXRlDQoNCiMgQ29tcGFyYXRpdmUgcGxvdHMgd2l0aCBsZWdlbmQNCg0KZGYlPiVnZ3Bsb3QoYWVzKHg9TEhfVEQpKSsNCiAgZ2VvbV9kZW5zaXR5KGFlcyh5ID0gYWZ0ZXJfc3RhdChkZW5zaXR5KSksIA0KICAgICAgICAgICAgICAgZmlsbCA9ICJnb2xkIiwNCiAgICAgICAgICAgICAgIGNvbG9yID0gTkEsDQogICAgICAgICAgICAgICBsaW5ldHlwZSA9IDEsDQogICAgICAgICAgICAgICBhbHBoYSA9IDAuNikgKw0KICBzdGF0X2Z1bmN0aW9uKGZ1biA9IGRnYW1tYSwgDQogICAgICAgICAgICAgICAgYXJncyA9IGxpc3Qoc2hhcGUgPSBmaXRfZ2FtbWEkZXN0aW1hdGVbMV0sIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJhdGUgPSBmaXRfZ2FtbWEkZXN0aW1hdGVbMl0pLCANCiAgICAgICAgICAgICAgICBsaW5ldHlwZSA9IDIsDQogICAgICAgICAgICAgICAgY29sb3IgPSAiYmxhY2siKSArDQogIHRoZW1lX2J3KCkNCmBgYA0KDQpUdXkgbmhpw6puLCB0cm9uZyBiw6BpIGLDoW8sIG5nxrDhu51pIHRhIGNo4buJIGLDoW8gY8OhbyBtZWFuKHNkKSBob+G6t2MgbWVkaWFuKElRUikgY+G7p2EgZ2nDoSB0cuG7iyBMSCBuaMawIHNhdToNCg0KYGBge3J9DQpkZiAlPiUgDQogIHN1bW1hcmlzZShtZWRpYW4gPSBtZWRpYW4oTEhfVEQpLA0KICAgICAgICAgICAgaXFyID0gSVFSKExIX1REKSwNCiAgICAgICAgICAgIG1lYW4gPSBtZWFuKExIX1REKSwNCiAgICAgICAgICAgIHNkID0gc2QoTEhfVEQpKSU+JQ0KICBrbml0cjo6a2FibGUoKQ0KYGBgDQoNCiMgUGjDom4gdMOtY2ggYsOgaSB0b8Ohbg0KDQpQaMOibiBwaOG7kWkgR2FtbWEgbMOgIG3hu5l0IHBow6JuIHBo4buRaSB4w6FjIHN14bqldCBsacOqbiB04bulYywgdGjGsOG7nW5nIMSRxrDhu6NjIHRoYW0gc+G7kSBow7NhIGLhu59pIGhhaSB0aGFtIHPhu5E6ICRrJCAoc2hhcGUsIGjDrG5oIGThuqFuZyBj4bunYSBwaMOibiBwaOG7kWksIOG6o25oIGjGsOG7n25nIMSR4bq/biDEkeG7mSBs4buHY2ggdsOgIMSR4buZIG5o4buNbiksICRcdGhldGEkIChzY2FsZSkgdsOgICRcYmV0YSA9IDEvXHRoZXRhJCAocmF0ZSkgKOG6o25oIGjGsOG7n25nIMSR4bq/biDEkeG7mSBwaMOibiB0w6FuIGPhu6dhIHBow6JuIHBo4buRaSksIHbhu5tpIGjDoG0gbeG6rXQgxJHhu5kgeMOhYyBzdeG6pXQ6DQoNCiQkZih4OyBrLCBcdGhldGEpID0gXGZyYWN7MX17XEdhbW1hKGspIFx0aGV0YV5rfSB4XntrLTF9IGVeey14L1x0aGV0YX0sIFxxdWFkIHggPiAwLCAsIGsgPiAwLCAsIFx0aGV0YSA+IDAkJA0KVHJvbmcgxJHDsyAkXEdhbW1hKGspJCBsw6AgaMOgbSBHYW1tYSwgbeG7mXQgcGhpw6puIGLhuqNuIHThu5VuZyBxdcOhdCBj4bunYSBnaWFpIHRo4burYSBjaG8gY8OhYyBz4buRIHRo4buxYy4NCg0KTeG7pWMgdGnDqnUgY+G7p2EgYsOgaSB0b8OhbiBoaeG7h24gdGjhu51pIGzDoCB4w6FjIMSR4buLbmggY8OhYyB0aGFtIHPhu5EgJGskIHbDoCAkXHRoZXRhJCAoaG/hurdjICRcYmV0YSQpLCBzYW8gY2hvIHBow6JuIHBo4buRaSBHYW1tYSB04bqhbyByYSBjw7MgdHJ1bmcgduG7iyB2w6Aga2hv4bqjbmcgdOG7qSBwaMOibiB24buLLCBob+G6t2MgdHJ1bmcgYsOsbmggdsOgIMSR4buZIGzhu4djaCBjaHXhuqluIGto4bubcCB24bubaSBjw6FjIGdpw6EgdHLhu4sgxJHGsOG7o2MgYsOhbyBjw6FvIHThu6sgeSB2xINuLg0KDQojIFRyxrDhu51uZyBo4bujcCBiaeG6v3QgdHJ1bmcgYsOsbmggdsOgIMSR4buZIGzhu4djaCBjaHXhuqluDQoNClRyb25nIHRyxrDhu51uZyBo4bujcCBiaeG6v3QgdHJ1bmcgYsOsbmggdsOgIMSR4buZIGzhu4djaCBjaHXhuqluLCBnaeG6o2kgcGjDoXAgdMawxqFuZyDEkeG7kWkgxJHGoW4gZ2nhuqNuIHbDrCBjw6FjIHRoYW0gc+G7kSBj4bunYSBwaMOibiBwaOG7kWkgR2FtbWEgY8OzIHRo4buDIMSRxrDhu6NjIMaw4bubYyBsxrDhu6NuZyBk4buxYSB2w6BvIGPDtG5nIHRo4bupYyB0w61uaCBnacOhIHRy4buLIGvhu7MgduG7jW5nIHbDoCBwaMawxqFuZyBzYWkgY+G7p2EgcGjDom4gcGjhu5FpIEdhbW1hOg0KDQokJEUoWCkgPSBrXHRoZXRhLCBccXVhZCBWYXIoWCkgPSBrXHRoZXRhXjIkJA0KVOG7qyDEkcOzOg0KDQokJGsgPSBcZnJhY3tcbXVeMn17XHNpZ21hXjJ9LCBccXVhZCBcdGhldGEgPSBcZnJhY3tcc2lnbWFeMn17XG11fSwgXHF1YWQgXGJldGEgPSAxL1x0aGV0YSQkDQpUYSB2aeG6v3QgaGFpIGZ1bmN0aW9uIMSR4buDIG3DtCBwaOG7j25nIGThu68gbGnhu4d1IHThu6sgdHJ1bmcgYsOsbmggdsOgIMSR4buZIGzhu4djaCBjaHXhuqluDQoNCmBgYHtyfQ0KcGFyYW1fZnJvbV9tZWFuX3NkIDwtIGZ1bmN0aW9uKG11LCBzaWdtYSkgew0KICANCiAgc2hhcGUgPC0gKG11XjIpLyhzaWdtYV4yKQ0KICByYXRlIDwtIG11L3NpZ21hXjINCiAgc2NhbGUgPC0gMS9yYXRlDQogIA0KICByZXR1cm4obGlzdChzaGFwZSA9IHNoYXBlLCANCiAgICAgICAgICAgICAgcmF0ZSA9IHJhdGUsIA0KICAgICAgICAgICAgICBzY2FsZSA9IHNjYWxlKSkNCn0NCg0KIyBTaW11bGF0ZSBkYXRhIGZyb20gbWVhbiBhbmQgc2QNCnNpbV9nYW1tYV9mcm9tX21lYW5fc2QgPC0gZnVuY3Rpb24obXUsIHNpZ21hLCBuKSB7DQogIA0KICBwYXJhbXMgPC0gcGFyYW1fZnJvbV9tZWFuX3NkKG11LCBzaWdtYSkNCiAgDQogIHNpbV9kYXRhIDwtIHJnYW1tYShuLCANCiAgICAgICAgICAgICAgICAgICAgIHNoYXBlID0gcGFyYW1zW1snc2hhcGUnXV0sIA0KICAgICAgICAgICAgICAgICAgICAgcmF0ZSA9IHBhcmFtc1tbJ3JhdGUnXV0pDQogIA0KICByZXR1cm4oc2ltX2RhdGEpDQp9DQpgYGANCg0KVGhlbyBr4bq/dCBxdeG6oyBtw7QgdOG6oyDhu58gdHLDqm4sIHRydW5nIGLDrG5oIHbDoCDEkeG7mSBs4buHY2ggY2h14bqpbiBj4bunYSBnacOhIHRy4buLIExIIGzDoCAxLjg1NDI2NCB2w6AgMS4wNDIyNzEsIHRhIGPDsyB0aOG7gyBtw7QgcGjhu49uZyBk4buvIGxp4buHdSB04burIHRow7RuZyB0aW4gbsOgeS4NCg0KS+G6v3QgcXXhuqMgY2hvIHRo4bqleSBk4buvIGxp4buHdSBtw7QgcGjhu49uZyAobcOgdSB4YW5oKSBraMOhIGfhuqduIHbhu5tpIGThu68gbGnhu4d1IHRo4buxYyB04bq/IChtw6B1IMSR4buPKQ0KDQpgYGB7cn0NCnNldC5zZWVkKDEyMykNCg0Kc2ltX3ggPC0gc2ltX2dhbW1hX2Zyb21fbWVhbl9zZCgxLjg1NDI2NCwgMS4wNDIyNzEsMTAwMCkNCg0KZGYgJT4lIGdncGxvdCgpKw0KICBnZW9tX2RlbnNpdHkoYWVzKHggPSBMSF9URCwgeSA9IGFmdGVyX3N0YXQoZGVuc2l0eSkpLCANCiAgICAgICAgICAgICAgIGZpbGwgPSAicmVkIiwNCiAgICAgICAgICAgICAgIGNvbG9yID0gTkEsDQogICAgICAgICAgICAgICBsaW5ldHlwZSA9IDEsDQogICAgICAgICAgICAgICBhbHBoYSA9IDAuMykgKw0KICAgZ2VvbV9kZW5zaXR5KGRhdGEgPSB0aWJibGUoc2ltX3gpLA0KICAgICAgICAgICAgICAgYWVzKHggPSBzaW1feCksIA0KICAgICAgICAgICAgICAgZmlsbCA9ImJsdWUiLA0KICAgICAgICAgICAgICAgY29sb3IgPSBOQSwNCiAgICAgICAgICAgICAgIGFscGhhID0gMC4zKSsNCiAgdGhlbWVfYncoKQ0KYGBgDQoNCiMgVHLGsOG7nW5nIGjhu6NwIGJp4bq/dCB0cnVuZyB24buLIHbDoCBraG/huqNuZyB04bupIHBow6JuIHbhu4sNCg0KVHLGsOG7nW5nIGjhu6NwIG7DoHkgdGjhu7FjIHJhIGzDoCBraMOzIGjGoW4gbmhp4buBdSwgdsOsIDIgdGjDtG5nIHPhu5EgbsOgeSBraMO0bmcgY2hvIHBow6lwIMaw4bubYyBsxrDhu6NuZyB0cuG7sWMgdGnhur9wIGPDoWMgdGhhbSBz4buRIGPhu6dhIHBow6JuIHBo4buRaSBHYW1tYS4NCg0KIyMgR2nhuqNpIHBow6FwIDE6IGto4bubcCBwaMOibiB24buLDQoNCkLDoGkgdG/DoW4gbsOgeSBjw7MgdGjhu4MgZ2nhuqNpIGLhurFuZyBwaMawxqFuZyBwaMOhcCAia2jhu5twIHBow6JuIHbhu4siIChxdWFudGlsZXMgbWF0Y2hpbmcpLiANCsSQ4bqndSB0acOqbiwgdGEgdMOtbmggdHJ1bmcgduG7iywgdsOgIElRUiAoYsOhY2ggcGjDom4gduG7iyB0aOG7qSAyNSB2w6AgNzUpIHThu6sgbeG7mXQgZ2nDoSB0cuG7iyAkayQgdsOgICRcdGhldGEkIGLhuqV0IGvDrCwgY8OhY2ggbMOgbSBsw6Agc+G7rSBk4bulbmcgbmdo4buLY2ggxJHhuqNvIGPhu6dhIGjDoG0gQ0RGLCBoYXkgaMOgbSBxdWFudGlsZSBj4bunYSBwaMOibiBwaOG7kWkgR2FtbWEuDQoNCk5oxrAgduG6rXksIHRhIHThuqFvIHJhIG3hu5l0IGjhu4cgcGjGsMahbmcgdHLDrG5oIHBoaSB0dXnhur9uIDIg4bqpbjoNCg0KJCRcYmVnaW57Y2FzZXN9IHFnYW1tYShxPTAuNSwgc2hhcGUgPSBrLCBzY2FsZSA9IFx0aGV0YSkgLSBtZWRpYW5fe2ssXHRoZXRhfSA9IDAgXFwgcWdhbW1hKHE9MC43NSwgc2hhcGUgPSBrLCBzY2FsZSA9IFx0aGV0YSkgLSBxZ2FtbWEocT0wLjI1LCBzaGFwZSA9IGssIHNjYWxlID0gXHRoZXRhKSAtIElRUl97ayxcdGhldGF9ID0gMFxlbmR7Y2FzZXN9JCQNCg0KVsOsIHFnYW1tYSBsw6AgbeG7mXQgaMOgbSBwaGkgdHV54bq/biBwaOG7qWMgdOG6oXAgKGtow7RuZyBjw7MgbmdoaeG7h20gZOG6oW5nIMSRw7NuZyksIHRhIHBo4bqjaSBz4butIGThu6VuZyB0aHXhuq10IHRvw6FuIHPhu5EgaOG7jWMgxJHhu4MgZ2nhuqNpIGjhu4cgcGjGsMahbmcgdHLDrG5oICRGKGsszrgpPTAkDQoNClBhY2thZ2UgbmxlcXNsdiB0cm9uZyBSIGN1bmcgY+G6pXAgcGjGsMahbmcgcGjDoXAgZ2nhuqNpIGjhu4cgcGjGsMahbmcgdHLDrG5oIHBoaSB0dXnhur9uIGThu7FhIHRyw6puIGPDoWMgdGh14bqtdCB0b8OhbiBuaMawIE5ld3Rvbi1SYXBoc29uIGhv4bq3YyBCcm95ZGVuLiBDw6FjIGLGsOG7m2MgY8ahIGLhuqNuIGPhu6dhIHRodeG6rXQgdG/DoW46DQoNCisgS2jhu59pIHThuqFvIGdpw6EgdHLhu4sgYmFuIMSR4bqndTogQ2jhu41uIG3hu5l0IMSRaeG7g20ga2jhu59pIMSR4bqndSAkeF8wID0gW2tfMCxcdGhldGFfMF0kIGfhuqduIHbhu5tpIG5naGnhu4dtIHRo4buxYyB04bq/IMSR4buDIMSR4bqjbSBi4bqjbyBo4buZaSB04bulLg0KDQorIFPhu60gZOG7pW5nIHBoxrDGoW5nIHBow6FwIGzhurdwIMSR4buDIGPhuq1wIG5o4bqtdCBnacOhIHRy4buLICR4X2kkIHThu6sgJHhfe2ktMX0kLCB24bubaSBjw7RuZyB0aOG7qWM6ICR4X2kgPSB4X3tpLTF9IC0gSl57LTF9Rih4X3tpLTF9KSQsIHRyb25nIMSRw7MgJEokIGzDoCBtYSB0cuG6rW4gSmFjb2JpYW4gY+G7p2EgaMOgbSAkRiQuDQoNCisgTOG6t3AgbOG6oWkgcXXDoSB0csOsbmggdHLDqm4gY2hvIMSR4bq/biBraGkgaOG7mWkgdOG7pSwgdOG7qWMgbMOgICRGKHhfaSkkIMSR4bunIG5o4buPLg0KDQpTYXUga2hpIGdp4bqjaSBo4buHIHBoxrDGoW5nIHRyw6xuaCwgdGEgdGh1IMSRxrDhu6NjIGdpw6EgdHLhu4sgJGskIHbDoCAkXHRoZXRhJCB04buRaSDGsHUsIHThu6sgxJHDsyB0YSBjw7MgdGjhu4MgdOG6oW8gcmEgbeG7mXQgbeG6q3UgZOG7ryBsaeG7h3UgdHXDom4gdGhlbyBwaMOibiBwaOG7kWkgR2FtbWEgduG7m2kgY8OhYyB0aGFtIHPhu5EgbsOgeS4NCg0KVGEgdmnhur90IG3hu5l0IGZ1bmN0aW9uIMSR4buDIHRyaeG7g24ga2hhaSBwaMawxqFuZyBwaMOhcCBuw6B5Og0KDQpgYGB7cn0NCnF1YW50aWxlX21hdGNoaW5nX25sZXFzbHYgPC0gZnVuY3Rpb24obSwgaXFyKSB7DQogIA0KICBzeXNfZnVuIDwtIGZ1bmN0aW9uKHgpIHsNCiAgICBrIDwtIHhbMV0NCiAgICB0aGV0YSA8LSB4WzJdDQogICAgDQogICAgZXExIDwtIHFnYW1tYSgwLjUsIHNoYXBlID0gaywgc2NhbGUgPSB0aGV0YSkgLSBtDQogICAgDQogICAgZXEyIDwtIHFnYW1tYSgwLjc1LCBzaGFwZSA9IGssc2NhbGUgPSB0aGV0YSkgLSBxZ2FtbWEoMC4yNSwgc2hhcGUgPSBrLCBzY2FsZSA9IHRoZXRhKSAtIGlxcg0KICAgIA0KICAgIHJldHVybihjKGVxMSwgZXEyKSkNCiAgfQ0KICANCiAgaW5pdF9rIDwtIDENCiAgaW5pdF90aGV0YSA8LSBtIC8gcWdhbW1hKDAuNSwgc2hhcGUgPSBpbml0X2ssIHNjYWxlID0gMSkNCiAgDQogIHhfaW5pdCA8LSBjKGluaXRfaywgaW5pdF90aGV0YSkNCiAgDQogIHNvbCA8LSBubGVxc2x2KHhfaW5pdCwgc3lzX2Z1bikNCiAgaWYgKHNvbCR0ZXJtY2QgIT0gMSkgd2FybmluZygibmxlcXNsdiBkaWQgbm90IGNvbnZlcmdlLiIpDQogIA0KICByZXR1cm4obGlzdChzaGFwZSA9IHNvbCR4WzFdLCANCiAgICAgICAgICAgICAgcmF0ZSA9IDEvc29sJHhbMl0sIA0KICAgICAgICAgICAgICBzY2FsZSA9IHNvbCR4WzJdKSkNCn0NCmBgYA0KDQoNCsOBcCBk4bulbmcgZnVuY3Rpb24gbsOgeSBjaG8gdHJ1bmcgduG7iyB2w6AgSVFSIGPhu6dhIGThu68gbGnhu4d1IExILCBr4bq/dCBxdeG6oyBsw6AgdGEgY8WpbmcgdGh1IMSRxrDhu6NjIGvhur90IHF14bqjIGtow6EgZ+G6p24gduG7m2kgdGjhu7FjIHThur86DQoNCmBgYHtyfQ0Kc2V0LnNlZWQoMTIzKQ0KDQpwYXJhbXMgPC0gcXVhbnRpbGVfbWF0Y2hpbmdfbmxlcXNsdigxLjcyLCAxLjQ2KQ0KDQpzaW1feCA8LSByZ2FtbWEoMTAwMCwgDQogICAgICAgICAgICAgICAgc2hhcGUgPSBwYXJhbXNbWydzaGFwZSddXSwgDQogICAgICAgICAgICAgICAgcmF0ZSA9IHBhcmFtc1tbJ3JhdGUnXV0pDQoNCmRmICU+JSBnZ3Bsb3QoKSsNCiAgZ2VvbV9kZW5zaXR5KGFlcyh4ID0gTEhfVEQsIHkgPSBhZnRlcl9zdGF0KGRlbnNpdHkpKSwgDQogICAgICAgICAgICAgICBmaWxsID0gInJlZCIsDQogICAgICAgICAgICAgICBjb2xvciA9IE5BLA0KICAgICAgICAgICAgICAgbGluZXR5cGUgPSAxLA0KICAgICAgICAgICAgICAgYWxwaGEgPSAwLjMpICsNCiAgICBnZW9tX2RlbnNpdHkoZGF0YSA9IHRpYmJsZShzaW1feCksDQogICAgICAgICAgICAgICBhZXMoeCA9IHNpbV94KSwgDQogICAgICAgICAgICAgICBmaWxsID0iYmx1ZSIsDQogICAgICAgICAgICAgICBjb2xvciA9IE5BLA0KICAgICAgICAgICAgICAgYWxwaGEgPSAwLjMpKw0KICB0aGVtZV9idygpDQpgYGANCg0KIyMgR2nhuqNpIHBow6FwIGtow6FjOiBraOG7m3AgdOG7tyBz4buRDQoNCk3hu5l0IHBoxrDGoW5nIHBow6FwIGtow6FjIGdp4bqjbiBsxrDhu6NjIGjGoW4gxJHhu4MgZ2nhuqNpIGLDoGkgdG/DoW4gbsOgeSBsw6AgImto4bubcCB04bu3IHPhu5EiIChyYXRpbyBtYXRjaGluZykuDQoNClBoxrDGoW5nIHBow6FwIG7DoHkgdOG6rW4gZOG7pW5nIMSR4bq3YyB0w61uaCBj4bunYSBwaMOibiBwaOG7kWkgR2FtbWE6IHThu7cgc+G7kSBnaeG7r2EgbWVkaWFuIHbDoCBJUVIgcGjhu6UgdGh14buZYyBkdXkgbmjhuqV0IHbDoG8gdGhhbSBz4buRICRrJCAoc2hhcGUpIGtoaSAkXHRoZXRhJCDEkcaw4bujYyBjaHXhuqluIGjDs2EgKHNjYWxlID0gMSkuIEtoaSBzbyBzw6FuaCB04bu3IHPhu5EgbsOgeSB24bubaSB04bu3IHPhu5EgxJHGsOG7o2MgY3VuZyBj4bqlcCB04burIHkgdsSDbiwgdGEgdOG6oW8gcmEgbeG7mXQgcGjGsMahbmcgdHLDrG5oIHBoaSB0dXnhur9uIHbhu5tpIG3hu5l0IOG6qW4gc+G7kSBsw6AgJGskOg0KDQokJGYoaykgPSBcZnJhY3ttZWRpYW5fa317SVFSX2t9IC0gXGZyYWN7bWVkaWFuX3t0cnVlfX17SVFSX3t0cnVlfX0kJA0KTmjGsCB24bqteSB0YSDEkcOjIGdp4bqjbiBsxrDhu6NjIGLDoGkgdG/DoW4gdOG7qyBo4buHIDIgcGjGsMahbmcgdHLDrG5oIHh14buRbmcgY8OybiAxIHBoxrDGoW5nIHRyw6xuaCB2w6AgbeG7mXQg4bqpbiwgdHV5IG5oacOqbiBwaMawxqFuZyB0csOsbmggbsOgeSBjxaluZyBraMO0bmcgY8OzIGPDtG5nIHRo4bupYyBuZ2hp4buHbSDEkcOzbmcsIG7DsyBjaOG7iSBjw7MgdGjhu4MgxJHGsOG7o2MgZ2nhuqNpIGLhurFuZyBwaMawxqFuZyBwaMOhcCBz4buRIGjhu41jLg0KDQpWw60gZOG7pSwgdGEgY8OzIHRo4buDIGTDuW5nIHRodeG6rXQgdG/DoW4gdW5pcm9vdCB0cm9uZyBSIHbhu5tpIGPGoSBjaOG6vyBuaMawIHNhdToNCg0KKyBDdW5nIGPhuqVwIG3hu5l0IGtob+G6o25nIMaw4bubYyBsxrDhu6NuZyBjaOG7qWEgbmdoaeG7h20gJFtsLHVdJCwgduG7m2kgJGwkIHbDoCAkdSQgbMOgIGhhaSBnacOhIHRy4buLIGPhu6dhIGssIHbDoCB0YSBiaeG6v3QgcuG6sW5nIG5naGnhu4dtIGsgbuG6sW0gdHJvbmcga2hv4bqjbmcgbsOgeS4NCg0KKyBDaGlhIMSRw7RpIHR14bqnbiB04buxIGtob+G6o25nIG7DoHkgdGjDoG5oIG5oaeG7gXUga2hv4bqjbmcgbmjhu48gaMahbiwgcuG7k2kgdMOtbmggZ2nDoSB0cuG7iyBj4bunYSBow6BtICRmKG0pJCB04bqhaSBnaeG7r2EgbeG7l2kga2hv4bqjbmcsIHbhu5tpICRtID0gKGwrdSkvMiQuIE7hur91ICRmKG0pJCBjw7MgZOG6pXUgdHLDoWkgduG7m2kgJGYobCkkLCB0YSBjaHV54buDbiAkdSQgdGjDoG5oICRtJCwgbmfGsOG7o2MgbOG6oWksIHRhIGNodXnhu4NuICRsJCB0aMOgbmggJG0kLg0KDQorIEzhurdwIGzhuqFpIHF1w6EgdHLDrG5oIHRyw6puIGNobyDEkeG6v24ga2hpIGtob+G6o25nICRbbCx1XSQgxJHhu6cgbmjhu48sIHbDoCB0YSBjb2kgJG0kIGzDoCBuZ2hp4buHbSBj4bunYSBwaMawxqFuZyB0csOsbmguDQoNClNhdSBraGkgZ2nhuqNpIMSRxrDhu6NjICRrJCwgdGEgY8OzIHRo4buDIGTDuW5nICRrJCB2w6AgdOG7tyBz4buRICRcZnJhY3ttZWRpYW5fa317SVFSX2t9JCDEkeG7gyBnaeG6o2kgaOG7hyBwaMawxqFuZyB0csOsbmggYmFuIMSR4bqndSwgdOG7qyDEkcOzIHTDrG0gcmEgJFx0aGV0YSQgaG/hurdjICRcYmV0YSQuDQoNClRhIHZp4bq/dCBSIGZ1bmN0aW9uIMSR4buDIHRyaeG7g24ga2hhaSBnaeG6o2kgcGjDoXAgbsOgeS4gS+G6v3QgcXXhuqMgY+G7p2EgMyB0aGFtIHPhu5EgaG/DoG4gdG/DoG4gZ2nhu5FuZyB24bubaSBjw6FjaCBsw6BtIHRo4bupIG5o4bqldCwgdsOgIGThu68gbGnhu4d1IG3DtCBwaOG7j25nIHJhIGPFqW5nIGfhuqduIHbhu5tpIHRo4buxYyB04bq/Og0KDQpgYGB7cn0NCiMgUXVhbnRpbGUgbWF0Y2hpbmcgdXNpbmcgdW5pcm9vdA0KDQpxdWFudGlsZV9tYXRjaF91bmlyb290IDwtIGZ1bmN0aW9uKG0saXFyKXsNCiAgDQogIGYgPC0gZnVuY3Rpb24oayl7DQogICAgDQogICAgbWVkX2sgPC0gcWdhbW1hKDAuNSwgc2hhcGUgPSBrLCBzY2FsZSA9IDEpDQogICAgaXFyX2sgPC0gcWdhbW1hKDAuNzUsIHNoYXBlID0gaywgc2NhbGUgPSAxKSAtIHFnYW1tYSgwLjI1LCBzaGFwZSA9IGssIHNjYWxlID0gMSkNCiAgICANCiAgICByYXRpb19rIDwtIG1lZF9rL2lxcl9rDQogICAgdGFyZ2V0X3JhdGlvIDwtIG0vaXFyDQogICAgDQogICAgcmV0dXJuKHJhdGlvX2sgLSB0YXJnZXRfcmF0aW8pDQogIH0NCiAgDQogIGFscGhhX2luaXRpYWwgPC0gKDEuMzUgKiBtIC8gaXFyKV4yDQogIGxvd2VyX2sgPC0gbWF4KDAuMSwgYWxwaGFfaW5pdGlhbCAtIDUpDQogIHVwcGVyX2sgPC0gYWxwaGFfaW5pdGlhbCArIDUNCiAgDQogIGtfc29sIDwtIHVuaXJvb3QoZiwgDQogICAgICAgICAgICAgICAgICAgbG93ZXIgPSBsb3dlcl9rLCANCiAgICAgICAgICAgICAgICAgICB1cHBlciA9IHVwcGVyX2spJHJvb3QNCiAgDQogIHRoZXRhX3NvbCA8LSBtIC8gcWdhbW1hKDAuNSwgc2hhcGUgPSBrX3NvbCwgc2NhbGUgPSAxKQ0KICANCiAgcmV0dXJuKGxpc3Qoc2hhcGUgPSBrX3NvbCwgDQogICAgICAgICAgICAgIHJhdGUgPSAxL3RoZXRhX3NvbCwgDQogICAgICAgICAgICAgIHNjYWxlID0gdGhldGFfc29sKSkNCn0NCg0Kc29sdmVkX2QgPC0gcXVhbnRpbGVfbWF0Y2hfdW5pcm9vdCgxLjcyLCAxLjQ2KQ0KDQojIFNpbXVsYXRlIGRhdGEgd2l0aCB0aGUgc29sdmVkIGsgYW5kIHRoZXRhDQoNCnNpbV94IDwtIHJnYW1tYSgxMDAwLCANCiAgICAgICAgICAgICAgICAgICBzaGFwZSA9IHNvbHZlZF9kW1snc2hhcGUnXV0sIA0KICAgICAgICAgICAgICAgICAgIHJhdGUgPSBzb2x2ZWRfZFtbJ3JhdGUnXV0pDQoNCiMgY29tcGFyZWQgMiBLREUNCg0KZGYgJT4lIGdncGxvdCgpKw0KICBnZW9tX2RlbnNpdHkoYWVzKHggPSBMSF9URCwgeSA9IGFmdGVyX3N0YXQoZGVuc2l0eSkpLCANCiAgICAgICAgICAgICAgIGZpbGwgPSAicmVkIiwNCiAgICAgICAgICAgICAgIGNvbG9yID0gTkEsDQogICAgICAgICAgICAgICBsaW5ldHlwZSA9IDEsDQogICAgICAgICAgICAgICBhbHBoYSA9IDAuMykgKw0KICAgIGdlb21fZGVuc2l0eShkYXRhID0gdGliYmxlKHNpbV94KSwNCiAgICAgICAgICAgICAgIGFlcyh4ID0gc2ltX3gpLCANCiAgICAgICAgICAgICAgIGZpbGwgPSJibHVlIiwNCiAgICAgICAgICAgICAgIGNvbG9yID0gTkEsDQogICAgICAgICAgICAgICBhbHBoYSA9IDAuMykrDQogIHRoZW1lX2J3KCkNCmBgYA0KDQojIEvhur90IGx14bqtbg0KDQpRdWEgYsOgaSB0aOG7sWMgaMOgbmggbsOgeSwgY2jDum5nIHRhIMSRw6MgY8OzIHRo4buDIG3DtCBwaOG7j25nIHBow6JuIHBo4buRaSBHYW1tYSB04burIHRow7RuZyB0aW4gbcO0IHThuqMgdHJvbmcgeSB2xINuLCB0aMO0bmcgcXVhIDIgcGjGsMahbmcgcGjDoXA6IGto4bubcCB0cnVuZyBiw6xuaCB2w6AgxJHhu5kgbOG7h2NoIGNodeG6qW4sIHbDoCBraOG7m3AgdHJ1bmcgduG7iyB2w6Aga2hv4bqjbmcgdOG7qSBwaMOibiB24buLLiBD4bqjIGhhaSBwaMawxqFuZyBwaMOhcCDEkeG7gXUgY2hvIGvhur90IHF14bqjIGtow6EgZ+G6p24gduG7m2kgZOG7ryBsaeG7h3UgdGjhu7FjIHThur8sIHbDoCBjw7MgdGjhu4Mgw6FwIGThu6VuZyB0cm9uZyBuaGnhu4F1IHTDrG5oIGh14buRbmcgdGjhu7FjIHRp4buFbiBraMOhYyBuaGF1Lg==