Chapter20

Many Models with purr and broom

Hans Rosling 의 ted 강연보기 (한글 자막) https://youtu.be/NVwB_of8ZYs

Hans rosling

powerful ideas

  • 많은 수의 간단한 모델은 복잡한 데이터셋을 더 잘 이해하게 해준다.
  • list-columns를 이용하여 데이터를 구조화시킨다.
  • broom 패키지를 이용해서 tidy data를 만들고, 이로 모델링을 한다.
library(modelr)
library(tidyverse)
library(gapminder)

we will use the “gapminder”data.

gapminder

Q. [“How does life expectancy(lifeExp) change over time(year) for each country(country)?”]

: 3가지 변수에 초점을 맞추어 분석하고자 함.

gapminder %>% 
  ggplot(aes(year, lifeExp, group = country)) +
    geom_line(alpha = 1/3)

1700개의 관측치와 3개의 변수로 plotting해 본 결과, 많은 수가 아님에도 데이터의 흐름을 파악하기가 쉽지 않다. 전반적으로 증가하는 양상을 볼 수는 있으나, 몇 개의 국가들은 이를 따르지 않는 다는 것을 볼 수 있다. 따라서 “strong signal”을 사용해서 트랜드를 분석해보자. 선형 회귀를 적합시킨 것과 남은 residual에 대해 plotting을 해보자.

nz <- filter(gapminder, country == "New Zealand")
nz %>% 
  ggplot(aes(year, lifeExp)) + 
  geom_line() + 
  ggtitle("Full data = ")

nz_mod <- lm(lifeExp ~ year, data = nz)
nz %>% 
  add_predictions(nz_mod) %>%
  ggplot(aes(year, pred)) + 
  geom_line() + 
  ggtitle("Linear trend + ")

nz %>% 
  add_residuals(nz_mod) %>% 
  ggplot(aes(year, resid)) + 
  geom_hline(yintercept = 0, colour = "white", size = 3) + 
  geom_line() + 
  ggtitle("Remaining pattern")

new zealand 말고도, 모든 나라에 대해 쉽게 fitting 시키려면 어떻게 해야 할까? 방금과 같은 코드를 나라만 바꿔가며 복사 붙여넣기를 하는 방법도 있겠지만, 더 효율적인 방법이 있다.

Nested Data

데이터셋을 나라별로 뽑아서 반복을 하면 되는데, 이를 위해 자료의 구조를 새롭게 정의해야 한다. 이를 nested data frame이라고 한다. 이를 위한 코드는 다음과 같다.

변형된 자료를 파악하기 위해서, 하나의 원소를 골라서 어떤 정보를 어떻게 담고 있는지를 확인해보자. (Afghanistan을 고른 예시)

위에서 처럼, nested data frame에서는 각 열 자체가 그룹임을 확인 할 수 있다. 이제, 이 nested data frame으로 모델을 fitting 시켜 보는 함수를 만들어보자.

country_model <- function(df) {
  lm(lifeExp ~ year, data = df)
}

이 함수와 데이터를 이제, purrr패키지의 map 함수를 이용해서 연결시켜보자.

library(purrr)
models <- map(by_country$data, country_model)

그리고, 이 결과를 다른 새로운 object로 만들어서 global environment에 저장하는 것이 아니라, by_country라는 데이터 프레임에 새로운 변수를 만들어서 저장하는 방식을 택하는 것이 더 낫다. 이를 위해 dplyr패키지의 mutate 함수를 이용한다.

by_country <- by_country %>% 
  mutate(model = map(data, country_model))
by_country

이렇게 작업을 하면, 모든 관련 개체가 함께 저장되므로 필터링하거나 정렬 할 때 수동으로 동기화 할 필요가 없다는 강력한 장점이 있다. 만약 이렇게 하지 않고, 별개의 객체로 저장을 해놓으면, 벡터를 재정의 하거나 벡터를 subset시킬때 하위집합을 계속 동기화해주어야 한다. 이를 잊어버린다면, 코드는 돌아는 가겠지만, 잘못된 답을 출력해주게 된다.

Unnesting

142개의 나라가 있으므로, 우리는 142개의 데이터프레임과 142개의 모델을 갖고 있다. 잔차를 계산하기 위해서, add_residuals()을 이용한다.

by_country <- by_country %>% 
  mutate(
    resids = map2(data, model, add_residuals)
  )
by_country

Q. How can you plot a list of data frames?

–> nested된 데이터 프레임을 unnest()를 이용하여 정규 데이터 프레임 형식으로 바꾼 후 plot을 그린다. 그럼 다음과 같이 나라별로 묶여있던 데이터프레임이 풀리게 됨을 확인할 수 있다.

resids <- unnest(by_country, resids)
resids

이제, 잔차도를 그릴 수 있게 되었다.

resids %>% 
  ggplot(aes(year, resid)) +
    geom_line(aes(group = country), alpha = 1 / 3)  +
    geom_smooth(se = FALSE)

대륙별로 보면, 대륙마다의 특별한 특징이 더 드러나게 된다.

resids %>% 
  ggplot(aes(year, resid, group = country)) +
    geom_line(alpha = 1 / 3) + 
    facet_wrap(~continent)

잔차그림의 모양을 보니, fitting 시킨 모형이 적절하지 않았다고 판단할 수 있다. 특히 africa에서 큰 잔차들을 볼 수 있다. 다음 섹션에서 이를 다른 방향으로 접근하여 분석해보자.

Model Quality

모델로부터 잔차를 구하여 보는 것보다, model quality에 대한 보편적인 측정치를 보는 방법에 대해 생각해보자. broom 패키지로, 모델을 tidy데이터로 만들 수 있다. 또한, broom 패키지의 glance()함수를 써서 모형 평가 지표를 데이터 프레임 형태로 뽑아낼 수 있다.

broom::glance(nz_mod)

여기서 구한 이 정보를 mutate()와 unnest()를 이용해서 각 나라별의 행으로 다시 넣은 데이터 프레임을 만들 수 있다.

by_country %>% 
  mutate(glance = map(model, broom::glance)) %>% 
  unnest(glance)

그러나 이렇게, 모든 list 열을 포함하는 것은 우리가 원하던 결과가 아니므로, unnest에서 .drop=TRUE로 다시 옵션을 설정하여 바꿔준다.

glance <- by_country %>% 
  mutate(glance = map(model, broom::glance)) %>% 
  unnest(glance, .drop = TRUE)
glance

이렇게 얻어진 데이터프레임으로 드디어, 잘 맞지 않는 모델이 무엇인지 찾을 수 있게 되었다. 모형 평가 지표중의 하나인 r square값에 대해 오름차순으로 sorting을 해보자.

그 결과, Africa에서 모델이 제일 안맞음을 확인 할 수 있었다. 이를 다시 재확인해보기 위해 그림을 그려보려 한다. 관측수가 적고, 이산형 변수임으로, 지터그림을 보는 것이 효과적이다.

glance %>% 
  ggplot(aes(continent, r.squared)) + 
    geom_jitter(width = 0.5)

우리는 다음과 같이 낮은 R-square값을 갖고 있는 국가들만을 가지고 다시 그림을 그려볼 수 있다.

bad_fit <- filter(glance, r.squared < 0.25)
gapminder %>% 
  semi_join(bad_fit, by = "country") %>% 
  ggplot(aes(year, lifeExp, colour = country)) +
    geom_line()

우리는 여기서 HIV / AIDS 전염병과 르완다 대학살, 이 두가지 비극의 효과를 그림으로 확인 할 수 있었다. (동영상 내용 참고)

Hans rosling

