Yapay öğrenme’nin belki de ilk dersi Doğrusal Bağlanım (Lineer Regression)’dır. Andrew Ng’nin Coursera’daki dersinden esinlenerek basit bir çalışma yapacağız. Öncelikle timsahların ağırlığı ile burun uzunluğu arasındaki ilişkiyi tutan bir veriyi inceleyeceğiz. Ağırlık ve uzunluk arasındaki ilişkiyi, doğrusal bir denklem ile açıklayacağız. Bu işe doğrusal bağlanım ya da ingilizce adıyla lineer regression diyoruz. Çalışmamız iki aşamadan oluşuyor.

Verimizi Oluşturalım

Aşağıdaki veriyi r-bloggers’dan aldım.

uzunluk = c(3.87, 3.61, 4.33, 3.43, 3.81, 3.83, 3.46, 3.76,3.50, 3.58, 4.19, 3.78, 3.71, 3.73, 3.78)
agirlik = c(4.87, 3.93, 6.46, 3.33, 4.38, 4.70, 3.50, 4.50,3.58, 3.64, 5.90, 4.43, 4.38, 4.42, 4.25)
timsah = data.frame(uzunluk,agirlik)

Veriyi Çizdirelim

plot(agirlik ~ uzunluk, data = timsah,
  xlab = "Burun Uzunluğu",
  ylab = "Ağırlık",
  main = "Central Florida'daki Timsahlar"
)
grid(5, 5, lwd = 2)

Veriyi çizdiğimizde, burun uzunluğu ve ağırlık arasında doğrusal bir ilişkiyi saptıyabiliyoruz değil mi? Görünüşe göre, burun uzunluğu arttıkça, ağırlık da artıyor.

R Hazır İşlevleri ile Doğrusal Bağlanım

R ile bağlanım oldukça kolay. Bunun içinde aşağıdaki gibi lm() fonksiyonu kulanılır. summary() ile model parametrelerini elde edebiliriz.

Kolay.Baglanim = lm(agirlik ~ uzunluk, data = timsah)
summary(Kolay.Baglanim)

Call:
lm(formula = agirlik ~ uzunluk, data = timsah)

Residuals:
     Min       1Q   Median       3Q      Max 
-0.24348 -0.03186  0.03740  0.07727  0.12669 

Coefficients:
            Estimate Std. Error t value Pr(>|t|)    
(Intercept)  -8.4761     0.5007  -16.93 3.08e-10 ***
uzunluk       3.4311     0.1330   25.80 1.49e-12 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 0.1229 on 13 degrees of freedom
Multiple R-squared:  0.9808,    Adjusted R-squared:  0.9794 
F-statistic: 665.8 on 1 and 13 DF,  p-value: 1.495e-12

Burada lm(agirlik ~ uzunluk, data = timsah) komutunu kullanıyoruz. ~ işareti bağımlı ve bağımsız değişkenleri birbirinden ayırmak için kullanılır. Yukarıda pekçok bilgi var ama şu aşamada biz aşağıdaki bilgilerle ilgileniyoruz.

theta0 = Kolay.Baglanim$coefficients['(Intercept)']
theta1 = Kolay.Baglanim$coefficients['uzunluk']
print(c(theta0, theta1))
(Intercept)     uzunluk 
  -8.476067    3.431098 

Paramtrelerimiz şu şekilde,

Peşinde olduğumuz matematiksel denklem ise, \[y = \theta_0 + \theta_1 x \]

Değerleri yerine yazarsak

\[ agirlik = -8.4761 + 3.4311 * uzunluk\]

Bu doğrusal ilişkiyi verimizle birlikte çizdirelim.

plot(agirlik ~ uzunluk, data = timsah,
  xlab = "Burun Uzunluğu",
  ylab = "Ağırlık",
  main = "Central Florida'daki Timsahlar"
)
x = seq(2,6,0.5)
lines(x, (theta0 + theta1 * x), col = "red")
grid(5, 5, lwd = 2)

Ufak Bir Tahmin Yapalım mı?

Artık elimizde, veriyi açıklayan doğrusal denklemin parametreleri olduğuna göre, tahminde bulunabiliriz. Uzunluk 4’e eşitse Ağırlık ne olacaktır?

x.nokta = 4
tahmin = as.numeric(theta0 + theta1 * x.nokta)
print(tahmin)
[1] 5.248326
plot(agirlik ~ uzunluk, data = timsah,
  xlab = "Burun Uzunluğu",
  ylab = "Ağırlık",
  main = "Central Florida'daki Timsahlar"
)
x = seq(2,6,0.5)
lines(x, (theta0 + theta1 * x), col = "red")
# Tahminimizi büyük bir pembe nokta olarak çizelim
points(x.nokta, tahmin, col= 'pink', lwd= 23)
# Noktadan x ve y eksenlerine dik pembe çizgiler indirelim
segments(x.nokta, 0, x.nokta, tahmin, col= 'pink', lty = "dashed")
segments(0, tahmin, x.nokta, tahmin, col= 'pink', lty = "dashed")
grid(5, 5, lwd = 2)

İleri Seviye - Doğrusal Bağlanım

Bu işi R’ın hazır doğrusal bağlanım fonksiyonunu kullanmadan yapabilir miyiz?

Model

Verimizde \(x\) bağımsız değişken olmak üzere, \(x^{(1)}\) birinci \(x\) gözlemimiz, \(x^{(2)}\) ikinci \(x\) gözlemimiz, .. \(x^{(i)}\) de \(i\)’inci gözlemimiz olsun. Aşağıdaki gibi bir modelimiz olduğunu düşünelim.

\[ h_\theta (x^{(i)})= \theta_0 + \theta_1 x^{(i)} \] Biz en iyi \((\theta_0, \theta_1 )\) değerlerini arıyoruz. Diğer bir ifadeyle, hatayı minimize eden \((\theta_0, \theta_1 )\) değerlerini arıyoruz.

Hata Fonksiyonu

Modelimizin ürettiği \(i\)’inci tahmin \(h_\theta(x^{(i)})\) ile gerçek çıktı değeri \(y^{(i)}\) arasındaki farkın mümkün olduğunca az olmasını istiyoruz. Bunun için \(h_\theta(x^{(i)}) - y^{(i)}\) farklarının karesini minimize eden \((\theta_0, \theta_1 )\) değerlerini arayacağız. Dolayısıyla hata fonksiyonumuz şu şekildedir.

\[ J(\theta_0, \theta_1 ) = \frac{1}{2m} \sum^m_i \left(h_\theta(x^{(i)}) - y^{(i)} \right)^2 \] Burdaki 2, türev alındığında sadeleşecek, m ise ortalama hatayı bulmak amacıyla kullanılmıştır. Şimdi, R ile hipotez’imizin hatasını döndüren bir fonksiyon yazalım.

hipotez <- function(theta0 = -8.4761, theta1, veri = timsah){
  x = veri[,1] # Bağımsız değişken
  y = veri[,2] # Bağımlı değişken
  m = length(x)
  h = theta0  + x * theta1
  hata = (1 / (2*m)) * sum((h - y)^2) # hatanın hesaplanması 
  return(hata)
}

Varsayılan argüman olarak veri = timsah dediğimiz için, farklı bir veri kullanmak istemediğimiz sürece timsah verisini tekrar yazmamıza gerek yok. Burada anlatımı basitleştirmek için, \(\theta_0 = −8.4761\) kabul ederek yolumuza devam ediyoruz. Aşağıdaki örnekte \(\theta_1 = 3\) için hata değerini görüyoruz.

hata = hipotez(theta1 = 3)
hata
[1] 1.324207

Şimdi Farklı \(\theta_1\) değerleri için hata fonksiyonunu çizdirelim.

theta.degerleri = seq(0,7,0.05)
hatalar = theta.degerleri * 0
for (i in 1:length(theta.degerleri)){
  hatalar[i] = hipotez(theta1 = theta.degerleri[i])
}
plot(theta.degerleri, hatalar, type = "l", ylab = expression(J(theta[0], theta[1])), xlab=expression(theta[1]), lwd = 3)
grid(5, 5, lwd = 2)

Aa, hata fonksiyonumuz bir parabol çıktı. :) Aslında bu oldukça beklenen bir durum, çünkü hata fonksiyonuz \(J(\theta_0, \theta_1 )\) gerçekten de ikinci dereceden bir parabol denklemidir. Bu tür bir quadratik denklemin minimum ya da maksimum noktası olacaktır. Biz bu noktayı arıyoruz. Sizce hata fonksiyonumuz, hangi \(\theta_1\) değeri için minimum oluyor? Daha önce bulduğumuz \(\theta_1 = 3.4311\) değeri olabilir mi?

plot(theta.degerleri, hatalar, type = "l", ylab = expression(J(theta[0], theta[1])),    xlab=expression(theta[1]), lwd = 3)
# theta1= 3.4311'den bir dikme çekelim
segments(theta1, 0, theta1, 100, col= 'pink', lty = "dashed", lwd = 3)
grid(5, 5, lwd = 2)

Optimizasyon ile Öğrenmek Arasındaki İnce Çizgi

Optimizasyon ile öğrenmek arasında ince bir çizgi var. Çünkü, en iyi paramatre değerlerini öğrenmek ile optimizasyon aynı şeydir. \(J(\theta_0, \theta_1 )\) hata fonksiyonunu en küçük değerini arıyoruz. Bunu nasıl bulabiliriz? Basitlik açısından, \(\theta_0 = −8.4761\) kabul ederek boyut sayısını 3’ten 2’ye indirelim. Farklı \(\theta_1\) değerleri için \(J(\theta_0, \theta_1 )\)’in nerede en küçük değere ulaşacağını bulmaya çalışacağız. Bunun için \(J(\theta_0, \theta_1 )\) türevini alıp sıfıra eşitlemek asıl yapılması gereken şeydir. Bilgisayar biz programlamadığımız sürece, kendiliğinden türev alıp minimum noktayı bulamaz. Bilgisayara bu işi nasıl yaptıracağımızı görelim.

