Hôm nay Nhi muốn giới thiệu với các bạn một công cụ rất hay trong R là recipes.

1 Giới thiệu về 2 tác giả

Package recipes là sản phẩm của hai tác giả: Hadley Wickham và Max Kuhn. Danh tiếng của hai nhân vật này trong cộng đồng R users chắc các bạn đã biết rõ. Và nếu bạn từng trải nghiệm những packages khác của 2 cao thủ này, bạn có thể hình dung về sự tiện dụng và ý nghĩa quan trọng của package recipes mà Nhi sắp giới thiệu.

Thực vậy, tất cả những công cụ do H. Wickham và Max Kuhn tạo ra không chỉ đơn giản là những ứng dụng có mục tiêu hạn hẹp, chuyên biệt nhắm vào một quy trình, một thuật toán nào đó như những package khác, nhưng đặt ra một nền tảng phổ quáthơn rất nhiều. Những công cụ này đã làm thay đổi cách thức làm việc trong R, từ quy trình cho đến phong cách viết code, và bản thân cấu trúc ngữ pháp của R code. Từ khi có ggplot2, người ta viết code đồ họa hoàn toàn khác với thời sơ khai, từ khi có tidyverse, dplyr và pipes thì phong cách R codes thay đổi hoàn toàn so với quá khứ. Tương tự, package caret của Max Kuhn đã phổ biến Machine learning đến mọi người, hầu như tất cả R users bắt đầu học về Machine Learning qua caret trước khi họ có thể tự code những algorithm phức tạp khác.

Khi Wickham và Max Kuhn cùng ngồi lại với nhau và tạo ra 1 package như recipes, các bạn có thể dự đoán: Đây sẽ là một giao thức phổ quát, một hệ ngôn ngữ mới nằm trong ngôn ngữ R,và nó sẽ làm thay đổi cách làm việc của bạn.

2 Ý tưởng về nấu ăn

Trong quá trình thực hành thống kê, Nhi nhận ra sự tương đồng thú vị giữa việc phân tích dữ liệu và nấu ăn, và nhiều lần Nhi đã sử dụng việc nấu ăn để minh họa cho các ý tưởng về thống kê. Ý tưởng này chính là nguyên tắc của package recipes: các hàm quan trọng của package này nhằm mô phỏng một quy trình chế biến món ăn, cụ thể là làm bánh: gồm hàm recipe(), prep( ), juice( ) và bake( )

Trước khi đi vào chi tiết, ta sẽ tóm tắt lại quy trình nấu ăn/thống kê:

Cùng một dữ liệu gốc, ta có thể dựng nhiều mô hình thống kê khác nhau - giống như từ những nguyên liệu trong tự nhiên như thịt, cá, rau, củ, gia vị… nhiều món ăn khác nhau có thể được tạo thành.

Mỗi mô hình thống kê có những yêu cầu khác nhau về cấu trúc, nội dung dữ liệu đầu vào, do đó dữ liệu gốc phải trải qua các bước xử lý chuyên biệt tùy theo mỗi loại mô hình, bao gồm chọn lọc biến, hoán chuyển,tạo biến mới, giảm chiều thông tin, … tương tự như mỗi món ăn đòi hỏi nguyên liệu phải được sơ chế phù hợp và thêm bớt gia vị. Thí dụ từ cá hồi, có thể làm món nướng, kho hay ăn sống như sushi.

Quy trình chuẩn bị, sơ chế, tẩm ướp nguyên liệutrở thành một bí quyết cho mỗi món ăn, và được đúc kết lại trong sách vở thành những “công thức nấu nướng” (recipes). Một bà nội trợ sau khi ăn Sushi tại nhà hàng thường có ý định tự thực hiện món ăn này tại nhà, điều đầu tiên cô ta sẽ làm là mua một quyển sách hướng dẫn làm sushi hay tìm công thức trên mạng. Lúc này, ý tưởng chỉ nằm trên giấy,bao gồm mô tả về bao nhiêu nguyên liệu cần chuẩn bị,cách rửa, cắt xén,sơ chế, ướp gia vị…

Tương tự, quy trình chuẩn bị dữ liệu cho mô hình thống kê thường được giảng dạy trong trường lớp hay sách vở và ghi trong trí nhớ của nghiên cứu sinh dưới dạng những công thức. Vào lúc này, thứ mà họ có chỉ là ý tưởng và chỉ dẫn, với các nguyên tắc,công thức toán và thứ tự thực hiện, nhưng chưa thực sự được thi hành trên bất cứ dữ liệu nào.

Trước kia, việc chuyển từ lý thuyết sang thi hành những ý tưởng, quy trình, công thức trên R codes thường rất thủ công và tủn mủn, manh mún. Thí dụ một sinh viên có thể hiểu rõ bản chất của Box-Cox transformation là gì, hay chuẩn hóa là gì, nhưng không biết phải viết R code như thế nào. Ta thường phải tìm trên mạng những R codes rải rác cho mỗi quy trình, và mỗi lần như vậy lại có một hàm mới lạ, với cú pháp khác nhau, tên gọi thường chẳng ăn nhập gì đến mục tiêu. CÓ khi một quy trình phải qua trung gian nhiều hàm khác nhau, của nhiều package khác nhau…

Chưa hết, khó khăn còn ở chỗ đa số các hàm trong R chỉ thao tác trên từng vector đơn lẻ. Khi ta muốn thực hiện hàng loạt một quy trình cho toàn bộ dữ liệu, ta lại phải học thêm các hàm như apply, map hoặc phải viết code cho vòng lặp…

Một vấn đề tế nhị khác lại nảy sinh khi thứ gọi là Machine learning bắt đầu phổ biến như một cách suy diễn thống kê mới. Theo trường phái này, chúng ta không chỉ làm việc trên 1 tập dữ liệu nguyên thủy duy nhất, mà thường cắt dữ liệu thành 2 phần là trainset và Testset. Những quy trình trước kia ta chỉ cần làm 1 lần, nay ta phải làm đến 2 lần trên mỗi tập dữ liệu train và test. Chưa hết, điểm phức tạp nằm ở chỗ: ta không được nhìn vào bên trong testset để giữ tính độc lập tuyệt đối của nó, do đó các tham số trong quy trình hoán chuyển dữ liệu (thí dụ Mean trong centering, sd trong hàm scale, Lambda trong Box-Cox, centroid trong knn Imputation, PCA) đều dựa vào trainset, ngay cả khi áp dụng trên testset. Như vậy việc sơ chế dữ liệu trở thành một Mô hình hay 1 Hàm, được xác định trên trainset rồi áp dụng trên chính trainset hay dữ liệu mới là testset.

Điều này tương tự như công thức làm món Maki sushi, bà nội trợ sau khi thực hành công thức học được từ sách trên nguyên liệu là Maki, bà ta sẽ rút ra được kinh nghiệm của chính bản thân, từ đó cải biên ra một bí quyết, công thức của riêng mình. Sau đó bà ta có thể áp dụng công thức này cho một nguyên liệu mới là cá Ngừ và tạo ra món ăn mới là Maki cá ngừ.

3 Viết công thức nấu ăn: hàm recipe( )

Sau khi tải package recipes, việc đầu tiên ta có thể làm là tạo ra 1 công thức, hàm recipe()

Muốn tạo 1 công thức, đầu tiên phải dựa vào 1 object dữ liệu. Nhi sẽ dùng chính dataset biomass trong package recipes:

library(recipes)

bio_train<- biomass[biomass$dataset == "Training",]
bio_test <- biomass[biomass$dataset == "Testing",]

bio_train%>%head()%>%knitr::kable()
sample dataset carbon hydrogen oxygen nitrogen sulfur HHV
Akhrot Shell Training 49.81 5.64 42.94 0.41 0.00 20.008
Alabama Oak Wood Waste Training 49.50 5.70 41.30 0.20 0.00 19.228
Alder Training 47.82 5.80 46.25 0.11 0.02 18.299
Alfalfa Training 45.10 4.97 35.60 3.30 0.16 18.151
Alfalfa Seed Straw Training 46.76 5.40 40.72 1.00 0.02 18.450
Alfalfa Stalks Training 45.40 5.75 40.20 2.04 0.10 18.465