=============================================================================================

List-Columns

지금까지 많은 모델을 다루는 것에 대한 전반적인 흐름을 보았다. 지금부터는 list-column 데이터 구조에 대해 더 자세히 살펴 볼 것이다. 데이터 프레임은 동일한 길이 벡터가 모여 명명되고, list-column은 데이터프레임의 정의안에 내포되어 있는 개념이다. list는 벡터이므로, list를 데이터 프레임의 열로 추가하는 것은 괜찮다. 그러나, data.frame()에서는 list를 열의 list로 다루므로, 기존의 R에서는 이러한 작업이 쉽지 않았다. 예를 통해 살펴보자.

data.frame(x = list(1:3, 3:5))

이렇게 하는 것은 우리가 원했던 결과가 아니므로, 이때는 I()를 사용하여 아래와 같이 나타내야 한다. 그러나 완전히 잘 출력해준다고는 볼 수 없다.

data.frame(
  x = I(list(1:3, 3:5)), 
  y = c("1, 2", "3, 4, 5")
)
tibble(
  x = list(1:3, 3:5), 
  y = c("1, 2", "3, 4, 5")
)

tibble은 입력값을 복사하는 개념이 아니므로, 더 나은 출력 방법을 제공하여 위의 예시들에서 발생한 문제들을 해결한다.

tribble(
   ~x, ~y,
  1:3, "1, 2",
  3:5, "3, 4, 5"
)

이렇게 하면 내가 필요로 하는 리스트를 더 쉽게 자동적으로 tibble을 이용하여 만들 수 있다. 이렇게, list-columns는 즉각적인 데이터 구조로 활용되기에 유용하다. 대부분의 R함수들은 atomic 벡터와 데이터 프레임을 이용해서 함수가 짜여져 있으므로, tibble을 이용해서 바로 작업을 수행하기는 어려울 수 있지만, 관련있는 항목들을 연결하여 쓸 수 있다는 장점이 있으므로 그만한 가치가 있다고 본다.

Creating List-Columns

통상적으로, tibble()을 이용해서 list-columns를 바로 만들지는 않는다. 대신에, regular columns를 이용해서 만드는데, 다음 세가지 방법들 중의 하나로 한다. - (1) tidyr::nest() 로 그룹화된 데이터 프레임을 nested 데이터 프레임으로 바꾼다. - (2) mutate() 와 벡터화 시키는 함수를 이용해 list를 반환한다. - (3) summerize() 와 요약해주는 함수를 통해 여러 결과를 출력한다. 대체적으로, tibble::enframe()을 사용하면, 명명된 리스트로 부터 만들어 낼 수도 있다. 일반적으로, list-columns를 만들때는, 그들이 homogeneous한 것을 확실하게 해야한다. 즉, 같은 요소를 담고 있어야 한다는 것이다. purrr에서 type-stable 함수를 배웠으므로, 이를 기억하고 자연스럽게 작업을 할 수 있을 것이다.

(1) With Nesting

nest()는 리스트 열을 갖는 nested 데이터 프레임을 만들어 준다. nest()를 쓰는 방법에는 두 가지가 있는데, 지금까지는 그룹화된 데이터 프레임에서 사용해왔었다. 이 때 nest()는 그룹화된 열을 그 그대로 유지시켜주면서 모든 것을 다발로 그 리스트 열 안에 담아 주었었다. 다음 예시를 확인해보면 이해가 될 것이다.

gapminder %>% 
  group_by(country, continent) %>% 
  nest()

우리는 이것을 그룹화되지 않았던 데이터 프레임에도 사용할 수 있고, 중첩 할 열을 특별히 지정할 수도 있다.

gapminder %>% 
  nest(year:gdpPercap)

(2) From Vectorized Functions

atomic 벡터를 리스트로 바꿔주는 유용한 함수들에 대해서 배웠었다(11장 참고). mutate를 사용하면, list-column을 만들 수 있다. 다음 예시를 확인해보자.

이를 unnest()를 사용하면, x2에 할당된 리스트를 벡터로 바꿀 수 있게 된다.

df %>% 
  mutate(x2 = stringr::str_split(x1, ",")) %>% 
  unnest()

이러한 유형은 purrr 패키지의 map(), map2(), pmap()등을 사용하여 비슷한 작업을 할 수 있다.

(3) From Multivalued Summeries

summerize()의 제약조건은, 한 값에 대해서만 출력을 해주는 함수라는 것이다. 쉽게 말해, 우리는 이것을 임의의 길이를 가진 벡터 즉, quantile()같은 함수에는 사용을 할 수 없다는 것이다. 때문에 다음 코드에서 에러가 나타난다.

mtcars %>% 
  group_by(cyl) %>% 
  summarise(q = quantile(mpg))
Error in summarise_impl(.data, dots) : expecting a single value

에러가 나지 않게 하려면 아래처럼 결과를 list로 묶어서 돌리면 돌아가게 된다. 왜냐하면 summary는 길이가 1이 된 리스트에 적용되기 때문이다.

mtcars %>% 
  group_by(cyl) %>% 
  summarise(q = list(quantile(mpg)))

이 결과를 unnest()를 이용하여 다시 풀어주면, 우리가 원하던 결과와 확률값을 얻게 된다.

probs <- c(0.01, 0.25, 0.5, 0.75, 0.99)
mtcars %>% 
  group_by(cyl) %>% 
  summarise(p = list(probs), q = list(quantile(mpg, probs))) %>% 
  unnest()

(4) From a Named List

list열을 갖고 있는 데이터 프레임은 “목록의 내용과 요소를 모두 반복하려는 경우 어떻게 해야 하나?”라는 흔한 문제에 대한 해결책을 제시한다. 모든 것을 하나의 객체로 묶는 대신 데이터 프레임을 만드는 것이 더 쉽다. 한 열에는 element가 포함될 수 있고 또 한 열에는 list가 포함될 수 있다. list로부터 그러한 데이터 프레임을 만드는 쉬운 방법은 tibble :: enframe ()를 쓰면 된다. 다음 예시를 보자.

x <- list(
  a = 1:5,
  b = 3:4, 
  c = 5:6
) 
df <- enframe(x)
df

이러한 구조의 장점은 직설적인 방식으로 일반화된다는 것이다. name은 문자형 변수가 meta data로 있으면 유용하지만, 다른 유형의 데이터나 여러개의 벡터가 이름에 들어가있으면 유용하지는 않은 표현방식이다. 어쨌든, 이제 이름과 값을 함께 반복하려면, map2()를 사용하면 된다.

df %>% 
  mutate(
    smry = map2_chr(name, value, ~ stringr::str_c(.x, ": ", .y[1]))
  )

List-Columns를 간단히 하기

  • (1). 리스트를 벡터화 시키기 : 단일 값을 원할 경우 map_lgl (), map_int (), map_dbl () 및 map_chr ()에 mutate ()를 사용하여 원자 벡터를 만든다.
  • (2). Unnesting : 많은 값을 원하면 unnest ()를 사용하여 목록 열을 일반 열로 다시 변환하고 필요한만큼 행을 반복한다. 예시1

예시2

Broom을 이용해 Tidy data 만들기

  • broom::glance(model)
  • broom::tidy(model)
  • broom::augment(model, data)

Broom은 가장 인기인는 모델링 패키지들을 이용해 다양한 모델들로 작동한다. 최근에 제공해주는 모델들에 대한 정보는 다음의 깃허브를 확인해보자. https://github.com/tidyverse/broom

