図の右上のshowボタンを押すとRのコードが表示されます。

4.1 三つ以上の変数の可視化

4.1.1 レーダーチャートの印象

Rでレーダーチャートを描くパッケージはいくつかあるのだが、どれもイマイチ納得いかなかったのでポーラーチャートで勘弁して下さい。

中澤先生から教えていただいたfmsbパッケージを使う方法でキレイに描けました!

fmsbパッケージの日本語マニュアルはこちら

library(conflicted)
library(tidyverse)
library(fmsb)

data1 <- data.frame(
  国語 = c(5, 1, 5, 3),
  理科 = c(5, 1, 5, 4),
  社会 = c(5, 1, 4, 3),
  体育 = c(5, 1, 4, 4),
  音楽 = c(5, 1, 5, 2),
  数学 = c(5, 1, 5, 2)
  )

data2 <- data.frame(
  数学 = c(5, 1, 5, 5),
  国語 = c(5, 1, 5, 1),
  理科 = c(5, 1, 1, 5),
  社会 = c(5, 1, 1, 1),
  体育 = c(5, 1, 1, 5),
  音楽 = c(5, 1, 5, 1)
  )

my_radarchart <- function(data){
  radarchartcirc(
    data, plty = 1, axistype = 4, seg = 4, pty = 32, pcol = c(4, 2),
    cglwd = 0.5, cglcol = "gray20", caxislabels=sprintf("%d", 1:5),
    pfcol = c(adjustcolor("lightblue", 0.5), adjustcolor("pink", 0.5))
    ) 
  text(-0.4, -0.3, "Aさん", col="red")
  text(-0.4, 0.6, "Bさん", col="blue")
  }

par(mfrow = c(2, 2), oma = c(0,0,0,0), mar = c(0,0,0,0))
# plot1
data1 |>
  select(数学, 国語, 理科, 社会, 体育, 音楽) |>
  my_radarchart()

# plot2
data1 |>
  select(音楽, 数学, 理科, 体育, 社会, 国語) |>
  my_radarchart()
  
# plot3
data2 |>
  select(数学, 国語, 理科, 社会, 体育, 音楽) |>
  my_radarchart()

# plot4
data2 |>
  select(音楽, 数学, 理科, 体育, 社会, 国語) |>
  my_radarchart()

par(mfrow = c(1, 1))

4.1.2 各変数の特徴を概観して比較する

library(conflicted)
library(tidyverse)
library(patchwork)

v_names <- c(
    "ブドウの品種", "アルコール度数", "リンゴ酸", "ミネラル分", 
    "ミネラル分のアルカリ度", "マグネシウム", "全フェノール類", "フラバノイド",
    "非フラバノイドフェノール類", "プロアントシアニン", "色の強さ", "色相",
    "OD280/OD315値", "プロリン"
  )

df <- read_csv(
  "https://archive.ics.uci.edu/ml/machine-learning-databases/wine/wine.data",
  col_names = v_names,
  col_types = "f"
  ) |>
  mutate(across(where(is.numeric), \(x) scale(x))) |> #標準化
  rowid_to_column() |>
  pivot_longer(!c(ブドウの品種, rowid)) |>
  mutate(name = factor(name, levels = v_names))

df |>
  ggplot(aes(x = name, y = value, colour = ブドウの品種, group =rowid)) +
  geom_line(alpha = 0.5) +
  coord_flip() +
  labs(title = "ワインの特徴とブドウの品種", y = "相対スコア", x = "")

4.1.3 変数ごとに個別に可視化する方法

library(conflicted)
library(tidyverse)

v_names <- c(
    "ブドウの品種", "アルコール度数", "リンゴ酸", "ミネラル分", 
    "ミネラル分のアルカリ度", "マグネシウム", "全フェノール類", "フラバノイド",
    "非フラバノイドフェノール類", "プロアントシアニン", "色の強さ", "色相",
    "OD280/OD315値", "プロリン"
  )

df <- read_csv(
  "https://archive.ics.uci.edu/ml/machine-learning-databases/wine/wine.data",
  col_names = v_names,
  col_types = "f"
) |>
  mutate(across(where(is.numeric), \(x) scale(x))) |> #標準化
  rowid_to_column() |>
  pivot_longer(!c(ブドウの品種, rowid)) |>
  mutate(name = factor(name, levels = v_names))

p1 <- df |>
  ggplot(aes(x = name, y = value, group = ブドウの品種, fill = ブドウの品種)) +
  stat_summary(geom = "bar", fun = "mean", position = "dodge2") +
  stat_summary(geom = "errorbar", fun.data = "mean_se", position = "dodge2") +
  labs(y = "相対スコア", x = "") +
  theme(axis.text.x = element_text(angle = 45, hjust = 1),
        aspect.ratio = 1/2,
        legend.position = "none")

p2 <- df |>
  ggplot(aes(x = name, y = value, fill = ブドウの品種)) +
  geom_violin(position="dodge") +
  labs(y = "相対スコア", x = "") +
  theme(axis.text.x = element_text(angle = 45, hjust = 1),
        aspect.ratio = 1/2,
        legend.position = "none")

p1 / p2

4.1.4 ヒートマップ

library(conflicted)
library(tidyverse)
library(viridis)

v_names <- c(
    "ブドウの品種", "アルコール度数", "リンゴ酸", "ミネラル分", 
    "ミネラル分のアルカリ度", "マグネシウム", "全フェノール類", "フラバノイド",
    "非フラバノイドフェノール類", "プロアントシアニン", "色の強さ", "色相",
    "OD280/OD315値", "プロリン"
  )

df <- read_csv(
  "https://archive.ics.uci.edu/ml/machine-learning-databases/wine/wine.data",
  col_names = v_names,
  col_types = "f"
  ) |>
  mutate(across(where(is.numeric), \(x) scale(x))) |> #標準化
  rowid_to_column() |>
  pivot_longer(!c(rowid, ブドウの品種)) |>
  mutate(name = factor(name, levels = rev(v_names)))

df |>
  ggplot(aes(x = rowid, y = name, fill = value)) +
  geom_tile() +
  facet_wrap(vars(ブドウの品種), scales = "free_x") + 
  scale_fill_viridis(option = "turbo") +
  theme(axis.title.y = element_blank()) +
  labs(x = "ワイン銘柄番号", title = "カラーコードで値を表現する")

4.1.5 各個体の行動時系列を可視化する

library(conflicted)
library(tidyverse)

df <- read_csv(
  "https://raw.githubusercontent.com/tkEzaki/data_visualization/main/4%E7%AB%A0/data/behavior_data.csv"
  ) |>
  mutate(Time = Time / 3600)  |> # Time列を秒から時間に変換
  pivot_longer(!Time) |>
  mutate(
    value = case_when(
      value == "Garbage" ~ "ゴミ捨て場",
      value == "Nest" ~ "寝室",
      value == "Other" ~ "一般の部屋",
      value == "Toilet" ~ "トイレ"
      ) |>
      factor(levels = c("ゴミ捨て場", "トイレ", "寝室", "一般の部屋"))
    ) 

df |>
  ggplot(aes(x =name , y = Time, fill = value)) +
  geom_tile() +
  scale_y_reverse() +
  scale_fill_manual(values = c("#E69F00", "#56B4E9", "#009E73", "#F0E442")) +
  labs(title = "ヒートマップによる行動時系列の可視化", x = "個体", y = "時間[h]")

4.1.6 1個体の各行動の可視化

library(conflicted)
library(tidyverse)
library(patchwork)

# CSVデータを読み込む
df <- read_csv(
  "https://raw.githubusercontent.com/tkEzaki/data_visualization/main/4%E7%AB%A0/data/behavior_data.csv"
) |>
  mutate(Time = Time / 3600)  |> # Time列を秒から時間に変換
  pivot_longer(!Time) |>
  mutate(
    value = case_when(
      value == "Garbage" ~ "ゴミ捨て場",
      value == "Nest" ~ "寝室",
      value == "Other" ~ "一般の部屋",
      value == "Toilet" ~ "トイレ"
    ) |>
      factor(levels = rev(c("ゴミ捨て場", "トイレ", "寝室", "一般の部屋")))
  )

# 1個体(J)のデータ
df_j <- df |>
  dplyr::filter(name == "J") |> 
  dplyr::select(!name) |>
  mutate(action = "1")

p1 <- df_j |>
  ggplot(aes(x = value, y = Time, fill = value)) +
  geom_tile() +
  scale_y_reverse() +
  labs(x = "", y = "時間[h]") +
  scale_fill_brewer(palette = "Set1") +
  theme(
    legend.position="none",
    aspect.ratio = 4 / 1.5
    )

p2 <- df_j |>
  dplyr::filter(Time >= 13, Time <= 16) |>
  ggplot(aes(x = value, y = Time, fill = value)) +
  geom_tile() +
  scale_y_reverse() +
  labs(x = "", y = "時間[h]") +
  scale_fill_manual(values = c("#E69F00", "#56B4E9", "#009E73", "#F0E442")) +
  theme(
    legend.position="none",
    aspect.ratio = 4 / 1.5
  )

p1 + p2

4.2.1 関係データのヒートマップによる可視化

library(conflicted)
library(tidyverse) 
library(patchwork)
library(viridis)

# 共著データ
researchers <- c("A", "B", "C", "D", "E", "F", "G", "H", "I", "J")
collaboration <- matrix(
  c(
    NA, 1, 1, 0, 0, 0, 0, 0, 0, 1,
    1, NA, 0, 1, 1, 1, 0, 0, 0, 1,
    1, 0, NA, 0, 0, 0, 1, 1, 1, 0,
    0, 1, 0, NA, 0, 0, 0, 0, 0, 0,
    0, 1, 0, 0, NA, 0, 0, 0, 0, 0,
    0, 1, 0, 0, 0, NA, 0, 0, 0, 0,
    0, 0, 1, 0, 0, 0, NA, 0, 0, 0,
    0, 0, 1, 0, 0, 0, 0, NA, 0, 0,
    0, 0, 1, 0, 0, 0, 0, 0, NA, 0,
    1, 1, 0, 0, 0, 0, 0, 0, 0, NA
    ),
  nrow=10,
  dimnames = list(researchers, researchers)
  )

p1 <- collaboration |>
  as.data.frame() |>
  rownames_to_column() |>
  pivot_longer(!rowname) |>
  mutate(
    value = factor(value),
    name = factor(name, levels = rev(LETTERS[1:10]))
    ) |>
  ggplot(aes(x = rowname, y = name, fill = value, label = value)) +
  geom_tile() +
  geom_text() +
  scale_fill_hue(name = "", labels = c("0" = "共著なし", "1" ="共著あり")) +
  labs(title = "共著関係の有無", x = "", y = "") +
  theme(aspect.ratio = 1)

# ここはもうちょっとスマートに書けないか?
set.seed(0)
v0 <- runif(n = (100 -10)/2, min = 0.0, max = 0.5) |> round(2)
v1 <- runif(n = (100 -10)/2, min = 0.5, max = 1.0) |> round(2)
collaboration_score <- collaboration
collaboration_score[upper.tri(collaboration_score)] <-
  if_else(collaboration[lower.tri(collaboration)]== 1, v1, v0) 
collaboration_score <- t(collaboration_score)
collaboration_score[upper.tri(collaboration_score)] <-
  if_else(collaboration[lower.tri(collaboration)]== 1, v1, v0)

p2 <- collaboration_score |>
  as.data.frame() |>
  rownames_to_column() |>
  pivot_longer(!rowname) |>
  mutate(name = factor(name, levels = rev(LETTERS[1:10]))) |>
  ggplot(aes(x = rowname, y = name, fill = value, label = value)) +
  geom_tile() +
  geom_text(size = 2) +
  labs(title = "研究の興味の類似度", x = "", y = "") +
  theme(aspect.ratio = 1) +
  scale_fill_viridis(option = "turbo")

p1 + p2

4.2.2 ネットワークによる関係データの可視化

igraphで書いていたものをggraphで書き直しました。

library(conflicted)
library(tidyverse)
library(igraph)
library(tidygraph)
library(ggraph)
library(patchwork)

# 共著データ
researchers <- c("A", "B", "C", "D", "E", "F", "G", "H", "I", "J")
collaboration <- matrix(
  c(
    NA, 1, 1, 0, 0, 0, 0, 0, 0, 1,
    1, NA, 0, 1, 1, 1, 0, 0, 0, 1,
    1, 0, NA, 0, 0, 0, 1, 1, 1, 0,
    0, 1, 0, NA, 0, 0, 0, 0, 0, 0,
    0, 1, 0, 0, NA, 0, 0, 0, 0, 0,
    0, 1, 0, 0, 0, NA, 0, 0, 0, 0,
    0, 0, 1, 0, 0, 0, NA, 0, 0, 0,
    0, 0, 1, 0, 0, 0, 0, NA, 0, 0,
    0, 0, 1, 0, 0, 0, 0, 0, NA, 0,
    1, 1, 0, 0, 0, 0, 0, 0, 0, NA
  ),
  nrow=10,
  dimnames = list(researchers, researchers)
)

# 研究の興味の類似度
set.seed(0)
v0 <- runif(n = (100 -10)/2, min = 0.0, max = 0.5) |> round(2)
v1 <- runif(n = (100 -10)/2, min = 0.5, max = 1.0) |> round(2)
collaboration_score <- collaboration
collaboration_score[upper.tri(collaboration_score)] <-
  if_else(collaboration[lower.tri(collaboration)]== 1, v1, v0) 
collaboration_score <-
  t(collaboration_score)
collaboration_score[upper.tri(collaboration_score)] <-
  if_else(collaboration[lower.tri(collaboration)]== 1, v1, v0)

# 共著ネットワーク
diag(collaboration) <- 0
collabo_g <- collaboration |>
  graph_from_adjacency_matrix(mode = "undirected")

set.seed(0)

# tidygraphへ変換
collabo_g_tidy <- as_tbl_graph(collabo_g, directed = FALSE)

# 共著関係の有無
p1 <- collabo_g_tidy |>
  ggraph(layout = "linear", circular = TRUE) +
  geom_edge_link() +
  geom_node_label(aes(label = name), repel = FALSE) +
  theme_void() +
  theme(aspect.ratio = 1) +
  labs(title = "共著関係の有無、円状レイアウト")

p2 <- collabo_g_tidy |>
  ggraph(layout = "fr") +
  geom_edge_link() +
  geom_node_label(aes(label = name), repel = FALSE) +
  theme_void() +
  theme(aspect.ratio = 1) +
  labs(title = "共著関係の有無、スプリングレイアウト")


# 類似度ネットワーク
diag(collaboration_score) <- 0
score_g <- collaboration_score |>
  graph_from_adjacency_matrix(mode = "undirected", weighted = TRUE)

E(score_g)$color <- round((E(score_g)$weight / max(E(score_g)$weight)) * 100, 0)
E(score_g)$weight
##  [1] 0.76 0.89 0.29 0.45 0.54 0.38 0.63 0.19 0.33 0.19 0.45 0.47 0.55 0.96 0.74
## [16] 0.43 0.40 0.10 0.72 0.66 0.36 0.33 0.17 0.05 0.31 0.34 0.50 0.06 0.24 0.36
## [31] 0.19 0.19 0.13 0.30 0.21 0.83 0.19 0.25 0.41 0.01 0.09 0.32 0.41 0.39 0.28
# tidygraphへ変換
score_g_tidy <- as_tbl_graph(score_g, directed = FALSE)

p3 <- score_g_tidy |>
  ggraph(layout = "linear", circular = TRUE) +
  geom_edge_link(aes(width = color, color = color)) +
  scale_edge_color_viridis(option = "turbo", alpha = 0.5) + 
  geom_node_label(aes(label = name), repel = FALSE) +
  theme_void() +
  theme(aspect.ratio = 1, legend.position = "none") +
  labs(title = "研究の興味の類似度、円状レイアウト")

p4 <- score_g_tidy |>
  ggraph(layout = "fr") +
  geom_edge_link(aes(width = color, color = color)) +
  scale_edge_color_viridis(option = "turbo", alpha = 0.5) + 
  geom_node_label(aes(label = name), repel = FALSE) +
  theme_void() +
  theme(aspect.ratio = 1, legend.position = "none") +
  labs(title = "研究の興味の類似度、スプリングレイアウト")

{p1|p2}/{p3|p4}

4.2.3 指導関係の可視化

library(conflicted)
library(tidyverse)
library(igraph)
library(tidygraph)
library(ggraph)
library(patchwork)

# 研究者リスト
researchers <- c("A", "B", "C", "D", "E", "F", "G", "H", "I", "J")

# 共著データ
collaboration <- matrix(
  c(
    NA, 1, 1, 0, 0, 0, 0, 0, 0, 1,
    0, NA, 0, 1, 1, 1, 0, 0, 0, 0,
    0, 0, NA, 0, 0, 0, 1, 1, 1, 0,
    0, 0, 0, NA, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, NA, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, NA, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, NA, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, NA, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, NA, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, NA
    ),
  nrow = 10,
  dimnames = list(researchers, researchers)
  )

# 共著ネットワーク
p1 <- collaboration |>
  as.data.frame() |>
  rownames_to_column() |>
  pivot_longer(!rowname) |>
  mutate(
    value = factor(value),
    name = factor(name, levels = rev(LETTERS[1:10]))
    ) |>
  ggplot(aes(x = rowname, y = name, fill = value, label = value)) +
  geom_tile() +
  geom_text() +
  scale_fill_hue(name = "", labels = c("0" = "指導関係なし", "1" ="指導関係あり")) +
  labs(title = "隣接行列表示", x = "指導された研究者", y = "指導した研究者") +
  theme(aspect.ratio = 1)

diag(collaboration) <- 0