Đưa object bio_train vào hàm recipe, ta có 1 công thức sơ khai tên là rec. Dùng hàm summary, ta có thể xem cấu trúc dữ liệu trong công thức: 2 cột variable và type dễ hiểu: chính là tên biến và định dạng. Ta chú ý đến cột role: đây là một thuộc tính dữ liệu cho phép ta quy định vai trò của từng biến trong công thức. Điều này quan trọng vì sau này có một số can thiệp chỉ được làm trên predictors, hoặc outcome, cũng như một số biến không có vai trò nào cả trong mô hình.

rec<-recipe(bio_train)

summary(rec)
## # A tibble: 8 x 4
##   variable type    role  source  
##   <chr>    <chr>   <lgl> <chr>   
## 1 sample   nominal NA    original
## 2 dataset  nominal NA    original
## 3 carbon   numeric NA    original
## 4 hydrogen numeric NA    original
## 5 oxygen   numeric NA    original
## 6 nitrogen numeric NA    original
## 7 sulfur   numeric NA    original
## 8 HHV      numeric NA    original

Để đặt vai trò cho mỗi biến, có hai cách: hoặc ta dùng công thức, hoặc ta khai báo thủ công bằng hàm add_role.

Thí dụ hàm add_role cho phép định vai trò cho từng biến như sau:

rec2<-rec%>%
  add_role(HHV,new_role = "outcome") %>%
  add_role(sample, new_role="id variable")%>%
  add_role(dataset,new_role = "splitting variable")%>%
  add_role(carbon,contains("gen"),sulfur,new_role="predictor")

summary(rec2)
## # A tibble: 8 x 4
##   variable type    role               source  
##   <chr>    <chr>   <chr>              <chr>   
## 1 sample   nominal id variable        original
## 2 dataset  nominal splitting variable original
## 3 carbon   numeric predictor          original
## 4 hydrogen numeric predictor          original
## 5 oxygen   numeric predictor          original
## 6 nitrogen numeric predictor          original
## 7 sulfur   numeric predictor          original
## 8 HHV      numeric outcome            original

Nếu dùng công thức, cú pháp có dạng: Outcome ~ Predictor + Predictor 2… , thứ gì đứng trước dấu ~ là Outcome, đứng sau là predictors. Thí dụ:

rec3<-recipe(bio_train, oxygen+nitrogen~carbon+hydrogen)

summary(rec3)
## # A tibble: 4 x 4
##   variable type    role      source  
##   <chr>    <chr>   <chr>     <chr>   
## 1 carbon   numeric predictor original
## 2 hydrogen numeric predictor original
## 3 oxygen   numeric outcome   original
## 4 nitrogen numeric outcome   original

Ta có thể cập nhật công thức bằng hàm add_role()

rec4<-recipe(HHV~.,bio_train)%>%
    add_role(sample,new_role="id variable")%>%
  add_role(dataset,new_role="splitting variable")

summary(rec4)
## # A tibble: 8 x 4
##   variable type    role               source  
##   <chr>    <chr>   <chr>              <chr>   
## 1 sample   nominal id variable        original
## 2 dataset  nominal splitting variable original
## 3 carbon   numeric predictor          original
## 4 hydrogen numeric predictor          original
## 5 oxygen   numeric predictor          original
## 6 nitrogen numeric predictor          original
## 7 sulfur   numeric predictor          original
## 8 HHV      numeric outcome            original

Bây giờ ta khảo sát sâu hơn cấu trúc của 1 công thức; nó chính là 1 list, gồm nhiều nội dung. Dữ liệu gốc được lưu trong công thức ở nội dung template:

rec2$template%>%head()%>%knitr::kable()
sample dataset carbon hydrogen oxygen nitrogen sulfur HHV
Akhrot Shell Training 49.81 5.64 42.94 0.41 0.00 20.008
Alabama Oak Wood Waste Training 49.50 5.70 41.30 0.20 0.00 19.228
Alder Training 47.82 5.80 46.25 0.11 0.02 18.299
Alfalfa Training 45.10 4.97 35.60 3.30 0.16 18.151
Alfalfa Seed Straw Training 46.76 5.40 40.72 1.00 0.02 18.450
Alfalfa Stalks Training 45.40 5.75 40.20 2.04 0.10 18.465

Công thức này chưa có 1 quy trình nào cả, nên nội dung steps là rỗng

rec2$steps
## NULL

Package recipes cung cấp đến 54 quy trình chuẩn bị, hoán chuyển dữ liệu thông dụng trong phân tích hồi quy và Machine learning, từ những thứ đơn giản nhất như cắt biến định lượng thành nhiều phần, chuẩn hóa, hoán chuyển logarit, Box-Cox, Yeo-Johnshon… cho đến những giải thuật imputation như knn, bagging, hay các thủ thuật giảm chiều dữ liệu như PCA. Điểm thú vị nhất là tất cả những quy trình này đều có chung danh pháp dạng step_tên quy trình và cú pháp đồng nhất bao gồm : đối tượng recipe cần cập nhật, các tùy chỉnh tùy theo quy trình dạng option(list), hoặc tùy chỉnh về sao lưu dữ liệu, bỏ qua, loại bỏ giá trị NA…

Thí dụ: Kết hợp quy trình centering và scale sẽ chuẩn hóa toàn bộ predictor trong công thức:

rec2%<>%
  step_center(all_predictors()) %>%
  step_scale(all_predictors())

rec2
## Data Recipe
## 
## Inputs:
## 
##                role #variables
##         id variable          1
##             outcome          1
##           predictor          5
##  splitting variable          1
## 
## Operations:
## 
## Centering for all_predictors()
## Scaling for all_predictors()

4 Thực hiện công thức này: hàm prep( )

Lưu ý là khi quy trình mới được khai báo trong công thức, nó chưa thực sự có công hiệu, quy trình cần được huấn luyện bằng hàm prep:

Quá trình huấn luyện này cũng giống như người nội trợ thực hành công thức nấu ăn trên nguyên liệu thực tế (trainset), sau khi thực hành công thức lý thuyết mới trở thành bí quyết nấu ăn của bản thân người đó. Điều này quan trọng vì nhiều quy trình hoán chuyển, bổ túc dữ liệu chỉ mới là những hàm rỗng, chưa có tham số bên trong. Những tham số này chỉ được hình thành sau khi quy trình được huấn luyện trên trainset, khi đó chúng mới thực sự có khả năng làm việc.

Thí dụ ta dùng hàm prep để kích hoạt một quy trình chuẩn hóa và thực sự tạo ra 1 công thức hay hàm “chuẩn hóa dữ liệu” :

Lưu ý tùy chỉnh retain: nếu bạn muốn dùng kết quả huấn luyện này cho chính tập train, thay thế trainset bằng dữ liệu mới sau hoán chuyển, bạn sẽ đặt retain=T, lúc này template dữ liệu trong công thức mới sẽ bị thay thế bằng kết quả sau hoán chuyển

rec_trainedR<-prep(rec2,training=bio_train,retain=T)

rec_trained<-prep(rec2,training=bio_train,retain=F)


rec_trained$template%>%head%>%knitr::kable()
sample dataset carbon hydrogen oxygen nitrogen sulfur HHV
Akhrot Shell Training 49.81 5.64 42.94 0.41 0.00 20.008
Alabama Oak Wood Waste Training 49.50 5.70 41.30 0.20 0.00 19.228
Alder Training 47.82 5.80 46.25 0.11 0.02 18.299
Alfalfa Training 45.10 4.97 35.60 3.30 0.16 18.151
Alfalfa Seed Straw Training 46.76 5.40 40.72 1.00 0.02 18.450
Alfalfa Stalks Training 45.40 5.75 40.20 2.04 0.10 18.465
rec_trainedR$template%>%head%>%knitr::kable()
sample dataset carbon hydrogen oxygen nitrogen sulfur HHV
Akhrot Shell Training 0.1398746 0.1511606 0.4078940 -0.5516292 -0.4938064 20.008
Alabama Oak Wood Waste Training 0.1100906 0.2012910 0.2563886 -0.7259349 -0.4938064 19.228
Alder Training -0.0513192 0.2848415 0.7136763 -0.8006373 -0.4482289 18.299
Alfalfa Training -0.3126493 -0.4086283 -0.2701851 1.8471481 -0.1291863 18.151
Alfalfa Seed Straw Training -0.1531611 -0.0493608 0.2028075 -0.0619134 -0.4482289 18.450
Alfalfa Stalks Training -0.2838262 0.2430663 0.1547691 0.8013144 -0.2659188 18.465

