Processing math: 100%

목차


정형 데이터 마이닝

머신러닝의 종류

  • 지도학습 : 독립변수와 종속변수의 쌍으로 된 자료가 주어질 때 새로운 자료에 대하여 문제를 풀 수 있는 함수를 찾는 학습방법
  • 비지도학습 : 종속변수가 없이 자료들로부터 패턴을 추출하는 학습방법(데이터 마이닝의 군집의 개념)
  • 강화학습 : 문제에 대한 직접적인 해답을 주지 않고 경험을 통해 기대값이 최대가 되는 것을 찾는 학습방법

비지도학습

각 개체에 대해 여러 변수값들로부터 n개의 유사한 성격의 군집으로 군집화하고 군집간 관계를 분석하는 기법

KernSmooth 2.23 loaded
Copyright M. P. Wand 1997-2009

가장 유사한 개체를 묶어 나가는 과정을 반복하여 원하는 개수의 군집을 형성하는 방법. 작은 군집으로부터 출발하여 을 병합해 나가는 방법(병합적 방법)과 큰 군집으로부터 군집을 분리해 나가는 방법(분할적 방법)이 있다.

군집 간의 연결방법
  • 최단연결법(single linkage method) : 두 군집 사이의 거리를 각 군집에서 관측값을 하나씩 뽑았을 때 나타날 수 있는 최소거리로 측정
  • 최장연결법(complete linkage method) : 두 군집 사이의 거리를 각 군집에서 관측값을 하나씩 뽑았을 때 나타날 수 있는 최장거리로 측정
  • 평균연결법(average linkage) : 모든 항목에 대한 거리의 평균을 거리로 측정
  • 중심연결법(centroid linkage) : 두 군집의 중심을 거리로 측정
  • 와드연결법(Ward linkage) : 군집내 오차제곱합이 가장 작아지는 방향으로 군집을 형성

