Nama: JOANS HENKY SERVATIUS SIMANULLANG
NIM: 52240017
Prodi: Sains Data
Institusi: Institut Teknologi Sains Bandung (ITSB)
PENDAHULUAN
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).
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:
- Menguji stabilitas solusi menggunakan Ensemble
Clustering.
- Menguji dampak penambahan pengetahuan pakar menggunakan
Constraint-based Clustering.
- Menguji kemampuan pencarian global menggunakan Evolutionary
Clustering (Genetic Algorithm).
LANDASAN TEORI &
MODEL MATEMATIS
Pada bab ini, kita akan membedah landasan teoretis dan formulasi
matematis dari setiap metode yang digunakan.
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).
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:
- Must-Link (ML): Pasangan titik \((x_i, x_j)\) yang harus
berada dalam klaster yang sama.
- 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)\)
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:
- Kromosom: Representasi solusi (misalnya string
biner sepanjang \(N\) data).
- 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} \]
- Operator Genetika: Seleksi (memilih individu
terbaik), Crossover (kawin silang), dan Mutasi (perubahan acak).
METODOLOGI &
PROSEDUR KERJA
Alur Kerja &
Hyperparameter
Berikut adalah rincian teknis pelaksanaan setiap algoritma:
| 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 |
Analisis Kelebihan
& Keterbatasan
EKSPLORASI DATA
(EDA)
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)")
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.
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.
PRA-PROSES DATA
(PREPROCESSING)
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)
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)
|
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 |
TEKNIK REDUKSI
DIMENSI
Untuk memahami struktur data berdimensi tinggi (30 dimensi), kita
menggunakan tiga teknik reduksi dimensi untuk visualisasi:
PCA, t-SNE, dan
UMAP.
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.
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")
IMPLEMENTASI ALGORITMA
(CASE 6)
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.
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.
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.
VISUALISASI HASIL
Berikut adalah visualisasi interaktif dari hasil klasterisasi.
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)")
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)")
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)")
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")
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)")

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")

EVALUASI &
PERBANDINGAN
Untuk mengevaluasi performa secara objektif, kita menggunakan
kombinasi metrik Eksternal (membandingkan dengan label asli) dan
Internal (geometri klaster), serta efisiensi waktu.
- ARI (Adjusted Rand Index): 0-1, semakin tinggi
semakin mirip label asli.
- NMI (Normalized Mutual Information): Informasi yang
dibagi dengan label asli.
- Silhouette Score: Kepadatan klaster (-1 s.d
1).
- DB Index: Rasio dispersi (Semakin KECIL semakin
baik).
- 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
| 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 |
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)
KESIMPULAN &
REKOMENDASI
Kesimpulan
Analisis
Dari hasil eksperimen di atas, kita dapat menarik kesimpulan
berikut:
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.
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.
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.
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=