5 Sơ chế nguyên liệu: hàm juice( )

Khi ta vừa muốn tạo ra 1 công thức, vừa muốn áp dụng chính công thức này cho trainset, ta phải đặt retain=T và sau đó có thể dùng hàm juice(), như vậy kết quả của hàm juice chính là một dataset mới sau hoán chuyển, và có nội dung giống y như tempate của công thức sau hàm prep() với tùy chỉnh retain=T

og_values <- juice(rec_trainedR, all_predictors())

og_values%>%head%>%knitr::kable()
carbon hydrogen oxygen nitrogen sulfur
0.1398746 0.1511606 0.4078940 -0.5516292 -0.4938064
0.1100906 0.2012910 0.2563886 -0.7259349 -0.4938064
-0.0513192 0.2848415 0.7136763 -0.8006373 -0.4482289
-0.3126493 -0.4086283 -0.2701851 1.8471481 -0.1291863
-0.1531611 -0.0493608 0.2028075 -0.0619134 -0.4482289
-0.2838262 0.2430663 0.1547691 0.8013144 -0.2659188

6 Áp dụng công thức: hàm bake( )

Cuối cùng, khi ta muốn áp dụng quy trình này cho một dataset mới, thí dụ testset, ta chỉ cần dùng hàm bake().Lưu ý rằng khi làm điều này, thực chất ta đang chuẩn hóa testset dựa vào mean và sd của trainset,điều này có nghĩa là ngay cả khi mô hình được tạo ra từ trainset cũng chưa hề “nhìn thấy” qua nội dung testset và do đó kết quả kiểm định mới đáng tin cậy.

test_values <- bake(rec_trained, newdata=bio_test,all_predictors())

test_values%>%head%>%knitr::kable()
carbon hydrogen oxygen nitrogen sulfur
-0.1925527 0.1762258 0.8014386 -0.6429322 0.0075463
-0.4903923 0.0341898 0.8808866 1.4736361 0.2810114
-0.5432348 0.0341898 0.9769632 1.1001240 0.1898564
-0.1877489 0.5354933 -0.1131368 0.6021080 0.6456315
0.0389935 0.7193046 0.3921892 -0.7259349 -0.4938064
-0.3895111 0.0341898 0.2933412 -0.3109215 -0.0380312

7 Kết luận

recipes là một package thú vị và hữu ích. Xuất phát từ ý tưởng “Công thức nấu ăn”, công cụ này đã tạo ra một cách thức hoàn toàn mới cho việc hoán chuyển và chuẩn bị dữ liệu trước khi dựng mô hình thống kê. Cách làm này đã khắc phục tất cả những trở ngại, khó khăn trong quá khứ như:

  1. Nhiều quy trình thông dụng đã được tích hợp sẵn trong recipes với danh pháp hàm dễ nhớ, cú pháp đơn giản; ta không cần phải tìm kiếm xa xôi và nhớ từng hàm một

  2. Có thể kết hợp nhiều quy trình với nhau một cách thứ bậc, thí dụ Imputation trước, sau đó chuẩn hóa, rồi hoán chuyển, làm PCA hay lọc biến số.

  3. Áp dụng quy trình cho hàng loạt biến trong dữ liệu,

  4. Xử lý trainset và testset độc lập.

Chúc các bạn “nấu ăn ngon” với package recipes

