1 Đặ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.

2 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\)\(\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()
median iqr mean sd
1.72 1.46 1.854264 1.042271

3 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\)\(\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.

4 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()

5 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.

5.1 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\)\(\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\)\(\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()

5.2 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\)\(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()

6 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==