# tidygraphへ変換
collaboration_tidy <- as_tbl_graph(t(collaboration), directed = TRUE)

p2 <- collaboration_tidy |>
  ggraph(layout = "tree") +
  geom_edge_link(
    arrow = arrow(type = "closed", length = unit(4, 'mm')),
    start_cap = circle(4, 'mm'),
    end_cap = circle(4, 'mm')
    ) +
  geom_node_label(aes(label = name), repel = FALSE) +
  theme_void() +
  theme(aspect.ratio = 1, legend.position = "none") +
  labs(title = "階層レイアウトによるネットワーク表示")

p1 + p2

4.2.4 様々なレイアウトによるネットワーク描画

circoレイアウトがigraphには無いようなのでFruchterman and Reingoldレイアウトで代用。

library(conflicted)
library(tidyverse)
library(igraph)
library(tidygraph)
library(ggraph)
library(patchwork)

set.seed(0)
G_er <- erdos.renyi.game(30, 0.2)
G_ws <- watts.strogatz.game(1, 30, 5, 0.1)
G_ba <- barabasi.game(30, 1)

ErdosR <- as_adjacency_matrix(G_er, sparse = FALSE)
WattsStrogatz <- as_adjacency_matrix(G_ws, sparse = FALSE)
BarabasiAlbert <- as_adjacency_matrix(G_ba, sparse = FALSE)

net1 <- graph_from_adjacency_matrix(ErdosR, mode = "max")
net2 <- graph_from_adjacency_matrix(WattsStrogatz, mode = "max")
net3 <- graph_from_adjacency_matrix(BarabasiAlbert, mode = "max")

# tidygraphへ変換
net1_tidy <- as_tbl_graph(net1, directed = FALSE)
net2_tidy <- as_tbl_graph(net2, directed = FALSE)
net3_tidy <- as_tbl_graph(net3, directed = FALSE)

p1 <- net1_tidy |>
  ggraph(layout = "linear", circular = TRUE) +
  geom_edge_link(color = "#666666", alpha = 0.5) +
  geom_node_point(size = 2, color = "#F8766D") +
  theme_void() +
  theme(aspect.ratio = 1) +
  labs(title = "Erdos-Reyni", subtitle = "Circular Layout")

p2 <- net2_tidy |>
  ggraph(layout = "linear", circular = TRUE) +
  geom_edge_link(color = "#666666", alpha = 0.5) +
  geom_node_point(size = 2, color = "#F8766D") +
  theme_void() +
  theme(aspect.ratio = 1) +
  labs(title = "Watts-Strogatz", subtitle = "Circular Layout")

p3 <- net3_tidy |>
  ggraph(layout = "linear", circular = TRUE) +
  geom_edge_link(color = "#666666", alpha = 0.5) +
  geom_node_point(size = 2, color = "#F8766D") +
  theme_void() +
  theme(aspect.ratio = 1) +
  labs(title = "Barabasi-Albert", subtitle = "Circular Layout")

p4 <- net1_tidy |>
  ggraph(layout = "igraph", algorithm ="fr") +
  geom_edge_link(color = "#666666", alpha = 0.5) +
  geom_node_point(size = 2, color = "#B79F00") +
  theme_void() +
  theme(aspect.ratio = 1) +
  labs(title = "Erdos-Reyni", subtitle = "FR Layout")

p5 <- net2_tidy |>
  ggraph(layout = "igraph", algorithm ="fr") +
  geom_edge_link(color = "#666666", alpha = 0.5) +
  geom_node_point(size = 2, color = "#B79F00") +
  theme_void() +
  theme(aspect.ratio = 1) +
  labs(title = "Watts-Strogatz", subtitle = "FR Layout")

p6 <- net3_tidy |>
  ggraph(layout = "igraph", algorithm ="fr") +
  geom_edge_link(color = "#666666", alpha = 0.5) +
  geom_node_point(size = 2, color = "#B79F00") +
  theme_void() +
  theme(aspect.ratio = 1) +
  labs(title = "Barabasi-Albert", subtitle = "FR Layout")

p7 <- net1_tidy |>
  ggraph(layout = "igraph", algorithm ="kk") +
  geom_edge_link(color = "#666666", alpha = 0.5) +
  geom_node_point(size = 2, color = "#00BA38") +
  theme_void() +
  theme(aspect.ratio = 1) +
  labs(title = "Erdos-Reyni", subtitle = "KK Layout")

p8 <- net2_tidy |>
  ggraph(layout = "igraph", algorithm ="kk") +
  geom_edge_link(color = "#666666", alpha = 0.5) +
  geom_node_point(size = 2, color = "#00BA38") +
  theme_void() +
  theme(aspect.ratio = 1) +
  labs(title = "Watts-Strogatz", subtitle = "KK Layout")

p9 <- net3_tidy |>
  ggraph(layout = "igraph", algorithm ="kk") +
  geom_edge_link(color = "#666666", alpha = 0.5) +
  geom_node_point(size = 2, color = "#00BA38") +
  theme_void() +
  theme(aspect.ratio = 1) +
  labs(title = "Barabasi-Albert", subtitle = "KK Layout")


{p1|p2|p3}/{p4|p5|p6}/{p7|p8|p9}

4.2.5 有向ネットワークの可視化

library(conflicted)
library(tidyverse)
library(igraph)
library(tidygraph)
library(ggraph)

# ランダム有向グラフを生成
set.seed(0)
G_dir_random <- random.graph.game(n=30, p=0.1, directed = TRUE) 

# 階層構造を持つ有向グラフを生成
set.seed(0)
G_dir_hierarchy <- sample_tree(30, directed = TRUE)

# tidygraphへ変換
G_dir_random_tidy <- as_tbl_graph(G_dir_random, directed = TRUE)
G_dir_hierarchy_tidy <- as_tbl_graph(G_dir_hierarchy, directed = TRUE)

p1 <- G_dir_random_tidy |>
  ggraph(layout = "igraph", algorithm = "tree") +
  geom_edge_link(color = "#666666", alpha = 0.5, arrow = arrow(type = "closed", length = unit(4, 'mm'))) +
  geom_node_point(size = 2, color = "#00BFC4") +
  theme_void() +
  theme(aspect.ratio = 1) +
  labs(title = "Directed Random Graph", subtitle = "Tree Layout")

p2 <- G_dir_hierarchy_tidy |>
  ggraph(layout = "igraph", algorithm = "tree") +
  geom_edge_link(color = "#666666", alpha = 0.5, arrow = arrow(type = "closed", length = unit(4, 'mm'))) +
  geom_node_point(size = 2, color = "#00BFC4") +
  theme_void() +
  theme(aspect.ratio = 1) +
  labs(title = "Directed Graph with Wide Hierarchy", subtitle = "Tree Layout")

p3 <- G_dir_random_tidy |>
  ggraph(layout = "igraph", algorithm = "fr") +
  geom_edge_link(color = "#666666", alpha = 0.5, arrow = arrow(type = "closed", length = unit(4, 'mm'))) +
  geom_node_point(size = 2, color = "#619CFF") +
  theme_void() +
  theme(aspect.ratio = 1) +
  labs(title = "Directed Random Graph", subtitle = "FR Layout")

p4 <- G_dir_hierarchy_tidy |>
  ggraph(layout = "igraph", algorithm = "fr") +
  geom_edge_link(color = "#666666", alpha = 0.5, arrow = arrow(type = "closed", length = unit(4, 'mm'))) +
  geom_node_point(size = 2, color = "#619CFF") +
  theme_void() +
  theme(aspect.ratio = 1) +
  labs(title = "Directed Graph with Wide Hierarchy", subtitle = "FR Layout")

p5 <- G_dir_random_tidy |>
  ggraph(layout = "igraph", algorithm = "kk") +
  geom_edge_link(color = "#666666", alpha = 0.5, arrow = arrow(type = "closed", length = unit(4, 'mm'))) +
  geom_node_point(size = 2, color = "#F564E3") +
  theme_void() +
  theme(aspect.ratio = 1) +
  labs(title = "Directed Random Graph", subtitle = "KK Layout")

p6 <- G_dir_hierarchy_tidy |>
  ggraph(layout = "igraph", algorithm = "kk") +
  geom_edge_link(color = "#666666", alpha = 0.5, arrow = arrow(type = "closed", length = unit(4, 'mm'))) +
  geom_node_point(size = 2, color = "#F564E3") +
  theme_void() +
  theme(aspect.ratio = 1) +
  labs(title = "Directed Graph with Wide Hierarchy", subtitle = "KK Layout")

{p1|p2}/{p3|p4}/{p5|p6}

4.3.1 クラスターマップによるデータの可視化

library(conflicted)
library(tidyverse)
library(pheatmap)
library(viridisLite)

df <- read_csv(
  "https://archive.ics.uci.edu/ml/machine-learning-databases/wine/wine.data",
  col_names = c(
    "ブドウの品種", "アルコール度数", "リンゴ酸", "ミネラル分", 
    "ミネラル分のアルカリ度", "マグネシウム", "全フェノール類", "フラバノイド",
    "非フラバノイドフェノール類", "プロアントシアニン", "色の強さ", "色相",
    "OD280/OD315値", "プロリン"
  ),
  col_types = "f"
) |>
  mutate(across(where(is.numeric), \(x) scale(x))) #標準化

pheatmap(
  df |>
    dplyr::select(!ブドウの品種) |>
    t(),
  color = turbo(50),
  clustering_method = "ward.D2"
  )

4.3.3 様々なクラスタリング手法

BIRCHだけ見つからないので省略。

ちなみにRではmlbenchパッケージにベンチマーク用の様々なデータセットと人工データ生成用の関数が用意されています。

library(conflicted)
library(tidyverse)
library(ClusterR)   # MiniBatch KMeans
library(apcluster)  # Affinity Propagation
library(meanShiftR) # MeanShift
library(skmeans)    # Spectral Clustering
library(cluster)    # Agglomerative Clustering
library(dbscan)     # DBSCAN, HDBSCAN, OPTICS,
library(mclust)     # Gaussian Mixture
library(patchwork)

# データ用意
my_read_csv <- function(file){
  read_csv(file, col_names = c("x", "y", "class")) |>
    mutate( #標準化しておく
      x = (x - mean(x))/sd(x),
      y = (y - mean(x))/sd(x),  
      class = factor(class + 1))
}

# 円形のクラスタ
noisy_circles <- my_read_csv(
  "https://raw.githubusercontent.com/morimotoosamu/data_visualization/main/data/noisy_circles.csv"
  )

# 月型のクラスタ
noisy_moons <- my_read_csv(
  "https://raw.githubusercontent.com/morimotoosamu/data_visualization/main/data/noisy_moons.csv"
  )     

# 正規分布に従うクラスタ
blobs <- my_read_csv(
  "https://raw.githubusercontent.com/morimotoosamu/data_visualization/main/data/blobs.csv"
  )    

# 異方性のあるデータ
aniso <- my_read_csv(
  "https://raw.githubusercontent.com/morimotoosamu/data_visualization/main/data/aniso.csv"
  )       

# 3つの正規分布に従うデータ
varied <- my_read_csv(
  "https://raw.githubusercontent.com/morimotoosamu/data_visualization/main/data/varied.csv"
  )               

n <- 500
set.seed(0)
no_structure <- data.frame(x = runif(n), y = runif(n), class = factor("0"))# 構造のないデータ

# 各手法のラッパー関数を用意
# MiniBatch KMeans
my_MiniBatchKmeans <- function(data, cluster_num) {
  data_cleaned <- data[, 1:2]
  start <- Sys.time()
  fit <- MiniBatchKmeans(data_cleaned, clusters = cluster_num)
  pred <- predict(fit, data_cleaned)
  end <- Sys.time()
  diff <- end - start
  data$pred <- as.factor(pred)
  return(list(res = data, diff = diff))
}

# Affinity Propagation
my_apcluster <- function(data, cluster_num) {
  data_cleaned <- data[, 1:2]
  start <- Sys.time()
  pred <- apcluster(s = negDistMat(r=2), x = data_cleaned, p = -200, q = 0.9) |>
    cutree(cluster_num) |>
    labels(type = "enum")
  end <- Sys.time()
  diff <- end - start
  data$pred <- as.factor(pred)
  return(list(res = data, diff = diff))
}

# MeanShift
my_meanShift <- function(data) {
  data_cleaned <- as.matrix(data[, 1:2])
  start <- Sys.time()
  pred <- meanShift(data_cleaned, data_cleaned)$assignment
  end <- Sys.time()
  diff <- end - start
  data$pred <- as.factor(pred)
  return(list(res = data, diff = diff))
}

# Spectral Clustering
my_skmeans <- function(data, cluster_num) {
  data_cleaned <- base::as.matrix(data[, 1:2])
  start <- Sys.time()
  pred <- skmeans(data_cleaned, k = cluster_num)$cluster
  end <- Sys.time()
  diff <- end - start
  data$pred <- as.factor(pred)
  return(list(res = data, diff = diff))
}

# Ward
my_hclust <- function(data, cluster_num) {
  data_cleaned <- data[, 1:2]
  start <- Sys.time()
  pred <- data_cleaned |>
    stats::dist() |>
    hclust(method = "ward.D2") |>
    cutree(k = cluster_num)
  end <- Sys.time()
  diff <- end - start
  data$pred <- as.factor(pred)
  return(list(res = data, diff = diff))
}

# Agglomerative Clustering
my_agnes <- function(data, cluster_num) {
  data_cleaned <- data[, 1:2]
  start <- Sys.time()
  pred <- agnes(data_cleaned) |> cutree(k = cluster_num)
  end <- Sys.time()
  diff <- end - start
  data$pred <- as.factor(pred)
  return(list(res = data, diff = diff))
}

# DBSCAN
my_dbscan <- function(data) {
  data_cleaned <- data[, 1:2]
  start <- Sys.time()
  pred <- dbscan::dbscan(data_cleaned, eps = 0.3)$cluster
  end <- Sys.time()
  diff <- end - start
  data$pred <- as.factor(pred)
  return(list(res = data, diff = diff))
}

# HDBSCAN
my_hdbscan <- function(data) {
  data_cleaned <- data[, 1:2]
  start <- Sys.time()
  pred <- dbscan::hdbscan(data_cleaned, minPts = 15)$cluster
  end <- Sys.time()
  diff <- end - start
  data$pred <- as.factor(pred)
  return(list(res = data, diff = diff))
}

# OPTICS
my_optics <- function(data) {
  data_cleaned <- data[, 1:2]
  start <- Sys.time()
  pred <- dbscan::optics(data_cleaned, eps = 0.1, minPts = 7) |>
    extractXi(xi = 0.05) |>
    pluck("cluster")
  end <- Sys.time()
  diff <- end - start
  data$pred <- as.factor(pred)
  return(list(res = data, diff = diff))
}

# Gaussian Mixture
my_Mclust <- function(data) {
  data_cleaned <- data[, 1:2]
  start <- Sys.time()
  pred <- mclust::Mclust(data_cleaned, G = 1:3)$classification
  end <- Sys.time()
  diff <- end - start
  data$pred <- as.factor(pred)
  return(list(res = data, diff = diff))
}

### クラスタリング実行
dats <- list(noisy_circles, noisy_moons, varied, aniso, blobs, no_structure)
cnums <- list(2,2,3,3,3,3)

set.seed(0)
res_clustering <- list(
  res_MiniBatchKmeans = map2(dats, cnums, \(dats, cnums) my_MiniBatchKmeans(dats, cnums)),
  res_apcluster       = map2(dats, cnums, \(dats, cnums) my_apcluster(dats, cnums)),
  res_meanShift       = purrr::map(dats, \(dats) my_meanShift(dats)),
  res_skmeans         = map2(dats, cnums, \(dats, cnums) my_skmeans(dats, cnums)),
  res_hclust          = map2(dats, cnums, \(dats, cnums) my_hclust(dats, cnums)),
  res_agnes           = map2(dats, cnums, \(dats, cnums) my_agnes(dats, cnums)),
  res_dbscan          = purrr::map(dats, \(dats) my_dbscan(dats)),
  res_hdbscan         = purrr::map(dats, \(dats) my_hdbscan(dats)),
  res_optics          = purrr::map(dats, \(dats) my_optics(dats)),
  res_Mclust          = purrr::map(dats, \(dats) my_Mclust(dats))
) 

# 描画用ラッパー関数を用意
my_plot <- function(result) {
  result$res |>
    ggplot(aes(x = x, y = y, color = pred)) +
    geom_point(size = 1) + 
    labs(caption = paste0(round(result$diff,3),"s")) +
    theme(
      axis.ticks = element_blank(),  # tickの線を消す
      axis.text = element_blank(),   # tickの数字を消す
      axis.title = element_blank(),  # 軸のラベルを消す
      axis.line = element_blank(),   # 軸の線を消す
      legend.position="none",
      aspect.ratio = 1
      )
}

nums <- expand_grid(d = 1:6, m = 1:10)
res_plot <- map2(nums$m, nums$d, \(.x, .y) my_plot(res_clustering[[.x]][[.y]]))