LS0tDQp0aXRsZTogIkdp4bubaSB0aGnhu4d1IHBhY2thZ2UgcmVjaXBlcyIgDQphdXRob3I6ICJMw6ogTmfhu41jIEto4bqjIE5oaSINCmRhdGU6ICIxMyBUaMOhbmcgNSAyMDE4Ig0Kb3V0cHV0Og0KICBodG1sX2RvY3VtZW50OiANCiAgICBjb2RlX2Rvd25sb2FkOiB0cnVlDQogICAgY29kZV9mb2xkaW5nOiBoaWRlDQogICAgbnVtYmVyX3NlY3Rpb25zOiB5ZXMNCiAgICB0aGVtZTogImRlZmF1bHQiDQogICAgdG9jOiBUUlVFDQogICAgdG9jX2Zsb2F0OiBUUlVFDQotLS0NCg0KYGBge3Igc2V0dXAsaW5jbHVkZT1GQUxTRX0NCmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvID0gVFJVRSkNCmxpYnJhcnkodGlkeXZlcnNlKQ0KYGBgDQoNCiFbXShyZWNpcGVzU29jaGVkdWxpZXUucG5nKQ0KDQpIw7RtIG5heSBOaGkgbXXhu5FuIGdp4bubaSB0aGnhu4d1IHbhu5tpIGPDoWMgYuG6oW4gbeG7mXQgY8O0bmcgY+G7pSBy4bqldCBoYXkgdHJvbmcgUiBsw6AgcmVjaXBlcy4gDQoNCiMgR2nhu5tpIHRoaeG7h3UgduG7gSAyIHTDoWMgZ2nhuqMgDQoNClBhY2thZ2UgcmVjaXBlcyBsw6Agc+G6o24gcGjhuqltIGPhu6dhIGhhaSB0w6FjIGdp4bqjOiBIYWRsZXkgV2lja2hhbSB2w6AgTWF4IEt1aG4uIERhbmggdGnhur9uZyBj4bunYSBoYWkgbmjDom4gduG6rXQgbsOgeSB0cm9uZyBj4buZbmcgxJHhu5NuZyBSIHVzZXJzIGNo4bqvYyBjw6FjIGLhuqFuIMSRw6MgYmnhur90IHLDtS4gVsOgIG7hur91IGLhuqFuIHThu6tuZyB0cuG6o2kgbmdoaeG7h20gbmjhu69uZyBwYWNrYWdlcyBraMOhYyBj4bunYSAyIGNhbyB0aOG7pyBuw6B5LCBi4bqhbiBjw7MgdGjhu4MgaMOsbmggZHVuZyB24buBIHPhu7EgdGnhu4duIGThu6VuZyB2w6Agw70gbmdoxKlhIHF1YW4gdHLhu41uZyBj4bunYSBwYWNrYWdlIHJlY2lwZXMgbcOgIE5oaSBz4bqvcCBnaeG7m2kgdGhp4buHdS4NCg0KVGjhu7FjIHbhuq15LCB04bqldCBj4bqjIG5o4buvbmcgY8O0bmcgY+G7pSBkbyBILiBXaWNraGFtIHbDoCBNYXggS3VobiB04bqhbyByYSBraMO0bmcgY2jhu4kgxJHGoW4gZ2nhuqNuIGzDoCBuaOG7r25nIOG7qW5nIGThu6VuZyBjw7MgbeG7pWMgdGnDqnUgaOG6oW4gaOG6uXAsIGNodXnDqm4gYmnhu4d0IG5o4bqvbSB2w6BvIG3hu5l0IHF1eSB0csOsbmgsIG3hu5l0IHRodeG6rXQgdG/DoW4gbsOgbyDEkcOzIG5oxrAgbmjhu69uZyBwYWNrYWdlIGtow6FjLCBuaMawbmcgxJHhurd0IHJhIG3hu5l0IG7hu4FuIHThuqNuZyBwaOG7lSBxdcOhdGjGoW4gcuG6pXQgbmhp4buBdS4gTmjhu69uZyBjw7RuZyBj4bulIG7DoHkgxJHDoyBsw6BtIHRoYXkgxJHhu5VpIGPDoWNoIHRo4bupYyBsw6BtIHZp4buHYyB0cm9uZyBSLCB04burIHF1eSB0csOsbmggY2hvIMSR4bq/biBwaG9uZyBjw6FjaCB2aeG6v3QgY29kZSwgdsOgIGLhuqNuIHRow6JuIGPhuqV1IHRyw7pjIG5n4buvIHBow6FwIGPhu6dhIFIgY29kZS4gVOG7qyBraGkgY8OzIGdncGxvdDIsIG5nxrDhu51pIHRhIHZp4bq/dCBjb2RlIMSR4buTIGjhu41hIGhvw6BuIHRvw6BuIGtow6FjIHbhu5tpIHRo4budaSBzxqEga2hhaSwgdOG7qyBraGkgY8OzIHRpZHl2ZXJzZSwgZHBseXIgdsOgIHBpcGVzIHRow6wgcGhvbmcgY8OhY2ggUiBjb2RlcyB0aGF5IMSR4buVaSBob8OgbiB0b8OgbiBzbyB24bubaSBxdcOhIGto4bupLiBUxrDGoW5nIHThu7EsIHBhY2thZ2UgY2FyZXQgY+G7p2EgTWF4IEt1aG4gxJHDoyBwaOG7lSBiaeG6v24gTWFjaGluZSBsZWFybmluZyDEkeG6v24gbeG7jWkgbmfGsOG7nWksIGjhuqd1IG5oxrAgdOG6pXQgY+G6oyBSIHVzZXJzIGLhuq90IMSR4bqndSBo4buNYyB24buBIE1hY2hpbmUgTGVhcm5pbmcgcXVhIGNhcmV0IHRyxrDhu5tjIGtoaSBo4buNIGPDsyB0aOG7gyB04buxIGNvZGUgbmjhu69uZyBhbGdvcml0aG0gcGjhu6ljIHThuqFwIGtow6FjLg0KDQpLaGkgV2lja2hhbSB2w6AgTWF4IEt1aG4gY8O5bmcgbmfhu5NpIGzhuqFpIHbhu5tpIG5oYXUgdsOgIHThuqFvIHJhIDEgcGFja2FnZSBuaMawIHJlY2lwZXMsIGPDoWMgYuG6oW4gY8OzIHRo4buDIGThu7EgxJFvw6FuOiDEkMOieSBz4bq9IGzDoCBt4buZdCBnaWFvIHRo4bupYyBwaOG7lSBxdcOhdCwgbeG7mXQgaOG7hyBuZ8O0biBuZ+G7ryBt4bubaSBu4bqxbSB0cm9uZyBuZ8O0biBuZ+G7ryBSLHbDoCBuw7Mgc+G6vSBsw6BtIHRoYXkgxJHhu5VpIGPDoWNoIGzDoG0gdmnhu4djIGPhu6dhIGLhuqFuLg0KDQojIMOdIHTGsOG7n25nIHbhu4EgbuG6pXUgxINuDQoNClRyb25nIHF1w6EgdHLDrG5oIHRo4buxYyBow6BuaCB0aOG7kW5nIGvDqiwgTmhpIG5o4bqtbiByYSBz4buxIHTGsMahbmcgxJHhu5NuZyB0aMO6IHbhu4sgZ2nhu69hIHZp4buHYyBwaMOibiB0w61jaCBk4buvIGxp4buHdSB2w6AgbuG6pXUgxINuLCB2w6Agbmhp4buBdSBs4bqnbiBOaGkgxJHDoyBz4butIGThu6VuZyB2aeG7h2MgbuG6pXUgxINuIMSR4buDIG1pbmggaOG7jWEgY2hvIGPDoWMgw70gdMaw4bufbmcgduG7gSB0aOG7kW5nIGvDqi4gw50gdMaw4bufbmcgbsOgeSBjaMOtbmggbMOgIG5ndXnDqm4gdOG6r2MgY+G7p2EgcGFja2FnZSByZWNpcGVzOiBjw6FjIGjDoG0gcXVhbiB0cuG7jW5nIGPhu6dhIHBhY2thZ2UgbsOgeSBuaOG6sW0gbcO0IHBo4buPbmcgbeG7mXQgcXV5IHRyw6xuaCBjaOG6vyBiaeG6v24gbcOzbiDEg24sIGPhu6UgdGjhu4MgbMOgIGzDoG0gYsOhbmg6IGfhu5NtIGjDoG0gcmVjaXBlKCksIHByZXAoICksIGp1aWNlKCApIHbDoCBiYWtlKCApDQoNClRyxrDhu5tjIGtoaSDEkWkgdsOgbyBjaGkgdGnhur90LCB0YSBz4bq9IHTDs20gdOG6r3QgbOG6oWkgcXV5IHRyw6xuaCBu4bqldSDEg24vdGjhu5FuZyBrw6o6DQoNCkPDuW5nIG3hu5l0IGThu68gbGnhu4d1IGfhu5FjLCB0YSBjw7MgdGjhu4MgZOG7sW5nIG5oaeG7gXUgbcO0IGjDrG5oIHRo4buRbmcga8OqIGtow6FjIG5oYXUgLSBnaeG7kW5nIG5oxrAgdOG7qyBuaOG7r25nIG5ndXnDqm4gbGnhu4d1IHRyb25nIHThu7Egbmhpw6puIG5oxrAgdGjhu4t0LCBjw6EsIHJhdSwgY+G7pywgZ2lhIHbhu4suLi4gbmhp4buBdSBtw7NuIMSDbiBraMOhYyBuaGF1IGPDsyB0aOG7gyDEkcaw4bujYyB04bqhbyB0aMOgbmguDQoNCk3hu5dpIG3DtCBow6xuaCB0aOG7kW5nIGvDqiBjw7Mgbmjhu69uZyB5w6p1IGPhuqd1IGtow6FjIG5oYXUgduG7gSBj4bqldSB0csO6YywgbuG7mWkgZHVuZyBk4buvIGxp4buHdSDEkeG6p3UgdsOgbywgZG8gxJHDsyBk4buvIGxp4buHdSBn4buRYyBwaOG6o2kgdHLhuqNpIHF1YSBjw6FjIGLGsOG7m2MgeOG7rSBsw70gY2h1ecOqbiBiaeG7h3QgdMO5eSB0aGVvIG3hu5dpIGxv4bqhaSBtw7QgaMOsbmgsIGJhbyBn4buTbSBjaOG7jW4gbOG7jWMgYmnhur9uLCBob8OhbiBjaHV54buDbix04bqhbyBiaeG6v24gbeG7m2ksIGdp4bqjbSBjaGnhu4F1IHRow7RuZyB0aW4sIC4uLiB0xrDGoW5nIHThu7EgbmjGsCBt4buXaSBtw7NuIMSDbiDEkcOyaSBo4buPaSBuZ3V5w6puIGxp4buHdSBwaOG6o2kgxJHGsOG7o2Mgc8ahIGNo4bq/IHBow7kgaOG7o3AgdsOgIHRow6ptIGLhu5t0IGdpYSB24buLLiBUaMOtIGThu6UgdOG7qyBjw6EgaOG7k2ksIGPDsyB0aOG7gyBsw6BtIG3Ds24gbsaw4bubbmcsIGtobyBoYXkgxINuIHPhu5FuZyBuaMawIHN1c2hpLiAgDQoNClF1eSB0csOsbmggY2h14bqpbiBi4buLLCBzxqEgY2jhur8sIHThuqltIMaw4bubcCBuZ3V5w6puIGxp4buHdXRy4bufIHRow6BuaCBt4buZdCBiw60gcXV54bq/dCBjaG8gbeG7l2kgbcOzbiDEg24sIHbDoCDEkcaw4bujYyDEkcO6YyBr4bq/dCBs4bqhaSB0cm9uZyBzw6FjaCB24bufIHRow6BuaCBuaOG7r25nICJjw7RuZyB0aOG7qWMgbuG6pXUgbsaw4bubbmciIChyZWNpcGVzKS4gTeG7mXQgYsOgIG7hu5lpIHRy4bujIHNhdSBraGkgxINuIFN1c2hpIHThuqFpIG5ow6AgaMOgbmcgdGjGsOG7nW5nIGPDsyDDvSDEkeG7i25oIHThu7EgdGjhu7FjIGhp4buHbiBtw7NuIMSDbiBuw6B5IHThuqFpIG5ow6AsIMSRaeG7gXUgxJHhuqd1IHRpw6puIGPDtCB0YSBz4bq9IGzDoG0gbMOgIG11YSBt4buZdCBxdXnhu4NuIHPDoWNoIGjGsOG7m25nIGThuqtuIGzDoG0gc3VzaGkgaGF5IHTDrG0gY8O0bmcgdGjhu6ljIHRyw6puIG3huqFuZy4gTMO6YyBuw6B5LCDDvSB0xrDhu59uZyBjaOG7iSBu4bqxbSB0csOqbiBnaeG6pXksYmFvIGfhu5NtIG3DtCB04bqjIHbhu4EgYmFvIG5oacOqdSBuZ3V5w6puIGxp4buHdSBj4bqnbiBjaHXhuqluIGLhu4ssY8OhY2ggcuG7rWEsIGPhuq90IHjDqW4sc8ahIGNo4bq/LCDGsOG7m3AgZ2lhIHbhu4suLi4gDQoNClTGsMahbmcgdOG7sSwgcXV5IHRyw6xuaCBjaHXhuqluIGLhu4sgZOG7ryBsaeG7h3UgY2hvIG3DtCBow6xuaCB0aOG7kW5nIGvDqiB0aMaw4budbmcgxJHGsOG7o2MgZ2nhuqNuZyBk4bqheSB0cm9uZyB0csaw4budbmcgbOG7m3AgaGF5IHPDoWNoIHbhu58gdsOgIGdoaSB0cm9uZyB0csOtIG5o4bubIGPhu6dhIG5naGnDqm4gY+G7qXUgc2luaCBkxrDhu5tpIGThuqFuZyBuaOG7r25nIGPDtG5nIHRo4bupYy4gVsOgbyBsw7pjIG7DoHksIHRo4bupIG3DoCBo4buNIGPDsyBjaOG7iSBsw6Agw70gdMaw4bufbmcgdsOgIGNo4buJIGThuqtuLCB24bubaSBjw6FjIG5ndXnDqm4gdOG6r2MsY8O0bmcgdGjhu6ljIHRvw6FuIHbDoCB0aOG7qSB04buxIHRo4buxYyBoaeG7h24sIG5oxrBuZyBjaMawYSB0aOG7sWMgc+G7sSDEkcaw4bujYyB0aGkgaMOgbmggdHLDqm4gYuG6pXQgY+G7qSBk4buvIGxp4buHdSBuw6BvLiANCg0KVHLGsOG7m2Mga2lhLCB2aeG7h2MgY2h1eeG7g24gdOG7qyBsw70gdGh1eeG6v3Qgc2FuZyB0aGkgaMOgbmggbmjhu69uZyDDvSB0xrDhu59uZywgcXV5IHRyw6xuaCwgY8O0bmcgdGjhu6ljIHRyw6puIFIgY29kZXMgdGjGsOG7nW5nIHLhuqV0IHRo4bunIGPDtG5nIHbDoCB04bunbiBt4bunbiwgbWFuaCBtw7puLiBUaMOtIGThu6UgbeG7mXQgc2luaCB2acOqbiBjw7MgdGjhu4MgaGnhu4N1IHLDtSBi4bqjbiBjaOG6pXQgY+G7p2EgQm94LUNveCB0cmFuc2Zvcm1hdGlvbiBsw6AgZ8OsLCBoYXkgY2h14bqpbiBow7NhIGzDoCBnw6wsIG5oxrBuZyBraMO0bmcgYmnhur90IHBo4bqjaSB2aeG6v3QgUiBjb2RlIG5oxrAgdGjhur8gbsOgby4gVGEgdGjGsOG7nW5nIHBo4bqjaSB0w6xtIHRyw6puIG3huqFuZyBuaOG7r25nIFIgY29kZXMgcuG6o2kgcsOhYyBjaG8gbeG7l2kgcXV5IHRyw6xuaCwgdsOgIG3hu5dpIGzhuqduIG5oxrAgduG6rXkgbOG6oWkgY8OzIG3hu5l0IGjDoG0gbeG7m2kgbOG6oSwgduG7m2kgY8O6IHBow6FwIGtow6FjIG5oYXUsIHTDqm4gZ+G7jWkgdGjGsOG7nW5nIGNo4bqzbmcgxINuIG5o4bqtcCBnw6wgxJHhur9uIG3hu6VjIHRpw6p1LiBDw5Mga2hpIG3hu5l0IHF1eSB0csOsbmggcGjhuqNpIHF1YSB0cnVuZyBnaWFuIG5oaeG7gXUgaMOgbSBraMOhYyBuaGF1LCBj4bunYSBuaGnhu4F1IHBhY2thZ2Uga2jDoWMgbmhhdS4uLg0KDQpDaMawYSBo4bq/dCwga2jDsyBraMSDbiBjw7JuIOG7nyBjaOG7lyDEkWEgc+G7kSBjw6FjIGjDoG0gdHJvbmcgUiBjaOG7iSB0aGFvIHTDoWMgdHLDqm4gdOG7q25nIHZlY3RvciDEkcahbiBs4bq7LiBLaGkgdGEgbXXhu5FuIHRo4buxYyBoaeG7h24gaMOgbmcgbG/huqF0IG3hu5l0IHF1eSB0csOsbmggY2hvIHRvw6BuIGLhu5kgZOG7ryBsaeG7h3UsIHRhIGzhuqFpIHBo4bqjaSBo4buNYyB0aMOqbSBjw6FjIGjDoG0gbmjGsCBhcHBseSwgbWFwIGhv4bq3YyBwaOG6o2kgdmnhur90IGNvZGUgY2hvIHbDsm5nIGzhurdwLi4uDQoNCk3hu5l0IHbhuqVuIMSR4buBIHThur8gbmjhu4sga2jDoWMgbOG6oWkgbuG6o3kgc2luaCBraGkgdGjhu6kgZ+G7jWkgbMOgIE1hY2hpbmUgbGVhcm5pbmcgYuG6r3QgxJHhuqd1IHBo4buVIGJp4bq/biBuaMawIG3hu5l0IGPDoWNoIHN1eSBkaeG7hW4gdGjhu5FuZyBrw6ogbeG7m2kuIFRoZW8gdHLGsOG7nW5nIHBow6FpIG7DoHksIGNow7puZyB0YSBraMO0bmcgY2jhu4kgbMOgbSB2aeG7h2MgdHLDqm4gMSB04bqtcCBk4buvIGxp4buHdSBuZ3V5w6puIHRo4buneSBkdXkgbmjhuqV0LCBtw6AgdGjGsOG7nW5nIGPhuq90IGThu68gbGnhu4d1IHRow6BuaCAyIHBo4bqnbiBsw6AgdHJhaW5zZXQgdsOgIFRlc3RzZXQuIE5o4buvbmcgcXV5IHRyw6xuaCB0csaw4bubYyBraWEgdGEgY2jhu4kgY+G6p24gbMOgbSAxIGzhuqduLCBuYXkgdGEgcGjhuqNpIGzDoG0gxJHhur9uIDIgbOG6p24gdHLDqm4gbeG7l2kgdOG6rXAgZOG7ryBsaeG7h3UgdHJhaW4gdsOgIHRlc3QuIENoxrBhIGjhur90LCDEkWnhu4NtIHBo4bupYyB04bqhcCBu4bqxbSDhu58gY2jhu5c6IHRhIGtow7RuZyDEkcaw4bujYyBuaMOsbiB2w6BvIGLDqm4gdHJvbmcgdGVzdHNldCDEkeG7gyBnaeG7ryB0w61uaCDEkeG7mWMgbOG6rXAgdHV54buHdCDEkeG7kWkgY+G7p2EgbsOzLCBkbyDEkcOzIGPDoWMgdGhhbSBz4buRIHRyb25nIHF1eSB0csOsbmggaG/DoW4gY2h1eeG7g24gZOG7ryBsaeG7h3UgKHRow60gZOG7pSBNZWFuIHRyb25nIGNlbnRlcmluZywgc2QgdHJvbmcgaMOgbSBzY2FsZSwgTGFtYmRhIHRyb25nIEJveC1Db3gsIGNlbnRyb2lkIHRyb25nIGtubiBJbXB1dGF0aW9uLCBQQ0EpIMSR4buBdSBk4buxYSB2w6BvIHRyYWluc2V0LCBuZ2F5IGPhuqMga2hpIMOhcCBk4bulbmcgdHLDqm4gdGVzdHNldC4gTmjGsCB24bqteSB2aeG7h2Mgc8ahIGNo4bq/IGThu68gbGnhu4d1IHRy4bufIHRow6BuaCBt4buZdCBNw7QgaMOsbmggaGF5IDEgSMOgbSwgxJHGsOG7o2MgeMOhYyDEkeG7i25oIHRyw6puIHRyYWluc2V0IHLhu5NpIMOhcCBk4bulbmcgdHLDqm4gY2jDrW5oIHRyYWluc2V0IGhheSBk4buvIGxp4buHdSBt4bubaSBsw6AgdGVzdHNldC4NCg0KxJBp4buBdSBuw6B5IHTGsMahbmcgdOG7sSBuaMawIGPDtG5nIHRo4bupYyBsw6BtIG3Ds24gTWFraSBzdXNoaSwgYsOgIG7hu5lpIHRy4bujIHNhdSBraGkgdGjhu7FjIGjDoG5oIGPDtG5nIHRo4bupYyBo4buNYyDEkcaw4bujYyB04burIHPDoWNoIHRyw6puIG5ndXnDqm4gbGnhu4d1IGzDoCBNYWtpLCBiw6AgdGEgc+G6vSByw7p0IHJhIMSRxrDhu6NjIGtpbmggbmdoaeG7h20gY+G7p2EgY2jDrW5oIGLhuqNuIHRow6JuLCB04burIMSRw7MgY+G6o2kgYmnDqm4gcmEgbeG7mXQgYsOtIHF1eeG6v3QsIGPDtG5nIHRo4bupYyBj4bunYSByacOqbmcgbcOsbmguIFNhdSDEkcOzIGLDoCB0YSBjw7MgdGjhu4Mgw6FwIGThu6VuZyBjw7RuZyB0aOG7qWMgbsOgeSBjaG8gbeG7mXQgbmd1ecOqbiBsaeG7h3UgbeG7m2kgbMOgIGPDoSBOZ+G7qyB2w6AgdOG6oW8gcmEgbcOzbiDEg24gbeG7m2kgbMOgIE1ha2kgY8OhIG5n4burLg0KDQohW10obWFraXJlY2lwZS5wbmcpDQoNCiMgVmnhur90IGPDtG5nIHRo4bupYyBu4bqldSDEg246IGjDoG0gcmVjaXBlKCApDQoNClNhdSBraGkgdOG6o2kgcGFja2FnZSByZWNpcGVzLCB2aeG7h2MgxJHhuqd1IHRpw6puIHRhIGPDsyB0aOG7gyBsw6BtIGzDoCB04bqhbyByYSAxIGPDtG5nIHRo4bupYywgaMOgbSByZWNpcGUoKQ0KDQpNdeG7kW4gdOG6oW8gMSBjw7RuZyB0aOG7qWMsIMSR4bqndSB0acOqbiBwaOG6o2kgZOG7sWEgdsOgbyAxIG9iamVjdCBk4buvIGxp4buHdS4gTmhpIHPhur0gZMO5bmcgY2jDrW5oIGRhdGFzZXQgYmlvbWFzcyB0cm9uZyBwYWNrYWdlIHJlY2lwZXM6IA0KDQpgYGB7cixtZXNzYWdlID0gRkFMU0Usd2FybmluZz1GQUxTRX0NCmxpYnJhcnkocmVjaXBlcykNCg0KYmlvX3RyYWluPC0gYmlvbWFzc1tiaW9tYXNzJGRhdGFzZXQgPT0gIlRyYWluaW5nIixdDQpiaW9fdGVzdCA8LSBiaW9tYXNzW2Jpb21hc3MkZGF0YXNldCA9PSAiVGVzdGluZyIsXQ0KDQpiaW9fdHJhaW4lPiVoZWFkKCklPiVrbml0cjo6a2FibGUoKQ0KYGBgDQoNCsSQxrBhIG9iamVjdCBiaW9fdHJhaW4gdsOgbyBow6BtIHJlY2lwZSwgdGEgY8OzIDEgY8O0bmcgdGjhu6ljIHPGoSBraGFpIHTDqm4gbMOgIHJlYy4gRMO5bmcgaMOgbSBzdW1tYXJ5LCB0YSBjw7MgdGjhu4MgeGVtIGPhuqV1IHRyw7pjIGThu68gbGnhu4d1IHRyb25nIGPDtG5nIHRo4bupYzogMiBj4buZdCB2YXJpYWJsZSB2w6AgdHlwZSBk4buFIGhp4buDdTogY2jDrW5oIGzDoCB0w6puIGJp4bq/biB2w6AgxJHhu4tuaCBk4bqhbmcuIFRhIGNow7ogw70gxJHhur9uIGPhu5l0IHJvbGU6IMSRw6J5IGzDoCBt4buZdCB0aHXhu5ljIHTDrW5oIGThu68gbGnhu4d1IGNobyBwaMOpcCB0YSBxdXkgxJHhu4tuaCB2YWkgdHLDsiBj4bunYSB04burbmcgYmnhur9uIHRyb25nIGPDtG5nIHRo4bupYy4gxJBp4buBdSBuw6B5IHF1YW4gdHLhu41uZyB2w6wgc2F1IG7DoHkgY8OzIG3hu5l0IHPhu5EgY2FuIHRoaeG7h3AgY2jhu4kgxJHGsOG7o2MgbMOgbSB0csOqbiBwcmVkaWN0b3JzLCBob+G6t2Mgb3V0Y29tZSwgY8WpbmcgbmjGsCBt4buZdCBz4buRIGJp4bq/biBraMO0bmcgY8OzIHZhaSB0csOyIG7DoG8gY+G6oyB0cm9uZyBtw7QgaMOsbmguDQoNCmBgYHtyLG1lc3NhZ2UgPSBGQUxTRSx3YXJuaW5nPUZBTFNFfQ0KcmVjPC1yZWNpcGUoYmlvX3RyYWluKQ0KDQpzdW1tYXJ5KHJlYykNCmBgYA0KDQrEkOG7gyDEkeG6t3QgdmFpIHRyw7IgY2hvIG3hu5dpIGJp4bq/biwgY8OzIGhhaSBjw6FjaDogaG/hurdjIHRhIGTDuW5nIGPDtG5nIHRo4bupYywgaG/hurdjIHRhIGtoYWkgYsOhbyB0aOG7pyBjw7RuZyBi4bqxbmcgaMOgbSBhZGRfcm9sZS4NCg0KVGjDrSBk4bulIGjDoG0gYWRkX3JvbGUgY2hvIHBow6lwIMSR4buLbmggdmFpIHRyw7IgY2hvIHThu6tuZyBiaeG6v24gbmjGsCBzYXU6DQoNCmBgYHtyLG1lc3NhZ2UgPSBGQUxTRSx3YXJuaW5nPUZBTFNFfQ0KcmVjMjwtcmVjJT4lDQogIGFkZF9yb2xlKEhIVixuZXdfcm9sZSA9ICJvdXRjb21lIikgJT4lDQogIGFkZF9yb2xlKHNhbXBsZSwgbmV3X3JvbGU9ImlkIHZhcmlhYmxlIiklPiUNCiAgYWRkX3JvbGUoZGF0YXNldCxuZXdfcm9sZSA9ICJzcGxpdHRpbmcgdmFyaWFibGUiKSU+JQ0KICBhZGRfcm9sZShjYXJib24sY29udGFpbnMoImdlbiIpLHN1bGZ1cixuZXdfcm9sZT0icHJlZGljdG9yIikNCg0Kc3VtbWFyeShyZWMyKQ0KYGBgDQoNCk7hur91IGTDuW5nIGPDtG5nIHRo4bupYywgY8O6IHBow6FwIGPDsyBk4bqhbmc6IE91dGNvbWUgfiBQcmVkaWN0b3IgKyBQcmVkaWN0b3IgMi4uLiAsIHRo4bupIGfDrCDEkeG7qW5nIHRyxrDhu5tjIGThuqV1IH4gbMOgIE91dGNvbWUsIMSR4bupbmcgc2F1IGzDoCBwcmVkaWN0b3JzLiBUaMOtIGThu6U6DQoNCmBgYHtyLG1lc3NhZ2UgPSBGQUxTRSx3YXJuaW5nPUZBTFNFfQ0KcmVjMzwtcmVjaXBlKGJpb190cmFpbiwgb3h5Z2VuK25pdHJvZ2VufmNhcmJvbitoeWRyb2dlbikNCg0Kc3VtbWFyeShyZWMzKQ0KYGBgDQoNClRhIGPDsyB0aOG7gyBj4bqtcCBuaOG6rXQgY8O0bmcgdGjhu6ljIGLhurFuZyBow6BtIGFkZF9yb2xlKCkNCg0KYGBge3IsbWVzc2FnZSA9IEZBTFNFLHdhcm5pbmc9RkFMU0V9DQpyZWM0PC1yZWNpcGUoSEhWfi4sYmlvX3RyYWluKSU+JQ0KICAgIGFkZF9yb2xlKHNhbXBsZSxuZXdfcm9sZT0iaWQgdmFyaWFibGUiKSU+JQ0KICBhZGRfcm9sZShkYXRhc2V0LG5ld19yb2xlPSJzcGxpdHRpbmcgdmFyaWFibGUiKQ0KDQpzdW1tYXJ5KHJlYzQpDQpgYGANCg0KQsOieSBnaeG7nSB0YSBraOG6o28gc8OhdCBzw6J1IGjGoW4gY+G6pXUgdHLDumMgY+G7p2EgMSBjw7RuZyB0aOG7qWM7IG7DsyBjaMOtbmggbMOgIDEgbGlzdCwgZ+G7k20gbmhp4buBdSBu4buZaSBkdW5nLiBE4buvIGxp4buHdSBn4buRYyDEkcaw4bujYyBsxrB1IHRyb25nIGPDtG5nIHRo4bupYyDhu58gbuG7mWkgZHVuZyB0ZW1wbGF0ZToNCg0KDQpgYGB7cixtZXNzYWdlID0gRkFMU0Usd2FybmluZz1GQUxTRX0NCnJlYzIkdGVtcGxhdGUlPiVoZWFkKCklPiVrbml0cjo6a2FibGUoKQ0KYGBgDQoNCkPDtG5nIHRo4bupYyBuw6B5IGNoxrBhIGPDsyAxIHF1eSB0csOsbmggbsOgbyBj4bqjLCBuw6puIG7hu5lpIGR1bmcgc3RlcHMgbMOgIHLhu5duZw0KDQpgYGB7cixtZXNzYWdlID0gRkFMU0Usd2FybmluZz1GQUxTRX0NCnJlYzIkc3RlcHMNCmBgYA0KDQpQYWNrYWdlIHJlY2lwZXMgY3VuZyBj4bqlcCDEkeG6v24gNTQgcXV5IHRyw6xuaCBjaHXhuqluIGLhu4ssIGhvw6FuIGNodXnhu4NuIGThu68gbGnhu4d1IHRow7RuZyBk4bulbmcgdHJvbmcgcGjDom4gdMOtY2ggaOG7k2kgcXV5IHbDoCBNYWNoaW5lIGxlYXJuaW5nLCB04burIG5o4buvbmcgdGjhu6kgxJHGoW4gZ2nhuqNuIG5o4bqldCBuaMawIGPhuq90IGJp4bq/biDEkeG7i25oIGzGsOG7o25nIHRow6BuaCBuaGnhu4F1IHBo4bqnbiwgY2h14bqpbiBow7NhLCBob8OhbiBjaHV54buDbiBsb2dhcml0LCBCb3gtQ294LCBZZW8tSm9obnNob24uLi4gY2hvIMSR4bq/biBuaOG7r25nIGdp4bqjaSB0aHXhuq10IGltcHV0YXRpb24gbmjGsCBrbm4sIGJhZ2dpbmcsIGhheSBjw6FjIHRo4bunIHRodeG6rXQgZ2nhuqNtIGNoaeG7gXUgZOG7ryBsaeG7h3UgbmjGsCBQQ0EuIMSQaeG7g20gdGjDuiB24buLIG5o4bqldCBsw6AgdOG6pXQgY+G6oyBuaOG7r25nIHF1eSB0csOsbmggbsOgeSDEkeG7gXUgY8OzIGNodW5nIGRhbmggcGjDoXAgZOG6oW5nIHN0ZXBfdMOqbiBxdXkgdHLDrG5oIHbDoCBjw7ogcGjDoXAgxJHhu5NuZyBuaOG6pXQgYmFvIGfhu5NtIDogxJHhu5FpIHTGsOG7o25nIHJlY2lwZSBj4bqnbiBj4bqtcCBuaOG6rXQsIGPDoWMgdMO5eSBjaOG7iW5oIHTDuXkgdGhlbyBxdXkgdHLDrG5oIGThuqFuZyBvcHRpb24obGlzdCksIGhv4bq3YyB0w7l5IGNo4buJbmggduG7gSBzYW8gbMawdSBk4buvIGxp4buHdSwgYuG7jyBxdWEsIGxv4bqhaSBi4buPIGdpw6EgdHLhu4sgTkEuLi4gDQoNClRow60gZOG7pTogS+G6v3QgaOG7o3AgcXV5IHRyw6xuaCBjZW50ZXJpbmcgdsOgIHNjYWxlIHPhur0gY2h14bqpbiBow7NhIHRvw6BuIGLhu5kgcHJlZGljdG9yIHRyb25nIGPDtG5nIHRo4bupYzoNCg0KYGBge3IsbWVzc2FnZSA9IEZBTFNFLHdhcm5pbmc9RkFMU0V9DQpyZWMyJTw+JQ0KICBzdGVwX2NlbnRlcihhbGxfcHJlZGljdG9ycygpKSAlPiUNCiAgc3RlcF9zY2FsZShhbGxfcHJlZGljdG9ycygpKQ0KDQpyZWMyDQpgYGANCg0KIyBUaOG7sWMgaGnhu4duIGPDtG5nIHRo4bupYyBuw6B5OiBow6BtIHByZXAoICkNCg0KTMawdSDDvSBsw6Aga2hpIHF1eSB0csOsbmggbeG7m2kgxJHGsOG7o2Mga2hhaSBiw6FvIHRyb25nIGPDtG5nIHRo4bupYywgbsOzIGNoxrBhIHRo4buxYyBz4buxIGPDsyBjw7RuZyBoaeG7h3UsIHF1eSB0csOsbmggY+G6p24gxJHGsOG7o2MgaHXhuqVuIGx1eeG7h24gYuG6sW5nIGjDoG0gcHJlcDoNCg0KUXXDoSB0csOsbmggaHXhuqVuIGx1eeG7h24gbsOgeSBjxaluZyBnaeG7kW5nIG5oxrAgbmfGsOG7nWkgbuG7mWkgdHLhu6MgdGjhu7FjIGjDoG5oIGPDtG5nIHRo4bupYyBu4bqldSDEg24gdHLDqm4gbmd1ecOqbiBsaeG7h3UgdGjhu7FjIHThur8gKHRyYWluc2V0KSwgc2F1IGtoaSB0aOG7sWMgaMOgbmggY8O0bmcgdGjhu6ljIGzDvSB0aHV54bq/dCBt4bubaSB0cuG7nyB0aMOgbmggYsOtIHF1eeG6v3QgbuG6pXUgxINuIGPhu6dhIGLhuqNuIHRow6JuIG5nxrDhu51pIMSRw7MuIMSQaeG7gXUgbsOgeSBxdWFuIHRy4buNbmcgdsOsIG5oaeG7gXUgcXV5IHRyw6xuaCBob8OhbiBjaHV54buDbiwgYuG7lSB0w7pjIGThu68gbGnhu4d1IGNo4buJIG3hu5tpIGzDoCBuaOG7r25nIGjDoG0gcuG7l25nLCBjaMawYSBjw7MgdGhhbSBz4buRIGLDqm4gdHJvbmcuIE5o4buvbmcgdGhhbSBz4buRIG7DoHkgY2jhu4kgxJHGsOG7o2MgaMOsbmggdGjDoG5oIHNhdSBraGkgcXV5IHRyw6xuaCDEkcaw4bujYyBodeG6pW4gbHV54buHbiB0csOqbiB0cmFpbnNldCwga2hpIMSRw7MgY2jDum5nIG3hu5tpIHRo4buxYyBz4buxIGPDsyBraOG6oyBuxINuZyBsw6BtIHZp4buHYy4NCg0KVGjDrSBk4bulIHRhIGTDuW5nIGjDoG0gcHJlcCDEkeG7gyBrw61jaCBob+G6oXQgbeG7mXQgcXV5IHRyw6xuaCBjaHXhuqluIGjDs2EgdsOgIHRo4buxYyBz4buxIHThuqFvIHJhIDEgY8O0bmcgdGjhu6ljIGhheSBow6BtICJjaHXhuqluIGjDs2EgZOG7ryBsaeG7h3UiIDoNCg0KTMawdSDDvSB0w7l5IGNo4buJbmggcmV0YWluOiBu4bq/dSBi4bqhbiBtdeG7kW4gZMO5bmcga+G6v3QgcXXhuqMgaHXhuqVuIGx1eeG7h24gbsOgeSBjaG8gY2jDrW5oIHThuq1wIHRyYWluLCB0aGF5IHRo4bq/IHRyYWluc2V0IGLhurFuZyBk4buvIGxp4buHdSBt4bubaSBzYXUgaG/DoW4gY2h1eeG7g24sIGLhuqFuIHPhur0gxJHhurd0IHJldGFpbj1ULCBsw7pjIG7DoHkgdGVtcGxhdGUgZOG7ryBsaeG7h3UgdHJvbmcgY8O0bmcgdGjhu6ljIG3hu5tpIHPhur0gYuG7iyB0aGF5IHRo4bq/IGLhurFuZyBr4bq/dCBxdeG6oyBzYXUgaG/DoW4gY2h1eeG7g24NCg0KYGBge3IsbWVzc2FnZSA9IEZBTFNFLHdhcm5pbmc9RkFMU0V9DQoNCnJlY190cmFpbmVkUjwtcHJlcChyZWMyLHRyYWluaW5nPWJpb190cmFpbixyZXRhaW49VCkNCg0KcmVjX3RyYWluZWQ8LXByZXAocmVjMix0cmFpbmluZz1iaW9fdHJhaW4scmV0YWluPUYpDQoNCg0KcmVjX3RyYWluZWQkdGVtcGxhdGUlPiVoZWFkJT4la25pdHI6OmthYmxlKCkNCg0KcmVjX3RyYWluZWRSJHRlbXBsYXRlJT4laGVhZCU+JWtuaXRyOjprYWJsZSgpDQoNCmBgYA0KDQojIFPGoSBjaOG6vyBuZ3V5w6puIGxp4buHdTogaMOgbSBqdWljZSggKQ0KDQpLaGkgdGEgduG7q2EgbXXhu5FuIHThuqFvIHJhIDEgY8O0bmcgdGjhu6ljLCB24burYSBtdeG7kW4gw6FwIGThu6VuZyBjaMOtbmggY8O0bmcgdGjhu6ljIG7DoHkgY2hvIHRyYWluc2V0LCB0YSBwaOG6o2kgxJHhurd0IHJldGFpbj1UIHbDoCBzYXUgxJHDsyBjw7MgdGjhu4MgZMO5bmcgaMOgbSBqdWljZSgpLCBuaMawIHbhuq15IGvhur90IHF14bqjIGPhu6dhIGjDoG0ganVpY2UgY2jDrW5oIGzDoCBt4buZdCBkYXRhc2V0IG3hu5tpIHNhdSBob8OhbiBjaHV54buDbiwgdsOgIGPDsyBu4buZaSBkdW5nIGdp4buRbmcgeSBuaMawIHRlbXBhdGUgY+G7p2EgY8O0bmcgdGjhu6ljIHNhdSBow6BtIHByZXAoKSB24bubaSB0w7l5IGNo4buJbmggcmV0YWluPVQNCg0KYGBge3IsbWVzc2FnZSA9IEZBTFNFLHdhcm5pbmc9RkFMU0V9DQpvZ192YWx1ZXMgPC0ganVpY2UocmVjX3RyYWluZWRSLCBhbGxfcHJlZGljdG9ycygpKQ0KDQpvZ192YWx1ZXMlPiVoZWFkJT4la25pdHI6OmthYmxlKCkNCmBgYA0KDQojIMOBcCBk4bulbmcgY8O0bmcgdGjhu6ljOiBow6BtIGJha2UoICkNCg0KQ3Xhu5FpIGPDuW5nLCBraGkgdGEgbXXhu5FuIMOhcCBk4bulbmcgcXV5IHRyw6xuaCBuw6B5IGNobyBt4buZdCBkYXRhc2V0IG3hu5tpLCB0aMOtIGThu6UgdGVzdHNldCwgdGEgY2jhu4kgY+G6p24gZMO5bmcgaMOgbSBiYWtlKCkuTMawdSDDvSBy4bqxbmcga2hpIGzDoG0gxJFp4buBdSBuw6B5LCB0aOG7sWMgY2jhuqV0IHRhIMSRYW5nIGNodeG6qW4gaMOzYSB0ZXN0c2V0IGThu7FhIHbDoG8gbWVhbiB2w6Agc2QgY+G7p2EgdHJhaW5zZXQsxJFp4buBdSBuw6B5IGPDsyBuZ2jEqWEgbMOgIG5nYXkgY+G6oyBraGkgbcO0IGjDrG5oIMSRxrDhu6NjIHThuqFvIHJhIHThu6sgdHJhaW5zZXQgY8WpbmcgY2jGsGEgaOG7gSAibmjDrG4gdGjhuqV5IiBxdWEgbuG7mWkgZHVuZyB0ZXN0c2V0IHbDoCBkbyDEkcOzIGvhur90IHF14bqjIGtp4buDbSDEkeG7i25oIG3hu5tpIMSRw6FuZyB0aW4gY+G6rXkuDQoNCmBgYHtyLG1lc3NhZ2UgPSBGQUxTRSx3YXJuaW5nPUZBTFNFfQ0KdGVzdF92YWx1ZXMgPC0gYmFrZShyZWNfdHJhaW5lZCwgbmV3ZGF0YT1iaW9fdGVzdCxhbGxfcHJlZGljdG9ycygpKQ0KDQp0ZXN0X3ZhbHVlcyU+JWhlYWQlPiVrbml0cjo6a2FibGUoKQ0KYGBgDQoNCiMgS+G6v3QgbHXhuq1uDQoNCnJlY2lwZXMgbMOgIG3hu5l0IHBhY2thZ2UgdGjDuiB24buLIHbDoCBo4buvdSDDrWNoLiBYdeG6pXQgcGjDoXQgdOG7qyDDvSB0xrDhu59uZyAiQ8O0bmcgdGjhu6ljIG7huqV1IMSDbiIsIGPDtG5nIGPhu6UgbsOgeSDEkcOjIHThuqFvIHJhIG3hu5l0IGPDoWNoIHRo4bupYyBob8OgbiB0b8OgbiBt4bubaSBjaG8gdmnhu4djIGhvw6FuIGNodXnhu4NuIHbDoCBjaHXhuqluIGLhu4sgZOG7ryBsaeG7h3UgdHLGsOG7m2Mga2hpIGThu7FuZyBtw7QgaMOsbmggdGjhu5FuZyBrw6ouIEPDoWNoIGzDoG0gbsOgeSDEkcOjIGto4bqvYyBwaOG7pWMgdOG6pXQgY+G6oyBuaOG7r25nIHRy4bufIG5n4bqhaSwga2jDsyBraMSDbiB0cm9uZyBxdcOhIGto4bupIG5oxrA6IA0KDQooMSkgTmhp4buBdSBxdXkgdHLDrG5oIHRow7RuZyBk4bulbmcgxJHDoyDEkcaw4bujYyB0w61jaCBo4bujcCBz4bq1biB0cm9uZyByZWNpcGVzIHbhu5tpIGRhbmggcGjDoXAgaMOgbSBk4buFIG5o4bubLCBjw7ogcGjDoXAgxJHGoW4gZ2nhuqNuOyB0YSBraMO0bmcgY+G6p24gcGjhuqNpIHTDrG0ga2nhur9tIHhhIHjDtGkgdsOgIG5o4bubIHThu6tuZyBow6BtIG3hu5l0DQoNCigyKSBDw7MgdGjhu4Mga+G6v3QgaOG7o3Agbmhp4buBdSBxdXkgdHLDrG5oIHbhu5tpIG5oYXUgbeG7mXQgY8OhY2ggdGjhu6kgYuG6rWMsIHRow60gZOG7pSBJbXB1dGF0aW9uIHRyxrDhu5tjLCBzYXUgxJHDsyBjaHXhuqluIGjDs2EsIHLhu5NpIGhvw6FuIGNodXnhu4NuLCBsw6BtIFBDQSBoYXkgbOG7jWMgYmnhur9uIHPhu5EuDQoNCigzKSDDgXAgZOG7pW5nIHF1eSB0csOsbmggY2hvIGjDoG5nIGxv4bqhdCBiaeG6v24gdHJvbmcgZOG7ryBsaeG7h3UsIA0KDQooNCkgWOG7rSBsw70gdHJhaW5zZXQgdsOgIHRlc3RzZXQgxJHhu5ljIGzhuq1wLg0KDQpDaMO6YyBjw6FjIGLhuqFuICJu4bqldSDEg24gbmdvbiIgduG7m2kgcGFja2FnZSByZWNpcGVzDQo=