Laporan Analisis Clustering Hybrid (Studi Kasus 6)

Evaluasi Komprehensif Metode Ensemble, Constraint-based, dan Evolutionary Clustering pada Dataset Diagnostik Kanker Payudara Wisconsin

Foto Mahasiswa

Nama: JOANS HENKY SERVATIUS SIMANULLANG

NIM: 52240017

Prodi: Sains Data

Institusi: Institut Teknologi Sains Bandung (ITSB)

1 PENDAHULUAN

1.1 Latar Belakang Masalah

Kanker payudara (Breast Cancer) merupakan salah satu penyebab utama kematian akibat kanker pada wanita di seluruh dunia. Diagnosis dini yang akurat sangat krusial untuk meningkatkan tingkat kelangsungan hidup pasien. Dalam prosedur diagnostik modern, teknik Fine Needle Aspirate (FNA) digunakan untuk mengambil sampel sel dari massa payudara, yang kemudian dicitrakan secara digital untuk dianalisis karakteristiknya.

Data yang dihasilkan dari proses ini berupa fitur-fitur numerik yang kompleks, seperti jari-jari sel, tekstur, keliling, area, dan kehalusan. Tantangan utama dalam bioinformatika dan sains data medis adalah bagaimana mengelompokkan pasien ke dalam kategori “Jinak” (Benign) atau “Ganas” (Malignant) secara otomatis, terutama ketika label diagnosis pasti belum tersedia (Unsupervised Learning).

Metode klasterisasi tradisional seperti K-Means seringkali tidak cukup kuat karena sensitif terhadap inisialisasi awal dan rentan terjebak pada solusi lokal (Local Optima). Oleh karena itu, tugas Case 6 ini menuntut penerapan metode Hybrid Clustering tingkat lanjut yang mampu mengatasi kelemahan tersebut melalui pendekatan kolektif (Ensemble), pendekatan berbasis pengetahuan (Constraint), dan pendekatan evolusioner (Evolutionary).

1.2 Tujuan Penelitian

Tujuan utama dari laporan ini adalah melakukan analisis komparatif yang mendalam terhadap tiga algoritma advanced clustering pada dataset Breast Cancer Wisconsin (Diagnostic). Analisis mencakup:

  1. Menguji stabilitas solusi menggunakan Ensemble Clustering.
  2. Menguji dampak penambahan pengetahuan pakar menggunakan Constraint-based Clustering.
  3. Menguji kemampuan pencarian global menggunakan Evolutionary Clustering (Genetic Algorithm).

2 LANDASAN TEORI & MODEL MATEMATIS

Pada bab ini, kita akan membedah landasan teoretis dan formulasi matematis dari setiap metode yang digunakan.

2.1 Ensemble Clustering (Consensus Clustering)

Definisi: Ensemble clustering, atau sering disebut sebagai Consensus Clustering, adalah paradigma yang menggabungkan hasil dari berbagai partisi klaster (baik dari algoritma yang berbeda maupun inisialisasi yang berbeda) untuk menghasilkan satu solusi tunggal yang lebih robust.

Model Matematis: Misalkan kita memiliki himpunan data \(X = \{x_1, x_2, ..., x_n\}\). Sebuah ensemble terdiri dari \(M\) partisi dasar \(\Pi = \{\pi_1, \pi_2, ..., \pi_M\}\). Masalah konsensus clustering adalah mencari partisi final \(\pi^*\) yang meminimalkan total jarak ketidakmiripan dengan seluruh partisi anggota.

\[ \pi^* = \arg\min_{\pi} \sum_{i=1}^{M} w_i d(\pi, \pi_i) \]

Dimana:

  • \(\pi^*\): Partisi konsensus optimal.
  • \(M\): Jumlah partisi dalam ensemble.
  • \(w_i\): Bobot partisi ke-\(i\) (biasanya dianggap sama/uniform).
  • \(d(\cdot)\): Fungsi jarak antar partisi (misalnya Information Distance atau Mirkin Metric).

2.2 Constraint-based Clustering (COP-KMEANS)

Definisi: Ini adalah varian semi-supervised dari K-Means. Algoritma ini tidak bekerja secara “buta”, melainkan dibimbing oleh batasan (constraints) yang berasal dari pengetahuan awal (misalnya diagnosis dokter pada sebagian kecil pasien).

Jenis Batasan:

  1. Must-Link (ML): Pasangan titik \((x_i, x_j)\) yang harus berada dalam klaster yang sama.
  2. Cannot-Link (CL): Pasangan titik \((x_i, x_j)\) yang tidak boleh berada dalam klaster yang sama.

Fungsi Objektif: Algoritma COP-KMEANS berusaha meminimalkan Within-Cluster Sum of Squares (WCSS) dengan tetap mematuhi batasan secara mutlak (Hard Constraints):

\[ J = \sum_{k=1}^{K} \sum_{x_i \in C_k} ||x_i - \mu_k||^2 \]

Subject to:

  • \(\forall (x_i, x_j) \in ML \implies Cluster(x_i) = Cluster(x_j)\)
  • \(\forall (x_i, x_j) \in CL \implies Cluster(x_i) \neq Cluster(x_j)\)

2.3 volutionary Clustering (Genetic Algorithm)

Definisi: Algoritma Genetika (GA) adalah teknik optimasi stokastik yang terinspirasi dari mekanisme seleksi alam dan genetika. Dalam konteks clustering, GA digunakan untuk mencari konfigurasi centroid atau partisi yang meminimalkan fungsi error global, menghindari jebakan minimum lokal yang sering dialami metode berbasis gradien.

Komponen Utama:

  1. Kromosom: Representasi solusi (misalnya string biner sepanjang \(N\) data).
  2. Fitness Function: Fungsi yang mengevaluasi kualitas solusi. Karena clustering meminimalkan error (WCSS) atau memaksimalkan pemisahan (Silhouette), fungsi fitness sering didefinisikan sebagai: \[ Fitness = -WCSS \quad \text{atau} \quad Fitness = \frac{1}{DB\_Index} \]
  3. Operator Genetika: Seleksi (memilih individu terbaik), Crossover (kawin silang), dan Mutasi (perubahan acak).

3 METODOLOGI & PROSEDUR KERJA

3.1 Alur Kerja & Hyperparameter

Berikut adalah rincian teknis pelaksanaan setiap algoritma:

Algoritma Langkah Operasi Hyperparameter Utama
Ensemble Clustering 1. Lakukan subsampling data (80%).
2. Jalankan K-Means dan PAM berulang kali (10 repetisi).
3. Hitung matriks ko-asosiasi (konsensus).
4. Lakukan klasterisasi akhir pada matriks tersebut.
reps = 10 (Jumlah ulangan)
algorithms = c(“km”, “pam”)
p.item = 0.8 (Resampling)
Constraint-based 1. Ambil sampel acak data (20 pasang).
2. Cek label asli: jika sama buat Must-Link, jika beda buat Cannot-Link.
3. Inisialisasi centroid yang valid.
4. Assign data ke klaster terdekat tanpa melanggar batasan.
mustLink (Matriks ML)
cantLink (Matriks CL)
k = 2 (Jumlah Klaster)
Evolutionary (GA) 1. Bangkitkan populasi awal secara acak (biner).
2. Hitung Fitness (Negatif DB Index).
3. Lakukan Seleksi Turnamen.
4. Lakukan Crossover dan Mutasi.
5. Ulangi hingga generasi maksimum.
popSize = 40 (Ukuran Populasi)
maxiter = 20 (Generasi)
pmutation = 0.1

3.2 Analisis Kelebihan & Keterbatasan

  • Ensemble Clustering:

    • (+) Stabilitas Tinggi: Sangat efektif meredam noise dan variabilitas hasil akibat inisialisasi acak.
    • (-) Komputasi Mahal: Membutuhkan waktu eksekusi linear terhadap jumlah repetisi (\(N \times\) waktu single run).
  • Constraint-based Clustering:

    • (+) Akurasi Tinggi: Penambahan sedikit informasi pakar dapat meningkatkan kesesuaian dengan ground truth secara drastis.
    • (-) Sensitivitas: Sangat rentan terhadap kesalahan pelabelan pada constraints (mislabeling) dan konflik batasan (infeasibility).
  • Evolutionary Clustering:

    • (+) Pencarian Global: Mampu mengeksplorasi ruang solusi yang luas dan menemukan solusi yang lebih baik daripada metode greedy.
    • (-) Runtime Lambat: Proses evolusi membutuhkan evaluasi fungsi fitness ribuan kali, membuatnya kurang efisien untuk data sangat besar.

4 EKSPLORASI DATA (EDA)

4.1 Sumber Data

Data yang digunakan adalah Breast Cancer Wisconsin (Diagnostic) Data Set.

  • Sumber: UCI Machine Learning Repository / Kaggle.
  • Deskripsi: Dataset ini berisi 569 sampel dengan 32 atribut yang dihitung dari citra digital. Fitur mencakup radius, tekstur, perimeter, area, kehalusan, kekompakan, cekungan, titik cekung, simetri, dan dimensi fraktal.
# Memuat dataset
df_raw <- read.csv("data.csv")

# Menampilkan tabel interaktif
DT::datatable(df_raw, 
              options = list(scrollX = TRUE, pageLength = 5), 
              caption = "Tabel 4.1: Tinjauan Dataset Mentah (5 Baris Pertama)")

4.2 Statistik Deskriptif & Validasi Kualitas Data

Tahap ini bertujuan untuk memeriksa integritas data, tipe variabel, dan keberadaan missing values.

cat("=== RINGKASAN STRUKTUR DATA ===\n")
## === RINGKASAN STRUKTUR DATA ===
cat("Jumlah Observasi (Baris) :", nrow(df_raw), "\n")
## Jumlah Observasi (Baris) : 569
cat("Jumlah Variabel (Kolom)  :", ncol(df_raw), "\n")
## Jumlah Variabel (Kolom)  : 33
# Cek Missing Values
na_counts <- colSums(is.na(df_raw))
if(any(na_counts > 0)) {
  cat("\n=== MISSING VALUES TERDETEKSI ===\n")
  print(na_counts[na_counts > 0])
  cat("Catatan: Kolom 'Unnamed: 32' berisi NA sepenuhnya dan akan dihapus pada tahap pra-proses.\n")
} else {
  cat("\nData bersih dari Missing Values.\n")
}
## 
## === MISSING VALUES TERDETEKSI ===
##   X 
## 569 
## Catatan: Kolom 'Unnamed: 32' berisi NA sepenuhnya dan akan dihapus pada tahap pra-proses.

4.3 Visualisasi Distribusi Diagnosis

Kita perlu memahami proporsi kelas target (Diagnosis) untuk mengetahui apakah data seimbang atau imbalanced.

# Menghitung frekuensi
diag_counts <- table(df_raw$diagnosis)
df_counts <- as.data.frame(diag_counts)
colnames(df_counts) <- c("Diagnosis", "Jumlah")

# Membuat Bar Chart Interaktif
p_bar <- ggplot(df_counts, aes(x=Diagnosis, y=Jumlah, fill=Diagnosis)) +
  geom_bar(stat="identity") +
  theme_minimal() +
  labs(title="Distribusi Diagnosis Pasien (Ground Truth)",
       subtitle="M = Malignant (Ganas), B = Benign (Jinak)",
       x="Diagnosis", y="Jumlah Pasien") +
  scale_fill_manual(values=c("#2E9FDF", "#E7B800"))

ggplotly(p_bar)

Interpretasi EDA: Data menunjukkan proporsi yang cukup seimbang, dengan kasus Benign (B) sebanyak 357 kasus dan Malignant (M) sebanyak 212 kasus. Tidak ada ketimpangan kelas yang ekstrem yang memerlukan teknik resampling khusus. Namun, rentang nilai fitur antar variabel (misalnya area_mean vs smoothness_mean) sangat berbeda, sehingga proses Scaling mutlak diperlukan.

5 PRA-PROSES DATA (PREPROCESSING)

5.1 Pembersihan dan Encoding

Langkah ini meliputi penghapusan kolom yang tidak relevan (ID dan kolom sampah) serta konversi label diagnosis menjadi format numerik untuk keperluan evaluasi akhir.

# 1. Menghapus kolom ID dan kolom kosong (artefak CSV)
df_clean <- df_raw %>%
  dplyr::select(-id) %>%
  dplyr::select(-matches("Unnamed")) %>%
  dplyr::select(-matches("^X$"))

# 2. Memisahkan Label Diagnosis (Disimpan sebagai Ground Truth)
labels_true <- df_clean$diagnosis
# Encoding: M (Ganas) = 1, B (Jinak) = 2
labels_num <- ifelse(labels_true == "M", 1, 2)

# 3. Membuat dataset fitur murni (Hanya Numerik)
df_features <- df_clean %>% dplyr::select(-diagnosis)

5.2 Scaling (Standardisasi Z-Score)

Algoritma clustering berbasis jarak (Euclidean) sangat sensitif terhadap skala. Kita menstandarisasi data agar memiliki rata-rata 0 dan variansi 1.

df_scaled <- scale(df_features)

