library(tidyverse)
## Warning: package 'tidyverse' was built under R version 4.4.3
## Warning: package 'ggplot2' was built under R version 4.4.3
## Warning: package 'tidyr' was built under R version 4.4.3
## Warning: package 'readr' was built under R version 4.4.3
## Warning: package 'purrr' was built under R version 4.4.3
## Warning: package 'dplyr' was built under R version 4.4.3
## Warning: package 'stringr' was built under R version 4.4.2
## Warning: package 'forcats' was built under R version 4.4.3
## Warning: package 'lubridate' was built under R version 4.4.3
## ── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
## ✔ dplyr     1.1.4     ✔ readr     2.1.5
## ✔ forcats   1.0.0     ✔ stringr   1.5.1
## ✔ ggplot2   3.5.1     ✔ tibble    3.2.1
## ✔ lubridate 1.9.4     ✔ tidyr     1.3.1
## ✔ purrr     1.0.4     
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ dplyr::filter() masks stats::filter()
## ✖ dplyr::lag()    masks stats::lag()
## ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
library(epitools)
library(DescTools)
## Warning: package 'DescTools' was built under R version 4.4.3
library(DT)
## Warning: package 'DT' was built under R version 4.4.3
library(energy)
## Warning: package 'energy' was built under R version 4.4.3
library(readr)
options(digits = 4)

Tóm tắt sách Generalized Linear Models with R Examples

Cuốn sách “Generalized Linear Models with Examples in R” tập trung giới thiệu về lý thuyết và ứng dụng của mô hình tuyến tính tổng quát (GLM) — một khung tổng quát mở rộng các mô hình tuyến tính cổ điển, thích hợp cho nhiều loại biến kết quả không tuân theo phân phối chuẩn như nhị phân, đếm, và phân phối khác.

Sách đi sâu vào các thành phần chính của GLM: hàm liên kết, phân phối xác suất trong họ phân phối mũ (exponential family), phương pháp ước lượng (thường là tối đa hợp lý), kiểm định mô hình, lựa chọn mô hình, và chẩn đoán mô hình. R được dùng làm ngôn ngữ chính để minh họa các bước phân tích thực tế.

Dưới đây là phân tích chuyên sâu về nội dung của từng chương trong cuốn sách “Generalized Linear Models with Examples in R” của Peter K. Dunn và Gordon K. Smyth, kết hợp với các công thức toán học và lý thuyết liên quan. Mỗi chương sẽ được trình bày với các khái niệm chính, công thức, và ví dụ minh họa cụ thể.


Chương 1: Mô hình Thống kê (Statistical Models)

Giới thiệu tổng quan: - Mô hình thống kê là một công cụ mạnh mẽ để mô tả và phân tích dữ liệu. Chương này nhấn mạnh rằng mô hình thống kê không chỉ là các công thức toán học mà còn là cách để hiểu và giải thích dữ liệu thực tế. - Mô hình thống kê có thể được sử dụng cho nhiều mục đích khác nhau, bao gồm dự đoán và suy luận nhân quả.

Khám phá dữ liệu bằng đồ thị (Data visualization): - Vẽ đồ thị là bước đầu tiên quan trọng để hiểu mối quan hệ giữa các biến. Sử dụng dữ liệu dung tích phổi (FEV) để minh họa mối quan hệ giữa FEV và các biến như tuổi, chiều cao, giới tính, và tình trạng hút thuốc. - Các đồ thị như biểu đồ phân tán cho thấy mối quan hệ giữa FEV và tuổi, chiều cao, và phân tách theo tình trạng hút thuốc, giúp phát hiện các mối quan hệ phi tuyến.

Mã hóa biến định tính: - Các biến định tính cần được mã hóa thành biến số để có thể sử dụng trong mô hình hồi quy. Giải thích chi tiết về treatment coding cho biến Smoke (0: non-smoker, 1: smoker) và Gender. - Ma trận thiết kế X được xây dựng thông qua hàm model.matrix(), cho phép mô hình hóa các biến định tính một cách hiệu quả.

Phân tích mô hình: - Đề xuất 3 dạng hàm hệ thống cho FEV: 1. Tuyến tính đơn giản: \(\mu = \beta_0 + \beta_1 \text{Age} + \beta_2 \text{Height}\) 2. Đa thức bậc 2: \(\mu = \beta_0 + \beta_1 \text{Age} + \beta_2 \text{Age}^2\) 3. Log-linear: \(\log(\mu) = \beta_0 + \beta_1 \text{Height}\) - Sử dụng Q-Q plot và Shapiro-Wilk test để kiểm định giả thiết chuẩn của phần dư.

Lý thuyết căn bản: - Phát triển công thức ma trận cho ước lượng OLS: \[ \hat{\beta} = (X^T W X)^{-1} X^T W y \] với W là ma trận trọng số đường chéo. Chứng minh tính chất BLUE (Best Linear Unbiased Estimator) của \(\hat{\beta}\) dùng Gauss-Markov theorem.


Chương 2: Mô hình Hồi quy Tuyến tính (Linear Regression Models)

Ước lượng tham số: - Phương pháp bình phương tối thiểu trọng số (WLS) cho dữ liệu gestation (n=21): \[ \min_{\beta} \sum_{i=1}^n w_i(y_i - x_i^T \beta)^2 \] với \(w_i\) là số trường hợp quan sát tại mỗi tuổi thai.

Kiểm định giả thuyết: - Xây dựng kiểm định t cho \(H_0: \beta_j = 0\): \[ t = \frac{\hat{\beta}_j}{SE(\hat{\beta}_j)} \sim t_{n-p'} \] - Phân tích ANOVA với decomposition: \[ SST = SS_{reg} + SSE \]\[ R^2_{adj} = 1 - \frac{SSE/(n-p')}{SST/(n-1)} \]

Case study lungcap: - So sánh 4 mô hình lồng nhau bằng F-test: \[ F = \frac{(SSE_{reduced} - SSE_{full})/(df_{red} - df_{full})}{MSE_{full}} \] - Phát hiện hiệu ứng confounder: hệ số Smoke thay đổi từ 0.15 (p=0.12) sang -0.046 (p=0.028) sau khi kiểm soát Height.


Chương 3: Chẩn đoán Mô hình (Model Diagnostics)

Phân tích phần dư: - Xác định outliers bằng studentized residuals: \[ r_i^* = \frac{r_i}{\hat{\sigma}\sqrt{1-h_{ii}}} \] với \(h_{ii}\) là leverage từ ma trận hat \(H = X(X^TX)^{-1}X^T\).

Biến đổi Box-Cox: - Tối ưu tham số λ bằng profile likelihood: \[ L_{max}(\lambda) = -\frac{n}{2}\log(SSE(\lambda)) \] - Ứng dụng cho dữ liệu FEV tìm được λ ≈ 0.3, gợi ý biến đổi căn bậc 3.


Chương 4: Ước lượng Hợp lý Cực đại (Maximum Likelihood Estimation)

Lý thuyết căn bản: - Hàm score function: \[ U(\theta) = \frac{\partial \ell(\theta)}{\partial \theta} \] - Ma trận thông tin Fisher: \[ I(\theta) = -E\left[\frac{\partial^2 \ell}{\partial \theta \partial \theta^T}\right] \]

Thuật toán Fisher Scoring: - Cập nhật tham số: \[ \theta^{(t+1)} = \theta^{(t)} + I^{-1}(\theta^{(t)})U(\theta^{(t)}) \] - Áp dụng cho Poisson regression với link log.


Chương 5: Cấu trúc GLM (GLM Structure)

Thành phần ngẫu nhiên: - Phân phối thuộc Exponential Dispersion Family: \[ f(y;\theta,\phi) = \exp\left\{\frac{y\theta - b(\theta)}{a(\phi)} + c(y,\phi)\right\} \] - Ví dụ: Poisson(μ) có θ=logμ, ϕ=1, b(θ)=e^θ.

Link functions: - Canonical link: \(g(\mu) = \theta\) - Identity link cho Normal, Logit cho Binomial, Log cho Poisson.


Chương 6: Ước lượng trong GLM (GLM Estimation)