Gradient Descent -Dereceli İniş- Yöntemi

En iyi \(\theta_1\) değerini bulmak için rastgele bir değerden başlayıp, türevin tersi yönde adım adım ilerlersek, hedefimize ulaşabiliriz.

\[\theta_1^{(t+1)} = \theta_1^{(t)} - \alpha \frac{\partial}{\partial \theta_1} J(\theta_0, \theta_1 )\]

Yukarıdaki fonksiyon aracılığı ile, \(\theta_1\)’in değerini nasıl güncelleyeceğimizi tanımladık. Bu yöntemin adı, dereceli iniş - gradient descent - yöntemidir. Negatif \(\frac{\partial}{\partial \theta_1} J(\theta_0, \theta_1 )\) diyerek, türevin tersi yönünde (yani akıntı ile birlikte) \(\alpha\) adım (kulaç) büyüklüğü ile ilerliyoruz (yüzüyoruz). Burada dikkat edilmesi gereken nokta, algoritmamızın \(J(\theta_0, \theta_1 )\) amaç-hata fonksiyonun türevlenebilir olmasıdır. Ya amaç fonksiyonumuz türevlenebilir olmasaydı ne yapardık?

  • Heuristic-Sezgisel Yöntemlere (örneğin, Genetik Algoritmalara) başvurmamız gerekirdi. Bu konuyu başka bir yazıya bırakalım.
    • Arşimet banyonda çıkıp, Heuristic kelimesinin fiil hali olan eureka (keşfettim) diye bağırmıştır.
    • Sezgisel algoritmalar, en iyi çözümü bulmayı garanti edemez.
    • Sonsuz bir uzayda, en iyi çözümü değil, yeterince iyi bir çözümü arıyor oluruz. Bu bakış açısı çok daha gerçekçidir. (En iyi eşi ya da en iyi işi bulamazsınız, yeterince iyi olanla idare etmelisiniz :) yanılıyor muyum? İsterseniz arayın.. Hayatınızın sonuna kadar arayıp gene de hiç bir şey bulamayabilirsiniz!!)

Neyse bu konuyu kapatıp, biz işimize dönelim. :)

Kısmi Türevler

Hata fonksiyonun türevini bulalım.

\[ \begin{split} \frac{\partial}{\partial \theta_j} J(\theta_0, \theta_1 ) & = \frac{\partial}{\partial \theta_j} \left( \frac{1}{2m}\sum^m_i \left(h_\theta(x^{(i)}) - y \right)^2 \right) \\ & = \frac{1}{2m} \sum^m_i \frac{\partial}{\partial \theta_j} \left(h_\theta(x^{(i)}) - y \right)^2 \\ & = \frac{1}{2m} \sum^m_i \frac{\partial}{\partial \theta_j} \left(\theta_0 + \theta_1 x^{(i)} - y \right)^2 \end{split} \]

Ufak bir hatırlatma yapalım. Bileşike fonksiyonun türevini nasıl buluyorduk? \[ (f o g)'(x) = f' \left(g(x) \right) * g'(x) \]

Buna göre, \(\theta_0\)’a göre kısmi türev

\[ \frac{\partial}{\partial \theta_0} J(\theta_0, \theta_1 ) = \frac{1}{2m} \sum^m_i 2 \left(\theta_0 + \theta_1 x^{(i)} - y \right) = \frac{1}{m} \sum^m_i \left(h_\theta(x^{(i)}) - y \right) \]

Sadece \(\theta_1\)’a göre kısmi türev \[ \frac{\partial}{\partial \theta_1} J(\theta_0, \theta_1 ) = \frac{1}{2m} \sum^m_i 2 \left(\theta_0 + \theta_1 x^{(i)} - y \right)x^{(i)} = \frac{1}{m} \sum^m_i \left(h_\theta(x^{(i)}) - y \right)x^{(i)} \]

Bu kısmi türevlere bakıp, hangi \(\theta_0\) ve \(\theta_1\) değerleri için türevin sıfır yaptığını söyleminiz mümkün mü? Değilse, dereceli iniş yöntemine başvuracağız. Bu arada 2’lerin sadeleştiğini fark ettiniz değil mi?

Dereceli İniş Yöntemi ile Doğrusal Bağlanım

Dereceli iniş yöntemi, rastgele bir başlangıç noktasından, türevin tersi yönde \(\alpha\) adım büyüklüğü ile ilerlemek anlamına geliyor.

İniş algoritması \[\theta_i^{(t+1)} = \theta_i^{(t)} - \alpha \frac{\partial}{\partial \theta_i} J(\theta_0, \theta_1 ) \]

Şimdi mucizevi bir şekilde doğru \(\theta_0 = −8.4761\) ile başlayıp, en uygun \(\theta_1\)’in ne olduğunu bulalım. (Eğitim içeriğini kolaylaştırmak için bunu yaptık. Böylece değişken sayısını azaltarak \(J(\theta_0, \theta_1 )\)’yi iki boyutlu uzayda göreselleştirebiliyoruz.) Adım büyüklüğümüz \(\alpha = 0.043\) olsun. (Neden mi, benim ayak numaram 43 de ondan :) Tabiki bu kadar keyfi, davranmamak gerek. Çok küçük \(\alpha\) değerleri minimum noktayı bulmayı geciktirir. Çok büyük \(\alpha\) değerleri ise, sizi tam minimumu bulacakken size öyle büyük bir adım attırır ki, diğer tarafa savurabilir. Aşırı uçlarda olmayan bir adım büyüklüğü ile yola devam etmek gerekiyor. Acaba \(\theta_1\)’in nerden başlatsak? Sıfırdan başlayalım mı?

Yukarıdaki iniş algoritmasında, \(\theta_1\) için az önce bulduğumuz kısmi türevi yazalım,

\[\theta_1^{(t+1)} = \theta_1^{(t)} - \alpha \frac{1}{m} \sum^m_i \left(h_\theta(x^{(i)}) - y^{(i)} \right) \cdot x^{(i)}\]

Dikkat ederseniz, her adımda, \(1\)’den \(m\)’e kadar eğitim setindeki tüm verilere baktığımız için bu kısmen maliyeti yüksek bir hesaplama işidir.

Hesaplama

# Dereceli İniş Algoritması ile theta1'i güncelleyelim 
dereceli.inis <- function(theta0 = -8.4761, theta1, alpha = 0.043, veri = timsah){
  x = veri[,1] # Bağımsız değişken
  y = veri[,2] # Bağımlı değişken
  h = theta0  + x * theta1
  yeni.theta = theta1 - (alpha/length(x)) * (sum((h - y)*x)) # Dereceli İniş Denklemi
  return(yeni.theta)
}

Şimdi \(\theta_1^{(1)}= 0\) ile başlayalım. Ve \(\theta_1^{(2)}\), \(\theta_1^{(3)}\), \(\theta_1^{(4)}\) ve .. \(\theta_1^{(7)}\) değerlerine bakalım.

theta1.1 = 0
theta1.2 = dereceli.inis(theta1 = theta1.1)
theta1.3 = dereceli.inis(theta1 = theta1.2)
theta1.4 = dereceli.inis(theta1 = theta1.3)
theta1.5 = dereceli.inis(theta1 = theta1.4)
theta1.6 = dereceli.inis(theta1 = theta1.5)
theta1.7 = dereceli.inis(theta1 = theta1.6)
th <- c(theta1.1, theta1.2, theta1.3, theta1.4, theta1.5, theta1.6, theta1.7)
print(th)
[1] 0.000000 2.092017 2.908488 3.227140 3.351503 3.400039 3.418982

Şimdi bu \(\theta_1\) değerleri için hataları hesaplayalım.

th.hata <- th * 0
for(i in 1:length(th)){
  th.hata[i] <- hipotez(theta1 = th[i])
}
th.hata
[1] 83.470908738 12.719662204  1.942983799  0.301503216  0.051476407
[6]  0.013392859  0.007592055

Nispeten büyük bir hata ile başladık ama çok hızlı bir biçimde, hata azalarak sıfıra kadar indi. Şimdi olan biteni görselleştirelim.

# İkili Çizim
par(mfrow=c(1, 2))
# Soldaki Çizim
plot(agirlik ~ uzunluk, data = timsah,
  xlab = "Burun Uzunluğu",
  ylab = "Ağırlık",
  ylim = c(-10,10),
  main = "Central Florida'daki Timsahlar"
)
grid(5, 5, lwd = 2)
x = seq(2,6,0.5)
renkler <- c("red", "orange","gold","green", "blue", "magenta","purple")
for(i in 1:length(th)){
  h = -8.4761  + x * th[i]
  lines(x, h, col = renkler[i],lwd=2)
}
# Sağdaki Çizim
plot(theta.degerleri, hatalar, type = "l", ylab = expression(J(theta[0], theta[1])),    xlab=expression(theta[1]), main = "Hata Değeri")
points(th, th.hata, col = renkler, lwd= 5)
grid(5, 5, lwd = 2)

Yorum

Yukarıdaki görseller ne olup bittiğini anlamak adına çok faydalı. Solda veriyi açıklayan denklemleri ve sağda ise o denklemlerin hatalarını görüyoruz. \(\theta_1^{(1)} = 0\) başlangıç durumu soldaki görselde kırmızı renkli doğruya karşılık geliyor. Bu sağdaki görselde de, hata fonksiyonu üzerinde, kırmızı nokta ile gösterilmiş. Dikkat ederseniz hata oldukça fazla. Sonraki adımda ise, dereceli iniş yöntemi ile elde ettiğimiz yeni \(\theta_1^{(2)} = 2.092017\) değeri turuncu renkle gösterilmiş. Algoritmayı tekrarlı bir biçimde çalıştırarak her adımda, daha az hata yapan bir \(\theta_1^{(t)}\) elde ediyoruz, ta ki hata sıfır olana kadar. Bu sağdaki resimde, hata fonksiyonunu gösteren parabolün minimum noktasına gelmek anlamına geliyor. Biz yedinci tekrarda, \(\theta_1^{(7)} = 3.418982\) bulduk. Ve orada bıraktık. Böylece, sağdaki parabolde, mor renkli noktaya vardık. Nerdeyse minimumdayız.

\(\theta_0\) Nerede?

\(\theta_0 = -8.476067\) görmek isteseniz, soldaki resimde x ve y eksenleri ile biraz oynamamız gerekir.

# İkili Çizim
par(mfrow=c(1, 2))
# Soldaki Çizim
plot(agirlik ~ uzunluk, data = timsah,
  xlab = "Burun Uzunluğu",
  ylab = "Ağırlık",
  xlim = c(-1,5),
  ylim = c(-10,10),
  main = "Central Florida'daki Timsahlar",
  lwd = 6
)
grid(5, 5, lwd = 2)
x = seq(-1,6,0.5)
renkler <- c("red", "orange","gold","green", "blue", "magenta","purple")
for(i in 1:length(th)){
  h = -8.4761  + x * th[i]
  lines(x, h, col = renkler[i],lwd=2)
}
# Sağdaki Çizim
plot(theta.degerleri, hatalar, type = "l", ylab = expression(J(theta[0], theta[1])),    xlab=expression(theta[1]), main = "Hata Değeri")
points(th, th.hata, col = renkler, lwd= 5)
grid(5, 5, lwd = 2)

Gördüğünüz gibi bütün doğrular y eksenini, \(\theta_0 = -8.476067\)’de kesiyor. Olması gereken de bu. Çünkü, farklı \(\theta_1^{(t)}\) değeleri için çizdireceğiniz doğrularda, \(\theta_1^{(t)}= 0\) olduğunda aşağıdaki denklem \(\theta_0 = -8.476067\)’e eşit olur.

\[ h_\theta (x^{(i)})= \theta_0 + \theta_1^{(t)}x^{(i)} \]

Yeni bir veri seti

İsteseniz siz de, R’daki hazır veri kümelerinden biri olan women verisini kullanarak, doğrusal regresyon analizine devam edin.

dogrusal <-lm(weight ~ height, data=women)
summary(dogrusal)

Call:
lm(formula = weight ~ height, data = women)

Residuals:
    Min      1Q  Median      3Q     Max 
-1.7333 -1.1333 -0.3833  0.7417  3.1167 

Coefficients:
             Estimate Std. Error t value Pr(>|t|)    
(Intercept) -87.51667    5.93694  -14.74 1.71e-09 ***
height        3.45000    0.09114   37.85 1.09e-14 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 1.525 on 13 degrees of freedom
Multiple R-squared:  0.991, Adjusted R-squared:  0.9903 
F-statistic:  1433 on 1 and 13 DF,  p-value: 1.091e-14

Son Söz

Veri bilimi ile çalışmanın en güzel taraflarından biri, hiç şüphesiz öğrenmiş olduğunuz bir yapay öğrenme yönteminin biribirinden çok farklı alanlarda, birbirinde çok farklı veri setleri üzerinde çalışabilmesidir. Az önce timsahlar hakkında çalıştık, şimdi ise kadınlar hakkında neden çalışma yapmalıyım :) Çok farklılar gerçekten çok :) Kavga çıkmadan, son sözü söyleyelim. Kadınlar timsah mimsah değildir, kadınlar çiçektir.