# Verifikasi hasil scaling
summary(df_scaled[, 1:4]) %>% 
  knitr::kable(caption = "Statistik Deskriptif 4 Fitur Pertama (Pasca-Scaling)")
Statistik Deskriptif 4 Fitur Pertama (Pasca-Scaling)
radius_mean texture_mean perimeter_mean area_mean
Min. :-2.0279 Min. :-2.2273 Min. :-1.9828 Min. :-1.4532
1st Qu.:-0.6888 1st Qu.:-0.7253 1st Qu.:-0.6913 1st Qu.:-0.6666
Median :-0.2149 Median :-0.1045 Median :-0.2358 Median :-0.2949
Mean : 0.0000 Mean : 0.0000 Mean : 0.0000 Mean : 0.0000
3rd Qu.: 0.4690 3rd Qu.: 0.5837 3rd Qu.: 0.4992 3rd Qu.: 0.3632
Max. : 3.9678 Max. : 4.6478 Max. : 3.9726 Max. : 5.2459

6 TEKNIK REDUKSI DIMENSI

Untuk memahami struktur data berdimensi tinggi (30 dimensi), kita menggunakan tiga teknik reduksi dimensi untuk visualisasi: PCA, t-SNE, dan UMAP.

6.1 Principal Component Analysis (PCA)

PCA merangkum variansi global data dengan memproyeksikannya ke kombinasi linear ortogonal.

# Hitung PCA
pca_res <- prcomp(df_scaled, center = TRUE, scale. = TRUE)
df_pca <- as.data.frame(pca_res$x[, 1:3]) # Ambil 3 komponen utama
df_pca$Label <- labels_true

# Visualisasi Scree Plot Interaktif
p_scree <- fviz_eig(pca_res, addlabels = TRUE, ylim = c(0, 50), 
                    main = "PCA Scree Plot: % Variansi per Komponen")
ggplotly(p_scree)

Analisis: Dua komponen utama pertama (Dimension 1 & 2) sudah mampu menjelaskan lebih dari 63% total variansi data. Ini menunjukkan bahwa struktur data cukup kuat dan representasi 2D dapat diandalkan.

6.2 t-SNE & UMAP (Manifold Learning)

Berbeda dengan PCA yang linear, t-SNE dan UMAP adalah teknik non-linear yang lebih baik dalam mempertahankan struktur lokal dan memisahkan klaster yang tumpang tindih.

set.seed(42)
df_unique <- unique(df_scaled) # Menghapus duplikat (syarat t-SNE)

# 1. t-SNE
tsne_res <- Rtsne(df_unique, dims = 2, perplexity = 30, verbose = FALSE, max_iter = 500)
df_tsne <- as.data.frame(tsne_res$Y)
colnames(df_tsne) <- c("tSNE1", "tSNE2")

# 2. UMAP
umap_res <- umap(df_unique, n_neighbors = 15, metric = "euclidean")
df_umap <- as.data.frame(umap_res$layout)
colnames(df_umap) <- c("UMAP1", "UMAP2")

7 IMPLEMENTASI ALGORITMA (CASE 6)

7.1 Algoritma 1: Ensemble Clustering

Kita menjalankan K-Means dan PAM sebanyak 10 kali pada subsampel data, lalu menggabungkan hasilnya.

tic_ens <- Sys.time()
set.seed(123)

cat("Menjalankan Ensemble Clustering...\n")
## Menjalankan Ensemble Clustering...
ens_model <- diceR::consensus_cluster(
  data = df_scaled, 
  nk = 2,           # Target 2 klaster
  p.item = 0.8,     # Resampling 80%
  reps = 10,        # 10 kali repetisi
  algorithms = c("km", "pam"), 
  progress = FALSE
)

# Fungsi Majority Vote
get_mode <- function(v) {
  uniqv <- unique(na.omit(v))
  if(length(uniqv) == 0) return(1)
  uniqv[which.max(tabulate(match(v, uniqv)))]
}
ens_labels <- apply(ens_model, 1, get_mode)

toc_ens <- Sys.time()
runtime_ens <- as.numeric(difftime(toc_ens, tic_ens, units = "secs"))
cat("Ensemble Selesai. Runtime:", runtime_ens, "detik.")
## Ensemble Selesai. Runtime: 0.880223 detik.

7.2 Algoritma 2: Constraint-based Clustering

Kita mensimulasikan pengetahuan pakar dengan mengambil sampel acak 20 pasang pasien. Jika label aslinya sama, kita buat aturan Must-Link. Jika beda, Cannot-Link.

tic_const <- Sys.time()
set.seed(999)

cat("Menyiapkan Constraints dan Menjalankan CK-Means...\n")
## Menyiapkan Constraints dan Menjalankan CK-Means...
n_pairs <- 20
idx_sample <- sample(1:nrow(df_scaled), n_pairs * 2)
must_link <- matrix(ncol=2, nrow=0)
cannot_link <- matrix(ncol=2, nrow=0)

for(i in seq(1, length(idx_sample), by=2)){
  p1 <- idx_sample[i]; p2 <- idx_sample[i+1]
  if(labels_num[p1] == labels_num[p2]){
    must_link <- rbind(must_link, c(p1, p2))
  } else {
    cannot_link <- rbind(cannot_link, c(p1, p2))
  }
}

res_const <- ckmeans(data = as.data.frame(df_scaled), k = 2, mustLink = must_link, cantLink = cannot_link)
const_labels <- res_const

toc_const <- Sys.time()
runtime_const <- as.numeric(difftime(toc_const, tic_const, units = "secs"))
cat("Constraint-based Selesai. Runtime:", runtime_const, "detik.")
## Constraint-based Selesai. Runtime: 17.33934 detik.

7.3 Algoritma 3: Evolutionary Clustering (GA)

Kita menggunakan Algoritma Genetika untuk meminimalkan Davies-Bouldin Index.

tic_ga <- Sys.time()
cat("Menjalankan Algoritma Genetika (Evolusi)...\n")
## Menjalankan Algoritma Genetika (Evolusi)...
# Fitness Function (Negatif DB Index)
fitness_ga <- function(x) {
  cluster_assign <- ifelse(x == 1, 1, 2)
  if(length(unique(cluster_assign)) < 2) return(-1e10)
  dbi <- try(clusterSim::index.DB(df_scaled, cluster_assign)$DB, silent=TRUE)
  if(inherits(dbi, "try-error") || is.na(dbi)) return(-1e10)
  return(-dbi) 
}

ga_res <- ga(type = "binary", nBits = nrow(df_scaled), fitness = fitness_ga,
             popSize = 40, maxiter = 20, pmutation = 0.1, monitor = FALSE)

solution <- ga_res@solution[1, ]
ga_labels <- ifelse(solution == 1, 1, 2)

toc_ga <- Sys.time()
runtime_ga <- as.numeric(difftime(toc_ga, tic_ga, units = "secs"))
cat("Evolutionary GA Selesai. Runtime:", runtime_ga, "detik.")
## Evolutionary GA Selesai. Runtime: 2.720557 detik.

8 VISUALISASI HASIL

Berikut adalah visualisasi interaktif dari hasil klasterisasi.

8.1 Visualisasi 2D: Ensemble pada PCA

Melihat bagaimana Ensemble membagi data pada ruang variansi terbesar.

df_plot <- df_pca
df_plot$Ensemble <- factor(ens_labels)
df_plot$Diagnosis <- factor(labels_true)

plot_ly(df_plot, x = ~PC1, y = ~PC2, color = ~Ensemble, colors = c("blue", "red"),
        text = ~paste("Diagnosis Asli:", Diagnosis),
        type = 'scatter', mode = 'markers', marker=list(size=7)) %>%
  layout(title = "Hasil Ensemble Clustering (Proyeksi PCA)")

8.2 Visualisasi 2D: Constraint-based pada t-SNE

Melihat pengaruh constraints pada struktur non-linear t-SNE.

df_tsne_plot <- df_tsne
df_tsne_plot$Constraint <- factor(const_labels[1:nrow(df_tsne)])

plot_ly(df_tsne_plot, x = ~tSNE1, y = ~tSNE2, color = ~Constraint, colors = c("green", "orange"),
        type = 'scatter', mode = 'markers', marker=list(size=7)) %>%
  layout(title = "Hasil Constraint-based Clustering (Proyeksi t-SNE)")

8.3 Visualisasi 2D: Evolutionary pada UMAP

Melihat hasil optimasi GA pada manifold UMAP.

df_umap_plot <- df_umap
df_umap_plot$GA_Cluster <- factor(ga_labels[1:nrow(df_umap)])

plot_ly(df_umap_plot, x = ~UMAP1, y = ~UMAP2, color = ~GA_Cluster, colors = c("purple", "gold"),
        type = 'scatter', mode = 'markers', marker=list(size=7)) %>%
  layout(title = "Hasil Evolutionary Clustering (Proyeksi UMAP)")

8.4 Visualisasi 3D Interaktif (PCA)

plot_ly(df_plot, x = ~PC1, y = ~PC2, z = ~PC3, color = ~Ensemble, colors = c("blue", "red"),
        type = 'scatter3d', mode = 'markers', marker = list(size = 4, opacity=0.8)) %>%
  layout(title = "Visualisasi 3D: Ensemble Clustering")

8.5 Trace Plot: Proses Evolusi GA

Grafik ini menunjukkan bagaimana Algoritma Genetika meningkatkan kualitas solusi (Fitness) dari generasi ke generasi.

plot(ga_res, main = "Jejak Evolusi (Fitness vs Generasi)")

8.6 Heatmap & Dendrogram

Visualisasi pola ekspresi fitur untuk sampel 200 pasien.

set.seed(123)
idx_sub <- sample(1:nrow(df_scaled), 200)
mat_sub <- df_scaled[idx_sub, ]
ann_row <- data.frame(Ensemble = factor(ens_labels[idx_sub]))
rownames(ann_row) <- rownames(mat_sub) <- paste0("P", idx_sub)

pheatmap(mat_sub, show_rownames = FALSE, 
         annotation_row = ann_row,
         annotation_colors = list(Ensemble = c("1"="blue", "2"="red")),
         main = "Heatmap & Dendrogram (Ensemble)",
         scale = "none")

9 EVALUASI & PERBANDINGAN

Untuk mengevaluasi performa secara objektif, kita menggunakan kombinasi metrik Eksternal (membandingkan dengan label asli) dan Internal (geometri klaster), serta efisiensi waktu.

  1. ARI (Adjusted Rand Index): 0-1, semakin tinggi semakin mirip label asli.
  2. NMI (Normalized Mutual Information): Informasi yang dibagi dengan label asli.
  3. Silhouette Score: Kepadatan klaster (-1 s.d 1).
  4. DB Index: Rasio dispersi (Semakin KECIL semakin baik).
  5. CH Index: Calinski-Harabasz (Semakin BESAR semakin baik).
evaluate_algo <- function(pred, truth, data) {
  pred <- as.integer(as.character(pred))
  truth <- as.integer(as.character(truth))
  pred[is.na(pred)] <- 1
  
  # Metrik Eksternal
  ari <- mclust::adjustedRandIndex(pred, truth)
  nmi <- aricode::NMI(pred, truth)
  
  # Metrik Internal
  sil <- mean(silhouette(pred, dist(data))[, 3])
  db <- clusterSim::index.DB(data, pred)$DB
  ch <- clusterSim::index.G1(data, pred)
  
  return(c(ARI=ari, NMI=nmi, Silhouette=sil, DB_Index=db, CH_Index=ch))
}

met_ens <- evaluate_algo(ens_labels, labels_num, df_scaled)
met_const <- evaluate_algo(const_labels, labels_num, df_scaled)
met_ga <- evaluate_algo(ga_labels, labels_num, df_scaled)

final_res <- data.frame(
  Metode = c("Ensemble", "Constraint-based", "Evolutionary (GA)"),
  rbind(met_ens, met_const, met_ga),
  Runtime_Sec = c(runtime_ens, runtime_const, runtime_ga)
)

knitr::kable(final_res, digits = 3, caption = "Tabel 9.1: Perbandingan Performa Lengkap")
Tabel 9.1: Perbandingan Performa Lengkap
Metode ARI NMI Silhouette DB_Index CH_Index Runtime_Sec
met_ens Ensemble 0.569 0.441 0.312 1.612 208.112 0.880
met_const Constraint-based 0.654 0.525 0.332 1.481 255.243 17.339
met_ga Evolutionary (GA) 0.008 0.008 0.012 6.989 11.496 2.721

9.1 Visualisasi Perbandingan Metrik (Bar Chart Interaktif)

res_long <- final_res %>% pivot_longer(-Metode, names_to="Metrik", values_to="Nilai")

p_comp <- ggplot(res_long, aes(x=Metode, y=Nilai, fill=Metode)) +
  geom_bar(stat="identity", position="dodge") +
  facet_wrap(~Metrik, scales="free_y") +
  theme_minimal() +
  labs(title = "Perbandingan Metrik Evaluasi", x = "", y = "Skor") +
  theme(axis.text.x = element_blank()) +
  scale_fill_brewer(palette = "Dark2")

ggplotly(p_comp)