개체간 거리의 종류
  • 민코우스키(MinKowski) 거리 : Dist(x,y)=[(xrixsi)p]1/p

  • 맨하튼 거리 : 두 점 간의 차이의 절대값을 합한 값, r=1일 때의 민코우스키의 거리와 같음. Dist(x,y)=|xiyi|

  • 유클리드 거리 : 민코우스키거리의 특별 경우(r=2) 자료의 분포적 특성을 고려할 수 없으며, 단위 또한 같아야한다는 단점이 존재. Dist(x,y)=(xrixsi)2=[XrXs]T[XrXs]

  • 체비셰프 거리(=maximum distance) : 두 집단에서 가장 긴 지점에서의 거리 max(|x1y1|,,|xpyp|

  • 표준화 거리 : 유클리드 거리를 공분산으로 나눈 거리 [(XrXs)TD1(XrXs)]1/2,D=diag(S11,,Spp)

  • 마할라노비스(Mahalanobis) 거리 : 공분산 구조를 함께 고려한 통계적 거리, 자료의 분포적 특성을 고려하기 위한 방법. [(XrXs)TS1(XrXs)]1/2,S=

참고:공분산이 단위행렬이 되면 유클리드거리와 같아지는데 이러한 변환을 화이트닝변환이라 한다.

  • 캔버라 거리 : 맨하튼 거리에 가중치를 적용한 거리의 개념 dist(x,y)=|xiyi|xiyi

method에 euclidean, maximum, manhattan, canbera, minkowski 등이 들어갈 수 있다.

x1=rnorm(30)
x2=rnorm(30)
dist(rbind(x1,x2),method='euclidean')
         x1
x2 8.833439
  • 코사인 거리 : 코사인 유사도는 무게나 크기는 전혀 고려하지 않고 벡터 사이의 각도만으로 측정하는 것과 유사 cos(Xr,Xs)=Xr×Xs|Xr||Xs|
#거리계산 패키지
library('lsa')
Loading required package: SnowballC
x1=sample(c(0,1),30,replace = T)
x2=sample(c(0,1),30,replace = T)
#코사인 거리 계산
cosine(x1,x2)
          [,1]
[1,] 0.5477226
  • 피어슨 거리 : 피어슨 상관계수의 범위가 -1~1이므로 1에서 피어슨 상관계수를 뺀 값을 거리로 사용

hclust 함수의 method 옵션에는 “ward.D”, “ward.D2”, “single”, “complete”, “average”, “mcquitty”, “median” or “centroid”이 있다.


자료 설명 미국 내 50개 주의1973년에 발생한 범죄관련 통계자료

  • Murder : 10만명 당 살인죄 수
  • Assault : 10만명 당 폭행죄 수
  • UrbanPop : 도시에 살고 있는 인구 비율
  • Rape : 10만명당 성범죄 수
library(cluster)
data(USArrests)
str(USArrests)
'data.frame':   50 obs. of  4 variables:
 $ Murder  : num  13.2 10 8.1 8.8 9 7.9 3.3 5.9 15.4 17.4 ...
 $ Assault : int  236 263 294 190 276 204 110 238 335 211 ...
 $ UrbanPop: int  58 48 80 50 91 78 77 72 80 60 ...
 $ Rape    : num  21.2 44.5 31 19.5 40.6 38.7 11.1 15.8 31.9 25.8 ...
rownames(USArrests)
 [1] "Alabama"        "Alaska"         "Arizona"        "Arkansas"       "California"    
 [6] "Colorado"       "Connecticut"    "Delaware"       "Florida"        "Georgia"       
[11] "Hawaii"         "Idaho"          "Illinois"       "Indiana"        "Iowa"          
[16] "Kansas"         "Kentucky"       "Louisiana"      "Maine"          "Maryland"      
[21] "Massachusetts"  "Michigan"       "Minnesota"      "Mississippi"    "Missouri"      
[26] "Montana"        "Nebraska"       "Nevada"         "New Hampshire"  "New Jersey"    
[31] "New Mexico"     "New York"       "North Carolina" "North Dakota"   "Ohio"          
[36] "Oklahoma"       "Oregon"         "Pennsylvania"   "Rhode Island"   "South Carolina"
[41] "South Dakota"   "Tennessee"      "Texas"          "Utah"           "Vermont"       
[46] "Virginia"       "Washington"     "West Virginia"  "Wisconsin"      "Wyoming"       
#인구비율이 가장 높은 지역의 정보
USArrests[which.max(USArrests$UrbanPop),]
ABCDEFGHIJ0123456789
 
 
Murder
<dbl>
Assault
<int>
UrbanPop
<int>
Rape
<dbl>
California92769140.6
#살인비율이 가장 높은 지역의 정보
USArrests[which.max(USArrests$Murder),]
ABCDEFGHIJ0123456789
 
 
Murder
<dbl>
Assault
<int>
UrbanPop
<int>
Rape
<dbl>
Georgia17.42116025.8
#살인비율이 가장 늦은 지역의 정보
USArrests[which.min(USArrests$Murder),]
ABCDEFGHIJ0123456789
 
 
Murder
<dbl>
Assault
<int>
UrbanPop
<int>
Rape
<dbl>
North Dakota0.845447.3
병합적 방법(1)

작은 군집으로부터 출발하여 을 병합해 나가는 방법

#유클리드거리 계산
d <-dist(USArrests, method="euclidean")
#평균연결법을 활용한 계층적군집
fit <-hclust(d, method="average")

#시각화
plot(fit)

plot(fit,hang=-1)

병합적 방법(1)

metric 옵션을 통해 거리계산의 종류를 지정해 줄 수 있다. metric 대신 daisy함수를 통해 거리계산 가능 stand 옵션을 TRUE로 주면 거리 계산 전에 표준화를 수행한다.

#계층적 군집 관련 패키지
library(cluster)
#병합적방법
#stand=T이면 표준화 진행
agn1 <-agnes(USArrests, metric="manhattan", stand=TRUE)
plot(agn1)

#최장연결법으로 계층적 군집 실행
agn2 <-agnes(daisy(USArrests), diss=TRUE, method="complete")
plot(agn2)

병합적방법(2)

(분할적 방법) 큰 군집으로부터 군집을 분리해 나가는 방법

library(cluster)
#분할적 방법
#stand=T이면 표준화 진행
agn1 <-diana(USArrests, metric="manhattan", stand=TRUE)
plot(agn1)

#최장연결법으로 계층적 군집 실행
agn2 <-diana(daisy(USArrests), diss=TRUE, metric ="complete")
plot(agn2)

K개(원하는 군집수)의 초기값을 지정하고 각 자료를 가까운 초깃값에 할당하여 군집을 형성한 뒤, 각 군집의 평균을 계산하여 초깃값을 갱신하는 것을 반복하는 군집 방법

집단 내 제곱합의 그래프를 통한 군집수 결정

k에 해당하는 초깃값을 최적화 하는 방법

#집합내 잔차제곱합을 활용하여 군집 수 최적화
wssplot<-function(data, nc=15, seed=1234){
wss<-(nrow(data)-1)*sum(apply(data,2,var))
for (i in 2:nc){
set.seed(seed)
wss[i] <-sum(kmeans(data, centers=i)$withinss)}
plot(1:nc, wss, type="b", xlab="Number of Clusters",
ylab="Within groups sum of squares")}

wssplot(USArrests)

package를 이용

library(NbClust)
set.seed(1234)
nc=NbClust(USArrests,min.nc=2,max.nc=15,method='kmeans')
*** : The Hubert index is a graphical method of determining the number of clusters.
                In the plot of Hubert index, we seek a significant knee that corresponds to a 
                significant increase of the value of the measure i.e the significant peak in Hubert
                index second differences plot. 
 

*** : The D index is a graphical method of determining the number of clusters. 
                In the plot of D index, we seek a significant knee (the significant peak in Dindex
                second differences plot) that corresponds to a significant increase of the value of
                the measure. 
 
******************************************************************* 
* Among all indices:                                                
* 9 proposed 2 as the best number of clusters 
* 6 proposed 3 as the best number of clusters 
* 1 proposed 4 as the best number of clusters 
* 3 proposed 5 as the best number of clusters 
* 3 proposed 7 as the best number of clusters 
* 1 proposed 11 as the best number of clusters 
* 1 proposed 15 as the best number of clusters 

                   ***** Conclusion *****                            
 
* According to the majority rule, the best number of clusters is  2 
 
 
******************************************************************* 

nc$Best.n[1,,drop=F]
                KL CH Hartigan CCC Scott Marriot TrCovW TraceW Friedman Rubin Cindex DB Silhouette
Number_clusters  5  5        3   2     7       7      3      3       11     7      4  2          2
                Duda PseudoT2 Beale Ratkowsky Ball PtBiserial Frey McClain Dunn Hubert SDindex Dindex
Number_clusters    2        2     2         2    3          2    3       2    5      0       3      0
                SDbw
Number_clusters   15
which.max(table(nc$Best.n[1,]))
2 
2 
set.seed(1234)
#2개의 군집 생성
fit.km <-kmeans(USArrests, 2, nstart=25)
fit.km$size
[1] 29 21
#시각화
plot(USArrests, col=fit.km$cluster)

#군집별 변수별 평균값
aggregate(USArrests, by=list(cluster=fit.km$cluster), mean)
ABCDEFGHIJ0123456789
cluster
<int>
Murder
<dbl>
Assault
<dbl>
UrbanPop
<dbl>
Rape
<dbl>
14.841379109.758664.0344816.24828
211.857143255.000067.6190528.11429

밀도 기반의 클러스터링은 점이 세밀하게 몰려 있어서 밀도가 높은 부분을 클러스터링 하는 방식. epsilon 반경 내에 최소 n개의 개수의 점이 있으면 군집으로 인식

dbscan() : 밀도기반 군집을 생성하는 코드, eps와 MinPts를 옵션으로 주어야 하며, eps는 epsilon, MinPts는 반경 내의 최소 점의 개수를 의미한다.

library(fpc)
#밀도기반 군집
test.dbscan1 <-dbscan(USArrests, eps=20, MinPts=7)
n <-max(test.dbscan1$cluster)
dscenter<-as.data.frame(matrix(0, nrow=1, ncol=2))
for (i in 1:n){
  dscenter[i,] <-colMeans(USArrests[test.dbscan1$cluster == i,])
}
plot(USArrests, col=test.dbscan1$cluster+1)

밀도기반 군집과 k-means 군집간 비교

#아래 그림과 같은 분포의 자료 생성 (test.sample1)

set.seed(7979)
get.sample<-function (n=1000, p=0.7) {
x1 <-rnorm(n)
y1 <-rnorm(n)
r2 <-7+rnorm(n)
t2 <-runif(n,0,2*pi)
x2 <-r2*cos(t2)
y2 <-r2*sin(t2)
r <-runif(n)>p
x <-ifelse(r,x1,x2)
y <-ifelse(r,y1,y2)
d <-data.frame(x=x, y=y)
d
}
test.sample1 <-get.sample()


plot(test.sample1)

library(KernSmooth)
r <-bkde2D(test.sample1, bandwidth=c(0.5,0.5))
persp(r$fhat, scale=T, expand=0.3, col="white", theta = 30, phi = 30)

library(fpc)
#밀도기반 군집
test.dbscan1 <-dbscan(test.sample1, eps=0.8, MinPts=5)
n <-max(test.dbscan1$cluster)
dscenter<-as.data.frame(matrix(0, nrow=1, ncol=2))
for (i in 1:n){
dscenter[i,] <-colMeans(test.sample1[test.dbscan1$cluster == i,])
}
plot(test.sample1, col=test.dbscan1$cluster+1,main='DBSCAN')

#k means 군집
test.kmeans1 <-kmeans(test.sample1, centers=2)
plot(test.sample1, col=test.kmeans1$cluster+1,main='k-means')

각 군집 방법의 장단점 및 비교

장점

  • k-평균 군집 : 다른 군집 방법보다 많은 양의 자료를 다룰 수 있다.
  • 밀도기반 군집 : 특이한 형태의 군집을 찾는데 유리하며, 잡음을 포함한 데이터를 다루는데 적합

단점

  • k-평균 군집 : 초기값, 이상치에 영향을 많이 받으며, 볼록한 형태가 아닌 군집에 대해 성능이 떨어짐
  • 밀도기반 군집 : 경계점에 대하여 두 군집 모두에 속할 수 있다는 문제가 존재. 고차원자료의경우‘차원의저주(curse of dimensionality)’로인해거리측도가무의미하거나, 이로인해적절한반경(𝜖)을찾기어렵다.

iris 자료에 대한 모형 비교

iris2=iris[,1:4]

dist=dist(iris2)
#계층적 군집
hcl=hclust(dist,method='average')
#군집 수 3개로 설정
cut=cutree(hcl,k=3)
#군집 시각화
plot(hcl,hang=-1,cex=.8)
rect.hclust(hcl,k=3)

#실제 자료의 분류
plot(iris[,-5],col=iris$Species)

#계층적 군집 분류 시각화
plot(iris[,-5],col=cut,pch=cut)

#분류 비율
class=as.factor(cut)
levels(class)= levels(iris$Species)
table(class)
class
    setosa versicolor  virginica 
        50         64         36 
#정오분류표
table(class,iris$Species)
            
class        setosa versicolor virginica
  setosa         50          0         0
  versicolor      0         50        14
  virginica       0          0        36
library(NbClust)
set.seed(1234)
#kmeans 군집
fit.km <-kmeans(iris[,-5], 3, nstart=25)
fit.km$size
[1] 50 38 62
#실제 자료의 분류
plot(iris[,-5],col=iris$Species)


#kmean 군집의 분류 
plot(iris[,-5], col=fit.km$cluster)

#kmean 군집의 분류별 평균
aggregate(iris[,-5], by=list(cluster=fit.km$cluster), mean)
ABCDEFGHIJ0123456789
cluster
<int>
Sepal.Length
<dbl>
Sepal.Width
<dbl>
Petal.Length
<dbl>
Petal.Width
<dbl>
15.0060003.4280001.4620000.246000
26.8500003.0736845.7421052.071053
35.9016132.7483874.3935481.433871

#분류비율
class=as.factor(fit.km$cluster)
levels(class)= levels(iris$Species)
table(class)
class
    setosa versicolor  virginica 
        50         38         62 
#정오분류표
table(class,iris$Species)
            
class        setosa versicolor virginica
  setosa         50          0         0
  versicolor      0          2        36
  virginica       0         48        14
library(fpc)
#밀도기반 군집
test.dbscan1 <-dbscan(iris[,-5], eps=0.8, MinPts=5)
n <-max(test.dbscan1$cluster)
dscenter<-as.data.frame(matrix(0, nrow=1, ncol=2))
for (i in 1:n){
dscenter[i,] <-colMeans(iris[,-5][test.dbscan1$cluster == i,])
}

#실제 자료의 분류
plot(iris[,-5],col=iris$Species)

#밀도기반군짐의 분류
plot(iris[,-5], col=test.dbscan1$cluster+1)


#분류비율
class=as.factor(test.dbscan1$cluster+1)
levels(class)= levels(iris$Species)
table(class)
class
    setosa versicolor  virginica 
         2         50         98 
#정오분류표
table(class,iris$Species)
            
class        setosa versicolor virginica
  setosa          0          0         2
  versicolor     50          0         0
  virginica       0         50        48
library(fpc)
#밀도기반 군집(적은 변수로)
test.dbscan1 <-dbscan(iris[,-c(1,4:5)], eps=0.8, MinPts=7)
test.dbscan1
dbscan Pts=150 MinPts=7 eps=0.8
        1   2
border  0   4
seed   50  96
total  50 100
n <-max(test.dbscan1$cluster)
n
[1] 2
dscenter<-as.data.frame(matrix(0, nrow=1, ncol=2))
for (i in 1:n){
  dscenter[i,] <-colMeans(iris[,-5][test.dbscan1$cluster == i,])
}

#실제자료의 분류
plot(iris[,-5],col=iris$Species)


#밀도기반군집의 분류
plot(iris[,-5], col=test.dbscan1$cluster+1)



#분류비율
class=as.factor(test.dbscan1$cluster+1)
levels(class)= levels(iris$Species)
table(class)
class
    setosa versicolor  virginica 
        50        100          0 
#정오분류표
table(class,iris$Species)
            
class        setosa versicolor virginica
  setosa         50          0         0
  versicolor      0         50        50
  virginica       0          0         0
LS0tCnRpdGxlOiAiQUkg7JmAIE1hY2hpbmUgTGVhcm5pbmfsnYQg7JyE7ZWcIOu5heuNsOydtO2EsCDsi6TsirUiCm91dHB1dDogCiAgaHRtbF9ub3RlYm9vazogCiAgICB0aGVtZTogc3BhY2VsYWIKLS0tCgoKKioqKgoKIyDrqqnssKgKCisgWzIwMTktMDctMTcgKOyYpOyghCkgOiDrjbDsnbTthLDrp4jsnbTri50g6rCc7JqU7JmAIFIg6riw7LSIIOyCrOyaqeuylV0oaHR0cDovL3JwdWJzLmNvbS9xa2Ryazc3Nzc3Ny81MTE5NDIpCisgWzIwMTktMDctMTcgKOyYpO2bhCkgOiDquLDstIgg7Ya16rOE67aE7ISdXShodHRwOi8vcnB1YnMuY29tL3FrZHJrNzc3Nzc3LzUxMTk0MSkKKyBbMjAxOS0wNy0xOCAo7Jik7KCEKSA6IOygle2YleuNsOydtO2EsOuniOydtOuLnSAtIOyngOuPhO2VmeyKtV0oaHR0cDovL3JwdWJzLmNvbS9xa2Ryazc3Nzc3Ny81MTE5NDApCisgKipbMjAxOS0wNy0xOCAo7Jik7ZuEKSA6IOygle2YleuNsOydtO2EsOuniOydtOuLnSAtIOu5hOyngOuPhO2VmeyKtV0oaHR0cDovL3JwdWJzLmNvbS9xa2Ryazc3Nzc3Ny81MTE5MzgpKioKKyBbMjAxOS0wNy0xOSAo7Jik7KCEKSA6IOu5hOygle2YleuNsOydtO2EsOuniOydtOuLnS3snpDro4wg64uk66Oo6riwXShodHRwOi8vcnB1YnMuY29tL3FrZHJrNzc3Nzc3LzUxMTkzNykKKyBbMjAxOS0wNy0xOSAo7Jik7ZuEKSA6IOu5hOygle2YleuNsOydtO2EsOuniOydtOuLnS3thY3siqTtirjrp4jsnbTri51dKGh0dHA6Ly9ycHVicy5jb20vcWtkcms3Nzc3NzcvNTExMTk4KQoKKioqCgojIOygle2YlSDrjbDsnbTthLAg66eI7J2064udCgojIyDrqLjsi6Drn6zri53snZgg7KKF66WYCgorIOyngOuPhO2VmeyKtSA6IOuPheumveuzgOyImOyZgCDsooXsho3rs4DsiJjsnZgg7IyN7Jy866GcIOuQnCDsnpDro4zqsIAg7KO87Ja07KeIIOuVjCDsg4jroZzsmrQg7J6Q66OM7JeQIOuMgO2VmOyXrCDrrLjsoJzrpbwg7ZKAIOyImCDsnojripQg7ZWo7IiY66W8IOywvuuKlCDtlZnsirXrsKnrspUKKyAqKuu5hOyngOuPhO2VmeyKtSoqIDog7KKF7IaN67OA7IiY6rCAIOyXhuydtCDsnpDro4zrk6TroZzrtoDthLAg7Yyo7YS07J2EIOy2lOy2nO2VmOuKlCDtlZnsirXrsKnrspUo642w7J207YSwIOuniOydtOuLneydmCDqtbDsp5HsnZgg6rCc64WQKQorIOqwle2ZlO2VmeyKtSA6ICDrrLjsoJzsl5Ag64yA7ZWcIOyngeygkeyggeyduCDtlbTri7XsnYQg7KO87KeAIOyViuqzoCDqsr3tl5jsnYQg7Ya17ZW0IOq4sOuMgOqwkuydtCDstZzrjIDqsIAg65CY64qUIOqyg+ydhCDssL7ripQg7ZWZ7Iq167Cp67KVCgoKIyMg67mE7KeA64+E7ZWZ7Iq1ey50YWJzZXQgLnRhYnNldC1mYWRlIC50YWJzZXQtcGlsbHN9CgrqsIEg6rCc7LK07JeQIOuMgO2VtCDsl6zrn6wg67OA7IiY6rCS65Ok66Gc67aA7YSwIG7qsJzsnZggKirsnKDsgqztlZwg7ISx6rKpKirsnZgg6rWw7KeR7Jy866GcIOq1sOynke2ZlO2VmOqzoCDqtbDsp5HqsIQg6rSA6rOE66W8IOu2hOyEne2VmOuKlCDquLDrspUKCmBgYHtyIGVjaG89RkFMU0V9CgpzZXQuc2VlZCg3OTc5KQpnZXQuc2FtcGxlPC1mdW5jdGlvbiAobj0xMDAwLCBwPTAuNykgewp4MSA8LXJub3JtKG4pCnkxIDwtcm5vcm0obikKcjIgPC03K3Jub3JtKG4pCnQyIDwtcnVuaWYobiwwLDIqcGkpCngyIDwtcjIqY29zKHQyKQp5MiA8LXIyKnNpbih0MikKciA8LXJ1bmlmKG4pPnAKeCA8LWlmZWxzZShyLHgxLHgyKQp5IDwtaWZlbHNlKHIseTEseTIpCmQgPC1kYXRhLmZyYW1lKHg9eCwgeT15KQpkfQp0ZXN0LnNhbXBsZTEgPC1nZXQuc2FtcGxlKCkKcGxvdCh0ZXN0LnNhbXBsZTEpCmxpYnJhcnkoS2VyblNtb290aCkKbGlicmFyeShmcGMpCnRlc3QuZGJzY2FuMSA8LWRic2Nhbih0ZXN0LnNhbXBsZTEsIGVwcz0wLjgsIE1pblB0cz01KQpuIDwtbWF4KHRlc3QuZGJzY2FuMSRjbHVzdGVyKQpkc2NlbnRlcjwtYXMuZGF0YS5mcmFtZShtYXRyaXgoMCwgbnJvdz0xLCBuY29sPTIpKQpmb3IgKGkgaW4gMTpuKXsKZHNjZW50ZXJbaSxdIDwtY29sTWVhbnModGVzdC5zYW1wbGUxW3Rlc3QuZGJzY2FuMSRjbHVzdGVyID09IGksXSkKfQpwbG90KHRlc3Quc2FtcGxlMSwgY29sPXRlc3QuZGJzY2FuMSRjbHVzdGVyKzEpCmBgYAoKCiMjIyDqs4TsuLXsoIEg6rWw7KeRKEhpYXJpY2hpY2FsIGNsdXN0ZXJpbmcpIHsudGFic2V0IC50YWJzZXQtZmFkZX0KCuqwgOyepSDsnKDsgqztlZwgKirqsJzssrTrpbwg66y27Ja0IOuCmOqwgOuKlCDqs7zsoJXsnYQg67CY67O1KirtlZjsl6wg7JuQ7ZWY64qUIOqwnOyImOydmCDqtbDsp5HsnYQg7ZiV7ISx7ZWY64qUIOuwqeuylS4K7J6R7J2AIOq1sOynkeycvOuhnOu2gO2EsCDstpzrsJztlZjsl6wg7J2EIOuzke2Vqe2VtCDrgpjqsIDripQg67Cp67KVKOuzke2VqeyggSDrsKnrspUp6rO8IO2BsCDqtbDsp5HsnLzroZzrtoDthLAg6rWw7KeR7J2EIOu2hOumrO2VtCDrgpjqsIDripQg67Cp67KVKOu2hO2VoOyggSDrsKnrspUp7J20IOyeiOuLpC4KCgojIyMjICoq6rWw7KeRIOqwhOydmCDsl7DqsrDrsKnrspXqs7wg6rGw66as7J2YIOyiheulmCoqCgojIyMjIyAqKuq1sOynkSDqsITsnZgg7Jew6rKw67Cp67KVKioKCisg7LWc64uo7Jew6rKw67KVKHNpbmdsZSBsaW5rYWdlIG1ldGhvZCkgOiDrkZAg6rWw7KeRIOyCrOydtOydmCDqsbDrpqzrpbwg6rCBIOq1sOynkeyXkOyEnCDqtIDsuKHqsJLsnYQg7ZWY64KY7JSpIOu9keyVmOydhCDrlYwg64KY7YOA64KgIOyImCDsnojripQg7LWc7IaM6rGw66as66GcIOy4oeyglQorIOy1nOyepeyXsOqysOuylShjb21wbGV0ZSBsaW5rYWdlIG1ldGhvZCkgOiDrkZAg6rWw7KeRIOyCrOydtOydmCDqsbDrpqzrpbwg6rCBIOq1sOynkeyXkOyEnCDqtIDsuKHqsJLsnYQg7ZWY64KY7JSpIOu9keyVmOydhCDrlYwg64KY7YOA64KgIOyImCDsnojripQg7LWc7J6l6rGw66as66GcIOy4oeyglQorIO2Pieq3oOyXsOqysOuylShhdmVyYWdlIGxpbmthZ2UpIDog66qo65OgIO2VreuqqeyXkCDrjIDtlZwg6rGw66as7J2YIO2Pieq3oOydhCDqsbDrpqzroZwg7Lih7KCVCisg7KSR7Ius7Jew6rKw67KVKGNlbnRyb2lkIGxpbmthZ2UpIDog65GQIOq1sOynkeydmCDspJHsi6zsnYQg6rGw66as66GcIOy4oeyglQorIOyZgOuTnOyXsOqysOuylShXYXJkIGxpbmthZ2UpIDog6rWw7KeR64K0IOyYpOywqOygnOqzse2VqeydtCDqsIDsnqUg7J6R7JWE7KeA64qUIOuwqe2WpeycvOuhnCDqtbDsp5HsnYQg7ZiV7ISxCgoqKioKCiMjIyMjICoq6rCc7LK06rCEIOqxsOumrOydmCDsooXrpZgqKgoKKyAqKuuvvOy9lOyasOyKpO2CpChNaW5Lb3dza2kpIOqxsOumrCoqIDogJERpc3QoeCx5KT1bXHN1bSh4X3tyaX0teF97c2l9KV5wXV57MS9wfSQKCisgKirrp6jtlZjtirwg6rGw66asKiogOiDrkZAg7KCQIOqwhOydmCDssKjsnbTsnZgg7KCI64yA6rCS7J2EIO2Vqe2VnCDqsJIsIHI9MeydvCDrlYzsnZgg66+87L2U7Jqw7Iqk7YKk7J2YIOqxsOumrOyZgCDqsJnsnYwuIAokRGlzdCh4LHkpPVxzdW18eF9pLXlfaXwkCgorICoq7Jyg7YG066as65OcIOqxsOumrCoqIDog66+87L2U7Jqw7Iqk7YKk6rGw66as7J2YIO2KueuzhCDqsr3smrAocj0yKSDsnpDro4zsnZgg67aE7Y+s7KCBIO2KueyEseydhCDqs6DroKTtlaAg7IiYIOyXhuycvOupsCwg64uo7JyEIOuYkO2VnCDqsJnslYTslbztlZzri6TripQg64uo7KCQ7J20IOyhtOyerC4gJERpc3QoeCx5KT1cc3FydHtcc3VtKHhfe3JpfS14X3tzaX0pXjJ9PVxzcXJ0e1tYX3ItWF9zXV5UW1hfci1YX3NdfSQKCisgKirssrTruYTshbDtlIQg6rGw66asKD1tYXhpbXVtIGRpc3RhbmNlKSoqIDog65GQIOynkeuLqOyXkOyEnCDqsIDsnqUg6ri0IOyngOygkOyXkOyEnOydmCDqsbDrpqwgJG1heCh8eF8xLXlfMXwsXGNkb3RzLHx4X3AteV9wfCQKCisgKirtkZzspIDtmZQg6rGw66asKiogOiDsnKDtgbTrpqzrk5wg6rGw66as66W8IOqzteu2hOyCsOycvOuhnCDrgpjriIgg6rGw66asICRbKFhfci1YX3MpXlREXnstMX0oWF9yLVhfcyldXnsxLzJ9ICwgRD1kaWFnKFNfezExfSxcY2RvdHMsU197cHB9KSQKCisgKirrp4jtlaDrnbzrhbjruYTsiqQoTWFoYWxhbm9iaXMpIOqxsOumrCoqIDog6rO167aE7IKwIOq1rOyhsOulvCDtlajqu5gg6rOg66Ck7ZWcIO2GteqzhOyggSDqsbDrpqwsIOyekOujjOydmCDrtoTtj6zsoIEg7Yq57ISx7J2EIOqzoOugpO2VmOq4sCDsnITtlZwg67Cp67KVLiAkWyhYX3ItWF9zKV5UU157LTF9KFhfci1YX3MpXV57MS8yfSAsIFM97ZGc67O46rO167aE7IKw7ZaJ66CsJAoKKuywuOqzoDrqs7XrtoTsgrDsnbQg64uo7JyE7ZaJ66Cs7J20IOuQmOuptCDsnKDtgbTrpqzrk5zqsbDrpqzsmYAg6rCZ7JWE7KeA64qU642wIOydtOufrO2VnCDrs4DtmZjsnYQg7ZmU7J207Yq464ud67OA7ZmY7J206528IO2VnOuLpC4qIAoKKyAqKuy6lOuyhOudvCDqsbDrpqwgKiogOiDrp6jtlZjtirwg6rGw66as7JeQIOqwgOykkey5mOulvCDsoIHsmqntlZwg6rGw66as7J2YIOqwnOuFkCAkZGlzdCh4LHkpPVxzdW1cZnJhY3t8eF9pLXlfaXx9e3hfaS15X2l9JAoKbWV0aG9k7JeQIGV1Y2xpZGVhbiwgbWF4aW11bSwgbWFuaGF0dGFuLCBjYW5iZXJhLCBtaW5rb3dza2kg65Ox7J20IOuTpOyWtOqwiCDsiJgg7J6I64ukLiAKCmBgYHtyfQp4MT1ybm9ybSgzMCkKeDI9cm5vcm0oMzApCmRpc3QocmJpbmQoeDEseDIpLG1ldGhvZD0nZXVjbGlkZWFuJykKYGBgCgoKKyAqKuy9lOyCrOyduCDqsbDrpqwqKiA6IOy9lOyCrOyduCDsnKDsgqzrj4TripQg66y06rKM64KYIO2BrOq4sOuKlCDsoITtmIAg6rOg66Ck7ZWY7KeAIOyViuqzoCDrsqHthLAg7IKs7J207J2YIOqwgeuPhOunjOycvOuhnCDsuKHsoJXtlZjripQg6rKD6rO8IOycoOyCrCAkY29zKFhfcixYX3MpPVxmcmFje1hfclx0aW1lcyBYX3N9e3xYX3J8fFhfc3x9JAoKYGBge3J9CiPqsbDrpqzqs4TsgrAg7Yyo7YKk7KeACmxpYnJhcnkoJ2xzYScpCngxPXNhbXBsZShjKDAsMSksMzAscmVwbGFjZSA9IFQpCngyPXNhbXBsZShjKDAsMSksMzAscmVwbGFjZSA9IFQpCiPsvZTsgqzsnbgg6rGw66asIOqzhOyCsApjb3NpbmUoeDEseDIpCmBgYAoKKyAqKu2UvOyWtOyKqCDqsbDrpqwgKiogOiDtlLzslrTsiqgg7IOB6rSA6rOE7IiY7J2YIOuylOychOqwgCAtMX4x7J2066+A66GcIDHsl5DshJwg7ZS87Ja07IqoIOyDgeq0gOqzhOyImOulvCDruoAg6rCS7J2EIOqxsOumrOuhnCDsgqzsmqkKCgpoY2x1c3Qg7ZWo7IiY7J2YIG1ldGhvZCDsmLXshZjsl5DripQgIndhcmQuRCIsICJ3YXJkLkQyIiwgInNpbmdsZSIsICJjb21wbGV0ZSIsICJhdmVyYWdlIiwgIm1jcXVpdHR5IiwgIm1lZGlhbiIgb3IgImNlbnRyb2lkIuydtCDsnojri6QuCgoqKioKCiMjIyMgQ29kZQoKKirsnpDro4wg7ISk66qFKioK66+46rWtIOuCtCA1MOqwnCDso7zsnZgxOTcz64WE7JeQIOuwnOyDne2VnCDrspTso4TqtIDroKgg7Ya16rOE7J6Q66OMCgorIE11cmRlciA6IDEw66eM66qFIOuLuSDsgrTsnbjso4Qg7IiYCisgQXNzYXVsdCA6IDEw66eM66qFIOuLuSDtj63tlonso4Qg7IiYCisgVXJiYW5Qb3AgOiDrj4Tsi5zsl5Ag7IK06rOgIOyeiOuKlCDsnbjqtawg67mE7JyoCisgUmFwZSA6IDEw66eM66qF64u5IOyEseuylOyjhCDsiJgKCmBgYHtyfQpsaWJyYXJ5KGNsdXN0ZXIpCmRhdGEoVVNBcnJlc3RzKQpzdHIoVVNBcnJlc3RzKQoKcm93bmFtZXMoVVNBcnJlc3RzKQoj7J246rWs67mE7Jyo7J20IOqwgOyepSDrhpLsnYAg7KeA7Jet7J2YIOygleuztApVU0FycmVzdHNbd2hpY2gubWF4KFVTQXJyZXN0cyRVcmJhblBvcCksXQoj7IK07J2467mE7Jyo7J20IOqwgOyepSDrhpLsnYAg7KeA7Jet7J2YIOygleuztApVU0FycmVzdHNbd2hpY2gubWF4KFVTQXJyZXN0cyRNdXJkZXIpLF0KI+yCtOyduOu5hOycqOydtCDqsIDsnqUg64qm7J2AIOyngOyXreydmCDsoJXrs7QKVVNBcnJlc3RzW3doaWNoLm1pbihVU0FycmVzdHMkTXVyZGVyKSxdCmBgYAojIyMjIyAqKuuzke2VqeyggSDrsKnrspUoMSkqKgoK7J6R7J2AIOq1sOynkeycvOuhnOu2gO2EsCDstpzrsJztlZjsl6wg7J2EIOuzke2Vqe2VtCDrgpjqsIDripQg67Cp67KVCgpgYGB7cn0KI+ycoO2BtOumrOuTnOqxsOumrCDqs4TsgrAKZCA8LWRpc3QoVVNBcnJlc3RzLCBtZXRob2Q9ImV1Y2xpZGVhbiIpCiPtj4nqt6Dsl7DqsrDrspXsnYQg7Zmc7Jqp7ZWcIOqzhOy4teyggeq1sOynkQpmaXQgPC1oY2x1c3QoZCwgbWV0aG9kPSJhdmVyYWdlIikKCiPsi5zqsIHtmZQKcGxvdChmaXQpCnBsb3QoZml0LGhhbmc9LTEpCmBgYAoKKirrs5HtlansoIEg67Cp67KVKDEpKioKCm1ldHJpYyDsmLXshZjsnYQg7Ya17ZW0IOqxsOumrOqzhOyCsOydmCDsooXrpZjrpbwg7KeA7KCV7ZW0IOykhCDsiJgg7J6I64ukLgptZXRyaWMg64yA7IugIGRhaXN57ZWo7IiY66W8IO2Gte2VtCDqsbDrpqzqs4TsgrAg6rCA64qlCnN0YW5kIOyYteyFmOydhCBUUlVF66GcIOyjvOuptCDqsbDrpqwg6rOE7IKwIOyghOyXkCDtkZzspIDtmZTrpbwg7IiY7ZaJ7ZWc64ukLgoKYGBge3J9CiPqs4TsuLXsoIEg6rWw7KeRIOq0gOugqCDtjKjtgqTsp4AKbGlicmFyeShjbHVzdGVyKQoj67OR7ZWp7KCB67Cp67KVCiNzdGFuZD1U7J2066m0IO2RnOykgO2ZlCDsp4TtlokKYWduMSA8LWFnbmVzKFVTQXJyZXN0cywgbWV0cmljPSJtYW5oYXR0YW4iLCBzdGFuZD1UUlVFKQpwbG90KGFnbjEpCiPstZzsnqXsl7DqsrDrspXsnLzroZwg6rOE7Li17KCBIOq1sOynkSDsi6TtlokKYWduMiA8LWFnbmVzKGRhaXN5KFVTQXJyZXN0cyksIGRpc3M9VFJVRSwgbWV0aG9kPSJjb21wbGV0ZSIpCnBsb3QoYWduMikKYGBgCgojIyMjIyAqKuuzke2VqeyggeuwqeuylSgyKSoqCgoo67aE7ZWg7KCBIOuwqeuylSkg7YGwIOq1sOynkeycvOuhnOu2gO2EsCDqtbDsp5HsnYQg67aE66as7ZW0IOuCmOqwgOuKlCDrsKnrspUKCmBgYHtyfQpsaWJyYXJ5KGNsdXN0ZXIpCiPrtoTtlaDsoIEg67Cp67KVCiNzdGFuZD1U7J2066m0IO2RnOykgO2ZlCDsp4TtlokKYWduMSA8LWRpYW5hKFVTQXJyZXN0cywgbWV0cmljPSJtYW5oYXR0YW4iLCBzdGFuZD1UUlVFKQpwbG90KGFnbjEpCiPstZzsnqXsl7DqsrDrspXsnLzroZwg6rOE7Li17KCBIOq1sOynkSDsi6TtlokKYWduMiA8LWRpYW5hKGRhaXN5KFVTQXJyZXN0cyksIGRpc3M9VFJVRSwgbWV0cmljID0iY29tcGxldGUiKQpwbG90KGFnbjIpCmBgYAoKCiMjIyBLLe2Pieq3oCDqtbDsp5Eoay1tZWFucyBjbHVzdGVyaW5nKQoKS+qwnCjsm5DtlZjripQg6rWw7KeR7IiYKeydmCDstIjquLDqsJLsnYQg7KeA7KCV7ZWY6rOgIOqwgSDsnpDro4zrpbwg6rCA6rmM7Jq0IOy0iOq5g+qwkuyXkCDtlaDri7ntlZjsl6wg6rWw7KeR7J2EIO2YleyEse2VnCDrkqQsIOqwgSDqtbDsp5HsnZgg7Y+J6reg7J2EIOqzhOyCsO2VmOyXrCDstIjquYPqsJLsnYQg6rCx7Iug7ZWY64qUIOqyg+ydhCDrsJjrs7XtlZjripQg6rWw7KeRIOuwqeuylQoK7KeR64uoIOuCtCDsoJzqs7HtlansnZgg6re4656Y7ZSE66W8IO2Gte2VnCDqtbDsp5HsiJgg6rKw7KCVCgojIyMjIGvsl5Ag7ZW064u57ZWY64qUIOy0iOq5g+qwkuydhCDstZzsoIHtmZQg7ZWY64qUIOuwqeuylQoKYGBge3J9CiPsp5HtlanrgrQg7J6U7LCo7KCc6rOx7ZWp7J2EIO2ZnOyaqe2VmOyXrCDqtbDsp5Eg7IiYIOy1nOygge2ZlAp3c3NwbG90PC1mdW5jdGlvbihkYXRhLCBuYz0xNSwgc2VlZD0xMjM0KXsKd3NzPC0obnJvdyhkYXRhKS0xKSpzdW0oYXBwbHkoZGF0YSwyLHZhcikpCmZvciAoaSBpbiAyOm5jKXsKc2V0LnNlZWQoc2VlZCkKd3NzW2ldIDwtc3VtKGttZWFucyhkYXRhLCBjZW50ZXJzPWkpJHdpdGhpbnNzKX0KcGxvdCgxOm5jLCB3c3MsIHR5cGU9ImIiLCB4bGFiPSJOdW1iZXIgb2YgQ2x1c3RlcnMiLAp5bGFiPSJXaXRoaW4gZ3JvdXBzIHN1bSBvZiBzcXVhcmVzIil9Cgp3c3NwbG90KFVTQXJyZXN0cykKYGBgCnBhY2thZ2Xrpbwg7J207JqpCgpgYGB7cn0KbGlicmFyeShOYkNsdXN0KQpzZXQuc2VlZCgxMjM0KQpuYz1OYkNsdXN0KFVTQXJyZXN0cyxtaW4ubmM9MixtYXgubmM9MTUsbWV0aG9kPSdrbWVhbnMnKQpuYyRCZXN0Lm5bMSwsZHJvcD1GXQp3aGljaC5tYXgodGFibGUobmMkQmVzdC5uWzEsXSkpCmBgYAoKYGBge3J9CnNldC5zZWVkKDEyMzQpCiMy6rCc7J2YIOq1sOynkSDsg53shLEKZml0LmttIDwta21lYW5zKFVTQXJyZXN0cywgMiwgbnN0YXJ0PTI1KQpmaXQua20kc2l6ZQoj7Iuc6rCB7ZmUCnBsb3QoVVNBcnJlc3RzLCBjb2w9Zml0LmttJGNsdXN0ZXIpCiPqtbDsp5Hrs4Qg67OA7IiY67OEIO2Pieq3oOqwkgphZ2dyZWdhdGUoVVNBcnJlc3RzLCBieT1saXN0KGNsdXN0ZXI9Zml0LmttJGNsdXN0ZXIpLCBtZWFuKQpgYGAKCiMjIyDrsIDrj4TquLDrsJjqtbDsp5EoREJTQ0FOKQoK67CA64+EIOq4sOuwmOydmCDtgbTrn6zsiqTthLDrp4HsnYAg7KCQ7J20IOyEuOuwgO2VmOqyjCDrqrDroKQg7J6I7Ja07IScIOuwgOuPhOqwgCDrhpLsnYAg67aA67aE7J2EIO2BtOufrOyKpO2EsOungSDtlZjripQg67Cp7IudLiAqKmVwc2lsb24g67CY6rK9IOuCtOyXkCDstZzshowgbuqwnOydmCDqsJzsiJjsnZgg7KCQ7J20IOyeiOycvOuptCDqtbDsp5HsnLzroZwg7J247IudKioKCmRic2NhbigpIDog67CA64+E6riw67CYIOq1sOynkeydhCDsg53shLHtlZjripQg7L2U65OcLCBlcHPsmYAgTWluUHRz66W8IOyYteyFmOycvOuhnCDso7zslrTslbwg7ZWY66mwLCBlcHPripQgZXBzaWxvbiwgTWluUHRz64qUIOuwmOqyvSDrgrTsnZgg7LWc7IaMIOygkOydmCDqsJzsiJjrpbwg7J2Y66+47ZWc64ukLgoKCmBgYHtyfQpsaWJyYXJ5KGZwYykKI+uwgOuPhOq4sOuwmCDqtbDsp5EKdGVzdC5kYnNjYW4xIDwtZGJzY2FuKFVTQXJyZXN0cywgZXBzPTIwLCBNaW5QdHM9NykKbiA8LW1heCh0ZXN0LmRic2NhbjEkY2x1c3RlcikKZHNjZW50ZXI8LWFzLmRhdGEuZnJhbWUobWF0cml4KDAsIG5yb3c9MSwgbmNvbD0yKSkKZm9yIChpIGluIDE6bil7CiAgZHNjZW50ZXJbaSxdIDwtY29sTWVhbnMoVVNBcnJlc3RzW3Rlc3QuZGJzY2FuMSRjbHVzdGVyID09IGksXSkKfQpwbG90KFVTQXJyZXN0cywgY29sPXRlc3QuZGJzY2FuMSRjbHVzdGVyKzEpCmBgYAoKIyMjIOq1sOynkSDrsKnrspUg6rCEIOu5hOq1kCAKCiMjIyMg67CA64+E6riw67CYIOq1sOynkeqzvCBrLW1lYW5zIOq1sOynkeqwhCDruYTqtZAKCgpgYGB7cn0KI+yVhOuemCDqt7jrprzqs7wg6rCZ7J2AIOu2hO2PrOydmCDsnpDro4wg7IOd7ISxICh0ZXN0LnNhbXBsZTEpCgpzZXQuc2VlZCg3OTc5KQpnZXQuc2FtcGxlPC1mdW5jdGlvbiAobj0xMDAwLCBwPTAuNykgewp4MSA8LXJub3JtKG4pCnkxIDwtcm5vcm0obikKcjIgPC03K3Jub3JtKG4pCnQyIDwtcnVuaWYobiwwLDIqcGkpCngyIDwtcjIqY29zKHQyKQp5MiA8LXIyKnNpbih0MikKciA8LXJ1bmlmKG4pPnAKeCA8LWlmZWxzZShyLHgxLHgyKQp5IDwtaWZlbHNlKHIseTEseTIpCmQgPC1kYXRhLmZyYW1lKHg9eCwgeT15KQpkCn0KdGVzdC5zYW1wbGUxIDwtZ2V0LnNhbXBsZSgpCgoKcGxvdCh0ZXN0LnNhbXBsZTEpCmxpYnJhcnkoS2VyblNtb290aCkKciA8LWJrZGUyRCh0ZXN0LnNhbXBsZTEsIGJhbmR3aWR0aD1jKDAuNSwwLjUpKQpwZXJzcChyJGZoYXQsIHNjYWxlPVQsIGV4cGFuZD0wLjMsIGNvbD0id2hpdGUiLCB0aGV0YSA9IDMwLCBwaGkgPSAzMCkKYGBgCgoKYGBge3J9CmxpYnJhcnkoZnBjKQoj67CA64+E6riw67CYIOq1sOynkQp0ZXN0LmRic2NhbjEgPC1kYnNjYW4odGVzdC5zYW1wbGUxLCBlcHM9MC44LCBNaW5QdHM9NSkKbiA8LW1heCh0ZXN0LmRic2NhbjEkY2x1c3RlcikKZHNjZW50ZXI8LWFzLmRhdGEuZnJhbWUobWF0cml4KDAsIG5yb3c9MSwgbmNvbD0yKSkKZm9yIChpIGluIDE6bil7CmRzY2VudGVyW2ksXSA8LWNvbE1lYW5zKHRlc3Quc2FtcGxlMVt0ZXN0LmRic2NhbjEkY2x1c3RlciA9PSBpLF0pCn0KcGxvdCh0ZXN0LnNhbXBsZTEsIGNvbD10ZXN0LmRic2NhbjEkY2x1c3RlcisxLG1haW49J0RCU0NBTicpCiNrIG1lYW5zIOq1sOynkQp0ZXN0LmttZWFuczEgPC1rbWVhbnModGVzdC5zYW1wbGUxLCBjZW50ZXJzPTIpCnBsb3QodGVzdC5zYW1wbGUxLCBjb2w9dGVzdC5rbWVhbnMxJGNsdXN0ZXIrMSxtYWluPSdrLW1lYW5zJykKYGBgCgoKIyMjIyDqsIEg6rWw7KeRIOuwqeuyleydmCDsnqXri6jsoJAg67CPIOu5hOq1kAoKKirsnqXsoJAqKgoKKyBrLe2Pieq3oCDqtbDsp5EgOiDri6Trpbgg6rWw7KeRIOuwqeuyleuztOuLpCDrp47snYAg7JaR7J2YIOyekOujjOulvCDri6Tro7Ag7IiYIOyeiOuLpC4KKyDrsIDrj4TquLDrsJgg6rWw7KeRIDog7Yq57J207ZWcIO2Yle2DnOydmCDqtbDsp5HsnYQg7LC+64qU642wIOycoOumrO2VmOupsCwg7J6h7J2M7J2EIO2PrO2VqO2VnCDrjbDsnbTthLDrpbwg64uk66Oo64qU642wIOygge2VqSAKCioq64uo7KCQKioKCisgay3tj4nqt6Ag6rWw7KeRIDog7LSI6riw6rCSLCDsnbTsg4HsuZjsl5Ag7JiB7Zal7J2EIOunjuydtCDrsJvsnLzrqbAsIOuzvOuhne2VnCDtmJXtg5zqsIAg7JWE64uMIOq1sOynkeyXkCDrjIDtlbQg7ISx64ql7J20IOuWqOyWtOynkAorIOuwgOuPhOq4sOuwmCDqtbDsp5EgOiDqsr3qs4TsoJDsl5Ag64yA7ZWY7JesIOuRkCDqtbDsp5Eg66qo65GQ7JeQIOyGje2VoCDsiJgg7J6I64uk64qUIOusuOygnOqwgCDsobTsnqwuIOqzoOywqOybkOyekOujjOydmOqyveyasOKAmOywqOybkOydmOyggOyjvChjdXJzZSBvZiBkaW1lbnNpb25hbGl0eSkn66Gc7J247ZW06rGw66as7Lih64+E6rCA66y07J2Y66+47ZWY6rGw64KYLCDsnbTroZzsnbjtlbTsoIHsoIjtlZzrsJjqsr0o8J2clinsnYTssL7quLDslrTroLXri6QuCgojIyMjIGlyaXMg7J6Q66OM7JeQIOuMgO2VnCDrqqjtmJUg67mE6rWQCgpgYGB7cn0KaXJpczI9aXJpc1ssMTo0XQoKZGlzdD1kaXN0KGlyaXMyKQoj6rOE7Li17KCBIOq1sOynkQpoY2w9aGNsdXN0KGRpc3QsbWV0aG9kPSdhdmVyYWdlJykKI+q1sOynkSDsiJggM+qwnOuhnCDshKTsoJUKY3V0PWN1dHJlZShoY2wsaz0zKQoj6rWw7KeRIOyLnOqwge2ZlApwbG90KGhjbCxoYW5nPS0xLGNleD0uOCkKcmVjdC5oY2x1c3QoaGNsLGs9MykKI+yLpOygnCDsnpDro4zsnZgg67aE66WYCnBsb3QoaXJpc1ssLTVdLGNvbD1pcmlzJFNwZWNpZXMpCiPqs4TsuLXsoIEg6rWw7KeRIOu2hOulmCDsi5zqsIHtmZQKcGxvdChpcmlzWywtNV0sY29sPWN1dCxwY2g9Y3V0KQoj67aE66WYIOu5hOycqApjbGFzcz1hcy5mYWN0b3IoY3V0KQpsZXZlbHMoY2xhc3MpPSBsZXZlbHMoaXJpcyRTcGVjaWVzKQp0YWJsZShjbGFzcykKI+ygleyYpOu2hOulmO2RnAp0YWJsZShjbGFzcyxpcmlzJFNwZWNpZXMpCgoKCmxpYnJhcnkoTmJDbHVzdCkKc2V0LnNlZWQoMTIzNCkKI2ttZWFucyDqtbDsp5EKZml0LmttIDwta21lYW5zKGlyaXNbLC01XSwgMywgbnN0YXJ0PTI1KQpmaXQua20kc2l6ZQoKI+yLpOygnCDsnpDro4zsnZgg67aE66WYCnBsb3QoaXJpc1ssLTVdLGNvbD1pcmlzJFNwZWNpZXMpCgoja21lYW4g6rWw7KeR7J2YIOu2hOulmCAKcGxvdChpcmlzWywtNV0sIGNvbD1maXQua20kY2x1c3RlcikKI2ttZWFuIOq1sOynkeydmCDrtoTrpZjrs4Qg7Y+J6regCmFnZ3JlZ2F0ZShpcmlzWywtNV0sIGJ5PWxpc3QoY2x1c3Rlcj1maXQua20kY2x1c3RlciksIG1lYW4pCgoj67aE66WY67mE7JyoCmNsYXNzPWFzLmZhY3RvcihmaXQua20kY2x1c3RlcikKbGV2ZWxzKGNsYXNzKT0gbGV2ZWxzKGlyaXMkU3BlY2llcykKdGFibGUoY2xhc3MpCgoj7KCV7Jik67aE66WY7ZGcCnRhYmxlKGNsYXNzLGlyaXMkU3BlY2llcykKCgpsaWJyYXJ5KGZwYykKI+uwgOuPhOq4sOuwmCDqtbDsp5EKdGVzdC5kYnNjYW4xIDwtZGJzY2FuKGlyaXNbLC01XSwgZXBzPTAuOCwgTWluUHRzPTUpCm4gPC1tYXgodGVzdC5kYnNjYW4xJGNsdXN0ZXIpCmRzY2VudGVyPC1hcy5kYXRhLmZyYW1lKG1hdHJpeCgwLCBucm93PTEsIG5jb2w9MikpCmZvciAoaSBpbiAxOm4pewpkc2NlbnRlcltpLF0gPC1jb2xNZWFucyhpcmlzWywtNV1bdGVzdC5kYnNjYW4xJGNsdXN0ZXIgPT0gaSxdKQp9Cgoj7Iuk7KCcIOyekOujjOydmCDrtoTrpZgKcGxvdChpcmlzWywtNV0sY29sPWlyaXMkU3BlY2llcykKI+uwgOuPhOq4sOuwmOq1sOynkOydmCDrtoTrpZgKcGxvdChpcmlzWywtNV0sIGNvbD10ZXN0LmRic2NhbjEkY2x1c3RlcisxKQoKI+u2hOulmOu5hOycqApjbGFzcz1hcy5mYWN0b3IodGVzdC5kYnNjYW4xJGNsdXN0ZXIrMSkKbGV2ZWxzKGNsYXNzKT0gbGV2ZWxzKGlyaXMkU3BlY2llcykKdGFibGUoY2xhc3MpCgoj7KCV7Jik67aE66WY7ZGcCnRhYmxlKGNsYXNzLGlyaXMkU3BlY2llcykKCgpsaWJyYXJ5KGZwYykKI+uwgOuPhOq4sOuwmCDqtbDsp5Eo7KCB7J2AIOuzgOyImOuhnCkKdGVzdC5kYnNjYW4xIDwtZGJzY2FuKGlyaXNbLC1jKDEsNDo1KV0sIGVwcz0wLjgsIE1pblB0cz03KQp0ZXN0LmRic2NhbjEKCm4gPC1tYXgodGVzdC5kYnNjYW4xJGNsdXN0ZXIpCm4KZHNjZW50ZXI8LWFzLmRhdGEuZnJhbWUobWF0cml4KDAsIG5yb3c9MSwgbmNvbD0yKSkKZm9yIChpIGluIDE6bil7CiAgZHNjZW50ZXJbaSxdIDwtY29sTWVhbnMoaXJpc1ssLTVdW3Rlc3QuZGJzY2FuMSRjbHVzdGVyID09IGksXSkKfQoKI+yLpOygnOyekOujjOydmCDrtoTrpZgKcGxvdChpcmlzWywtNV0sY29sPWlyaXMkU3BlY2llcykKCiPrsIDrj4TquLDrsJjqtbDsp5HsnZgg67aE66WYCnBsb3QoaXJpc1ssLTVdLCBjb2w9dGVzdC5kYnNjYW4xJGNsdXN0ZXIrMSkKCgoj67aE66WY67mE7JyoCmNsYXNzPWFzLmZhY3Rvcih0ZXN0LmRic2NhbjEkY2x1c3RlcisxKQpsZXZlbHMoY2xhc3MpPSBsZXZlbHMoaXJpcyRTcGVjaWVzKQp0YWJsZShjbGFzcykKCiPsoJXsmKTrtoTrpZjtkZwKdGFibGUoY2xhc3MsaXJpcyRTcGVjaWVzKQoKCgoKYGBgCg==