1. B-spline là gì?

Các đường cong Spline bao gồm những đoạn đa thức kết nối với nhau một cách trơn nhẵn tại các điểm được gọi là nút. Spline được sử dụng trong các ước lượng làm trơn các đường cong hồi quy và trong các ước lượng mô hình cộng tính mở rộng (Theo TỪ ĐIỂN CÁC THUẬT NGỮ THỐNG KÊ OXFORD, Tác giả: Yadolah Dodge, Người dịch: PGS.TS Tô Cẩm Tú, Nhà xuất bản: Đại học Quốc gia Hà Nội, 2018).

Splines được đặc trưng bởi các nút chia (knots) trên 1 khoảng xác định (domain partition), bậc (order/degree).

Các đa thức Spline bậc ba được sử dụng phổ biến nhất, trong đó, các splines này phải có đạo hàm bậc nhất và bậc hai liên tục tại các nút.

B-spline (Curry-Schoenberg B-Spines) là các hàm cơ sở cho các spline có cùng thứ tự được xác định trên các nút giống nhau, có nghĩa là tất cả các hàm spline có thể có có thể được xây dựng từ sự kết hợp tuyến tính của B-splines và chỉ có một sự kết hợp độc đáo cho mỗi chức năng spline.

Lưu ý: Bậc (degree) của B-splines được định nghĩa từ 0, trong khi thứ tự (order) của B-splines được định nghĩa từ 1.

\[ order = degree +1 \]

2. Định nghĩa B-splines, đạo hàm của B-splines và KGVT \(\mathcal{S}_{k}^{\Delta \lambda}[a, b]\)

Một chút toán nhé!

Định nghĩa dưới này được tổng hợp từ De Boor, C., & De Boor, C. (1978) (page 88, 106) và Schumaker, L. (2007) (page 124).

2.1 Định nghĩa B-splines

Let the sequence of knots: ${0}=a<{1}<<{g}<b={g+1} $

The (normalized) B-spline of degree 0 (order 1), for \(i=0, \ldots, g\) \[ B_{i}^{1}(x)= \begin{cases}1 & \text { if } x \in\left[\lambda_{i}, \lambda_{i+1}\right) \\ 0 & \text { otherwise }\end{cases} \]

The (normalized) B-spline of degree 1 (order 2), for \(i=0, \ldots, g\) \[\begin{align*} B_{i}^{2}(x) & = \dfrac{x-\lambda_{i}}{\lambda_{i+1}-\lambda_{i}} B_{i}^{1}(x) +\dfrac{\lambda_{i+2}-x}{\lambda_{i+2}-\lambda_{i+1}} B_{i+1}^{1}(x)\\ & = \begin{cases} \dfrac{x-\lambda_{i}}{\lambda_{i+1}-\lambda_{i}} & \text { if } x \in\left[\lambda_{i}, \lambda_{i+1}\right) \\ \dfrac{\lambda_{i+2}-x}{\lambda_{i+2}-\lambda_{i+1}} & \text { if } x \in\left[\lambda_{i+1}, \lambda_{i+2}\right) \\ 0 & \text { otherwise }\end{cases}\\ \end{align*}\] And \[\begin{align*} B_{i+1}^{2}(x) & = \dfrac{x-\lambda_{i+1}}{\lambda_{i+2}-\lambda_{i+1}} B_{i+1}^{1}(x) +\dfrac{\lambda_{i+3}-x}{\lambda_{i+3}-\lambda_{i+2}} B_{i+2}^{1}(x)\\ & = \begin{cases} \dfrac{x-\lambda_{i+1}}{\lambda_{i+2}-\lambda_{i+1}} & \text { if } x \in \left[\lambda_{i+1}, \lambda_{i+2}\right) \\ \dfrac{\lambda_{i+3}-x}{\lambda_{i+3}-\lambda_{i+2}} & \text { if } x \in\left[\lambda_{i+2}, \lambda_{i+3}\right) \\ 0 & \text { otherwise }\end{cases}\\ \end{align*}\]

The (normalized) B-spline of degree \(k, k \in \mathbb{N},(\) order \(k+1)\) is \[ B_{i}^{k+1}(x)=\dfrac{x-\lambda_{i}}{\lambda_{i+k}-\lambda_{i}} B_{i}^{k}(x)+\dfrac{\lambda_{i+k+1}-x}{\lambda_{i+k+1}-\lambda_{i+1}} B_{i+1}^{k}(x) \]

2.2. Đạo hàm của B-splines

  • The first derivatives of the B-splines