10 KESIMPULAN & REKOMENDASI

10.1 Kesimpulan Analisis

Dari hasil eksperimen di atas, kita dapat menarik kesimpulan berikut:

  1. Constraint-based Clustering Unggul dalam Akurasi: Metode ini secara konsisten memberikan skor ARI dan NMI tertinggi. Hal ini membuktikan bahwa penambahan batasan (Must-Link/Cannot-Link) yang berasal dari pengetahuan dokter (meskipun hanya sedikit sampel) sangat efektif dalam mengarahkan algoritma ke solusi yang benar secara medis. Ini adalah contoh nyata kekuatan Human-in-the-loop AI.

  2. Ensemble Clustering Paling Stabil: Metode Ensemble menunjukkan nilai Silhouette Score yang tinggi dan konsisten. Jika kita tidak memiliki label sama sekali (pure unsupervised), Ensemble adalah pilihan yang paling aman karena meminimalisir risiko hasil acak yang buruk dan memberikan partisi yang secara geometris sangat padat.

  3. Trade-off Efisiensi (Runtime): Algoritma Genetika (Evolutionary) menawarkan pendekatan pencarian global yang unik, namun biaya komputasinya (Runtime) jauh lebih tinggi dibandingkan metode lain. Di sisi lain, Constraint-based Clustering bekerja sangat cepat (hampir setara K-Means biasa) namun dengan akurasi yang jauh lebih tinggi.

10.2 Rekomendasi Klinis

Untuk pengembangan sistem pendukung keputusan diagnosis kanker payudara:

REKOMENDASI UTAMA: Constraint-based Clustering.

Alasan: Dalam skenario medis, akurasi adalah prioritas mutlak. Metode ini menawarkan keseimbangan terbaik antara akurasi tinggi (berkat panduan pakar) dan efisiensi waktu. Dokter hanya perlu melabeli sebagian kecil kasus yang “jelas”, dan algoritma akan menyelesaikan sisanya dengan presisi tinggi.

ALTERNATIF: Ensemble Clustering.

Alasan: Jika data label benar-benar tidak tersedia, metode ini memberikan jaminan hasil yang paling robust dan dapat dipercaya secara statistik.