# ここがダサい……もうちょっとどうにかならないか
res_plot[[1]] + res_plot[[2]] +res_plot[[3]] +res_plot[[4]] +res_plot[[5]] +res_plot[[6]] +
  res_plot[[7]] + res_plot[[8]] +res_plot[[9]] +res_plot[[10]] +res_plot[[11]] +res_plot[[12]] +
  res_plot[[13]] + res_plot[[14]] +res_plot[[15]] +res_plot[[16]] +res_plot[[17]] +res_plot[[18]] +
  res_plot[[19]] + res_plot[[20]] +res_plot[[21]] +res_plot[[22]] +res_plot[[23]] +res_plot[[24]] +
  res_plot[[25]] + res_plot[[26]] +res_plot[[27]] +res_plot[[28]] +res_plot[[29]] +res_plot[[30]] +
  res_plot[[31]] + res_plot[[32]] +res_plot[[33]] +res_plot[[34]] +res_plot[[35]] +res_plot[[36]] +
  res_plot[[37]] + res_plot[[38]] +res_plot[[39]] +res_plot[[40]] +res_plot[[41]] +res_plot[[42]] +
  res_plot[[43]] + res_plot[[44]] +res_plot[[45]] +res_plot[[46]] +res_plot[[47]] +res_plot[[48]] +
  res_plot[[49]] + res_plot[[50]] +res_plot[[51]] +res_plot[[52]] +res_plot[[53]] +res_plot[[54]] +
  res_plot[[55]] + res_plot[[56]] +res_plot[[57]] +res_plot[[58]] +res_plot[[59]] +res_plot[[60]] +
  plot_layout(ncol = 10)

4.3.4 多変数をペアプロットで見る

library(conflicted)
library(tidyverse)
library(GGally)

df <- read_csv(
  "https://raw.githubusercontent.com/morimotoosamu/data_visualization/main/data/df_434.csv"
  )

df |>
  ggpairs()

4.3.5 主成分分析のイメージ

library(conflicted)
library(tidyverse)
library(skmeans)
library(GGally)
library(patchwork)

df <- read_csv(
  "https://raw.githubusercontent.com/morimotoosamu/data_visualization/main/data/df_434.csv"
  )

# 主成分分析(標準化)
pca_result <- prcomp(df)#, scale. = TRUE)
pca_importance <- as.data.frame(summary(pca_result)$importance[2,]) |>
  rownames_to_column()
names(pca_importance) <- c("pc", "value")

# Spectral Clustering
clusters_pca <- skmeans(pca_result$x[, 1:2], 3)

p1 <- data.frame(
  pc1 = pca_result$x[, 1],
  pc2 = pca_result$x[, 2],
  class = factor(clusters_pca$cluster)
    ) |>
  ggplot(aes(x = pc1, y = pc2, color = class)) +
  geom_point() +
  labs(title = "二つの主成分で見る")+
  theme(legend.position="none", aspect.ratio = 1)

p2 <- pca_importance |>
  ggplot(aes(x = reorder(pc, desc(value)), y = value)) +
  geom_col()+
  labs(title = "各主成分のデータ悦明力", x = "", y = "")+
  theme(aspect.ratio = 1)

p1 + p2

4.3.6 画像データの次元削減

library(conflicted)
library(tidyverse)
library(MASS) # MDS(sammon)
library(Rtsne) # t-SNE
library(umap) # UMAP

df <- read_csv(
  "https://raw.githubusercontent.com/morimotoosamu/data_visualization/main/data/digits.csv"
  )

df_cleaned <- df |>
  dplyr::select(!target)

# 各手法で2次元に圧縮
# 主成分分析
X_pca <- prcomp(df_cleaned)$x[, 1:2]

# t-SNE
set.seed(42)
X_tsne <- Rtsne(df_cleaned, num_threads = 2)$Y

# MDS
X_mds <- df_cleaned |>
  dist() |>
  sammon(trace = FALSE) |>
  pluck("points")

# UMAP
set.seed(42)
X_umap <- umap(df_cleaned)$layout

# K-means
set.seed(42)
clusters_pca <- kmeans(X_pca, 10)$cluster  # PCA
set.seed(42)
clusters_mds <- kmeans(X_mds, 10)$cluster #MDS
set.seed(42)
clusters_tsne <- kmeans(X_tsne, 10)$cluster # t-SNE
set.seed(42)
clusters_umap <- kmeans(X_umap, 10)$cluster  # UMAP

# 結果をデータフレームにまとめる
df_base <- data.frame(
    x = c(X_pca[, 1], X_mds[, 1], X_tsne[, 1], X_umap[, 1]),
    y = c(X_pca[, 2], X_mds[, 2], X_tsne[, 2], X_umap[, 2])
    )

n <- 1797

dimred <- bind_rows(
  df_base |>
    mutate(
      label = rep(df$target, 4),
      method = c(rep("pca_label", n), rep("mds_label", n),rep("tsne_label", n),rep("umap_label", n))
      ),
  df_base |>
    mutate(
      label = c(clusters_pca, clusters_mds, clusters_tsne, clusters_umap),
      method = c(rep("pca_kmeans", n), rep("mds_kmeans", n),rep("tsne_kmeans", n),rep("umap_kmeans", n))
      )
) |>
  mutate(
    method = factor(
      method,
      levels = c("pca_kmeans", "pca_label", "mds_kmeans", "mds_label",
                 "tsne_kmeans",  "tsne_label", "umap_kmeans", "umap_label")),
    label = factor(label)
    )

# クラスタリング結果と正解ラベルの描画
dimred |>
  ggplot(aes(x = x, y = y, color = label)) + 
  geom_point() + 
  labs(title="様々な次元圧縮方法") +
  theme(
    axis.ticks = element_blank(),  # tickの線を消す
    axis.text = element_blank(),   # tickの数字を消す
    axis.title = element_blank(),  # 軸のラベルを消す
    axis.line = element_blank(),   # 軸の線を消す
    legend.position="none",
    aspect.ratio = 1
  ) +
  facet_wrap(vars(method), nrow = 2, scales = "free")

第4章はここまで。