Bu çalışmaya atıfta bulunmak isterseniz,

yazabilirsiniz. Teşekkürler

LS0tCnRpdGxlOiAiRG/En3J1c2FsIEJhxJ9sYW7EsW0iCmF1dGhvcjogRHIuIFV6YXkgw4dldGluCm91dHB1dDogaHRtbF9ub3RlYm9vawotLS0KCllhcGF5IMO2xJ9yZW5tZSduaW4gYmVsa2kgZGUgaWxrIGRlcnNpIERvxJ9ydXNhbCBCYcSfbGFuxLFtIChMaW5lZXIgUmVncmVzc2lvbiknZMSxci4gQW5kcmV3IE5nJ25pbiBDb3Vyc2VyYSdkYWtpIFtkZXJzaW5kZW5dKGh0dHBzOi8vd3d3LmNvdXJzZXJhLm9yZy9sZWFybi9tYWNoaW5lLWxlYXJuaW5nLykgZXNpbmxlbmVyZWsgYmFzaXQgYmlyIMOnYWzEscWfbWEgeWFwYWNhxJ/EsXouIMOWbmNlbGlrbGUgdGltc2FobGFyxLFuIGHEn8SxcmzEscSfxLEgaWxlIGJ1cnVuIHV6dW5sdcSfdSBhcmFzxLFuZGFraSBpbGnFn2tpeWkgdHV0YW4gYmlyIHZlcml5aSBpbmNlbGV5ZWNlxJ9pei4gQcSfxLFybMSxayB2ZSB1enVubHVrIGFyYXPEsW5kYWtpIGlsacWfa2l5aSwgZG/En3J1c2FsIGJpciBkZW5rbGVtIGlsZSBhw6fEsWtsYXlhY2HEn8Sxei4gQnUgacWfZSBkb8SfcnVzYWwgYmHEn2xhbsSxbSB5YSBkYSBpbmdpbGl6Y2UgYWTEsXlsYSBsaW5lZXIgcmVncmVzc2lvbiBkaXlvcnV6LiDDh2FsxLHFn21hbcSxeiBpa2kgYcWfYW1hZGFuIG9sdcWfdXlvci4KCiAgKiDEsGxrIGHFn2FtYWRhIFInxLFuIGhhesSxciBsaW5lZXIgcmVncmVzc2lvbiBmb25rc2l5b25sYXLEsSBrdWxsYW5hY2HEn8Sxei4KICAqIGlraW5jaSBhxZ9hbWEgaXNlLCBkYWhhIGlsZXJpIGJpciBzZXZpeWVkZSwgbGluZWVyIHJlZ3Jlc3Npb24nxLFuIGFya2EgcGxhbsSxbmRhIMOnYWzEscWfYW4gKmdyYWRpZW50IGRlc2NlbnQqIChkZXJlY2VsaSBpbmnFnykgb3B0aW1pemFzeW9uIHnDtm50ZW1pIGlsZSB2ZXJpeWkgYcOnxLFrbGFkxLHEn8SxbmEgaW5hbmTEscSfxLFtxLF6IGRvxJ9ydXNhbCBkZW5rbGVtaW4gcGFyYW1ldHJlbGVyaW5pIGtlbmRpbWl6IGJ1bGFjYcSfxLF6LgoKIyMjIFZlcmltaXppIE9sdcWfdHVyYWzEsW0KQcWfYcSfxLFkYWtpIHZlcml5aSBbci1ibG9nZ2Vyc10oaHR0cHM6Ly93d3cuci1ibG9nZ2Vycy5jb20vc2ltcGxlLWxpbmVhci1yZWdyZXNzaW9uLTIvKSdkYW4gYWxkxLFtLgoKYGBge3J9CnV6dW5sdWsgPSBjKDMuODcsIDMuNjEsIDQuMzMsIDMuNDMsIDMuODEsIDMuODMsIDMuNDYsIDMuNzYsMy41MCwgMy41OCwgNC4xOSwgMy43OCwgMy43MSwgMy43MywgMy43OCkKYWdpcmxpayA9IGMoNC44NywgMy45MywgNi40NiwgMy4zMywgNC4zOCwgNC43MCwgMy41MCwgNC41MCwzLjU4LCAzLjY0LCA1LjkwLCA0LjQzLCA0LjM4LCA0LjQyLCA0LjI1KQoKdGltc2FoID0gZGF0YS5mcmFtZSh1enVubHVrLGFnaXJsaWspCmBgYAoKIyMjIFZlcml5aSDDh2l6ZGlyZWxpbQoKYGBge3J9CnBsb3QoYWdpcmxpayB+IHV6dW5sdWssIGRhdGEgPSB0aW1zYWgsCiAgeGxhYiA9ICJCdXJ1biBVenVubHXEn3UiLAogIHlsYWIgPSAiQcSfxLFybMSxayIsCiAgbWFpbiA9ICJDZW50cmFsIEZsb3JpZGEnZGFraSBUaW1zYWhsYXIiCikKZ3JpZCg1LCA1LCBsd2QgPSAyKQpgYGAKClZlcml5aSDDp2l6ZGnEn2ltaXpkZSwgYnVydW4gdXp1bmx1xJ91IHZlIGHEn8SxcmzEsWsgYXJhc8SxbmRhIGRvxJ9ydXNhbCBiaXIgaWxpxZ9raXlpIHNhcHTEsXlhYmlsaXlvcnV6IGRlxJ9pbCBtaT8gR8O2csO8bsO8xZ9lIGfDtnJlLCBidXJ1biB1enVubHXEn3UgYXJ0dMSxa8OnYSwgYcSfxLFybMSxayBkYSBhcnTEsXlvci4KCgojIyMgUiBIYXrEsXIgxLDFn2xldmxlcmkgaWxlIERvxJ9ydXNhbCBCYcSfbGFuxLFtClIgaWxlIGJhxJ9sYW7EsW0gb2xkdWvDp2Ega29sYXkuIEJ1bnVuIGnDp2luZGUgYcWfYcSfxLFkYWtpIGdpYmkgYGxtKClgIGZvbmtzaXlvbnUga3VsYW7EsWzEsXIuIGBzdW1tYXJ5KClgIGlsZSBtb2RlbCBwYXJhbWV0cmVsZXJpbmkgZWxkZSBlZGViaWxpcml6LgpgYGB7cn0KS29sYXkuQmFnbGFuaW0gPSBsbShhZ2lybGlrIH4gdXp1bmx1aywgZGF0YSA9IHRpbXNhaCkKc3VtbWFyeShLb2xheS5CYWdsYW5pbSkKYGBgCgpCdXJhZGEgYGxtKGFnaXJsaWsgfiB1enVubHVrLCBkYXRhID0gdGltc2FoKWAga29tdXR1bnUga3VsbGFuxLF5b3J1ei4gYH5gIGnFn2FyZXRpIGJhxJ/EsW1sxLEgdmUgYmHEn8SxbXPEsXogZGXEn2nFn2tlbmxlcmkgYmlyYmlyaW5kZW4gYXnEsXJtYWsgacOnaW4ga3VsbGFuxLFsxLFyLiBZdWthcsSxZGEgcGVrw6dvayBiaWxnaSB2YXIgYW1hIMWfdSBhxZ9hbWFkYSBiaXogYcWfYcSfxLFkYWtpIGJpbGdpbGVybGUgaWxnaWxlbml5b3J1ei4KCmBgYHtyfQp0aGV0YTAgPSBLb2xheS5CYWdsYW5pbSRjb2VmZmljaWVudHNbJyhJbnRlcmNlcHQpJ10KdGhldGExID0gS29sYXkuQmFnbGFuaW0kY29lZmZpY2llbnRzWyd1enVubHVrJ10KcHJpbnQoYyh0aGV0YTAsIHRoZXRhMSkpCmBgYAoKCgpQYXJhbXRyZWxlcmltaXogxZ91IMWfZWtpbGRlLAoKICAqICAkXHRoZXRhXzAgPSAtOC40NzYwNjckCiAgKiAgJFx0aGV0YV8xID0gMy40MzEwOTgkCiAgClBlxZ9pbmRlIG9sZHXEn3VtdXogbWF0ZW1hdGlrc2VsIGRlbmtsZW0gaXNlLAokJHkgPSBcdGhldGFfMCArIFx0aGV0YV8xICB4ICQkCgpEZcSfZXJsZXJpIHllcmluZSB5YXphcnNhawoKJCQgYWdpcmxpayA9ICAtOC40NzYxICsgMy40MzExICogdXp1bmx1ayQkCgpCdSBkb8SfcnVzYWwgaWxpxZ9raXlpIHZlcmltaXpsZSBiaXJsaWt0ZSDDp2l6ZGlyZWxpbS4KCmBgYHtyfQpwbG90KGFnaXJsaWsgfiB1enVubHVrLCBkYXRhID0gdGltc2FoLAogIHhsYWIgPSAiQnVydW4gVXp1bmx1xJ91IiwKICB5bGFiID0gIkHEn8SxcmzEsWsiLAogIG1haW4gPSAiQ2VudHJhbCBGbG9yaWRhJ2Rha2kgVGltc2FobGFyIgopCnggPSBzZXEoMiw2LDAuNSkKbGluZXMoeCwgKHRoZXRhMCArIHRoZXRhMSAqIHgpLCBjb2wgPSAicmVkIikKZ3JpZCg1LCA1LCBsd2QgPSAyKQpgYGAKCiMjIyBVZmFrIEJpciBUYWhtaW4gWWFwYWzEsW0gbcSxPwpBcnTEsWsgZWxpbWl6ZGUsIHZlcml5aSBhw6fEsWtsYXlhbiBkb8SfcnVzYWwgZGVua2xlbWluIHBhcmFtZXRyZWxlcmkgb2xkdcSfdW5hIGfDtnJlLCB0YWhtaW5kZSBidWx1bmFiaWxpcml6LiBVenVubHVrIDQnZSBlxZ9pdHNlIEHEn8SxcmzEsWsgbmUgb2xhY2FrdMSxcj8KCmBgYHtyfQp4Lm5va3RhID0gNAp0YWhtaW4gPSBhcy5udW1lcmljKHRoZXRhMCArIHRoZXRhMSAqIHgubm9rdGEpCnByaW50KHRhaG1pbikKYGBgCgoKCmBgYHtyfQpwbG90KGFnaXJsaWsgfiB1enVubHVrLCBkYXRhID0gdGltc2FoLAogIHhsYWIgPSAiQnVydW4gVXp1bmx1xJ91IiwKICB5bGFiID0gIkHEn8SxcmzEsWsiLAogIG1haW4gPSAiQ2VudHJhbCBGbG9yaWRhJ2Rha2kgVGltc2FobGFyIgopCnggPSBzZXEoMiw2LDAuNSkKbGluZXMoeCwgKHRoZXRhMCArIHRoZXRhMSAqIHgpLCBjb2wgPSAicmVkIikKIyBUYWhtaW5pbWl6aSBiw7x5w7xrIGJpciBwZW1iZSBub2t0YSBvbGFyYWsgw6dpemVsaW0KcG9pbnRzKHgubm9rdGEsIHRhaG1pbiwgY29sPSAncGluaycsIGx3ZD0gMjMpCiMgTm9rdGFkYW4geCB2ZSB5IGVrc2VubGVyaW5lIGRpayBwZW1iZSDDp2l6Z2lsZXIgaW5kaXJlbGltCnNlZ21lbnRzKHgubm9rdGEsIDAsIHgubm9rdGEsIHRhaG1pbiwgY29sPSAncGluaycsIGx0eSA9ICJkYXNoZWQiKQpzZWdtZW50cygwLCB0YWhtaW4sIHgubm9rdGEsIHRhaG1pbiwgY29sPSAncGluaycsIGx0eSA9ICJkYXNoZWQiKQpncmlkKDUsIDUsIGx3ZCA9IDIpCmBgYAoKCiMjIMSwbGVyaSBTZXZpeWUgLSBEb8SfcnVzYWwgQmHEn2xhbsSxbSAKQnUgacWfaSBSJ8SxbiBoYXrEsXIgZG/En3J1c2FsIGJhxJ9sYW7EsW0gZm9ua3NpeW9udW51IGt1bGxhbm1hZGFuIHlhcGFiaWxpciBtaXlpej8KCgojIyMgTW9kZWwgClZlcmltaXpkZSAkeCQgYmHEn8SxbXPEsXogZGXEn2nFn2tlbiBvbG1hayDDvHplcmUsICR4XnsoMSl9JCBiaXJpbmNpICR4JCBnw7Z6bGVtaW1peiwKJHheeygyKX0kIGlraW5jaSAkeCQgZ8O2emxlbWltaXosIC4uICR4XnsoaSl9JCBkZSAkaSQnaW5jaSBnw7Z6bGVtaW1peiBvbHN1bi4KQcWfYcSfxLFkYWtpIGdpYmkgYmlyIG1vZGVsaW1peiBvbGR1xJ91bnUgZMO8xZ/DvG5lbGltLgoKJCQgaF9cdGhldGEgKHheeyhpKX0pPSBcdGhldGFfMCArIFx0aGV0YV8xIHheeyhpKX0gJCQKQml6IGVuIGl5aSAkKFx0aGV0YV8wLCBcdGhldGFfMSApJCBkZcSfZXJsZXJpbmkgYXLEsXlvcnV6LiBEacSfZXIgYmlyIGlmYWRleWxlLCBoYXRhecSxIG1pbmltaXplIGVkZW4gJChcdGhldGFfMCwgXHRoZXRhXzEgKSQgZGXEn2VybGVyaW5pIGFyxLF5b3J1ei4gCgojIyMgSGF0YSBGb25rc2l5b251Ck1vZGVsaW1pemluIMO8cmV0dGnEn2kgJGkkJ2luY2kgdGFobWluICRoX1x0aGV0YSh4XnsoaSl9KSQgaWxlIGdlcsOnZWsgw6fEsWt0xLEgZGXEn2VyaSAkeV57KGkpfSQgYXJhc8SxbmRha2kgZmFya8SxbiBtw7xta8O8biBvbGR1xJ91bmNhIGF6IG9sbWFzxLFuxLEgaXN0aXlvcnV6LiBCdW51biBpw6dpbiAkaF9cdGhldGEoeF57KGkpfSkgLSB5XnsoaSl9JCBmYXJrbGFyxLFuxLFuIGthcmVzaW5pIG1pbmltaXplIGVkZW4gJChcdGhldGFfMCwgXHRoZXRhXzEgKSQgZGXEn2VybGVyaW5pIGFyYXlhY2HEn8Sxei4gRG9sYXnEsXPEsXlsYSBoYXRhIGZvbmtzaXlvbnVtdXogxZ91IMWfZWtpbGRlZGlyLgoKCiQkCkooXHRoZXRhXzAsIFx0aGV0YV8xICkgPSBcZnJhY3sxfXsybX0KXHN1bV5tX2kgXGxlZnQoaF9cdGhldGEoeF57KGkpfSkgLSB5XnsoaSl9IFxyaWdodCleMgokJApCdXJkYWtpIDIsIHTDvHJldiBhbMSxbmTEscSfxLFuZGEgc2FkZWxlxZ9lY2VrLCBtIGlzZSBvcnRhbGFtYSBoYXRhecSxIGJ1bG1hayBhbWFjxLF5bGEga3VsbGFuxLFsbcSxxZ90xLFyLiDFnmltZGksIFIgaWxlIGhpcG90ZXonaW1pemluIGhhdGFzxLFuxLEgZMO2bmTDvHJlbiBiaXIgZm9ua3NpeW9uIHlhemFsxLFtLgoKCmBgYHtyfQpoaXBvdGV6IDwtIGZ1bmN0aW9uKHRoZXRhMCA9IC04LjQ3NjEsIHRoZXRhMSwgdmVyaSA9IHRpbXNhaCl7CiAgeCA9IHZlcmlbLDFdICMgQmHEn8SxbXPEsXogZGXEn2nFn2tlbgogIHkgPSB2ZXJpWywyXSAjIEJhxJ/EsW1sxLEgZGXEn2nFn2tlbgogIG0gPSBsZW5ndGgoeCkKICBoID0gdGhldGEwICArIHggKiB0aGV0YTEKICBoYXRhID0gKDEgLyAoMiptKSkgKiBzdW0oKGggLSB5KV4yKSAjIGhhdGFuxLFuIGhlc2FwbGFubWFzxLEgCiAgcmV0dXJuKGhhdGEpCn0KYGBgCgpWYXJzYXnEsWxhbiBhcmfDvG1hbiBvbGFyYWsgYHZlcmkgPSB0aW1zYWhgIGRlZGnEn2ltaXogacOnaW4sIGZhcmtsxLEgYmlyIHZlcmkga3VsbGFubWFrIGlzdGVtZWRpxJ9pbWl6IHPDvHJlY2UgdGltc2FoIHZlcmlzaW5pIHRla3JhciB5YXptYW3EsXphIGdlcmVrIHlvay4gQnVyYWRhIGFubGF0xLFtxLEgYmFzaXRsZcWfdGlybWVrIGnDp2luLCAkXHRoZXRhXzAgPSDiiJI4LjQ3NjEkIGthYnVsIGVkZXJlayB5b2x1bXV6YSBkZXZhbSBlZGl5b3J1ei4gQcWfYcSfxLFkYWtpIMO2cm5la3RlICRcdGhldGFfMSA9IDMkIGnDp2luIGhhdGEgZGXEn2VyaW5pIGfDtnLDvHlvcnV6LgoKYGBge3J9CmhhdGEgPSBoaXBvdGV6KHRoZXRhMSA9IDMpCmhhdGEKYGBgCgrFnmltZGkgRmFya2zEsSAkXHRoZXRhXzEkIGRlxJ9lcmxlcmkgacOnaW4gaGF0YSBmb25rc2l5b251bnUgw6dpemRpcmVsaW0uCgpgYGB7cn0KdGhldGEuZGVnZXJsZXJpID0gc2VxKDAsNywwLjA1KQpoYXRhbGFyID0gdGhldGEuZGVnZXJsZXJpICogMApmb3IgKGkgaW4gMTpsZW5ndGgodGhldGEuZGVnZXJsZXJpKSl7CiAgaGF0YWxhcltpXSA9IGhpcG90ZXoodGhldGExID0gdGhldGEuZGVnZXJsZXJpW2ldKQp9CnBsb3QodGhldGEuZGVnZXJsZXJpLCBoYXRhbGFyLCB0eXBlID0gImwiLCB5bGFiID0gZXhwcmVzc2lvbihKKHRoZXRhWzBdLCB0aGV0YVsxXSkpLCB4bGFiPWV4cHJlc3Npb24odGhldGFbMV0pLCBsd2QgPSAzKQpncmlkKDUsIDUsIGx3ZCA9IDIpCmBgYApBYSwgaGF0YSBmb25rc2l5b251bXV6IGJpciBwYXJhYm9sIMOnxLFrdMSxLiA6KSBBc2zEsW5kYSBidSBvbGR1a8OnYSBiZWtsZW5lbiBiaXIgZHVydW0sIMOnw7xua8O8IGhhdGEgZm9ua3NpeW9udXogJEooXHRoZXRhXzAsIFx0aGV0YV8xICkkIGdlcsOnZWt0ZW4gZGUgaWtpbmNpIGRlcmVjZWRlbiBiaXIgcGFyYWJvbCBkZW5rbGVtaWRpci4gQnUgdMO8ciBiaXIgcXVhZHJhdGlrIGRlbmtsZW1pbiBtaW5pbXVtIHlhIGRhIG1ha3NpbXVtIG5va3Rhc8SxIG9sYWNha3TEsXIuIEJpeiBidSBub2t0YXnEsSBhcsSxeW9ydXouIFNpemNlIGhhdGEgZm9ua3NpeW9udW11eiwgaGFuZ2kgJFx0aGV0YV8xJCBkZcSfZXJpIGnDp2luIG1pbmltdW0gb2x1eW9yPyBEYWhhIMO2bmNlIGJ1bGR1xJ91bXV6ICRcdGhldGFfMSA9IDMuNDMxMSQgZGXEn2VyaSBvbGFiaWxpciBtaT8KCgpgYGB7cn0KcGxvdCh0aGV0YS5kZWdlcmxlcmksIGhhdGFsYXIsIHR5cGUgPSAibCIsIHlsYWIgPSBleHByZXNzaW9uKEoodGhldGFbMF0sIHRoZXRhWzFdKSksICAgIHhsYWI9ZXhwcmVzc2lvbih0aGV0YVsxXSksIGx3ZCA9IDMpCiMgdGhldGExPSAzLjQzMTEnZGVuIGJpciBkaWttZSDDp2VrZWxpbQpzZWdtZW50cyh0aGV0YTEsIDAsIHRoZXRhMSwgMTAwLCBjb2w9ICdwaW5rJywgbHR5ID0gImRhc2hlZCIsIGx3ZCA9IDMpCmdyaWQoNSwgNSwgbHdkID0gMikKYGBgCgoKIyMgT3B0aW1pemFzeW9uIGlsZSDDlsSfcmVubWVrIEFyYXPEsW5kYWtpIMSwbmNlIMOHaXpnaQpPcHRpbWl6YXN5b24gaWxlIMO2xJ9yZW5tZWsgYXJhc8SxbmRhIGluY2UgYmlyIMOnaXpnaSB2YXIuIMOHw7xua8O8LAplbiBpeWkgcGFyYW1hdHJlIGRlxJ9lcmxlcmluaSDDtsSfcmVubWVrIGlsZSBvcHRpbWl6YXN5b24gYXluxLEgxZ9leWRpci4gJEooXHRoZXRhXzAsIFx0aGV0YV8xICkkIGhhdGEgZm9ua3NpeW9udW51IGVuIGvDvMOnw7xrIGRlxJ9lcmluaSBhcsSxeW9ydXouIEJ1bnUgbmFzxLFsIGJ1bGFiaWxpcml6PyBCYXNpdGxpayBhw6fEsXPEsW5kYW4sICRcdGhldGFfMCA9IOKIkjguNDc2MSQga2FidWwgZWRlcmVrIGJveXV0IHNhecSxc8SxbsSxIDMndGVuIDIneWUgaW5kaXJlbGltLiBGYXJrbMSxICRcdGhldGFfMSQgZGXEn2VybGVyaSBpw6dpbiAkSihcdGhldGFfMCwgXHRoZXRhXzEgKSQnaW4gbmVyZWRlIGVuIGvDvMOnw7xrIGRlxJ9lcmUgdWxhxZ9hY2HEn8SxbsSxIGJ1bG1heWEgw6dhbMSxxZ9hY2HEn8Sxei4gQnVudW4gacOnaW4gJEooXHRoZXRhXzAsIFx0aGV0YV8xICkkIHTDvHJldmluaSBhbMSxcCBzxLFmxLFyYSBlxZ9pdGxlbWVrIGFzxLFsIHlhcMSxbG1hc8SxIGdlcmVrZW4gxZ9leWRpci4gQmlsZ2lzYXlhciBiaXogcHJvZ3JhbWxhbWFkxLHEn8SxbcSxeiBzw7xyZWNlLCBrZW5kaWxpxJ9pbmRlbiB0w7xyZXYgYWzEsXAgbWluaW11bSBub2t0YXnEsSBidWxhbWF6LiBCaWxnaXNheWFyYSBidSBpxZ9pIG5hc8SxbCB5YXB0xLFyYWNhxJ/EsW3EsXrEsSBnw7ZyZWxpbS4KCgoKIyMjIEdyYWRpZW50IERlc2NlbnQgLURlcmVjZWxpIMSwbmnFny0gWcO2bnRlbWkKRW4gaXlpICRcdGhldGFfMSQgZGXEn2VyaW5pIGJ1bG1hayBpw6dpbiByYXN0Z2VsZSBiaXIgZGXEn2VyZGVuIGJhxZ9sYXnEsXAsIHTDvHJldmluIHRlcnNpIHnDtm5kZSBhZMSxbSBhZMSxbSBpbGVybGVyc2VrLCBoZWRlZmltaXplIHVsYcWfYWJpbGlyaXouIAoKJCRcdGhldGFfMV57KHQrMSl9ID0gXHRoZXRhXzFeeyh0KX0gLSBcYWxwaGEgXGZyYWN7XHBhcnRpYWx9e1xwYXJ0aWFsIFx0aGV0YV8xfSBKKFx0aGV0YV8wLCBcdGhldGFfMSApJCQKCll1a2FyxLFkYWtpIGZvbmtzaXlvbiBhcmFjxLFsxLHEn8SxIGlsZSwgJFx0aGV0YV8xJCdpbiBkZcSfZXJpbmkgbmFzxLFsIGfDvG5jZWxsZXllY2XEn2ltaXppIHRhbsSxbWxhZMSxay4gQnUgecO2bnRlbWluIGFkxLEsIGRlcmVjZWxpIGluacWfIC0gKipncmFkaWVudCBkZXNjZW50KiogLSB5w7ZudGVtaWRpci4gCk5lZ2F0aWYgJFxmcmFje1xwYXJ0aWFsfXtccGFydGlhbCBcdGhldGFfMX0gSihcdGhldGFfMCwgXHRoZXRhXzEgKSQgZGl5ZXJlaywKdMO8cmV2aW4gdGVyc2kgecO2bsO8bmRlICh5YW5pIGFrxLFudMSxIGlsZSBiaXJsaWt0ZSkKJFxhbHBoYSQgYWTEsW0gKGt1bGHDpykgYsO8ecO8a2zDvMSfw7wgaWxlIGlsZXJsaXlvcnV6ICh5w7x6w7x5b3J1eikuIEJ1cmFkYSBkaWtrYXQgZWRpbG1lc2kgZ2VyZWtlbiBub2t0YSwgYWxnb3JpdG1hbcSxesSxbiAkSihcdGhldGFfMCwgXHRoZXRhXzEgKSQgYW1hw6ctaGF0YSBmb25rc2l5b251biB0w7xyZXZsZW5lYmlsaXIgb2xtYXPEsWTEsXIuIFlhIGFtYcOnIGZvbmtzaXlvbnVtdXogdMO8cmV2bGVuZWJpbGlyIG9sbWFzYXlkxLEgbmUgeWFwYXJkxLFrPyAKCiAgKiBIZXVyaXN0aWMtU2V6Z2lzZWwgWcO2bnRlbWxlcmUgKMO2cm5lxJ9pbiwgR2VuZXRpayBBbGdvcml0bWFsYXJhKSBiYcWfdnVybWFtxLF6IGdlcmVraXJkaS4gQnUga29udXl1IGJhxZ9rYSBiaXIgeWF6xLF5YSBixLFyYWthbMSxbS4KICAgICogQXLFn2ltZXQgYmFueW9uZGEgw6fEsWvEsXAsIEhldXJpc3RpYyBrZWxpbWVzaW5pbiBmaWlsIGhhbGkgb2xhbiAqZXVyZWthKiAoa2XFn2ZldHRpbSkgZGl5ZSBiYcSfxLFybcSxxZ90xLFyLgogICAgKiBTZXpnaXNlbCBhbGdvcml0bWFsYXIsIGVuIGl5aSDDp8O2esO8bcO8IGJ1bG1hecSxIGdhcmFudGkgZWRlbWV6LgogICAgKiBTb25zdXogYmlyIHV6YXlkYSwgZW4gaXlpIMOnw7Z6w7xtw7wgZGXEn2lsLCB5ZXRlcmluY2UgaXlpIGJpciDDp8O2esO8bcO8IGFyxLF5b3Igb2x1cnV6LiBCdSBiYWvEscWfIGHDp8Sxc8SxIMOnb2sgZGFoYSBnZXLDp2Vrw6dpZGlyLiAoRW4gaXlpIGXFn2kgeWEgZGEgZW4gaXlpIGnFn2kgYnVsYW1henPEsW7EsXosIHlldGVyaW5jZSBpeWkgb2xhbmxhIGlkYXJlIGV0bWVsaXNpbml6IDopIHlhbsSxbMSxeW9yIG11eXVtPyDEsHN0ZXJzZW5peiBhcmF5xLFuLi4gSGF5YXTEsW7EsXrEsW4gc29udW5hIGthZGFyIGFyYXnEsXAgZ2VuZSBkZSBoacOnIGJpciDFn2V5IGJ1bGFtYXlhYmlsaXJzaW5peiEhKQoKTmV5c2UgYnUga29udXl1IGthcGF0xLFwLCBiaXogacWfaW1pemUgZMO2bmVsaW0uIDopCgojIyMgS8Sxc21pIFTDvHJldmxlcgoKSGF0YSBmb25rc2l5b251biB0w7xyZXZpbmkgYnVsYWzEsW0uIAoKJCQgClxiZWdpbntzcGxpdH0KXGZyYWN7XHBhcnRpYWx9e1xwYXJ0aWFsIFx0aGV0YV9qfSBKKFx0aGV0YV8wLCBcdGhldGFfMSApIAomID0gClxmcmFje1xwYXJ0aWFsfXtccGFydGlhbCBcdGhldGFfan0gClxsZWZ0KCBcZnJhY3sxfXsybX1cc3VtXm1faSBcbGVmdChoX1x0aGV0YSh4XnsoaSl9KSAtIHkgXHJpZ2h0KV4yIFxyaWdodCkKXFwKJiA9IApcZnJhY3sxfXsybX0KXHN1bV5tX2kgClxmcmFje1xwYXJ0aWFsfXtccGFydGlhbCBcdGhldGFfan0gClxsZWZ0KGhfXHRoZXRhKHheeyhpKX0pICAtIHkgXHJpZ2h0KV4yClxcCiYgPSAKXGZyYWN7MX17Mm19ClxzdW1ebV9pIApcZnJhY3tccGFydGlhbH17XHBhcnRpYWwgXHRoZXRhX2p9IApcbGVmdChcdGhldGFfMCArIFx0aGV0YV8xIHheeyhpKX0gIC0geSBccmlnaHQpXjIKXGVuZHtzcGxpdH0KJCQKCgpVZmFrIGJpciBoYXTEsXJsYXRtYSB5YXBhbMSxbS4gQmlsZcWfaWtlIGZvbmtzaXlvbnVuIHTDvHJldmluaSBuYXPEsWwgYnVsdXlvcmR1az8gCiQkCiAoZiBvIGcpJyh4KSAgPSBmJyBcbGVmdChnKHgpIFxyaWdodCkgKiBnJyh4KQokJAoKQnVuYSBnw7ZyZSwgJFx0aGV0YV8wJCdhIGfDtnJlIGvEsXNtaSB0w7xyZXYKCiQkClxmcmFje1xwYXJ0aWFsfXtccGFydGlhbCBcdGhldGFfMH0gSihcdGhldGFfMCwgXHRoZXRhXzEgKSAgCj0gClxmcmFjezF9ezJtfQpcc3VtXm1faSAyIFxsZWZ0KFx0aGV0YV8wICsgXHRoZXRhXzEgeF57KGkpfSAgLSB5IFxyaWdodCkKPSAKXGZyYWN7MX17bX0KXHN1bV5tX2kgXGxlZnQoaF9cdGhldGEoeF57KGkpfSkgLSB5IFxyaWdodCkKJCQKClNhZGVjZSAkXHRoZXRhXzEkJ2EgZ8O2cmUga8Sxc21pIHTDvHJldgokJApcZnJhY3tccGFydGlhbH17XHBhcnRpYWwgXHRoZXRhXzF9IEooXHRoZXRhXzAsIFx0aGV0YV8xICkgIAo9IApcZnJhY3sxfXsybX0KXHN1bV5tX2kgMiBcbGVmdChcdGhldGFfMCArIFx0aGV0YV8xIHheeyhpKX0gIC0geSBccmlnaHQpeF57KGkpfQo9IApcZnJhY3sxfXttfQpcc3VtXm1faSBcbGVmdChoX1x0aGV0YSh4XnsoaSl9KSAtIHkgXHJpZ2h0KXheeyhpKX0KJCQKCkJ1IGvEsXNtaSB0w7xyZXZsZXJlIGJha8SxcCwgaGFuZ2kgJFx0aGV0YV8wJCB2ZSAkXHRoZXRhXzEkIGRlxJ9lcmxlcmkgacOnaW4gdMO8cmV2aW4gc8SxZsSxciB5YXB0xLHEn8SxbsSxIHPDtnlsZW1pbml6IG3DvG1rw7xuIG3DvD8gRGXEn2lsc2UsIGRlcmVjZWxpIGluacWfIHnDtm50ZW1pbmUgYmHFn3Z1cmFjYcSfxLF6LiBCdSBhcmFkYSAyJ2xlcmluIHNhZGVsZcWfdGnEn2luaSBmYXJrIGV0dGluaXogZGXEn2lsIG1pPwoKIyMgRGVyZWNlbGkgxLBuacWfIFnDtm50ZW1pIGlsZSBEb8SfcnVzYWwgQmHEn2xhbsSxbQpEZXJlY2VsaSBpbmnFnyB5w7ZudGVtaSwgcmFzdGdlbGUgYmlyIGJhxZ9sYW5nxLHDpyBub2t0YXPEsW5kYW4sIHTDvHJldmluIHRlcnNpIHnDtm5kZSAkXGFscGhhJCBhZMSxbSBiw7x5w7xrbMO8xJ/DvCBpbGUgaWxlcmxlbWVrIGFubGFtxLFuYSBnZWxpeW9yLgoKYMSwbmnFnyBhbGdvcml0bWFzxLFgCiQkXHRoZXRhX2leeyh0KzEpfSA9IFx0aGV0YV9pXnsodCl9IC0gXGFscGhhIApcZnJhY3tccGFydGlhbH17XHBhcnRpYWwgXHRoZXRhX2l9IEooXHRoZXRhXzAsIFx0aGV0YV8xICkKJCQKIArFnmltZGkgbXVjaXpldmkgYmlyIMWfZWtpbGRlIGRvxJ9ydSAgJFx0aGV0YV8wID0g4oiSOC40NzYxJCBpbGUgYmHFn2xhecSxcCwgZW4gdXlndW4gICRcdGhldGFfMSQnaW4gbmUgb2xkdcSfdW51IGJ1bGFsxLFtLiAoRcSfaXRpbSBpw6dlcmnEn2luaSBrb2xheWxhxZ90xLFybWFrIGnDp2luIGJ1bnUgeWFwdMSxay4gQsO2eWxlY2UgZGXEn2nFn2tlbiBzYXnEsXPEsW7EsSBhemFsdGFyYWsgJEooXHRoZXRhXzAsIFx0aGV0YV8xICkkJ3lpIGlraSBib3l1dGx1IHV6YXlkYSBnw7ZyZXNlbGxlxZ90aXJlYmlsaXlvcnV6LikgQWTEsW0gYsO8ecO8a2zDvMSfw7xtw7x6ICRcYWxwaGEgPSAwLjA0MyQgb2xzdW4uIChOZWRlbiBtaSwgYmVuaW0gYXlhayBudW1hcmFtIDQzIGRlIG9uZGFuIDopIFRhYmlraSBidSBrYWRhciBrZXlmaSwgZGF2cmFubWFtYWsgZ2VyZWsuIMOHb2sga8O8w6fDvGsgJFxhbHBoYSQgZGXEn2VybGVyaSBtaW5pbXVtIG5va3RhecSxIGJ1bG1hecSxIGdlY2lrdGlyaXIuIMOHb2sgYsO8ecO8ayAkXGFscGhhJCBkZcSfZXJsZXJpIGlzZSwgc2l6aSB0YW0gbWluaW11bXUgYnVsYWNha2tlbiBzaXplIMO2eWxlIGLDvHnDvGsgYmlyIGFkxLFtIGF0dMSxcsSxciBraSwgZGnEn2VyIHRhcmFmYSBzYXZ1cmFiaWxpci4gQcWfxLFyxLEgdcOnbGFyZGEgb2xtYXlhbiBiaXIgYWTEsW0gYsO8ecO8a2zDvMSfw7wgaWxlIHlvbGEgZGV2YW0gZXRtZWsgZ2VyZWtpeW9yLgpBY2FiYSAKJFx0aGV0YV8xJCdpbiBuZXJkZW4gYmHFn2xhdHNhaz8gU8SxZsSxcmRhbiBiYcWfbGF5YWzEsW0gbcSxPwoKCgpZdWthcsSxZGFraSBpbmnFnyBhbGdvcml0bWFzxLFuZGEsICRcdGhldGFfMSQgacOnaW4gYXogw7ZuY2UgYnVsZHXEn3VtdXoga8Sxc21pIHTDvHJldmkgeWF6YWzEsW0sIAoKCgokJFx0aGV0YV8xXnsodCsxKX0gPSBcdGhldGFfMV57KHQpfSAtIFxhbHBoYSAgClxmcmFjezF9e219IApcc3VtXm1faSBcbGVmdChoX1x0aGV0YSh4XnsoaSl9KSAtIHleeyhpKX0gXHJpZ2h0KSBcY2RvdCB4XnsoaSl9JCQKCkRpa2thdCBlZGVyc2VuaXosCmhlciBhZMSxbWRhLCAkMSQnZGVuICRtJCdlIGthZGFyIGXEn2l0aW0gc2V0aW5kZWtpIHTDvG0gdmVyaWxlcmUgYmFrdMSxxJ/EsW3EsXogacOnaW4gYnUga8Sxc21lbiBtYWxpeWV0aSB5w7xrc2VrIGJpciBoZXNhcGxhbWEgacWfaWRpci4KCiMjIEhlc2FwbGFtYQoKYGBge3J9CiMgRGVyZWNlbGkgxLBuacWfIEFsZ29yaXRtYXPEsSBpbGUgdGhldGExJ2kgZ8O8bmNlbGxleWVsaW0gCmRlcmVjZWxpLmluaXMgPC0gZnVuY3Rpb24odGhldGEwID0gLTguNDc2MSwgdGhldGExLCBhbHBoYSA9IDAuMDQzLCB2ZXJpID0gdGltc2FoKXsKICB4ID0gdmVyaVssMV0gIyBCYcSfxLFtc8SxeiBkZcSfacWfa2VuCiAgeSA9IHZlcmlbLDJdICMgQmHEn8SxbWzEsSBkZcSfacWfa2VuCiAgaCA9IHRoZXRhMCAgKyB4ICogdGhldGExCiAgeWVuaS50aGV0YSA9IHRoZXRhMSAtIChhbHBoYS9sZW5ndGgoeCkpICogKHN1bSgoaCAtIHkpKngpKSAjIERlcmVjZWxpIMSwbmnFnyBEZW5rbGVtaQogIHJldHVybih5ZW5pLnRoZXRhKQp9CmBgYAoKxZ5pbWRpICRcdGhldGFfMV57KDEpfT0gMCQgaWxlIGJhxZ9sYXlhbMSxbS4gVmUgJFx0aGV0YV8xXnsoMil9JCwgJFx0aGV0YV8xXnsoMyl9JCwgJFx0aGV0YV8xXnsoNCl9JCB2ZSAuLiAkXHRoZXRhXzFeeyg3KX0kIGRlxJ9lcmxlcmluZSBiYWthbMSxbS4KCmBgYHtyfQp0aGV0YTEuMSA9IDAKdGhldGExLjIgPSBkZXJlY2VsaS5pbmlzKHRoZXRhMSA9IHRoZXRhMS4xKQp0aGV0YTEuMyA9IGRlcmVjZWxpLmluaXModGhldGExID0gdGhldGExLjIpCnRoZXRhMS40ID0gZGVyZWNlbGkuaW5pcyh0aGV0YTEgPSB0aGV0YTEuMykKdGhldGExLjUgPSBkZXJlY2VsaS5pbmlzKHRoZXRhMSA9IHRoZXRhMS40KQp0aGV0YTEuNiA9IGRlcmVjZWxpLmluaXModGhldGExID0gdGhldGExLjUpCnRoZXRhMS43ID0gZGVyZWNlbGkuaW5pcyh0aGV0YTEgPSB0aGV0YTEuNikKCnRoIDwtIGModGhldGExLjEsIHRoZXRhMS4yLCB0aGV0YTEuMywgdGhldGExLjQsIHRoZXRhMS41LCB0aGV0YTEuNiwgdGhldGExLjcpCnByaW50KHRoKQpgYGAKCsWeaW1kaSBidSAkXHRoZXRhXzEkIGRlxJ9lcmxlcmkgacOnaW4gaGF0YWxhcsSxIGhlc2FwbGF5YWzEsW0uCgpgYGB7cn0KdGguaGF0YSA8LSB0aCAqIDAKZm9yKGkgaW4gMTpsZW5ndGgodGgpKXsKICB0aC5oYXRhW2ldIDwtIGhpcG90ZXoodGhldGExID0gdGhbaV0pCn0KdGguaGF0YQpgYGAKCk5pc3BldGVuIGLDvHnDvGsgYmlyIGhhdGEgaWxlIGJhxZ9sYWTEsWsgYW1hIMOnb2sgaMSxemzEsSBiaXIgYmnDp2ltZGUsIGhhdGEgYXphbGFyYWsgc8SxZsSxcmEga2FkYXIgaW5kaS4gxZ5pbWRpIG9sYW4gYml0ZW5pIGfDtnJzZWxsZcWfdGlyZWxpbS4KCgoKCgpgYGB7cn0KIyDEsGtpbGkgw4dpemltCnBhcihtZnJvdz1jKDEsIDIpKQojIFNvbGRha2kgw4dpemltCnBsb3QoYWdpcmxpayB+IHV6dW5sdWssIGRhdGEgPSB0aW1zYWgsCiAgeGxhYiA9ICJCdXJ1biBVenVubHXEn3UiLAogIHlsYWIgPSAiQcSfxLFybMSxayIsCiAgeWxpbSA9IGMoLTEwLDEwKSwKICBtYWluID0gIkNlbnRyYWwgRmxvcmlkYSdkYWtpIFRpbXNhaGxhciIKKQpncmlkKDUsIDUsIGx3ZCA9IDIpCgp4ID0gc2VxKDIsNiwwLjUpCnJlbmtsZXIgPC0gYygicmVkIiwgIm9yYW5nZSIsImdvbGQiLCJncmVlbiIsICJibHVlIiwgIm1hZ2VudGEiLCJwdXJwbGUiKQpmb3IoaSBpbiAxOmxlbmd0aCh0aCkpewogIGggPSAtOC40NzYxICArIHggKiB0aFtpXQogIGxpbmVzKHgsIGgsIGNvbCA9IHJlbmtsZXJbaV0sbHdkPTIpCn0KCiMgU2HEn2Rha2kgw4dpemltCnBsb3QodGhldGEuZGVnZXJsZXJpLCBoYXRhbGFyLCB0eXBlID0gImwiLCB5bGFiID0gZXhwcmVzc2lvbihKKHRoZXRhWzBdLCB0aGV0YVsxXSkpLCAgICB4bGFiPWV4cHJlc3Npb24odGhldGFbMV0pLCBtYWluID0gIkhhdGEgRGXEn2VyaSIpCnBvaW50cyh0aCwgdGguaGF0YSwgY29sID0gcmVua2xlciwgbHdkPSA1KQpncmlkKDUsIDUsIGx3ZCA9IDIpCgpgYGAKIyMjIFlvcnVtCll1a2FyxLFkYWtpIGfDtnJzZWxsZXIgbmUgb2x1cCBiaXR0acSfaW5pIGFubGFtYWsgYWTEsW5hIMOnb2sgZmF5ZGFsxLEuIFNvbGRhIHZlcml5aSBhw6fEsWtsYXlhbiBkZW5rbGVtbGVyaSB2ZSBzYcSfZGEgaXNlIG8gZGVua2xlbWxlcmluIGhhdGFsYXLEsW7EsSBnw7Zyw7x5b3J1ei4gJFx0aGV0YV8xXnsoMSl9ID0gMCQgYmHFn2xhbmfEscOnIGR1cnVtdSBzb2xkYWtpIGfDtnJzZWxkZSBrxLFybcSxesSxIHJlbmtsaSBkb8SfcnV5YSBrYXLFn8SxbMSxayBnZWxpeW9yLiBCdSBzYcSfZGFraSBnw7Zyc2VsZGUgZGUsIGhhdGEgZm9ua3NpeW9udSDDvHplcmluZGUsIGvEsXJtxLF6xLEgbm9rdGEgaWxlIGfDtnN0ZXJpbG1pxZ8uIERpa2thdCBlZGVyc2VuaXogaGF0YSBvbGR1a8OnYSBmYXpsYS4gU29ucmFraSBhZMSxbWRhIGlzZSwgZGVyZWNlbGkgaW5pxZ8gecO2bnRlbWkgaWxlIGVsZGUgZXR0acSfaW1peiB5ZW5pICRcdGhldGFfMV57KDIpfSA9IDIuMDkyMDE3JCBkZcSfZXJpIHR1cnVuY3UgcmVua2xlIGfDtnN0ZXJpbG1pxZ8uIEFsZ29yaXRtYXnEsSB0ZWtyYXJsxLEgYmlyIGJpw6dpbWRlIMOnYWzEscWfdMSxcmFyYWsgaGVyIGFkxLFtZGEsIGRhaGEgYXogaGF0YSB5YXBhbiBiaXIgJFx0aGV0YV8xXnsodCl9JCBlbGRlIGVkaXlvcnV6LCB0YSBraSBoYXRhIHPEsWbEsXIgb2xhbmEga2FkYXIuIEJ1IHNhxJ9kYWtpIHJlc2ltZGUsIGhhdGEgZm9ua3NpeW9udW51IGfDtnN0ZXJlbiBwYXJhYm9sw7xuIG1pbmltdW0gbm9rdGFzxLFuYSBnZWxtZWsgYW5sYW3EsW5hIGdlbGl5b3IuIEJpeiB5ZWRpbmNpIAp0ZWtyYXJkYSwgJFx0aGV0YV8xXnsoNyl9ID0gMy40MTg5ODIkIGJ1bGR1ay4gVmUgb3JhZGEgYsSxcmFrdMSxay4gQsO2eWxlY2UsIHNhxJ9kYWtpIHBhcmFib2xkZSwgbW9yIHJlbmtsaSBub2t0YXlhIHZhcmTEsWsuIE5lcmRleXNlIG1pbmltdW1kYXnEsXouIAoKCgojIyMgJFx0aGV0YV8wJCBOZXJlZGU/CiRcdGhldGFfMCA9IC04LjQ3NjA2NyQgZ8O2cm1layBpc3Rlc2VuaXosIHNvbGRha2kgcmVzaW1kZSB4IHZlIHkgZWtzZW5sZXJpIGlsZSBiaXJheiBveW5hbWFtxLF6IGdlcmVraXIuCgpgYGB7cn0KIyDEsGtpbGkgw4dpemltCnBhcihtZnJvdz1jKDEsIDIpKQojIFNvbGRha2kgw4dpemltCnBsb3QoYWdpcmxpayB+IHV6dW5sdWssIGRhdGEgPSB0aW1zYWgsCiAgeGxhYiA9ICJCdXJ1biBVenVubHXEn3UiLAogIHlsYWIgPSAiQcSfxLFybMSxayIsCiAgeGxpbSA9IGMoLTEsNSksCiAgeWxpbSA9IGMoLTEwLDEwKSwKICBtYWluID0gIkNlbnRyYWwgRmxvcmlkYSdkYWtpIFRpbXNhaGxhciIsCiAgbHdkID0gNgopCmdyaWQoNSwgNSwgbHdkID0gMikKCnggPSBzZXEoLTEsNiwwLjUpCnJlbmtsZXIgPC0gYygicmVkIiwgIm9yYW5nZSIsImdvbGQiLCJncmVlbiIsICJibHVlIiwgIm1hZ2VudGEiLCJwdXJwbGUiKQpmb3IoaSBpbiAxOmxlbmd0aCh0aCkpewogIGggPSAtOC40NzYxICArIHggKiB0aFtpXQogIGxpbmVzKHgsIGgsIGNvbCA9IHJlbmtsZXJbaV0sbHdkPTIpCn0KCiMgU2HEn2Rha2kgw4dpemltCnBsb3QodGhldGEuZGVnZXJsZXJpLCBoYXRhbGFyLCB0eXBlID0gImwiLCB5bGFiID0gZXhwcmVzc2lvbihKKHRoZXRhWzBdLCB0aGV0YVsxXSkpLCAgICB4bGFiPWV4cHJlc3Npb24odGhldGFbMV0pLCBtYWluID0gIkhhdGEgRGXEn2VyaSIpCnBvaW50cyh0aCwgdGguaGF0YSwgY29sID0gcmVua2xlciwgbHdkPSA1KQpncmlkKDUsIDUsIGx3ZCA9IDIpCmBgYApHw7ZyZMO8xJ/DvG7DvHogZ2liaSBiw7x0w7xuIGRvxJ9ydWxhciB5IGVrc2VuaW5pLCAkXHRoZXRhXzAgPSAtOC40NzYwNjckJ2RlIGtlc2l5b3IuIE9sbWFzxLEgZ2VyZWtlbiBkZSBidS4gw4fDvG5rw7wsIGZhcmtsxLEgJFx0aGV0YV8xXnsodCl9JCBkZcSfZWxlcmkgacOnaW4gw6dpemRpcmVjZcSfaW5peiBkb8SfcnVsYXJkYSwgJFx0aGV0YV8xXnsodCl9PSAwJCBvbGR1xJ91bmRhIGHFn2HEn8SxZGFraSBkZW5rbGVtICRcdGhldGFfMCA9IC04LjQ3NjA2NyQnZSBlxZ9pdCBvbHVyLgoKCiQkIGhfXHRoZXRhICh4XnsoaSl9KT0gXHRoZXRhXzAgKyBcdGhldGFfMV57KHQpfXheeyhpKX0gJCQKCgojIyBZZW5pIGJpciB2ZXJpIHNldGkKxLBzdGVzZW5peiBzaXogZGUsIFInZGFraSBoYXrEsXIgdmVyaSBrw7xtZWxlcmluZGVuIGJpcmkgb2xhbiAqKndvbWVuKiogdmVyaXNpbmkga3VsbGFuYXJhaywgZG/En3J1c2FsIHJlZ3Jlc3lvbiBhbmFsaXppbmUgZGV2YW0gZWRpbi4gCgpgYGB7cn0KZG9ncnVzYWwgPC1sbSh3ZWlnaHQgfiBoZWlnaHQsIGRhdGE9d29tZW4pCnN1bW1hcnkoZG9ncnVzYWwpCmBgYAojIyMgU29uIFPDtnoKIApWZXJpIGJpbGltaSBpbGUgw6dhbMSxxZ9tYW7EsW4gZW4gZ8O8emVsIHRhcmFmbGFyxLFuZGFuIGJpcmksIGhpw6cgxZ/DvHBoZXNpeiDDtsSfcmVubWnFnyBvbGR1xJ91bnV6IGJpciB5YXBheSDDtsSfcmVubWUgecO2bnRlbWluaW4gYmlyaWJpcmluZGVuIMOnb2sgZmFya2zEsSBhbGFubGFyZGEsIGJpcmJpcmluZGUgw6dvayBmYXJrbMSxIHZlcmkgc2V0bGVyaSDDvHplcmluZGUgw6dhbMSxxZ9hYmlsbWVzaWRpci4gQXogw7ZuY2UgdGltc2FobGFyIGhha2vEsW5kYSDDp2FsxLHFn3TEsWssIMWfaW1kaSBpc2Uga2FkxLFubGFyIGhha2vEsW5kYSBuZWRlbiDDp2FsxLHFn21hIHlhcG1hbMSxecSxbSA6KSDDh29rIGZhcmtsxLFsYXIgZ2Vyw6dla3RlbiDDp29rIDopIEthdmdhIMOnxLFrbWFkYW4sIHNvbiBzw7Z6w7wgc8O2eWxleWVsaW0uIEthZMSxbmxhciB0aW1zYWggbWltc2FoIGRlxJ9pbGRpciwga2FkxLFubGFyIMOnacOnZWt0aXIuCgoKPGhyPgpCdSDDp2FsxLHFn21heWEgYXTEsWZ0YSBidWx1bm1hayBpc3RlcnNlbml6LAoKICAqIFV6YXkgw4dldGluLCBSIGlsZSBEb8SfcnVzYWwgQmHEn2xhbsSxbSwgaHR0cHM6Ly91emF5MDAuZ2l0aHViLmlvL2thaHZlL25vdGVib29rcy9Eb2dydXNhbEJhZ2xhbmltLm5iLmh0bWwsIDIwMTcKICAKeWF6YWJpbGlyc2luaXouIFRlxZ9la2vDvHJsZXI=