LS0tDQp0aXRsZTogImNoYXB0ZXIyMCINCm91dHB1dDogaHRtbF9ub3RlYm9vaw0KLS0tDQojI0NoYXB0ZXIyMA0KIyNNYW55IE1vZGVscyB3aXRoIHB1cnIgYW5kIGJyb29tDQojIyMgSGFucyBSb3NsaW5nIOydmCB0ZWQg6rCV7Jew67O06riwICjtlZzquIAg7J6Q66eJKSAgaHR0cHM6Ly95b3V0dS5iZS9OVndCX29mOFpZcw0KIyFbSGFucyByb3NsaW5nXShDOi9Vc2Vycy9FS0xlZS9EZXNrdG9wL2hhbnNfcm9zbGluZy5qcGcpDQojIyMjIHBvd2VyZnVsIGlkZWFzDQogLSDrp47snYAg7IiY7J2YIOqwhOuLqO2VnCDrqqjrjbjsnYAg67O17J6h7ZWcIOuNsOydtO2EsOyFi+ydhCDrjZQg7J6YIOydtO2VtO2VmOqyjCDtlbTspIDri6QuIA0KIC0gbGlzdC1jb2x1bW5z66W8IOydtOyaqe2VmOyXrCDrjbDsnbTthLDrpbwg6rWs7KGw7ZmU7Iuc7YKo64ukLg0KIC0gYnJvb20g7Yyo7YKk7KeA66W8IOydtOyaqe2VtOyEnCB0aWR5IGRhdGHrpbwg66eM65Ok6rOgLCDsnbTroZwg66qo642466eB7J2EIO2VnOuLpC4NCg0KYGBge3J9DQpsaWJyYXJ5KG1vZGVscikNCmxpYnJhcnkodGlkeXZlcnNlKQ0KbGlicmFyeShnYXBtaW5kZXIpDQpgYGANCndlIHdpbGwgdXNlIHRoZSAiZ2FwbWluZGVyImRhdGEuDQpgYGB7cn0NCmdhcG1pbmRlcg0KYGBgDQoNCiMjIyNRLiBbIkhvdyBkb2VzIGxpZmUgZXhwZWN0YW5jeShsaWZlRXhwKSBjaGFuZ2Ugb3ZlciB0aW1lKHllYXIpIGZvciBlYWNoIGNvdW50cnkoY291bnRyeSk/Il0NCjogM+qwgOyngCDrs4DsiJjsl5Ag7LSI7KCQ7J2EIOunnuy2lOyWtCDrtoTshJ3tlZjqs6DsnpAg7ZWoLg0KDQpgYGB7cn0NCmdhcG1pbmRlciAlPiUgDQogIGdncGxvdChhZXMoeWVhciwgbGlmZUV4cCwgZ3JvdXAgPSBjb3VudHJ5KSkgKw0KICAgIGdlb21fbGluZShhbHBoYSA9IDEvMykNCmBgYA0KMTcwMOqwnOydmCDqtIDsuKHsuZjsmYAgM+qwnOydmCDrs4DsiJjroZwgcGxvdHRpbmftlbQg67O4IOqysOqzvCwg66eO7J2AIOyImOqwgCDslYTri5jsl5Drj4Qg642w7J207YSw7J2YIO2dkOumhOydhCDtjIzslYXtlZjquLDqsIAg7Im97KeAIOyViuuLpC4g7KCE67CY7KCB7Jy866GcIOymneqwgO2VmOuKlCDslpHsg4HsnYQg67O8IOyImOuKlCDsnojsnLzrgpgsIOuqhyDqsJzsnZgg6rWt6rCA65Ok7J2AIOydtOulvCDrlLDrpbTsp4Ag7JWK64qUIOuLpOuKlCDqsoPsnYQg67O8IOyImCDsnojri6QuIOuUsOudvOyEnCAic3Ryb25nIHNpZ25hbCLsnYQg7IKs7Jqp7ZW07IScIO2KuOuenOuTnOulvCDrtoTshJ3tlbTrs7TsnpAuIOyEoO2YlSDtmozqt4Drpbwg7KCB7ZWp7Iuc7YKoIOqyg+qzvCDrgqjsnYAgcmVzaWR1YWzsl5Ag64yA7ZW0IHBsb3R0aW5n7J2EIO2VtOuztOyekC4NCg0KYGBge3J9DQpueiA8LSBmaWx0ZXIoZ2FwbWluZGVyLCBjb3VudHJ5ID09ICJOZXcgWmVhbGFuZCIpDQpueiAlPiUgDQogIGdncGxvdChhZXMoeWVhciwgbGlmZUV4cCkpICsgDQogIGdlb21fbGluZSgpICsgDQogIGdndGl0bGUoIkZ1bGwgZGF0YSA9ICIpDQoNCm56X21vZCA8LSBsbShsaWZlRXhwIH4geWVhciwgZGF0YSA9IG56KQ0KbnogJT4lIA0KICBhZGRfcHJlZGljdGlvbnMobnpfbW9kKSAlPiUNCiAgZ2dwbG90KGFlcyh5ZWFyLCBwcmVkKSkgKyANCiAgZ2VvbV9saW5lKCkgKyANCiAgZ2d0aXRsZSgiTGluZWFyIHRyZW5kICsgIikNCg0KbnogJT4lIA0KICBhZGRfcmVzaWR1YWxzKG56X21vZCkgJT4lIA0KICBnZ3Bsb3QoYWVzKHllYXIsIHJlc2lkKSkgKyANCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMCwgY29sb3VyID0gIndoaXRlIiwgc2l6ZSA9IDMpICsgDQogIGdlb21fbGluZSgpICsgDQogIGdndGl0bGUoIlJlbWFpbmluZyBwYXR0ZXJuIikNCmBgYA0KbmV3IHplYWxhbmQg66eQ6rOg64+ELCDrqqjrk6Ag64KY65287JeQIOuMgO2VtCDsib3qsowgZml0dGluZyDsi5ztgqTroKTrqbQg7Ja065a76rKMIO2VtOyVvCDtlaDquYw/IOuwqeq4iOqzvCDqsJnsnYAg7L2U65Oc66W8IOuCmOudvOunjCDrsJTqv5TqsIDrqbAg67O17IKsIOu2meyXrOuEo+q4sOulvCDtlZjripQg67Cp67KV64+EIOyeiOqyoOyngOunjCwg642UIO2aqOycqOyggeyduCDrsKnrspXsnbQg7J6I64ukLg0KDQojIyMjIE5lc3RlZCBEYXRhDQog642w7J207YSw7IWL7J2EIOuCmOudvOuzhOuhnCDrvZHslYTshJwg67CY67O17J2EIO2VmOuptCDrkJjripTrjbAsIOydtOulvCDsnITtlbQg7J6Q66OM7J2YIOq1rOyhsOulvCDsg4jroa3qsowg7KCV7J2Y7ZW07JW8IO2VnOuLpC4g7J2066W8IG5lc3RlZCBkYXRhIGZyYW1l7J2065286rOgIO2VnOuLpC4g7J2066W8IOychO2VnCDsvZTrk5zripQg64uk7J2M6rO8IOqwmeuLpC4NCmBgYHtyfQ0KYnlfY291bnRyeSA8LSBnYXBtaW5kZXIgJT4lIA0KICBncm91cF9ieShjb3VudHJ5LCBjb250aW5lbnQpICU+JSANCiAgbmVzdCgpDQoNCmJ5X2NvdW50cnkNCmBgYA0K67OA7ZiV65CcIOyekOujjOulvCDtjIzslYXtlZjquLAg7JyE7ZW07IScLCDtlZjrgpjsnZgg7JuQ7IaM66W8IOqzqOudvOyEnCDslrTrlqQg7KCV67O066W8IOyWtOuWu+qyjCDri7Tqs6Ag7J6I64qU7KeA66W8IO2ZleyduO2VtOuztOyekC4gKEFmZ2hhbmlzdGFu7J2EIOqzoOuluCDsmIjsi5wpDQpgYGB7cn0NCmJ5X2NvdW50cnkkZGF0YVtbMV1dDQpgYGANCuychOyXkOyEnCDsspjrn7wsIG5lc3RlZCBkYXRhIGZyYW1l7JeQ7ISc64qUIOqwgSDsl7Qg7J6Q7LK06rCAIOq3uOujueyehOydhCDtmZXsnbgg7ZWgIOyImCDsnojri6QuIOydtOygnCwg7J20IG5lc3RlZCBkYXRhIGZyYW1l7Jy866GcIOuqqOuNuOydhCBmaXR0aW5nIOyLnOy8nCDrs7TripQg7ZWo7IiY66W8IOunjOuTpOyWtOuztOyekC4NCmBgYHtyfQ0KY291bnRyeV9tb2RlbCA8LSBmdW5jdGlvbihkZikgew0KICBsbShsaWZlRXhwIH4geWVhciwgZGF0YSA9IGRmKQ0KfQ0KYGBgDQrsnbQg7ZWo7IiY7JmAIOuNsOydtO2EsOulvCDsnbTsoJwsIHB1cnJy7Yyo7YKk7KeA7J2YIG1hcCDtlajsiJjrpbwg7J207Jqp7ZW07IScIOyXsOqysOyLnOy8nOuztOyekC4NCmBgYHtyfQ0KbGlicmFyeShwdXJycikNCm1vZGVscyA8LSBtYXAoYnlfY291bnRyeSRkYXRhLCBjb3VudHJ5X21vZGVsKQ0KYGBgDQrqt7jrpqzqs6AsIOydtCDqsrDqs7zrpbwg64uk66W4IOyDiOuhnOyatCBvYmplY3TroZwg66eM65Ok7Ja07IScIGdsb2JhbCBlbnZpcm9ubWVudOyXkCDsoIDsnqXtlZjripQg6rKD7J20IOyVhOuLiOudvCwgYnlfY291bnRyeeudvOuKlCDrjbDsnbTthLAg7ZSE66CI7J6E7JeQIOyDiOuhnOyatCDrs4DsiJjrpbwg66eM65Ok7Ja07IScIOyggOyepe2VmOuKlCDrsKnsi53snYQg7YOd7ZWY64qUIOqyg+ydtCDrjZQg64Kr64ukLiDsnbTrpbwg7JyE7ZW0IGRwbHly7Yyo7YKk7KeA7J2YIG11dGF0ZSDtlajsiJjrpbwg7J207Jqp7ZWc64ukLiANCmBgYHtyfQ0KYnlfY291bnRyeSA8LSBieV9jb3VudHJ5ICU+JSANCiAgbXV0YXRlKG1vZGVsID0gbWFwKGRhdGEsIGNvdW50cnlfbW9kZWwpKQ0KYnlfY291bnRyeQ0KYGBgDQrsnbTroIfqsowg7J6R7JeF7J2EIO2VmOuptCwg66qo65OgIOq0gOugqCDqsJzssrTqsIAg7ZWo6ruYIOyggOyepeuQmOuvgOuhnCDtlYTthLDrp4HtlZjqsbDrgpgg7KCV66CsIO2VoCDrlYwg7IiY64+Z7Jy866GcIOuPmeq4sO2ZlCDtlaAg7ZWE7JqU6rCAIOyXhuuLpOuKlCDqsJXroKXtlZwg7J6l7KCQ7J20IOyeiOuLpC4g66eM7JW9IOydtOugh+qyjCDtlZjsp4Ag7JWK6rOgLCDrs4TqsJzsnZgg6rCd7LK066GcIOyggOyepeydhCDtlbTrhpPsnLzrqbQsIOuyoe2EsOulvCDsnqzsoJXsnZgg7ZWY6rGw64KYIOuyoe2EsOulvCBzdWJzZXTsi5ztgqzrlYwg7ZWY7JyE7KeR7ZWp7J2EIOqzhOyGjSDrj5nquLDtmZTtlbTso7zslrTslbwg7ZWc64ukLiDsnbTrpbwg7J6K7Ja067KE66aw64uk66m0LCDsvZTrk5zripQg64+M7JWE64qUIOqwgOqyoOyngOunjCwg7J6Y66q765CcIOuLteydhCDstpzroKXtlbTso7zqsowg65Cc64ukLg0KDQojIyMjIFVubmVzdGluZw0KMTQy6rCc7J2YIOuCmOudvOqwgCDsnojsnLzrr4DroZwsIOyasOumrOuKlCAxNDLqsJzsnZgg642w7J207YSw7ZSE66CI7J6E6rO8IDE0MuqwnOydmCDrqqjrjbjsnYQg6rCW6rOgIOyeiOuLpC4g7J6U7LCo66W8IOqzhOyCsO2VmOq4sCDsnITtlbTshJwsIGFkZF9yZXNpZHVhbHMoKeydhCDsnbTsmqntlZzri6QuIA0KYGBge3J9DQpieV9jb3VudHJ5IDwtIGJ5X2NvdW50cnkgJT4lIA0KICBtdXRhdGUoDQogICAgcmVzaWRzID0gbWFwMihkYXRhLCBtb2RlbCwgYWRkX3Jlc2lkdWFscykNCiAgKQ0KYnlfY291bnRyeQ0KYGBgDQojIyMjIFEuIEhvdyBjYW4geW91IHBsb3QgYSBsaXN0IG9mIGRhdGEgZnJhbWVzPw0KLS0+IG5lc3RlZOuQnCDrjbDsnbTthLAg7ZSE66CI7J6E7J2EIHVubmVzdCgp66W8IOydtOyaqe2VmOyXrCDsoJXqt5wg642w7J207YSwIO2UhOugiOyehCDtmJXsi53snLzroZwg67CU6r68IO2bhCBwbG907J2EIOq3uOumsOuLpC4g6re465+8IOuLpOydjOqzvCDqsJnsnbQg64KY652867OE66GcIOustuyXrOyeiOuNmCDrjbDsnbTthLDtlITroIjsnoTsnbQg7ZKA66as6rKMIOuQqOydhCDtmZXsnbjtlaAg7IiYIOyeiOuLpC4gDQpgYGB7cn0NCnJlc2lkcyA8LSB1bm5lc3QoYnlfY291bnRyeSwgcmVzaWRzKQ0KcmVzaWRzDQpgYGANCuydtOygnCwg7J6U7LCo64+E66W8IOq3uOumtCDsiJgg7J6I6rKMIOuQmOyXiOuLpC4NCmBgYHtyfQ0KcmVzaWRzICU+JSANCiAgZ2dwbG90KGFlcyh5ZWFyLCByZXNpZCkpICsNCiAgICBnZW9tX2xpbmUoYWVzKGdyb3VwID0gY291bnRyeSksIGFscGhhID0gMSAvIDMpICArDQogICAgZ2VvbV9zbW9vdGgoc2UgPSBGQUxTRSkNCmBgYA0K64yA66WZ67OE66GcIOuztOuptCwg64yA66WZ66eI64uk7J2YIO2KueuzhO2VnCDtirnsp5XsnbQg642UIOuTnOufrOuCmOqyjCDrkJzri6QuIA0KYGBge3J9DQpyZXNpZHMgJT4lIA0KICBnZ3Bsb3QoYWVzKHllYXIsIHJlc2lkLCBncm91cCA9IGNvdW50cnkpKSArDQogICAgZ2VvbV9saW5lKGFscGhhID0gMSAvIDMpICsgDQogICAgZmFjZXRfd3JhcCh+Y29udGluZW50KQ0KYGBgDQrsnpTssKjqt7jrprzsnZgg66qo7JaR7J2EIOuztOuLiCwgZml0dGluZyDsi5ztgqgg66qo7ZiV7J20IOyggeygiO2VmOyngCDslYrslZjri6Tqs6Ag7YyQ64uo7ZWgIOyImCDsnojri6QuIO2Kue2eiCBhZnJpY2Hsl5DshJwg7YGwIOyelOywqOuTpOydhCDrs7wg7IiYIOyeiOuLpC4g64uk7J2MIOyEueyFmOyXkOyEnCDsnbTrpbwg64uk66W4IOuwqe2WpeycvOuhnCDsoJHqt7ztlZjsl6wg67aE7ISd7ZW067O07J6QLg0KDQojIyMjIE1vZGVsIFF1YWxpdHkNCiDrqqjrjbjroZzrtoDthLAg7J6U7LCo66W8IOq1rO2VmOyXrCDrs7TripQg6rKD67O064ukLCBtb2RlbCBxdWFsaXR57JeQIOuMgO2VnCDrs7TtjrjsoIHsnbgg7Lih7KCV7LmY66W8IOuztOuKlCDrsKnrspXsl5Ag64yA7ZW0IOyDneqwge2VtOuztOyekC4gYnJvb20g7Yyo7YKk7KeA66GcLCDrqqjrjbjsnYQgdGlkeeuNsOydtO2EsOuhnCDrp4zrk6Qg7IiYIOyeiOuLpC4g65iQ7ZWcLCBicm9vbSDtjKjtgqTsp4DsnZggZ2xhbmNlKCntlajsiJjrpbwg7I2o7IScIOuqqO2YlSDtj4nqsIAg7KeA7ZGc66W8IOuNsOydtO2EsCDtlITroIjsnoQg7ZiV7YOc66GcIOu9keyVhOuCvCDsiJgg7J6I64ukLiANCmBgYHtyfQ0KYnJvb206OmdsYW5jZShuel9tb2QpDQpgYGANCuyXrOq4sOyEnCDqtaztlZwg7J20IOygleuztOulvCBtdXRhdGUoKeyZgCB1bm5lc3QoKeulvCDsnbTsmqntlbTshJwg6rCBIOuCmOudvOuzhOydmCDtlonsnLzroZwg64uk7IucIOuEo+ydgCDrjbDsnbTthLAg7ZSE66CI7J6E7J2EIOunjOuTpCDsiJgg7J6I64ukLg0KYGBge3J9DQpieV9jb3VudHJ5ICU+JSANCiAgbXV0YXRlKGdsYW5jZSA9IG1hcChtb2RlbCwgYnJvb206OmdsYW5jZSkpICU+JSANCiAgdW5uZXN0KGdsYW5jZSkNCmBgYA0K6re465+s64KYIOydtOugh+qyjCwg66qo65OgIGxpc3Qg7Je07J2EIO2PrO2VqO2VmOuKlCDqsoPsnYAg7Jqw66as6rCAIOybkO2VmOuNmCDqsrDqs7zqsIAg7JWE64uI66+A66GcLCB1bm5lc3Tsl5DshJwgLmRyb3A9VFJVReuhnCDri6Tsi5wg7Ji17IWY7J2EIOyEpOygle2VmOyXrCDrsJTqv5TspIDri6QuIA0KYGBge3J9DQpnbGFuY2UgPC0gYnlfY291bnRyeSAlPiUgDQogIG11dGF0ZShnbGFuY2UgPSBtYXAobW9kZWwsIGJyb29tOjpnbGFuY2UpKSAlPiUgDQogIHVubmVzdChnbGFuY2UsIC5kcm9wID0gVFJVRSkNCmdsYW5jZQ0KYGBgDQrsnbTroIfqsowg7Ja77Ja07KeEIOuNsOydtO2EsO2UhOugiOyehOycvOuhnCDrk5zrlJTslrQsIOyemCDrp57sp4Ag7JWK64qUIOuqqOuNuOydtCDrrLTsl4fsnbjsp4Ag7LC+7J2EIOyImCDsnojqsowg65CY7JeI64ukLiDrqqjtmJUg7Y+J6rCAIOyngO2RnOykkeydmCDtlZjrgpjsnbggciBzcXVhcmXqsJLsl5Ag64yA7ZW0IOyYpOumhOywqOyInOycvOuhnCBzb3J0aW5n7J2EIO2VtOuztOyekC4NCmBgYHtyfQ0KZ2xhbmNlICU+JSANCiAgYXJyYW5nZShyLnNxdWFyZWQpDQpgYGANCiDqt7gg6rKw6rO8LCBBZnJpY2Hsl5DshJwg66qo64247J20IOygnOydvCDslYjrp57snYzsnYQg7ZmV7J24IO2VoCDsiJgg7J6I7JeI64ukLiDsnbTrpbwg64uk7IucIOyerO2ZleyduO2VtOuztOq4sCDsnITtlbQg6re466a87J2EIOq3uOugpOuztOugpCDtlZzri6QuIOq0gOy4oeyImOqwgCDsoIHqs6AsIOydtOyCsO2YlSDrs4DsiJjsnoTsnLzroZwsIOyngO2EsOq3uOumvOydhCDrs7TripQg6rKD7J20IO2aqOqzvOyggeydtOuLpC4NCmBgYHtyfQ0KZ2xhbmNlICU+JSANCiAgZ2dwbG90KGFlcyhjb250aW5lbnQsIHIuc3F1YXJlZCkpICsgDQogICAgZ2VvbV9qaXR0ZXIod2lkdGggPSAwLjUpDQpgYGANCiDsmrDrpqzripQg64uk7J2M6rO8IOqwmeydtCDrgq7snYAgUi1zcXVhcmXqsJLsnYQg6rCW6rOgIOyeiOuKlCDqta3qsIDrk6Trp4zsnYQg6rCA7KeA6rOgIOuLpOyLnCDqt7jrprzsnYQg6re466Ck67O8IOyImCDsnojri6QuDQpgYGB7cn0NCmJhZF9maXQgPC0gZmlsdGVyKGdsYW5jZSwgci5zcXVhcmVkIDwgMC4yNSkNCg0KZ2FwbWluZGVyICU+JSANCiAgc2VtaV9qb2luKGJhZF9maXQsIGJ5ID0gImNvdW50cnkiKSAlPiUgDQogIGdncGxvdChhZXMoeWVhciwgbGlmZUV4cCwgY29sb3VyID0gY291bnRyeSkpICsNCiAgICBnZW9tX2xpbmUoKQ0KYGBgDQrsmrDrpqzripQg7Jes6riw7IScIEhJViAvIEFJRFMg7KCE7Je867OR6rO8IOultOyZhOuLpCDrjIDtlZnsgrQsIOydtCDrkZDqsIDsp4Ag67mE6re57J2YIO2aqOqzvOulvCDqt7jrprzsnLzroZwg7ZmV7J24IO2VoCDsiJgg7J6I7JeI64ukLg0KKOuPmeyYgeyDgSDrgrTsmqkg7LC46rOgKQ0KDQojIVtIYW5zIHJvc2xpbmddKEM6L1VzZXJzL0VLTGVlL0Rlc2t0b3AvaGFuc19yb3NsaW5nX2hpdi5qcGcpDQoNCg0KPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09DQoNCiMjI0xpc3QtQ29sdW1ucyANCuyngOq4iOq5jOyngCDrp47snYAg66qo64247J2EIOuLpOujqOuKlCDqsoPsl5Ag64yA7ZWcIOyghOuwmOyggeyduCDtnZDrpoTsnYQg67O07JWY64ukLiDsp4DquIjrtoDthLDripQgbGlzdC1jb2x1bW4g642w7J207YSwIOq1rOyhsOyXkCDrjIDtlbQg642UIOyekOyEuO2eiCDsgrTtjrQg67O8IOqyg+ydtOuLpC4g642w7J207YSwIO2UhOugiOyehOydgCDrj5nsnbztlZwg6ri47J20IOuyoe2EsOqwgCDrqqjsl6wg66qF66qF65CY6rOgLCBsaXN0LWNvbHVtbuydgCDrjbDsnbTthLDtlITroIjsnoTsnZgg7KCV7J2Y7JWI7JeQIOuCtO2PrOuQmOyWtCDsnojripQg6rCc64WQ7J2064ukLiBsaXN064qUIOuyoe2EsOydtOuvgOuhnCwgbGlzdOulvCDrjbDsnbTthLAg7ZSE66CI7J6E7J2YIOyXtOuhnCDstpTqsIDtlZjripQg6rKD7J2AIOq0nOywruuLpC4g6re465+s64KYLCBkYXRhLmZyYW1lKCnsl5DshJzripQgbGlzdOulvCDsl7TsnZggbGlzdOuhnCDri6Tro6jrr4DroZwsIOq4sOyhtOydmCBS7JeQ7ISc64qUIOydtOufrO2VnCDsnpHsl4XsnbQg7Im97KeAIOyViuyVmOuLpC4g7JiI66W8IO2Gte2VtCDsgrTtjrTrs7TsnpAuDQpgYGB7cn0NCmRhdGEuZnJhbWUoeCA9IGxpc3QoMTozLCAzOjUpKQ0KYGBgDQrsnbTroIfqsowg7ZWY64qUIOqyg+ydgCDsmrDrpqzqsIAg7JuQ7ZaI642YIOqysOqzvOqwgCDslYTri4jrr4DroZwsIOydtOuVjOuKlCBJKCnrpbwg7IKs7Jqp7ZWY7JesIOyVhOuemOyZgCDqsJnsnbQg64KY7YOA64K07JW8IO2VnOuLpC4g6re465+s64KYIOyZhOyghO2eiCDsnpgg7Lac66Cl7ZW07KSA64uk6rOg64qUIOuzvCDsiJgg7JeG64ukLg0KYGBge3J9DQpkYXRhLmZyYW1lKA0KICB4ID0gSShsaXN0KDE6MywgMzo1KSksIA0KICB5ID0gYygiMSwgMiIsICIzLCA0LCA1IikNCikNCmBgYA0KYGBge3J9DQp0aWJibGUoDQogIHggPSBsaXN0KDE6MywgMzo1KSwgDQogIHkgPSBjKCIxLCAyIiwgIjMsIDQsIDUiKQ0KKQ0KYGBgDQp0aWJibGXsnYAg7J6F66Cl6rCS7J2EIOuzteyCrO2VmOuKlCDqsJzrhZDsnbQg7JWE64uI66+A66GcLCDrjZQg64KY7J2AIOy2nOugpSDrsKnrspXsnYQg7KCc6rO17ZWY7JesIOychOydmCDsmIjsi5zrk6Tsl5DshJwg67Cc7IOd7ZWcIOusuOygnOuTpOydhCDtlbTqsrDtlZzri6QuDQpgYGB7cn0NCnRyaWJibGUoDQogICB+eCwgfnksDQogIDE6MywgIjEsIDIiLA0KICAzOjUsICIzLCA0LCA1Ig0KKQ0KYGBgDQrsnbTroIfqsowg7ZWY66m0IOuCtOqwgCDtlYTsmpTroZwg7ZWY64qUIOumrOyKpO2KuOulvCDrjZQg7Im96rKMIOyekOuPmeyggeycvOuhnCB0aWJibGXsnYQg7J207Jqp7ZWY7JesIOunjOuTpCDsiJgg7J6I64ukLiDsnbTroIfqsowsIGxpc3QtY29sdW1uc+uKlCDsponqsIHsoIHsnbgg642w7J207YSwIOq1rOyhsOuhnCDtmZzsmqnrkJjquLDsl5Ag7Jyg7Jqp7ZWY64ukLiDrjIDrtoDrtoTsnZggUu2VqOyImOuTpOydgCBhdG9taWMg67Kh7YSw7JmAIOuNsOydtO2EsCDtlITroIjsnoTsnYQg7J207Jqp7ZW07IScIO2VqOyImOqwgCDsp5zsl6zsoLgg7J6I7Jy866+A66GcLCB0aWJibGXsnYQg7J207Jqp7ZW07IScIOuwlOuhnCDsnpHsl4XsnYQg7IiY7ZaJ7ZWY6riw64qUIOyWtOugpOyauCDsiJgg7J6I7KeA66eMLCDqtIDroKjsnojripQg7ZWt66qp65Ok7J2EIOyXsOqysO2VmOyXrCDsk7gg7IiYIOyeiOuLpOuKlCDsnqXsoJDsnbQg7J6I7Jy866+A66GcIOq3uOunjO2VnCDqsIDsuZjqsIAg7J6I64uk6rOgIOuzuOuLpC4NCg0KIyMjQ3JlYXRpbmcgTGlzdC1Db2x1bW5zIA0K7Ya17IOB7KCB7Jy866GcLCB0aWJibGUoKeydhCDsnbTsmqntlbTshJwgbGlzdC1jb2x1bW5z66W8IOuwlOuhnCDrp4zrk6Tsp4DripQg7JWK64qU64ukLiDrjIDsi6Dsl5AsIHJlZ3VsYXIgY29sdW1uc+ulvCDsnbTsmqntlbTshJwg66eM65Oc64qU642wLCDri6TsnYwg7IS46rCA7KeAIOuwqeuyleuTpCDspJHsnZgg7ZWY64KY66GcIO2VnOuLpC4NCiAtICgxKSB0aWR5cjo6bmVzdCgpIOuhnCDqt7jro7ntmZTrkJwg642w7J207YSwIO2UhOugiOyehOydhCBuZXN0ZWQg642w7J207YSwIO2UhOugiOyehOycvOuhnCDrsJTqvrzri6QuDQogLSAoMikgbXV0YXRlKCkg7JmAIOuyoe2EsO2ZlCDsi5ztgqTripQg7ZWo7IiY66W8IOydtOyaqe2VtCBsaXN066W8IOuwmO2ZmO2VnOuLpC4NCiAtICgzKSBzdW1tZXJpemUoKSDsmYAg7JqU7JW97ZW07KO864qUIO2VqOyImOulvCDthrXtlbQg7Jes65+sIOqysOqzvOulvCDstpzroKXtlZzri6QuDQrrjIDssrTsoIHsnLzroZwsIHRpYmJsZTo6ZW5mcmFtZSgp7J2EIOyCrOyaqe2VmOuptCwg66qF66qF65CcIOumrOyKpO2KuOuhnCDrtoDthLAg66eM65Ok7Ja0IOuCvCDsiJjrj4Qg7J6I64ukLiDsnbzrsJjsoIHsnLzroZwsIGxpc3QtY29sdW1uc+ulvCDrp4zrk6TrlYzripQsIOq3uOuTpOydtCBob21vZ2VuZW91c+2VnCDqsoPsnYQg7ZmV7Iuk7ZWY6rKMIO2VtOyVvO2VnOuLpC4g7KaJLCDqsJnsnYAg7JqU7IaM66W8IOuLtOqzoCDsnojslrTslbwg7ZWc64uk64qUIOqyg+ydtOuLpC4gcHVycnLsl5DshJwgdHlwZS1zdGFibGUg7ZWo7IiY66W8IOuwsOyboOycvOuvgOuhnCwg7J2066W8IOq4sOyWte2VmOqzoCDsnpDsl7DsiqTrn73qsowg7J6R7JeF7J2EIO2VoCDsiJgg7J6I7J2EIOqyg+ydtOuLpC4NCg0KDQojIyMjICgxKSBXaXRoIE5lc3RpbmcNCm5lc3QoKeuKlCDrpqzsiqTtirgg7Je07J2EIOqwluuKlCBuZXN0ZWQg642w7J207YSwIO2UhOugiOyehOydhCDrp4zrk6TslrQg7KSA64ukLiBuZXN0KCnrpbwg7JOw64qUIOuwqeuyleyXkOuKlCDrkZAg6rCA7KeA6rCAIOyeiOuKlOuNsCwg7KeA6riI6rmM7KeA64qUIOq3uOujue2ZlOuQnCDrjbDsnbTthLAg7ZSE66CI7J6E7JeQ7IScIOyCrOyaqe2VtOyZlOyXiOuLpC4g7J20IOuVjCBuZXN0KCnripQg6re466O57ZmU65CcIOyXtOydhCDqt7gg6re464yA66GcIOycoOyngOyLnOy8nOyjvOuptOyEnCDrqqjrk6Ag6rKD7J2EIOuLpOuwnOuhnCDqt7gg66as7Iqk7Yq4IOyXtCDslYjsl5Ag64u07JWEIOyjvOyXiOyXiOuLpC4g64uk7J2MIOyYiOyLnOulvCDtmZXsnbjtlbTrs7TrqbQg7J207ZW06rCAIOuQoCDqsoPsnbTri6QuDQpgYGB7cn0NCmdhcG1pbmRlciAlPiUgDQogIGdyb3VwX2J5KGNvdW50cnksIGNvbnRpbmVudCkgJT4lIA0KICBuZXN0KCkNCmBgYA0K7Jqw66as64qUIOydtOqyg+ydhCDqt7jro7ntmZTrkJjsp4Ag7JWK7JWY642YIOuNsOydtO2EsCDtlITroIjsnoTsl5Drj4Qg7IKs7Jqp7ZWgIOyImCDsnojqs6AsIOykkeyyqSDtlaAg7Je07J2EIO2KueuzhO2eiCDsp4DsoJXtlaAg7IiY64+EIOyeiOuLpC4gDQpgYGB7cn0NCmdhcG1pbmRlciAlPiUgDQogIG5lc3QoeWVhcjpnZHBQZXJjYXApDQpgYGANCg0KIyMjIyAoMikgRnJvbSBWZWN0b3JpemVkIEZ1bmN0aW9ucw0KYXRvbWljIOuyoe2EsOulvCDrpqzsiqTtirjroZwg67CU6r+U7KO864qUIOycoOyaqe2VnCDtlajsiJjrk6Tsl5Ag64yA7ZW07IScIOuwsOyboOyXiOuLpCgxMeyepSDssLjqs6ApLiBtdXRhdGXrpbwg7IKs7Jqp7ZWY66m0LCBsaXN0LWNvbHVtbuydhCDrp4zrk6Qg7IiYIOyeiOuLpC4NCuuLpOydjCDsmIjsi5zrpbwg7ZmV7J247ZW067O07J6QLg0KYGBge3J9DQpkZiA8LSB0cmliYmxlKA0KICB+eDEsDQogICJhLGIsYyIsIA0KICAiZCxlLGYsZyINCikgDQpkZiAlPiUgDQogbXV0YXRlKHgyID0gc3RyaW5ncjo6c3RyX3NwbGl0KHgxLCAiLCIpKQ0KYGBgDQrsnbTrpbwgdW5uZXN0KCnrpbwg7IKs7Jqp7ZWY66m0LCB4MuyXkCDtlaDri7nrkJwg66as7Iqk7Yq466W8IOuyoe2EsOuhnCDrsJTqv4Ag7IiYIOyeiOqyjCDrkJzri6QuIA0KYGBge3J9DQpkZiAlPiUgDQogIG11dGF0ZSh4MiA9IHN0cmluZ3I6OnN0cl9zcGxpdCh4MSwgIiwiKSkgJT4lIA0KICB1bm5lc3QoKQ0KYGBgDQrsnbTrn6ztlZwg7Jyg7ZiV7J2AIHB1cnJyIO2MqO2CpOyngOydmCBtYXAoKSwgbWFwMigpLCBwbWFwKCnrk7HsnYQg7IKs7Jqp7ZWY7JesIOu5hOyKt+2VnCDsnpHsl4XsnYQg7ZWgIOyImCDsnojri6QuIA0KDQoNCiMjIyMgKDMpIEZyb20gTXVsdGl2YWx1ZWQgU3VtbWVyaWVzDQogc3VtbWVyaXplKCnsnZgg7KCc7JW97KGw6rG07J2ALCDtlZwg6rCS7JeQIOuMgO2VtOyEnOunjCDstpzroKXsnYQg7ZW07KO864qUIO2VqOyImOudvOuKlCDqsoPsnbTri6QuIOyJveqyjCDrp5DtlbQsIOyasOumrOuKlCDsnbTqsoPsnYQg7J6E7J2Y7J2YIOq4uOydtOulvCDqsIDsp4Qg67Kh7YSwIOymiSwgcXVhbnRpbGUoKeqwmeydgCDtlajsiJjsl5DripQg7IKs7Jqp7J2EIO2VoCDsiJgg7JeG64uk64qUIOqyg+ydtOuLpC4g65WM66y47JeQIOuLpOydjCDsvZTrk5zsl5DshJwg7JeQ65+s6rCAIOuCmO2DgOuCnOuLpC4NCmBgYHtyfQ0KbXRjYXJzICU+JSANCiAgZ3JvdXBfYnkoY3lsKSAlPiUgDQogIHN1bW1hcmlzZShxID0gcXVhbnRpbGUobXBnKSkNCmBgYA0K7JeQ65+s6rCAIOuCmOyngCDslYrqsowg7ZWY66Ck66m0IOyVhOuemOyymOufvCDqsrDqs7zrpbwgbGlzdOuhnCDrrLbslrTshJwg64+M66as66m0IOuPjOyVhOqwgOqyjCDrkJzri6QuIOyZnOuDkO2VmOuptCBzdW1tYXJ564qUIOq4uOydtOqwgCAx7J20IOuQnCDrpqzsiqTtirjsl5Ag7KCB7Jqp65CY6riwIOuVjOusuOydtOuLpC4NCmBgYHtyfQ0KbXRjYXJzICU+JSANCiAgZ3JvdXBfYnkoY3lsKSAlPiUgDQogIHN1bW1hcmlzZShxID0gbGlzdChxdWFudGlsZShtcGcpKSkNCmBgYA0K7J20IOqysOqzvOulvCB1bm5lc3QoKeulvCDsnbTsmqntlZjsl6wg64uk7IucIO2SgOyWtOyjvOuptCwg7Jqw66as6rCAIOybkO2VmOuNmCDqsrDqs7zsmYAg7ZmV66Wg6rCS7J2EIOyWu+qyjCDrkJzri6QuDQpgYGB7cn0NCnByb2JzIDwtIGMoMC4wMSwgMC4yNSwgMC41LCAwLjc1LCAwLjk5KQ0KbXRjYXJzICU+JSANCiAgZ3JvdXBfYnkoY3lsKSAlPiUgDQogIHN1bW1hcmlzZShwID0gbGlzdChwcm9icyksIHEgPSBsaXN0KHF1YW50aWxlKG1wZywgcHJvYnMpKSkgJT4lIA0KICB1bm5lc3QoKQ0KYGBgDQoNCiMjIyMgKDQpIEZyb20gYSBOYW1lZCBMaXN0DQpsaXN07Je07J2EIOqwluqzoCDsnojripQg642w7J207YSwIO2UhOugiOyehOydgCAi66qp66Gd7J2YIOuCtOyaqeqzvCDsmpTshozrpbwg66qo65GQIOuwmOuzte2VmOugpOuKlCDqsr3smrAg7Ja065a76rKMIO2VtOyVvCDtlZjrgpg/IuudvOuKlCDtnZTtlZwg66y47KCc7JeQIOuMgO2VnCDtlbTqsrDssYXsnYQg7KCc7Iuc7ZWc64ukLiDrqqjrk6Ag6rKD7J2EIO2VmOuCmOydmCDqsJ3ssrTroZwg66y264qUIOuMgOyLoCDrjbDsnbTthLAg7ZSE66CI7J6E7J2EIOunjOuTnOuKlCDqsoPsnbQg642UIOyJveuLpC4g7ZWcIOyXtOyXkOuKlCBlbGVtZW506rCAIO2PrO2VqOuQoCDsiJgg7J6I6rOgIOuYkCDtlZwg7Je07JeQ64qUIGxpc3TqsIAg7Y+s7ZWo65CgIOyImCDsnojri6QuIGxpc3TroZzrtoDthLAg6re465+s7ZWcIOuNsOydtO2EsCDtlITroIjsnoTsnYQg66eM65Oc64qUIOyJrOyatCDrsKnrspXsnYAgdGliYmxlIDo6IGVuZnJhbWUgKCnrpbwg7JOw66m0IOuQnOuLpC4g64uk7J2MIOyYiOyLnOulvCDrs7TsnpAuDQoNCmBgYHtyfQ0KeCA8LSBsaXN0KA0KICBhID0gMTo1LA0KICBiID0gMzo0LCANCiAgYyA9IDU6Ng0KKSANCmRmIDwtIGVuZnJhbWUoeCkNCmRmDQpgYGANCuydtOufrO2VnCDqtazsobDsnZgg7J6l7KCQ7J2AIOyngeyEpOyggeyduCDrsKnsi53snLzroZwg7J2867CY7ZmU65Cc64uk64qUIOqyg+ydtOuLpC4gbmFtZeydgCDrrLjsnpDtmJUg67OA7IiY6rCAIG1ldGEgZGF0YeuhnCDsnojsnLzrqbQg7Jyg7Jqp7ZWY7KeA66eMLCDri6Trpbgg7Jyg7ZiV7J2YIOuNsOydtO2EsOuCmCDsl6zrn6zqsJzsnZgg67Kh7YSw6rCAIOydtOumhOyXkCDrk6TslrTqsIDsnojsnLzrqbQg7Jyg7Jqp7ZWY7KeA64qUIOyViuydgCDtkZztmITrsKnsi53snbTri6QuDQrslrTsqIzrk6AsIOydtOygnCDsnbTrpoTqs7wg6rCS7J2EIO2VqOq7mCDrsJjrs7XtlZjroKTrqbQsIG1hcDIoKeulvCDsgqzsmqntlZjrqbQg65Cc64ukLg0KYGBge3J9DQpkZiAlPiUgDQogIG11dGF0ZSgNCiAgICBzbXJ5ID0gbWFwMl9jaHIobmFtZSwgdmFsdWUsIH4gc3RyaW5ncjo6c3RyX2MoLngsICI6ICIsIC55WzFdKSkNCiAgKQ0KYGBgDQoNCiMjIyBMaXN0LUNvbHVtbnPrpbwg6rCE64uo7Z6IIO2VmOq4sA0KIC0gKDEpLiDrpqzsiqTtirjrpbwg67Kh7YSw7ZmUIOyLnO2CpOq4sCA6IOuLqOydvCDqsJLsnYQg7JuQ7ZWgIOqyveyasCBtYXBfbGdsICgpLCBtYXBfaW50ICgpLCBtYXBfZGJsICgpIOuwjyBtYXBfY2hyICgp7JeQIG11dGF0ZSAoKeulvCDsgqzsmqntlZjsl6wg7JuQ7J6QIOuyoe2EsOulvCDrp4zrk6Dri6QuDQpgYGB7cn0NCmRmIDwtIHRyaWJibGUoDQogIH54LA0KICBsZXR0ZXJzWzE6NV0sDQogIDE6MywNCiAgcnVuaWYoNSkNCikNCiAgDQpkZiAlPiUgbXV0YXRlKA0KICB0eXBlID0gbWFwX2Nocih4LCB0eXBlb2YpLA0KICBsZW5ndGggPSBtYXBfaW50KHgsIGxlbmd0aCkNCikNCmBgYA0KDQogLSAoMikuIFVubmVzdGluZyA6IOunjuydgCDqsJLsnYQg7JuQ7ZWY66m0IHVubmVzdCAoKeulvCDsgqzsmqntlZjsl6wg66qp66GdIOyXtOydhCDsnbzrsJgg7Je066GcIOuLpOyLnCDrs4DtmZjtlZjqs6Ag7ZWE7JqU7ZWc66eM7YG8IO2WieydhCDrsJjrs7XtlZzri6QuDQrsmIjsi5wxDQpgYGB7cn0NCnRpYmJsZSh4ID0gMToyLCB5ID0gbGlzdCgxOjQsIDEpKSAlPiUgdW5uZXN0KHkpDQpgYGANCuyYiOyLnDINCmBgYHtyfQ0KZGYxIDwtIHRyaWJibGUoDQogIH54LCB+eSwgICAgICAgICAgIH56LA0KICAgMSwgYygiYSIsICJiIiksIDE6MiwNCiAgIDIsICJjIiwgICAgICAgICAgIDMNCikNCmRmMQ0KYGBgDQpgYGB7cn0NCmRmMSAlPiUgdW5uZXN0KHksIHopDQpgYGANCiMjIyBCcm9vbeydhCDsnbTsmqntlbQgVGlkeSBkYXRhIOunjOuTpOq4sA0KIC0gYnJvb206OmdsYW5jZShtb2RlbCkgDQogLSBicm9vbTo6dGlkeShtb2RlbCkNCiAtIGJyb29tOjphdWdtZW50KG1vZGVsLCBkYXRhKQ0KDQpCcm9vbeydgCDqsIDsnqUg7J246riw7J2464qUIOuqqOuNuOungSDtjKjtgqTsp4Drk6TsnYQg7J207Jqp7ZW0IOuLpOyWke2VnCDrqqjrjbjrk6TroZwg7J6R64+Z7ZWc64ukLiDstZzqt7zsl5Ag7KCc6rO17ZW07KO864qUIOuqqOuNuOuTpOyXkCDrjIDtlZwg7KCV67O064qUIOuLpOydjOydmCDquYPtl4jruIzrpbwg7ZmV7J247ZW067O07J6QLiBodHRwczovL2dpdGh1Yi5jb20vdGlkeXZlcnNlL2Jyb29tDQoNCg0K