LS0tCnRpdGxlOiAi56ysNOeroCDlpJrlpInmlbDjgpLjgajjgonjgYjjgovjg4fjg7zjgr/lj6/oppbljJYiCmF1dGhvcjogIk9zYW11LCBNT1JJTU9UTyIKZGF0ZTogImByIFN5cy5EYXRlKClgIgpvdXRwdXQ6CiAgaHRtbF9kb2N1bWVudDogCiAgICBjb2RlX2Rvd25sb2FkOiB0cnVlCiAgICB0b2M6IHllcwogICAgdG9jX2RlcHRoOiAzCiAgICB0aGVtZTogdW5pdGVkICAgIAogICAgbWRfZXh0ZW5zaW9uczogIi1hc2NpaV9pZGVudGlmaWVycyIKICAgIHRvY19mbG9hdDogeWVzCiAgICBmaWdfd2lkdGg6IDcuNQogICAgZmlnX2hlaWdodDogNS42MjUKICAgIGRldjogcmFnZ19wbmcKICAgIGhpZ2hsaWdodDogdGFuZ28KICAgIGNvZGVfZm9sZGluZzogaGlkZQogICAgZGZfcHJpbnQ6IHBhZ2VkCi0tLQoKYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9CmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvID0gVFJVRSkKYGBgCgrlm7Pjga7lj7PkuIrjga5gc2hvd2Djg5zjgr/jg7PjgpLmirzjgZnjgahS44Gu44Kz44O844OJ44GM6KGo56S644GV44KM44G+44GZ44CCCgojIyA0LjEg5LiJ44Gk5Lul5LiK44Gu5aSJ5pWw44Gu5Y+v6KaW5YyWCgojIyMgNC4xLjEg44Os44O844OA44O844OB44Oj44O844OI44Gu5Y2w6LGhCgp+flLjgafjg6zjg7zjg4Djg7zjg4Hjg6Pjg7zjg4jjgpLmj4/jgY/jg5Hjg4PjgrHjg7zjgrjjga/jgYTjgY/jgaTjgYvjgYLjgovjga7jgaDjgYzjgIHjganjgozjgoLjgqTjg57jgqTjg4HntI3lvpfjgYTjgYvjgarjgYvjgaPjgZ/jga7jgafjg53jg7zjg6njg7zjg4Hjg6Pjg7zjg4jjgafli5jlvIHjgZfjgabkuIvjgZXjgYTjgIJ+fgoKW+S4rea+pOWFiOeUn10oaHR0cHM6Ly90d2l0dGVyLmNvbS9NaW5hdG9OYWthemF3YSnjgYvjgonmlZnjgYjjgabjgYTjgZ/jgaDjgYTjgZ9bZm1zYuODkeODg+OCseODvOOCuOOCkuS9v+OBhuaWueazlV0oaHR0cHM6Ly9taW5hdG8uc2lwMjFjLm9yZy9pbTNyLzIwMjQwMTExLmh0bWwp44Gn44Kt44Os44Kk44Gr5o+P44GR44G+44GX44Gf77yBCgpmbXNi44OR44OD44Kx44O844K444Gu5pel5pys6Kqe44Oe44OL44Ol44Ki44Or44GvW+OBk+OBoeOCiV0oaHR0cHM6Ly9taW5hdG8uc2lwMjFjLm9yZy9tc2IvbWFuL2luZGV4Lmh0bWwp44CCCgpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpsaWJyYXJ5KGNvbmZsaWN0ZWQpCmxpYnJhcnkodGlkeXZlcnNlKQpsaWJyYXJ5KGZtc2IpCgpkYXRhMSA8LSBkYXRhLmZyYW1lKAogIOWbveiqniA9IGMoNSwgMSwgNSwgMyksCiAg55CG56eRID0gYyg1LCAxLCA1LCA0KSwKICDnpL7kvJogPSBjKDUsIDEsIDQsIDMpLAogIOS9k+iCsiA9IGMoNSwgMSwgNCwgNCksCiAg6Z+z5qW9ID0gYyg1LCAxLCA1LCAyKSwKICDmlbDlraYgPSBjKDUsIDEsIDUsIDIpCiAgKQoKZGF0YTIgPC0gZGF0YS5mcmFtZSgKICDmlbDlraYgPSBjKDUsIDEsIDUsIDUpLAogIOWbveiqniA9IGMoNSwgMSwgNSwgMSksCiAg55CG56eRID0gYyg1LCAxLCAxLCA1KSwKICDnpL7kvJogPSBjKDUsIDEsIDEsIDEpLAogIOS9k+iCsiA9IGMoNSwgMSwgMSwgNSksCiAg6Z+z5qW9ID0gYyg1LCAxLCA1LCAxKQogICkKCm15X3JhZGFyY2hhcnQgPC0gZnVuY3Rpb24oZGF0YSl7CiAgcmFkYXJjaGFydGNpcmMoCiAgICBkYXRhLCBwbHR5ID0gMSwgYXhpc3R5cGUgPSA0LCBzZWcgPSA0LCBwdHkgPSAzMiwgcGNvbCA9IGMoNCwgMiksCiAgICBjZ2x3ZCA9IDAuNSwgY2dsY29sID0gImdyYXkyMCIsIGNheGlzbGFiZWxzPXNwcmludGYoIiVkIiwgMTo1KSwKICAgIHBmY29sID0gYyhhZGp1c3Rjb2xvcigibGlnaHRibHVlIiwgMC41KSwgYWRqdXN0Y29sb3IoInBpbmsiLCAwLjUpKQogICAgKSAKICB0ZXh0KC0wLjQsIC0wLjMsICJB44GV44KTIiwgY29sPSJyZWQiKQogIHRleHQoLTAuNCwgMC42LCAiQuOBleOCkyIsIGNvbD0iYmx1ZSIpCiAgfQoKcGFyKG1mcm93ID0gYygyLCAyKSwgb21hID0gYygwLDAsMCwwKSwgbWFyID0gYygwLDAsMCwwKSkKIyBwbG90MQpkYXRhMSB8PgogIHNlbGVjdCjmlbDlraYsIOWbveiqniwg55CG56eRLCDnpL7kvJosIOS9k+iCsiwg6Z+z5qW9KSB8PgogIG15X3JhZGFyY2hhcnQoKQoKIyBwbG90MgpkYXRhMSB8PgogIHNlbGVjdCjpn7Pmpb0sIOaVsOWtpiwg55CG56eRLCDkvZPogrIsIOekvuS8miwg5Zu96KqeKSB8PgogIG15X3JhZGFyY2hhcnQoKQogIAojIHBsb3QzCmRhdGEyIHw+CiAgc2VsZWN0KOaVsOWtpiwg5Zu96KqeLCDnkIbnp5EsIOekvuS8miwg5L2T6IKyLCDpn7Pmpb0pIHw+CiAgbXlfcmFkYXJjaGFydCgpCgojIHBsb3Q0CmRhdGEyIHw+CiAgc2VsZWN0KOmfs+alvSwg5pWw5a2mLCDnkIbnp5EsIOS9k+iCsiwg56S+5LyaLCDlm73oqp4pIHw+CiAgbXlfcmFkYXJjaGFydCgpCgpwYXIobWZyb3cgPSBjKDEsIDEpKQpgYGAKCgojIyMgNC4xLjIg5ZCE5aSJ5pWw44Gu54m55b6044KS5qaC6Kaz44GX44Gm5q+U6LyD44GZ44KLCgpgYGB7ciBmaWcuaGVpZ2h0PTcuNSwgZmlnLndpZHRoPTUsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CmxpYnJhcnkoY29uZmxpY3RlZCkKbGlicmFyeSh0aWR5dmVyc2UpCmxpYnJhcnkocGF0Y2h3b3JrKQoKdl9uYW1lcyA8LSBjKAogICAgIuODluODieOCpuOBruWTgeeoriIsICLjgqLjg6vjgrPjg7zjg6vluqbmlbAiLCAi44Oq44Oz44K06YW4IiwgIuODn+ODjeODqeODq+WIhiIsIAogICAgIuODn+ODjeODqeODq+WIhuOBruOCouODq+OCq+ODquW6piIsICLjg57jgrDjg43jgrfjgqbjg6AiLCAi5YWo44OV44Kn44OO44O844Or6aGeIiwgIuODleODqeODkOODjuOCpOODiSIsCiAgICAi6Z2e44OV44Op44OQ44OO44Kk44OJ44OV44Kn44OO44O844Or6aGeIiwgIuODl+ODreOCouODs+ODiOOCt+OCouODi+ODsyIsICLoibLjga7lvLfjgZUiLCAi6Imy55u4IiwKICAgICJPRDI4MC9PRDMxNeWApCIsICLjg5fjg63jg6rjg7MiCiAgKQoKZGYgPC0gcmVhZF9jc3YoCiAgImh0dHBzOi8vYXJjaGl2ZS5pY3MudWNpLmVkdS9tbC9tYWNoaW5lLWxlYXJuaW5nLWRhdGFiYXNlcy93aW5lL3dpbmUuZGF0YSIsCiAgY29sX25hbWVzID0gdl9uYW1lcywKICBjb2xfdHlwZXMgPSAiZiIKICApIHw+CiAgbXV0YXRlKGFjcm9zcyh3aGVyZShpcy5udW1lcmljKSwgXCh4KSBzY2FsZSh4KSkpIHw+ICPmqJnmupbljJYKICByb3dpZF90b19jb2x1bW4oKSB8PgogIHBpdm90X2xvbmdlcighYyjjg5bjg4njgqbjga7lk4HnqK4sIHJvd2lkKSkgfD4KICBtdXRhdGUobmFtZSA9IGZhY3RvcihuYW1lLCBsZXZlbHMgPSB2X25hbWVzKSkKCmRmIHw+CiAgZ2dwbG90KGFlcyh4ID0gbmFtZSwgeSA9IHZhbHVlLCBjb2xvdXIgPSDjg5bjg4njgqbjga7lk4HnqK4sIGdyb3VwID1yb3dpZCkpICsKICBnZW9tX2xpbmUoYWxwaGEgPSAwLjUpICsKICBjb29yZF9mbGlwKCkgKwogIGxhYnModGl0bGUgPSAi44Ov44Kk44Oz44Gu54m55b6044Go44OW44OJ44Km44Gu5ZOB56iuIiwgeSA9ICLnm7jlr77jgrnjgrPjgqIiLCB4ID0gIiIpCmBgYAoKIyMjIDQuMS4zIOWkieaVsOOBlOOBqOOBq+WAi+WIpeOBq+WPr+imluWMluOBmeOCi+aWueazlQoKYGBge3IgZmlnLmhlaWdodD03LjUsIGZpZy53aWR0aD03LjUsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CmxpYnJhcnkoY29uZmxpY3RlZCkKbGlicmFyeSh0aWR5dmVyc2UpCgp2X25hbWVzIDwtIGMoCiAgICAi44OW44OJ44Km44Gu5ZOB56iuIiwgIuOCouODq+OCs+ODvOODq+W6puaVsCIsICLjg6rjg7PjgrTphbgiLCAi44Of44ON44Op44Or5YiGIiwgCiAgICAi44Of44ON44Op44Or5YiG44Gu44Ki44Or44Kr44Oq5bqmIiwgIuODnuOCsOODjeOCt+OCpuODoCIsICLlhajjg5Xjgqfjg47jg7zjg6vpoZ4iLCAi44OV44Op44OQ44OO44Kk44OJIiwKICAgICLpnZ7jg5Xjg6njg5Djg47jgqTjg4njg5Xjgqfjg47jg7zjg6vpoZ4iLCAi44OX44Ot44Ki44Oz44OI44K344Ki44OL44OzIiwgIuiJsuOBruW8t+OBlSIsICLoibLnm7giLAogICAgIk9EMjgwL09EMzE15YCkIiwgIuODl+ODreODquODsyIKICApCgpkZiA8LSByZWFkX2NzdigKICAiaHR0cHM6Ly9hcmNoaXZlLmljcy51Y2kuZWR1L21sL21hY2hpbmUtbGVhcm5pbmctZGF0YWJhc2VzL3dpbmUvd2luZS5kYXRhIiwKICBjb2xfbmFtZXMgPSB2X25hbWVzLAogIGNvbF90eXBlcyA9ICJmIgopIHw+CiAgbXV0YXRlKGFjcm9zcyh3aGVyZShpcy5udW1lcmljKSwgXCh4KSBzY2FsZSh4KSkpIHw+ICPmqJnmupbljJYKICByb3dpZF90b19jb2x1bW4oKSB8PgogIHBpdm90X2xvbmdlcighYyjjg5bjg4njgqbjga7lk4HnqK4sIHJvd2lkKSkgfD4KICBtdXRhdGUobmFtZSA9IGZhY3RvcihuYW1lLCBsZXZlbHMgPSB2X25hbWVzKSkKCnAxIDwtIGRmIHw+CiAgZ2dwbG90KGFlcyh4ID0gbmFtZSwgeSA9IHZhbHVlLCBncm91cCA9IOODluODieOCpuOBruWTgeeoriwgZmlsbCA9IOODluODieOCpuOBruWTgeeorikpICsKICBzdGF0X3N1bW1hcnkoZ2VvbSA9ICJiYXIiLCBmdW4gPSAibWVhbiIsIHBvc2l0aW9uID0gImRvZGdlMiIpICsKICBzdGF0X3N1bW1hcnkoZ2VvbSA9ICJlcnJvcmJhciIsIGZ1bi5kYXRhID0gIm1lYW5fc2UiLCBwb3NpdGlvbiA9ICJkb2RnZTIiKSArCiAgbGFicyh5ID0gIuebuOWvvuOCueOCs+OCoiIsIHggPSAiIikgKwogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIGhqdXN0ID0gMSksCiAgICAgICAgYXNwZWN0LnJhdGlvID0gMS8yLAogICAgICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikKCnAyIDwtIGRmIHw+CiAgZ2dwbG90KGFlcyh4ID0gbmFtZSwgeSA9IHZhbHVlLCBmaWxsID0g44OW44OJ44Km44Gu5ZOB56iuKSkgKwogIGdlb21fdmlvbGluKHBvc2l0aW9uPSJkb2RnZSIpICsKICBsYWJzKHkgPSAi55u45a++44K544Kz44KiIiwgeCA9ICIiKSArCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA0NSwgaGp1c3QgPSAxKSwKICAgICAgICBhc3BlY3QucmF0aW8gPSAxLzIsCiAgICAgICAgbGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKQoKcDEgLyBwMgpgYGAKCiMjIyA0LjEuNCDjg5Ljg7zjg4jjg57jg4Pjg5cKCmBgYHtyICBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpsaWJyYXJ5KGNvbmZsaWN0ZWQpCmxpYnJhcnkodGlkeXZlcnNlKQpsaWJyYXJ5KHZpcmlkaXMpCgp2X25hbWVzIDwtIGMoCiAgICAi44OW44OJ44Km44Gu5ZOB56iuIiwgIuOCouODq+OCs+ODvOODq+W6puaVsCIsICLjg6rjg7PjgrTphbgiLCAi44Of44ON44Op44Or5YiGIiwgCiAgICAi44Of44ON44Op44Or5YiG44Gu44Ki44Or44Kr44Oq5bqmIiwgIuODnuOCsOODjeOCt+OCpuODoCIsICLlhajjg5Xjgqfjg47jg7zjg6vpoZ4iLCAi44OV44Op44OQ44OO44Kk44OJIiwKICAgICLpnZ7jg5Xjg6njg5Djg47jgqTjg4njg5Xjgqfjg47jg7zjg6vpoZ4iLCAi44OX44Ot44Ki44Oz44OI44K344Ki44OL44OzIiwgIuiJsuOBruW8t+OBlSIsICLoibLnm7giLAogICAgIk9EMjgwL09EMzE15YCkIiwgIuODl+ODreODquODsyIKICApCgpkZiA8LSByZWFkX2NzdigKICAiaHR0cHM6Ly9hcmNoaXZlLmljcy51Y2kuZWR1L21sL21hY2hpbmUtbGVhcm5pbmctZGF0YWJhc2VzL3dpbmUvd2luZS5kYXRhIiwKICBjb2xfbmFtZXMgPSB2X25hbWVzLAogIGNvbF90eXBlcyA9ICJmIgogICkgfD4KICBtdXRhdGUoYWNyb3NzKHdoZXJlKGlzLm51bWVyaWMpLCBcKHgpIHNjYWxlKHgpKSkgfD4gI+aomea6luWMlgogIHJvd2lkX3RvX2NvbHVtbigpIHw+CiAgcGl2b3RfbG9uZ2VyKCFjKHJvd2lkLCDjg5bjg4njgqbjga7lk4HnqK4pKSB8PgogIG11dGF0ZShuYW1lID0gZmFjdG9yKG5hbWUsIGxldmVscyA9IHJldih2X25hbWVzKSkpCgpkZiB8PgogIGdncGxvdChhZXMoeCA9IHJvd2lkLCB5ID0gbmFtZSwgZmlsbCA9IHZhbHVlKSkgKwogIGdlb21fdGlsZSgpICsKICBmYWNldF93cmFwKHZhcnMo44OW44OJ44Km44Gu5ZOB56iuKSwgc2NhbGVzID0gImZyZWVfeCIpICsgCiAgc2NhbGVfZmlsbF92aXJpZGlzKG9wdGlvbiA9ICJ0dXJibyIpICsKICB0aGVtZShheGlzLnRpdGxlLnkgPSBlbGVtZW50X2JsYW5rKCkpICsKICBsYWJzKHggPSAi44Ov44Kk44Oz6YqY5p+E55Wq5Y+3IiwgdGl0bGUgPSAi44Kr44Op44O844Kz44O844OJ44Gn5YCk44KS6KGo54++44GZ44KLIikKCmBgYAoKIyMjIDQuMS41IOWQhOWAi+S9k+OBruihjOWLleaZguezu+WIl+OCkuWPr+imluWMluOBmeOCiwoKYGBge3IgZmlnLmhlaWdodD0xMiwgZmlnLndpZHRoPTYsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CmxpYnJhcnkoY29uZmxpY3RlZCkKbGlicmFyeSh0aWR5dmVyc2UpCgpkZiA8LSByZWFkX2NzdigKICAiaHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL3RrRXpha2kvZGF0YV92aXN1YWxpemF0aW9uL21haW4vNCVFNyVBQiVBMC9kYXRhL2JlaGF2aW9yX2RhdGEuY3N2IgogICkgfD4KICBtdXRhdGUoVGltZSA9IFRpbWUgLyAzNjAwKSAgfD4gIyBUaW1l5YiX44KS56eS44GL44KJ5pmC6ZaT44Gr5aSJ5o+bCiAgcGl2b3RfbG9uZ2VyKCFUaW1lKSB8PgogIG11dGF0ZSgKICAgIHZhbHVlID0gY2FzZV93aGVuKAogICAgICB2YWx1ZSA9PSAiR2FyYmFnZSIgfiAi44K044Of5o2o44Gm5aC0IiwKICAgICAgdmFsdWUgPT0gIk5lc3QiIH4gIuWvneWupCIsCiAgICAgIHZhbHVlID09ICJPdGhlciIgfiAi5LiA6Iis44Gu6YOo5bGLIiwKICAgICAgdmFsdWUgPT0gIlRvaWxldCIgfiAi44OI44Kk44OsIgogICAgICApIHw+CiAgICAgIGZhY3RvcihsZXZlbHMgPSBjKCLjgrTjg5/mjajjgabloLQiLCAi44OI44Kk44OsIiwgIuWvneWupCIsICLkuIDoiKzjga7pg6jlsYsiKSkKICAgICkgCgpkZiB8PgogIGdncGxvdChhZXMoeCA9bmFtZSAsIHkgPSBUaW1lLCBmaWxsID0gdmFsdWUpKSArCiAgZ2VvbV90aWxlKCkgKwogIHNjYWxlX3lfcmV2ZXJzZSgpICsKICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBjKCIjRTY5RjAwIiwgIiM1NkI0RTkiLCAiIzAwOUU3MyIsICIjRjBFNDQyIikpICsKICBsYWJzKHRpdGxlID0gIuODkuODvOODiOODnuODg+ODl+OBq+OCiOOCi+ihjOWLleaZguezu+WIl+OBruWPr+imluWMliIsIHggPSAi5YCL5L2TIiwgeSA9ICLmmYLplpNbaF0iKQpgYGAKCiMjIyA0LjEuNiAx5YCL5L2T44Gu5ZCE6KGM5YuV44Gu5Y+v6KaW5YyWCgpgYGB7ciBmaWcuaGVpZ2h0PTEyLCBmaWcud2lkdGg9NiwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KbGlicmFyeShjb25mbGljdGVkKQpsaWJyYXJ5KHRpZHl2ZXJzZSkKbGlicmFyeShwYXRjaHdvcmspCgojIENTVuODh+ODvOOCv+OCkuiqreOBv+i+vOOCgApkZiA8LSByZWFkX2NzdigKICAiaHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL3RrRXpha2kvZGF0YV92aXN1YWxpemF0aW9uL21haW4vNCVFNyVBQiVBMC9kYXRhL2JlaGF2aW9yX2RhdGEuY3N2IgopIHw+CiAgbXV0YXRlKFRpbWUgPSBUaW1lIC8gMzYwMCkgIHw+ICMgVGltZeWIl+OCkuenkuOBi+OCieaZgumWk+OBq+WkieaPmwogIHBpdm90X2xvbmdlcighVGltZSkgfD4KICBtdXRhdGUoCiAgICB2YWx1ZSA9IGNhc2Vfd2hlbigKICAgICAgdmFsdWUgPT0gIkdhcmJhZ2UiIH4gIuOCtOODn+aNqOOBpuWgtCIsCiAgICAgIHZhbHVlID09ICJOZXN0IiB+ICLlr53lrqQiLAogICAgICB2YWx1ZSA9PSAiT3RoZXIiIH4gIuS4gOiIrOOBrumDqOWxiyIsCiAgICAgIHZhbHVlID09ICJUb2lsZXQiIH4gIuODiOOCpOODrCIKICAgICkgfD4KICAgICAgZmFjdG9yKGxldmVscyA9IHJldihjKCLjgrTjg5/mjajjgabloLQiLCAi44OI44Kk44OsIiwgIuWvneWupCIsICLkuIDoiKzjga7pg6jlsYsiKSkpCiAgKQoKIyAx5YCL5L2TKEop44Gu44OH44O844K/CmRmX2ogPC0gZGYgfD4KICBkcGx5cjo6ZmlsdGVyKG5hbWUgPT0gIkoiKSB8PiAKICBkcGx5cjo6c2VsZWN0KCFuYW1lKSB8PgogIG11dGF0ZShhY3Rpb24gPSAiMSIpCgpwMSA8LSBkZl9qIHw+CiAgZ2dwbG90KGFlcyh4ID0gdmFsdWUsIHkgPSBUaW1lLCBmaWxsID0gdmFsdWUpKSArCiAgZ2VvbV90aWxlKCkgKwogIHNjYWxlX3lfcmV2ZXJzZSgpICsKICBsYWJzKHggPSAiIiwgeSA9ICLmmYLplpNbaF0iKSArCiAgc2NhbGVfZmlsbF9icmV3ZXIocGFsZXR0ZSA9ICJTZXQxIikgKwogIHRoZW1lKAogICAgbGVnZW5kLnBvc2l0aW9uPSJub25lIiwKICAgIGFzcGVjdC5yYXRpbyA9IDQgLyAxLjUKICAgICkKCnAyIDwtIGRmX2ogfD4KICBkcGx5cjo6ZmlsdGVyKFRpbWUgPj0gMTMsIFRpbWUgPD0gMTYpIHw+CiAgZ2dwbG90KGFlcyh4ID0gdmFsdWUsIHkgPSBUaW1lLCBmaWxsID0gdmFsdWUpKSArCiAgZ2VvbV90aWxlKCkgKwogIHNjYWxlX3lfcmV2ZXJzZSgpICsKICBsYWJzKHggPSAiIiwgeSA9ICLmmYLplpNbaF0iKSArCiAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gYygiI0U2OUYwMCIsICIjNTZCNEU5IiwgIiMwMDlFNzMiLCAiI0YwRTQ0MiIpKSArCiAgdGhlbWUoCiAgICBsZWdlbmQucG9zaXRpb249Im5vbmUiLAogICAgYXNwZWN0LnJhdGlvID0gNCAvIDEuNQogICkKCnAxICsgcDIKYGBgCgojIyMgNC4yLjEg6Zai5L+C44OH44O844K/44Gu44OS44O844OI44Oe44OD44OX44Gr44KI44KL5Y+v6KaW5YyWCgpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpsaWJyYXJ5KGNvbmZsaWN0ZWQpCmxpYnJhcnkodGlkeXZlcnNlKSAKbGlicmFyeShwYXRjaHdvcmspCmxpYnJhcnkodmlyaWRpcykKCiMg5YWx6JGX44OH44O844K/CnJlc2VhcmNoZXJzIDwtIGMoIkEiLCAiQiIsICJDIiwgIkQiLCAiRSIsICJGIiwgIkciLCAiSCIsICJJIiwgIkoiKQpjb2xsYWJvcmF0aW9uIDwtIG1hdHJpeCgKICBjKAogICAgTkEsIDEsIDEsIDAsIDAsIDAsIDAsIDAsIDAsIDEsCiAgICAxLCBOQSwgMCwgMSwgMSwgMSwgMCwgMCwgMCwgMSwKICAgIDEsIDAsIE5BLCAwLCAwLCAwLCAxLCAxLCAxLCAwLAogICAgMCwgMSwgMCwgTkEsIDAsIDAsIDAsIDAsIDAsIDAsCiAgICAwLCAxLCAwLCAwLCBOQSwgMCwgMCwgMCwgMCwgMCwKICAgIDAsIDEsIDAsIDAsIDAsIE5BLCAwLCAwLCAwLCAwLAogICAgMCwgMCwgMSwgMCwgMCwgMCwgTkEsIDAsIDAsIDAsCiAgICAwLCAwLCAxLCAwLCAwLCAwLCAwLCBOQSwgMCwgMCwKICAgIDAsIDAsIDEsIDAsIDAsIDAsIDAsIDAsIE5BLCAwLAogICAgMSwgMSwgMCwgMCwgMCwgMCwgMCwgMCwgMCwgTkEKICAgICksCiAgbnJvdz0xMCwKICBkaW1uYW1lcyA9IGxpc3QocmVzZWFyY2hlcnMsIHJlc2VhcmNoZXJzKQogICkKCnAxIDwtIGNvbGxhYm9yYXRpb24gfD4KICBhcy5kYXRhLmZyYW1lKCkgfD4KICByb3duYW1lc190b19jb2x1bW4oKSB8PgogIHBpdm90X2xvbmdlcighcm93bmFtZSkgfD4KICBtdXRhdGUoCiAgICB2YWx1ZSA9IGZhY3Rvcih2YWx1ZSksCiAgICBuYW1lID0gZmFjdG9yKG5hbWUsIGxldmVscyA9IHJldihMRVRURVJTWzE6MTBdKSkKICAgICkgfD4KICBnZ3Bsb3QoYWVzKHggPSByb3duYW1lLCB5ID0gbmFtZSwgZmlsbCA9IHZhbHVlLCBsYWJlbCA9IHZhbHVlKSkgKwogIGdlb21fdGlsZSgpICsKICBnZW9tX3RleHQoKSArCiAgc2NhbGVfZmlsbF9odWUobmFtZSA9ICIiLCBsYWJlbHMgPSBjKCIwIiA9ICLlhbHokZfjgarjgZciLCAiMSIgPSLlhbHokZfjgYLjgooiKSkgKwogIGxhYnModGl0bGUgPSAi5YWx6JGX6Zai5L+C44Gu5pyJ54ShIiwgeCA9ICIiLCB5ID0gIiIpICsKICB0aGVtZShhc3BlY3QucmF0aW8gPSAxKQoKIyDjgZPjgZPjga/jgoLjgYbjgaHjgofjgaPjgajjgrnjg57jg7zjg4jjgavmm7jjgZHjgarjgYTjgYvvvJ8Kc2V0LnNlZWQoMCkKdjAgPC0gcnVuaWYobiA9ICgxMDAgLTEwKS8yLCBtaW4gPSAwLjAsIG1heCA9IDAuNSkgfD4gcm91bmQoMikKdjEgPC0gcnVuaWYobiA9ICgxMDAgLTEwKS8yLCBtaW4gPSAwLjUsIG1heCA9IDEuMCkgfD4gcm91bmQoMikKY29sbGFib3JhdGlvbl9zY29yZSA8LSBjb2xsYWJvcmF0aW9uCmNvbGxhYm9yYXRpb25fc2NvcmVbdXBwZXIudHJpKGNvbGxhYm9yYXRpb25fc2NvcmUpXSA8LQogIGlmX2Vsc2UoY29sbGFib3JhdGlvbltsb3dlci50cmkoY29sbGFib3JhdGlvbildPT0gMSwgdjEsIHYwKSAKY29sbGFib3JhdGlvbl9zY29yZSA8LSB0KGNvbGxhYm9yYXRpb25fc2NvcmUpCmNvbGxhYm9yYXRpb25fc2NvcmVbdXBwZXIudHJpKGNvbGxhYm9yYXRpb25fc2NvcmUpXSA8LQogIGlmX2Vsc2UoY29sbGFib3JhdGlvbltsb3dlci50cmkoY29sbGFib3JhdGlvbildPT0gMSwgdjEsIHYwKQoKcDIgPC0gY29sbGFib3JhdGlvbl9zY29yZSB8PgogIGFzLmRhdGEuZnJhbWUoKSB8PgogIHJvd25hbWVzX3RvX2NvbHVtbigpIHw+CiAgcGl2b3RfbG9uZ2VyKCFyb3duYW1lKSB8PgogIG11dGF0ZShuYW1lID0gZmFjdG9yKG5hbWUsIGxldmVscyA9IHJldihMRVRURVJTWzE6MTBdKSkpIHw+CiAgZ2dwbG90KGFlcyh4ID0gcm93bmFtZSwgeSA9IG5hbWUsIGZpbGwgPSB2YWx1ZSwgbGFiZWwgPSB2YWx1ZSkpICsKICBnZW9tX3RpbGUoKSArCiAgZ2VvbV90ZXh0KHNpemUgPSAyKSArCiAgbGFicyh0aXRsZSA9ICLnoJTnqbbjga7oiIjlkbPjga7poZ7kvLzluqYiLCB4ID0gIiIsIHkgPSAiIikgKwogIHRoZW1lKGFzcGVjdC5yYXRpbyA9IDEpICsKICBzY2FsZV9maWxsX3ZpcmlkaXMob3B0aW9uID0gInR1cmJvIikKCnAxICsgcDIKYGBgCgojIyMgNC4yLjIg44ON44OD44OI44Ov44O844Kv44Gr44KI44KL6Zai5L+C44OH44O844K/44Gu5Y+v6KaW5YyWCgppZ3JhcGjjgafmm7jjgYTjgabjgYTjgZ/jgoLjga7jgpJnZ3JhcGjjgafmm7jjgY3nm7TjgZfjgb7jgZfjgZ/jgIIKCmBgYHtyIGZpZy5oZWlnaHQ9Ny41LCBmaWcud2lkdGg9Ny41LCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpsaWJyYXJ5KGNvbmZsaWN0ZWQpCmxpYnJhcnkodGlkeXZlcnNlKQpsaWJyYXJ5KGlncmFwaCkKbGlicmFyeSh0aWR5Z3JhcGgpCmxpYnJhcnkoZ2dyYXBoKQpsaWJyYXJ5KHBhdGNod29yaykKCiMg5YWx6JGX44OH44O844K/CnJlc2VhcmNoZXJzIDwtIGMoIkEiLCAiQiIsICJDIiwgIkQiLCAiRSIsICJGIiwgIkciLCAiSCIsICJJIiwgIkoiKQpjb2xsYWJvcmF0aW9uIDwtIG1hdHJpeCgKICBjKAogICAgTkEsIDEsIDEsIDAsIDAsIDAsIDAsIDAsIDAsIDEsCiAgICAxLCBOQSwgMCwgMSwgMSwgMSwgMCwgMCwgMCwgMSwKICAgIDEsIDAsIE5BLCAwLCAwLCAwLCAxLCAxLCAxLCAwLAogICAgMCwgMSwgMCwgTkEsIDAsIDAsIDAsIDAsIDAsIDAsCiAgICAwLCAxLCAwLCAwLCBOQSwgMCwgMCwgMCwgMCwgMCwKICAgIDAsIDEsIDAsIDAsIDAsIE5BLCAwLCAwLCAwLCAwLAogICAgMCwgMCwgMSwgMCwgMCwgMCwgTkEsIDAsIDAsIDAsCiAgICAwLCAwLCAxLCAwLCAwLCAwLCAwLCBOQSwgMCwgMCwKICAgIDAsIDAsIDEsIDAsIDAsIDAsIDAsIDAsIE5BLCAwLAogICAgMSwgMSwgMCwgMCwgMCwgMCwgMCwgMCwgMCwgTkEKICApLAogIG5yb3c9MTAsCiAgZGltbmFtZXMgPSBsaXN0KHJlc2VhcmNoZXJzLCByZXNlYXJjaGVycykKKQoKIyDnoJTnqbbjga7oiIjlkbPjga7poZ7kvLzluqYKc2V0LnNlZWQoMCkKdjAgPC0gcnVuaWYobiA9ICgxMDAgLTEwKS8yLCBtaW4gPSAwLjAsIG1heCA9IDAuNSkgfD4gcm91bmQoMikKdjEgPC0gcnVuaWYobiA9ICgxMDAgLTEwKS8yLCBtaW4gPSAwLjUsIG1heCA9IDEuMCkgfD4gcm91bmQoMikKY29sbGFib3JhdGlvbl9zY29yZSA8LSBjb2xsYWJvcmF0aW9uCmNvbGxhYm9yYXRpb25fc2NvcmVbdXBwZXIudHJpKGNvbGxhYm9yYXRpb25fc2NvcmUpXSA8LQogIGlmX2Vsc2UoY29sbGFib3JhdGlvbltsb3dlci50cmkoY29sbGFib3JhdGlvbildPT0gMSwgdjEsIHYwKSAKY29sbGFib3JhdGlvbl9zY29yZSA8LQogIHQoY29sbGFib3JhdGlvbl9zY29yZSkKY29sbGFib3JhdGlvbl9zY29yZVt1cHBlci50cmkoY29sbGFib3JhdGlvbl9zY29yZSldIDwtCiAgaWZfZWxzZShjb2xsYWJvcmF0aW9uW2xvd2VyLnRyaShjb2xsYWJvcmF0aW9uKV09PSAxLCB2MSwgdjApCgojIOWFseiRl+ODjeODg+ODiOODr+ODvOOCrwpkaWFnKGNvbGxhYm9yYXRpb24pIDwtIDAKY29sbGFib19nIDwtIGNvbGxhYm9yYXRpb24gfD4KICBncmFwaF9mcm9tX2FkamFjZW5jeV9tYXRyaXgobW9kZSA9ICJ1bmRpcmVjdGVkIikKCnNldC5zZWVkKDApCgojIHRpZHlncmFwaOOBuOWkieaPmwpjb2xsYWJvX2dfdGlkeSA8LSBhc190YmxfZ3JhcGgoY29sbGFib19nLCBkaXJlY3RlZCA9IEZBTFNFKQoKIyDlhbHokZfplqLkv4Ljga7mnInnhKEKcDEgPC0gY29sbGFib19nX3RpZHkgfD4KICBnZ3JhcGgobGF5b3V0ID0gImxpbmVhciIsIGNpcmN1bGFyID0gVFJVRSkgKwogIGdlb21fZWRnZV9saW5rKCkgKwogIGdlb21fbm9kZV9sYWJlbChhZXMobGFiZWwgPSBuYW1lKSwgcmVwZWwgPSBGQUxTRSkgKwogIHRoZW1lX3ZvaWQoKSArCiAgdGhlbWUoYXNwZWN0LnJhdGlvID0gMSkgKwogIGxhYnModGl0bGUgPSAi5YWx6JGX6Zai5L+C44Gu5pyJ54Sh44CB5YaG54q244Os44Kk44Ki44Km44OIIikKCnAyIDwtIGNvbGxhYm9fZ190aWR5IHw+CiAgZ2dyYXBoKGxheW91dCA9ICJmciIpICsKICBnZW9tX2VkZ2VfbGluaygpICsKICBnZW9tX25vZGVfbGFiZWwoYWVzKGxhYmVsID0gbmFtZSksIHJlcGVsID0gRkFMU0UpICsKICB0aGVtZV92b2lkKCkgKwogIHRoZW1lKGFzcGVjdC5yYXRpbyA9IDEpICsKICBsYWJzKHRpdGxlID0gIuWFseiRl+mWouS/guOBruacieeEoeOAgeOCueODl+ODquODs+OCsOODrOOCpOOCouOCpuODiCIpCgoKIyDpoZ7kvLzluqbjg43jg4Pjg4jjg6/jg7zjgq8KZGlhZyhjb2xsYWJvcmF0aW9uX3Njb3JlKSA8LSAwCnNjb3JlX2cgPC0gY29sbGFib3JhdGlvbl9zY29yZSB8PgogIGdyYXBoX2Zyb21fYWRqYWNlbmN5X21hdHJpeChtb2RlID0gInVuZGlyZWN0ZWQiLCB3ZWlnaHRlZCA9IFRSVUUpCgpFKHNjb3JlX2cpJGNvbG9yIDwtIHJvdW5kKChFKHNjb3JlX2cpJHdlaWdodCAvIG1heChFKHNjb3JlX2cpJHdlaWdodCkpICogMTAwLCAwKQpFKHNjb3JlX2cpJHdlaWdodAoKCiMgdGlkeWdyYXBo44G45aSJ5o+bCnNjb3JlX2dfdGlkeSA8LSBhc190YmxfZ3JhcGgoc2NvcmVfZywgZGlyZWN0ZWQgPSBGQUxTRSkKCnAzIDwtIHNjb3JlX2dfdGlkeSB8PgogIGdncmFwaChsYXlvdXQgPSAibGluZWFyIiwgY2lyY3VsYXIgPSBUUlVFKSArCiAgZ2VvbV9lZGdlX2xpbmsoYWVzKHdpZHRoID0gY29sb3IsIGNvbG9yID0gY29sb3IpKSArCiAgc2NhbGVfZWRnZV9jb2xvcl92aXJpZGlzKG9wdGlvbiA9ICJ0dXJibyIsIGFscGhhID0gMC41KSArIAogIGdlb21fbm9kZV9sYWJlbChhZXMobGFiZWwgPSBuYW1lKSwgcmVwZWwgPSBGQUxTRSkgKwogIHRoZW1lX3ZvaWQoKSArCiAgdGhlbWUoYXNwZWN0LnJhdGlvID0gMSwgbGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKSArCiAgbGFicyh0aXRsZSA9ICLnoJTnqbbjga7oiIjlkbPjga7poZ7kvLzluqbjgIHlhobnirbjg6zjgqTjgqLjgqbjg4giKQoKcDQgPC0gc2NvcmVfZ190aWR5IHw+CiAgZ2dyYXBoKGxheW91dCA9ICJmciIpICsKICBnZW9tX2VkZ2VfbGluayhhZXMod2lkdGggPSBjb2xvciwgY29sb3IgPSBjb2xvcikpICsKICBzY2FsZV9lZGdlX2NvbG9yX3ZpcmlkaXMob3B0aW9uID0gInR1cmJvIiwgYWxwaGEgPSAwLjUpICsgCiAgZ2VvbV9ub2RlX2xhYmVsKGFlcyhsYWJlbCA9IG5hbWUpLCByZXBlbCA9IEZBTFNFKSArCiAgdGhlbWVfdm9pZCgpICsKICB0aGVtZShhc3BlY3QucmF0aW8gPSAxLCBsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpICsKICBsYWJzKHRpdGxlID0gIueglOeptuOBruiIiOWRs+OBrumhnuS8vOW6puOAgeOCueODl+ODquODs+OCsOODrOOCpOOCouOCpuODiCIpCgp7cDF8cDJ9L3twM3xwNH0KCmBgYAoKIyMjIyA0LjIuM+OAgOaMh+WwjumWouS/guOBruWPr+imluWMlgoKYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KbGlicmFyeShjb25mbGljdGVkKQpsaWJyYXJ5KHRpZHl2ZXJzZSkKbGlicmFyeShpZ3JhcGgpCmxpYnJhcnkodGlkeWdyYXBoKQpsaWJyYXJ5KGdncmFwaCkKbGlicmFyeShwYXRjaHdvcmspCgojIOeglOeptuiAheODquOCueODiApyZXNlYXJjaGVycyA8LSBjKCJBIiwgIkIiLCAiQyIsICJEIiwgIkUiLCAiRiIsICJHIiwgIkgiLCAiSSIsICJKIikKCiMg5YWx6JGX44OH44O844K/CmNvbGxhYm9yYXRpb24gPC0gbWF0cml4KAogIGMoCiAgICBOQSwgMSwgMSwgMCwgMCwgMCwgMCwgMCwgMCwgMSwKICAgIDAsIE5BLCAwLCAxLCAxLCAxLCAwLCAwLCAwLCAwLAogICAgMCwgMCwgTkEsIDAsIDAsIDAsIDEsIDEsIDEsIDAsCiAgICAwLCAwLCAwLCBOQSwgMCwgMCwgMCwgMCwgMCwgMCwKICAgIDAsIDAsIDAsIDAsIE5BLCAwLCAwLCAwLCAwLCAwLAogICAgMCwgMCwgMCwgMCwgMCwgTkEsIDAsIDAsIDAsIDAsCiAgICAwLCAwLCAwLCAwLCAwLCAwLCBOQSwgMCwgMCwgMCwKICAgIDAsIDAsIDAsIDAsIDAsIDAsIDAsIE5BLCAwLCAwLAogICAgMCwgMCwgMCwgMCwgMCwgMCwgMCwgMCwgTkEsIDAsCiAgICAwLCAwLCAwLCAwLCAwLCAwLCAwLCAwLCAwLCBOQQogICAgKSwKICBucm93ID0gMTAsCiAgZGltbmFtZXMgPSBsaXN0KHJlc2VhcmNoZXJzLCByZXNlYXJjaGVycykKICApCgojIOWFseiRl+ODjeODg+ODiOODr+ODvOOCrwpwMSA8LSBjb2xsYWJvcmF0aW9uIHw+CiAgYXMuZGF0YS5mcmFtZSgpIHw+CiAgcm93bmFtZXNfdG9fY29sdW1uKCkgfD4KICBwaXZvdF9sb25nZXIoIXJvd25hbWUpIHw+CiAgbXV0YXRlKAogICAgdmFsdWUgPSBmYWN0b3IodmFsdWUpLAogICAgbmFtZSA9IGZhY3RvcihuYW1lLCBsZXZlbHMgPSByZXYoTEVUVEVSU1sxOjEwXSkpCiAgICApIHw+CiAgZ2dwbG90KGFlcyh4ID0gcm93bmFtZSwgeSA9IG5hbWUsIGZpbGwgPSB2YWx1ZSwgbGFiZWwgPSB2YWx1ZSkpICsKICBnZW9tX3RpbGUoKSArCiAgZ2VvbV90ZXh0KCkgKwogIHNjYWxlX2ZpbGxfaHVlKG5hbWUgPSAiIiwgbGFiZWxzID0gYygiMCIgPSAi5oyH5bCO6Zai5L+C44Gq44GXIiwgIjEiID0i5oyH5bCO6Zai5L+C44GC44KKIikpICsKICBsYWJzKHRpdGxlID0gIumao+aOpeihjOWIl+ihqOekuiIsIHggPSAi5oyH5bCO44GV44KM44Gf56CU56m26ICFIiwgeSA9ICLmjIflsI7jgZfjgZ/noJTnqbbogIUiKSArCiAgdGhlbWUoYXNwZWN0LnJhdGlvID0gMSkKCmRpYWcoY29sbGFib3JhdGlvbikgPC0gMAoKIyB0aWR5Z3JhcGjjgbjlpInmj5sKY29sbGFib3JhdGlvbl90aWR5IDwtIGFzX3RibF9ncmFwaCh0KGNvbGxhYm9yYXRpb24pLCBkaXJlY3RlZCA9IFRSVUUpCgpwMiA8LSBjb2xsYWJvcmF0aW9uX3RpZHkgfD4KICBnZ3JhcGgobGF5b3V0ID0gInRyZWUiKSArCiAgZ2VvbV9lZGdlX2xpbmsoCiAgICBhcnJvdyA9IGFycm93KHR5cGUgPSAiY2xvc2VkIiwgbGVuZ3RoID0gdW5pdCg0LCAnbW0nKSksCiAgICBzdGFydF9jYXAgPSBjaXJjbGUoNCwgJ21tJyksCiAgICBlbmRfY2FwID0gY2lyY2xlKDQsICdtbScpCiAgICApICsKICBnZW9tX25vZGVfbGFiZWwoYWVzKGxhYmVsID0gbmFtZSksIHJlcGVsID0gRkFMU0UpICsKICB0aGVtZV92b2lkKCkgKwogIHRoZW1lKGFzcGVjdC5yYXRpbyA9IDEsIGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikgKwogIGxhYnModGl0bGUgPSAi6ZqO5bGk44Os44Kk44Ki44Km44OI44Gr44KI44KL44ON44OD44OI44Ov44O844Kv6KGo56S6IikKCnAxICsgcDIKYGBgCgoKIyMjIDQuMi40IOanmOOAheOBquODrOOCpOOCouOCpuODiOOBq+OCiOOCi+ODjeODg+ODiOODr+ODvOOCr+aPj+eUuwoKY2lyY2/jg6zjgqTjgqLjgqbjg4jjgYxpZ3JhcGjjgavjga/nhKHjgYTjgojjgYbjgarjga7jgadGcnVjaHRlcm1hbiBhbmQgUmVpbmdvbGTjg6zjgqTjgqLjgqbjg4jjgafku6PnlKjjgIIKCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CmxpYnJhcnkoY29uZmxpY3RlZCkKbGlicmFyeSh0aWR5dmVyc2UpCmxpYnJhcnkoaWdyYXBoKQpsaWJyYXJ5KHRpZHlncmFwaCkKbGlicmFyeShnZ3JhcGgpCmxpYnJhcnkocGF0Y2h3b3JrKQoKc2V0LnNlZWQoMCkKR19lciA8LSBlcmRvcy5yZW55aS5nYW1lKDMwLCAwLjIpCkdfd3MgPC0gd2F0dHMuc3Ryb2dhdHouZ2FtZSgxLCAzMCwgNSwgMC4xKQpHX2JhIDwtIGJhcmFiYXNpLmdhbWUoMzAsIDEpCgpFcmRvc1IgPC0gYXNfYWRqYWNlbmN5X21hdHJpeChHX2VyLCBzcGFyc2UgPSBGQUxTRSkKV2F0dHNTdHJvZ2F0eiA8LSBhc19hZGphY2VuY3lfbWF0cml4KEdfd3MsIHNwYXJzZSA9IEZBTFNFKQpCYXJhYmFzaUFsYmVydCA8LSBhc19hZGphY2VuY3lfbWF0cml4KEdfYmEsIHNwYXJzZSA9IEZBTFNFKQoKbmV0MSA8LSBncmFwaF9mcm9tX2FkamFjZW5jeV9tYXRyaXgoRXJkb3NSLCBtb2RlID0gIm1heCIpCm5ldDIgPC0gZ3JhcGhfZnJvbV9hZGphY2VuY3lfbWF0cml4KFdhdHRzU3Ryb2dhdHosIG1vZGUgPSAibWF4IikKbmV0MyA8LSBncmFwaF9mcm9tX2FkamFjZW5jeV9tYXRyaXgoQmFyYWJhc2lBbGJlcnQsIG1vZGUgPSAibWF4IikKCiMgdGlkeWdyYXBo44G45aSJ5o+bCm5ldDFfdGlkeSA8LSBhc190YmxfZ3JhcGgobmV0MSwgZGlyZWN0ZWQgPSBGQUxTRSkKbmV0Ml90aWR5IDwtIGFzX3RibF9ncmFwaChuZXQyLCBkaXJlY3RlZCA9IEZBTFNFKQpuZXQzX3RpZHkgPC0gYXNfdGJsX2dyYXBoKG5ldDMsIGRpcmVjdGVkID0gRkFMU0UpCgpwMSA8LSBuZXQxX3RpZHkgfD4KICBnZ3JhcGgobGF5b3V0ID0gImxpbmVhciIsIGNpcmN1bGFyID0gVFJVRSkgKwogIGdlb21fZWRnZV9saW5rKGNvbG9yID0gIiM2NjY2NjYiLCBhbHBoYSA9IDAuNSkgKwogIGdlb21fbm9kZV9wb2ludChzaXplID0gMiwgY29sb3IgPSAiI0Y4NzY2RCIpICsKICB0aGVtZV92b2lkKCkgKwogIHRoZW1lKGFzcGVjdC5yYXRpbyA9IDEpICsKICBsYWJzKHRpdGxlID0gIkVyZG9zLVJleW5pIiwgc3VidGl0bGUgPSAiQ2lyY3VsYXIgTGF5b3V0IikKCnAyIDwtIG5ldDJfdGlkeSB8PgogIGdncmFwaChsYXlvdXQgPSAibGluZWFyIiwgY2lyY3VsYXIgPSBUUlVFKSArCiAgZ2VvbV9lZGdlX2xpbmsoY29sb3IgPSAiIzY2NjY2NiIsIGFscGhhID0gMC41KSArCiAgZ2VvbV9ub2RlX3BvaW50KHNpemUgPSAyLCBjb2xvciA9ICIjRjg3NjZEIikgKwogIHRoZW1lX3ZvaWQoKSArCiAgdGhlbWUoYXNwZWN0LnJhdGlvID0gMSkgKwogIGxhYnModGl0bGUgPSAiV2F0dHMtU3Ryb2dhdHoiLCBzdWJ0aXRsZSA9ICJDaXJjdWxhciBMYXlvdXQiKQoKcDMgPC0gbmV0M190aWR5IHw+CiAgZ2dyYXBoKGxheW91dCA9ICJsaW5lYXIiLCBjaXJjdWxhciA9IFRSVUUpICsKICBnZW9tX2VkZ2VfbGluayhjb2xvciA9ICIjNjY2NjY2IiwgYWxwaGEgPSAwLjUpICsKICBnZW9tX25vZGVfcG9pbnQoc2l6ZSA9IDIsIGNvbG9yID0gIiNGODc2NkQiKSArCiAgdGhlbWVfdm9pZCgpICsKICB0aGVtZShhc3BlY3QucmF0aW8gPSAxKSArCiAgbGFicyh0aXRsZSA9ICJCYXJhYmFzaS1BbGJlcnQiLCBzdWJ0aXRsZSA9ICJDaXJjdWxhciBMYXlvdXQiKQoKcDQgPC0gbmV0MV90aWR5IHw+CiAgZ2dyYXBoKGxheW91dCA9ICJpZ3JhcGgiLCBhbGdvcml0aG0gPSJmciIpICsKICBnZW9tX2VkZ2VfbGluayhjb2xvciA9ICIjNjY2NjY2IiwgYWxwaGEgPSAwLjUpICsKICBnZW9tX25vZGVfcG9pbnQoc2l6ZSA9IDIsIGNvbG9yID0gIiNCNzlGMDAiKSArCiAgdGhlbWVfdm9pZCgpICsKICB0aGVtZShhc3BlY3QucmF0aW8gPSAxKSArCiAgbGFicyh0aXRsZSA9ICJFcmRvcy1SZXluaSIsIHN1YnRpdGxlID0gIkZSIExheW91dCIpCgpwNSA8LSBuZXQyX3RpZHkgfD4KICBnZ3JhcGgobGF5b3V0ID0gImlncmFwaCIsIGFsZ29yaXRobSA9ImZyIikgKwogIGdlb21fZWRnZV9saW5rKGNvbG9yID0gIiM2NjY2NjYiLCBhbHBoYSA9IDAuNSkgKwogIGdlb21fbm9kZV9wb2ludChzaXplID0gMiwgY29sb3IgPSAiI0I3OUYwMCIpICsKICB0aGVtZV92b2lkKCkgKwogIHRoZW1lKGFzcGVjdC5yYXRpbyA9IDEpICsKICBsYWJzKHRpdGxlID0gIldhdHRzLVN0cm9nYXR6Iiwgc3VidGl0bGUgPSAiRlIgTGF5b3V0IikKCnA2IDwtIG5ldDNfdGlkeSB8PgogIGdncmFwaChsYXlvdXQgPSAiaWdyYXBoIiwgYWxnb3JpdGhtID0iZnIiKSArCiAgZ2VvbV9lZGdlX2xpbmsoY29sb3IgPSAiIzY2NjY2NiIsIGFscGhhID0gMC41KSArCiAgZ2VvbV9ub2RlX3BvaW50KHNpemUgPSAyLCBjb2xvciA9ICIjQjc5RjAwIikgKwogIHRoZW1lX3ZvaWQoKSArCiAgdGhlbWUoYXNwZWN0LnJhdGlvID0gMSkgKwogIGxhYnModGl0bGUgPSAiQmFyYWJhc2ktQWxiZXJ0Iiwgc3VidGl0bGUgPSAiRlIgTGF5b3V0IikKCnA3IDwtIG5ldDFfdGlkeSB8PgogIGdncmFwaChsYXlvdXQgPSAiaWdyYXBoIiwgYWxnb3JpdGhtID0ia2siKSArCiAgZ2VvbV9lZGdlX2xpbmsoY29sb3IgPSAiIzY2NjY2NiIsIGFscGhhID0gMC41KSArCiAgZ2VvbV9ub2RlX3BvaW50KHNpemUgPSAyLCBjb2xvciA9ICIjMDBCQTM4IikgKwogIHRoZW1lX3ZvaWQoKSArCiAgdGhlbWUoYXNwZWN0LnJhdGlvID0gMSkgKwogIGxhYnModGl0bGUgPSAiRXJkb3MtUmV5bmkiLCBzdWJ0aXRsZSA9ICJLSyBMYXlvdXQiKQoKcDggPC0gbmV0Ml90aWR5IHw+CiAgZ2dyYXBoKGxheW91dCA9ICJpZ3JhcGgiLCBhbGdvcml0aG0gPSJrayIpICsKICBnZW9tX2VkZ2VfbGluayhjb2xvciA9ICIjNjY2NjY2IiwgYWxwaGEgPSAwLjUpICsKICBnZW9tX25vZGVfcG9pbnQoc2l6ZSA9IDIsIGNvbG9yID0gIiMwMEJBMzgiKSArCiAgdGhlbWVfdm9pZCgpICsKICB0aGVtZShhc3BlY3QucmF0aW8gPSAxKSArCiAgbGFicyh0aXRsZSA9ICJXYXR0cy1TdHJvZ2F0eiIsIHN1YnRpdGxlID0gIktLIExheW91dCIpCgpwOSA8LSBuZXQzX3RpZHkgfD4KICBnZ3JhcGgobGF5b3V0ID0gImlncmFwaCIsIGFsZ29yaXRobSA9ImtrIikgKwogIGdlb21fZWRnZV9saW5rKGNvbG9yID0gIiM2NjY2NjYiLCBhbHBoYSA9IDAuNSkgKwogIGdlb21fbm9kZV9wb2ludChzaXplID0gMiwgY29sb3IgPSAiIzAwQkEzOCIpICsKICB0aGVtZV92b2lkKCkgKwogIHRoZW1lKGFzcGVjdC5yYXRpbyA9IDEpICsKICBsYWJzKHRpdGxlID0gIkJhcmFiYXNpLUFsYmVydCIsIHN1YnRpdGxlID0gIktLIExheW91dCIpCgoKe3AxfHAyfHAzfS97cDR8cDV8cDZ9L3twN3xwOHxwOX0KCmBgYAoKCiMjIyA0LjIuNSDmnInlkJHjg43jg4Pjg4jjg6/jg7zjgq/jga7lj6/oppbljJYKCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CmxpYnJhcnkoY29uZmxpY3RlZCkKbGlicmFyeSh0aWR5dmVyc2UpCmxpYnJhcnkoaWdyYXBoKQpsaWJyYXJ5KHRpZHlncmFwaCkKbGlicmFyeShnZ3JhcGgpCgojIOODqeODs+ODgOODoOacieWQkeOCsOODqeODleOCkueUn+aIkApzZXQuc2VlZCgwKQpHX2Rpcl9yYW5kb20gPC0gcmFuZG9tLmdyYXBoLmdhbWUobj0zMCwgcD0wLjEsIGRpcmVjdGVkID0gVFJVRSkgCgojIOmajuWxpOani+mAoOOCkuaMgeOBpOacieWQkeOCsOODqeODleOCkueUn+aIkApzZXQuc2VlZCgwKQpHX2Rpcl9oaWVyYXJjaHkgPC0gc2FtcGxlX3RyZWUoMzAsIGRpcmVjdGVkID0gVFJVRSkKCiMgdGlkeWdyYXBo44G45aSJ5o+bCkdfZGlyX3JhbmRvbV90aWR5IDwtIGFzX3RibF9ncmFwaChHX2Rpcl9yYW5kb20sIGRpcmVjdGVkID0gVFJVRSkKR19kaXJfaGllcmFyY2h5X3RpZHkgPC0gYXNfdGJsX2dyYXBoKEdfZGlyX2hpZXJhcmNoeSwgZGlyZWN0ZWQgPSBUUlVFKQoKcDEgPC0gR19kaXJfcmFuZG9tX3RpZHkgfD4KICBnZ3JhcGgobGF5b3V0ID0gImlncmFwaCIsIGFsZ29yaXRobSA9ICJ0cmVlIikgKwogIGdlb21fZWRnZV9saW5rKGNvbG9yID0gIiM2NjY2NjYiLCBhbHBoYSA9IDAuNSwgYXJyb3cgPSBhcnJvdyh0eXBlID0gImNsb3NlZCIsIGxlbmd0aCA9IHVuaXQoNCwgJ21tJykpKSArCiAgZ2VvbV9ub2RlX3BvaW50KHNpemUgPSAyLCBjb2xvciA9ICIjMDBCRkM0IikgKwogIHRoZW1lX3ZvaWQoKSArCiAgdGhlbWUoYXNwZWN0LnJhdGlvID0gMSkgKwogIGxhYnModGl0bGUgPSAiRGlyZWN0ZWQgUmFuZG9tIEdyYXBoIiwgc3VidGl0bGUgPSAiVHJlZSBMYXlvdXQiKQoKcDIgPC0gR19kaXJfaGllcmFyY2h5X3RpZHkgfD4KICBnZ3JhcGgobGF5b3V0ID0gImlncmFwaCIsIGFsZ29yaXRobSA9ICJ0cmVlIikgKwogIGdlb21fZWRnZV9saW5rKGNvbG9yID0gIiM2NjY2NjYiLCBhbHBoYSA9IDAuNSwgYXJyb3cgPSBhcnJvdyh0eXBlID0gImNsb3NlZCIsIGxlbmd0aCA9IHVuaXQoNCwgJ21tJykpKSArCiAgZ2VvbV9ub2RlX3BvaW50KHNpemUgPSAyLCBjb2xvciA9ICIjMDBCRkM0IikgKwogIHRoZW1lX3ZvaWQoKSArCiAgdGhlbWUoYXNwZWN0LnJhdGlvID0gMSkgKwogIGxhYnModGl0bGUgPSAiRGlyZWN0ZWQgR3JhcGggd2l0aCBXaWRlIEhpZXJhcmNoeSIsIHN1YnRpdGxlID0gIlRyZWUgTGF5b3V0IikKCnAzIDwtIEdfZGlyX3JhbmRvbV90aWR5IHw+CiAgZ2dyYXBoKGxheW91dCA9ICJpZ3JhcGgiLCBhbGdvcml0aG0gPSAiZnIiKSArCiAgZ2VvbV9lZGdlX2xpbmsoY29sb3IgPSAiIzY2NjY2NiIsIGFscGhhID0gMC41LCBhcnJvdyA9IGFycm93KHR5cGUgPSAiY2xvc2VkIiwgbGVuZ3RoID0gdW5pdCg0LCAnbW0nKSkpICsKICBnZW9tX25vZGVfcG9pbnQoc2l6ZSA9IDIsIGNvbG9yID0gIiM2MTlDRkYiKSArCiAgdGhlbWVfdm9pZCgpICsKICB0aGVtZShhc3BlY3QucmF0aW8gPSAxKSArCiAgbGFicyh0aXRsZSA9ICJEaXJlY3RlZCBSYW5kb20gR3JhcGgiLCBzdWJ0aXRsZSA9ICJGUiBMYXlvdXQiKQoKcDQgPC0gR19kaXJfaGllcmFyY2h5X3RpZHkgfD4KICBnZ3JhcGgobGF5b3V0ID0gImlncmFwaCIsIGFsZ29yaXRobSA9ICJmciIpICsKICBnZW9tX2VkZ2VfbGluayhjb2xvciA9ICIjNjY2NjY2IiwgYWxwaGEgPSAwLjUsIGFycm93ID0gYXJyb3codHlwZSA9ICJjbG9zZWQiLCBsZW5ndGggPSB1bml0KDQsICdtbScpKSkgKwogIGdlb21fbm9kZV9wb2ludChzaXplID0gMiwgY29sb3IgPSAiIzYxOUNGRiIpICsKICB0aGVtZV92b2lkKCkgKwogIHRoZW1lKGFzcGVjdC5yYXRpbyA9IDEpICsKICBsYWJzKHRpdGxlID0gIkRpcmVjdGVkIEdyYXBoIHdpdGggV2lkZSBIaWVyYXJjaHkiLCBzdWJ0aXRsZSA9ICJGUiBMYXlvdXQiKQoKcDUgPC0gR19kaXJfcmFuZG9tX3RpZHkgfD4KICBnZ3JhcGgobGF5b3V0ID0gImlncmFwaCIsIGFsZ29yaXRobSA9ICJrayIpICsKICBnZW9tX2VkZ2VfbGluayhjb2xvciA9ICIjNjY2NjY2IiwgYWxwaGEgPSAwLjUsIGFycm93ID0gYXJyb3codHlwZSA9ICJjbG9zZWQiLCBsZW5ndGggPSB1bml0KDQsICdtbScpKSkgKwogIGdlb21fbm9kZV9wb2ludChzaXplID0gMiwgY29sb3IgPSAiI0Y1NjRFMyIpICsKICB0aGVtZV92b2lkKCkgKwogIHRoZW1lKGFzcGVjdC5yYXRpbyA9IDEpICsKICBsYWJzKHRpdGxlID0gIkRpcmVjdGVkIFJhbmRvbSBHcmFwaCIsIHN1YnRpdGxlID0gIktLIExheW91dCIpCgpwNiA8LSBHX2Rpcl9oaWVyYXJjaHlfdGlkeSB8PgogIGdncmFwaChsYXlvdXQgPSAiaWdyYXBoIiwgYWxnb3JpdGhtID0gImtrIikgKwogIGdlb21fZWRnZV9saW5rKGNvbG9yID0gIiM2NjY2NjYiLCBhbHBoYSA9IDAuNSwgYXJyb3cgPSBhcnJvdyh0eXBlID0gImNsb3NlZCIsIGxlbmd0aCA9IHVuaXQoNCwgJ21tJykpKSArCiAgZ2VvbV9ub2RlX3BvaW50KHNpemUgPSAyLCBjb2xvciA9ICIjRjU2NEUzIikgKwogIHRoZW1lX3ZvaWQoKSArCiAgdGhlbWUoYXNwZWN0LnJhdGlvID0gMSkgKwogIGxhYnModGl0bGUgPSAiRGlyZWN0ZWQgR3JhcGggd2l0aCBXaWRlIEhpZXJhcmNoeSIsIHN1YnRpdGxlID0gIktLIExheW91dCIpCgp7cDF8cDJ9L3twM3xwNH0ve3A1fHA2fQpgYGAKCiMjIyA0LjMuMSDjgq/jg6njgrnjgr/jg7zjg57jg4Pjg5fjgavjgojjgovjg4fjg7zjgr/jga7lj6/oppbljJYKCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CmxpYnJhcnkoY29uZmxpY3RlZCkKbGlicmFyeSh0aWR5dmVyc2UpCmxpYnJhcnkocGhlYXRtYXApCmxpYnJhcnkodmlyaWRpc0xpdGUpCgpkZiA8LSByZWFkX2NzdigKICAiaHR0cHM6Ly9hcmNoaXZlLmljcy51Y2kuZWR1L21sL21hY2hpbmUtbGVhcm5pbmctZGF0YWJhc2VzL3dpbmUvd2luZS5kYXRhIiwKICBjb2xfbmFtZXMgPSBjKAogICAgIuODluODieOCpuOBruWTgeeoriIsICLjgqLjg6vjgrPjg7zjg6vluqbmlbAiLCAi44Oq44Oz44K06YW4IiwgIuODn+ODjeODqeODq+WIhiIsIAogICAgIuODn+ODjeODqeODq+WIhuOBruOCouODq+OCq+ODquW6piIsICLjg57jgrDjg43jgrfjgqbjg6AiLCAi5YWo44OV44Kn44OO44O844Or6aGeIiwgIuODleODqeODkOODjuOCpOODiSIsCiAgICAi6Z2e44OV44Op44OQ44OO44Kk44OJ44OV44Kn44OO44O844Or6aGeIiwgIuODl+ODreOCouODs+ODiOOCt+OCouODi+ODsyIsICLoibLjga7lvLfjgZUiLCAi6Imy55u4IiwKICAgICJPRDI4MC9PRDMxNeWApCIsICLjg5fjg63jg6rjg7MiCiAgKSwKICBjb2xfdHlwZXMgPSAiZiIKKSB8PgogIG11dGF0ZShhY3Jvc3Mod2hlcmUoaXMubnVtZXJpYyksIFwoeCkgc2NhbGUoeCkpKSAj5qiZ5rqW5YyWCgpwaGVhdG1hcCgKICBkZiB8PgogICAgZHBseXI6OnNlbGVjdCgh44OW44OJ44Km44Gu5ZOB56iuKSB8PgogICAgdCgpLAogIGNvbG9yID0gdHVyYm8oNTApLAogIGNsdXN0ZXJpbmdfbWV0aG9kID0gIndhcmQuRDIiCiAgKQpgYGAKCiMjIyA0LjMuMyDmp5jjgIXjgarjgq/jg6njgrnjgr/jg6rjg7PjgrDmiYvms5UKCkJJUkNI44Gg44GR6KaL44Gk44GL44KJ44Gq44GE44Gu44Gn55yB55Wl44CCCgrjgaHjgarjgb/jgatS44Gn44GvW21sYmVuY2hdKGh0dHBzOi8vcWlpdGEuY29tL3B1cnBsZV9qcC9pdGVtcy8wZjJmYzA0ZWZjOTNkYzJhMWY4Zinjg5Hjg4PjgrHjg7zjgrjjgavjg5njg7Pjg4Hjg57jg7zjgq/nlKjjga7mp5jjgIXjgarjg4fjg7zjgr/jgrvjg4Pjg4jjgajkurrlt6Xjg4fjg7zjgr/nlJ/miJDnlKjjga7plqLmlbDjgYznlKjmhI/jgZXjgozjgabjgYTjgb7jgZnjgIIKCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIGNhY2hlID0gVFJVRX0KbGlicmFyeShjb25mbGljdGVkKQpsaWJyYXJ5KHRpZHl2ZXJzZSkKbGlicmFyeShDbHVzdGVyUikgICAjIE1pbmlCYXRjaCBLTWVhbnMKbGlicmFyeShhcGNsdXN0ZXIpICAjIEFmZmluaXR5IFByb3BhZ2F0aW9uCmxpYnJhcnkobWVhblNoaWZ0UikgIyBNZWFuU2hpZnQKbGlicmFyeShza21lYW5zKSAgICAjIFNwZWN0cmFsIENsdXN0ZXJpbmcKbGlicmFyeShjbHVzdGVyKSAgICAjIEFnZ2xvbWVyYXRpdmUgQ2x1c3RlcmluZwpsaWJyYXJ5KGRic2NhbikgICAgICMgREJTQ0FOLCBIREJTQ0FOLCBPUFRJQ1MsCmxpYnJhcnkobWNsdXN0KSAgICAgIyBHYXVzc2lhbiBNaXh0dXJlCmxpYnJhcnkocGF0Y2h3b3JrKQoKIyDjg4fjg7zjgr/nlKjmhI8KbXlfcmVhZF9jc3YgPC0gZnVuY3Rpb24oZmlsZSl7CiAgcmVhZF9jc3YoZmlsZSwgY29sX25hbWVzID0gYygieCIsICJ5IiwgImNsYXNzIikpIHw+CiAgICBtdXRhdGUoICPmqJnmupbljJbjgZfjgabjgYrjgY8KICAgICAgeCA9ICh4IC0gbWVhbih4KSkvc2QoeCksCiAgICAgIHkgPSAoeSAtIG1lYW4oeCkpL3NkKHgpLCAgCiAgICAgIGNsYXNzID0gZmFjdG9yKGNsYXNzICsgMSkpCn0KCiMg5YaG5b2i44Gu44Kv44Op44K544K/Cm5vaXN5X2NpcmNsZXMgPC0gbXlfcmVhZF9jc3YoCiAgImh0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9tb3JpbW90b29zYW11L2RhdGFfdmlzdWFsaXphdGlvbi9tYWluL2RhdGEvbm9pc3lfY2lyY2xlcy5jc3YiCiAgKQoKIyDmnIjlnovjga7jgq/jg6njgrnjgr8Kbm9pc3lfbW9vbnMgPC0gbXlfcmVhZF9jc3YoCiAgImh0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9tb3JpbW90b29zYW11L2RhdGFfdmlzdWFsaXphdGlvbi9tYWluL2RhdGEvbm9pc3lfbW9vbnMuY3N2IgogICkgICAgIAoKIyDmraPopo/liIbluIPjgavlvpPjgYbjgq/jg6njgrnjgr8KYmxvYnMgPC0gbXlfcmVhZF9jc3YoCiAgImh0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9tb3JpbW90b29zYW11L2RhdGFfdmlzdWFsaXphdGlvbi9tYWluL2RhdGEvYmxvYnMuY3N2IgogICkgICAgCgojIOeVsOaWueaAp+OBruOBguOCi+ODh+ODvOOCvwphbmlzbyA8LSBteV9yZWFkX2NzdigKICAiaHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL21vcmltb3Rvb3NhbXUvZGF0YV92aXN1YWxpemF0aW9uL21haW4vZGF0YS9hbmlzby5jc3YiCiAgKSAgICAgICAKCiMgM+OBpOOBruato+imj+WIhuW4g+OBq+W+k+OBhuODh+ODvOOCvwp2YXJpZWQgPC0gbXlfcmVhZF9jc3YoCiAgImh0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9tb3JpbW90b29zYW11L2RhdGFfdmlzdWFsaXphdGlvbi9tYWluL2RhdGEvdmFyaWVkLmNzdiIKICApICAgICAgICAgICAgICAgCgpuIDwtIDUwMApzZXQuc2VlZCgwKQpub19zdHJ1Y3R1cmUgPC0gZGF0YS5mcmFtZSh4ID0gcnVuaWYobiksIHkgPSBydW5pZihuKSwgY2xhc3MgPSBmYWN0b3IoIjAiKSkjIOani+mAoOOBruOBquOBhOODh+ODvOOCvwoKIyDlkITmiYvms5Xjga7jg6njg4Pjg5Hjg7zplqLmlbDjgpLnlKjmhI8KIyBNaW5pQmF0Y2ggS01lYW5zCm15X01pbmlCYXRjaEttZWFucyA8LSBmdW5jdGlvbihkYXRhLCBjbHVzdGVyX251bSkgewogIGRhdGFfY2xlYW5lZCA8LSBkYXRhWywgMToyXQogIHN0YXJ0IDwtIFN5cy50aW1lKCkKICBmaXQgPC0gTWluaUJhdGNoS21lYW5zKGRhdGFfY2xlYW5lZCwgY2x1c3RlcnMgPSBjbHVzdGVyX251bSkKICBwcmVkIDwtIHByZWRpY3QoZml0LCBkYXRhX2NsZWFuZWQpCiAgZW5kIDwtIFN5cy50aW1lKCkKICBkaWZmIDwtIGVuZCAtIHN0YXJ0CiAgZGF0YSRwcmVkIDwtIGFzLmZhY3RvcihwcmVkKQogIHJldHVybihsaXN0KHJlcyA9IGRhdGEsIGRpZmYgPSBkaWZmKSkKfQoKIyBBZmZpbml0eSBQcm9wYWdhdGlvbgpteV9hcGNsdXN0ZXIgPC0gZnVuY3Rpb24oZGF0YSwgY2x1c3Rlcl9udW0pIHsKICBkYXRhX2NsZWFuZWQgPC0gZGF0YVssIDE6Ml0KICBzdGFydCA8LSBTeXMudGltZSgpCiAgcHJlZCA8LSBhcGNsdXN0ZXIocyA9IG5lZ0Rpc3RNYXQocj0yKSwgeCA9IGRhdGFfY2xlYW5lZCwgcCA9IC0yMDAsIHEgPSAwLjkpIHw+CiAgICBjdXRyZWUoY2x1c3Rlcl9udW0pIHw+CiAgICBsYWJlbHModHlwZSA9ICJlbnVtIikKICBlbmQgPC0gU3lzLnRpbWUoKQogIGRpZmYgPC0gZW5kIC0gc3RhcnQKICBkYXRhJHByZWQgPC0gYXMuZmFjdG9yKHByZWQpCiAgcmV0dXJuKGxpc3QocmVzID0gZGF0YSwgZGlmZiA9IGRpZmYpKQp9CgojIE1lYW5TaGlmdApteV9tZWFuU2hpZnQgPC0gZnVuY3Rpb24oZGF0YSkgewogIGRhdGFfY2xlYW5lZCA8LSBhcy5tYXRyaXgoZGF0YVssIDE6Ml0pCiAgc3RhcnQgPC0gU3lzLnRpbWUoKQogIHByZWQgPC0gbWVhblNoaWZ0KGRhdGFfY2xlYW5lZCwgZGF0YV9jbGVhbmVkKSRhc3NpZ25tZW50CiAgZW5kIDwtIFN5cy50aW1lKCkKICBkaWZmIDwtIGVuZCAtIHN0YXJ0CiAgZGF0YSRwcmVkIDwtIGFzLmZhY3RvcihwcmVkKQogIHJldHVybihsaXN0KHJlcyA9IGRhdGEsIGRpZmYgPSBkaWZmKSkKfQoKIyBTcGVjdHJhbCBDbHVzdGVyaW5nCm15X3NrbWVhbnMgPC0gZnVuY3Rpb24oZGF0YSwgY2x1c3Rlcl9udW0pIHsKICBkYXRhX2NsZWFuZWQgPC0gYmFzZTo6YXMubWF0cml4KGRhdGFbLCAxOjJdKQogIHN0YXJ0IDwtIFN5cy50aW1lKCkKICBwcmVkIDwtIHNrbWVhbnMoZGF0YV9jbGVhbmVkLCBrID0gY2x1c3Rlcl9udW0pJGNsdXN0ZXIKICBlbmQgPC0gU3lzLnRpbWUoKQogIGRpZmYgPC0gZW5kIC0gc3RhcnQKICBkYXRhJHByZWQgPC0gYXMuZmFjdG9yKHByZWQpCiAgcmV0dXJuKGxpc3QocmVzID0gZGF0YSwgZGlmZiA9IGRpZmYpKQp9CgojIFdhcmQKbXlfaGNsdXN0IDwtIGZ1bmN0aW9uKGRhdGEsIGNsdXN0ZXJfbnVtKSB7CiAgZGF0YV9jbGVhbmVkIDwtIGRhdGFbLCAxOjJdCiAgc3RhcnQgPC0gU3lzLnRpbWUoKQogIHByZWQgPC0gZGF0YV9jbGVhbmVkIHw+CiAgICBzdGF0czo6ZGlzdCgpIHw+CiAgICBoY2x1c3QobWV0aG9kID0gIndhcmQuRDIiKSB8PgogICAgY3V0cmVlKGsgPSBjbHVzdGVyX251bSkKICBlbmQgPC0gU3lzLnRpbWUoKQogIGRpZmYgPC0gZW5kIC0gc3RhcnQKICBkYXRhJHByZWQgPC0gYXMuZmFjdG9yKHByZWQpCiAgcmV0dXJuKGxpc3QocmVzID0gZGF0YSwgZGlmZiA9IGRpZmYpKQp9CgojIEFnZ2xvbWVyYXRpdmUgQ2x1c3RlcmluZwpteV9hZ25lcyA8LSBmdW5jdGlvbihkYXRhLCBjbHVzdGVyX251bSkgewogIGRhdGFfY2xlYW5lZCA8LSBkYXRhWywgMToyXQogIHN0YXJ0IDwtIFN5cy50aW1lKCkKICBwcmVkIDwtIGFnbmVzKGRhdGFfY2xlYW5lZCkgfD4gY3V0cmVlKGsgPSBjbHVzdGVyX251bSkKICBlbmQgPC0gU3lzLnRpbWUoKQogIGRpZmYgPC0gZW5kIC0gc3RhcnQKICBkYXRhJHByZWQgPC0gYXMuZmFjdG9yKHByZWQpCiAgcmV0dXJuKGxpc3QocmVzID0gZGF0YSwgZGlmZiA9IGRpZmYpKQp9CgojIERCU0NBTgpteV9kYnNjYW4gPC0gZnVuY3Rpb24oZGF0YSkgewogIGRhdGFfY2xlYW5lZCA8LSBkYXRhWywgMToyXQogIHN0YXJ0IDwtIFN5cy50aW1lKCkKICBwcmVkIDwtIGRic2Nhbjo6ZGJzY2FuKGRhdGFfY2xlYW5lZCwgZXBzID0gMC4zKSRjbHVzdGVyCiAgZW5kIDwtIFN5cy50aW1lKCkKICBkaWZmIDwtIGVuZCAtIHN0YXJ0CiAgZGF0YSRwcmVkIDwtIGFzLmZhY3RvcihwcmVkKQogIHJldHVybihsaXN0KHJlcyA9IGRhdGEsIGRpZmYgPSBkaWZmKSkKfQoKIyBIREJTQ0FOCm15X2hkYnNjYW4gPC0gZnVuY3Rpb24oZGF0YSkgewogIGRhdGFfY2xlYW5lZCA8LSBkYXRhWywgMToyXQogIHN0YXJ0IDwtIFN5cy50aW1lKCkKICBwcmVkIDwtIGRic2Nhbjo6aGRic2NhbihkYXRhX2NsZWFuZWQsIG1pblB0cyA9IDE1KSRjbHVzdGVyCiAgZW5kIDwtIFN5cy50aW1lKCkKICBkaWZmIDwtIGVuZCAtIHN0YXJ0CiAgZGF0YSRwcmVkIDwtIGFzLmZhY3RvcihwcmVkKQogIHJldHVybihsaXN0KHJlcyA9IGRhdGEsIGRpZmYgPSBkaWZmKSkKfQoKIyBPUFRJQ1MKbXlfb3B0aWNzIDwtIGZ1bmN0aW9uKGRhdGEpIHsKICBkYXRhX2NsZWFuZWQgPC0gZGF0YVssIDE6Ml0KICBzdGFydCA8LSBTeXMudGltZSgpCiAgcHJlZCA8LSBkYnNjYW46Om9wdGljcyhkYXRhX2NsZWFuZWQsIGVwcyA9IDAuMSwgbWluUHRzID0gNykgfD4KICAgIGV4dHJhY3RYaSh4aSA9IDAuMDUpIHw+CiAgICBwbHVjaygiY2x1c3RlciIpCiAgZW5kIDwtIFN5cy50aW1lKCkKICBkaWZmIDwtIGVuZCAtIHN0YXJ0CiAgZGF0YSRwcmVkIDwtIGFzLmZhY3RvcihwcmVkKQogIHJldHVybihsaXN0KHJlcyA9IGRhdGEsIGRpZmYgPSBkaWZmKSkKfQoKIyBHYXVzc2lhbiBNaXh0dXJlCm15X01jbHVzdCA8LSBmdW5jdGlvbihkYXRhKSB7CiAgZGF0YV9jbGVhbmVkIDwtIGRhdGFbLCAxOjJdCiAgc3RhcnQgPC0gU3lzLnRpbWUoKQogIHByZWQgPC0gbWNsdXN0OjpNY2x1c3QoZGF0YV9jbGVhbmVkLCBHID0gMTozKSRjbGFzc2lmaWNhdGlvbgogIGVuZCA8LSBTeXMudGltZSgpCiAgZGlmZiA8LSBlbmQgLSBzdGFydAogIGRhdGEkcHJlZCA8LSBhcy5mYWN0b3IocHJlZCkKICByZXR1cm4obGlzdChyZXMgPSBkYXRhLCBkaWZmID0gZGlmZikpCn0KCiMjIyDjgq/jg6njgrnjgr/jg6rjg7PjgrDlrp/ooYwKZGF0cyA8LSBsaXN0KG5vaXN5X2NpcmNsZXMsIG5vaXN5X21vb25zLCB2YXJpZWQsIGFuaXNvLCBibG9icywgbm9fc3RydWN0dXJlKQpjbnVtcyA8LSBsaXN0KDIsMiwzLDMsMywzKQoKc2V0LnNlZWQoMCkKcmVzX2NsdXN0ZXJpbmcgPC0gbGlzdCgKICByZXNfTWluaUJhdGNoS21lYW5zID0gbWFwMihkYXRzLCBjbnVtcywgXChkYXRzLCBjbnVtcykgbXlfTWluaUJhdGNoS21lYW5zKGRhdHMsIGNudW1zKSksCiAgcmVzX2FwY2x1c3RlciAgICAgICA9IG1hcDIoZGF0cywgY251bXMsIFwoZGF0cywgY251bXMpIG15X2FwY2x1c3RlcihkYXRzLCBjbnVtcykpLAogIHJlc19tZWFuU2hpZnQgICAgICAgPSBwdXJycjo6bWFwKGRhdHMsIFwoZGF0cykgbXlfbWVhblNoaWZ0KGRhdHMpKSwKICByZXNfc2ttZWFucyAgICAgICAgID0gbWFwMihkYXRzLCBjbnVtcywgXChkYXRzLCBjbnVtcykgbXlfc2ttZWFucyhkYXRzLCBjbnVtcykpLAogIHJlc19oY2x1c3QgICAgICAgICAgPSBtYXAyKGRhdHMsIGNudW1zLCBcKGRhdHMsIGNudW1zKSBteV9oY2x1c3QoZGF0cywgY251bXMpKSwKICByZXNfYWduZXMgICAgICAgICAgID0gbWFwMihkYXRzLCBjbnVtcywgXChkYXRzLCBjbnVtcykgbXlfYWduZXMoZGF0cywgY251bXMpKSwKICByZXNfZGJzY2FuICAgICAgICAgID0gcHVycnI6Om1hcChkYXRzLCBcKGRhdHMpIG15X2Ric2NhbihkYXRzKSksCiAgcmVzX2hkYnNjYW4gICAgICAgICA9IHB1cnJyOjptYXAoZGF0cywgXChkYXRzKSBteV9oZGJzY2FuKGRhdHMpKSwKICByZXNfb3B0aWNzICAgICAgICAgID0gcHVycnI6Om1hcChkYXRzLCBcKGRhdHMpIG15X29wdGljcyhkYXRzKSksCiAgcmVzX01jbHVzdCAgICAgICAgICA9IHB1cnJyOjptYXAoZGF0cywgXChkYXRzKSBteV9NY2x1c3QoZGF0cykpCikgCgojIOaPj+eUu+eUqOODqeODg+ODkeODvOmWouaVsOOCkueUqOaEjwpteV9wbG90IDwtIGZ1bmN0aW9uKHJlc3VsdCkgewogIHJlc3VsdCRyZXMgfD4KICAgIGdncGxvdChhZXMoeCA9IHgsIHkgPSB5LCBjb2xvciA9IHByZWQpKSArCiAgICBnZW9tX3BvaW50KHNpemUgPSAxKSArIAogICAgbGFicyhjYXB0aW9uID0gcGFzdGUwKHJvdW5kKHJlc3VsdCRkaWZmLDMpLCJzIikpICsKICAgIHRoZW1lKAogICAgICBheGlzLnRpY2tzID0gZWxlbWVudF9ibGFuaygpLCAgIyB0aWNr44Gu57ea44KS5raI44GZCiAgICAgIGF4aXMudGV4dCA9IGVsZW1lbnRfYmxhbmsoKSwgICAjIHRpY2vjga7mlbDlrZfjgpLmtojjgZkKICAgICAgYXhpcy50aXRsZSA9IGVsZW1lbnRfYmxhbmsoKSwgICMg6Lu444Gu44Op44OZ44Or44KS5raI44GZCiAgICAgIGF4aXMubGluZSA9IGVsZW1lbnRfYmxhbmsoKSwgICAjIOi7uOOBrue3muOCkua2iOOBmQogICAgICBsZWdlbmQucG9zaXRpb249Im5vbmUiLAogICAgICBhc3BlY3QucmF0aW8gPSAxCiAgICAgICkKfQoKbnVtcyA8LSBleHBhbmRfZ3JpZChkID0gMTo2LCBtID0gMToxMCkKcmVzX3Bsb3QgPC0gbWFwMihudW1zJG0sIG51bXMkZCwgXCgueCwgLnkpIG15X3Bsb3QocmVzX2NsdXN0ZXJpbmdbWy54XV1bWy55XV0pKQoKIyDjgZPjgZPjgYzjg4DjgrXjgYTigKbigKbjgoLjgYbjgaHjgofjgaPjgajjganjgYbjgavjgYvjgarjgonjgarjgYTjgYsKcmVzX3Bsb3RbWzFdXSArIHJlc19wbG90W1syXV0gK3Jlc19wbG90W1szXV0gK3Jlc19wbG90W1s0XV0gK3Jlc19wbG90W1s1XV0gK3Jlc19wbG90W1s2XV0gKwogIHJlc19wbG90W1s3XV0gKyByZXNfcGxvdFtbOF1dICtyZXNfcGxvdFtbOV1dICtyZXNfcGxvdFtbMTBdXSArcmVzX3Bsb3RbWzExXV0gK3Jlc19wbG90W1sxMl1dICsKICByZXNfcGxvdFtbMTNdXSArIHJlc19wbG90W1sxNF1dICtyZXNfcGxvdFtbMTVdXSArcmVzX3Bsb3RbWzE2XV0gK3Jlc19wbG90W1sxN11dICtyZXNfcGxvdFtbMThdXSArCiAgcmVzX3Bsb3RbWzE5XV0gKyByZXNfcGxvdFtbMjBdXSArcmVzX3Bsb3RbWzIxXV0gK3Jlc19wbG90W1syMl1dICtyZXNfcGxvdFtbMjNdXSArcmVzX3Bsb3RbWzI0XV0gKwogIHJlc19wbG90W1syNV1dICsgcmVzX3Bsb3RbWzI2XV0gK3Jlc19wbG90W1syN11dICtyZXNfcGxvdFtbMjhdXSArcmVzX3Bsb3RbWzI5XV0gK3Jlc19wbG90W1szMF1dICsKICByZXNfcGxvdFtbMzFdXSArIHJlc19wbG90W1szMl1dICtyZXNfcGxvdFtbMzNdXSArcmVzX3Bsb3RbWzM0XV0gK3Jlc19wbG90W1szNV1dICtyZXNfcGxvdFtbMzZdXSArCiAgcmVzX3Bsb3RbWzM3XV0gKyByZXNfcGxvdFtbMzhdXSArcmVzX3Bsb3RbWzM5XV0gK3Jlc19wbG90W1s0MF1dICtyZXNfcGxvdFtbNDFdXSArcmVzX3Bsb3RbWzQyXV0gKwogIHJlc19wbG90W1s0M11dICsgcmVzX3Bsb3RbWzQ0XV0gK3Jlc19wbG90W1s0NV1dICtyZXNfcGxvdFtbNDZdXSArcmVzX3Bsb3RbWzQ3XV0gK3Jlc19wbG90W1s0OF1dICsKICByZXNfcGxvdFtbNDldXSArIHJlc19wbG90W1s1MF1dICtyZXNfcGxvdFtbNTFdXSArcmVzX3Bsb3RbWzUyXV0gK3Jlc19wbG90W1s1M11dICtyZXNfcGxvdFtbNTRdXSArCiAgcmVzX3Bsb3RbWzU1XV0gKyByZXNfcGxvdFtbNTZdXSArcmVzX3Bsb3RbWzU3XV0gK3Jlc19wbG90W1s1OF1dICtyZXNfcGxvdFtbNTldXSArcmVzX3Bsb3RbWzYwXV0gKwogIHBsb3RfbGF5b3V0KG5jb2wgPSAxMCkKYGBgCgojIyMgNC4zLjQg5aSa5aSJ5pWw44KS44Oa44Ki44OX44Ot44OD44OI44Gn6KaL44KLCgpgYGB7ciBmaWcuaGVpZ2h0PTcuNSwgZmlnLndpZHRoPTcuNSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgY2FjaGUgPSBUUlVFfQpsaWJyYXJ5KGNvbmZsaWN0ZWQpCmxpYnJhcnkodGlkeXZlcnNlKQpsaWJyYXJ5KEdHYWxseSkKCmRmIDwtIHJlYWRfY3N2KAogICJodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vbW9yaW1vdG9vc2FtdS9kYXRhX3Zpc3VhbGl6YXRpb24vbWFpbi9kYXRhL2RmXzQzNC5jc3YiCiAgKQoKZGYgfD4KICBnZ3BhaXJzKCkKYGBgCgojIyMgNC4zLjUg5Li75oiQ5YiG5YiG5p6Q44Gu44Kk44Oh44O844K4CgpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpsaWJyYXJ5KGNvbmZsaWN0ZWQpCmxpYnJhcnkodGlkeXZlcnNlKQpsaWJyYXJ5KHNrbWVhbnMpCmxpYnJhcnkoR0dhbGx5KQpsaWJyYXJ5KHBhdGNod29yaykKCmRmIDwtIHJlYWRfY3N2KAogICJodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vbW9yaW1vdG9vc2FtdS9kYXRhX3Zpc3VhbGl6YXRpb24vbWFpbi9kYXRhL2RmXzQzNC5jc3YiCiAgKQoKIyDkuLvmiJDliIbliIbmnpDvvIjmqJnmupbljJbvvIkKcGNhX3Jlc3VsdCA8LSBwcmNvbXAoZGYpIywgc2NhbGUuID0gVFJVRSkKcGNhX2ltcG9ydGFuY2UgPC0gYXMuZGF0YS5mcmFtZShzdW1tYXJ5KHBjYV9yZXN1bHQpJGltcG9ydGFuY2VbMixdKSB8PgogIHJvd25hbWVzX3RvX2NvbHVtbigpCm5hbWVzKHBjYV9pbXBvcnRhbmNlKSA8LSBjKCJwYyIsICJ2YWx1ZSIpCgojIFNwZWN0cmFsIENsdXN0ZXJpbmcKY2x1c3RlcnNfcGNhIDwtIHNrbWVhbnMocGNhX3Jlc3VsdCR4WywgMToyXSwgMykKCnAxIDwtIGRhdGEuZnJhbWUoCiAgcGMxID0gcGNhX3Jlc3VsdCR4WywgMV0sCiAgcGMyID0gcGNhX3Jlc3VsdCR4WywgMl0sCiAgY2xhc3MgPSBmYWN0b3IoY2x1c3RlcnNfcGNhJGNsdXN0ZXIpCiAgICApIHw+CiAgZ2dwbG90KGFlcyh4ID0gcGMxLCB5ID0gcGMyLCBjb2xvciA9IGNsYXNzKSkgKwogIGdlb21fcG9pbnQoKSArCiAgbGFicyh0aXRsZSA9ICLkuozjgaTjga7kuLvmiJDliIbjgafopovjgosiKSsKICB0aGVtZShsZWdlbmQucG9zaXRpb249Im5vbmUiLCBhc3BlY3QucmF0aW8gPSAxKQoKcDIgPC0gcGNhX2ltcG9ydGFuY2UgfD4KICBnZ3Bsb3QoYWVzKHggPSByZW9yZGVyKHBjLCBkZXNjKHZhbHVlKSksIHkgPSB2YWx1ZSkpICsKICBnZW9tX2NvbCgpKwogIGxhYnModGl0bGUgPSAi5ZCE5Li75oiQ5YiG44Gu44OH44O844K/5oKm5piO5YqbIiwgeCA9ICIiLCB5ID0gIiIpKwogIHRoZW1lKGFzcGVjdC5yYXRpbyA9IDEpCgpwMSArIHAyCmBgYAoKIyMjIDQuMy42IOeUu+WDj+ODh+ODvOOCv+OBruasoeWFg+WJiua4mwoKYGBge3IgY2x1c3Rlcl9tZXRob2RzLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpsaWJyYXJ5KGNvbmZsaWN0ZWQpCmxpYnJhcnkodGlkeXZlcnNlKQpsaWJyYXJ5KE1BU1MpICMgTURTKHNhbW1vbikKbGlicmFyeShSdHNuZSkgIyB0LVNORQpsaWJyYXJ5KHVtYXApICMgVU1BUAoKZGYgPC0gcmVhZF9jc3YoCiAgImh0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9tb3JpbW90b29zYW11L2RhdGFfdmlzdWFsaXphdGlvbi9tYWluL2RhdGEvZGlnaXRzLmNzdiIKICApCgpkZl9jbGVhbmVkIDwtIGRmIHw+CiAgZHBseXI6OnNlbGVjdCghdGFyZ2V0KQoKIyDlkITmiYvms5Xjgacy5qyh5YWD44Gr5Zyn57iuCiMg5Li75oiQ5YiG5YiG5p6QClhfcGNhIDwtIHByY29tcChkZl9jbGVhbmVkKSR4WywgMToyXQoKIyB0LVNORQpzZXQuc2VlZCg0MikKWF90c25lIDwtIFJ0c25lKGRmX2NsZWFuZWQsIG51bV90aHJlYWRzID0gMikkWQoKIyBNRFMKWF9tZHMgPC0gZGZfY2xlYW5lZCB8PgogIGRpc3QoKSB8PgogIHNhbW1vbih0cmFjZSA9IEZBTFNFKSB8PgogIHBsdWNrKCJwb2ludHMiKQoKIyBVTUFQCnNldC5zZWVkKDQyKQpYX3VtYXAgPC0gdW1hcChkZl9jbGVhbmVkKSRsYXlvdXQKCiMgSy1tZWFucwpzZXQuc2VlZCg0MikKY2x1c3RlcnNfcGNhIDwtIGttZWFucyhYX3BjYSwgMTApJGNsdXN0ZXIgICMgUENBCnNldC5zZWVkKDQyKQpjbHVzdGVyc19tZHMgPC0ga21lYW5zKFhfbWRzLCAxMCkkY2x1c3RlciAjTURTCnNldC5zZWVkKDQyKQpjbHVzdGVyc190c25lIDwtIGttZWFucyhYX3RzbmUsIDEwKSRjbHVzdGVyICMgdC1TTkUKc2V0LnNlZWQoNDIpCmNsdXN0ZXJzX3VtYXAgPC0ga21lYW5zKFhfdW1hcCwgMTApJGNsdXN0ZXIgICMgVU1BUAoKIyDntZDmnpzjgpLjg4fjg7zjgr/jg5Xjg6zjg7zjg6Djgavjgb7jgajjgoHjgosKZGZfYmFzZSA8LSBkYXRhLmZyYW1lKAogICAgeCA9IGMoWF9wY2FbLCAxXSwgWF9tZHNbLCAxXSwgWF90c25lWywgMV0sIFhfdW1hcFssIDFdKSwKICAgIHkgPSBjKFhfcGNhWywgMl0sIFhfbWRzWywgMl0sIFhfdHNuZVssIDJdLCBYX3VtYXBbLCAyXSkKICAgICkKCm4gPC0gMTc5NwoKZGltcmVkIDwtIGJpbmRfcm93cygKICBkZl9iYXNlIHw+CiAgICBtdXRhdGUoCiAgICAgIGxhYmVsID0gcmVwKGRmJHRhcmdldCwgNCksCiAgICAgIG1ldGhvZCA9IGMocmVwKCJwY2FfbGFiZWwiLCBuKSwgcmVwKCJtZHNfbGFiZWwiLCBuKSxyZXAoInRzbmVfbGFiZWwiLCBuKSxyZXAoInVtYXBfbGFiZWwiLCBuKSkKICAgICAgKSwKICBkZl9iYXNlIHw+CiAgICBtdXRhdGUoCiAgICAgIGxhYmVsID0gYyhjbHVzdGVyc19wY2EsIGNsdXN0ZXJzX21kcywgY2x1c3RlcnNfdHNuZSwgY2x1c3RlcnNfdW1hcCksCiAgICAgIG1ldGhvZCA9IGMocmVwKCJwY2Ffa21lYW5zIiwgbiksIHJlcCgibWRzX2ttZWFucyIsIG4pLHJlcCgidHNuZV9rbWVhbnMiLCBuKSxyZXAoInVtYXBfa21lYW5zIiwgbikpCiAgICAgICkKKSB8PgogIG11dGF0ZSgKICAgIG1ldGhvZCA9IGZhY3RvcigKICAgICAgbWV0aG9kLAogICAgICBsZXZlbHMgPSBjKCJwY2Ffa21lYW5zIiwgInBjYV9sYWJlbCIsICJtZHNfa21lYW5zIiwgIm1kc19sYWJlbCIsCiAgICAgICAgICAgICAgICAgInRzbmVfa21lYW5zIiwgICJ0c25lX2xhYmVsIiwgInVtYXBfa21lYW5zIiwgInVtYXBfbGFiZWwiKSksCiAgICBsYWJlbCA9IGZhY3RvcihsYWJlbCkKICAgICkKCiMg44Kv44Op44K544K/44Oq44Oz44Kw57WQ5p6c44Go5q2j6Kej44Op44OZ44Or44Gu5o+P55S7CmRpbXJlZCB8PgogIGdncGxvdChhZXMoeCA9IHgsIHkgPSB5LCBjb2xvciA9IGxhYmVsKSkgKyAKICBnZW9tX3BvaW50KCkgKyAKICBsYWJzKHRpdGxlPSLmp5jjgIXjgarmrKHlhYPlnKfnuK7mlrnms5UiKSArCiAgdGhlbWUoCiAgICBheGlzLnRpY2tzID0gZWxlbWVudF9ibGFuaygpLCAgIyB0aWNr44Gu57ea44KS5raI44GZCiAgICBheGlzLnRleHQgPSBlbGVtZW50X2JsYW5rKCksICAgIyB0aWNr44Gu5pWw5a2X44KS5raI44GZCiAgICBheGlzLnRpdGxlID0gZWxlbWVudF9ibGFuaygpLCAgIyDou7jjga7jg6njg5njg6vjgpLmtojjgZkKICAgIGF4aXMubGluZSA9IGVsZW1lbnRfYmxhbmsoKSwgICAjIOi7uOOBrue3muOCkua2iOOBmQogICAgbGVnZW5kLnBvc2l0aW9uPSJub25lIiwKICAgIGFzcGVjdC5yYXRpbyA9IDEKICApICsKICBmYWNldF93cmFwKHZhcnMobWV0aG9kKSwgbnJvdyA9IDIsIHNjYWxlcyA9ICJmcmVlIikKYGBgCgrnrKw056ug44Gv44GT44GT44G+44Gn44CCCg==