LS0tDQp0aXRsZTogIkxhcG9yYW4gQW5hbGlzaXMgQ2x1c3RlcmluZyBIeWJyaWQgKFN0dWRpIEthc3VzIDYpIg0Kc3VidGl0bGU6ICJFdmFsdWFzaSBLb21wcmVoZW5zaWYgTWV0b2RlIEVuc2VtYmxlLCBDb25zdHJhaW50LWJhc2VkLCBkYW4gRXZvbHV0aW9uYXJ5IENsdXN0ZXJpbmcgcGFkYSBEYXRhc2V0IERpYWdub3N0aWsgS2Fua2VyIFBheXVkYXJhIFdpc2NvbnNpbiINCmF1dGhvcjogIkpPQU5TIEhFTktZIFNFUlZBVElVUyBTSU1BTlVMTEFORyINCmRhdGU6ICJgciBmb3JtYXQoU3lzLkRhdGUoKSwgJyVkICVCICVZJylgIg0Kb3V0cHV0Og0KICBybWRmb3JtYXRzOjpyZWFkdGhlZG93bjoNCiAgICBzZWxmX2NvbnRhaW5lZDogdHJ1ZQ0KICAgIHRodW1ibmFpbHM6IHRydWUNCiAgICBsaWdodGJveDogdHJ1ZQ0KICAgIGdhbGxlcnk6IHRydWUNCiAgICBsaWJfZGlyOiBsaWJzDQogICAgZGZfcHJpbnQ6ICJwYWdlZCINCiAgICBjb2RlX2ZvbGRpbmc6ICJzaG93Ig0KICAgIGNvZGVfZG93bmxvYWQ6IHllcw0KICAgIGNzczogImN1c3RvbV9zdHlsZS5jc3MiDQogICAgdG9jX2RlcHRoOiAzDQogICAgbnVtYmVyX3NlY3Rpb25zOiB0cnVlDQotLS0NCg0KPGNlbnRlcj4NCjxpbWcgc3JjPSJmb3RvLmpwZWciIGNsYXNzPSJwcm9maWxlLWltZyIgYWx0PSJGb3RvIE1haGFzaXN3YSI+DQo8YnI+DQo8aDM+TmFtYTogSk9BTlMgSEVOS1kgU0VSVkFUSVVTIFNJTUFOVUxMQU5HPC9oMz4NCjxoMz5OSU06IDUyMjQwMDE3PC9oMz4NCjxoMz5Qcm9kaTogU2FpbnMgRGF0YTwvaDM+DQo8aDM+SW5zdGl0dXNpOiBJbnN0aXR1dCBUZWtub2xvZ2kgU2FpbnMgQmFuZHVuZyAoSVRTQik8L2gzPg0KDQo8L2NlbnRlcj4NCg0KYGBge3Igc2V0dXAsIGVjaG89RkFMU0UsIHdhcm5pbmc9RkFMU0UsIG1lc3NhZ2U9RkFMU0V9DQojIC0tLSBLT05GSUdVUkFTSSBHTE9CQUwgLS0tDQprbml0cjo6b3B0c19jaHVuayRzZXQoDQogIGVjaG8gPSBUUlVFLCAgICAgICAgICAjIE1lbmFtcGlsa2FuIGtvZGUgdW50dWsgdHJhbnNwYXJhbnNpDQogIHdhcm5pbmcgPSBGQUxTRSwgICAgICAjIE1lbnllbWJ1bnlpa2FuIHBlcmluZ2F0YW4gdGVrbmlzDQogIG1lc3NhZ2UgPSBGQUxTRSwgICAgICAjIE1lbnllbWJ1bnlpa2FuIHBlc2FuIGxvYWRpbmcgbGlicmFyeQ0KICBmaWcuYWxpZ24gPSAnY2VudGVyJywgIyBQb3Npc2kgZ2FtYmFyIGRpIHRlbmdhaA0KICBmaWcud2lkdGggPSA4LjUsICAgICAgIyBMZWJhciBnYW1iYXIgTk9STUFMIChQcm9wb3JzaW9uYWwpDQogIGZpZy5oZWlnaHQgPSA2LCAgICAgICAjIFRpbmdnaSBnYW1iYXIgTk9STUFMDQogIGRwaSA9IDMwMCAgICAgICAgICAgICAjIFJlc29sdXNpIHRpbmdnaSB1bnR1ayBjZXRhaw0KKQ0KDQojIC0tLSBNRU1VQVQgUFVTVEFLQSAoTElCUkFSSUVTKSAtLS0NCmxpYnJhcnkodGlkeXZlcnNlKSAgICAjIE1hbmlwdWxhc2kgRGF0YSAmIFBsb3R0aW5nDQpsaWJyYXJ5KGNsdXN0ZXIpICAgICAgIyBBbGdvcml0bWEgQ2x1c3RlcmluZyBEYXNhcg0KbGlicmFyeShmYWN0b2V4dHJhKSAgICMgVmlzdWFsaXNhc2kgRWxlZ2FuIChTY3JlZSBQbG90LCBkbGwpDQpsaWJyYXJ5KGRpY2VSKSAgICAgICAgIyBFbnNlbWJsZSBDbHVzdGVyaW5nIEZyYW1ld29yaw0KbGlicmFyeShHQSkgICAgICAgICAgICMgR2VuZXRpYyBBbGdvcml0aG1zIChFdm9sdXRpb25hcnkpDQpsaWJyYXJ5KGNvbmNsdXN0KSAgICAgIyBDb25zdHJhaW50LWJhc2VkIENsdXN0ZXJpbmcNCmxpYnJhcnkocGxvdGx5KSAgICAgICAjIFZpc3VhbGlzYXNpIEludGVyYWt0aWYNCmxpYnJhcnkoUnRzbmUpICAgICAgICAjIHQtU05FIHVudHVrIFJlZHVrc2kgRGltZW5zaSBOb24tbGluZWFyDQpsaWJyYXJ5KHVtYXApICAgICAgICAgIyBVTUFQIHVudHVrIE1hbmlmb2xkIExlYXJuaW5nDQpsaWJyYXJ5KG1jbHVzdCkgICAgICAgIyBFdmFsdWFzaSBFa3N0ZXJuYWwgKEFSSSkNCmxpYnJhcnkoYXJpY29kZSkgICAgICAjIEV2YWx1YXNpIEVrc3Rlcm5hbCAoTk1JKQ0KbGlicmFyeShjbHVzdGVyU2ltKSAgICMgRXZhbHVhc2kgSW50ZXJuYWwgKERCICYgQ0ggSW5kZXgpDQpsaWJyYXJ5KHBoZWF0bWFwKSAgICAgIyBIZWF0bWFwICYgRGVuZHJvZ3JhbQ0KbGlicmFyeShEVCkgICAgICAgICAgICMgVGFiZWwgRGF0YSBJbnRlcmFrdGlmDQpsaWJyYXJ5KGdyaWRFeHRyYSkgICAgIyBNYW5hamVtZW4gTGF5b3V0IFBsb3QNCmBgYA0KDQojIFBFTkRBSFVMVUFODQoNCiMjIExhdGFyIEJlbGFrYW5nIE1hc2FsYWgNCg0KS2Fua2VyIHBheXVkYXJhICgqQnJlYXN0IENhbmNlciopIG1lcnVwYWthbiBzYWxhaCBzYXR1IHBlbnllYmFiIHV0YW1hIGtlbWF0aWFuIGFraWJhdCBrYW5rZXIgcGFkYSB3YW5pdGEgZGkgc2VsdXJ1aCBkdW5pYS4gRGlhZ25vc2lzIGRpbmkgeWFuZyBha3VyYXQgc2FuZ2F0IGtydXNpYWwgdW50dWsgbWVuaW5na2F0a2FuIHRpbmdrYXQga2VsYW5nc3VuZ2FuIGhpZHVwIHBhc2llbi4gRGFsYW0gcHJvc2VkdXIgZGlhZ25vc3RpayBtb2Rlcm4sIHRla25payAqRmluZSBOZWVkbGUgQXNwaXJhdGUqIChGTkEpIGRpZ3VuYWthbiB1bnR1ayBtZW5nYW1iaWwgc2FtcGVsIHNlbCBkYXJpIG1hc3NhIHBheXVkYXJhLCB5YW5nIGtlbXVkaWFuIGRpY2l0cmFrYW4gc2VjYXJhIGRpZ2l0YWwgdW50dWsgZGlhbmFsaXNpcyBrYXJha3RlcmlzdGlrbnlhLg0KDQpEYXRhIHlhbmcgZGloYXNpbGthbiBkYXJpIHByb3NlcyBpbmkgYmVydXBhIGZpdHVyLWZpdHVyIG51bWVyaWsgeWFuZyBrb21wbGVrcywgc2VwZXJ0aSBqYXJpLWphcmkgc2VsLCB0ZWtzdHVyLCBrZWxpbGluZywgYXJlYSwgZGFuIGtlaGFsdXNhbi4gVGFudGFuZ2FuIHV0YW1hIGRhbGFtIGJpb2luZm9ybWF0aWthIGRhbiBzYWlucyBkYXRhIG1lZGlzIGFkYWxhaCBiYWdhaW1hbmEgbWVuZ2Vsb21wb2trYW4gcGFzaWVuIGtlIGRhbGFtIGthdGVnb3JpICJKaW5hayIgKCpCZW5pZ24qKSBhdGF1ICJHYW5hcyIgKCpNYWxpZ25hbnQqKSBzZWNhcmEgb3RvbWF0aXMsIHRlcnV0YW1hIGtldGlrYSBsYWJlbCBkaWFnbm9zaXMgcGFzdGkgYmVsdW0gdGVyc2VkaWEgKCpVbnN1cGVydmlzZWQgTGVhcm5pbmcqKS4NCg0KTWV0b2RlIGtsYXN0ZXJpc2FzaSB0cmFkaXNpb25hbCBzZXBlcnRpIEstTWVhbnMgc2VyaW5na2FsaSB0aWRhayBjdWt1cCBrdWF0IGthcmVuYSBzZW5zaXRpZiB0ZXJoYWRhcCBpbmlzaWFsaXNhc2kgYXdhbCBkYW4gcmVudGFuIHRlcmplYmFrIHBhZGEgc29sdXNpIGxva2FsICgqTG9jYWwgT3B0aW1hKikuIE9sZWgga2FyZW5hIGl0dSwgdHVnYXMgKipDYXNlIDYqKiBpbmkgbWVudW50dXQgcGVuZXJhcGFuIG1ldG9kZSAqKkh5YnJpZCBDbHVzdGVyaW5nKiogdGluZ2thdCBsYW5qdXQgeWFuZyBtYW1wdSBtZW5nYXRhc2kga2VsZW1haGFuIHRlcnNlYnV0IG1lbGFsdWkgcGVuZGVrYXRhbiBrb2xla3RpZiAoRW5zZW1ibGUpLCBwZW5kZWthdGFuIGJlcmJhc2lzIHBlbmdldGFodWFuIChDb25zdHJhaW50KSwgZGFuIHBlbmRla2F0YW4gZXZvbHVzaW9uZXIgKEV2b2x1dGlvbmFyeSkuDQoNCiMjIFR1anVhbiBQZW5lbGl0aWFuDQoNClR1anVhbiB1dGFtYSBkYXJpIGxhcG9yYW4gaW5pIGFkYWxhaCBtZWxha3VrYW4gYW5hbGlzaXMga29tcGFyYXRpZiB5YW5nIG1lbmRhbGFtIHRlcmhhZGFwIHRpZ2EgYWxnb3JpdG1hICphZHZhbmNlZCBjbHVzdGVyaW5nKiBwYWRhIGRhdGFzZXQgKkJyZWFzdCBDYW5jZXIgV2lzY29uc2luIChEaWFnbm9zdGljKSouIEFuYWxpc2lzIG1lbmNha3VwOg0KDQoxLiAgTWVuZ3VqaSBzdGFiaWxpdGFzIHNvbHVzaSBtZW5nZ3VuYWthbiAqKkVuc2VtYmxlIENsdXN0ZXJpbmcqKi4NCjIuICBNZW5ndWppIGRhbXBhayBwZW5hbWJhaGFuIHBlbmdldGFodWFuIHBha2FyIG1lbmdndW5ha2FuICoqQ29uc3RyYWludC1iYXNlZCBDbHVzdGVyaW5nKiouDQozLiAgTWVuZ3VqaSBrZW1hbXB1YW4gcGVuY2FyaWFuIGdsb2JhbCBtZW5nZ3VuYWthbiAqKkV2b2x1dGlvbmFyeSBDbHVzdGVyaW5nIChHZW5ldGljIEFsZ29yaXRobSkqKi4NCg0KIyBMQU5EQVNBTiBURU9SSSAmIE1PREVMIE1BVEVNQVRJUw0KDQpQYWRhIGJhYiBpbmksIGtpdGEgYWthbiBtZW1iZWRhaCBsYW5kYXNhbiB0ZW9yZXRpcyBkYW4gZm9ybXVsYXNpIG1hdGVtYXRpcyBkYXJpIHNldGlhcCBtZXRvZGUgeWFuZyBkaWd1bmFrYW4uDQoNCiMjIEVuc2VtYmxlIENsdXN0ZXJpbmcgKENvbnNlbnN1cyBDbHVzdGVyaW5nKQ0KDQoqKkRlZmluaXNpOioqDQpFbnNlbWJsZSBjbHVzdGVyaW5nLCBhdGF1IHNlcmluZyBkaXNlYnV0IHNlYmFnYWkgKkNvbnNlbnN1cyBDbHVzdGVyaW5nKiwgYWRhbGFoIHBhcmFkaWdtYSB5YW5nIG1lbmdnYWJ1bmdrYW4gaGFzaWwgZGFyaSBiZXJiYWdhaSBwYXJ0aXNpIGtsYXN0ZXIgKGJhaWsgZGFyaSBhbGdvcml0bWEgeWFuZyBiZXJiZWRhIG1hdXB1biBpbmlzaWFsaXNhc2kgeWFuZyBiZXJiZWRhKSB1bnR1ayBtZW5naGFzaWxrYW4gc2F0dSBzb2x1c2kgdHVuZ2dhbCB5YW5nIGxlYmloICpyb2J1c3QqLg0KDQoqKk1vZGVsIE1hdGVtYXRpczoqKg0KTWlzYWxrYW4ga2l0YSBtZW1pbGlraSBoaW1wdW5hbiBkYXRhICRYID0gXHt4XzEsIHhfMiwgLi4uLCB4X25cfSQuIFNlYnVhaCBlbnNlbWJsZSB0ZXJkaXJpIGRhcmkgJE0kIHBhcnRpc2kgZGFzYXIgJFxQaSA9IFx7XHBpXzEsIFxwaV8yLCAuLi4sIFxwaV9NXH0kLiBNYXNhbGFoIGtvbnNlbnN1cyBjbHVzdGVyaW5nIGFkYWxhaCBtZW5jYXJpIHBhcnRpc2kgZmluYWwgJFxwaV4qJCB5YW5nIG1lbWluaW1hbGthbiB0b3RhbCBqYXJhayBrZXRpZGFrbWlyaXBhbiBkZW5nYW4gc2VsdXJ1aCBwYXJ0aXNpIGFuZ2dvdGEuDQoNCiQkIFxwaV4qID0gXGFyZ1xtaW5fe1xwaX0gXHN1bV97aT0xfV57TX0gd19pIGQoXHBpLCBccGlfaSkgJCQNCg0KRGltYW5hOg0KDQogICogJFxwaV4qJDogUGFydGlzaSBrb25zZW5zdXMgb3B0aW1hbC4NCiAgKiAkTSQ6IEp1bWxhaCBwYXJ0aXNpIGRhbGFtIGVuc2VtYmxlLg0KICAqICR3X2kkOiBCb2JvdCBwYXJ0aXNpIGtlLSRpJCAoYmlhc2FueWEgZGlhbmdnYXAgc2FtYS91bmlmb3JtKS4NCiAgKiAkZChcY2RvdCkkOiBGdW5nc2kgamFyYWsgYW50YXIgcGFydGlzaSAobWlzYWxueWEgKkluZm9ybWF0aW9uIERpc3RhbmNlKiBhdGF1ICpNaXJraW4gTWV0cmljKikuDQoNCiMjIENvbnN0cmFpbnQtYmFzZWQgQ2x1c3RlcmluZyAoQ09QLUtNRUFOUykNCg0KKipEZWZpbmlzaToqKg0KSW5pIGFkYWxhaCB2YXJpYW4gKnNlbWktc3VwZXJ2aXNlZCogZGFyaSBLLU1lYW5zLiBBbGdvcml0bWEgaW5pIHRpZGFrIGJla2VyamEgc2VjYXJhICJidXRhIiwgbWVsYWlua2FuIGRpYmltYmluZyBvbGVoIGJhdGFzYW4gKCpjb25zdHJhaW50cyopIHlhbmcgYmVyYXNhbCBkYXJpIHBlbmdldGFodWFuIGF3YWwgKG1pc2FsbnlhIGRpYWdub3NpcyBkb2t0ZXIgcGFkYSBzZWJhZ2lhbiBrZWNpbCBwYXNpZW4pLg0KDQoqKkplbmlzIEJhdGFzYW46KioNCg0KMS4gICoqTXVzdC1MaW5rIChNTCk6KiogUGFzYW5nYW4gdGl0aWsgJCh4X2ksIHhfaikkIHlhbmcgKipoYXJ1cyoqIGJlcmFkYSBkYWxhbSBrbGFzdGVyIHlhbmcgc2FtYS4NCjIuICAqKkNhbm5vdC1MaW5rIChDTCk6KiogUGFzYW5nYW4gdGl0aWsgJCh4X2ksIHhfaikkIHlhbmcgKip0aWRhayBib2xlaCoqIGJlcmFkYSBkYWxhbSBrbGFzdGVyIHlhbmcgc2FtYS4NCg0KKipGdW5nc2kgT2JqZWt0aWY6KioNCkFsZ29yaXRtYSBDT1AtS01FQU5TIGJlcnVzYWhhIG1lbWluaW1hbGthbiAqV2l0aGluLUNsdXN0ZXIgU3VtIG9mIFNxdWFyZXMqIChXQ1NTKSBkZW5nYW4gdGV0YXAgbWVtYXR1aGkgYmF0YXNhbiBzZWNhcmEgbXV0bGFrICgqSGFyZCBDb25zdHJhaW50cyopOg0KDQokJCBKID0gXHN1bV97az0xfV57S30gXHN1bV97eF9pIFxpbiBDX2t9IHx8eF9pIC0gXG11X2t8fF4yICQkDQoNCioqU3ViamVjdCB0bzoqKg0KDQogICogJFxmb3JhbGwgKHhfaSwgeF9qKSBcaW4gTUwgXGltcGxpZXMgQ2x1c3Rlcih4X2kpID0gQ2x1c3Rlcih4X2opJA0KICAqICRcZm9yYWxsICh4X2ksIHhfaikgXGluIENMIFxpbXBsaWVzIENsdXN0ZXIoeF9pKSBcbmVxIENsdXN0ZXIoeF9qKSQNCg0KIyMgdm9sdXRpb25hcnkgQ2x1c3RlcmluZyAoR2VuZXRpYyBBbGdvcml0aG0pDQoNCioqRGVmaW5pc2k6KioNCkFsZ29yaXRtYSBHZW5ldGlrYSAoR0EpIGFkYWxhaCB0ZWtuaWsgb3B0aW1hc2kgc3Rva2FzdGlrIHlhbmcgdGVyaW5zcGlyYXNpIGRhcmkgbWVrYW5pc21lIHNlbGVrc2kgYWxhbSBkYW4gZ2VuZXRpa2EuIERhbGFtIGtvbnRla3MgY2x1c3RlcmluZywgR0EgZGlndW5ha2FuIHVudHVrIG1lbmNhcmkga29uZmlndXJhc2kgY2VudHJvaWQgYXRhdSBwYXJ0aXNpIHlhbmcgbWVtaW5pbWFsa2FuIGZ1bmdzaSBlcnJvciBnbG9iYWwsIG1lbmdoaW5kYXJpIGplYmFrYW4gbWluaW11bSBsb2thbCB5YW5nIHNlcmluZyBkaWFsYW1pIG1ldG9kZSBiZXJiYXNpcyBncmFkaWVuLg0KDQoqKktvbXBvbmVuIFV0YW1hOioqDQoNCjEuICAqKktyb21vc29tOioqIFJlcHJlc2VudGFzaSBzb2x1c2kgKG1pc2FsbnlhIHN0cmluZyBiaW5lciBzZXBhbmphbmcgJE4kIGRhdGEpLg0KMi4gICoqRml0bmVzcyBGdW5jdGlvbjoqKiBGdW5nc2kgeWFuZyBtZW5nZXZhbHVhc2kga3VhbGl0YXMgc29sdXNpLiBLYXJlbmEgY2x1c3RlcmluZyBtZW1pbmltYWxrYW4gZXJyb3IgKFdDU1MpIGF0YXUgbWVtYWtzaW1hbGthbiBwZW1pc2FoYW4gKFNpbGhvdWV0dGUpLCBmdW5nc2kgZml0bmVzcyBzZXJpbmcgZGlkZWZpbmlzaWthbiBzZWJhZ2FpOg0KICAgICQkIEZpdG5lc3MgPSAtV0NTUyBccXVhZCBcdGV4dHthdGF1fSBccXVhZCBGaXRuZXNzID0gXGZyYWN7MX17REJcX0luZGV4fSAkJA0KMy4gICoqT3BlcmF0b3IgR2VuZXRpa2E6KiogU2VsZWtzaSAobWVtaWxpaCBpbmRpdmlkdSB0ZXJiYWlrKSwgQ3Jvc3NvdmVyIChrYXdpbiBzaWxhbmcpLCBkYW4gTXV0YXNpIChwZXJ1YmFoYW4gYWNhaykuDQoNCiMgTUVUT0RPTE9HSSAmIFBST1NFRFVSIEtFUkpBDQoNCiMjIEFsdXIgS2VyamEgJiBIeXBlcnBhcmFtZXRlcg0KDQpCZXJpa3V0IGFkYWxhaCByaW5jaWFuIHRla25pcyBwZWxha3NhbmFhbiBzZXRpYXAgYWxnb3JpdG1hOg0KDQp8IEFsZ29yaXRtYSB8IExhbmdrYWggT3BlcmFzaSB8IEh5cGVycGFyYW1ldGVyIFV0YW1hIHwNCnwgOi0tLSB8IDotLS0gfCA6LS0tIHwNCnwgKipFbnNlbWJsZSBDbHVzdGVyaW5nKiogfCAxLiBMYWt1a2FuIHN1YnNhbXBsaW5nIGRhdGEgKDgwJSkuPGJyPjIuIEphbGFua2FuIEstTWVhbnMgZGFuIFBBTSBiZXJ1bGFuZyBrYWxpICgxMCByZXBldGlzaSkuPGJyPjMuIEhpdHVuZyBtYXRyaWtzIGtvLWFzb3NpYXNpIChrb25zZW5zdXMpLjxicj40LiBMYWt1a2FuIGtsYXN0ZXJpc2FzaSBha2hpciBwYWRhIG1hdHJpa3MgdGVyc2VidXQuIHwgYHJlcHNgID0gMTAgKEp1bWxhaCB1bGFuZ2FuKTxicj5gYWxnb3JpdGhtc2AgPSBjKCJrbSIsICJwYW0iKTxicj5gcC5pdGVtYCA9IDAuOCAoUmVzYW1wbGluZykgfA0KfCAqKkNvbnN0cmFpbnQtYmFzZWQqKiB8IDEuIEFtYmlsIHNhbXBlbCBhY2FrIGRhdGEgKDIwIHBhc2FuZykuPGJyPjIuIENlayBsYWJlbCBhc2xpOiBqaWthIHNhbWEgYnVhdCAqTXVzdC1MaW5rKiwgamlrYSBiZWRhIGJ1YXQgKkNhbm5vdC1MaW5rKi48YnI+My4gSW5pc2lhbGlzYXNpIGNlbnRyb2lkIHlhbmcgdmFsaWQuPGJyPjQuIEFzc2lnbiBkYXRhIGtlIGtsYXN0ZXIgdGVyZGVrYXQgdGFucGEgbWVsYW5nZ2FyIGJhdGFzYW4uIHwgYG11c3RMaW5rYCAoTWF0cmlrcyBNTCk8YnI+YGNhbnRMaW5rYCAoTWF0cmlrcyBDTCk8YnI+YGtgID0gMiAoSnVtbGFoIEtsYXN0ZXIpIHwNCnwgKipFdm9sdXRpb25hcnkgKEdBKSoqIHwgMS4gQmFuZ2tpdGthbiBwb3B1bGFzaSBhd2FsIHNlY2FyYSBhY2FrIChiaW5lcikuPGJyPjIuIEhpdHVuZyBGaXRuZXNzIChOZWdhdGlmIERCIEluZGV4KS48YnI+My4gTGFrdWthbiBTZWxla3NpIFR1cm5hbWVuLjxicj40LiBMYWt1a2FuIENyb3Nzb3ZlciBkYW4gTXV0YXNpLjxicj41LiBVbGFuZ2kgaGluZ2dhIGdlbmVyYXNpIG1ha3NpbXVtLiB8IGBwb3BTaXplYCA9IDQwIChVa3VyYW4gUG9wdWxhc2kpPGJyPmBtYXhpdGVyYCA9IDIwIChHZW5lcmFzaSk8YnI+YHBtdXRhdGlvbmAgPSAwLjEgfA0KDQojIyBBbmFsaXNpcyBLZWxlYmloYW4gJiBLZXRlcmJhdGFzYW4NCg0KICAqICoqRW5zZW1ibGUgQ2x1c3RlcmluZzoqKg0KDQogICAgICAqICooKykqICoqU3RhYmlsaXRhcyBUaW5nZ2k6KiogU2FuZ2F0IGVmZWt0aWYgbWVyZWRhbSBub2lzZSBkYW4gdmFyaWFiaWxpdGFzIGhhc2lsIGFraWJhdCBpbmlzaWFsaXNhc2kgYWNhay4NCiAgICAgICogKigtKSogKipLb21wdXRhc2kgTWFoYWw6KiogTWVtYnV0dWhrYW4gd2FrdHUgZWtzZWt1c2kgbGluZWFyIHRlcmhhZGFwIGp1bWxhaCByZXBldGlzaSAoJE4gXHRpbWVzJCB3YWt0dSBzaW5nbGUgcnVuKS4NCg0KICAqICoqQ29uc3RyYWludC1iYXNlZCBDbHVzdGVyaW5nOioqDQoNCiAgICAgICogKigrKSogKipBa3VyYXNpIFRpbmdnaToqKiBQZW5hbWJhaGFuIHNlZGlraXQgaW5mb3JtYXNpIHBha2FyIGRhcGF0IG1lbmluZ2thdGthbiBrZXNlc3VhaWFuIGRlbmdhbiAqZ3JvdW5kIHRydXRoKiBzZWNhcmEgZHJhc3Rpcy4NCiAgICAgICogKigtKSogKipTZW5zaXRpdml0YXM6KiogU2FuZ2F0IHJlbnRhbiB0ZXJoYWRhcCBrZXNhbGFoYW4gcGVsYWJlbGFuIHBhZGEgY29uc3RyYWludHMgKCptaXNsYWJlbGluZyopIGRhbiBrb25mbGlrIGJhdGFzYW4gKCppbmZlYXNpYmlsaXR5KikuDQoNCiAgKiAqKkV2b2x1dGlvbmFyeSBDbHVzdGVyaW5nOioqDQoNCiAgICAgICogKigrKSogKipQZW5jYXJpYW4gR2xvYmFsOioqIE1hbXB1IG1lbmdla3NwbG9yYXNpIHJ1YW5nIHNvbHVzaSB5YW5nIGx1YXMgZGFuIG1lbmVtdWthbiBzb2x1c2kgeWFuZyBsZWJpaCBiYWlrIGRhcmlwYWRhIG1ldG9kZSBncmVlZHkuDQogICAgICAqICooLSkqICoqUnVudGltZSBMYW1iYXQ6KiogUHJvc2VzIGV2b2x1c2kgbWVtYnV0dWhrYW4gZXZhbHVhc2kgZnVuZ3NpIGZpdG5lc3MgcmlidWFuIGthbGksIG1lbWJ1YXRueWEga3VyYW5nIGVmaXNpZW4gdW50dWsgZGF0YSBzYW5nYXQgYmVzYXIuDQoNCiMgRUtTUExPUkFTSSBEQVRBIChFREEpDQoNCiMjIFN1bWJlciBEYXRhDQoNCkRhdGEgeWFuZyBkaWd1bmFrYW4gYWRhbGFoICoqQnJlYXN0IENhbmNlciBXaXNjb25zaW4gKERpYWdub3N0aWMpIERhdGEgU2V0KiouDQoNCiAgKiAqKlN1bWJlcjoqKiA8YSBocmVmPSJodHRwczovL2FyY2hpdmUuaWNzLnVjaS5lZHUvZGF0YXNldC8xNy9icmVhc3QrY2FuY2VyK3dpc2NvbnNpbitkaWFnbm9zdGljIiB0YXJnZXQ9Il9ibGFuayI+VUNJIE1hY2hpbmUgTGVhcm5pbmcgUmVwb3NpdG9yeTwvYT4gLyA8YSBocmVmPSJodHRwczovL3d3dy5rYWdnbGUuY29tL2RhdGFzZXRzL3VjaW1sL2JyZWFzdC1jYW5jZXItd2lzY29uc2luLWRhdGE/cmVzb3VyY2U9ZG93bmxvYWQiIHRhcmdldD0iX2JsYW5rIj5LYWdnbGU8L2E+Lg0KICAqICoqRGVza3JpcHNpOioqIERhdGFzZXQgaW5pIGJlcmlzaSA1Njkgc2FtcGVsIGRlbmdhbiAzMiBhdHJpYnV0IHlhbmcgZGloaXR1bmcgZGFyaSBjaXRyYSBkaWdpdGFsLiBGaXR1ciBtZW5jYWt1cCByYWRpdXMsIHRla3N0dXIsIHBlcmltZXRlciwgYXJlYSwga2VoYWx1c2FuLCBrZWtvbXBha2FuLCBjZWt1bmdhbiwgdGl0aWsgY2VrdW5nLCBzaW1ldHJpLCBkYW4gZGltZW5zaSBmcmFrdGFsLg0KDQo8IS0tIGVuZCBsaXN0IC0tPg0KDQpgYGB7ciBsb2FkX2RhdGF9DQojIE1lbXVhdCBkYXRhc2V0DQpkZl9yYXcgPC0gcmVhZC5jc3YoImRhdGEuY3N2IikNCg0KIyBNZW5hbXBpbGthbiB0YWJlbCBpbnRlcmFrdGlmDQpEVDo6ZGF0YXRhYmxlKGRmX3JhdywgDQogICAgICAgICAgICAgIG9wdGlvbnMgPSBsaXN0KHNjcm9sbFggPSBUUlVFLCBwYWdlTGVuZ3RoID0gNSksIA0KICAgICAgICAgICAgICBjYXB0aW9uID0gIlRhYmVsIDQuMTogVGluamF1YW4gRGF0YXNldCBNZW50YWggKDUgQmFyaXMgUGVydGFtYSkiKQ0KYGBgDQoNCiMjIFN0YXRpc3RpayBEZXNrcmlwdGlmICYgVmFsaWRhc2kgS3VhbGl0YXMgRGF0YQ0KDQpUYWhhcCBpbmkgYmVydHVqdWFuIHVudHVrIG1lbWVyaWtzYSBpbnRlZ3JpdGFzIGRhdGEsIHRpcGUgdmFyaWFiZWwsIGRhbiBrZWJlcmFkYWFuICptaXNzaW5nIHZhbHVlcyouDQoNCmBgYHtyIGVkYV9zdW1tYXJ5fQ0KY2F0KCI9PT0gUklOR0tBU0FOIFNUUlVLVFVSIERBVEEgPT09XG4iKQ0KY2F0KCJKdW1sYWggT2JzZXJ2YXNpIChCYXJpcykgOiIsIG5yb3coZGZfcmF3KSwgIlxuIikNCmNhdCgiSnVtbGFoIFZhcmlhYmVsIChLb2xvbSkgIDoiLCBuY29sKGRmX3JhdyksICJcbiIpDQoNCiMgQ2VrIE1pc3NpbmcgVmFsdWVzDQpuYV9jb3VudHMgPC0gY29sU3Vtcyhpcy5uYShkZl9yYXcpKQ0KaWYoYW55KG5hX2NvdW50cyA+IDApKSB7DQogIGNhdCgiXG49PT0gTUlTU0lORyBWQUxVRVMgVEVSREVURUtTSSA9PT1cbiIpDQogIHByaW50KG5hX2NvdW50c1tuYV9jb3VudHMgPiAwXSkNCiAgY2F0KCJDYXRhdGFuOiBLb2xvbSAnVW5uYW1lZDogMzInIGJlcmlzaSBOQSBzZXBlbnVobnlhIGRhbiBha2FuIGRpaGFwdXMgcGFkYSB0YWhhcCBwcmEtcHJvc2VzLlxuIikNCn0gZWxzZSB7DQogIGNhdCgiXG5EYXRhIGJlcnNpaCBkYXJpIE1pc3NpbmcgVmFsdWVzLlxuIikNCn0NCmBgYA0KDQojIyBWaXN1YWxpc2FzaSBEaXN0cmlidXNpIERpYWdub3NpcyANCg0KS2l0YSBwZXJsdSBtZW1haGFtaSBwcm9wb3JzaSBrZWxhcyB0YXJnZXQgKERpYWdub3NpcykgdW50dWsgbWVuZ2V0YWh1aSBhcGFrYWggZGF0YSBzZWltYmFuZyBhdGF1ICppbWJhbGFuY2VkKi4NCg0KYGBge3IgZWRhX3Zpen0NCiMgTWVuZ2hpdHVuZyBmcmVrdWVuc2kNCmRpYWdfY291bnRzIDwtIHRhYmxlKGRmX3JhdyRkaWFnbm9zaXMpDQpkZl9jb3VudHMgPC0gYXMuZGF0YS5mcmFtZShkaWFnX2NvdW50cykNCmNvbG5hbWVzKGRmX2NvdW50cykgPC0gYygiRGlhZ25vc2lzIiwgIkp1bWxhaCIpDQoNCiMgTWVtYnVhdCBCYXIgQ2hhcnQgSW50ZXJha3RpZg0KcF9iYXIgPC0gZ2dwbG90KGRmX2NvdW50cywgYWVzKHg9RGlhZ25vc2lzLCB5PUp1bWxhaCwgZmlsbD1EaWFnbm9zaXMpKSArDQogIGdlb21fYmFyKHN0YXQ9ImlkZW50aXR5IikgKw0KICB0aGVtZV9taW5pbWFsKCkgKw0KICBsYWJzKHRpdGxlPSJEaXN0cmlidXNpIERpYWdub3NpcyBQYXNpZW4gKEdyb3VuZCBUcnV0aCkiLA0KICAgICAgIHN1YnRpdGxlPSJNID0gTWFsaWduYW50IChHYW5hcyksIEIgPSBCZW5pZ24gKEppbmFrKSIsDQogICAgICAgeD0iRGlhZ25vc2lzIiwgeT0iSnVtbGFoIFBhc2llbiIpICsNCiAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzPWMoIiMyRTlGREYiLCAiI0U3QjgwMCIpKQ0KDQpnZ3Bsb3RseShwX2JhcikNCmBgYA0KDQoqKkludGVycHJldGFzaSBFREE6KioNCkRhdGEgbWVudW5qdWtrYW4gcHJvcG9yc2kgeWFuZyBjdWt1cCBzZWltYmFuZywgZGVuZ2FuIGthc3VzICpCZW5pZ24qIChCKSBzZWJhbnlhayAzNTcga2FzdXMgZGFuICpNYWxpZ25hbnQqIChNKSBzZWJhbnlhayAyMTIga2FzdXMuIFRpZGFrIGFkYSBrZXRpbXBhbmdhbiBrZWxhcyB5YW5nIGVrc3RyZW0geWFuZyBtZW1lcmx1a2FuIHRla25payAqcmVzYW1wbGluZyoga2h1c3VzLiBOYW11biwgcmVudGFuZyBuaWxhaSBmaXR1ciBhbnRhciB2YXJpYWJlbCAobWlzYWxueWEgYGFyZWFfbWVhbmAgdnMgYHNtb290aG5lc3NfbWVhbmApIHNhbmdhdCBiZXJiZWRhLCBzZWhpbmdnYSBwcm9zZXMgKlNjYWxpbmcqIG11dGxhayBkaXBlcmx1a2FuLg0KDQojIFBSQS1QUk9TRVMgREFUQSAoUFJFUFJPQ0VTU0lORykNCg0KIyMgUGVtYmVyc2loYW4gZGFuIEVuY29kaW5nDQoNCkxhbmdrYWggaW5pIG1lbGlwdXRpIHBlbmdoYXB1c2FuIGtvbG9tIHlhbmcgdGlkYWsgcmVsZXZhbiAoSUQgZGFuIGtvbG9tIHNhbXBhaCkgc2VydGEga29udmVyc2kgbGFiZWwgZGlhZ25vc2lzIG1lbmphZGkgZm9ybWF0IG51bWVyaWsgdW50dWsga2VwZXJsdWFuIGV2YWx1YXNpIGFraGlyLg0KDQpgYGB7ciBwcmVwcm9jZXNzaW5nfQ0KIyAxLiBNZW5naGFwdXMga29sb20gSUQgZGFuIGtvbG9tIGtvc29uZyAoYXJ0ZWZhayBDU1YpDQpkZl9jbGVhbiA8LSBkZl9yYXcgJT4lDQogIGRwbHlyOjpzZWxlY3QoLWlkKSAlPiUNCiAgZHBseXI6OnNlbGVjdCgtbWF0Y2hlcygiVW5uYW1lZCIpKSAlPiUNCiAgZHBseXI6OnNlbGVjdCgtbWF0Y2hlcygiXlgkIikpDQoNCiMgMi4gTWVtaXNhaGthbiBMYWJlbCBEaWFnbm9zaXMgKERpc2ltcGFuIHNlYmFnYWkgR3JvdW5kIFRydXRoKQ0KbGFiZWxzX3RydWUgPC0gZGZfY2xlYW4kZGlhZ25vc2lzDQojIEVuY29kaW5nOiBNIChHYW5hcykgPSAxLCBCIChKaW5haykgPSAyDQpsYWJlbHNfbnVtIDwtIGlmZWxzZShsYWJlbHNfdHJ1ZSA9PSAiTSIsIDEsIDIpDQoNCiMgMy4gTWVtYnVhdCBkYXRhc2V0IGZpdHVyIG11cm5pIChIYW55YSBOdW1lcmlrKQ0KZGZfZmVhdHVyZXMgPC0gZGZfY2xlYW4gJT4lIGRwbHlyOjpzZWxlY3QoLWRpYWdub3NpcykNCmBgYA0KDQojIyBTY2FsaW5nIChTdGFuZGFyZGlzYXNpIFotU2NvcmUpDQoNCkFsZ29yaXRtYSBjbHVzdGVyaW5nIGJlcmJhc2lzIGphcmFrIChFdWNsaWRlYW4pIHNhbmdhdCBzZW5zaXRpZiB0ZXJoYWRhcCBza2FsYS4gS2l0YSBtZW5zdGFuZGFyaXNhc2kgZGF0YSBhZ2FyIG1lbWlsaWtpIHJhdGEtcmF0YSAwIGRhbiB2YXJpYW5zaSAxLg0KDQpgYGB7ciBzY2FsaW5nfQ0KZGZfc2NhbGVkIDwtIHNjYWxlKGRmX2ZlYXR1cmVzKQ0KDQojIFZlcmlmaWthc2kgaGFzaWwgc2NhbGluZw0Kc3VtbWFyeShkZl9zY2FsZWRbLCAxOjRdKSAlPiUgDQogIGtuaXRyOjprYWJsZShjYXB0aW9uID0gIlN0YXRpc3RpayBEZXNrcmlwdGlmIDQgRml0dXIgUGVydGFtYSAoUGFzY2EtU2NhbGluZykiKQ0KYGBgDQoNCiMgVEVLTklLIFJFRFVLU0kgRElNRU5TSQ0KDQpVbnR1ayBtZW1haGFtaSBzdHJ1a3R1ciBkYXRhIGJlcmRpbWVuc2kgdGluZ2dpICgzMCBkaW1lbnNpKSwga2l0YSBtZW5nZ3VuYWthbiB0aWdhIHRla25payByZWR1a3NpIGRpbWVuc2kgdW50dWsgdmlzdWFsaXNhc2k6ICoqUENBKiosICoqdC1TTkUqKiwgZGFuICoqVU1BUCoqLg0KDQojIyBQcmluY2lwYWwgQ29tcG9uZW50IEFuYWx5c2lzIChQQ0EpDQoNClBDQSBtZXJhbmdrdW0gdmFyaWFuc2kgZ2xvYmFsIGRhdGEgZGVuZ2FuIG1lbXByb3lla3Npa2FubnlhIGtlIGtvbWJpbmFzaSBsaW5lYXIgb3J0b2dvbmFsLg0KDQpgYGB7ciBwY2F9DQojIEhpdHVuZyBQQ0ENCnBjYV9yZXMgPC0gcHJjb21wKGRmX3NjYWxlZCwgY2VudGVyID0gVFJVRSwgc2NhbGUuID0gVFJVRSkNCmRmX3BjYSA8LSBhcy5kYXRhLmZyYW1lKHBjYV9yZXMkeFssIDE6M10pICMgQW1iaWwgMyBrb21wb25lbiB1dGFtYQ0KZGZfcGNhJExhYmVsIDwtIGxhYmVsc190cnVlDQoNCiMgVmlzdWFsaXNhc2kgU2NyZWUgUGxvdCBJbnRlcmFrdGlmDQpwX3NjcmVlIDwtIGZ2aXpfZWlnKHBjYV9yZXMsIGFkZGxhYmVscyA9IFRSVUUsIHlsaW0gPSBjKDAsIDUwKSwgDQogICAgICAgICAgICAgICAgICAgIG1haW4gPSAiUENBIFNjcmVlIFBsb3Q6ICUgVmFyaWFuc2kgcGVyIEtvbXBvbmVuIikNCmdncGxvdGx5KHBfc2NyZWUpDQpgYGANCg0KKipBbmFsaXNpczoqKg0KRHVhIGtvbXBvbmVuIHV0YW1hIHBlcnRhbWEgKERpbWVuc2lvbiAxICYgMikgc3VkYWggbWFtcHUgbWVuamVsYXNrYW4gbGViaWggZGFyaSAqKjYzJSoqIHRvdGFsIHZhcmlhbnNpIGRhdGEuIEluaSBtZW51bmp1a2thbiBiYWh3YSBzdHJ1a3R1ciBkYXRhIGN1a3VwIGt1YXQgZGFuIHJlcHJlc2VudGFzaSAyRCBkYXBhdCBkaWFuZGFsa2FuLg0KDQojIyB0LVNORSAmIFVNQVAgKE1hbmlmb2xkIExlYXJuaW5nKQ0KDQpCZXJiZWRhIGRlbmdhbiBQQ0EgeWFuZyBsaW5lYXIsIHQtU05FIGRhbiBVTUFQIGFkYWxhaCB0ZWtuaWsgbm9uLWxpbmVhciB5YW5nIGxlYmloIGJhaWsgZGFsYW0gbWVtcGVydGFoYW5rYW4gc3RydWt0dXIgbG9rYWwgZGFuIG1lbWlzYWhrYW4ga2xhc3RlciB5YW5nIHR1bXBhbmcgdGluZGloLg0KDQpgYGB7ciB0c25lX3VtYXB9DQpzZXQuc2VlZCg0MikNCmRmX3VuaXF1ZSA8LSB1bmlxdWUoZGZfc2NhbGVkKSAjIE1lbmdoYXB1cyBkdXBsaWthdCAoc3lhcmF0IHQtU05FKQ0KDQojIDEuIHQtU05FDQp0c25lX3JlcyA8LSBSdHNuZShkZl91bmlxdWUsIGRpbXMgPSAyLCBwZXJwbGV4aXR5ID0gMzAsIHZlcmJvc2UgPSBGQUxTRSwgbWF4X2l0ZXIgPSA1MDApDQpkZl90c25lIDwtIGFzLmRhdGEuZnJhbWUodHNuZV9yZXMkWSkNCmNvbG5hbWVzKGRmX3RzbmUpIDwtIGMoInRTTkUxIiwgInRTTkUyIikNCg0KIyAyLiBVTUFQDQp1bWFwX3JlcyA8LSB1bWFwKGRmX3VuaXF1ZSwgbl9uZWlnaGJvcnMgPSAxNSwgbWV0cmljID0gImV1Y2xpZGVhbiIpDQpkZl91bWFwIDwtIGFzLmRhdGEuZnJhbWUodW1hcF9yZXMkbGF5b3V0KQ0KY29sbmFtZXMoZGZfdW1hcCkgPC0gYygiVU1BUDEiLCAiVU1BUDIiKQ0KYGBgDQoNCiMgSU1QTEVNRU5UQVNJIEFMR09SSVRNQSAoQ0FTRSA2KQ0KDQojIyBBbGdvcml0bWEgMTogRW5zZW1ibGUgQ2x1c3RlcmluZw0KDQpLaXRhIG1lbmphbGFua2FuIEstTWVhbnMgZGFuIFBBTSBzZWJhbnlhayAxMCBrYWxpIHBhZGEgc3Vic2FtcGVsIGRhdGEsIGxhbHUgbWVuZ2dhYnVuZ2thbiBoYXNpbG55YS4NCg0KYGBge3IgZW5zX2FsZ299DQp0aWNfZW5zIDwtIFN5cy50aW1lKCkNCnNldC5zZWVkKDEyMykNCg0KY2F0KCJNZW5qYWxhbmthbiBFbnNlbWJsZSBDbHVzdGVyaW5nLi4uXG4iKQ0KZW5zX21vZGVsIDwtIGRpY2VSOjpjb25zZW5zdXNfY2x1c3RlcigNCiAgZGF0YSA9IGRmX3NjYWxlZCwgDQogIG5rID0gMiwgICAgICAgICAgICMgVGFyZ2V0IDIga2xhc3Rlcg0KICBwLml0ZW0gPSAwLjgsICAgICAjIFJlc2FtcGxpbmcgODAlDQogIHJlcHMgPSAxMCwgICAgICAgICMgMTAga2FsaSByZXBldGlzaQ0KICBhbGdvcml0aG1zID0gYygia20iLCAicGFtIiksIA0KICBwcm9ncmVzcyA9IEZBTFNFDQopDQoNCiMgRnVuZ3NpIE1ham9yaXR5IFZvdGUNCmdldF9tb2RlIDwtIGZ1bmN0aW9uKHYpIHsNCiAgdW5pcXYgPC0gdW5pcXVlKG5hLm9taXQodikpDQogIGlmKGxlbmd0aCh1bmlxdikgPT0gMCkgcmV0dXJuKDEpDQogIHVuaXF2W3doaWNoLm1heCh0YWJ1bGF0ZShtYXRjaCh2LCB1bmlxdikpKV0NCn0NCmVuc19sYWJlbHMgPC0gYXBwbHkoZW5zX21vZGVsLCAxLCBnZXRfbW9kZSkNCg0KdG9jX2VucyA8LSBTeXMudGltZSgpDQpydW50aW1lX2VucyA8LSBhcy5udW1lcmljKGRpZmZ0aW1lKHRvY19lbnMsIHRpY19lbnMsIHVuaXRzID0gInNlY3MiKSkNCmNhdCgiRW5zZW1ibGUgU2VsZXNhaS4gUnVudGltZToiLCBydW50aW1lX2VucywgImRldGlrLiIpDQpgYGANCg0KIyMgQWxnb3JpdG1hIDI6IENvbnN0cmFpbnQtYmFzZWQgQ2x1c3RlcmluZw0KDQpLaXRhIG1lbnNpbXVsYXNpa2FuIHBlbmdldGFodWFuIHBha2FyIGRlbmdhbiBtZW5nYW1iaWwgc2FtcGVsIGFjYWsgMjAgcGFzYW5nIHBhc2llbi4gSmlrYSBsYWJlbCBhc2xpbnlhIHNhbWEsIGtpdGEgYnVhdCBhdHVyYW4gKk11c3QtTGluayouIEppa2EgYmVkYSwgKkNhbm5vdC1MaW5rKi4NCg0KYGBge3IgY29uc3RfYWxnb30NCnRpY19jb25zdCA8LSBTeXMudGltZSgpDQpzZXQuc2VlZCg5OTkpDQoNCmNhdCgiTWVueWlhcGthbiBDb25zdHJhaW50cyBkYW4gTWVuamFsYW5rYW4gQ0stTWVhbnMuLi5cbiIpDQpuX3BhaXJzIDwtIDIwDQppZHhfc2FtcGxlIDwtIHNhbXBsZSgxOm5yb3coZGZfc2NhbGVkKSwgbl9wYWlycyAqIDIpDQptdXN0X2xpbmsgPC0gbWF0cml4KG5jb2w9MiwgbnJvdz0wKQ0KY2Fubm90X2xpbmsgPC0gbWF0cml4KG5jb2w9MiwgbnJvdz0wKQ0KDQpmb3IoaSBpbiBzZXEoMSwgbGVuZ3RoKGlkeF9zYW1wbGUpLCBieT0yKSl7DQogIHAxIDwtIGlkeF9zYW1wbGVbaV07IHAyIDwtIGlkeF9zYW1wbGVbaSsxXQ0KICBpZihsYWJlbHNfbnVtW3AxXSA9PSBsYWJlbHNfbnVtW3AyXSl7DQogICAgbXVzdF9saW5rIDwtIHJiaW5kKG11c3RfbGluaywgYyhwMSwgcDIpKQ0KICB9IGVsc2Ugew0KICAgIGNhbm5vdF9saW5rIDwtIHJiaW5kKGNhbm5vdF9saW5rLCBjKHAxLCBwMikpDQogIH0NCn0NCg0KcmVzX2NvbnN0IDwtIGNrbWVhbnMoZGF0YSA9IGFzLmRhdGEuZnJhbWUoZGZfc2NhbGVkKSwgayA9IDIsIG11c3RMaW5rID0gbXVzdF9saW5rLCBjYW50TGluayA9IGNhbm5vdF9saW5rKQ0KY29uc3RfbGFiZWxzIDwtIHJlc19jb25zdA0KDQp0b2NfY29uc3QgPC0gU3lzLnRpbWUoKQ0KcnVudGltZV9jb25zdCA8LSBhcy5udW1lcmljKGRpZmZ0aW1lKHRvY19jb25zdCwgdGljX2NvbnN0LCB1bml0cyA9ICJzZWNzIikpDQpjYXQoIkNvbnN0cmFpbnQtYmFzZWQgU2VsZXNhaS4gUnVudGltZToiLCBydW50aW1lX2NvbnN0LCAiZGV0aWsuIikNCmBgYA0KDQojIyBBbGdvcml0bWEgMzogRXZvbHV0aW9uYXJ5IENsdXN0ZXJpbmcgKEdBKQ0KDQpLaXRhIG1lbmdndW5ha2FuIEFsZ29yaXRtYSBHZW5ldGlrYSB1bnR1ayBtZW1pbmltYWxrYW4gKkRhdmllcy1Cb3VsZGluIEluZGV4Ki4NCg0KYGBge3IgZ2FfYWxnb30NCnRpY19nYSA8LSBTeXMudGltZSgpDQpjYXQoIk1lbmphbGFua2FuIEFsZ29yaXRtYSBHZW5ldGlrYSAoRXZvbHVzaSkuLi5cbiIpDQoNCiMgRml0bmVzcyBGdW5jdGlvbiAoTmVnYXRpZiBEQiBJbmRleCkNCmZpdG5lc3NfZ2EgPC0gZnVuY3Rpb24oeCkgew0KICBjbHVzdGVyX2Fzc2lnbiA8LSBpZmVsc2UoeCA9PSAxLCAxLCAyKQ0KICBpZihsZW5ndGgodW5pcXVlKGNsdXN0ZXJfYXNzaWduKSkgPCAyKSByZXR1cm4oLTFlMTApDQogIGRiaSA8LSB0cnkoY2x1c3RlclNpbTo6aW5kZXguREIoZGZfc2NhbGVkLCBjbHVzdGVyX2Fzc2lnbikkREIsIHNpbGVudD1UUlVFKQ0KICBpZihpbmhlcml0cyhkYmksICJ0cnktZXJyb3IiKSB8fCBpcy5uYShkYmkpKSByZXR1cm4oLTFlMTApDQogIHJldHVybigtZGJpKSANCn0NCg0KZ2FfcmVzIDwtIGdhKHR5cGUgPSAiYmluYXJ5IiwgbkJpdHMgPSBucm93KGRmX3NjYWxlZCksIGZpdG5lc3MgPSBmaXRuZXNzX2dhLA0KICAgICAgICAgICAgIHBvcFNpemUgPSA0MCwgbWF4aXRlciA9IDIwLCBwbXV0YXRpb24gPSAwLjEsIG1vbml0b3IgPSBGQUxTRSkNCg0Kc29sdXRpb24gPC0gZ2FfcmVzQHNvbHV0aW9uWzEsIF0NCmdhX2xhYmVscyA8LSBpZmVsc2Uoc29sdXRpb24gPT0gMSwgMSwgMikNCg0KdG9jX2dhIDwtIFN5cy50aW1lKCkNCnJ1bnRpbWVfZ2EgPC0gYXMubnVtZXJpYyhkaWZmdGltZSh0b2NfZ2EsIHRpY19nYSwgdW5pdHMgPSAic2VjcyIpKQ0KY2F0KCJFdm9sdXRpb25hcnkgR0EgU2VsZXNhaS4gUnVudGltZToiLCBydW50aW1lX2dhLCAiZGV0aWsuIikNCmBgYA0KDQojIFZJU1VBTElTQVNJIEhBU0lMDQoNCkJlcmlrdXQgYWRhbGFoIHZpc3VhbGlzYXNpIGludGVyYWt0aWYgZGFyaSBoYXNpbCBrbGFzdGVyaXNhc2kuDQoNCiMjIFZpc3VhbGlzYXNpIDJEOiBFbnNlbWJsZSBwYWRhIFBDQQ0KDQpNZWxpaGF0IGJhZ2FpbWFuYSBFbnNlbWJsZSBtZW1iYWdpIGRhdGEgcGFkYSBydWFuZyB2YXJpYW5zaSB0ZXJiZXNhci4NCg0KYGBge3IgcGxvdF8yZF9wY2F9DQpkZl9wbG90IDwtIGRmX3BjYQ0KZGZfcGxvdCRFbnNlbWJsZSA8LSBmYWN0b3IoZW5zX2xhYmVscykNCmRmX3Bsb3QkRGlhZ25vc2lzIDwtIGZhY3RvcihsYWJlbHNfdHJ1ZSkNCg0KcGxvdF9seShkZl9wbG90LCB4ID0gflBDMSwgeSA9IH5QQzIsIGNvbG9yID0gfkVuc2VtYmxlLCBjb2xvcnMgPSBjKCJibHVlIiwgInJlZCIpLA0KICAgICAgICB0ZXh0ID0gfnBhc3RlKCJEaWFnbm9zaXMgQXNsaToiLCBEaWFnbm9zaXMpLA0KICAgICAgICB0eXBlID0gJ3NjYXR0ZXInLCBtb2RlID0gJ21hcmtlcnMnLCBtYXJrZXI9bGlzdChzaXplPTcpKSAlPiUNCiAgbGF5b3V0KHRpdGxlID0gIkhhc2lsIEVuc2VtYmxlIENsdXN0ZXJpbmcgKFByb3lla3NpIFBDQSkiKQ0KYGBgDQoNCiMjIFZpc3VhbGlzYXNpIDJEOiBDb25zdHJhaW50LWJhc2VkIHBhZGEgdC1TTkUNCg0KTWVsaWhhdCBwZW5nYXJ1aCBjb25zdHJhaW50cyBwYWRhIHN0cnVrdHVyIG5vbi1saW5lYXIgdC1TTkUuDQoNCmBgYHtyIHBsb3RfMmRfdHNuZX0NCmRmX3RzbmVfcGxvdCA8LSBkZl90c25lDQpkZl90c25lX3Bsb3QkQ29uc3RyYWludCA8LSBmYWN0b3IoY29uc3RfbGFiZWxzWzE6bnJvdyhkZl90c25lKV0pDQoNCnBsb3RfbHkoZGZfdHNuZV9wbG90LCB4ID0gfnRTTkUxLCB5ID0gfnRTTkUyLCBjb2xvciA9IH5Db25zdHJhaW50LCBjb2xvcnMgPSBjKCJncmVlbiIsICJvcmFuZ2UiKSwNCiAgICAgICAgdHlwZSA9ICdzY2F0dGVyJywgbW9kZSA9ICdtYXJrZXJzJywgbWFya2VyPWxpc3Qoc2l6ZT03KSkgJT4lDQogIGxheW91dCh0aXRsZSA9ICJIYXNpbCBDb25zdHJhaW50LWJhc2VkIENsdXN0ZXJpbmcgKFByb3lla3NpIHQtU05FKSIpDQpgYGANCg0KIyMgVmlzdWFsaXNhc2kgMkQ6IEV2b2x1dGlvbmFyeSBwYWRhIFVNQVANCg0KTWVsaWhhdCBoYXNpbCBvcHRpbWFzaSBHQSBwYWRhIG1hbmlmb2xkIFVNQVAuDQoNCmBgYHtyIHBsb3RfMmRfdW1hcH0NCmRmX3VtYXBfcGxvdCA8LSBkZl91bWFwDQpkZl91bWFwX3Bsb3QkR0FfQ2x1c3RlciA8LSBmYWN0b3IoZ2FfbGFiZWxzWzE6bnJvdyhkZl91bWFwKV0pDQoNCnBsb3RfbHkoZGZfdW1hcF9wbG90LCB4ID0gflVNQVAxLCB5ID0gflVNQVAyLCBjb2xvciA9IH5HQV9DbHVzdGVyLCBjb2xvcnMgPSBjKCJwdXJwbGUiLCAiZ29sZCIpLA0KICAgICAgICB0eXBlID0gJ3NjYXR0ZXInLCBtb2RlID0gJ21hcmtlcnMnLCBtYXJrZXI9bGlzdChzaXplPTcpKSAlPiUNCiAgbGF5b3V0KHRpdGxlID0gIkhhc2lsIEV2b2x1dGlvbmFyeSBDbHVzdGVyaW5nIChQcm95ZWtzaSBVTUFQKSIpDQpgYGANCg0KIyMgVmlzdWFsaXNhc2kgM0QgSW50ZXJha3RpZiAoUENBKQ0KDQpgYGB7ciBwbG90XzNkfQ0KcGxvdF9seShkZl9wbG90LCB4ID0gflBDMSwgeSA9IH5QQzIsIHogPSB+UEMzLCBjb2xvciA9IH5FbnNlbWJsZSwgY29sb3JzID0gYygiYmx1ZSIsICJyZWQiKSwNCiAgICAgICAgdHlwZSA9ICdzY2F0dGVyM2QnLCBtb2RlID0gJ21hcmtlcnMnLCBtYXJrZXIgPSBsaXN0KHNpemUgPSA0LCBvcGFjaXR5PTAuOCkpICU+JQ0KICBsYXlvdXQodGl0bGUgPSAiVmlzdWFsaXNhc2kgM0Q6IEVuc2VtYmxlIENsdXN0ZXJpbmciKQ0KYGBgDQoNCiMjIFRyYWNlIFBsb3Q6IFByb3NlcyBFdm9sdXNpIEdBDQoNCkdyYWZpayBpbmkgbWVudW5qdWtrYW4gYmFnYWltYW5hIEFsZ29yaXRtYSBHZW5ldGlrYSBtZW5pbmdrYXRrYW4ga3VhbGl0YXMgc29sdXNpIChGaXRuZXNzKSBkYXJpIGdlbmVyYXNpIGtlIGdlbmVyYXNpLg0KDQpgYGB7ciBnYV90cmFjZX0NCnBsb3QoZ2FfcmVzLCBtYWluID0gIkplamFrIEV2b2x1c2kgKEZpdG5lc3MgdnMgR2VuZXJhc2kpIikNCmBgYA0KDQojIyBIZWF0bWFwICYgRGVuZHJvZ3JhbQ0KDQpWaXN1YWxpc2FzaSBwb2xhIGVrc3ByZXNpIGZpdHVyIHVudHVrIHNhbXBlbCAyMDAgcGFzaWVuLg0KDQpgYGB7ciBoZWF0bWFwfQ0Kc2V0LnNlZWQoMTIzKQ0KaWR4X3N1YiA8LSBzYW1wbGUoMTpucm93KGRmX3NjYWxlZCksIDIwMCkNCm1hdF9zdWIgPC0gZGZfc2NhbGVkW2lkeF9zdWIsIF0NCmFubl9yb3cgPC0gZGF0YS5mcmFtZShFbnNlbWJsZSA9IGZhY3RvcihlbnNfbGFiZWxzW2lkeF9zdWJdKSkNCnJvd25hbWVzKGFubl9yb3cpIDwtIHJvd25hbWVzKG1hdF9zdWIpIDwtIHBhc3RlMCgiUCIsIGlkeF9zdWIpDQoNCnBoZWF0bWFwKG1hdF9zdWIsIHNob3dfcm93bmFtZXMgPSBGQUxTRSwgDQogICAgICAgICBhbm5vdGF0aW9uX3JvdyA9IGFubl9yb3csDQogICAgICAgICBhbm5vdGF0aW9uX2NvbG9ycyA9IGxpc3QoRW5zZW1ibGUgPSBjKCIxIj0iYmx1ZSIsICIyIj0icmVkIikpLA0KICAgICAgICAgbWFpbiA9ICJIZWF0bWFwICYgRGVuZHJvZ3JhbSAoRW5zZW1ibGUpIiwNCiAgICAgICAgIHNjYWxlID0gIm5vbmUiKQ0KYGBgDQoNCiMgRVZBTFVBU0kgJiBQRVJCQU5ESU5HQU4NCg0KVW50dWsgbWVuZ2V2YWx1YXNpIHBlcmZvcm1hIHNlY2FyYSBvYmpla3RpZiwga2l0YSBtZW5nZ3VuYWthbiBrb21iaW5hc2kgbWV0cmlrIEVrc3Rlcm5hbCAobWVtYmFuZGluZ2thbiBkZW5nYW4gbGFiZWwgYXNsaSkgZGFuIEludGVybmFsIChnZW9tZXRyaSBrbGFzdGVyKSwgc2VydGEgZWZpc2llbnNpIHdha3R1Lg0KDQoxLiAgKipBUkkgKEFkanVzdGVkIFJhbmQgSW5kZXgpOioqIDAtMSwgc2VtYWtpbiB0aW5nZ2kgc2VtYWtpbiBtaXJpcCBsYWJlbCBhc2xpLg0KMi4gICoqTk1JIChOb3JtYWxpemVkIE11dHVhbCBJbmZvcm1hdGlvbik6KiogSW5mb3JtYXNpIHlhbmcgZGliYWdpIGRlbmdhbiBsYWJlbCBhc2xpLg0KMy4gICoqU2lsaG91ZXR0ZSBTY29yZToqKiBLZXBhZGF0YW4ga2xhc3RlciAoLTEgcy5kIDEpLg0KNC4gICoqREIgSW5kZXg6KiogUmFzaW8gZGlzcGVyc2kgKFNlbWFraW4gS0VDSUwgc2VtYWtpbiBiYWlrKS4NCjUuICAqKkNIIEluZGV4OioqIENhbGluc2tpLUhhcmFiYXN6IChTZW1ha2luIEJFU0FSIHNlbWFraW4gYmFpaykuDQoNCjwhLS0gZW5kIGxpc3QgLS0+DQoNCmBgYHtyIGV2YWx1YXRpb259DQpldmFsdWF0ZV9hbGdvIDwtIGZ1bmN0aW9uKHByZWQsIHRydXRoLCBkYXRhKSB7DQogIHByZWQgPC0gYXMuaW50ZWdlcihhcy5jaGFyYWN0ZXIocHJlZCkpDQogIHRydXRoIDwtIGFzLmludGVnZXIoYXMuY2hhcmFjdGVyKHRydXRoKSkNCiAgcHJlZFtpcy5uYShwcmVkKV0gPC0gMQ0KICANCiAgIyBNZXRyaWsgRWtzdGVybmFsDQogIGFyaSA8LSBtY2x1c3Q6OmFkanVzdGVkUmFuZEluZGV4KHByZWQsIHRydXRoKQ0KICBubWkgPC0gYXJpY29kZTo6Tk1JKHByZWQsIHRydXRoKQ0KICANCiAgIyBNZXRyaWsgSW50ZXJuYWwNCiAgc2lsIDwtIG1lYW4oc2lsaG91ZXR0ZShwcmVkLCBkaXN0KGRhdGEpKVssIDNdKQ0KICBkYiA8LSBjbHVzdGVyU2ltOjppbmRleC5EQihkYXRhLCBwcmVkKSREQg0KICBjaCA8LSBjbHVzdGVyU2ltOjppbmRleC5HMShkYXRhLCBwcmVkKQ0KICANCiAgcmV0dXJuKGMoQVJJPWFyaSwgTk1JPW5taSwgU2lsaG91ZXR0ZT1zaWwsIERCX0luZGV4PWRiLCBDSF9JbmRleD1jaCkpDQp9DQoNCm1ldF9lbnMgPC0gZXZhbHVhdGVfYWxnbyhlbnNfbGFiZWxzLCBsYWJlbHNfbnVtLCBkZl9zY2FsZWQpDQptZXRfY29uc3QgPC0gZXZhbHVhdGVfYWxnbyhjb25zdF9sYWJlbHMsIGxhYmVsc19udW0sIGRmX3NjYWxlZCkNCm1ldF9nYSA8LSBldmFsdWF0ZV9hbGdvKGdhX2xhYmVscywgbGFiZWxzX251bSwgZGZfc2NhbGVkKQ0KDQpmaW5hbF9yZXMgPC0gZGF0YS5mcmFtZSgNCiAgTWV0b2RlID0gYygiRW5zZW1ibGUiLCAiQ29uc3RyYWludC1iYXNlZCIsICJFdm9sdXRpb25hcnkgKEdBKSIpLA0KICByYmluZChtZXRfZW5zLCBtZXRfY29uc3QsIG1ldF9nYSksDQogIFJ1bnRpbWVfU2VjID0gYyhydW50aW1lX2VucywgcnVudGltZV9jb25zdCwgcnVudGltZV9nYSkNCikNCg0Ka25pdHI6OmthYmxlKGZpbmFsX3JlcywgZGlnaXRzID0gMywgY2FwdGlvbiA9ICJUYWJlbCA5LjE6IFBlcmJhbmRpbmdhbiBQZXJmb3JtYSBMZW5na2FwIikNCmBgYA0KDQojIyBWaXN1YWxpc2FzaSBQZXJiYW5kaW5nYW4gTWV0cmlrIChCYXIgQ2hhcnQgSW50ZXJha3RpZikNCg0KYGBge3Igdml6X2NvbXBhcmlzb259DQpyZXNfbG9uZyA8LSBmaW5hbF9yZXMgJT4lIHBpdm90X2xvbmdlcigtTWV0b2RlLCBuYW1lc190bz0iTWV0cmlrIiwgdmFsdWVzX3RvPSJOaWxhaSIpDQoNCnBfY29tcCA8LSBnZ3Bsb3QocmVzX2xvbmcsIGFlcyh4PU1ldG9kZSwgeT1OaWxhaSwgZmlsbD1NZXRvZGUpKSArDQogIGdlb21fYmFyKHN0YXQ9ImlkZW50aXR5IiwgcG9zaXRpb249ImRvZGdlIikgKw0KICBmYWNldF93cmFwKH5NZXRyaWssIHNjYWxlcz0iZnJlZV95IikgKw0KICB0aGVtZV9taW5pbWFsKCkgKw0KICBsYWJzKHRpdGxlID0gIlBlcmJhbmRpbmdhbiBNZXRyaWsgRXZhbHVhc2kiLCB4ID0gIiIsIHkgPSAiU2tvciIpICsNCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X2JsYW5rKCkpICsNCiAgc2NhbGVfZmlsbF9icmV3ZXIocGFsZXR0ZSA9ICJEYXJrMiIpDQoNCmdncGxvdGx5KHBfY29tcCkNCmBgYA0KDQojIEtFU0lNUFVMQU4gJiBSRUtPTUVOREFTSQ0KDQojIyBLZXNpbXB1bGFuIEFuYWxpc2lzDQoNCkRhcmkgaGFzaWwgZWtzcGVyaW1lbiBkaSBhdGFzLCBraXRhIGRhcGF0IG1lbmFyaWsga2VzaW1wdWxhbiBiZXJpa3V0Og0KDQoxLiAgKipDb25zdHJhaW50LWJhc2VkIENsdXN0ZXJpbmcgVW5nZ3VsIGRhbGFtIEFrdXJhc2k6KioNCiAgICBNZXRvZGUgaW5pIHNlY2FyYSBrb25zaXN0ZW4gbWVtYmVyaWthbiBza29yICoqQVJJKiogZGFuICoqTk1JKiogdGVydGluZ2dpLiBIYWwgaW5pIG1lbWJ1a3Rpa2FuIGJhaHdhIHBlbmFtYmFoYW4gYmF0YXNhbiAoKk11c3QtTGluay9DYW5ub3QtTGluayopIHlhbmcgYmVyYXNhbCBkYXJpIHBlbmdldGFodWFuIGRva3RlciAobWVza2lwdW4gaGFueWEgc2VkaWtpdCBzYW1wZWwpIHNhbmdhdCBlZmVrdGlmIGRhbGFtIG1lbmdhcmFoa2FuIGFsZ29yaXRtYSBrZSBzb2x1c2kgeWFuZyBiZW5hciBzZWNhcmEgbWVkaXMuIEluaSBhZGFsYWggY29udG9oIG55YXRhIGtla3VhdGFuICpIdW1hbi1pbi10aGUtbG9vcCBBSSouDQoNCjIuICAqKkVuc2VtYmxlIENsdXN0ZXJpbmcgUGFsaW5nIFN0YWJpbDoqKg0KICAgIE1ldG9kZSBFbnNlbWJsZSBtZW51bmp1a2thbiBuaWxhaSAqKlNpbGhvdWV0dGUgU2NvcmUqKiB5YW5nIHRpbmdnaSBkYW4ga29uc2lzdGVuLiBKaWthIGtpdGEgdGlkYWsgbWVtaWxpa2kgbGFiZWwgc2FtYSBzZWthbGkgKCpwdXJlIHVuc3VwZXJ2aXNlZCopLCBFbnNlbWJsZSBhZGFsYWggcGlsaWhhbiB5YW5nIHBhbGluZyBhbWFuIGthcmVuYSBtZW1pbmltYWxpc2lyIHJpc2lrbyBoYXNpbCBhY2FrIHlhbmcgYnVydWsgZGFuIG1lbWJlcmlrYW4gcGFydGlzaSB5YW5nIHNlY2FyYSBnZW9tZXRyaXMgc2FuZ2F0IHBhZGF0Lg0KDQozLiAgKipUcmFkZS1vZmYgRWZpc2llbnNpIChSdW50aW1lKToqKg0KICAgIEFsZ29yaXRtYSBHZW5ldGlrYSAoKipFdm9sdXRpb25hcnkqKikgbWVuYXdhcmthbiBwZW5kZWthdGFuIHBlbmNhcmlhbiBnbG9iYWwgeWFuZyB1bmlrLCBuYW11biBiaWF5YSBrb21wdXRhc2lueWEgKCoqUnVudGltZSoqKSBqYXVoIGxlYmloIHRpbmdnaSBkaWJhbmRpbmdrYW4gbWV0b2RlIGxhaW4uIERpIHNpc2kgbGFpbiwgQ29uc3RyYWludC1iYXNlZCBDbHVzdGVyaW5nIGJla2VyamEgc2FuZ2F0IGNlcGF0IChoYW1waXIgc2V0YXJhIEstTWVhbnMgYmlhc2EpIG5hbXVuIGRlbmdhbiBha3VyYXNpIHlhbmcgamF1aCBsZWJpaCB0aW5nZ2kuDQoNCiMjIFJla29tZW5kYXNpIEtsaW5pcw0KDQpVbnR1ayBwZW5nZW1iYW5nYW4gc2lzdGVtIHBlbmR1a3VuZyBrZXB1dHVzYW4gZGlhZ25vc2lzIGthbmtlciBwYXl1ZGFyYToNCg0KPiAqKlJFS09NRU5EQVNJIFVUQU1BOiBDb25zdHJhaW50LWJhc2VkIENsdXN0ZXJpbmcuKioNCg0KKipBbGFzYW46KiogRGFsYW0gc2tlbmFyaW8gbWVkaXMsIGFrdXJhc2kgYWRhbGFoIHByaW9yaXRhcyBtdXRsYWsuIE1ldG9kZSBpbmkgbWVuYXdhcmthbiBrZXNlaW1iYW5nYW4gdGVyYmFpayBhbnRhcmEgYWt1cmFzaSB0aW5nZ2kgKGJlcmthdCBwYW5kdWFuIHBha2FyKSBkYW4gZWZpc2llbnNpIHdha3R1LiBEb2t0ZXIgaGFueWEgcGVybHUgbWVsYWJlbGkgc2ViYWdpYW4ga2VjaWwga2FzdXMgeWFuZyAiamVsYXMiLCBkYW4gYWxnb3JpdG1hIGFrYW4gbWVueWVsZXNhaWthbiBzaXNhbnlhIGRlbmdhbiBwcmVzaXNpIHRpbmdnaS4NCg0KPiAqKkFMVEVSTkFUSUY6IEVuc2VtYmxlIENsdXN0ZXJpbmcuKioNCg0KKipBbGFzYW46KiogSmlrYSBkYXRhIGxhYmVsIGJlbmFyLWJlbmFyIHRpZGFrIHRlcnNlZGlhLCBtZXRvZGUgaW5pIG1lbWJlcmlrYW4gamFtaW5hbiBoYXNpbCB5YW5nIHBhbGluZyAqcm9idXN0KiBkYW4gZGFwYXQgZGlwZXJjYXlhIHNlY2FyYSBzdGF0aXN0aWsuDQoNCiMgUmVmZXJlbnNpDQotIGh0dHBzOi8vYm9va2Rvd24ub3JnL2NvbnRlbnQvYTE0MmIxNzItNjliMi00MzZkLWJkYjAtOWRhNmQwNDZhMGY5LzA0LUNsdXN0ZXJpbmcuaHRtbA0KLSBodHRwczovL3d3dy5rYWdnbGUuY29tL2RhdGFzZXRzL3VjaW1sL2JyZWFzdC1jYW5jZXItd2lzY29uc2luLWRhdGE/cmVzb3VyY2U9ZG93bmxvYWQ=