Thuật toán IWLS (Iteratively Reweighted Least Squares): - Cơ sở lý thuyết: - Tối ưu hóa hàm log-likelihood bằng phương pháp Newton-Raphson: \[ \beta^{(t+1)} = \beta^{(t)} + \mathcal{I}^{-1}(\beta^{(t)})U(\beta^{(t)}) \] với \(\mathcal{I}\) là ma trận thông tin Fisher, \(U\) là hàm score. - Trọng số cập nhật: \(w_i = [V(\mu_i)g'(\mu_i)^2]^{-1}\) với \(V\) là hàm phương sai.

  • Triển khai trong R:

    glm(FEV ~ Age + Height, family = Gamma(link="log"), data=lungcap)
    • Giải thích output: Deviance residuals, null/deviance, AIC.

Case study: Ước lượng mô hình Gamma cho chi phí y tế (dữ liệu medical_cost) với link log, so sánh hiệu năng qua AIC/BIC.


Chương 7: Suy luận trong GLM (GLM Inference)

Kiểm định Wald/Likelihood Ratio/Score: - Wald test: \[ W = \frac{\hat{\beta}_j}{SE(\hat{\beta}_j)} \sim N(0,1) \] - Ứng dụng kiểm tra significance của smoking status trong mô hình logistic.

  • Likelihood Ratio Test: \[ D = 2(\ell_{\text{full}} - \ell_{\text{reduced}}) \sim \chi^2_{df} \]
    • Ví dụ: So sánh mô hình đầy đủ và mô hình không có interaction term.

Phân tích Deviance: - Bảng ANOVA cho GLM: | Nguồn biến động | Deviance | df | p-value | |—————-|———-|—-|———| | Model | 58.79 | 4 | <0.001 | | Residual | 13.73 | 649| |


Chương 8: Chẩn đoán GLM (GLM Diagnostics)

Phần dư chuẩn hóa: - Pearson residuals: \[ r_i^P = \frac{y_i - \hat{\mu}_i}{\sqrt{V(\hat{\mu}_i)}} \] - Deviance residuals: \[ r_i^D = \text{sign}(y_i - \hat{\mu}_i)\sqrt{2\left[\ell(y_i;y_i) - \ell(\hat{\mu}_i;y_i)\right]} \] - Q-Q plot kiểm định phân phối phần dư, phát hiện overdispersion.

Ví dụ: Phân tích mô hình Poisson cho số lần nhập viện (dữ liệu hospital), phát hiện overdispersion qua Pearson statistic \(\chi^2/\text{df} = 2.5\).


Chương 9: Mô hình Binomial (Binomial GLMs)

Logistic Regression: - Link logit: \[ \log\left(\frac{\pi}{1-\pi}\right) = X\beta \] - Giải thích odds ratio: \(\exp(\beta_1) = 2.5\) → X tăng 1 đơn vị, odds tăng 2.5 lần.

Probit/Complementary Log-Log: - Ứng dụng trong dose-response analysis (dữ liệu toxicity), so sánh ED50 giữa các link functions.

Overdispersion: - Kiểm tra bằng: \[ \frac{\sum (r_i^P)^2}{n-p'} > 1 \] - Xử lý bằng quasi-binomial hoặc mixed models.


Chương 10: Mô hình Poisson & Negative Binomial

Poisson Regression: - Mô hình offset: \[ \log(\mu_i) = \log(t_i) + X\beta \] - Ứng dụng khi phân tích tỷ lệ (ví dụ: số ca bệnh theo dân số).

Negative Binomial: - Khắc phục overdispersion với tham số \(\theta\): \[ V(\mu) = \mu + \frac{\mu^2}{\theta} \] - Triển khai trong R: r glm.nb(count ~ treatment + offset(log(exposure)), data=epidemic)


Chương 11: Gamma & Inverse Gaussian

Gamma Regression: - Link log: \[ \log(\mu_i) = X\beta \] - Phù hợp cho dữ liệu chi phí (right-skewed). - Ước lượng \(\phi\) bằng phương pháp maximum likelihood.

Inverse Gaussian: - Ứng dụng cho dữ liệu thời gian sống (survival data) với \(V(\mu) = \mu^3\).


Chương 12: Mô hình Tweedie

Cấu trúc phân phối: - Kết hợp point mass tại 0 và phân phối liên tục dương: \[ f(y) = p_0 \delta_0(y) + (1-p_0)f_+(y) \] - Power variance function: \[ V(\mu) = \phi \mu^\xi \] - Ước lượng \(\xi\) bằng profile likelihood.

Case study: Bảo hiểm nông nghiệp với dữ liệu có excess zeros (tỉ lệ thiệt hại ≥ 0).


Chương 13: Các Vấn Đề Bổ Sung

Xử lý Dữ liệu Phức tạp: - Missing Data Mechanisms: - Phân loại theo Rubin (1976): - MCAR (Missing Completely At Random) - MAR (Missing At Random) - MNAR (Missing Not At Random)

  • Công cụ trong R:

    library(mice)
    imp_data <- mice(lungcap, m=5, method="pmm") # Predictive Mean Matching
    fit <- with(imp_data, lm(log(FEV) ~ Age + Height))
    pool(fit) # Kết hợp kết quả

Mô hình Đa cấp (Multilevel Models): - Phương trình: \[ \logit(\pi_{ij}) = \beta_0 + \beta_1X_{ij} + u_j \quad (u_j \sim N(0,\sigma_u^2)) \]

Kiểm định Mô hình: - Vuong Test cho so sánh non-nested models: \[ V = \frac{\sqrt{n}\bar{D}}{s_D} \sim N(0,1) \]


Chương 14: Sử Dụng R cho Phân Tích Dữ Liệu

Hệ sinh thái GLM trong R: - Các package chính:

Package Chức năng Ví dụ
lme4 GLMMs glmer(y ~ x + (1|group))
mgcv GAMs gam(y ~ s(x))
brms Bayesian GLMs brm(y ~ x, family=negbinomial))

Pipeline phân tích:

# Kiểm tra overdispersion
dispersion_test <- function(model) {
  r <- residuals(model, type="pearson")
  sum(r^2)/df.residual(model)
}

Chương 15: Giải Quyết Các Vấn Đề (Troubleshooting)

Convergence Issues: - Cảnh báo thường gặp: Warning: glm.fit: algorithm did not converge - Giải pháp: - Tăng maxit trong glm.control - Kiểm tra separation

Hiện tượng Quasi-complete Separation: - Phát hiện: - Hệ số \(\beta \rightarrow \infty\) trong logistic regression - Sử dụng Firth’s correction: r library(logistf) logistf(y ~ x1 + x2, data=...)

Xử lý Outliers: - Robust GLMs:

library(robust)
glmRob(y ~ x, family=poisson, data=...)

Chương 16: Case Studies Tổng hợp

Ứng dụng Y tế: - Dữ liệu: ICU mortality (n=2000) - Mô hình:

glm(death ~ age + sepsis + (1|hospital), family=binomial, data=icu)

Ứng dụng Kinh tế: - Mô hình Tweedie cho claim insurance:

library(tweedie)
glm(claim ~ age + type, family=tweedie(var.power=1.5, link.power=0), data=insurance)

Ứng dụng Môi trường: - Zero-inflated Poisson cho số lần xuất hiện loài:

library(pscl)
zeroinfl(count ~ temp + rainfall | 1, data=species)

Điểm nhấn đặc biệt

  1. Reproducible Research: Sử dụng knitr/rmarkdown để tích hợp code, kết quả và báo cáo.
  2. Interactive Visualization: Sử dụng plotly để tạo đồ thị tương tác.
  3. Shiny Apps: Xây dựng dashboard tương tác để khám phá mô hình.

Đọc dữ liệu

d <- read_csv("C:/Users/Hoang Quyen/Downloads/Supermarket Transactions.csv")
## New names:
## Rows: 14059 Columns: 16
## ── Column specification
## ──────────────────────────────────────────────────────── Delimiter: "," chr
## (10): Gender, MaritalStatus, Homeowner, AnnualIncome, City, StateorProv... dbl
## (5): ...1, CustomerID, Children, UnitsSold, Revenue date (1): PurchaseDate
## ℹ Use `spec()` to retrieve the full column specification for this data. ℹ
## Specify the column types or set `show_col_types = FALSE` to quiet this message.
## • `` -> `...1`

Dữ liệu “Supermarket Transactions.csv” với 14.059 quan sát được phân tích nhằm mô tả đặc điểm của một số biến định tính như Giới tính, Tình trạng hôn nhân, Sở hữu nhà, Bang/Tỉnh và Nhóm sản phẩm. Phân tích được thực hiện bằng ngôn ngữ R, sử dụng các hàm như table(), prop.table() để tạo bảng tần số và tỷ lệ phần trăm, và ggplot2 để trực quan hóa dữ liệu.

Thống kê mô tả cho biến định tính

Biến Gender (Giới tính)

table(d$Gender)
## 
##    F    M 
## 7170 6889
table(d$Gender) / sum(table(d$Gender))
## 
##    F    M 
## 0.51 0.49
ggplot(d, aes(Gender)) + 
  geom_bar(fill = 'steelblue') + 
  xlab('Giới tính') + ylab('Số lượng')

ggplot(d, aes(Gender)) + 
  geom_bar(aes(y = after_stat(count)/sum(after_stat(count))), fill = 'steelblue') + 
  xlab('Giới tính') + ylab('Tỷ lệ %')

abc_gender <- d |> group_by(Gender) |> summarise(freq = n()) |> mutate(per = freq/sum(freq))
ggplot(abc_gender, aes(x = '', y = per, fill = Gender)) + 
  geom_bar(stat = 'identity') + 
  coord_polar('y') + 
  theme_void() + 
  labs(fill = 'Giới tính')

Biến Gender phản ánh giới tính của khách hàng. Tần số cho thấy có 7,170 khách hàng nữ và 6,889 khách hàng nam, tương ứng với tỷ lệ lần lượt là 51% và 49%. Biểu đồ cột và biểu đồ bánh được sử dụng để trực quan hóa sự phân bố này, cho thấy phân bố giới tính tương đối đồng đều giữa hai nhóm.

Biến MaritalStatus (Tình trạng hôn nhân)

table(d$MaritalStatus)
## 
##    M    S 
## 6866 7193
table(d$MaritalStatus) / sum(table(d$MaritalStatus))
## 
##      M      S 
## 0.4884 0.5116
ggplot(d, aes(MaritalStatus)) + 
  geom_bar(fill = 'darkorange') + 
  xlab('Tình trạng hôn nhân') + ylab('Số lượng')

ggplot(d, aes(MaritalStatus)) + 
  geom_bar(aes(y = after_stat(count)/sum(after_stat(count))), fill = 'darkorange') + 
  xlab('Tình trạng hôn nhân') + ylab('Tỷ lệ %')

abc_marital <- d |> group_by(MaritalStatus) |> summarise(freq = n()) |> mutate(per = freq/sum(freq))
ggplot(abc_marital, aes(x = '', y = per, fill = MaritalStatus)) + 
  geom_bar(stat = 'identity') + 
  coord_polar('y') + 
  theme_void() + 
  labs(fill = 'Tình trạng hôn nhân')

Biến MaritalStatus cho biết khách hàng đã kết hôn hay còn độc thân. Kết quả cho thấy có 7,193 khách hàng độc thân (51.16%) và 6,866 khách hàng đã kết hôn (48.84%). Các biểu đồ cột và biểu đồ tròn thể hiện rõ sự phân bố gần như đồng đều giữa hai nhóm khách hàng.

Biến Homeowner (Sở hữu nhà)

table(d$Homeowner)
## 
##    N    Y 
## 5615 8444
table(d$Homeowner) / sum(table(d$Homeowner))
## 
##      N      Y 
## 0.3994 0.6006

Sở hữu nhà (Own Home): Phân tích cho thấy khoảng 60.1% khách hàng sở hữu nhà. Biểu đồ cột và bảng tỷ lệ được sử dụng để phản ánh sự khác biệt giữa hai nhóm “Y” (Yes) và “N” (No).

ggplot(d, aes(Homeowner)) + 
  geom_bar(fill = 'forestgreen') + 
  xlab('Sở hữu nhà') + ylab('Số lượng')

ggplot(d, aes(Homeowner)) + 
  geom_bar(aes(y = after_stat(count)/sum(after_stat(count))), fill = 'forestgreen') + 
  xlab('Sở hữu nhà') + ylab('Tỷ lệ %')

abc_homeowner <- d |> group_by(Homeowner) |> summarise(freq = n()) |> mutate(per = freq/sum(freq))
ggplot(abc_homeowner, aes(x = '', y = per, fill = Homeowner)) + 
  geom_bar(stat = 'identity') + 
  coord_polar('y') + 
  theme_void() + 
  labs(fill = 'Sở hữu nhà')

Biến Homeowner phản ánh việc khách hàng có sở hữu nhà hay không. Có 8,444 người sở hữu nhà (60.06%) và 5,615 người không sở hữu (39.94%). Biểu đồ cột và biểu đồ bánh cung cấp cái nhìn trực quan về sự chênh lệch này, cho thấy phần lớn khách hàng trong tập dữ liệu là người sở hữu nhà.

Biến StateorProvince (Bang/Tỉnh)

table(d$StateorProvince)
## 
##        BC        CA        DF  Guerrero   Jalisco        OR  Veracruz        WA 
##       809      2733       815       383        75      2262       464      4567 
##   Yucatan Zacatecas 
##       654      1297
table(d$StateorProvince) / sum(table(d$StateorProvince))
## 
##        BC        CA        DF  Guerrero   Jalisco        OR  Veracruz        WA 
##  0.057543  0.194395  0.057970  0.027242  0.005335  0.160893  0.033004  0.324845 
##   Yucatan Zacatecas 
##  0.046518  0.092254
ggplot(d, aes(fct_infreq(StateorProvince))) + 
  geom_bar(color = 'blue', fill = 'green') + 
  xlab('Bang/Tỉnh') + ylab('Số lượng') + 
  theme(axis.text.x = element_text(angle = 45, hjust = 1))

ggplot(d, aes(fct_infreq(StateorProvince))) + 
  geom_bar(aes(y = after_stat(count)/sum(after_stat(count))), color = 'blue', fill = 'green') + 
  xlab('Bang/Tỉnh') + ylab('Tỷ lệ %') + 
  theme(axis.text.x = element_text(angle = 45, hjust = 1))

abc_state <- d |> group_by(StateorProvince) |> summarise(freq = n()) |> mutate(per = freq/sum(freq))
ggplot(abc_state, aes(x = '', y = per, fill = StateorProvince)) + 
  geom_bar(stat = 'identity') + 
  coord_polar('y') + 
  theme_void() + 
  labs(fill = 'Bang/Tỉnh')

Biến StateorProvince cho biết khu vực địa lý của khách hàng, với 10 bang/tỉnh khác nhau. Washington (WA) có số lượng khách hàng cao nhất (32.48%), tiếp theo là California (CA, 19.44%), trong khi Jalisco có số lượng thấp nhất (0.53%). Biểu đồ cột sắp xếp theo tần suất giảm dần và biểu đồ tròn giúp hình dung sự phân bố theo khu vực.

Biến ProductFamily (Nhóm sản phẩm)

table(d$ProductFamily)
## 
##          Drink           Food Non-Consumable 
##           1250          10153           2656
table(d$ProductFamily) / sum(table(d$ProductFamily))
## 
##          Drink           Food Non-Consumable 
##        0.08891        0.72217        0.18892
ggplot(d, aes(ProductFamily)) + 
  geom_bar(fill = 'purple') + 
  xlab('Nhóm sản phẩm') + ylab('Số lượng')

ggplot(d, aes(ProductFamily)) + 
  geom_bar(aes(y = after_stat(count)/sum(after_stat(count))), fill = 'purple') + 
  xlab('Nhóm sản phẩm') + ylab('Tỷ lệ %')

abc_product <- d |> group_by(ProductFamily) |> summarise(freq = n()) |> mutate(per = freq/sum(freq))
ggplot(abc_product, aes(x = '', y = per, fill = ProductFamily)) + 
  geom_bar(stat = 'identity') + 
  coord_polar('y') + 
  theme_void() + 
  labs(fill = 'Nhóm sản phẩm')

Biến ProductFamily phân loại sản phẩm theo ba nhóm chính: Food (72.22%), Non-Consumable (18.89%) và Drink (8.89%). Kết quả cho thấy nhóm sản phẩm thực phẩm chiếm ưu thế rõ rệt. Các biểu đồ trực quan hỗ trợ làm rõ sự phân bố không đồng đều này giữa các nhóm sản phẩm.

Thống kê mô tả cho biến định lượng

Biến Children (Số con)

summary(d$Children)
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##    0.00    1.00    3.00    2.53    4.00    5.00
ggplot(d, aes(y = Children)) + 
  geom_boxplot(fill = 'lightblue') + 
  ylab('Số con')

ggplot(d, aes(Children)) + 
  geom_histogram(binwidth = 1, fill = 'lightblue', color = 'black') + 
  xlab('Số con') + ylab('Số lượng')

Biến Children cho biết số lượng con của khách hàng. Thống kê mô tả cho thấy số con trung bình là 2.53, trung vị là 3, dao động từ 0 đến 5. Biểu đồ hộp và biểu đồ histogram giúp minh họa rõ hơn về sự phân bố, độ lệch và các giá trị ngoại lai.

Biến UnitsSold (Số đơn vị bán ra)

summary(d$UnitsSold)
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##    1.00    3.00    4.00    4.08    5.00    8.00
ggplot(d, aes(y = UnitsSold)) + 
  geom_boxplot(fill = 'lightgreen') + 
  ylab('Số đơn vị bán ra')

ggplot(d, aes(UnitsSold)) + 
  geom_histogram(binwidth = 1, fill = 'lightgreen', color = 'black') + 
  xlab('Số đơn vị bán ra') + ylab('Số lượng')

Biến UnitsSold phản ánh số lượng sản phẩm được bán trong mỗi giao dịch, với trung bình là 4.08 đơn vị. Giá trị nhỏ nhất là 1, lớn nhất là 8. Biểu đồ hộp và histogram được sử dụng để thể hiện phân bố và sự tập trung của biến này xung quanh trung vị.

Biến Revenue (Doanh thu)

summary(d$Revenue)
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##    0.53    6.84   11.25   13.00   17.37   56.70
ggplot(d, aes(y = Revenue)) + 
  geom_boxplot(fill = 'lightcoral') + 
  ylab('Doanh thu')

ggplot(d, aes(Revenue)) + 
  geom_histogram(binwidth = 5, fill = 'lightcoral', color = 'black') + 
  xlab('Doanh thu') + ylab('Số lượng')

Biến Revenue thể hiện tổng doanh thu từ mỗi giao dịch. Doanh thu trung bình là 13.00, với trung vị 11.25 và dao động từ 0.53 đến 56.70. Biểu đồ hộp và histogram cho thấy phân bố hơi lệch phải với một số giá trị doanh thu cao vượt trội.

Thống kê mô tả cho hai biến

Gender và ProductFamily

abc_gender_product <- table(d$Gender, d$ProductFamily)
abc_gender_product
##    
##     Drink Food Non-Consumable
##   F   669 5149           1352
##   M   581 5004           1304
abc_gender_product_prop <- prop.table(abc_gender_product)
addmargins(abc_gender_product_prop)
##      
##         Drink    Food Non-Consumable     Sum
##   F   0.04759 0.36624        0.09617 0.50999
##   M   0.04133 0.35593        0.09275 0.49001
##   Sum 0.08891 0.72217        0.18892 1.00000
ggplot(d, aes(Gender, fill = ProductFamily)) + 
  geom_bar(position = 'dodge') + 
  xlab('Giới tính') + ylab('Số lượng') + 
  labs(fill = 'Nhóm sản phẩm')

Mối quan hệ giữa Gender và ProductFamily cho thấy phụ nữ mua sản phẩm Food nhiều hơn nam giới, với các con số lần lượt là 5,149 và 5,004. Tỷ lệ tương ứng cho thấy xu hướng lựa chọn sản phẩm giữa hai giới tương đối tương đồng, tuy nhiên có sự khác biệt nhỏ trong nhóm Drink và Non-Consumable. Biểu đồ cột so sánh trực tiếp giúp nhận diện xu hướng này một cách trực quan.

MaritalStatus và Homeowner

abc_marital_homeowner <- table(d$MaritalStatus, d$Homeowner)
abc_marital_homeowner
##    
##        N    Y
##   M 1719 5147
##   S 3896 3297
abc_marital_homeowner_prop <- prop.table(abc_marital_homeowner)
addmargins(abc_marital_homeowner)
##      
##           N     Y   Sum
##   M    1719  5147  6866
##   S    3896  3297  7193
##   Sum  5615  8444 14059
riskratio(abc_marital_homeowner, rev = 'c')
## $data
##        
##            Y    N Total
##   M     5147 1719  6866
##   S     3297 3896  7193
##   Total 8444 5615 14059
## 
## $measure
##    risk ratio with 95% C.I.
##     estimate lower upper
##   M    1.000    NA    NA
##   S    2.163 2.066 2.266
## 
## $p.value
##    two-sided
##     midp.exact fisher.exact chi.square
##   M         NA           NA         NA
##   S          0   1.822e-277 3.663e-272
## 
## $correction
## [1] FALSE
## 
## attr(,"method")
## [1] "Unconditional MLE & normal approximation (Wald) CI"
oddsratio(abc_marital_homeowner, rev = 'c')
## $data
##        
##            Y    N Total
##   M     5147 1719  6866
##   S     3297 3896  7193
##   Total 8444 5615 14059
## 
## $measure
##    odds ratio with 95% C.I.
##     estimate lower upper
##   M    1.000    NA    NA
##   S    3.538 3.294 3.801
## 
## $p.value
##    two-sided
##     midp.exact fisher.exact chi.square
##   M         NA           NA         NA
##   S          0   1.822e-277 3.663e-272
## 
## $correction
## [1] FALSE
## 
## attr(,"method")
## [1] "median-unbiased estimate & mid-p exact CI"
ggplot(d, aes(MaritalStatus, fill = Homeowner)) + 
  geom_bar(position = 'dodge') + 
  xlab('Tình trạng hôn nhân') + ylab('Số lượng') + 
  labs(fill = 'Sở hữu nhà')

Sự kết hợp giữa hai biến MaritalStatus và Homeowner cho thấy phần lớn khách hàng đã kết hôn sở hữu nhà (75%), trong khi tỷ lệ này ở nhóm độc thân chỉ khoảng 46%. Tỷ số chênh (odds ratio) là 3.538, và rủi ro tương đối (risk ratio) là 2.163, cho thấy sự khác biệt đáng kể giữa hai nhóm. Biểu đồ cột minh họa rõ sự khác biệt trong quyền sở hữu nhà theo tình trạng hôn nhân.

Thống kê mô tả cho ba biến

Gender, Homeowner, Country

Phân tích ba biến kết hợp cho thấy sự phân bố quyền sở hữu nhà theo giới tính và quốc gia. Ví dụ, tại Canada, tỷ lệ nữ sở hữu nhà cao hơn nam; trong khi tại Mexico, sự khác biệt giữa hai giới ít rõ rệt hơn. Dữ liệu được trình bày trong bảng tần số ba chiều, giúp nắm bắt tổng quan sự khác biệt theo từng quốc gia.

abc_gender_homeowner_country <- ftable(d$Gender, d$Homeowner, d$Country)
abc_gender_homeowner_country
##      Canada Mexico  USA
##                        
## F N     136    866 1824
##   Y     237   1190 2917
## M N     184    607 1998
##   Y     252   1025 2823
abc_gender_homeowner_country_prop <- prop.table(abc_gender_homeowner_country)
round(addmargins(abc_gender_homeowner_country_prop), 4)
##                             Sum
##     0.0097 0.0616 0.1297 0.2010
##     0.0169 0.0846 0.2075 0.3090
##     0.0131 0.0432 0.1421 0.1984
##     0.0179 0.0729 0.2008 0.2916
## Sum 0.0575 0.2623 0.6801 1.0000

Độ nhạy và độ đặc hiệu

Dựa trên mối quan hệ giữa MaritalStatus và Homeowner, độ nhạy (khả năng dự đoán đúng người sở hữu nhà khi đã kết hôn) là 74.96%, và độ đặc hiệu (khả năng dự đoán đúng người không sở hữu nhà khi độc thân) là 54.16%. Hai chỉ số này giúp đánh giá hiệu quả phân loại dựa vào tình trạng hôn nhân.

abc_marital_homeowner
##    
##        N    Y
##   M 1719 5147
##   S 3896 3297
sensitivity <- abc_marital_homeowner[1, 2] / (abc_marital_homeowner[1, 2] + abc_marital_homeowner[1, 1])
sensitivity
## [1] 0.7496
specificity <- abc_marital_homeowner[2, 1] / (abc_marital_homeowner[2, 1] + abc_marital_homeowner[2, 2])
specificity
## [1] 0.5416
LS0tDQp0aXRsZTogIk5oaeG7h20gduG7pSAxIg0KYXV0aG9yOiAiSG/DoG5nIFF1ecOqbiINCmRhdGU6ICIyMDI1LTA1LTE4Ig0Kb3V0cHV0Og0KICBodG1sX2RvY3VtZW50Og0KICAgIGNvZGVfZm9sZGluZzogaGlkZQ0KICAgIGNvZGVfZG93bmxvYWQ6IHRydWUNCiAgICBudW1iZXJfc2VjdGlvbnM6IGZhbHNlICANCiAgICB0b2M6IHRydWUNCiAgICB0b2NfZGVwdGg6IDUNCiAgICB0b2NfZmxvYXQ6DQogICAgICBjb2xsYXBzZWQ6IHRydWUNCiAgICAgIHNtb290aF9zY3JvbGw6IHRydWUNCiAgd29yZF9kb2N1bWVudDoNCiAgICB0b2M6IHRydWUNCi0tLQ0KDQpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0NCmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvID0gVFJVRSkNCmBgYA0KDQpgYGB7cn0NCmxpYnJhcnkodGlkeXZlcnNlKQ0KbGlicmFyeShlcGl0b29scykNCmxpYnJhcnkoRGVzY1Rvb2xzKQ0KbGlicmFyeShEVCkNCmxpYnJhcnkoZW5lcmd5KQ0KbGlicmFyeShyZWFkcikNCm9wdGlvbnMoZGlnaXRzID0gNCkNCmBgYA0KIyBUw7NtIHThuq90IHPDoWNoIEdlbmVyYWxpemVkIExpbmVhciBNb2RlbHMgd2l0aCBSIEV4YW1wbGVzDQoNCkN14buRbiBzw6FjaCAiR2VuZXJhbGl6ZWQgTGluZWFyIE1vZGVscyB3aXRoIEV4YW1wbGVzIGluIFIiIHThuq1wIHRydW5nIGdp4bubaSB0aGnhu4d1IHbhu4EgbMO9IHRodXnhur90IHbDoCDhu6luZyBk4bulbmcgY+G7p2EgbcO0IGjDrG5oIHR1eeG6v24gdMOtbmggdOG7lW5nIHF1w6F0IChHTE0pIOKAlCBt4buZdCBraHVuZyB04buVbmcgcXXDoXQgbeG7nyBy4buZbmcgY8OhYyBtw7QgaMOsbmggdHV54bq/biB0w61uaCBj4buVIMSRaeG7g24sIHRow61jaCBo4bujcCBjaG8gbmhp4buBdSBsb+G6oWkgYmnhur9uIGvhur90IHF14bqjIGtow7RuZyB0dcOibiB0aGVvIHBow6JuIHBo4buRaSBjaHXhuqluIG5oxrAgbmjhu4sgcGjDom4sIMSR4bq/bSwgdsOgIHBow6JuIHBo4buRaSBraMOhYy4NCg0KU8OhY2ggxJFpIHPDonUgdsOgbyBjw6FjIHRow6BuaCBwaOG6p24gY2jDrW5oIGPhu6dhIEdMTTogaMOgbSBsacOqbiBr4bq/dCwgcGjDom4gcGjhu5FpIHjDoWMgc3XhuqV0IHRyb25nIGjhu40gcGjDom4gcGjhu5FpIG3FqSAoZXhwb25lbnRpYWwgZmFtaWx5KSwgcGjGsMahbmcgcGjDoXAgxrDhu5tjIGzGsOG7o25nICh0aMaw4budbmcgbMOgIHThu5FpIMSRYSBo4bujcCBsw70pLCBraeG7g20gxJHhu4tuaCBtw7QgaMOsbmgsIGzhu7FhIGNo4buNbiBtw7QgaMOsbmgsIHbDoCBjaOG6qW4gxJFvw6FuIG3DtCBow6xuaC4gUiDEkcaw4bujYyBkw7luZyBsw6BtIG5nw7RuIG5n4buvIGNow61uaCDEkeG7gyBtaW5oIGjhu41hIGPDoWMgYsaw4bubYyBwaMOibiB0w61jaCB0aOG7sWMgdOG6vy4NCg0KRMaw4bubaSDEkcOieSBsw6AgcGjDom4gdMOtY2ggY2h1ecOqbiBzw6J1IHbhu4EgbuG7mWkgZHVuZyBj4bunYSB04burbmcgY2jGsMahbmcgdHJvbmcgY3Xhu5FuIHPDoWNoICJHZW5lcmFsaXplZCBMaW5lYXIgTW9kZWxzIHdpdGggRXhhbXBsZXMgaW4gUiIgY+G7p2EgUGV0ZXIgSy4gRHVubiB2w6AgR29yZG9uIEsuIFNteXRoLCBr4bq/dCBo4bujcCB24bubaSBjw6FjIGPDtG5nIHRo4bupYyB0b8OhbiBo4buNYyB2w6AgbMO9IHRodXnhur90IGxpw6puIHF1YW4uIE3hu5dpIGNoxrDGoW5nIHPhur0gxJHGsOG7o2MgdHLDrG5oIGLDoHkgduG7m2kgY8OhYyBraMOhaSBuaeG7h20gY2jDrW5oLCBjw7RuZyB0aOG7qWMsIHbDoCB2w60gZOG7pSBtaW5oIGjhu41hIGPhu6UgdGjhu4MuDQoNCi0tLQ0KDQojIyAqKkNoxrDGoW5nIDE6IE3DtCBow6xuaCBUaOG7kW5nIGvDqiAoU3RhdGlzdGljYWwgTW9kZWxzKSoqDQoNCioqR2nhu5tpIHRoaeG7h3UgdOG7lW5nIHF1YW46KioNCi0gTcO0IGjDrG5oIHRo4buRbmcga8OqIGzDoCBt4buZdCBjw7RuZyBj4bulIG3huqFuaCBt4bq9IMSR4buDIG3DtCB04bqjIHbDoCBwaMOibiB0w61jaCBk4buvIGxp4buHdS4gQ2jGsMahbmcgbsOgeSBuaOG6pW4gbeG6oW5oIHLhurFuZyBtw7QgaMOsbmggdGjhu5FuZyBrw6oga2jDtG5nIGNo4buJIGzDoCBjw6FjIGPDtG5nIHRo4bupYyB0b8OhbiBo4buNYyBtw6AgY8OybiBsw6AgY8OhY2ggxJHhu4MgaGnhu4N1IHbDoCBnaeG6o2kgdGjDrWNoIGThu68gbGnhu4d1IHRo4buxYyB04bq/Lg0KLSBNw7QgaMOsbmggdGjhu5FuZyBrw6ogY8OzIHRo4buDIMSRxrDhu6NjIHPhu60gZOG7pW5nIGNobyBuaGnhu4F1IG3hu6VjIMSRw61jaCBraMOhYyBuaGF1LCBiYW8gZ+G7k20gZOG7sSDEkW/DoW4gdsOgIHN1eSBsdeG6rW4gbmjDom4gcXXhuqMuDQoNCioqS2jDoW0gcGjDoSBk4buvIGxp4buHdSBi4bqxbmcgxJHhu5MgdGjhu4sgKERhdGEgdmlzdWFsaXphdGlvbik6KioNCi0gVuG6vSDEkeG7kyB0aOG7iyBsw6AgYsaw4bubYyDEkeG6p3UgdGnDqm4gcXVhbiB0cuG7jW5nIMSR4buDIGhp4buDdSBt4buRaSBxdWFuIGjhu4cgZ2nhu69hIGPDoWMgYmnhur9uLiBT4butIGThu6VuZyBk4buvIGxp4buHdSBkdW5nIHTDrWNoIHBo4buVaSAoRkVWKSDEkeG7gyBtaW5oIGjhu41hIG3hu5FpIHF1YW4gaOG7hyBnaeG7r2EgRkVWIHbDoCBjw6FjIGJp4bq/biBuaMawIHR14buVaSwgY2hp4buBdSBjYW8sIGdp4bubaSB0w61uaCwgdsOgIHTDrG5oIHRy4bqhbmcgaMO6dCB0aHXhu5FjLg0KLSBDw6FjIMSR4buTIHRo4buLIG5oxrAgYmnhu4N1IMSR4buTIHBow6JuIHTDoW4gY2hvIHRo4bqleSBt4buRaSBxdWFuIGjhu4cgZ2nhu69hIEZFViB2w6AgdHXhu5VpLCBjaGnhu4F1IGNhbywgdsOgIHBow6JuIHTDoWNoIHRoZW8gdMOsbmggdHLhuqFuZyBow7p0IHRodeG7kWMsIGdpw7pwIHBow6F0IGhp4buHbiBjw6FjIG3hu5FpIHF1YW4gaOG7hyBwaGkgdHV54bq/bi4NCg0KKipNw6MgaMOzYSBiaeG6v24gxJHhu4tuaCB0w61uaDoqKg0KLSBDw6FjIGJp4bq/biDEkeG7i25oIHTDrW5oIGPhuqduIMSRxrDhu6NjIG3DoyBow7NhIHRow6BuaCBiaeG6v24gc+G7kSDEkeG7gyBjw7MgdGjhu4Mgc+G7rSBk4bulbmcgdHJvbmcgbcO0IGjDrG5oIGjhu5NpIHF1eS4gR2nhuqNpIHRow61jaCBjaGkgdGnhur90IHbhu4EgdHJlYXRtZW50IGNvZGluZyBjaG8gYmnhur9uIFNtb2tlICgwOiBub24tc21va2VyLCAxOiBzbW9rZXIpIHbDoCBHZW5kZXIuDQotIE1hIHRy4bqtbiB0aGnhur90IGvhur8gWCDEkcaw4bujYyB4w6J5IGThu7FuZyB0aMO0bmcgcXVhIGjDoG0gYG1vZGVsLm1hdHJpeCgpYCwgY2hvIHBow6lwIG3DtCBow6xuaCBow7NhIGPDoWMgYmnhur9uIMSR4buLbmggdMOtbmggbeG7mXQgY8OhY2ggaGnhu4d1IHF14bqjLg0KDQoqKlBow6JuIHTDrWNoIG3DtCBow6xuaDoqKg0KLSDEkOG7gSB4deG6pXQgMyBk4bqhbmcgaMOgbSBo4buHIHRo4buRbmcgY2hvIEZFVjoNCiAgMS4gVHV54bq/biB0w61uaCDEkcahbiBnaeG6o246IFwoIFxtdSA9IFxiZXRhXzAgKyBcYmV0YV8xIFx0ZXh0e0FnZX0gKyBcYmV0YV8yIFx0ZXh0e0hlaWdodH0gXCkNCiAgMi4gxJBhIHRo4bupYyBi4bqtYyAyOiBcKCBcbXUgPSBcYmV0YV8wICsgXGJldGFfMSBcdGV4dHtBZ2V9ICsgXGJldGFfMiBcdGV4dHtBZ2V9XjIgXCkNCiAgMy4gTG9nLWxpbmVhcjogXCggXGxvZyhcbXUpID0gXGJldGFfMCArIFxiZXRhXzEgXHRleHR7SGVpZ2h0fSBcKQ0KLSBT4butIGThu6VuZyBRLVEgcGxvdCB2w6AgU2hhcGlyby1XaWxrIHRlc3QgxJHhu4Mga2nhu4NtIMSR4buLbmggZ2nhuqMgdGhp4bq/dCBjaHXhuqluIGPhu6dhIHBo4bqnbiBkxrAuDQoNCioqTMO9IHRodXnhur90IGPEg24gYuG6o246KioNCi0gUGjDoXQgdHJp4buDbiBjw7RuZyB0aOG7qWMgbWEgdHLhuq1uIGNobyDGsOG7m2MgbMaw4bujbmcgT0xTOg0KICBcWw0KICBcaGF0e1xiZXRhfSA9IChYXlQgVyBYKV57LTF9IFheVCBXIHkNCiAgXF0NCiAgduG7m2kgVyBsw6AgbWEgdHLhuq1uIHRy4buNbmcgc+G7kSDEkcaw4budbmcgY2jDqW8uIENo4bupbmcgbWluaCB0w61uaCBjaOG6pXQgQkxVRSAoQmVzdCBMaW5lYXIgVW5iaWFzZWQgRXN0aW1hdG9yKSBj4bunYSBcKCBcaGF0e1xiZXRhfSBcKSBkw7luZyBHYXVzcy1NYXJrb3YgdGhlb3JlbS4NCg0KLS0tDQoNCiMjICoqQ2jGsMahbmcgMjogTcO0IGjDrG5oIEjhu5NpIHF1eSBUdXnhur9uIHTDrW5oIChMaW5lYXIgUmVncmVzc2lvbiBNb2RlbHMpKioNCg0KKirGr+G7m2MgbMaw4bujbmcgdGhhbSBz4buROioqDQotIFBoxrDGoW5nIHBow6FwIGLDrG5oIHBoxrDGoW5nIHThu5FpIHRoaeG7g3UgdHLhu41uZyBz4buRIChXTFMpIGNobyBk4buvIGxp4buHdSBnZXN0YXRpb24gKG49MjEpOg0KICBcWw0KICBcbWluX3tcYmV0YX0gXHN1bV97aT0xfV5uIHdfaSh5X2kgLSB4X2leVCBcYmV0YSleMg0KICBcXQ0KICB24bubaSBcKCB3X2kgXCkgbMOgIHPhu5EgdHLGsOG7nW5nIGjhu6NwIHF1YW4gc8OhdCB04bqhaSBt4buXaSB0deG7lWkgdGhhaS4NCg0KKipLaeG7g20gxJHhu4tuaCBnaeG6oyB0aHV54bq/dDoqKg0KLSBYw6J5IGThu7FuZyBraeG7g20gxJHhu4tuaCB0IGNobyBcKCBIXzA6IFxiZXRhX2ogPSAwIFwpOg0KICBcWw0KICB0ID0gXGZyYWN7XGhhdHtcYmV0YX1fan17U0UoXGhhdHtcYmV0YX1fail9IFxzaW0gdF97bi1wJ30NCiAgXF0NCi0gUGjDom4gdMOtY2ggQU5PVkEgduG7m2kgZGVjb21wb3NpdGlvbjoNCiAgXFsNCiAgU1NUID0gU1Nfe3JlZ30gKyBTU0UNCiAgXF0NCiAgdsOgIA0KICBcWw0KICBSXjJfe2Fkan0gPSAxIC0gXGZyYWN7U1NFLyhuLXAnKX17U1NULyhuLTEpfQ0KICBcXQ0KDQoqKkNhc2Ugc3R1ZHkgbHVuZ2NhcDoqKg0KLSBTbyBzw6FuaCA0IG3DtCBow6xuaCBs4buTbmcgbmhhdSBi4bqxbmcgRi10ZXN0Og0KICBcWw0KICBGID0gXGZyYWN7KFNTRV97cmVkdWNlZH0gLSBTU0Vfe2Z1bGx9KS8oZGZfe3JlZH0gLSBkZl97ZnVsbH0pfXtNU0Vfe2Z1bGx9fQ0KICBcXQ0KLSBQaMOhdCBoaeG7h24gaGnhu4d1IOG7qW5nIGNvbmZvdW5kZXI6IGjhu4cgc+G7kSBTbW9rZSB0aGF5IMSR4buVaSB04burIDAuMTUgKHA9MC4xMikgc2FuZyAtMC4wNDYgKHA9MC4wMjgpIHNhdSBraGkga2nhu4NtIHNvw6F0IEhlaWdodC4NCg0KLS0tDQoNCiMjICoqQ2jGsMahbmcgMzogQ2jhuqluIMSRb8OhbiBNw7QgaMOsbmggKE1vZGVsIERpYWdub3N0aWNzKSoqDQoNCioqUGjDom4gdMOtY2ggcGjhuqduIGTGsDoqKg0KLSBYw6FjIMSR4buLbmggb3V0bGllcnMgYuG6sW5nIHN0dWRlbnRpemVkIHJlc2lkdWFsczoNCiAgXFsNCiAgcl9pXiogPSBcZnJhY3tyX2l9e1xoYXR7XHNpZ21hfVxzcXJ0ezEtaF97aWl9fX0NCiAgXF0NCiAgduG7m2kgXCggaF97aWl9IFwpIGzDoCBsZXZlcmFnZSB04burIG1hIHRy4bqtbiBoYXQgXCggSCA9IFgoWF5UWCleey0xfVheVCBcKS4NCg0KKipCaeG6v24gxJHhu5VpIEJveC1Db3g6KioNCi0gVOG7kWkgxrB1IHRoYW0gc+G7kSDOuyBi4bqxbmcgcHJvZmlsZSBsaWtlbGlob29kOg0KICBcWw0KICBMX3ttYXh9KFxsYW1iZGEpID0gLVxmcmFje259ezJ9XGxvZyhTU0UoXGxhbWJkYSkpDQogIFxdDQotIOG7qG5nIGThu6VuZyBjaG8gZOG7ryBsaeG7h3UgRkVWIHTDrG0gxJHGsOG7o2Mgzrsg4omIIDAuMywgZ+G7o2kgw70gYmnhur9uIMSR4buVaSBjxINuIGLhuq1jIDMuDQoNCi0tLQ0KDQojIyAqKkNoxrDGoW5nIDQ6IMav4bubYyBsxrDhu6NuZyBI4bujcCBsw70gQ+G7sWMgxJHhuqFpIChNYXhpbXVtIExpa2VsaWhvb2QgRXN0aW1hdGlvbikqKg0KDQoqKkzDvSB0aHV54bq/dCBjxINuIGLhuqNuOioqDQotIEjDoG0gc2NvcmUgZnVuY3Rpb246DQogIFxbDQogIFUoXHRoZXRhKSA9IFxmcmFje1xwYXJ0aWFsIFxlbGwoXHRoZXRhKX17XHBhcnRpYWwgXHRoZXRhfQ0KICBcXQ0KLSBNYSB0cuG6rW4gdGjDtG5nIHRpbiBGaXNoZXI6DQogIFxbDQogIEkoXHRoZXRhKSA9IC1FXGxlZnRbXGZyYWN7XHBhcnRpYWxeMiBcZWxsfXtccGFydGlhbCBcdGhldGEgXHBhcnRpYWwgXHRoZXRhXlR9XHJpZ2h0XQ0KICBcXQ0KDQoqKlRodeG6rXQgdG/DoW4gRmlzaGVyIFNjb3Jpbmc6KioNCi0gQ+G6rXAgbmjhuq10IHRoYW0gc+G7kToNCiAgXFsNCiAgXHRoZXRhXnsodCsxKX0gPSBcdGhldGFeeyh0KX0gKyBJXnstMX0oXHRoZXRhXnsodCl9KVUoXHRoZXRhXnsodCl9KQ0KICBcXQ0KLSDDgXAgZOG7pW5nIGNobyBQb2lzc29uIHJlZ3Jlc3Npb24gduG7m2kgbGluayBsb2cuDQoNCi0tLQ0KDQojIyAqKkNoxrDGoW5nIDU6IEPhuqV1IHRyw7pjIEdMTSAoR0xNIFN0cnVjdHVyZSkqKg0KDQoqKlRow6BuaCBwaOG6p24gbmfhuqt1IG5oacOqbjoqKg0KLSBQaMOibiBwaOG7kWkgdGh14buZYyBFeHBvbmVudGlhbCBEaXNwZXJzaW9uIEZhbWlseToNCiAgXFsNCiAgZih5O1x0aGV0YSxccGhpKSA9IFxleHBcbGVmdFx7XGZyYWN7eVx0aGV0YSAtIGIoXHRoZXRhKX17YShccGhpKX0gKyBjKHksXHBoaSlccmlnaHRcfQ0KICBcXQ0KLSBWw60gZOG7pTogUG9pc3NvbijOvCkgY8OzIM64PWxvZ868LCDPlT0xLCBiKM64KT1lXs64Lg0KDQoqKkxpbmsgZnVuY3Rpb25zOioqDQotIENhbm9uaWNhbCBsaW5rOiBcKCBnKFxtdSkgPSBcdGhldGEgXCkNCi0gSWRlbnRpdHkgbGluayBjaG8gTm9ybWFsLCBMb2dpdCBjaG8gQmlub21pYWwsIExvZyBjaG8gUG9pc3Nvbi4NCg0KLS0tDQoNCiMjICoqQ2jGsMahbmcgNjogxq/hu5tjIGzGsOG7o25nIHRyb25nIEdMTSAoR0xNIEVzdGltYXRpb24pKioNCg0KKipUaHXhuq10IHRvw6FuIElXTFMgKEl0ZXJhdGl2ZWx5IFJld2VpZ2h0ZWQgTGVhc3QgU3F1YXJlcyk6KioNCi0gKipDxqEgc+G7nyBsw70gdGh1eeG6v3Q6KioNCiAgLSBU4buRaSDGsHUgaMOzYSBow6BtIGxvZy1saWtlbGlob29kIGLhurFuZyBwaMawxqFuZyBwaMOhcCBOZXd0b24tUmFwaHNvbjoNCiAgICBcWw0KICAgIFxiZXRhXnsodCsxKX0gPSBcYmV0YV57KHQpfSArIFxtYXRoY2Fse0l9XnstMX0oXGJldGFeeyh0KX0pVShcYmV0YV57KHQpfSkNCiAgICBcXQ0KICAgIHbhu5tpIFwoXG1hdGhjYWx7SX1cKSBsw6AgbWEgdHLhuq1uIHRow7RuZyB0aW4gRmlzaGVyLCBcKFVcKSBsw6AgaMOgbSBzY29yZS4NCiAgLSBUcuG7jW5nIHPhu5EgY+G6rXAgbmjhuq10OiBcKHdfaSA9IFtWKFxtdV9pKWcnKFxtdV9pKV4yXV57LTF9XCkgduG7m2kgXChWXCkgbMOgIGjDoG0gcGjGsMahbmcgc2FpLg0KDQotICoqVHJp4buDbiBraGFpIHRyb25nIFI6KioNCiAgYGBgcg0KICBnbG0oRkVWIH4gQWdlICsgSGVpZ2h0LCBmYW1pbHkgPSBHYW1tYShsaW5rPSJsb2ciKSwgZGF0YT1sdW5nY2FwKQ0KICBgYGANCiAgLSBHaeG6o2kgdGjDrWNoIG91dHB1dDogRGV2aWFuY2UgcmVzaWR1YWxzLCBudWxsL2RldmlhbmNlLCBBSUMuDQoNCioqQ2FzZSBzdHVkeToqKiDGr+G7m2MgbMaw4bujbmcgbcO0IGjDrG5oIEdhbW1hIGNobyBjaGkgcGjDrSB5IHThur8gKGThu68gbGnhu4d1IG1lZGljYWxfY29zdCkgduG7m2kgbGluayBsb2csIHNvIHPDoW5oIGhp4buHdSBuxINuZyBxdWEgQUlDL0JJQy4NCg0KLS0tDQoNCiMjICoqQ2jGsMahbmcgNzogU3V5IGx14bqtbiB0cm9uZyBHTE0gKEdMTSBJbmZlcmVuY2UpKioNCg0KKipLaeG7g20gxJHhu4tuaCBXYWxkL0xpa2VsaWhvb2QgUmF0aW8vU2NvcmU6KioNCi0gKipXYWxkIHRlc3Q6KioNCiAgXFsNCiAgVyA9IFxmcmFje1xoYXR7XGJldGF9X2p9e1NFKFxoYXR7XGJldGF9X2opfSBcc2ltIE4oMCwxKQ0KICBcXQ0KICAtIOG7qG5nIGThu6VuZyBraeG7g20gdHJhIHNpZ25pZmljYW5jZSBj4bunYSBzbW9raW5nIHN0YXR1cyB0cm9uZyBtw7QgaMOsbmggbG9naXN0aWMuDQoNCi0gKipMaWtlbGlob29kIFJhdGlvIFRlc3Q6KioNCiAgXFsNCiAgRCA9IDIoXGVsbF97XHRleHR7ZnVsbH19IC0gXGVsbF97XHRleHR7cmVkdWNlZH19KSBcc2ltIFxjaGleMl97ZGZ9DQogIFxdDQogIC0gVsOtIGThu6U6IFNvIHPDoW5oIG3DtCBow6xuaCDEkeG6p3kgxJHhu6cgdsOgIG3DtCBow6xuaCBraMO0bmcgY8OzIGludGVyYWN0aW9uIHRlcm0uDQoNCioqUGjDom4gdMOtY2ggRGV2aWFuY2U6KioNCi0gQuG6o25nIEFOT1ZBIGNobyBHTE06DQogIHwgTmd14buTbiBiaeG6v24gxJHhu5luZyB8IERldmlhbmNlIHwgZGYgfCBwLXZhbHVlIHwNCiAgfC0tLS0tLS0tLS0tLS0tLS18LS0tLS0tLS0tLXwtLS0tfC0tLS0tLS0tLXwNCiAgfCBNb2RlbCAgICAgICAgICB8IDU4Ljc5ICAgIHwgNCAgfCA8MC4wMDEgIHwNCiAgfCBSZXNpZHVhbCAgICAgICB8IDEzLjczICAgIHwgNjQ5fCAgICAgICAgIHwNCg0KLS0tDQoNCiMjICoqQ2jGsMahbmcgODogQ2jhuqluIMSRb8OhbiBHTE0gKEdMTSBEaWFnbm9zdGljcykqKg0KDQoqKlBo4bqnbiBkxrAgY2h14bqpbiBow7NhOioqDQotICoqUGVhcnNvbiByZXNpZHVhbHM6KioNCiAgXFsNCiAgcl9pXlAgPSBcZnJhY3t5X2kgLSBcaGF0e1xtdX1faX17XHNxcnR7VihcaGF0e1xtdX1faSl9fQ0KICBcXQ0KLSAqKkRldmlhbmNlIHJlc2lkdWFsczoqKg0KICBcWw0KICByX2leRCA9IFx0ZXh0e3NpZ259KHlfaSAtIFxoYXR7XG11fV9pKVxzcXJ0ezJcbGVmdFtcZWxsKHlfaTt5X2kpIC0gXGVsbChcaGF0e1xtdX1faTt5X2kpXHJpZ2h0XX0NCiAgXF0NCi0gKipRLVEgcGxvdCoqIGtp4buDbSDEkeG7i25oIHBow6JuIHBo4buRaSBwaOG6p24gZMawLCBwaMOhdCBoaeG7h24gb3ZlcmRpc3BlcnNpb24uDQoNCioqVsOtIGThu6U6KiogUGjDom4gdMOtY2ggbcO0IGjDrG5oIFBvaXNzb24gY2hvIHPhu5EgbOG6p24gbmjhuq1wIHZp4buHbiAoZOG7ryBsaeG7h3UgaG9zcGl0YWwpLCBwaMOhdCBoaeG7h24gb3ZlcmRpc3BlcnNpb24gcXVhIFBlYXJzb24gc3RhdGlzdGljIFwoXGNoaV4yL1x0ZXh0e2RmfSA9IDIuNVwpLg0KDQotLS0NCg0KIyMgKipDaMawxqFuZyA5OiBNw7QgaMOsbmggQmlub21pYWwgKEJpbm9taWFsIEdMTXMpKioNCg0KKipMb2dpc3RpYyBSZWdyZXNzaW9uOioqDQotICoqTGluayBsb2dpdDoqKg0KICBcWw0KICBcbG9nXGxlZnQoXGZyYWN7XHBpfXsxLVxwaX1ccmlnaHQpID0gWFxiZXRhDQogIFxdDQotICoqR2nhuqNpIHRow61jaCBvZGRzIHJhdGlvOioqDQogIFwoXGV4cChcYmV0YV8xKSA9IDIuNVwpIOKGkiBYIHTEg25nIDEgxJHGoW4gduG7iywgb2RkcyB0xINuZyAyLjUgbOG6p24uDQoNCioqUHJvYml0L0NvbXBsZW1lbnRhcnkgTG9nLUxvZzoqKg0KLSDhu6huZyBk4bulbmcgdHJvbmcgZG9zZS1yZXNwb25zZSBhbmFseXNpcyAoZOG7ryBsaeG7h3UgdG94aWNpdHkpLCBzbyBzw6FuaCBFRDUwIGdp4buvYSBjw6FjIGxpbmsgZnVuY3Rpb25zLg0KDQoqKk92ZXJkaXNwZXJzaW9uOioqDQotIEtp4buDbSB0cmEgYuG6sW5nOg0KICBcWw0KICBcZnJhY3tcc3VtIChyX2leUCleMn17bi1wJ30gPiAxDQogIFxdDQogIC0gWOG7rSBsw70gYuG6sW5nIHF1YXNpLWJpbm9taWFsIGhv4bq3YyBtaXhlZCBtb2RlbHMuDQoNCi0tLQ0KDQojIyAqKkNoxrDGoW5nIDEwOiBNw7QgaMOsbmggUG9pc3NvbiAmIE5lZ2F0aXZlIEJpbm9taWFsKioNCg0KKipQb2lzc29uIFJlZ3Jlc3Npb246KioNCi0gKipNw7QgaMOsbmggb2Zmc2V0OioqDQogIFxbDQogIFxsb2coXG11X2kpID0gXGxvZyh0X2kpICsgWFxiZXRhDQogIFxdDQogIC0g4buobmcgZOG7pW5nIGtoaSBwaMOibiB0w61jaCB04bu3IGzhu4cgKHbDrSBk4bulOiBz4buRIGNhIGLhu4duaCB0aGVvIGTDom4gc+G7kSkuDQoNCioqTmVnYXRpdmUgQmlub21pYWw6KioNCi0gS2jhuq9jIHBo4bulYyBvdmVyZGlzcGVyc2lvbiB24bubaSB0aGFtIHPhu5EgXChcdGhldGFcKToNCiAgXFsNCiAgVihcbXUpID0gXG11ICsgXGZyYWN7XG11XjJ9e1x0aGV0YX0NCiAgXF0NCiAgLSBUcmnhu4NuIGtoYWkgdHJvbmcgUjoNCiAgICBgYGByDQogICAgZ2xtLm5iKGNvdW50IH4gdHJlYXRtZW50ICsgb2Zmc2V0KGxvZyhleHBvc3VyZSkpLCBkYXRhPWVwaWRlbWljKQ0KICAgIGBgYA0KDQotLS0NCg0KIyMgKipDaMawxqFuZyAxMTogR2FtbWEgJiBJbnZlcnNlIEdhdXNzaWFuKioNCg0KKipHYW1tYSBSZWdyZXNzaW9uOioqDQotICoqTGluayBsb2c6KioNCiAgXFsNCiAgXGxvZyhcbXVfaSkgPSBYXGJldGENCiAgXF0NCiAgLSBQaMO5IGjhu6NwIGNobyBk4buvIGxp4buHdSBjaGkgcGjDrSAocmlnaHQtc2tld2VkKS4NCiAgLSDGr+G7m2MgbMaw4bujbmcgXChccGhpXCkgYuG6sW5nIHBoxrDGoW5nIHBow6FwIG1heGltdW0gbGlrZWxpaG9vZC4NCg0KKipJbnZlcnNlIEdhdXNzaWFuOioqDQotIOG7qG5nIGThu6VuZyBjaG8gZOG7ryBsaeG7h3UgdGjhu51pIGdpYW4gc+G7kW5nIChzdXJ2aXZhbCBkYXRhKSB24bubaSBcKFYoXG11KSA9IFxtdV4zXCkuDQoNCi0tLQ0KDQojIyAqKkNoxrDGoW5nIDEyOiBNw7QgaMOsbmggVHdlZWRpZSoqDQoNCioqQ+G6pXUgdHLDumMgcGjDom4gcGjhu5FpOioqDQotIEvhur90IGjhu6NwIHBvaW50IG1hc3MgdOG6oWkgMCB2w6AgcGjDom4gcGjhu5FpIGxpw6puIHThu6VjIGTGsMahbmc6DQogIFxbDQogIGYoeSkgPSBwXzAgXGRlbHRhXzAoeSkgKyAoMS1wXzApZl8rKHkpDQogIFxdDQotICoqUG93ZXIgdmFyaWFuY2UgZnVuY3Rpb246KioNCiAgXFsNCiAgVihcbXUpID0gXHBoaSBcbXVeXHhpDQogIFxdDQogIC0gxq/hu5tjIGzGsOG7o25nIFwoXHhpXCkgYuG6sW5nIHByb2ZpbGUgbGlrZWxpaG9vZC4NCg0KKipDYXNlIHN0dWR5OioqIELhuqNvIGhp4buDbSBuw7RuZyBuZ2hp4buHcCB24bubaSBk4buvIGxp4buHdSBjw7MgZXhjZXNzIHplcm9zICh04buJIGzhu4cgdGhp4buHdCBo4bqhaSDiiaUgMCkuDQoNCi0tLQ0KDQojIyAqKkNoxrDGoW5nIDEzOiBDw6FjIFbhuqVuIMSQ4buBIELhu5UgU3VuZyoqDQoNCioqWOG7rSBsw70gROG7ryBsaeG7h3UgUGjhu6ljIHThuqFwOioqDQotICoqTWlzc2luZyBEYXRhIE1lY2hhbmlzbXMqKjoNCiAgLSBQaMOibiBsb+G6oWkgdGhlbyBSdWJpbiAoMTk3Nik6DQogICAgLSBNQ0FSIChNaXNzaW5nIENvbXBsZXRlbHkgQXQgUmFuZG9tKQ0KICAgIC0gTUFSIChNaXNzaW5nIEF0IFJhbmRvbSkNCiAgICAtIE1OQVIgKE1pc3NpbmcgTm90IEF0IFJhbmRvbSkNCg0KLSAqKkPDtG5nIGPhu6UgdHJvbmcgUioqOg0KICBgYGByDQogIGxpYnJhcnkobWljZSkNCiAgaW1wX2RhdGEgPC0gbWljZShsdW5nY2FwLCBtPTUsIG1ldGhvZD0icG1tIikgIyBQcmVkaWN0aXZlIE1lYW4gTWF0Y2hpbmcNCiAgZml0IDwtIHdpdGgoaW1wX2RhdGEsIGxtKGxvZyhGRVYpIH4gQWdlICsgSGVpZ2h0KSkNCiAgcG9vbChmaXQpICMgS+G6v3QgaOG7o3Aga+G6v3QgcXXhuqMNCiAgYGBgDQoNCioqTcO0IGjDrG5oIMSQYSBj4bqlcCAoTXVsdGlsZXZlbCBNb2RlbHMpOioqDQotICoqUGjGsMahbmcgdHLDrG5oKio6DQogIFxbDQogIFxsb2dpdChccGlfe2lqfSkgPSBcYmV0YV8wICsgXGJldGFfMVhfe2lqfSArIHVfaiBccXVhZCAodV9qIFxzaW0gTigwLFxzaWdtYV91XjIpKQ0KICBcXQ0KDQoqKktp4buDbSDEkeG7i25oIE3DtCBow6xuaDoqKg0KLSAqKlZ1b25nIFRlc3QqKiBjaG8gc28gc8Ohbmggbm9uLW5lc3RlZCBtb2RlbHM6DQogIFxbDQogIFYgPSBcZnJhY3tcc3FydHtufVxiYXJ7RH19e3NfRH0gXHNpbSBOKDAsMSkNCiAgXF0NCg0KLS0tDQoNCiMjICoqQ2jGsMahbmcgMTQ6IFPhu60gROG7pW5nIFIgY2hvIFBow6JuIFTDrWNoIEThu68gTGnhu4d1KioNCg0KKipI4buHIHNpbmggdGjDoWkgR0xNIHRyb25nIFI6KioNCi0gKipDw6FjIHBhY2thZ2UgY2jDrW5oKio6DQogIA0KICANCiAgfCBQYWNrYWdlIHwgQ2jhu6ljIG7Eg25nIHwgVsOtIGThu6UgfA0KICB8LS0tLS0tLS0tfC0tLS0tLS0tLS0tfC0tLS0tLS18DQogIHwgYGxtZTRgIHwgR0xNTXMgfCBgZ2xtZXIoeSB+IHggKyAoMXxncm91cCkpYCB8DQogIHwgYG1nY3ZgIHwgR0FNcyB8IGBnYW0oeSB+IHMoeCkpYCB8DQogIHwgYGJybXNgIHwgQmF5ZXNpYW4gR0xNcyB8IGBicm0oeSB+IHgsIGZhbWlseT1uZWdiaW5vbWlhbCkpYCB8DQoNCioqUGlwZWxpbmUgcGjDom4gdMOtY2g6KioNCmBgYHINCiMgS2nhu4NtIHRyYSBvdmVyZGlzcGVyc2lvbg0KZGlzcGVyc2lvbl90ZXN0IDwtIGZ1bmN0aW9uKG1vZGVsKSB7DQogIHIgPC0gcmVzaWR1YWxzKG1vZGVsLCB0eXBlPSJwZWFyc29uIikNCiAgc3VtKHJeMikvZGYucmVzaWR1YWwobW9kZWwpDQp9DQpgYGANCg0KLS0tDQoNCiMjICoqQ2jGsMahbmcgMTU6IEdp4bqjaSBRdXnhur90IEPDoWMgVuG6pW4gxJDhu4EgKFRyb3VibGVzaG9vdGluZykqKg0KDQoqKkNvbnZlcmdlbmNlIElzc3VlczoqKg0KLSAqKkPhuqNuaCBiw6FvIHRoxrDhu51uZyBn4bq3cCoqOg0KICBgYGANCiAgV2FybmluZzogZ2xtLmZpdDogYWxnb3JpdGhtIGRpZCBub3QgY29udmVyZ2UNCiAgYGBgDQogIC0gR2nhuqNpIHBow6FwOg0KICAgIC0gVMSDbmcgYG1heGl0YCB0cm9uZyBgZ2xtLmNvbnRyb2xgDQogICAgLSBLaeG7g20gdHJhIHNlcGFyYXRpb24NCg0KKipIaeG7h24gdMaw4bujbmcgUXVhc2ktY29tcGxldGUgU2VwYXJhdGlvbjoqKg0KLSAqKlBow6F0IGhp4buHbioqOg0KICAtIEjhu4cgc+G7kSBcKFxiZXRhIFxyaWdodGFycm93IFxpbmZ0eVwpIHRyb25nIGxvZ2lzdGljIHJlZ3Jlc3Npb24NCiAgLSBT4butIGThu6VuZyBGaXJ0aCdzIGNvcnJlY3Rpb246DQogIGBgYHINCiAgbGlicmFyeShsb2dpc3RmKQ0KICBsb2dpc3RmKHkgfiB4MSArIHgyLCBkYXRhPS4uLikNCiAgYGBgDQoNCioqWOG7rSBsw70gT3V0bGllcnM6KioNCi0gKipSb2J1c3QgR0xNcyoqOg0KYGBgcg0KbGlicmFyeShyb2J1c3QpDQpnbG1Sb2IoeSB+IHgsIGZhbWlseT1wb2lzc29uLCBkYXRhPS4uLikNCmBgYA0KDQotLS0NCg0KIyMgKipDaMawxqFuZyAxNjogQ2FzZSBTdHVkaWVzIFThu5VuZyBo4bujcCoqDQoNCioq4buobmcgZOG7pW5nIFkgdOG6vzoqKg0KLSAqKkThu68gbGnhu4d1Kio6IElDVSBtb3J0YWxpdHkgKG49MjAwMCkNCi0gKipNw7QgaMOsbmgqKjoNCmBgYHINCmdsbShkZWF0aCB+IGFnZSArIHNlcHNpcyArICgxfGhvc3BpdGFsKSwgZmFtaWx5PWJpbm9taWFsLCBkYXRhPWljdSkNCmBgYA0KDQoqKuG7qG5nIGThu6VuZyBLaW5oIHThur86KioNCi0gKipNw7QgaMOsbmggVHdlZWRpZSoqIGNobyBjbGFpbSBpbnN1cmFuY2U6DQpgYGByDQpsaWJyYXJ5KHR3ZWVkaWUpDQpnbG0oY2xhaW0gfiBhZ2UgKyB0eXBlLCBmYW1pbHk9dHdlZWRpZSh2YXIucG93ZXI9MS41LCBsaW5rLnBvd2VyPTApLCBkYXRhPWluc3VyYW5jZSkNCmBgYA0KDQoqKuG7qG5nIGThu6VuZyBNw7RpIHRyxrDhu51uZzoqKg0KLSAqKlplcm8taW5mbGF0ZWQgUG9pc3NvbioqIGNobyBz4buRIGzhuqduIHh14bqldCBoaeG7h24gbG/DoGk6DQpgYGByDQpsaWJyYXJ5KHBzY2wpDQp6ZXJvaW5mbChjb3VudCB+IHRlbXAgKyByYWluZmFsbCB8IDEsIGRhdGE9c3BlY2llcykNCmBgYA0KDQotLS0NCg0KIyMgKirEkGnhu4NtIG5o4bqlbiDEkeG6t2MgYmnhu4d0KioNCjEuICoqUmVwcm9kdWNpYmxlIFJlc2VhcmNoKio6IFPhu60gZOG7pW5nIGBrbml0cmAvYHJtYXJrZG93bmAgxJHhu4MgdMOtY2ggaOG7o3AgY29kZSwga+G6v3QgcXXhuqMgdsOgIGLDoW8gY8Ohby4NCjIuICoqSW50ZXJhY3RpdmUgVmlzdWFsaXphdGlvbioqOiBT4butIGThu6VuZyBgcGxvdGx5YCDEkeG7gyB04bqhbyDEkeG7kyB0aOG7iyB0xrDGoW5nIHTDoWMuDQozLiAqKlNoaW55IEFwcHMqKjogWMOieSBk4buxbmcgZGFzaGJvYXJkIHTGsMahbmcgdMOhYyDEkeG7gyBraMOhbSBwaMOhIG3DtCBow6xuaC4NCg0KLS0tDQoNCsSQ4buNYyBk4buvIGxp4buHdQ0KYGBge3J9DQpkIDwtIHJlYWRfY3N2KCJDOi9Vc2Vycy9Ib2FuZyBRdXllbi9Eb3dubG9hZHMvU3VwZXJtYXJrZXQgVHJhbnNhY3Rpb25zLmNzdiIpDQpgYGANCg0KROG7ryBsaeG7h3UgIlN1cGVybWFya2V0IFRyYW5zYWN0aW9ucy5jc3YiIHbhu5tpIDE0LjA1OSBxdWFuIHPDoXQgxJHGsOG7o2MgcGjDom4gdMOtY2ggbmjhurFtIG3DtCB04bqjIMSR4bq3YyDEkWnhu4NtIGPhu6dhIG3hu5l0IHPhu5EgYmnhur9uIMSR4buLbmggdMOtbmggbmjGsCBHaeG7m2kgdMOtbmgsIFTDrG5oIHRy4bqhbmcgaMO0biBuaMOibiwgU+G7nyBo4buvdSBuaMOgLCBCYW5nL1Thu4luaCB2w6AgTmjDs20gc+G6o24gcGjhuqltLiBQaMOibiB0w61jaCDEkcaw4bujYyB0aOG7sWMgaGnhu4duIGLhurFuZyBuZ8O0biBuZ+G7ryBSLCBz4butIGThu6VuZyBjw6FjIGjDoG0gbmjGsCB0YWJsZSgpLCBwcm9wLnRhYmxlKCkgxJHhu4MgdOG6oW8gYuG6o25nIHThuqduIHPhu5EgdsOgIHThu7cgbOG7hyBwaOG6p24gdHLEg20sIHbDoCBnZ3Bsb3QyIMSR4buDIHRy4buxYyBxdWFuIGjDs2EgZOG7ryBsaeG7h3UuDQoNCiMgVGjhu5FuZyBrw6ogbcO0IHThuqMgY2hvIGJp4bq/biDEkeG7i25oIHTDrW5oDQojIyBCaeG6v24gR2VuZGVyIChHaeG7m2kgdMOtbmgpDQpgYGB7cn0NCnRhYmxlKGQkR2VuZGVyKQ0KdGFibGUoZCRHZW5kZXIpIC8gc3VtKHRhYmxlKGQkR2VuZGVyKSkNCmBgYA0KDQpgYGB7cn0NCmdncGxvdChkLCBhZXMoR2VuZGVyKSkgKyANCiAgZ2VvbV9iYXIoZmlsbCA9ICdzdGVlbGJsdWUnKSArIA0KICB4bGFiKCdHaeG7m2kgdMOtbmgnKSArIHlsYWIoJ1Phu5EgbMaw4bujbmcnKQ0KDQpnZ3Bsb3QoZCwgYWVzKEdlbmRlcikpICsgDQogIGdlb21fYmFyKGFlcyh5ID0gYWZ0ZXJfc3RhdChjb3VudCkvc3VtKGFmdGVyX3N0YXQoY291bnQpKSksIGZpbGwgPSAnc3RlZWxibHVlJykgKyANCiAgeGxhYignR2nhu5tpIHTDrW5oJykgKyB5bGFiKCdU4bu3IGzhu4cgJScpDQpgYGANCg0KYGBge3J9DQphYmNfZ2VuZGVyIDwtIGQgfD4gZ3JvdXBfYnkoR2VuZGVyKSB8PiBzdW1tYXJpc2UoZnJlcSA9IG4oKSkgfD4gbXV0YXRlKHBlciA9IGZyZXEvc3VtKGZyZXEpKQ0KZ2dwbG90KGFiY19nZW5kZXIsIGFlcyh4ID0gJycsIHkgPSBwZXIsIGZpbGwgPSBHZW5kZXIpKSArIA0KICBnZW9tX2JhcihzdGF0ID0gJ2lkZW50aXR5JykgKyANCiAgY29vcmRfcG9sYXIoJ3knKSArIA0KICB0aGVtZV92b2lkKCkgKyANCiAgbGFicyhmaWxsID0gJ0dp4bubaSB0w61uaCcpDQpgYGANCg0KQmnhur9uIEdlbmRlciBwaOG6o24gw6FuaCBnaeG7m2kgdMOtbmggY+G7p2Ega2jDoWNoIGjDoG5nLiBU4bqnbiBz4buRIGNobyB0aOG6pXkgY8OzIDcsMTcwIGtow6FjaCBow6BuZyBu4buvIHbDoCA2LDg4OSBraMOhY2ggaMOgbmcgbmFtLCB0xrDGoW5nIOG7qW5nIHbhu5tpIHThu7cgbOG7hyBs4bqnbiBsxrDhu6N0IGzDoCA1MSUgdsOgIDQ5JS4gQmnhu4N1IMSR4buTIGPhu5l0IHbDoCBiaeG7g3UgxJHhu5MgYsOhbmggxJHGsOG7o2Mgc+G7rSBk4bulbmcgxJHhu4MgdHLhu7FjIHF1YW4gaMOzYSBz4buxIHBow6JuIGLhu5EgbsOgeSwgY2hvIHRo4bqleSBwaMOibiBi4buRIGdp4bubaSB0w61uaCB0xrDGoW5nIMSR4buRaSDEkeG7k25nIMSR4buBdSBnaeG7r2EgaGFpIG5ow7NtLg0KDQojIyBCaeG6v24gTWFyaXRhbFN0YXR1cyAoVMOsbmggdHLhuqFuZyBow7RuIG5ow6JuKQ0KYGBge3J9DQp0YWJsZShkJE1hcml0YWxTdGF0dXMpDQp0YWJsZShkJE1hcml0YWxTdGF0dXMpIC8gc3VtKHRhYmxlKGQkTWFyaXRhbFN0YXR1cykpDQpgYGANCg0KYGBge3J9DQpnZ3Bsb3QoZCwgYWVzKE1hcml0YWxTdGF0dXMpKSArIA0KICBnZW9tX2JhcihmaWxsID0gJ2RhcmtvcmFuZ2UnKSArIA0KICB4bGFiKCdUw6xuaCB0cuG6oW5nIGjDtG4gbmjDom4nKSArIHlsYWIoJ1Phu5EgbMaw4bujbmcnKQ0KDQpnZ3Bsb3QoZCwgYWVzKE1hcml0YWxTdGF0dXMpKSArIA0KICBnZW9tX2JhcihhZXMoeSA9IGFmdGVyX3N0YXQoY291bnQpL3N1bShhZnRlcl9zdGF0KGNvdW50KSkpLCBmaWxsID0gJ2RhcmtvcmFuZ2UnKSArIA0KICB4bGFiKCdUw6xuaCB0cuG6oW5nIGjDtG4gbmjDom4nKSArIHlsYWIoJ1Thu7cgbOG7hyAlJykNCmBgYA0KDQpgYGB7cn0NCmFiY19tYXJpdGFsIDwtIGQgfD4gZ3JvdXBfYnkoTWFyaXRhbFN0YXR1cykgfD4gc3VtbWFyaXNlKGZyZXEgPSBuKCkpIHw+IG11dGF0ZShwZXIgPSBmcmVxL3N1bShmcmVxKSkNCmdncGxvdChhYmNfbWFyaXRhbCwgYWVzKHggPSAnJywgeSA9IHBlciwgZmlsbCA9IE1hcml0YWxTdGF0dXMpKSArIA0KICBnZW9tX2JhcihzdGF0ID0gJ2lkZW50aXR5JykgKyANCiAgY29vcmRfcG9sYXIoJ3knKSArIA0KICB0aGVtZV92b2lkKCkgKyANCiAgbGFicyhmaWxsID0gJ1TDrG5oIHRy4bqhbmcgaMO0biBuaMOibicpDQpgYGANCg0KQmnhur9uIE1hcml0YWxTdGF0dXMgY2hvIGJp4bq/dCBraMOhY2ggaMOgbmcgxJHDoyBr4bq/dCBow7RuIGhheSBjw7JuIMSR4buZYyB0aMOibi4gS+G6v3QgcXXhuqMgY2hvIHRo4bqleSBjw7MgNywxOTMga2jDoWNoIGjDoG5nIMSR4buZYyB0aMOibiAoNTEuMTYlKSB2w6AgNiw4NjYga2jDoWNoIGjDoG5nIMSRw6Mga+G6v3QgaMO0biAoNDguODQlKS4gQ8OhYyBiaeG7g3UgxJHhu5MgY+G7mXQgdsOgIGJp4buDdSDEkeG7kyB0csOybiB0aOG7gyBoaeG7h24gcsO1IHPhu7EgcGjDom4gYuG7kSBn4bqnbiBuaMawIMSR4buTbmcgxJHhu4F1IGdp4buvYSBoYWkgbmjDs20ga2jDoWNoIGjDoG5nLg0KDQoNCg0KIyMgQmnhur9uIEhvbWVvd25lciAoU+G7nyBo4buvdSBuaMOgKQ0KYGBge3J9DQp0YWJsZShkJEhvbWVvd25lcikNCnRhYmxlKGQkSG9tZW93bmVyKSAvIHN1bSh0YWJsZShkJEhvbWVvd25lcikpDQpgYGANCg0KU+G7nyBo4buvdSBuaMOgIChPd24gSG9tZSk6IFBow6JuIHTDrWNoIGNobyB0aOG6pXkga2hv4bqjbmcgNjAuMSUga2jDoWNoIGjDoG5nIHPhu58gaOG7r3UgbmjDoC4gQmnhu4N1IMSR4buTIGPhu5l0IHbDoCBi4bqjbmcgdOG7tyBs4buHIMSRxrDhu6NjIHPhu60gZOG7pW5nIMSR4buDIHBo4bqjbiDDoW5oIHPhu7Ega2jDoWMgYmnhu4d0IGdp4buvYSBoYWkgbmjDs20gIlkiIChZZXMpIHbDoCAiTiIgKE5vKS4NCg0KYGBge3J9DQpnZ3Bsb3QoZCwgYWVzKEhvbWVvd25lcikpICsgDQogIGdlb21fYmFyKGZpbGwgPSAnZm9yZXN0Z3JlZW4nKSArIA0KICB4bGFiKCdT4bufIGjhu691IG5ow6AnKSArIHlsYWIoJ1Phu5EgbMaw4bujbmcnKQ0KDQpnZ3Bsb3QoZCwgYWVzKEhvbWVvd25lcikpICsgDQogIGdlb21fYmFyKGFlcyh5ID0gYWZ0ZXJfc3RhdChjb3VudCkvc3VtKGFmdGVyX3N0YXQoY291bnQpKSksIGZpbGwgPSAnZm9yZXN0Z3JlZW4nKSArIA0KICB4bGFiKCdT4bufIGjhu691IG5ow6AnKSArIHlsYWIoJ1Thu7cgbOG7hyAlJykNCmBgYA0KDQpgYGB7cn0NCmFiY19ob21lb3duZXIgPC0gZCB8PiBncm91cF9ieShIb21lb3duZXIpIHw+IHN1bW1hcmlzZShmcmVxID0gbigpKSB8PiBtdXRhdGUocGVyID0gZnJlcS9zdW0oZnJlcSkpDQpnZ3Bsb3QoYWJjX2hvbWVvd25lciwgYWVzKHggPSAnJywgeSA9IHBlciwgZmlsbCA9IEhvbWVvd25lcikpICsgDQogIGdlb21fYmFyKHN0YXQgPSAnaWRlbnRpdHknKSArIA0KICBjb29yZF9wb2xhcigneScpICsgDQogIHRoZW1lX3ZvaWQoKSArIA0KICBsYWJzKGZpbGwgPSAnU+G7nyBo4buvdSBuaMOgJykNCmBgYA0KDQpCaeG6v24gSG9tZW93bmVyIHBo4bqjbiDDoW5oIHZp4buHYyBraMOhY2ggaMOgbmcgY8OzIHPhu58gaOG7r3UgbmjDoCBoYXkga2jDtG5nLiBDw7MgOCw0NDQgbmfGsOG7nWkgc+G7nyBo4buvdSBuaMOgICg2MC4wNiUpIHbDoCA1LDYxNSBuZ8aw4budaSBraMO0bmcgc+G7nyBo4buvdSAoMzkuOTQlKS4gQmnhu4N1IMSR4buTIGPhu5l0IHbDoCBiaeG7g3UgxJHhu5MgYsOhbmggY3VuZyBj4bqlcCBjw6FpIG5ow6xuIHRy4buxYyBxdWFuIHbhu4Egc+G7sSBjaMOqbmggbOG7h2NoIG7DoHksIGNobyB0aOG6pXkgcGjhuqduIGzhu5tuIGtow6FjaCBow6BuZyB0cm9uZyB04bqtcCBk4buvIGxp4buHdSBsw6AgbmfGsOG7nWkgc+G7nyBo4buvdSBuaMOgLg0KDQojIyBCaeG6v24gU3RhdGVvclByb3ZpbmNlIChCYW5nL1Thu4luaCkNCmBgYHtyfQ0KdGFibGUoZCRTdGF0ZW9yUHJvdmluY2UpDQp0YWJsZShkJFN0YXRlb3JQcm92aW5jZSkgLyBzdW0odGFibGUoZCRTdGF0ZW9yUHJvdmluY2UpKQ0KYGBgDQoNCg0KYGBge3J9DQpnZ3Bsb3QoZCwgYWVzKGZjdF9pbmZyZXEoU3RhdGVvclByb3ZpbmNlKSkpICsgDQogIGdlb21fYmFyKGNvbG9yID0gJ2JsdWUnLCBmaWxsID0gJ2dyZWVuJykgKyANCiAgeGxhYignQmFuZy9U4buJbmgnKSArIHlsYWIoJ1Phu5EgbMaw4bujbmcnKSArIA0KICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdCA9IDEpKQ0KDQpnZ3Bsb3QoZCwgYWVzKGZjdF9pbmZyZXEoU3RhdGVvclByb3ZpbmNlKSkpICsgDQogIGdlb21fYmFyKGFlcyh5ID0gYWZ0ZXJfc3RhdChjb3VudCkvc3VtKGFmdGVyX3N0YXQoY291bnQpKSksIGNvbG9yID0gJ2JsdWUnLCBmaWxsID0gJ2dyZWVuJykgKyANCiAgeGxhYignQmFuZy9U4buJbmgnKSArIHlsYWIoJ1Thu7cgbOG7hyAlJykgKyANCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA0NSwgaGp1c3QgPSAxKSkNCmBgYA0KDQpgYGB7cn0NCmFiY19zdGF0ZSA8LSBkIHw+IGdyb3VwX2J5KFN0YXRlb3JQcm92aW5jZSkgfD4gc3VtbWFyaXNlKGZyZXEgPSBuKCkpIHw+IG11dGF0ZShwZXIgPSBmcmVxL3N1bShmcmVxKSkNCmdncGxvdChhYmNfc3RhdGUsIGFlcyh4ID0gJycsIHkgPSBwZXIsIGZpbGwgPSBTdGF0ZW9yUHJvdmluY2UpKSArIA0KICBnZW9tX2JhcihzdGF0ID0gJ2lkZW50aXR5JykgKyANCiAgY29vcmRfcG9sYXIoJ3knKSArIA0KICB0aGVtZV92b2lkKCkgKyANCiAgbGFicyhmaWxsID0gJ0JhbmcvVOG7iW5oJykNCmBgYA0KDQpCaeG6v24gU3RhdGVvclByb3ZpbmNlIGNobyBiaeG6v3Qga2h1IHbhu7FjIMSR4buLYSBsw70gY+G7p2Ega2jDoWNoIGjDoG5nLCB24bubaSAxMCBiYW5nL3Thu4luaCBraMOhYyBuaGF1LiBXYXNoaW5ndG9uIChXQSkgY8OzIHPhu5EgbMaw4bujbmcga2jDoWNoIGjDoG5nIGNhbyBuaOG6pXQgKDMyLjQ4JSksIHRp4bq/cCB0aGVvIGzDoCBDYWxpZm9ybmlhIChDQSwgMTkuNDQlKSwgdHJvbmcga2hpIEphbGlzY28gY8OzIHPhu5EgbMaw4bujbmcgdGjhuqVwIG5o4bqldCAoMC41MyUpLiBCaeG7g3UgxJHhu5MgY+G7mXQgc+G6r3AgeOG6v3AgdGhlbyB04bqnbiBzdeG6pXQgZ2nhuqNtIGThuqduIHbDoCBiaeG7g3UgxJHhu5MgdHLDsm4gZ2nDunAgaMOsbmggZHVuZyBz4buxIHBow6JuIGLhu5EgdGhlbyBraHUgduG7sWMuDQoNCiMjIEJp4bq/biBQcm9kdWN0RmFtaWx5IChOaMOzbSBz4bqjbiBwaOG6qW0pDQpgYGB7cn0NCnRhYmxlKGQkUHJvZHVjdEZhbWlseSkNCnRhYmxlKGQkUHJvZHVjdEZhbWlseSkgLyBzdW0odGFibGUoZCRQcm9kdWN0RmFtaWx5KSkNCmBgYA0KDQoNCmBgYHtyfQ0KZ2dwbG90KGQsIGFlcyhQcm9kdWN0RmFtaWx5KSkgKyANCiAgZ2VvbV9iYXIoZmlsbCA9ICdwdXJwbGUnKSArIA0KICB4bGFiKCdOaMOzbSBz4bqjbiBwaOG6qW0nKSArIHlsYWIoJ1Phu5EgbMaw4bujbmcnKQ0KDQpnZ3Bsb3QoZCwgYWVzKFByb2R1Y3RGYW1pbHkpKSArIA0KICBnZW9tX2JhcihhZXMoeSA9IGFmdGVyX3N0YXQoY291bnQpL3N1bShhZnRlcl9zdGF0KGNvdW50KSkpLCBmaWxsID0gJ3B1cnBsZScpICsgDQogIHhsYWIoJ05ow7NtIHPhuqNuIHBo4bqpbScpICsgeWxhYignVOG7tyBs4buHICUnKQ0KYGBgDQoNCmBgYHtyfQ0KYWJjX3Byb2R1Y3QgPC0gZCB8PiBncm91cF9ieShQcm9kdWN0RmFtaWx5KSB8PiBzdW1tYXJpc2UoZnJlcSA9IG4oKSkgfD4gbXV0YXRlKHBlciA9IGZyZXEvc3VtKGZyZXEpKQ0KZ2dwbG90KGFiY19wcm9kdWN0LCBhZXMoeCA9ICcnLCB5ID0gcGVyLCBmaWxsID0gUHJvZHVjdEZhbWlseSkpICsgDQogIGdlb21fYmFyKHN0YXQgPSAnaWRlbnRpdHknKSArIA0KICBjb29yZF9wb2xhcigneScpICsgDQogIHRoZW1lX3ZvaWQoKSArIA0KICBsYWJzKGZpbGwgPSAnTmjDs20gc+G6o24gcGjhuqltJykNCmBgYA0KDQpCaeG6v24gUHJvZHVjdEZhbWlseSBwaMOibiBsb+G6oWkgc+G6o24gcGjhuqltIHRoZW8gYmEgbmjDs20gY2jDrW5oOiBGb29kICg3Mi4yMiUpLCBOb24tQ29uc3VtYWJsZSAoMTguODklKSB2w6AgRHJpbmsgKDguODklKS4gS+G6v3QgcXXhuqMgY2hvIHRo4bqleSBuaMOzbSBz4bqjbiBwaOG6qW0gdGjhu7FjIHBo4bqpbSBjaGnhur9tIMawdSB0aOG6vyByw7UgcuG7h3QuIEPDoWMgYmnhu4N1IMSR4buTIHRy4buxYyBxdWFuIGjhu5cgdHLhu6MgbMOgbSByw7Ugc+G7sSBwaMOibiBi4buRIGtow7RuZyDEkeG7k25nIMSR4buBdSBuw6B5IGdp4buvYSBjw6FjIG5ow7NtIHPhuqNuIHBo4bqpbS4NCg0KIyBUaOG7kW5nIGvDqiBtw7QgdOG6oyBjaG8gYmnhur9uIMSR4buLbmggbMaw4bujbmcNCiMjIEJp4bq/biBDaGlsZHJlbiAoU+G7kSBjb24pDQpgYGB7cn0NCnN1bW1hcnkoZCRDaGlsZHJlbikNCmBgYA0KDQpgYGB7cn0NCmdncGxvdChkLCBhZXMoeSA9IENoaWxkcmVuKSkgKyANCiAgZ2VvbV9ib3hwbG90KGZpbGwgPSAnbGlnaHRibHVlJykgKyANCiAgeWxhYignU+G7kSBjb24nKQ0KYGBgDQoNCmBgYHtyfQ0KZ2dwbG90KGQsIGFlcyhDaGlsZHJlbikpICsgDQogIGdlb21faGlzdG9ncmFtKGJpbndpZHRoID0gMSwgZmlsbCA9ICdsaWdodGJsdWUnLCBjb2xvciA9ICdibGFjaycpICsgDQogIHhsYWIoJ1Phu5EgY29uJykgKyB5bGFiKCdT4buRIGzGsOG7o25nJykNCmBgYA0KQmnhur9uIENoaWxkcmVuIGNobyBiaeG6v3Qgc+G7kSBsxrDhu6NuZyBjb24gY+G7p2Ega2jDoWNoIGjDoG5nLiBUaOG7kW5nIGvDqiBtw7QgdOG6oyBjaG8gdGjhuqV5IHPhu5EgY29uIHRydW5nIGLDrG5oIGzDoCAyLjUzLCB0cnVuZyB24buLIGzDoCAzLCBkYW8gxJHhu5luZyB04burIDAgxJHhur9uIDUuIEJp4buDdSDEkeG7kyBo4buZcCB2w6AgYmnhu4N1IMSR4buTIGhpc3RvZ3JhbSBnacO6cCBtaW5oIGjhu41hIHLDtSBoxqFuIHbhu4Egc+G7sSBwaMOibiBi4buRLCDEkeG7mSBs4buHY2ggdsOgIGPDoWMgZ2nDoSB0cuG7iyBuZ2/huqFpIGxhaS4NCg0KDQojIyBCaeG6v24gVW5pdHNTb2xkIChT4buRIMSRxqFuIHbhu4sgYsOhbiByYSkNCmBgYHtyfQ0Kc3VtbWFyeShkJFVuaXRzU29sZCkNCmBgYA0KDQpgYGB7cn0NCmdncGxvdChkLCBhZXMoeSA9IFVuaXRzU29sZCkpICsgDQogIGdlb21fYm94cGxvdChmaWxsID0gJ2xpZ2h0Z3JlZW4nKSArIA0KICB5bGFiKCdT4buRIMSRxqFuIHbhu4sgYsOhbiByYScpDQpgYGANCg0KYGBge3J9DQpnZ3Bsb3QoZCwgYWVzKFVuaXRzU29sZCkpICsgDQogIGdlb21faGlzdG9ncmFtKGJpbndpZHRoID0gMSwgZmlsbCA9ICdsaWdodGdyZWVuJywgY29sb3IgPSAnYmxhY2snKSArIA0KICB4bGFiKCdT4buRIMSRxqFuIHbhu4sgYsOhbiByYScpICsgeWxhYignU+G7kSBsxrDhu6NuZycpDQoNCmBgYA0KDQpCaeG6v24gVW5pdHNTb2xkIHBo4bqjbiDDoW5oIHPhu5EgbMaw4bujbmcgc+G6o24gcGjhuqltIMSRxrDhu6NjIGLDoW4gdHJvbmcgbeG7l2kgZ2lhbyBk4buLY2gsIHbhu5tpIHRydW5nIGLDrG5oIGzDoCA0LjA4IMSRxqFuIHbhu4suIEdpw6EgdHLhu4sgbmjhu48gbmjhuqV0IGzDoCAxLCBs4bubbiBuaOG6pXQgbMOgIDguIEJp4buDdSDEkeG7kyBo4buZcCB2w6AgaGlzdG9ncmFtIMSRxrDhu6NjIHPhu60gZOG7pW5nIMSR4buDIHRo4buDIGhp4buHbiBwaMOibiBi4buRIHbDoCBz4buxIHThuq1wIHRydW5nIGPhu6dhIGJp4bq/biBuw6B5IHh1bmcgcXVhbmggdHJ1bmcgduG7iy4NCg0KDQoNCiMjIEJp4bq/biBSZXZlbnVlIChEb2FuaCB0aHUpDQpgYGB7cn0NCnN1bW1hcnkoZCRSZXZlbnVlKQ0KYGBgDQoNCmBgYHtyfQ0KZ2dwbG90KGQsIGFlcyh5ID0gUmV2ZW51ZSkpICsgDQogIGdlb21fYm94cGxvdChmaWxsID0gJ2xpZ2h0Y29yYWwnKSArIA0KICB5bGFiKCdEb2FuaCB0aHUnKQ0KYGBgDQoNCmBgYHtyfQ0KZ2dwbG90KGQsIGFlcyhSZXZlbnVlKSkgKyANCiAgZ2VvbV9oaXN0b2dyYW0oYmlud2lkdGggPSA1LCBmaWxsID0gJ2xpZ2h0Y29yYWwnLCBjb2xvciA9ICdibGFjaycpICsgDQogIHhsYWIoJ0RvYW5oIHRodScpICsgeWxhYignU+G7kSBsxrDhu6NuZycpDQpgYGANCg0KQmnhur9uIFJldmVudWUgdGjhu4MgaGnhu4duIHThu5VuZyBkb2FuaCB0aHUgdOG7qyBt4buXaSBnaWFvIGThu4tjaC4gRG9hbmggdGh1IHRydW5nIGLDrG5oIGzDoCAxMy4wMCwgduG7m2kgdHJ1bmcgduG7iyAxMS4yNSB2w6AgZGFvIMSR4buZbmcgdOG7qyAwLjUzIMSR4bq/biA1Ni43MC4gQmnhu4N1IMSR4buTIGjhu5lwIHbDoCBoaXN0b2dyYW0gY2hvIHRo4bqleSBwaMOibiBi4buRIGjGoWkgbOG7h2NoIHBo4bqjaSB24bubaSBt4buZdCBz4buRIGdpw6EgdHLhu4sgZG9hbmggdGh1IGNhbyB2xrDhu6N0IHRy4buZaS4NCg0KIyBUaOG7kW5nIGvDqiBtw7QgdOG6oyBjaG8gaGFpIGJp4bq/bg0KDQojIyBHZW5kZXIgdsOgIFByb2R1Y3RGYW1pbHkNCmBgYHtyfQ0KDQphYmNfZ2VuZGVyX3Byb2R1Y3QgPC0gdGFibGUoZCRHZW5kZXIsIGQkUHJvZHVjdEZhbWlseSkNCmFiY19nZW5kZXJfcHJvZHVjdA0KYGBgDQoNCmBgYHtyfQ0KYWJjX2dlbmRlcl9wcm9kdWN0X3Byb3AgPC0gcHJvcC50YWJsZShhYmNfZ2VuZGVyX3Byb2R1Y3QpDQphZGRtYXJnaW5zKGFiY19nZW5kZXJfcHJvZHVjdF9wcm9wKQ0KYGBgDQoNCmBgYHtyfQ0KZ2dwbG90KGQsIGFlcyhHZW5kZXIsIGZpbGwgPSBQcm9kdWN0RmFtaWx5KSkgKyANCiAgZ2VvbV9iYXIocG9zaXRpb24gPSAnZG9kZ2UnKSArIA0KICB4bGFiKCdHaeG7m2kgdMOtbmgnKSArIHlsYWIoJ1Phu5EgbMaw4bujbmcnKSArIA0KICBsYWJzKGZpbGwgPSAnTmjDs20gc+G6o24gcGjhuqltJykNCmBgYA0KDQpN4buRaSBxdWFuIGjhu4cgZ2nhu69hIEdlbmRlciB2w6AgUHJvZHVjdEZhbWlseSBjaG8gdGjhuqV5IHBo4bulIG7hu68gbXVhIHPhuqNuIHBo4bqpbSBGb29kIG5oaeG7gXUgaMahbiBuYW0gZ2nhu5tpLCB24bubaSBjw6FjIGNvbiBz4buRIGzhuqduIGzGsOG7o3QgbMOgIDUsMTQ5IHbDoCA1LDAwNC4gVOG7tyBs4buHIHTGsMahbmcg4bupbmcgY2hvIHRo4bqleSB4dSBoxrDhu5tuZyBs4buxYSBjaOG7jW4gc+G6o24gcGjhuqltIGdp4buvYSBoYWkgZ2nhu5tpIHTGsMahbmcgxJHhu5FpIHTGsMahbmcgxJHhu5NuZywgdHV5IG5oacOqbiBjw7Mgc+G7sSBraMOhYyBiaeG7h3Qgbmjhu48gdHJvbmcgbmjDs20gRHJpbmsgdsOgIE5vbi1Db25zdW1hYmxlLiBCaeG7g3UgxJHhu5MgY+G7mXQgc28gc8OhbmggdHLhu7FjIHRp4bq/cCBnacO6cCBuaOG6rW4gZGnhu4duIHh1IGjGsOG7m25nIG7DoHkgbeG7mXQgY8OhY2ggdHLhu7FjIHF1YW4uDQoNCg0KDQojIyBNYXJpdGFsU3RhdHVzIHbDoCBIb21lb3duZXINCmBgYHtyfQ0KYWJjX21hcml0YWxfaG9tZW93bmVyIDwtIHRhYmxlKGQkTWFyaXRhbFN0YXR1cywgZCRIb21lb3duZXIpDQphYmNfbWFyaXRhbF9ob21lb3duZXINCmBgYA0KDQpgYGB7cn0NCmFiY19tYXJpdGFsX2hvbWVvd25lcl9wcm9wIDwtIHByb3AudGFibGUoYWJjX21hcml0YWxfaG9tZW93bmVyKQ0KYWRkbWFyZ2lucyhhYmNfbWFyaXRhbF9ob21lb3duZXIpDQpgYGANCg0KYGBge3J9DQpyaXNrcmF0aW8oYWJjX21hcml0YWxfaG9tZW93bmVyLCByZXYgPSAnYycpDQpgYGANCg0KYGBge3J9DQpvZGRzcmF0aW8oYWJjX21hcml0YWxfaG9tZW93bmVyLCByZXYgPSAnYycpDQpgYGANCg0KYGBge3J9DQpnZ3Bsb3QoZCwgYWVzKE1hcml0YWxTdGF0dXMsIGZpbGwgPSBIb21lb3duZXIpKSArIA0KICBnZW9tX2Jhcihwb3NpdGlvbiA9ICdkb2RnZScpICsgDQogIHhsYWIoJ1TDrG5oIHRy4bqhbmcgaMO0biBuaMOibicpICsgeWxhYignU+G7kSBsxrDhu6NuZycpICsgDQogIGxhYnMoZmlsbCA9ICdT4bufIGjhu691IG5ow6AnKQ0KYGBgDQoNClPhu7Ega+G6v3QgaOG7o3AgZ2nhu69hIGhhaSBiaeG6v24gTWFyaXRhbFN0YXR1cyB2w6AgSG9tZW93bmVyIGNobyB0aOG6pXkgcGjhuqduIGzhu5tuIGtow6FjaCBow6BuZyDEkcOjIGvhur90IGjDtG4gc+G7nyBo4buvdSBuaMOgICg3NSUpLCB0cm9uZyBraGkgdOG7tyBs4buHIG7DoHkg4bufIG5ow7NtIMSR4buZYyB0aMOibiBjaOG7iSBraG/huqNuZyA0NiUuIFThu7cgc+G7kSBjaMOqbmggKG9kZHMgcmF0aW8pIGzDoCAzLjUzOCwgdsOgIHLhu6dpIHJvIHTGsMahbmcgxJHhu5FpIChyaXNrIHJhdGlvKSBsw6AgMi4xNjMsIGNobyB0aOG6pXkgc+G7sSBraMOhYyBiaeG7h3QgxJHDoW5nIGvhu4MgZ2nhu69hIGhhaSBuaMOzbS4gQmnhu4N1IMSR4buTIGPhu5l0IG1pbmggaOG7jWEgcsO1IHPhu7Ega2jDoWMgYmnhu4d0IHRyb25nIHF1eeG7gW4gc+G7nyBo4buvdSBuaMOgIHRoZW8gdMOsbmggdHLhuqFuZyBow7RuIG5ow6JuLg0KDQoNCg0KIyBUaOG7kW5nIGvDqiBtw7QgdOG6oyBjaG8gYmEgYmnhur9uDQoNCiMjIEdlbmRlciwgSG9tZW93bmVyLCBDb3VudHJ5DQoNClBow6JuIHTDrWNoIGJhIGJp4bq/biBr4bq/dCBo4bujcCBjaG8gdGjhuqV5IHPhu7EgcGjDom4gYuG7kSBxdXnhu4FuIHPhu58gaOG7r3UgbmjDoCB0aGVvIGdp4bubaSB0w61uaCB2w6AgcXXhu5FjIGdpYS4gVsOtIGThu6UsIHThuqFpIENhbmFkYSwgdOG7tyBs4buHIG7hu68gc+G7nyBo4buvdSBuaMOgIGNhbyBoxqFuIG5hbTsgdHJvbmcga2hpIHThuqFpIE1leGljbywgc+G7sSBraMOhYyBiaeG7h3QgZ2nhu69hIGhhaSBnaeG7m2kgw610IHLDtSBy4buHdCBoxqFuLiBE4buvIGxp4buHdSDEkcaw4bujYyB0csOsbmggYsOgeSB0cm9uZyBi4bqjbmcgdOG6p24gc+G7kSBiYSBjaGnhu4F1LCBnacO6cCBu4bqvbSBi4bqvdCB04buVbmcgcXVhbiBz4buxIGtow6FjIGJp4buHdCB0aGVvIHThu6tuZyBxdeG7kWMgZ2lhLg0KDQoNCmBgYHtyfQ0KYWJjX2dlbmRlcl9ob21lb3duZXJfY291bnRyeSA8LSBmdGFibGUoZCRHZW5kZXIsIGQkSG9tZW93bmVyLCBkJENvdW50cnkpDQphYmNfZ2VuZGVyX2hvbWVvd25lcl9jb3VudHJ5DQpgYGANCg0KYGBge3J9DQphYmNfZ2VuZGVyX2hvbWVvd25lcl9jb3VudHJ5X3Byb3AgPC0gcHJvcC50YWJsZShhYmNfZ2VuZGVyX2hvbWVvd25lcl9jb3VudHJ5KQ0Kcm91bmQoYWRkbWFyZ2lucyhhYmNfZ2VuZGVyX2hvbWVvd25lcl9jb3VudHJ5X3Byb3ApLCA0KQ0KYGBgDQojIMSQ4buZIG5o4bqheSB2w6AgxJHhu5kgxJHhurdjIGhp4buHdQ0KDQpE4buxYSB0csOqbiBt4buRaSBxdWFuIGjhu4cgZ2nhu69hIE1hcml0YWxTdGF0dXMgdsOgIEhvbWVvd25lciwgxJHhu5kgbmjhuqF5IChraOG6oyBuxINuZyBk4buxIMSRb8OhbiDEkcO6bmcgbmfGsOG7nWkgc+G7nyBo4buvdSBuaMOgIGtoaSDEkcOjIGvhur90IGjDtG4pIGzDoCA3NC45NiUsIHbDoCDEkeG7mSDEkeG6t2MgaGnhu4d1IChraOG6oyBuxINuZyBk4buxIMSRb8OhbiDEkcO6bmcgbmfGsOG7nWkga2jDtG5nIHPhu58gaOG7r3UgbmjDoCBraGkgxJHhu5ljIHRow6JuKSBsw6AgNTQuMTYlLiBIYWkgY2jhu4kgc+G7kSBuw6B5IGdpw7pwIMSRw6FuaCBnacOhIGhp4buHdSBxdeG6oyBwaMOibiBsb+G6oWkgZOG7sWEgdsOgbyB0w6xuaCB0cuG6oW5nIGjDtG4gbmjDom4uDQoNCg0KYGBge3J9DQphYmNfbWFyaXRhbF9ob21lb3duZXINCg0Kc2Vuc2l0aXZpdHkgPC0gYWJjX21hcml0YWxfaG9tZW93bmVyWzEsIDJdIC8gKGFiY19tYXJpdGFsX2hvbWVvd25lclsxLCAyXSArIGFiY19tYXJpdGFsX2hvbWVvd25lclsxLCAxXSkNCnNlbnNpdGl2aXR5DQoNCnNwZWNpZmljaXR5IDwtIGFiY19tYXJpdGFsX2hvbWVvd25lclsyLCAxXSAvIChhYmNfbWFyaXRhbF9ob21lb3duZXJbMiwgMV0gKyBhYmNfbWFyaXRhbF9ob21lb3duZXJbMiwgMl0pDQpzcGVjaWZpY2l0eQ0KYGBgDQo=