The functions \(Z_{i}^{k+1}(x)\) for \(k \geq 0, k \in \mathbb{N}\) are the first derivatives of the B-splines; \[ Z_{i}^{k+1}(x):=\dfrac{\mathrm{d}}{\mathrm{d} x} B_{i}^{k+2}(x) \] For \(k=0\) \[ Z_{i}^{1}(x)=\left\{\begin{array}{cl} \dfrac{1}{\lambda_{i+1}-\lambda_{i}} & \text { if } x \in\left[\lambda_{i}, \lambda_{i+1}\right) \\ \dfrac{-1}{\lambda_{i+2}-\lambda_{i+1}} & \text { if } x \in\left(\lambda_{i+1}, \lambda_{i+2}\right] \end{array}\right. \] For \(k \geq 1\) \[\begin{equation} Z_{i}^{k+1}(x) = (k+1)\left(\dfrac{B_{i}^{k+1}(x)}{\lambda_{i+k+1}-\lambda_{i}}-\dfrac{B_{i+1}^{k+1}(x)}{\lambda_{i+k+2}-\lambda_{i+1}}\right) = \end{equation}\]

  • Functions \(Z_{i}^{k+1}(x)\) have similar properties as B-splines \(B_{i}^{k+1}(x)\).
  • The second derivatives of the B-splines The functions \(S_i^{k+1}(x)\) for \(k \geq 0, k \in \mathbb{N}\) are the second derivatives of the B-splines; \[ S_i^{k+1}(x)(x):=\dfrac{\mathrm{d^2}}{\mathrm{d} x^2} B_{i}^{k+2}(x) =\dfrac{\mathrm{d}}{\mathrm{d} x} Z_{i}^{k+1}(x) \] For \(k=0\) \[ S_i^{1}(x)(x)=0 \] For \(k \geq 1\)

\[\begin{equation*} S_i^{k+1}(x)(x) = (k+1)\left(\dfrac{1}{\lambda_{i+k+1} -\lambda_{i}} \dfrac{\mathrm{d}}{\mathrm{d} x} B_{i}^{k+1}(x) -\dfrac{1}{\lambda_{i+k+2}-\lambda_{i+1}} \dfrac{\mathrm{d}}{\mathrm{d} x} B_{i+1}^{k+1}(x) \right) \end{equation*}\]

2.3.The vector space \(\mathcal{S}_{k}^{\Delta \lambda}[a, b]\)

The vector space \(\mathcal{S}_{k}^{\Delta \lambda}[a, b]\) of polynomial splines of degree \(k>0, k \in \mathbb{N}\), defined on a finite interval \(I=[a, b]\) with the sequence of knots \(\Delta \lambda=\left\{\lambda_{i}\right\}_{i=0}^{g+1}, \lambda_{0}=a<\lambda_{1}<\ldots<\lambda_{g}<b=\lambda_{g+1}\), the dimension is \[ \operatorname{dim}\left(\mathcal{S}_{k}^{\Delta \lambda}[a, b]\right)=g+k+1 \] Then adding more knots \[ \lambda_{-k}=\cdots=\lambda_{-1}=\lambda_{0}=a, \quad b=\lambda_{g+1}=\lambda_{g+2}=\cdots=\lambda_{g+k+1} \]

A basis functions \(B_{i}^{k+1}(x), i = \overline{1, g+k+1}\).\

A spline \(s_{k}(x) \in \mathcal{S}_{k}^{\Delta \lambda}[a, b]\) in \(L^{2}(I)\) has a unique representation \[ s_{k}(x)=\sum_{i=-k}^{g} b_{i} B_{i}^{k+1}(x) . \]

3. Biểu diễn B-splines: A toy example by hand!

Mục này minh họa B-plines bậc (degree) 0, 1, 2 và 3, trên khoảng \([0,6]\) và tại các nút chia 0, 1, 4, 6 để hiểu rõ về B-Splines.

Định nghĩa khoảng và nút

require(tidyverse)
lambda0 <- 0
lambda1 <- 1
lambda2 <- 4
lambda3 <- 6
lambda4 <- lambda3

B1 <- data.frame(x = seq(0, 6, by = 0.01))

3.1 B-spline degree 0 với các nút trên.

B1 <- B1 %>% mutate(B10 = ifelse(lambda0 <= x & x< lambda1, 1, 0),
                    B11 = ifelse(lambda1 <= x & x< lambda2, 1, 0),
                    B12 = ifelse(lambda2 <= x & x< lambda3, 1, 0))

plot(B1$x, B1$B10, col = "blue",cex = 1,lty = 1,
     main = "B-Spline of degree 0 ",
     ylim = c( 0, 1.2 ),
     xlab = "x", ylab = "Value")
points(B1$x, B1$B11, col = "green",cex = 1, 
       pch = 1)
points(B1$x, B1$B12, col = "pink",cex = 1, 
       pch = 20)
legend("topright",legend = c("B10", "B11", "B12"),
       lty = c(1,1,1), col = c("blue", "green", "pink"),
      cex = 1)

3.2 B-spline degree 1 với các nút trên.

#============B2=========
B2 <- B1 %>% mutate(B20 = ((x -lambda0)/(lambda1- lambda0))*B10 +  ((lambda2 - x)/(lambda2- lambda1))*B11,
                    B21 = ((x -lambda1)/(lambda2- lambda1))*B11 +  ((lambda3 - x)/(lambda3- lambda2))*B12)

plot(B2$x, B2$B20, col = "blue",cex = 1,lty = 1,
     main = "B-Spline of degree 1 ",
     ylim = c( 0, 1.2 ),
     xlab = "x", ylab = "Value")
points(B2$x, B2$B21, col = "green",cex = 1, 
       pch = 1)

legend("topright", legend = c("B20", "B21"),
       lty = c(1,1), col = c("blue", "green"),
      cex = 1)

3.3 B-spline degree 2 với các nút trên.

#============B3=========

B3 <- B2 %>% mutate(B30 = ((x -lambda0)/(lambda2- lambda1))*B20 +  
                      ((lambda3 - x)/(lambda3- lambda1))*B21)

plot(B3$x, B3$B30, col = "blue",cex = 1,lty = 1,
     main = "B-Spline of degree 2 ",
     ylim = c( 0, 1.2 ),
     xlab = "x", ylab = "Value")
legend("topright", legend = c("B30"),
       lty = c(1,1), col = c("blue"),
      cex = 1)

4. Gói splines trong R

Đây là gói lệnh cung cấp nhiều function trong ước lượng Splines, chi tiết tại https://www.rdocumentation.org/packages/splines/versions/3.6.2. Một số hàm đơn giản như dưới này:

4.1. Hàm splineDesign(): Thiết kế các B-splines trên khoảng xác định, các nút và thứ tự. Trong đó

** knots là vị trí các nút. ** x: Tập xác định của các B-splines. ** derivs: Cấp của đạo hàm của B-splines.

Ví dụ trong R:

require(splines)
## Loading required package: splines
knots <- c(1,1.8,3:5,6.5,7,8.1,9.2,10)  # 10 => 10-4 = 6 Basis splines
x <- seq(min(knots)-1, max(knots)+1, length.out = 501)
bb <- splineDesign(knots, x = x, outer.ok = TRUE)

plot(range(x), c(0,1), type = "n", xlab = "x", ylab = "",
     main =  "B-splines - sum to 1 inside inner knots")
#mtext(expression(B[j](x) *"  and "* sum(B[j](x), j == 1, 6)), adj = 0)
abline(v = knots, lty = 3, col = "light gray")
abline(v = knots[c(4,length(knots)-3)], lty = 3, col = "gray10")
lines(x, rowSums(bb), col = "gray", lwd = 2)
matlines(x, bb, ylim = c(0,1), lty = 1)

4.2. Hàm bs(): B-Spline Basis for Polynomial Splines

Thử 1 số code sẵn trong R!!

require(stats); require(graphics)
#bs(women$height, df = 5)
summary(fm1 <- lm(weight ~ bs(height, df = 5), data = women))
## 
## Call:
## lm(formula = weight ~ bs(height, df = 5), data = women)
## 
## Residuals:
##      Min       1Q   Median       3Q      Max 
## -0.31764 -0.13441  0.03922  0.11096  0.35086 
## 
## Coefficients:
##                     Estimate Std. Error t value Pr(>|t|)    
## (Intercept)         114.8799     0.2167 530.146  < 2e-16 ***
## bs(height, df = 5)1   3.4657     0.4595   7.543 3.53e-05 ***
## bs(height, df = 5)2  13.0300     0.3965  32.860 1.10e-10 ***
## bs(height, df = 5)3  27.6161     0.4571  60.415 4.70e-13 ***
## bs(height, df = 5)4  40.8481     0.3866 105.669 3.09e-15 ***
## bs(height, df = 5)5  49.1296     0.3090 158.979  < 2e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 0.2276 on 9 degrees of freedom
## Multiple R-squared:  0.9999, Adjusted R-squared:  0.9998 
## F-statistic: 1.298e+04 on 5 and 9 DF,  p-value: < 2.2e-16
## example of safe prediction
plot(women, xlab = "Height (in)", ylab = "Weight (lb)")
ht <- seq(57, 73, length.out = 200)
lines(ht, predict(fm1, data.frame(height = ht)))
## Warning in bs(height, degree = 3L, knots = c(`33.33333%` = 62.6666666666667, :
## some 'x' values beyond boundary knots may cause ill-conditioned bases

5. So sánh kết quả tự tính và hàm sẵn trong gói

Trong quá trình học, trong phần mềm R cũng thế!! Đôi khi chúng ta phải viết chi tiết các tính toán “manually” trước khi sử dụng một function để biết chính xác hàm đang tính như thế nào.

So sánh B-plines bậc (degree) 0, 1, 2 và 3, trên khoảng \([0,6]\) và tại các nút chia 0, 1, 4, 6 bằng hai phương pháp

5.1. B-Spline of degree 1 (order= 2)

knots <-c(lambda0, lambda1, lambda2, lambda3, lambda4) 
x = seq(0, 6, by = 0.01)
bb <- splineDesign(knots, x = x, outer.ok = TRUE,
                   ord = 2, derivs =0)
op <- par(mfrow = c(1, 2))
plot(B2$x, B2$B20, col = "black",cex = 1.5,lty = 1, type = "l",
     main = "By Huong ",
     ylim = c( 0, 1.2 ),
     xlab = "x", ylab = "Value")
lines(B2$x, B2$B21, col = "red",cex = 1.5)
abline(v = knots, lty = 3, col = "light gray")


plot(range(x), c(0,1), type = "n", xlab = "x", ylab = "",
     main =  "By splineDesign() function",
     ylim = c( 0, 1.2 ))
#mtext(expression(B[j](x) *"  and "* sum(B[j](x), j == 1, 6)), adj = 0)
abline(v = knots, lty = 3, col = "light gray")
abline(v = knots[c(4,length(knots)-3)], lty = 3, col = "gray10")
#lines(x, rowSums(bb), col = "gray", lwd = 2)
matlines(x, bb, ylim = c(0,1), lty = 1)

par(op)

5.1. B-Spline of degree 2 (order= 3)

#==============Compare Huong's code and package, ord = 3

knots <-c(lambda0, lambda1, lambda2, lambda3, lambda4) 
x = seq(0, 6, by = 0.01)
bb <- splineDesign(knots, x = x, outer.ok = TRUE,
                   ord = 3, derivs =0)
op <- par(mfrow = c(1, 2))
plot(B3$x, B3$B30, col = "black",cex = 1.5, lty = 1, type = "l",
     main = "By Huong ",
     ylim = c( 0, 1.2 ),
     xlab = "x", ylab = "Value")
abline(v = knots, lty = 3, col = "light gray")


plot(range(x), c(0,1), type = "n", xlab = "x", ylab = "",
     main =  "By splineDesign() function",
     ylim = c( 0, 1.2 ))
#mtext(expression(B[j](x) *"  and "* sum(B[j](x), j == 1, 6)), adj = 0)
abline(v = knots, lty = 3, col = "light gray")
abline(v = knots[c(4,length(knots)-3)], lty = 3, col = "gray10")
#lines(x, rowSums(bb), col = "gray", lwd = 2)
matlines(x, bb, ylim = c(0,1), lty = 1)

par(op)

THANK YOU!

LS0tDQp0aXRsZTogIkhp4buDdSB24buBIEItc3BsaW5lcyINCmF1dGhvcjogIlRy4buLbmggVGjhu4sgSMaw4budbmcgKFRNVSkiDQpkYXRlOiAiOS85LzIwMjEiDQpvdXRwdXQ6IA0KICBodG1sX2RvY3VtZW50OiANCiAgICBjb2RlX2Rvd25sb2FkOiB0cnVlDQogICAgY29kZV9mb2xkaW5nOiBoaWRlDQogICAgaGlnaGxpZ2h0OiBweWdtZW50cw0KICAgICMgbnVtYmVyX3NlY3Rpb25zOiB5ZXMNCiAgICB0aGVtZTogImZsYXRseSINCiAgICB0b2M6IFRSVUUNCiAgICB0b2NfZmxvYXQ6IFRSVUUNCi0tLQ0KDQpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0NCmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvID0gVFJVRSkNCmBgYA0KDQojIyAxLiBCLXNwbGluZSBsw6AgZ8OsPyB7LnRhYnNldH0NCkPDoWMgxJHGsOG7nW5nIGNvbmcgU3BsaW5lIGJhbyBn4buTbSBuaOG7r25nIMSRb+G6oW4gxJFhIHRo4bupYyBr4bq/dCBu4buRaSB24bubaSBuaGF1IG3hu5l0IGPDoWNoIHRyxqFuIG5o4bq1biB04bqhaSBjw6FjIMSRaeG7g20gxJHGsOG7o2MgZ+G7jWkgbMOgIG7DunQuIFNwbGluZSDEkcaw4bujYyBz4butIGThu6VuZyB0cm9uZyBjw6FjIMaw4bubYyBsxrDhu6NuZyBsw6BtIHRyxqFuIGPDoWMgxJHGsOG7nW5nIGNvbmcgaOG7k2kgcXV5IHbDoCB0cm9uZyBjw6FjIMaw4bubYyBsxrDhu6NuZyBtw7QgaMOsbmggY+G7mW5nIHTDrW5oIG3hu58gcuG7mW5nIChUaGVvIFThu6ogxJBJ4buCTiBDw4FDIFRIVeG6rFQgTkfhu64gVEjhu5BORyBLw4ogT1hGT1JELCBUw6FjIGdp4bqjOiBZYWRvbGFoIERvZGdlLCBOZ8aw4budaSBk4buLY2g6IFBHUy5UUyBUw7QgQ+G6qW0gVMO6LCBOaMOgIHh14bqldCBi4bqjbjogxJDhuqFpIGjhu41jIFF14buRYyBnaWEgSMOgIE7hu5lpLCAyMDE4KS4gDQoNClNwbGluZXMgxJHGsOG7o2MgxJHhurdjIHRyxrBuZyBi4bufaSBjw6FjIG7DunQgY2hpYSAoa25vdHMpIHRyw6puIDEga2hv4bqjbmcgeMOhYyDEkeG7i25oIChkb21haW4gcGFydGl0aW9uKSwgYuG6rWMgKG9yZGVyL2RlZ3JlZSkuDQoNCkPDoWMgxJFhIHRo4bupYyBTcGxpbmUgYuG6rWMgYmEgxJHGsOG7o2Mgc+G7rSBk4bulbmcgcGjhu5UgYmnhur9uIG5o4bqldCwgdHJvbmcgxJHDsywgY8OhYyBzcGxpbmVzIG7DoHkgcGjhuqNpIGPDsyDEkeG6oW8gaMOgbSBi4bqtYyBuaOG6pXQgdsOgIGLhuq1jIGhhaSBsacOqbiB04bulYyB04bqhaSBjw6FjIG7DunQuDQoNCkItc3BsaW5lIChDdXJyeS1TY2hvZW5iZXJnIEItU3BpbmVzKSBsw6AgY8OhYyBow6BtIGPGoSBz4bufIGNobyBjw6FjIHNwbGluZSBjw7MgY8O5bmcgdGjhu6kgdOG7sSDEkcaw4bujYyB4w6FjIMSR4buLbmggdHLDqm4gY8OhYyBuw7p0IGdp4buRbmcgbmhhdSwgY8OzIG5naMSpYSBsw6AgdOG6pXQgY+G6oyBjw6FjIGjDoG0gc3BsaW5lIGPDsyB0aOG7gyBjw7MgY8OzIHRo4buDIMSRxrDhu6NjIHjDonkgZOG7sW5nIHThu6sgc+G7sSBr4bq/dCBo4bujcCB0dXnhur9uIHTDrW5oIGPhu6dhIEItc3BsaW5lcyB2w6AgY2jhu4kgY8OzIG3hu5l0IHPhu7Ega+G6v3QgaOG7o3AgxJHhu5ljIMSRw6FvIGNobyBt4buXaSBjaOG7qWMgbsSDbmcgc3BsaW5lLg0KDQpMxrB1IMO9OiBC4bqtYyAoZGVncmVlKSBj4bunYSBCLXNwbGluZXMgxJHGsOG7o2MgxJHhu4tuaCBuZ2jEqWEgdOG7qyAwLCB0cm9uZyBraGkgdGjhu6kgdOG7sSAob3JkZXIpIGPhu6dhIEItc3BsaW5lcyDEkcaw4bujYyDEkeG7i25oIG5naMSpYSB04burIDEuIA0KDQokJCBvcmRlciA9IGRlZ3JlZSArMSAgJCQNCg0KIyMgMi4gxJDhu4tuaCBuZ2jEqWEgQi1zcGxpbmVzLCDEkeG6oW8gaMOgbSBj4bunYSBCLXNwbGluZXMgdsOgIEtHVlQgJFxtYXRoY2Fse1N9X3trfV57XERlbHRhIFxsYW1iZGF9W2EsIGJdJCB7LnRhYnNldH0NCipN4buZdCBjaMO6dCB0b8OhbiBuaMOpISoNCg0KxJDhu4tuaCBuZ2jEqWEgZMaw4bubaSBuw6B5IMSRxrDhu6NjIHThu5VuZyBo4bujcCB04burIERlIEJvb3IsIEMuLCAmIERlIEJvb3IsIEMuICgxOTc4KSAocGFnZSA4OCwgMTA2KSB2w6AgU2NodW1ha2VyLCBMLiAoMjAwNykgKHBhZ2UgMTI0KS4NCg0KIyMjIDIuMSDEkOG7i25oIG5naMSpYSBCLXNwbGluZXMNCkxldCB0aGUgc2VxdWVuY2Ugb2Yga25vdHM6ICRcbGFtYmRhX3swfT1hPFxsYW1iZGFfezF9PFxjZG90czxcbGFtYmRhX3tnfTxiPVxsYW1iZGFfe2crMX0gJA0KDQpUaGUgKG5vcm1hbGl6ZWQpIEItc3BsaW5lIG9mIGRlZ3JlZSAwIChvcmRlciAxKSwgZm9yICRpPTAsIFxsZG90cywgZyQNCiQkDQpCX3tpfV57MX0oeCk9IFxiZWdpbntjYXNlc30xICYgXHRleHQgeyBpZiB9IHggXGluXGxlZnRbXGxhbWJkYV97aX0sIFxsYW1iZGFfe2krMX1ccmlnaHQpIFxcIDAgJiBcdGV4dCB7IG90aGVyd2lzZSB9XGVuZHtjYXNlc30NCiQkDQoNClRoZSAobm9ybWFsaXplZCkgQi1zcGxpbmUgb2YgZGVncmVlIDEgKG9yZGVyIDIpLCBmb3IgJGk9MCwgXGxkb3RzLCBnJA0KXGJlZ2lue2FsaWduKn0NCiAgICBCX3tpfV57Mn0oeCkgJiA9IFxkZnJhY3t4LVxsYW1iZGFfe2l9fXtcbGFtYmRhX3tpKzF9LVxsYW1iZGFfe2l9fSBCX3tpfV57MX0oeCkgK1xkZnJhY3tcbGFtYmRhX3tpKzJ9LXh9e1xsYW1iZGFfe2krMn0tXGxhbWJkYV97aSsxfX0gQl97aSsxfV57MX0oeClcXA0KJiA9IA0KXGJlZ2lue2Nhc2VzfQ0KXGRmcmFje3gtXGxhbWJkYV97aX19e1xsYW1iZGFfe2krMX0tXGxhbWJkYV97aX19ICYgXHRleHQgeyBpZiB9IHggXGluXGxlZnRbXGxhbWJkYV97aX0sIFxsYW1iZGFfe2krMX1ccmlnaHQpIFxcDQpcZGZyYWN7XGxhbWJkYV97aSsyfS14fXtcbGFtYmRhX3tpKzJ9LVxsYW1iZGFfe2krMX19ICAmICAgXHRleHQgeyBpZiB9IHggXGluXGxlZnRbXGxhbWJkYV97aSsxfSwgXGxhbWJkYV97aSsyfVxyaWdodCkgXFwNCjAgJiBcdGV4dCB7IG90aGVyd2lzZSB9XGVuZHtjYXNlc31cXCANClxlbmR7YWxpZ24qfQ0KQW5kDQpcYmVnaW57YWxpZ24qfQ0KQl97aSsxfV57Mn0oeCkgJiA9IFxkZnJhY3t4LVxsYW1iZGFfe2krMX19e1xsYW1iZGFfe2krMn0tXGxhbWJkYV97aSsxfX0gQl97aSsxfV57MX0oeCkgK1xkZnJhY3tcbGFtYmRhX3tpKzN9LXh9e1xsYW1iZGFfe2krM30tXGxhbWJkYV97aSsyfX0gQl97aSsyfV57MX0oeClcXA0KJiA9IA0KICBcYmVnaW57Y2FzZXN9DQpcZGZyYWN7eC1cbGFtYmRhX3tpKzF9fXtcbGFtYmRhX3tpKzJ9LVxsYW1iZGFfe2krMX19ICYgXHRleHQgeyBpZiB9IHggXGluIFxsZWZ0W1xsYW1iZGFfe2krMX0sIFxsYW1iZGFfe2krMn1ccmlnaHQpIFxcDQpcZGZyYWN7XGxhbWJkYV97aSszfS14fXtcbGFtYmRhX3tpKzN9LVxsYW1iZGFfe2krMn19ICAmICAgXHRleHQgeyBpZiB9IHggXGluXGxlZnRbXGxhbWJkYV97aSsyfSwgXGxhbWJkYV97aSszfVxyaWdodCkgXFwNCjAgJiBcdGV4dCB7IG90aGVyd2lzZSB9XGVuZHtjYXNlc31cXCANClxlbmR7YWxpZ24qfQ0KDQoNClRoZSAobm9ybWFsaXplZCkgQi1zcGxpbmUgb2YgZGVncmVlICRrLCBrIFxpbiBcbWF0aGJie059LCgkIG9yZGVyICRrKzEpJCBpcw0KJCQNCkJfe2l9XntrKzF9KHgpPVxkZnJhY3t4LVxsYW1iZGFfe2l9fXtcbGFtYmRhX3tpK2t9LVxsYW1iZGFfe2l9fSBCX3tpfV57a30oeCkrXGRmcmFje1xsYW1iZGFfe2kraysxfS14fXtcbGFtYmRhX3tpK2srMX0tXGxhbWJkYV97aSsxfX0gQl97aSsxfV57a30oeCkNCiQkDQoNCiMjIyAyLjIuIMSQ4bqhbyBow6BtIGPhu6dhIEItc3BsaW5lcyB7LnRhYnNldH0NCiogVGhlIGZpcnN0IGRlcml2YXRpdmVzIG9mIHRoZSBCLXNwbGluZXMNCg0KVGhlIGZ1bmN0aW9ucyAkWl97aX1ee2srMX0oeCkkIGZvciAkayBcZ2VxIDAsIGsgXGluIFxtYXRoYmJ7Tn0kIGFyZSB0aGUgZmlyc3QgZGVyaXZhdGl2ZXMgb2YgdGhlIEItc3BsaW5lczsgDQokJA0KWl97aX1ee2srMX0oeCk6PVxkZnJhY3tcbWF0aHJte2R9fXtcbWF0aHJte2R9IHh9IEJfe2l9XntrKzJ9KHgpDQokJA0KRm9yICRrPTAkDQokJA0KWl97aX1eezF9KHgpPVxsZWZ0XHtcYmVnaW57YXJyYXl9e2NsfQ0KXGRmcmFjezF9e1xsYW1iZGFfe2krMX0tXGxhbWJkYV97aX19ICYgXHRleHQgeyBpZiB9IHggXGluXGxlZnRbXGxhbWJkYV97aX0sIFxsYW1iZGFfe2krMX1ccmlnaHQpIFxcDQpcZGZyYWN7LTF9e1xsYW1iZGFfe2krMn0tXGxhbWJkYV97aSsxfX0gJiBcdGV4dCB7IGlmIH0geCBcaW5cbGVmdChcbGFtYmRhX3tpKzF9LCBcbGFtYmRhX3tpKzJ9XHJpZ2h0XQ0KXGVuZHthcnJheX1ccmlnaHQuDQokJA0KRm9yICRrIFxnZXEgMSQNClxiZWdpbntlcXVhdGlvbn0NCiAgDQogICAgWl97aX1ee2srMX0oeCkgPSAoaysxKVxsZWZ0KFxkZnJhY3tCX3tpfV57aysxfSh4KX17XGxhbWJkYV97aStrKzF9LVxsYW1iZGFfe2l9fS1cZGZyYWN7Ql97aSsxfV57aysxfSh4KX17XGxhbWJkYV97aStrKzJ9LVxsYW1iZGFfe2krMX19XHJpZ2h0KSA9DQpcZW5ke2VxdWF0aW9ufQ0KDQoqIEZ1bmN0aW9ucyAkWl97aX1ee2srMX0oeCkkIGhhdmUgc2ltaWxhciBwcm9wZXJ0aWVzIGFzIEItc3BsaW5lcyAkQl97aX1ee2srMX0oeCkkLg0KXGJlZ2lue2l0ZW1pemV9DQogICAgXGl0ZW0gVGhleSBhcmUgcGllY2V3aXNlIHBvbHlub21pYWxzIG9mIGRlZ3JlZSAkayQuDQogICAgXGl0ZW0gSXQgaXMgZXZpZGVudCB0aGF0IGZvciAkayBcZ2VxIDEkIHRoZSBmdW5jdGlvbiAkWl97aX1ee2srMX0oeCkkIGFuZCBpdHMgZGVyaXZhdGl2ZXMgdXAgdG8gb3JkZXIgJGstMSQgYXJlIGFsbCBjb250aW51b3VzLiANCiAgICBcaXRlbSBGb3IgJGsgXGdlcSAwJA0KJCQNClxvcGVyYXRvcm5hbWV7c3VwcH0gWl97aX1ee2srMX0oeCk9XG9wZXJhdG9ybmFtZXtzdXBwfSBCX3tpfV57aysyfSh4KT1cbGVmdFtcbGFtYmRhX3tpfSwgXGxhbWJkYV97aStrKzJ9XHJpZ2h0XQ0KJCQNCiQkIFpfe2l9XntrKzF9KHgpPTAgXHRleHQgeyBpZiB9IHggXG5vdGluXGxlZnRbXGxhbWJkYV97aX0sIFxsYW1iZGFfe2kraysyfVxyaWdodF0gJCQNClxpdGVtIFRoZSBpbnRlZ3JhbCBvZiAkWl97aX1ee2srMX0oeCkkIGVxdWFscyB0byB6ZXJvLg0KJCQNClxpbnRfe1xtYXRoYmJ7Un19IFpfe2l9XntrKzF9KHgpIFxtYXRocm17ZH0geD0wDQokJA0KXGVuZHtpdGVtaXplfQ0KDQoqIFRoZSBzZWNvbmQgZGVyaXZhdGl2ZXMgb2YgdGhlIEItc3BsaW5lcw0KVGhlIGZ1bmN0aW9ucyAkU19pXntrKzF9KHgpJCBmb3IgJGsgXGdlcSAwLCBrIFxpbiBcbWF0aGJie059JCBhcmUgdGhlIHNlY29uZCBkZXJpdmF0aXZlcyBvZiB0aGUgQi1zcGxpbmVzOyANCiQkDQpTX2lee2srMX0oeCkoeCk6PVxkZnJhY3tcbWF0aHJte2ReMn19e1xtYXRocm17ZH0geF4yfSBCX3tpfV57aysyfSh4KSA9XGRmcmFje1xtYXRocm17ZH19e1xtYXRocm17ZH0geH0gWl97aX1ee2srMX0oeCkgDQokJA0KRm9yICRrPTAkDQokJA0KU19pXnsxfSh4KSh4KT0wDQokJA0KRm9yICRrIFxnZXEgMSQNCg0KXGJlZ2lue2VxdWF0aW9uKn0NClNfaV57aysxfSh4KSh4KSA9IChrKzEpXGxlZnQoXGRmcmFjezF9e1xsYW1iZGFfe2kraysxfSAgLVxsYW1iZGFfe2l9fSBcZGZyYWN7XG1hdGhybXtkfX17XG1hdGhybXtkfSB4fSBCX3tpfV57aysxfSh4KSAtXGRmcmFjezF9e1xsYW1iZGFfe2kraysyfS1cbGFtYmRhX3tpKzF9fSBcZGZyYWN7XG1hdGhybXtkfX17XG1hdGhybXtkfSB4fSBCX3tpKzF9XntrKzF9KHgpIFxyaWdodCkNClxlbmR7ZXF1YXRpb24qfQ0KDQojIyMgMi4zLlRoZSB2ZWN0b3Igc3BhY2UgJFxtYXRoY2Fse1N9X3trfV57XERlbHRhIFxsYW1iZGF9W2EsIGJdJCAgey50YWJzZXR9DQoNClRoZSB2ZWN0b3Igc3BhY2UgJFxtYXRoY2Fse1N9X3trfV57XERlbHRhIFxsYW1iZGF9W2EsIGJdJCBvZiBwb2x5bm9taWFsIHNwbGluZXMgb2YgZGVncmVlICRrPjAsIGsgXGluIFxtYXRoYmJ7Tn0kLCBkZWZpbmVkIG9uIGEgZmluaXRlIGludGVydmFsICRJPVthLCBiXSQgd2l0aCB0aGUgc2VxdWVuY2Ugb2Yga25vdHMgJFxEZWx0YSBcbGFtYmRhPVxsZWZ0XHtcbGFtYmRhX3tpfVxyaWdodFx9X3tpPTB9XntnKzF9LCBcbGFtYmRhX3swfT1hPFxsYW1iZGFfezF9PFxsZG90czxcbGFtYmRhX3tnfTxiPVxsYW1iZGFfe2crMX0kLCB0aGUgZGltZW5zaW9uIGlzDQokJA0KXG9wZXJhdG9ybmFtZXtkaW19XGxlZnQoXG1hdGhjYWx7U31fe2t9XntcRGVsdGEgXGxhbWJkYX1bYSwgYl1ccmlnaHQpPWcraysxDQokJA0KVGhlbiBhZGRpbmcgbW9yZSBrbm90cw0KJCQNClxsYW1iZGFfey1rfT1cY2RvdHM9XGxhbWJkYV97LTF9PVxsYW1iZGFfezB9PWEsIFxxdWFkIGI9XGxhbWJkYV97ZysxfT1cbGFtYmRhX3tnKzJ9PVxjZG90cz1cbGFtYmRhX3tnK2srMX0NCiQkDQoNCkEgYmFzaXMgZnVuY3Rpb25zICRCX3tpfV57aysxfSh4KSwgaSA9IFxvdmVybGluZXsxLCBnK2srMX0kLlxcDQoNCkEgc3BsaW5lICRzX3trfSh4KSBcaW4gXG1hdGhjYWx7U31fe2t9XntcRGVsdGEgXGxhbWJkYX1bYSwgYl0kIGluICRMXnsyfShJKSQgaGFzIGEgdW5pcXVlIHJlcHJlc2VudGF0aW9uDQokJA0Kc197a30oeCk9XHN1bV97aT0ta31ee2d9IGJfe2l9IEJfe2l9XntrKzF9KHgpIC4NCiQkDQoNCiMjIDMuIEJp4buDdSBkaeG7hW4gQi1zcGxpbmVzOiBBIHRveSBleGFtcGxlIGJ5IGhhbmQhIHsudGFic2V0fQ0KDQpN4bulYyBuw6B5IG1pbmggaOG7jWEgQi1wbGluZXMgYuG6rWMgKGRlZ3JlZSkgMCwgMSwgMiB2w6AgMywgdHLDqm4ga2hv4bqjbmcgJFswLDZdJCB2w6AgdOG6oWkgY8OhYyBuw7p0IGNoaWEgMCwgMSwgNCwgNiDEkeG7gyBoaeG7g3UgcsO1IHbhu4EgQi1TcGxpbmVzLg0KDQrEkOG7i25oIG5naMSpYSBraG/huqNuZyB2w6AgbsO6dA0KYGBge3IsICB3YXJuaW5nID0gRkFMU0UsIG1lc3NhZ2UgPSBGQUxTRX0NCnJlcXVpcmUodGlkeXZlcnNlKQ0KbGFtYmRhMCA8LSAwDQpsYW1iZGExIDwtIDENCmxhbWJkYTIgPC0gNA0KbGFtYmRhMyA8LSA2DQpsYW1iZGE0IDwtIGxhbWJkYTMNCg0KQjEgPC0gZGF0YS5mcmFtZSh4ID0gc2VxKDAsIDYsIGJ5ID0gMC4wMSkpDQoNCmBgYA0KDQojIyMgMy4xIEItc3BsaW5lIGRlZ3JlZSAwIHbhu5tpIGPDoWMgbsO6dCB0csOqbi4gDQpgYGB7cn0NCkIxIDwtIEIxICU+JSBtdXRhdGUoQjEwID0gaWZlbHNlKGxhbWJkYTAgPD0geCAmIHg8IGxhbWJkYTEsIDEsIDApLA0KICAgICAgICAgICAgICAgICAgICBCMTEgPSBpZmVsc2UobGFtYmRhMSA8PSB4ICYgeDwgbGFtYmRhMiwgMSwgMCksDQogICAgICAgICAgICAgICAgICAgIEIxMiA9IGlmZWxzZShsYW1iZGEyIDw9IHggJiB4PCBsYW1iZGEzLCAxLCAwKSkNCg0KcGxvdChCMSR4LCBCMSRCMTAsIGNvbCA9ICJibHVlIixjZXggPSAxLGx0eSA9IDEsDQogICAgIG1haW4gPSAiQi1TcGxpbmUgb2YgZGVncmVlIDAgIiwNCiAgICAgeWxpbSA9IGMoIDAsIDEuMiApLA0KICAgICB4bGFiID0gIngiLCB5bGFiID0gIlZhbHVlIikNCnBvaW50cyhCMSR4LCBCMSRCMTEsIGNvbCA9ICJncmVlbiIsY2V4ID0gMSwgDQogICAgICAgcGNoID0gMSkNCnBvaW50cyhCMSR4LCBCMSRCMTIsIGNvbCA9ICJwaW5rIixjZXggPSAxLCANCiAgICAgICBwY2ggPSAyMCkNCmxlZ2VuZCgidG9wcmlnaHQiLGxlZ2VuZCA9IGMoIkIxMCIsICJCMTEiLCAiQjEyIiksDQogICAgICAgbHR5ID0gYygxLDEsMSksIGNvbCA9IGMoImJsdWUiLCAiZ3JlZW4iLCAicGluayIpLA0KICAgICAgY2V4ID0gMSkNCg0KYGBgDQoNCiMjIyAzLjIgQi1zcGxpbmUgZGVncmVlIDEgduG7m2kgY8OhYyBuw7p0IHRyw6puLg0KDQpgYGB7cn0NCiM9PT09PT09PT09PT1CMj09PT09PT09PQ0KQjIgPC0gQjEgJT4lIG11dGF0ZShCMjAgPSAoKHggLWxhbWJkYTApLyhsYW1iZGExLSBsYW1iZGEwKSkqQjEwICsgICgobGFtYmRhMiAtIHgpLyhsYW1iZGEyLSBsYW1iZGExKSkqQjExLA0KICAgICAgICAgICAgICAgICAgICBCMjEgPSAoKHggLWxhbWJkYTEpLyhsYW1iZGEyLSBsYW1iZGExKSkqQjExICsgICgobGFtYmRhMyAtIHgpLyhsYW1iZGEzLSBsYW1iZGEyKSkqQjEyKQ0KDQpwbG90KEIyJHgsIEIyJEIyMCwgY29sID0gImJsdWUiLGNleCA9IDEsbHR5ID0gMSwNCiAgICAgbWFpbiA9ICJCLVNwbGluZSBvZiBkZWdyZWUgMSAiLA0KICAgICB5bGltID0gYyggMCwgMS4yICksDQogICAgIHhsYWIgPSAieCIsIHlsYWIgPSAiVmFsdWUiKQ0KcG9pbnRzKEIyJHgsIEIyJEIyMSwgY29sID0gImdyZWVuIixjZXggPSAxLCANCiAgICAgICBwY2ggPSAxKQ0KDQpsZWdlbmQoInRvcHJpZ2h0IiwgbGVnZW5kID0gYygiQjIwIiwgIkIyMSIpLA0KICAgICAgIGx0eSA9IGMoMSwxKSwgY29sID0gYygiYmx1ZSIsICJncmVlbiIpLA0KICAgICAgY2V4ID0gMSkNCmBgYA0KDQojIyMgMy4zIEItc3BsaW5lIGRlZ3JlZSAyIHbhu5tpIGPDoWMgbsO6dCB0csOqbi4NCmBgYHtyfQ0KIz09PT09PT09PT09PUIzPT09PT09PT09DQoNCkIzIDwtIEIyICU+JSBtdXRhdGUoQjMwID0gKCh4IC1sYW1iZGEwKS8obGFtYmRhMi0gbGFtYmRhMSkpKkIyMCArICANCiAgICAgICAgICAgICAgICAgICAgICAoKGxhbWJkYTMgLSB4KS8obGFtYmRhMy0gbGFtYmRhMSkpKkIyMSkNCg0KcGxvdChCMyR4LCBCMyRCMzAsIGNvbCA9ICJibHVlIixjZXggPSAxLGx0eSA9IDEsDQogICAgIG1haW4gPSAiQi1TcGxpbmUgb2YgZGVncmVlIDIgIiwNCiAgICAgeWxpbSA9IGMoIDAsIDEuMiApLA0KICAgICB4bGFiID0gIngiLCB5bGFiID0gIlZhbHVlIikNCmxlZ2VuZCgidG9wcmlnaHQiLCBsZWdlbmQgPSBjKCJCMzAiKSwNCiAgICAgICBsdHkgPSBjKDEsMSksIGNvbCA9IGMoImJsdWUiKSwNCiAgICAgIGNleCA9IDEpDQpgYGANCg0KIyMgNC4gR8OzaSAqc3BsaW5lcyogdHJvbmcgUiB7LnRhYnNldH0NCsSQw6J5IGzDoCBnw7NpIGzhu4duaCBjdW5nIGPhuqVwIG5oaeG7gXUgZnVuY3Rpb24gdHJvbmcgxrDhu5tjIGzGsOG7o25nIFNwbGluZXMsIGNoaSB0aeG6v3QgdOG6oWkgPGh0dHBzOi8vd3d3LnJkb2N1bWVudGF0aW9uLm9yZy9wYWNrYWdlcy9zcGxpbmVzL3ZlcnNpb25zLzMuNi4yPi4NCk3hu5l0IHPhu5EgaMOgbSDEkcahbiBnaeG6o24gbmjGsCBkxrDhu5tpIG7DoHk6DQoNCiMjIyA0LjEuIEjDoG0gKnNwbGluZURlc2lnbigpKjogVGhp4bq/dCBr4bq/IGPDoWMgQi1zcGxpbmVzIHRyw6puIGtob+G6o25nIHjDoWMgxJHhu4tuaCwgY8OhYyBuw7p0IHbDoCB0aOG7qSB04buxLiBUcm9uZyDEkcOzDQoqKiBrbm90cyBsw6AgduG7iyB0csOtIGPDoWMgbsO6dC4NCioqIHg6IFThuq1wIHjDoWMgxJHhu4tuaCBj4bunYSBjw6FjIEItc3BsaW5lcy4NCioqIGRlcml2czogQ+G6pXAgY+G7p2EgxJHhuqFvIGjDoG0gY+G7p2EgQi1zcGxpbmVzLg0KDQpWw60gZOG7pSB0cm9uZyBSOg0KYGBge3J9DQpyZXF1aXJlKHNwbGluZXMpDQprbm90cyA8LSBjKDEsMS44LDM6NSw2LjUsNyw4LjEsOS4yLDEwKSAgIyAxMCA9PiAxMC00ID0gNiBCYXNpcyBzcGxpbmVzDQp4IDwtIHNlcShtaW4oa25vdHMpLTEsIG1heChrbm90cykrMSwgbGVuZ3RoLm91dCA9IDUwMSkNCmJiIDwtIHNwbGluZURlc2lnbihrbm90cywgeCA9IHgsIG91dGVyLm9rID0gVFJVRSkNCg0KcGxvdChyYW5nZSh4KSwgYygwLDEpLCB0eXBlID0gIm4iLCB4bGFiID0gIngiLCB5bGFiID0gIiIsDQogICAgIG1haW4gPSAgIkItc3BsaW5lcyAtIHN1bSB0byAxIGluc2lkZSBpbm5lciBrbm90cyIpDQojbXRleHQoZXhwcmVzc2lvbihCW2pdKHgpICoiICBhbmQgIiogc3VtKEJbal0oeCksIGogPT0gMSwgNikpLCBhZGogPSAwKQ0KYWJsaW5lKHYgPSBrbm90cywgbHR5ID0gMywgY29sID0gImxpZ2h0IGdyYXkiKQ0KYWJsaW5lKHYgPSBrbm90c1tjKDQsbGVuZ3RoKGtub3RzKS0zKV0sIGx0eSA9IDMsIGNvbCA9ICJncmF5MTAiKQ0KbGluZXMoeCwgcm93U3VtcyhiYiksIGNvbCA9ICJncmF5IiwgbHdkID0gMikNCm1hdGxpbmVzKHgsIGJiLCB5bGltID0gYygwLDEpLCBsdHkgPSAxKQ0KYGBgDQoNCg0KIyMjIDQuMi4gSMOgbSAqYnMoKSo6IEItU3BsaW5lIEJhc2lzIGZvciBQb2x5bm9taWFsIFNwbGluZXMNClRo4butIDEgc+G7kSBjb2RlIHPhurVuIHRyb25nIFIhIQ0KDQpgYGB7cn0NCnJlcXVpcmUoc3RhdHMpOyByZXF1aXJlKGdyYXBoaWNzKQ0KI2JzKHdvbWVuJGhlaWdodCwgZGYgPSA1KQ0Kc3VtbWFyeShmbTEgPC0gbG0od2VpZ2h0IH4gYnMoaGVpZ2h0LCBkZiA9IDUpLCBkYXRhID0gd29tZW4pKQ0KDQojIyBleGFtcGxlIG9mIHNhZmUgcHJlZGljdGlvbg0KcGxvdCh3b21lbiwgeGxhYiA9ICJIZWlnaHQgKGluKSIsIHlsYWIgPSAiV2VpZ2h0IChsYikiKQ0KaHQgPC0gc2VxKDU3LCA3MywgbGVuZ3RoLm91dCA9IDIwMCkNCmxpbmVzKGh0LCBwcmVkaWN0KGZtMSwgZGF0YS5mcmFtZShoZWlnaHQgPSBodCkpKQ0KYGBgDQoNCiMjIDUuIFNvIHPDoW5oIGvhur90IHF14bqjIHThu7EgdMOtbmggdsOgIGjDoG0gc+G6tW4gdHJvbmcgZ8OzaSB7LnRhYnNldH0NClRyb25nIHF1w6EgdHLDrG5oIGjhu41jLCB0cm9uZyBwaOG6p24gbeG7gW0gUiBjxaluZyB0aOG6vyEhIMSQw7RpIGtoaSBjaMO6bmcgdGEgcGjhuqNpIHZp4bq/dCBjaGkgdGnhur90IGPDoWMgdMOtbmggdG/DoW4gIm1hbnVhbGx5IiB0csaw4bubYyBraGkgc+G7rSBk4bulbmcgbeG7mXQgZnVuY3Rpb24gxJHhu4MgYmnhur90IGNow61uaCB4w6FjIGjDoG0gxJFhbmcgdMOtbmggbmjGsCB0aOG6vyBuw6BvLg0KDQpTbyBzw6FuaCBCLXBsaW5lcyBi4bqtYyAoZGVncmVlKSAwLCAxLCAyIHbDoCAzLCB0csOqbiBraG/huqNuZyAkWzAsNl0kIHbDoCB04bqhaSBjw6FjIG7DunQgY2hpYSAwLCAxLCA0LCA2IGLhurFuZyBoYWkgcGjGsMahbmcgcGjDoXANCg0KIyMjIDUuMS4gQi1TcGxpbmUgb2YgZGVncmVlIDEgKG9yZGVyPSAyKSANCg0KYGBge3J9DQprbm90cyA8LWMobGFtYmRhMCwgbGFtYmRhMSwgbGFtYmRhMiwgbGFtYmRhMywgbGFtYmRhNCkgDQp4ID0gc2VxKDAsIDYsIGJ5ID0gMC4wMSkNCmJiIDwtIHNwbGluZURlc2lnbihrbm90cywgeCA9IHgsIG91dGVyLm9rID0gVFJVRSwNCiAgICAgICAgICAgICAgICAgICBvcmQgPSAyLCBkZXJpdnMgPTApDQpvcCA8LSBwYXIobWZyb3cgPSBjKDEsIDIpKQ0KcGxvdChCMiR4LCBCMiRCMjAsIGNvbCA9ICJibGFjayIsY2V4ID0gMS41LGx0eSA9IDEsIHR5cGUgPSAibCIsDQogICAgIG1haW4gPSAiQnkgSHVvbmcgIiwNCiAgICAgeWxpbSA9IGMoIDAsIDEuMiApLA0KICAgICB4bGFiID0gIngiLCB5bGFiID0gIlZhbHVlIikNCmxpbmVzKEIyJHgsIEIyJEIyMSwgY29sID0gInJlZCIsY2V4ID0gMS41KQ0KYWJsaW5lKHYgPSBrbm90cywgbHR5ID0gMywgY29sID0gImxpZ2h0IGdyYXkiKQ0KDQoNCnBsb3QocmFuZ2UoeCksIGMoMCwxKSwgdHlwZSA9ICJuIiwgeGxhYiA9ICJ4IiwgeWxhYiA9ICIiLA0KICAgICBtYWluID0gICJCeSBzcGxpbmVEZXNpZ24oKSBmdW5jdGlvbiIsDQogICAgIHlsaW0gPSBjKCAwLCAxLjIgKSkNCiNtdGV4dChleHByZXNzaW9uKEJbal0oeCkgKiIgIGFuZCAiKiBzdW0oQltqXSh4KSwgaiA9PSAxLCA2KSksIGFkaiA9IDApDQphYmxpbmUodiA9IGtub3RzLCBsdHkgPSAzLCBjb2wgPSAibGlnaHQgZ3JheSIpDQphYmxpbmUodiA9IGtub3RzW2MoNCxsZW5ndGgoa25vdHMpLTMpXSwgbHR5ID0gMywgY29sID0gImdyYXkxMCIpDQojbGluZXMoeCwgcm93U3VtcyhiYiksIGNvbCA9ICJncmF5IiwgbHdkID0gMikNCm1hdGxpbmVzKHgsIGJiLCB5bGltID0gYygwLDEpLCBsdHkgPSAxKQ0KDQpwYXIob3ApDQpgYGANCg0KDQoNCiMjIyA1LjEuIEItU3BsaW5lIG9mIGRlZ3JlZSAyIChvcmRlcj0gMykgDQpgYGB7cn0NCiM9PT09PT09PT09PT09PUNvbXBhcmUgSHVvbmcncyBjb2RlIGFuZCBwYWNrYWdlLCBvcmQgPSAzDQoNCmtub3RzIDwtYyhsYW1iZGEwLCBsYW1iZGExLCBsYW1iZGEyLCBsYW1iZGEzLCBsYW1iZGE0KSANCnggPSBzZXEoMCwgNiwgYnkgPSAwLjAxKQ0KYmIgPC0gc3BsaW5lRGVzaWduKGtub3RzLCB4ID0geCwgb3V0ZXIub2sgPSBUUlVFLA0KICAgICAgICAgICAgICAgICAgIG9yZCA9IDMsIGRlcml2cyA9MCkNCm9wIDwtIHBhcihtZnJvdyA9IGMoMSwgMikpDQpwbG90KEIzJHgsIEIzJEIzMCwgY29sID0gImJsYWNrIixjZXggPSAxLjUsIGx0eSA9IDEsIHR5cGUgPSAibCIsDQogICAgIG1haW4gPSAiQnkgSHVvbmcgIiwNCiAgICAgeWxpbSA9IGMoIDAsIDEuMiApLA0KICAgICB4bGFiID0gIngiLCB5bGFiID0gIlZhbHVlIikNCmFibGluZSh2ID0ga25vdHMsIGx0eSA9IDMsIGNvbCA9ICJsaWdodCBncmF5IikNCg0KDQpwbG90KHJhbmdlKHgpLCBjKDAsMSksIHR5cGUgPSAibiIsIHhsYWIgPSAieCIsIHlsYWIgPSAiIiwNCiAgICAgbWFpbiA9ICAiQnkgc3BsaW5lRGVzaWduKCkgZnVuY3Rpb24iLA0KICAgICB5bGltID0gYyggMCwgMS4yICkpDQojbXRleHQoZXhwcmVzc2lvbihCW2pdKHgpICoiICBhbmQgIiogc3VtKEJbal0oeCksIGogPT0gMSwgNikpLCBhZGogPSAwKQ0KYWJsaW5lKHYgPSBrbm90cywgbHR5ID0gMywgY29sID0gImxpZ2h0IGdyYXkiKQ0KYWJsaW5lKHYgPSBrbm90c1tjKDQsbGVuZ3RoKGtub3RzKS0zKV0sIGx0eSA9IDMsIGNvbCA9ICJncmF5MTAiKQ0KI2xpbmVzKHgsIHJvd1N1bXMoYmIpLCBjb2wgPSAiZ3JheSIsIGx3ZCA9IDIpDQptYXRsaW5lcyh4LCBiYiwgeWxpbSA9IGMoMCwxKSwgbHR5ID0gMSkNCnBhcihvcCkNCg0KYGBgDQoNClRIQU5LIFlPVSENCg==