Optimasi

~ Assignment Week 14 ~

Introduction

About

Optimasi penting dalam banyak bidang, termasuk dalam ilmu data. Dalam manufaktur, di mana setiap keputusan sangat penting untuk proses dan keuntungan organisasi, optimasi sering digunakan, mulai dari jumlah setiap produk yang dihasilkan, bagaimana unit dijadwalkan untuk produksi, mendapatkan parameter proses terbaik atau optimal, dan juga perutean. penentuan seperti masalah salesman keliling. Dalam ilmu data, kami akrab dengan penyetelan model, di mana kami menyetel model kami untuk meningkatkan kinerja model. Algoritma optimasi dapat membantu kita untuk mendapatkan performa model yang lebih baik. Algoritma Genetika (GA) merupakan salah satu algoritma optimasi yang banyak digunakan.

Artikel ini mencoba menjelaskan mekanisme di balik salah satu algoritma yang paling efektif untuk masalah optimasi: Algoritma Genetika (GA). Ada banyak algoritma yang khusus dibuat untuk optimasi, seperti Particle Swarm Optimization (PSO), Simulated Annealing (SA), dan Tabu Search. Namun, di banyak bidang, GA dapat mencapai tujuan lebih baik daripada metode tersebut. Ada berbagai turunan dan variasi implementasi GA, mungkin yang paling terkenal adalah implementasi GA dalam masalah optimasi multiobjective, di mana kita ingin mengoptimalkan dua tujuan secara bersamaan, yang disebut Multi Objective Evolutionary Algorithm (MOEA). Kita akan melihat bagaimana GA umum bekerja dan di mana itu dapat diterapkan, baik pada masalah bisnis maupun di bidang ilmu data.

Objectives

Tujuan Pembelajaran:

  • Pelajari pentingnya optimasi dalam bisnis
  • Pelajari tentang fungsi biaya/fungsi tujuan dan variabel keputusan
  • Pelajari hubungan antara pembelajaran mesin dan pengoptimalan
  • Pelajari prinsip dan konsep Algoritma Genetika
  • Pelajari implementasi GA dalam kasus bisnis dan bidang ilmu data

Library and Setup

Untuk menggunakan GA, Anda dapat menginstal paket GA menggunakan fungsi install.packages().

Berikut ini adalah paket-paket yang akan digunakan di seluruh artikel.

library(tidyverse)
library(GA)
library(ranger)
library(tidymodels)
library(caret)
library(tictoc)
library(tufte)
library(randomForest)

Optimization Problem

Why Optimization is Important

Katakanlah, kami memiliki 2 lini produk, dengan produk A memiliki keuntungan sebesar USD 50 sedangkan produk B memiliki keuntungan sebesar USD 30. Kami ingin memaksimalkan keuntungan kami, tetapi kami memiliki sumber daya yang terbatas. Kami hanya memiliki 80 jam kerja selama seminggu, dengan produk A memakan waktu sekitar 10 jam dalam pembuatan sedangkan produk B adalah 4 jam dalam pembuatan. Kedua produk berbagi bahan yang sama dan kami hanya memiliki 100 meter kain dengan masing-masing produk A membutuhkan 2 meter kain sedangkan produk B membutuhkan 4 meter kain. Bagaimana kita memutuskan berapa jumlah produk A dan B terbaik yang akan diproduksi? Begitulah masalah optimasi (dalam hal ini, optimasi profit).

Bagaimana jika kita memutuskan untuk memilih secara acak jumlah setiap produk yang akan diproduksi. Kita mungkin tidak punya cukup waktu untuk menyelesaikannya. Kami mungkin tidak memiliki cukup bahan, atau sebaliknya bisa terjadi: kami mungkin memiliki bahan cadangan.

Kasus lain, katakanlah kami ingin mengirimkan produk kami ke beberapa gudang. Bagaimana kami memutuskan gudang mana yang harus kami kunjungi terlebih dahulu atau bagaimana kami memutuskan rute apa yang harus kami ambil untuk meminimalkan waktu pengiriman?

Optimalisasi penting terutama jika kita memiliki sumber daya yang terbatas. Lebih penting lagi, optimasi memperhatikan keuntungan dan kerugian: memilih rute pengiriman yang salah akan mengakibatkan keterlambatan pengiriman, yang dapat menimbulkan biaya penalti atau merusak produk kami di sepanjang jalan. Itulah mengapa optimasi sangat penting untuk bisnis.

Objective Function and Decision Variables

Setiap masalah optimasi selalu memiliki 2 elemen: fungsi tujuan dan variabel keputusan. Fungsi objektif berarti bagaimana kita merumuskan tujuan kita, seperti bagaimana kita mengukur keuntungan untuk memaksimalkannya, atau bagaimana kita mengukur waktu pengiriman untuk meminimalkannya. Di dalam fungsi tujuan terdapat variabel keputusan, apa yang harus menjadi keputusan kita yang dapat menghasilkan keuntungan maksimal? Kami ingin memaksimalkan tujuan kami dengan memilih variabel keputusan yang tepat. Pada contoh sebelumnya, tujuan untuk memaksimalkan laba termasuk ke dalam fungsi tujuan, sedangkan jumlah produk A dan produk B yang akan diproduksi termasuk dalam variabel keputusan.

Beberapa tujuan mungkin memiliki kendala, seperti jumlah jam kerja seminggu hanya 80 jam atau jumlah bahan yang tersedia hanya 100 meter. Kendala itu penting, karena solusi apa pun yang tidak memenuhi kendala adalah solusi yang tidak layak.

Machine Learning and Optimization

Machine learning dan optimasi tidak dapat dipisahkan, keduanya saling berkaitan, meskipun tujuannya berbeda. Pembelajaran mesin berkaitan dengan prediksi atau klasifikasi berdasarkan beberapa prediktor, sedangkan optimasi berkaitan dengan menemukan nilai objektif terbaik berdasarkan pilihan variabel keputusan. Namun, mekanisme di balik sebagian besar model pembelajaran mesin adalah pengoptimalan. Sebagai contoh, metode gradient descent dari model pelatihan Neural Network adalah metode optimasi, yang tujuannya adalah untuk menemukan titik terendah dan konvergen. Contoh lain adalah pemasangan regresi linier, yang meminimalkan jumlah kesalahan kuadrat (dengan demikian, nama metode Kuadrat Terkecil).

Pembelajaran mesin dapat ditingkatkan dengan menyetel hyper-parameter dengan pengoptimalan, sementara pada saat yang sama metode pengoptimalan juga dapat ditingkatkan dengan model pembelajaran mesin. Misalnya, kita dapat secara signifikan mengurangi MSE model Regresi K-NN Standar dan Tertimbang dengan menggunakan Algoritma Genetika1. Dengan optimasi multi-tujuan, kita dapat memperoleh model ensemble yang memiliki keseimbangan keseimbangan antara runtime dan kinerja model2.

Genetic Algorithm: Concepts

Overview

Pertama-tama mari kita baca paragraf indah ini dari The Blind Watchmaker oleh Richard Dawkins

“Semua penampilan sebaliknya, satu-satunya pembuat jam di alam adalah kekuatan buta fisika, meskipun dikerahkan dengan cara yang sangat khusus. Seorang pembuat jam sejati memiliki pandangan jauh ke depan: ia merancang pegas roda giginya, dan merencanakan interkoneksinya, dengan tujuan masa depan di benaknya. Seleksi alam, proses buta, tidak sadar, otomatis yang ditemukan Darwin, dan yang sekarang kita ketahui adalah penjelasan atas keberadaan dan bentuk yang tampaknya memiliki tujuan dari semua kehidupan, tidak memiliki tujuan dalam pikiran. Ia tidak memiliki pikiran dan tidak memiliki mata pikiran. Itu tidak merencanakan masa depan. Ia tidak memiliki visi, tidak ada pandangan ke depan, tidak ada penglihatan sama sekali. Jika dapat dikatakan memainkan peran pembuat jam di alam, itu adalah pembuat jam buta.”
Richard Dawkins

Evolusi melalui seleksi alam pertama kali dikemukakan oleh Charles Darwin melalui bukunya On the Origin of Species by Means of Natural Selection, or the Preservation of Favoured Races in the Struggle for Life. Terlepas dari pernyataan berani Dawkins bahwa seleksi alam tidak memiliki tujuan, mekanismenya dapat disesuaikan untuk memecahkan masalah optimasi. Jika dalam dunia biologis nyata, seleksi alam bekerja secara tidak sengaja dengan memilih individu yang paling cocok yang dapat bertahan hidup dan bereproduksi, sehingga menyebarkan gennya, solusi optimal atau terbaik dalam masalah optimasi dapat diperoleh dengan memilih yang memiliki nilai tercocok, yaitu nilai fungsi tujuan. Ini mungkin perbedaan utama antara seleksi alam yang sebenarnya dan yang diadopsi untuk Algoritma Genetika.

“Algoritma genetika (GA) adalah algoritma pencarian stokastik yang terinspirasi oleh prinsip-prinsip dasar evolusi biologis dan seleksi alam. GA mensimulasikan evolusi organisme hidup, di mana individu yang paling kuat mendominasi yang lebih lemah, dengan meniru mekanisme evolusi biologis, seperti seleksi, persilangan dan mutasi..”
Luca Scrucca

Algoritma Genetika adalah algoritma optimasi yang menggunakan konsep evolusi melalui seleksi alam. Evolusi melalui seleksi alam, sebagaimana dikemukakan oleh Darwin, adalah mekanisme berapa banyak jenis makhluk hidup yang beradaptasi dengan lingkungannya untuk bertahan hidup, melalui 2 prinsip utama: seleksi alam dan mutasi acak. Algoritma genetika (GA) meminjam konsep dan menggunakannya untuk mendapatkan solusi optimal dengan memilih solusi terbaik atau paling cocok di samping mutasi yang jarang dan acak.

Misalkan kita memiliki fungsi non-cembung. Jika kita menggunakan metode berbasis turunan seperti penurunan gradien, itu berpotensi jatuh ke optima lokal di \(x_1\). Algoritma Genetika dapat mengatasi local optima sehingga dapat memberikan solusi yang lebih baik. Dikombinasikan dengan pembelajaran mesin seperti jaringan saraf, GA dapat membuat sistem kecerdasan buatan yang dapat bermain game, like Flappy Bird

The general flow of the algorithm is shown below:

  1. Akan ada populasi dari kromosom atau solusi yang dipilih secara acak.
  2. Nilai fitness atau nilai fungsi tujuan dari masing-masing solusi dihitung.
  3. Dari populasi tersebut akan dipilih dua solusi sebagai solusi induk, baik dengan pemilihan turnamen atau metode pemilihan lainnya.
  4. Solusi yang dipilih akan disilangkan untuk membuat solusi baru, yang disebut solusi anak.
  5. Solusi anak dapat berubah karena mutasi acak (yang kemungkinannya sangat kecil untuk terjadi).
  6. Di akhir iterasi, populasi baru akan dipilih dari solusi induk atau populasi awal dan solusi anak berdasarkan nilai fitness.
  7. Selama kriteria berhenti tidak terpenuhi, biasanya dalam hal jumlah iterasi, algoritma akan terus melakukan iterasi.
  8. Solusi terbaik atau optimal adalah solusi dengan nilai fitness yang optimal.

Ada beberapa keuntungan dari Algoritma Genetika:

  • Algoritma Genetika dapat menghindari optimal lokal
  • Dapat menangani masalah yang kompleks dengan banyak variabel keputusan dan ruang solusi yang besar
  • Ini mempertimbangkan banyak titik di ruang pencarian secara bersamaan, bukan satu titik, untuk berurusan dengan besar ruang parameter.
  • Dapat diparalelkan karena GA terdiri dari beberapa kromosom/solusi
  • Mendukung pengoptimalan multi-tujuan

Elements

Ada 3 elemen utama GA: Populasi, Kromosom, dan Gen.

Populasi adalah sekelompok individu atau solusi, terutama disebut kromosom. Sebuah kromosom terdiri dari urutan gen, dengan masing-masing atau beberapa gen (tergantung pada pengkodeannya) mewakili variabel keputusan tunggal yang akan dipasang pada fungsi tujuan.

Gen dapat direpresentasikan atau dikodekan dengan berbagai cara, termasuk:

  • Binary Encoding

Pengkodean biner berarti bahwa gen kita dikodekan ke dalam bilangan biner, ini adalah bentuk pengkodean GA yang paling awal dan paling umum.

  • Real-Valued Encoding

Pengkodean bernilai nyata berarti bahwa gen kita dikodekan dalam floating point atau angka desimal. Encoding bernilai nyata dapat digunakan untuk mengoptimalkan parameter proses, karena terdiri dari bilangan floating point dan tidak cocok untuk optimasi berbasis integer.

  • Permutation Encoding

Pengkodean permutasi berarti bahwa gen kita dikodekan ke dalam urutan nomor, setiap nomor secara unik ditugaskan untuk gen sehingga tidak ada 2 gen yang akan memiliki nomor atau nilai yang sama. Pengkodean ini sering digunakan dalam masalah penjadwalan atau perutean, di mana setiap permutasi mewakili satu lokasi atau titik unik.

Selection

Pemilihan parent dilakukan untuk memilih dua solusi yang akan digunakan untuk crossover untuk membuat solusi baru yang disebut solusi anak. Ada beberapa metode untuk memilih orang tua:

  • Pemilihan Roda Roulette

Orang tua akan dipilih secara acak berdasarkan nilai fitness. Nilai fitness yang lebih tinggi berarti kesempatan yang lebih tinggi untuk dipilih.

  • Pemilihan Peringkat Linier

Individu diberi kebugaran subjektif berdasarkan peringkat dalam populasi. Individu dalam populasi diurutkan dari yang terbaik hingga yang terburuk berdasarkan nilai fitnessnya. Setiap individu dalam populasi diberi peringkat numerik berdasarkan kebugaran, dan seleksi didasarkan pada peringkat ini daripada perbedaan dalam kebugaran.

  • Pemilihan Turnamen

Seleksi turnamen dilakukan dengan menyeleksi sekumpulan kromosom dari populasi dan membuat kriteria seleksi tertentu. Pemilihan turnamen efektif untuk GA yang memiliki populasi besar karena tidak perlu mengurutkan individu di dalam populasi.

Crossover and Mutation

Crossover

Crossover adalah proses perkawinan antara dua individu yang dipilih, proses tersebut mewakili bagaimana gen ditransfer ke keturunannya. Ada beberapa metode crossover: #### Binary

  • Crossover titik tunggal

Pisahkan induk berdasarkan satu titik yang dihasilkan secara acak. Anak pertama akan memiliki gen dari orang tua pertama untuk posisi 1 sampai dengan titik, sedangkan sisanya diisi dengan gen dari orang tua kedua. Anak kedua kebalikannya, posisi 1 sampai titik diisi dengan gen dari induk kedua.

  • Seragam Crossover

Setiap gen dipertukarkan antara sepasang kromosom yang dipilih secara acak dengan probabilitas tertentu, yang disebut sebagai probabilitas swapping. Biasanya, nilai probabilitas swapping diambil menjadi 0,5.

Real-Valued

  • Crossover Aritmatika Utuh

Mengambil jumlah tertimbang dari dua gen induk untuk setiap posisi. Bobot diterapkan untuk semua posisi. Misal x = nilai parent pertama dan y = nilai parent kedua untuk posisi/gen 1:

\[Child \ 1 = weight \ * x + (1-weight) \ * y\]

\[Child \ 2 = weight \ * y + (2-weight) \ * x\]

Jika berat = 0,5 dua keturunan identik

  • Crossover Aritmatika Lokal

Crossover lokal mirip dengan crossover aritmatika keseluruhan, tetapi menggunakan alpha acak untuk setiap posisi.

Permutation

  • One-point crossover

Crossover satu titik hanya menggunakan satu titik acak sebagai penanda dimana seharusnya crossover terjadi.

Nearchou, 2014

  • Position-Based crossover

Sejumlah posisi dari induk pertama dipilih secara acak dan mengisi posisi yang tersisa dari induk kedua.

Nearchou, 2014

Mutation

Mutasi berarti perubahan pada satu atau beberapa gen di dalam kromosom. Seperti halnya di alam, mutasi jarang terjadi, sehingga kemungkinannya kecil untuk terjadi, biasanya ditetapkan pada 0,01. Ada beberapa contoh mutasi:

Binary

  • Random Mutation

Gen dibalik secara acak dari 1 ke 0 atau sebaliknya dengan probabilitas seragam untuk setiap gen, artinya setiap gen memiliki peluang yang sama untuk bermutasi dengan tingkat mutasi atau probabilitas mutasi yang ditetapkan oleh pengguna.

Real Valued

  • Mutasi Acak Seragam

Gen bermutasi secara acak pada posisi dengan nilai dari kisaran tertentu dengan distribusi seragam.

  • Mutasi Acak Tidak Seragam

Pilih gen/posisi acak dari sebuah kromosom dan berikan nilai acak yang tidak seragam padanya.

Permutation

  • Mutasi Pertukaran Berdekatan

Dua gen yang berdekatan ditukar secara acak. Nearchou, 2014

  • Mutasi Terbalik

Mutasi terbalik dilakukan dengan cara membalik barisan antara dua titik acak. Nearchou, 2014

Application

Algoritma Genetika dapat diterapkan dalam berbagai permasalahan bisnis. Karena algoritme adalah tentang pengoptimalan, selama ada fungsi tujuan/kebugaran untuk dioptimalkan, GA dapat diterapkan. Beberapa contoh termasuk penjadwalan produksi, masalah knapsack (berapa banyak/berapa banyak barang yang dapat kami muat di tas atau truk kami untuk memaksimalkan ruang atau beban berat), masalah traveling salesman, dll. GA juga dapat diterapkan di bidang ilmu data, khususnya ketika kami melakukan penyetelan hyper-parameter untuk mengoptimalkan kinerja model. Masalah tertentu mungkin memiliki pengaturan parameter uniknya sendiri yang dapat memiliki nilai kebugaran yang lebih baik. Misalnya, masalah penjadwalan flowshop telah diteliti oleh Nearchou3. Gambaran detail dan penerapan GA dapat dilihat di karya Kramer4

Illustration

Mari kita ilustrasikan langkah demi langkah bagaimana Algoritma Genetika bekerja. Katakanlah saya memiliki satu set item dengan poin dan berat masing-masing (kg). Saya ingin memaksimalkan poin, tetapi aturan mengatakan bahwa saya tidak dapat membawa barang dengan berat total lebih dari 15 kg. Item mana yang harus saya pilih untuk memaksimalkan poin saya?

Define Encoding and Fitness Function

Karena masalahnya adalah apakah membawa item tertentu atau tidak, maka solusinya harus terdiri dari nilai logis dari setiap item. Jika nilainya 1 atau TRUE, maka saya harus membawa item tersebut, dan jika nilainya 0 atau FALSE, maka saya tidak boleh membawa item tersebut. Dengan logika ini, pengkodean untuk algoritma genetika kita harus pengkodean biner karena hanya terdiri dari 2 nilai (1 atau 0).

Fungsi kebugaran kemudian, harus memaksimalkan titik total.

\[Maximize \ Total\ Point = \Sigma_{i=1}^{k} \ P_k \ * \ chosen_k \]

  • \(Total\ Poin\): Total poin yang dihasilkan
  • \(k\): Jumlah barang
  • \(P_k\): Titik item k
  • \(chosen_k\): nilai logika setiap item (1 atau 0)

Batasan:

\[Total\ Weight <= 15\]

Karena kita memiliki kendala, kita perlu memasukkan kendala ke dalam fungsi fitness. Hal ini dilakukan untuk mencegah solusi yang tidak layak (yang melanggar batasan) untuk dipilih atau dipilih sebagai nilai optimal.

\[Maximize\ Total\ Point = \Sigma_{i=1}^{k} \ P_k \ chosen_k - 2 \ (total\ weight - 15)^2\]

Initial Population

Pertama, GA akan membangkitkan populasi solusi secara acak. Di sini, kami memiliki populasi 4 solusi/individu. Nilai biner pada setiap individu dibangkitkan secara acak.

Fitness Assignment

Now we calculate the fitness value (total point) of each individuals based on the fitness function above. We can see that solution that violate the constraint (have total weight > 15) will have worse fitness function.

Selection

Sekarang kita akan memilih individu mana yang akan disilangkan untuk mendapatkan solusi baru. Kami akan menggunakan pemilihan peringkat linier untuk memilih solusi induk. Kami akan memilih 2 solusi karena crossover membutuhkan 2 solusi parent.

Pemilihan peringkat linier bekerja sebagai berikut:

  • Berikan peringkat untuk setiap individu. Peringkat yang lebih besar diberikan untuk fungsi kebugaran yang lebih besar. Individu 2 dengan fungsi fitness 19 adalah yang tertinggi, sehingga akan diberikan rangking 4.
  • Hitung probabilitas setiap individu berdasarkan peringkat mereka. Jumlah peringkatnya adalah 10, jadi probabilitas setiap solusi adalah \(Rank_i/10\).
  • Pilih solusi secara acak berdasarkan probabilitas. Solusi dengan fungsi fitness yang lebih tinggi secara alami akan memiliki peluang lebih tinggi untuk dipilih.

Katakanlah misalnya, solusi pertama dan kedua dipilih sebagai parent

Crossover

Crossover berarti pertukaran gen antara dua orang tua dua membuat solusi baru yang disebut solusi anak. Kami akan menggunakan crossover titik tunggal di sini. Satu titik dipilih secara acak.

Katakanlah titik yang dipilih berada di antara posisi 2 dan 3. Untuk anak pertama akan memiliki gen pertama dan kedua dari induk pertama, sedangkan sisanya akan diisi dari induk kedua. Untuk anak kedua, sebaliknya, ia akan memiliki gen pertama dan kedua dari orang tua kedua, sedangkan sisanya akan diisi dari orang tua pertama.

Mutation

Mutasi adalah perubahan nilai pada gen. Di sini kita menggunakan mutasi acak. Ini akan secara acak menukar nilai gen. Jika gen di posisi 3 bernilai 1, maka akan diubah menjadi 0 dan sebaliknya. Mutasi jarang terjadi, probabilitasnya dapat diatur secara manual, tetapi umumnya orang menggunakan probabilitas mutasi 0,1.

Proses seleksi, crossover, dan mutasi akan diulangi dengan memilih induk yang berbeda sampai jumlah anak sama dengan ukuran populasi induk. Jika orang tua memiliki ukuran populasi 4, maka kita akan memiliki populasi anak-anak 4. ### Check Stopping Criteria

Setelah mutasi selesai, kami akan memeriksa apakah sudah waktunya untuk menghentikan algoritma. Sudahkah kita mencapai iterasi 100? Apakah dalam 10 iterasi terakhir nilai fitness optimal tidak berubah? Jika tidak, maka iterasi akan dilanjutkan. Setelah crossover dan mutasi, sekarang kami memiliki populasi induk 4 dan populasi anak-anak 4, jadi total kami sekarang memiliki 8 solusi. Untuk iterasi selanjutnya, kita hanya akan memilih 4 individu (sesuai dengan ukuran populasi awal) dari induk dan anak. Umumnya individu dengan nilai fitness tinggi akan dipilih, sehingga populasi berikutnya akan lebih baik dari yang terakhir.

Genetic Algorithm with R

Di sini kita akan mengilustrasikan bagaimana GA dapat bekerja menggunakan paket GA56 baik pada masalah bisnis maupun pada penyetelan hyper-parameter machine learning.

Business Application

Knapsack Problem

Misalkan kita memiliki beberapa item untuk dikirim ke pusat distribusi. Namun, truk kami hanya dapat memuat hingga total 10 ton atau 10.000 kg, jadi kami harus memilih barang mana yang akan dikirim dan memaksimalkan berat yang dimuat. Anda dapat langsung menyelesaikan masalah ini menggunakan Metode Knapsack, tetapi Anda juga dapat menggunakan Algoritma Genetika. Di sini kita akan melihat bagaimana GA menangani masalah yang dibatasi (berat tidak boleh melebihi 10 ton atau 10.000 kg).

df_item <- data.frame(item = c("Tires", "Bumper", "Engine", "Chasis", "Seat"),
                      freq = c(80,50,70,50,70),
                      weight = c(7, 17, 158, 100, 30))

df_item

Setiap barang memiliki jumlah dan beratnya masing-masing.

Mari kita buat dataset baru dengan satu observasi yang sesuai dengan hanya 1 item.

df_item_long <- df_item[rep(rownames(df_item), df_item$freq), c("item","weight")]
  
head(df_item_long)

Mari kita atur fungsi tujuan.

\[Total\ Weight = \Sigma_{i=1}^{k} \ W_k \ * \ n_k \]

  • \(Total\ Berat\): Berat total (kg)
  • \(k\): Jumlah item unik
  • \(W_k\): Berat barang k
  • \(n_k\): Jumlah item k yang dipilih

Batasan:

\[Total\ Weight <= 10000\]

We will translate the objective function into R function.

weightlimit <- 10000

evalFunc <- function(x) {
  # Subset only selected item based on the solution
  df <- df_item_long[x == 1, ]
  
  # calculate the total weight
  total_weight <- sum(df$weight)
  
  # Penalty if the total weight surpass the limit
  total_weight <- if_else(total_weight > weightlimit, 0, total_weight)
  
  return(total_weight)
}

Mari kita jalankan algoritmanya. Kami akan menggunakan pengkodean biner karena variabel keputusan logis (BENAR atau SALAH), dengan setiap bit mewakili nilai logis tunggal apakah item tersebut dipilih.

Parameter algoritma:

  • type: Jenis pengkodean gen, di sini kami menggunakan “biner”
  • fitness: Fungsi kebugaran yang akan dioptimalkan
  • nBits: Jumlah bit yang dihasilkan untuk variabel keputusan
  • pmutation: probabilitas mutasi, default 0.1
  • pcrossover: probabilitas crossover, default 0,8
  • seed: Nilai seed acak untuk mendapatkan hasil yang dapat direproduksi
  • elitisme: Jumlah solusi terbaik yang akan bertahan di setiap iterasi, defaultnya adalah 5% teratas dari populasi.
  • maxiter: Jumlah iterasi maksimal untuk menjalankan algoritme
  • popSize: Jumlah solusi dalam suatu populasi
  • run: Jumlah iterasi sebelum algoritma berhenti jika tidak ada perbaikan pada nilai fitness optimal
  • paralel: Apakah kita akan menggunakan komputasi paralel? Dapat berupa nilai logika atau jumlah inti yang digunakan
  • lower: Nilai terendah dari pengkodean permutasi
  • upper: Nilai tertinggi dari pengkodean permutasi
  • keepBest: Logis apakah kami menyimpan solusi terbaik di setiap generasi di slot terpisah

Ada beberapa opsi yang dapat dipilih untuk setiap proses GA. Misalnya, pemilihan induk default GA untuk biner adalah pemilihan peringkat linier. Metode default terperinci yang digunakan pada setiap penyandian dapat ditemukan di sini7.

Fungsi tic() dan toc() digunakan untuk mengukur waktu komputasi.

Di sini kita akan menjalankan algoritme genetika dengan pengkodean biner, fungsi fitnessnya adalah evalFunc() yang kita buat sebelumnya. Ukuran populasi (popSize) adalah 100 dengan iterasi maksimum (maxiter) juga 100. Maksimum run (run) tanpa perbaikan adalah 20, setelah itu algoritma akan dihentikan. Seed untuk reproduktifitas diatur pada 123. Karena kami menggunakan pengkodean biner, parameter nBits mengontrol jumlah bit yang dihasilkan. Karena setiap bit akan mewakili keputusan untuk setiap item, jumlah bit sama dengan jumlah baris dari df_item_long.

tic()
library(GA)
ga_knapsack <- ga(type = "binary", fitness = evalFunc, popSize = 100, 
                  maxiter = 100, 
                  run = 20, nBits = nrow(df_item_long), seed = 123)
toc()
## 0.39 sec elapsed

Iterasi berhenti pada iterasi 22 karena setelah 20 iterasi tidak ada perbaikan pada total weight. Solusi optimal dapat dilihat pada Nilai fungsi kebugaran yaitu 10.000 (10.000 kg).

Keluaran Solution adalah solusi yang menghasilkan nilai fitness yang optimal. Kita dapat memilih salah satunya karena akan menghasilkan nilai fitness yang sama. Mari kita periksa salah satunya.

summary(ga_knapsack)
## -- Genetic Algorithm ------------------- 
## 
## GA settings: 
## Type                  =  binary 
## Population size       =  100 
## Number of generations =  100 
## Elitism               =  5 
## Crossover probability =  0.8 
## Mutation probability  =  0.1 
## 
## GA results: 
## Iterations             = 22 
## Fitness function value = 10000 
## Solutions = 
##      x1 x2 x3 x4 x5 x6 x7 x8 x9 x10  ...  x319 x320
## [1,]  1  1  0  1  1  1  1  0  1   1          0    1
## [2,]  1  1  0  1  1  1  1  0  1   1          1    1
## [3,]  0  1  1  1  1  0  0  1  1   0          0    0
## [4,]  1  1  0  1  1  1  1  0  1   1          1    1
## [5,]  1  1  0  1  1  1  1  0  1   1          1    0

Solusi terburuk dalam populasi memiliki nilai fitness 0 (mereka yang terkena penalti karena melewati batasan batas bobot), sedangkan solusi optimal memiliki nilai fitness 10000. Dari semua 100 kromosom atau solusi dihasilkan, median nilai fitness adalah 9572 sedangkan meannya adalah 6733.

Untuk memeriksa ringkasan, kami menggunakan fungsi summary() pada objek GA

summary(ga_knapsack)
## -- Genetic Algorithm ------------------- 
## 
## GA settings: 
## Type                  =  binary 
## Population size       =  100 
## Number of generations =  100 
## Elitism               =  5 
## Crossover probability =  0.8 
## Mutation probability  =  0.1 
## 
## GA results: 
## Iterations             = 22 
## Fitness function value = 10000 
## Solutions = 
##      x1 x2 x3 x4 x5 x6 x7 x8 x9 x10  ...  x319 x320
## [1,]  1  1  0  1  1  1  1  0  1   1          0    1
## [2,]  1  1  0  1  1  1  1  0  1   1          1    1
## [3,]  0  1  1  1  1  0  0  1  1   0          0    0
## [4,]  1  1  0  1  1  1  1  0  1   1          1    1
## [5,]  1  1  0  1  1  1  1  0  1   1          1    0

Seperti yang kita lihat, ada beberapa solusi untuk masalah tersebut, semuanya memiliki nilai fitness yang sama: 10000. Oleh karena itu, kami memiliki fleksibilitas untuk memilih solusi mana yang akan kami pilih. Semua solusi optimal tidak memiliki nilai lebih besar dari kendalanya, yang juga 10.000.

Mari kita periksa plot progresi nilai fitness menggunakan plot().

plot(ga_knapsack)

Kita dapat melihat bahwa solusi optimal (Best) sudah tercapai pada iterasi 3.

Mari kita pilih salah satu solusi sebagai keputusan kita.

df_sol <- df_item_long[ga_knapsack@solution[1, ] == 1, ]

df_sol <- df_sol %>%  
  group_by(item, weight) %>% 
  summarise(freq = n()) %>% 
  mutate(total_weigth = freq * weight)
## `summarise()` has grouped output by 'item'. You can override using the `.groups` argument.
df_sol

Mari kita periksa berat totalnya

sum(df_sol$total_weigth)
## [1] 10000
ga_knapsack@solution
##      x1 x2 x3 x4 x5 x6 x7 x8 x9 x10 x11 x12 x13 x14 x15 x16 x17 x18 x19 x20 x21
## [1,]  1  1  0  1  1  1  1  0  1   1   1   0   1   0   1   1   1   1   1   0   1
## [2,]  1  1  0  1  1  1  1  0  1   1   1   0   1   0   1   1   1   1   1   0   1
## [3,]  0  1  1  1  1  0  0  1  1   0   1   1   1   0   1   1   0   1   1   0   1
## [4,]  1  1  0  1  1  1  1  0  1   1   1   0   1   1   1   0   1   1   1   0   1
## [5,]  1  1  0  1  1  1  1  0  1   1   1   0   1   0   1   1   1   1   1   0   1
##      x22 x23 x24 x25 x26 x27 x28 x29 x30 x31 x32 x33 x34 x35 x36 x37 x38 x39
## [1,]   0   1   1   1   0   0   1   1   0   0   0   1   0   0   0   1   0   0
## [2,]   0   1   1   1   0   0   1   1   0   0   0   1   0   0   0   1   0   0
## [3,]   0   1   1   1   1   1   1   1   0   1   1   1   1   0   0   1   0   1
## [4,]   0   1   1   1   0   0   1   1   0   0   0   1   0   0   0   1   0   0
## [5,]   0   1   1   1   0   0   1   1   0   0   0   1   0   0   0   1   0   0
##      x40 x41 x42 x43 x44 x45 x46 x47 x48 x49 x50 x51 x52 x53 x54 x55 x56 x57
## [1,]   0   1   1   1   0   0   1   1   0   0   0   1   0   1   0   1   0   0
## [2,]   0   1   1   1   0   0   1   1   0   0   0   1   0   1   0   1   0   0
## [3,]   1   1   1   0   0   0   0   1   0   0   0   1   0   1   0   1   0   0
## [4,]   0   1   1   1   0   0   1   1   0   0   0   1   0   1   0   1   0   0
## [5,]   0   1   1   1   0   0   1   1   0   0   0   1   0   1   0   1   0   0
##      x58 x59 x60 x61 x62 x63 x64 x65 x66 x67 x68 x69 x70 x71 x72 x73 x74 x75
## [1,]   1   0   0   1   1   0   0   1   1   1   0   1   0   0   1   1   1   1
## [2,]   1   0   0   1   1   0   0   1   1   1   0   1   0   0   1   1   1   1
## [3,]   1   0   0   1   1   0   0   1   1   1   0   1   0   0   1   1   1   1
## [4,]   1   0   0   1   1   0   0   1   1   1   0   1   0   0   1   1   1   1
## [5,]   1   0   0   1   1   0   0   1   1   1   0   1   0   0   1   1   1   1
##      x76 x77 x78 x79 x80 x81 x82 x83 x84 x85 x86 x87 x88 x89 x90 x91 x92 x93
## [1,]   1   1   1   1   1   0   0   0   1   1   1   1   1   0   1   0   0   0
## [2,]   1   1   1   1   1   0   0   0   1   1   1   1   1   0   1   0   0   0
## [3,]   1   1   1   1   1   0   0   0   1   1   1   1   1   0   1   0   0   0
## [4,]   1   1   1   1   1   0   0   0   1   1   1   1   1   0   1   0   0   0
## [5,]   1   1   1   1   1   0   0   0   1   1   1   1   1   0   1   0   0   0
##      x94 x95 x96 x97 x98 x99 x100 x101 x102 x103 x104 x105 x106 x107 x108 x109
## [1,]   0   0   0   1   1   0    0    1    1    1    1    0    1    1    1    0
## [2,]   0   0   0   1   1   0    0    1    1    1    1    0    1    1    1    0
## [3,]   0   0   0   1   1   0    0    1    1    0    1    0    0    0    1    1
## [4,]   0   0   0   1   1   0    0    1    1    1    1    0    1    1    1    0
## [5,]   0   0   0   1   1   0    0    1    1    1    1    0    1    1    1    0
##      x110 x111 x112 x113 x114 x115 x116 x117 x118 x119 x120 x121 x122 x123 x124
## [1,]    0    0    0    0    0    1    1    1    0    1    0    0    0    0    0
## [2,]    0    0    0    0    0    1    1    1    0    1    0    0    0    0    0
## [3,]    1    1    1    0    0    1    0    0    0    0    0    0    0    0    0
## [4,]    0    0    0    0    0    1    1    1    0    1    0    0    0    0    0
## [5,]    0    0    0    0    0    1    1    1    0    1    0    0    0    0    0
##      x125 x126 x127 x128 x129 x130 x131 x132 x133 x134 x135 x136 x137 x138 x139
## [1,]    0    0    0    0    1    1    0    0    1    1    1    1    0    0    1
## [2,]    0    0    0    0    1    1    0    0    1    1    1    1    0    0    1
## [3,]    1    1    1    0    0    0    1    1    1    1    0    1    1    0    1
## [4,]    0    0    0    0    1    1    0    0    1    1    1    1    0    0    1
## [5,]    0    0    0    0    1    1    0    0    1    1    1    1    0    0    1
##      x140 x141 x142 x143 x144 x145 x146 x147 x148 x149 x150 x151 x152 x153 x154
## [1,]    1    0    0    1    1    1    1    0    0    1    0    1    1    0    0
## [2,]    1    0    0    1    1    1    1    0    0    1    0    1    1    0    0
## [3,]    1    1    1    0    0    0    0    1    0    0    1    1    1    0    0
## [4,]    1    0    0    1    1    1    1    0    0    1    0    1    1    0    0
## [5,]    1    0    0    1    1    1    1    0    0    1    0    1    1    0    0
##      x155 x156 x157 x158 x159 x160 x161 x162 x163 x164 x165 x166 x167 x168 x169
## [1,]    1    1    1    0    1    0    0    1    0    0    1    1    0    1    0
## [2,]    1    1    1    0    1    0    0    1    0    0    1    1    0    1    0
## [3,]    0    1    0    0    0    1    0    0    1    1    1    0    0    0    1
## [4,]    1    1    1    0    1    0    0    1    0    0    1    1    0    1    0
## [5,]    1    1    1    0    1    0    0    1    0    0    1    1    0    1    0
##      x170 x171 x172 x173 x174 x175 x176 x177 x178 x179 x180 x181 x182 x183 x184
## [1,]    0    0    1    1    0    0    1    1    0    0    0    1    1    0    0
## [2,]    0    0    1    1    0    0    1    1    0    0    0    1    1    0    0
## [3,]    1    1    1    0    1    0    0    1    1    1    0    1    0    0    1
## [4,]    0    0    1    1    0    0    1    1    0    0    0    1    1    0    0
## [5,]    0    0    1    1    0    0    1    1    0    0    0    1    1    0    0
##      x185 x186 x187 x188 x189 x190 x191 x192 x193 x194 x195 x196 x197 x198 x199
## [1,]    1    1    0    0    0    0    0    0    1    1    0    0    0    0    1
## [2,]    1    1    0    0    0    0    0    0    1    1    0    0    0    0    1
## [3,]    0    0    0    0    0    1    0    0    1    0    1    1    1    0    1
## [4,]    1    1    0    0    0    0    0    0    1    1    0    0    0    0    1
## [5,]    1    1    0    0    0    0    0    0    1    1    0    0    0    0    1
##      x200 x201 x202 x203 x204 x205 x206 x207 x208 x209 x210 x211 x212 x213 x214
## [1,]    1    1    1    1    1    1    1    1    1    0    1    1    1    1    0
## [2,]    1    1    1    1    1    1    1    1    1    0    1    1    1    1    0
## [3,]    0    1    0    1    0    0    1    1    0    1    1    1    1    1    1
## [4,]    1    1    1    1    1    1    1    1    1    0    1    1    1    1    0
## [5,]    1    1    1    1    1    1    1    1    1    0    1    1    1    1    0
##      x215 x216 x217 x218 x219 x220 x221 x222 x223 x224 x225 x226 x227 x228 x229
## [1,]    1    1    0    1    1    1    1    1    0    1    1    1    0    0    1
## [2,]    1    1    0    1    1    1    1    1    0    1    1    1    0    0    1
## [3,]    0    1    0    0    0    1    0    1    1    1    1    0    0    0    0
## [4,]    1    1    0    1    1    1    1    1    0    1    1    1    0    0    1
## [5,]    1    1    0    1    1    1    1    1    0    1    1    1    0    0    1
##      x230 x231 x232 x233 x234 x235 x236 x237 x238 x239 x240 x241 x242 x243 x244
## [1,]    1    0    0    1    1    0    1    0    1    1    0    0    0    0    1
## [2,]    1    0    0    1    1    0    1    0    1    1    0    0    0    0    1
## [3,]    1    0    1    1    0    0    0    1    0    0    1    1    0    1    1
## [4,]    1    0    0    1    1    0    1    0    1    1    0    0    0    0    1
## [5,]    1    0    0    1    1    0    1    0    1    1    0    0    0    0    1
##      x245 x246 x247 x248 x249 x250 x251 x252 x253 x254 x255 x256 x257 x258 x259
## [1,]    0    0    0    0    1    1    1    1    0    1    0    0    1    0    1
## [2,]    0    0    0    0    1    1    1    1    0    1    0    0    1    0    1
## [3,]    0    1    0    1    1    0    1    1    0    1    1    1    1    1    1
## [4,]    0    0    0    0    1    1    1    1    0    1    0    0    1    0    1
## [5,]    0    0    0    0    1    1    1    1    0    1    0    0    1    0    1
##      x260 x261 x262 x263 x264 x265 x266 x267 x268 x269 x270 x271 x272 x273 x274
## [1,]    1    0    1    0    0    1    0    1    1    0    0    0    1    0    0
## [2,]    1    0    1    0    0    1    0    1    1    0    0    0    1    1    1
## [3,]    1    0    1    0    1    1    0    0    0    1    0    1    0    0    0
## [4,]    1    0    1    0    0    1    0    1    1    0    0    0    1    1    1
## [5,]    1    0    1    0    1    1    0    0    0    1    0    1    0    0    0
##      x275 x276 x277 x278 x279 x280 x281 x282 x283 x284 x285 x286 x287 x288 x289
## [1,]    1    0    1    1    1    0    1    1    0    1    0    0    0    0    1
## [2,]    0    1    0    0    0    0    0    0    1    0    1    1    0    0    0
## [3,]    0    0    1    0    1    1    0    0    0    0    0    0    0    0    1
## [4,]    0    1    0    0    0    0    0    0    1    0    1    1    0    0    0
## [5,]    0    0    1    0    1    1    0    1    1    1    0    0    0    0    1
##      x290 x291 x292 x293 x294 x295 x296 x297 x298 x299 x300 x301 x302 x303 x304
## [1,]    0    0    0    1    0    1    1    0    0    0    1    1    0    0    0
## [2,]    1    0    1    0    1    0    1    1    0    0    0    0    0    0    0
## [3,]    0    0    1    0    1    1    0    0    1    0    0    0    1    1    0
## [4,]    1    0    1    0    1    0    1    1    0    0    0    0    0    0    0
## [5,]    0    0    0    1    0    1    1    0    0    0    1    1    0    0    0
##      x305 x306 x307 x308 x309 x310 x311 x312 x313 x314 x315 x316 x317 x318 x319
## [1,]    1    0    0    0    1    1    1    0    0    0    1    0    0    0    0
## [2,]    0    0    0    0    0    1    1    1    1    0    0    1    0    1    1
## [3,]    1    1    1    1    1    1    0    1    1    1    1    1    0    1    0
## [4,]    0    0    0    0    0    1    1    1    1    0    0    1    0    1    1
## [5,]    1    0    0    0    1    1    1    0    1    1    0    0    0    0    1
##      x320
## [1,]    1
## [2,]    1
## [3,]    0
## [4,]    1
## [5,]    0

Nilai fitness yang optimal sama dengan constraintnya, sehingga kita dapat memuat kendaraan secara penuh sesuai dengan kapasitas maksimumnya.

Mari kita periksa solusi ketiga

df_sol <- df_item_long[ga_knapsack@solution[3, ] == 1, ]

df_sol <- df_sol %>%  
  group_by(item, weight) %>% 
  summarise(freq = n()) %>% 
  mutate(total_weigth = freq * weight)
## `summarise()` has grouped output by 'item'. You can override using the `.groups` argument.
df_sol
sum(df_sol$total_weigth)
## [1] 10000

Meskipun solusi pertama dan solusi kedua memiliki keputusan yang berbeda, fungsi fitness atau bobot totalnya sama, oleh karena itu keduanya merupakan solusi optimal dan keputusan untuk memilih mana yang harus digunakan sebenarnya diserahkan kepada pengambil keputusan.

Traveling Salesman Problem

Travelling Salesperson Problem (TSP) adalah salah satu masalah yang paling banyak dibahas dalam optimasi kombinatorial. Dalam bentuknya yang paling sederhana, pertimbangkan satu set n kota dengan jarak intra simetris yang diketahui, TSP melibatkan pencarian rute optimal untuk mengunjungi semua kota dan kembali ke titik awal sedemikian rupa sehingga jarak yang ditempuh diminimalkan. Himpunan solusi layak diberikan oleh jumlah total rute yang mungkin, yang sama dengan (n 1)!/2.

Pertimbangkan kumpulan data eurodist ini. Terdiri dari 21 x 21 matriks jarak antar kota. Jumlah total rute yang mungkin adalah (21 - 1)!/2 = 1216451004088320000. Mencoba setiap rute akan memakan banyak waktu. Pencarian acak sederhana mungkin gagal menemukan global optima.

cities <- as.matrix(eurodist)

head(cities)
##           Athens Barcelona Brussels Calais Cherbourg Cologne Copenhagen Geneva
## Athens         0      3313     2963   3175      3339    2762       3276   2610
## Barcelona   3313         0     1318   1326      1294    1498       2218    803
## Brussels    2963      1318        0    204       583     206        966    677
## Calais      3175      1326      204      0       460     409       1136    747
## Cherbourg   3339      1294      583    460         0     785       1545    853
## Cologne     2762      1498      206    409       785       0        760   1662
##           Gibraltar Hamburg Hook of Holland Lisbon Lyons Madrid Marseilles
## Athens         4485    2977            3030   4532  2753   3949       2865
## Barcelona      1172    2018            1490   1305   645    636        521
## Brussels       2256     597             172   2084   690   1558       1011
## Calais         2224     714             330   2052   739   1550       1059
## Cherbourg      2047    1115             731   1827   789   1347       1101
## Cologne        2436     460             269   2290   714   1764       1035
##           Milan Munich Paris Rome Stockholm Vienna
## Athens     2282   2179  3000  817      3927   1991
## Barcelona  1014   1365  1033 1460      2868   1802
## Brussels    925    747   285 1511      1616   1175
## Calais     1077    977   280 1662      1786   1381
## Cherbourg  1209   1160   340 1794      2196   1588
## Cologne     911    583   465 1497      1403    937

karena tujuannya adalah untuk meminimalkan jarak, nilai fitness akan bernilai negatif.

tsp_fitness <- function(x) {
  x <- c(x, x[1])
  
  # create from-to matrix 
  route <- embed(x, 2)[, 2:1]
 
 # Calculate distance
 distance <- -sum(cities[route])
 return(distance)
 }

Let’s run the algorithm.

tic()
ga_cities <- ga(type = "permutation", fitness = tsp_fitness, monitor = F,
         lower = 1, upper = nrow(cities), popSize = 100, maxiter = 5000, 
         run = 500, seed = 123)
summary(ga_cities)
## -- Genetic Algorithm ------------------- 
## 
## GA settings: 
## Type                  =  permutation 
## Population size       =  100 
## Number of generations =  5000 
## Elitism               =  5 
## Crossover probability =  0.8 
## Mutation probability  =  0.1 
## 
## GA results: 
## Iterations             = 1739 
## Fitness function value = -12919 
## Solutions = 
##      x1 x2 x3 x4 x5 x6 x7 x8 x9 x10  ...  x20 x21
## [1,] 15  2  9 12 14  5 18  4  3  11         8  13
## [2,]  5 18  4  3 11  7 20 10  6  17        12  14
## [3,]  1 21 17  6 10 20  7 11  3   4        16  19
## [4,]  6 10 20  7 11  3  4 18  5  14        21  17
## [5,]  1 19 16  8 13 15  2  9 12  14        17  21
toc()
## 10.5 sec elapsed

Kami memiliki 5 solusi/rute yang memiliki jarak minimal. Ubah informasi menjadi rute yang dapat dibaca

route_optimal <- ga_cities@solution

cities_name <- names(cities[1 , ])

cities_name <- apply(route_optimal, 1, function(x) {cities_name[x]})

apply(cities_name, 2, paste, collapse = " > ")
## [1] "Marseilles > Barcelona > Gibraltar > Lisbon > Madrid > Cherbourg > Paris > Calais > Brussels > Hook of Holland > Copenhagen > Stockholm > Hamburg > Cologne > Munich > Vienna > Athens > Rome > Milan > Geneva > Lyons"
## [2] "Cherbourg > Paris > Calais > Brussels > Hook of Holland > Copenhagen > Stockholm > Hamburg > Cologne > Munich > Vienna > Athens > Rome > Milan > Geneva > Lyons > Marseilles > Barcelona > Gibraltar > Lisbon > Madrid"
## [3] "Athens > Vienna > Munich > Cologne > Hamburg > Stockholm > Copenhagen > Hook of Holland > Brussels > Calais > Paris > Cherbourg > Madrid > Lisbon > Gibraltar > Barcelona > Marseilles > Lyons > Geneva > Milan > Rome"
## [4] "Cologne > Hamburg > Stockholm > Copenhagen > Hook of Holland > Brussels > Calais > Paris > Cherbourg > Madrid > Lisbon > Gibraltar > Barcelona > Marseilles > Lyons > Geneva > Milan > Rome > Athens > Vienna > Munich"
## [5] "Athens > Rome > Milan > Geneva > Lyons > Marseilles > Barcelona > Gibraltar > Lisbon > Madrid > Cherbourg > Paris > Calais > Brussels > Hook of Holland > Copenhagen > Stockholm > Hamburg > Cologne > Munich > Vienna"

Better representation for the route can be visualized through network or graph.

Business Application

Production Scheduling

Misalkan kita memiliki berbagai unit mobil yang akan diproduksi di pabrik kita. Ada 7 jenis warna produk yang berbeda. Jika unit berikut memiliki warna yang berbeda dari mobil sebelumnya, maka akan ada biaya changeover (biaya yang timbul karena perubahan jenis produk), seperti biaya material yang dibutuhkan untuk membersihkan peralatan dari warna cat sebelumnya. Ada banyak cara untuk mengoptimalkan urutan warna, tetapi kami akan menggunakan yang paling sederhana di sini. Kami ingin meminimalkan jumlah setup, membuat jumlah pergantian warna atau pergantian terjadi lebih sedikit, sehingga biaya pergantian juga dapat diminimalkan.

Pertama kita impor datanya. Saya sudah menyiapkan data dummy yang bisa Anda akses di sini.

df_car <- read.csv("data_input/car_data.csv")

df_car

The objective function will be: \[ S = 1 + \Sigma_{k=2}^{DT} \ S_k\]

  • \(S\) = jumlah pengaturan
  • \(D_T\) = Jumlah mobil/unit yang akan diproduksi
  • \(k\) = Posisi mobil dalam barisan
  • \(S_k\)= Apakah pengaturan diperlukan? 1 jika mobil berikutnya (K+1) tidak memiliki warna yang sama dengan mobil ke-k saat ini, 0 jika mobil berikutnya berwarna

Kami akan menerjemahkan fungsi tujuan menjadi fungsi R dari min_setup. Algoritma dalam paket GA dijalankan dalam konteks maksimisasi, jadi untuk menyelesaikan masalah minimasi, kita akan membuat nilai fitness kita menjadi negatif.

min_setup <- function(x){
  df <- df_car[x, ]
  print(df)
  S <- df %>% 
  mutate(color_next = lead(color),
         setup = if_else(color == color_next, 0, 1)) %>% 
  head(nrow(df)-1) %>% 
  pull("setup") %>% 
  sum() + 1
  S <- -S
  return(S)
}

Sekarang kita jalankan algoritmanya. Karena masalahnya adalah masalah penjadwalan, kami akan mengkodekan solusi kami dengan pengkodean permutasi. Kami akan menghentikan algoritma jika jumlah iterasi telah mencapai 1000 iterasi atau jika dalam 100 iterasi tidak ada peningkatan nilai fitness yang optimal.

PENTING Pemilihan ukuran populasi dan jumlah iterasi akan sangat mempengaruhi kinerja GA. Jika ukuran populasi terlalu kecil, akan ada kumpulan gen yang terbatas dalam populasi kita sehingga solusi baru tidak akan terlalu berbeda jauh dari generasi sebelumnya. Jika jumlah iterasi terlalu sedikit, tidak akan ada cukup waktu bagi algoritma untuk menemukan solusi optimal baru. Paket GA dapat dijalankan dengan komputasi paralel.

Parameter algoritma:

  • type: Jenis pengkodean gen, di sini kita menggunakan “permutasi”
  • kebugaran: Fungsi kebugaran yang akan dioptimalkan
  • lebih rendah: Nilai terendah dari pengkodean permutasi, kami akan mengkodekan permutasi dari 1 ke jumlah mobil yang tersedia
  • atas: Nilai tertinggi dari pengkodean permutasi
  • seed: Nilai seed acak untuk mendapatkan hasil yang dapat direproduksi
  • elitism : Jumlah solusi terbaik yang akan bertahan di setiap iterasi, default adalah 5% teratas dari populasi, di sini kami menetapkannya menjadi 50 solusi yang akan bertahan
  • maxiter: Jumlah iterasi maksimal untuk menjalankan algoritma
  • popSize: Jumlah solusi dalam suatu populasi
  • run: Jumlah iterasi sebelum algoritma berhenti jika tidak ada perbaikan pada nilai fitness optimal
  • paralel: Apakah kita akan menggunakan komputasi paralel? Dapat berupa nilai logika atau jumlah inti yang digunakan

Parameter lainnya, seperti probabilitas crossover dan mutasi diserahkan ke pengaturan default.

tic()
gann <- ga(type = "permutation", fitness = min_setup, lower = 1, upper = nrow(df_car), 
    seed = 123, elitism = 50, maxiter = 300, popSize = 300, run = 30, parallel = parallel::detectCores())

summary(gann)
## -- Genetic Algorithm ------------------- 
## 
## GA settings: 
## Type                  =  permutation 
## Population size       =  300 
## Number of generations =  300 
## Elitism               =  50 
## Crossover probability =  0.8 
## Mutation probability  =  0.1 
## 
## GA results: 
## Iterations             = 91 
## Fitness function value = -6 
## Solutions = 
##       x1 x2 x3 x4 x5 x6 x7 x8 x9 x10  ...  x27 x28
## [1,]  24  4 11 18 16 20  1  8  3  23        14  22
## [2,]   4 24 11 18  3 16 20  1  8  23        14  22
## [3,]   4 11 24 18 16 20  1  8  3  23        14  22
## [4,]   4 11 24 18 20  1  8  3 23  16        14  22
## [5,]   3 16 20  1  2 23  8 27  9  19        11  18
## [6,]   4 24 11 18  3 16 20  1  8  23        22  14
## [7,]   3 16 20  1  2 23  8 27  9  19        11  18
## [8,]  14 22  4 11 24 18  3 16 20   1        15  21
## [9,]   4 11 24 18  3 16 20  1  8  23        14  22
## [10,]  3 16 20  1  2 23  8 27  9  19        11  18
##  ...                                              
## [18,] 11 24  4 18  3 16 20  1  8  23        14  22
## [19,]  4 11 24 18 16 20  1  8 27   3        14  22
toc()
## 57.84 sec elapsed

Kita dapat memplot perkembangan algoritma dengan menggunakan fungsi plot() pada objek GA.

plot(gann)

Solusi terbaik atau optimal diperoleh setidaknya pada iterasi ke-67 dan tidak mengalami perbaikan sejak saat itu, sehingga algoritma berhenti setelah 30 iterasi pada iterasi ke-96.

Output Solution adalah solusi yang menghasilkan nilai fitness yang optimal. Kita dapat memilih salah satunya karena akan menghasilkan nilai fitness yang sama. Mari kita periksa salah satunya.

min_setup(gann@solution[1, ])
##     X       color
## 24 24        Gray
## 4   4        Gray
## 11 11        Gray
## 18 18        Gray
## 16 16       White
## 20 20       White
## 1   1       White
## 8   8       White
## 3   3       White
## 23 23       White
## 27 27       White
## 2   2       White
## 7   7      Silver
## 28 28      Silver
## 5   5      Silver
## 17 17      Silver
## 26 26      Silver
## 25 25      Silver
## 13 13      Silver
## 10 10      Silver
## 12 12       Black
## 6   6       Black
## 21 21       Black
## 15 15       Black
## 9   9      Orange
## 19 19      Orange
## 14 14 Solid White
## 22 22 Solid White
## [1] -6

Jika ingin melihat semua nilai fitness dari populasi terakhir, gunakan bisa menggunakan atribut fitness dari objek GA. Kami akan meringkas nilai fitness untuk mendapatkan distribusinya.

summary(gann@fitness)
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##  -22.00  -16.00  -13.00  -12.43   -8.00   -6.00

Solusi terburuk dalam populasi memiliki nilai fitness 25, sedangkan solusi optimal memiliki nilai fitness 7. Dari seluruh 300 kromosom atau solusi yang dihasilkan, median nilai fitness adalah 15 sedangkan meannya adalah 14,29.

Mari kita menempatkan urutan optimal untuk data kita. Data frame yang dihasilkan akan menjadi jadwal produksi kita. Karena ada banyak solusi, mari kita lihat dua solusi optimal pertama.

Solusi pertama:

df_car[gann@solution[1, ], ]

Solusi Kedua:

df_car[gann@solution[2, ], ]

Meskipun solusi pertama dan solusi kedua memiliki urutan yang berbeda, fungsi fitness atau jumlah setup adalah sama, oleh karena itu keduanya merupakan solusi optimal dan keputusan untuk memilih mana yang harus digunakan sebenarnya diserahkan kepada pengambil keputusan. , dalam hal ini, staf kontrol produksi.

Knapsack Problem

Misalkan kita memiliki beberapa item untuk dikirim ke pusat distribusi. Namun, truk kami hanya dapat memuat hingga total 10 ton atau 10.000 kg, jadi kami harus memilih barang mana yang akan dikirim dan memaksimalkan berat yang dimuat. Anda dapat langsung menyelesaikan masalah ini menggunakan Metode Knapsack, tetapi Anda juga dapat menggunakan Algoritma Genetika. Di sini kita akan melihat bagaimana GA menangani masalah yang dibatasi (berat tidak boleh melebihi 10 ton atau 10.000 kg).

df_item <- data.frame(item = c("Tires", "Bumper", "Engine", "Chasis", "Seat"), freq = c(80, 
    50, 70, 50, 70), weight = c(7, 17, 158, 100, 30))

df_item

Setiap item memiliki jumlah dan berat masing-masing.s

Mari kita buat dataset baru dengan satu observasi yang sesuai dengan hanya 1 item.

df_item_long <- df_item[rep(rownames(df_item), df_item$freq), c(1, 3)]

df_item_long

The objective function will be: \[ Total Weight= \Sigma_{i=1}^{k} \ W_k*n_k\]

  • \(Total Weight\) = Total berat
  • \(k\) = Jumlah data unik
  • \(W_k\) = berat data k
  • \(n_k\)= nomor item k yang dipilih

Batasan:

\[ Total Weight <= 10000 \]

weightlimit <- 10000

evalFunc <- function(x) {
    df <- df_item_long[x == 1, ]
    total_weight <- sum(df$weight)
    total_weight <- if_else(total_weight > weightlimit, 0, total_weight)
    return(total_weight)
}

Mari kita jalankan algoritmanya. Kami akan menggunakan pengkodean biner karena variabel keputusan bernilai integer. Setiap bit mewakili nilai logis tunggal apakah item tersebut dipilih.

tic()
gann2 <- ga(type = "binary", fitness = evalFunc, popSize = 100, maxiter = 100, run = 20, 
    nBits = nrow(df_item_long), seed = 123)

summary(gann2)
## -- Genetic Algorithm ------------------- 
## 
## GA settings: 
## Type                  =  binary 
## Population size       =  100 
## Number of generations =  100 
## Elitism               =  5 
## Crossover probability =  0.8 
## Mutation probability  =  0.1 
## 
## GA results: 
## Iterations             = 22 
## Fitness function value = 10000 
## Solutions = 
##      x1 x2 x3 x4 x5 x6 x7 x8 x9 x10  ...  x319 x320
## [1,]  1  1  0  1  1  1  1  0  1   1          0    1
## [2,]  1  1  0  1  1  1  1  0  1   1          1    1
## [3,]  0  1  1  1  1  0  0  1  1   0          0    0
## [4,]  1  1  0  1  1  1  1  0  1   1          1    1
## [5,]  1  1  0  1  1  1  1  0  1   1          1    0
toc()
## 0.49 sec elapsed
plot(gann2)

Mari pilih salah satu solusi sebagai pilihan optimal.

df_sol <- df_item_long[gann2@solution[1, ] == 1, ]

df_sol <- df_sol %>% group_by(item, weight) %>% summarise(freq = n()) %>% mutate(total_weigth = freq * 
    weight)
## `summarise()` has grouped output by 'item'. You can override using the `.groups` argument.
df_sol

Total berat:

sum(df_sol$total_weigth)
## [1] 10000

Nilai fitness yang optimal sama dengan constrainnya, sehingga kita dapat memuat kendaraan secara penuh sesuai dengan kapasitas maksimumnya.

Penerapan GA di berbagai bidang masih banyak, terutama dalam proses manufaktur dan engineering. Selama ada fungsi fitness dan variabel keputusan, GA dapat diterapkan terhadap masalah tersebut.

Hyper-Parameter Tuning on Machine Learning Model

Sekarang kami akan mencoba menerapkan algoritme untuk mengoptimalkan model pembelajaran mesin kami. Kami akan menggunakan dataset attrition.

Import Data

attrition <- read.csv("data_input/attrition.csv")

attrition <- attrition %>% mutate(job_involvement = as.factor(job_involvement), education = as.factor(education), 
    environment_satisfaction = as.factor(environment_satisfaction), performance_rating = as.factor(performance_rating), 
    relationship_satisfaction = as.factor(relationship_satisfaction), work_life_balance = as.factor(work_life_balance))

attrition

Cross-Validation

Kami membagi data menjadi training set dan testing dataset, dengan 80% data akan digunakan sebagai training set.

set.seed(123)
intrain <- initial_split(attrition, prop = 0.8, strata = "attrition")

intrain
## <Analysis/Assess/Total>
## <1175/295/1470>

Data Preprocessing

Kami akan melakukan preprocess data dengan langkah berikut:

  • Upsample untuk meningkatkan jumlah kelas minoritas dan mencegah ketidakseimbangan kelas
  • Menskalakan semua variabel numerik
  • Hapus variabel numerik dengan varian hampir nol
  • Gunakan PCA untuk mengurangi dimensi dengan ambang 90% informasi disimpan
  • Hapus variabel over_18 karena hanya memiliki 1 level faktor
rec <- recipe(attrition ~ ., data = training(intrain)) %>% 
  themis::step_upsample(attrition) %>% 
  step_scale(all_numeric()) %>% 
  step_nzv(all_numeric()) %>% 
  step_pca(all_numeric(), threshold = 0.9) %>% 
  step_rm(over_18) %>% 
  prep()
## Registered S3 methods overwritten by 'themis':
##   method                  from   
##   bake.step_downsample    recipes
##   bake.step_upsample      recipes
##   prep.step_downsample    recipes
##   prep.step_upsample      recipes
##   tidy.step_downsample    recipes
##   tidy.step_upsample      recipes
##   tunable.step_downsample recipes
##   tunable.step_upsample   recipes
data_train <- juice(rec)
data_test <- bake(rec, testing(intrain))

data_train

Create Fitness Function

Kami akan melatih model menggunakan hutan acak. Kami akan mengoptimalkan nilai mtry (jumlah node di setiap pohon) dan nilai min_n (jumlah minimum anggota di setiap pohon untuk dipecah) dengan memaksimalkan akurasi model pada kumpulan data pengujian. Karena jumlah mtry dan min_n adalah bilangan bulat, lebih baik kita menggunakan encoding biner daripada encoding real-valued.

Kami akan mengatur rentang mtry dari 1 hingga 32. Kami akan membatasi rentang min_n dari 1 hingga 128. Mari kita periksa berapa bit yang kita perlukan untuk menutupi angka-angka itu.

# max value of mtry = 32
2^5
## [1] 32
# max value of min_n = 128
2^7
## [1] 128

Jadi, kita membutuhkan setidaknya 12 bit biner. Jika nilai konversi bit untuk mtry atau min_n adalah 0, kami mengubahnya menjadi 1.

Kami akan menulis model sebagai fungsi kebugaran. Kami akan memaksimalkan akurasi model kami pada dataset pengujian.

fit_function <- function(x){
  a <- binary2decimal(x[1:5])
  b <- binary2decimal(x[6:12])
  
  if (a == 0) {
    a <- 1
  }
  if (b == 0) {
    b <- 1
  }
  if (a > 17) {
    a <- 17
  }
  
#define model spec
model_spec <- rand_forest(
  mode = "classification",
  mtry = a,
  min_n = b,
  trees = 500)

#define model engine
model_spec <- set_engine(model_spec,
                         engine = "ranger",
                         seed = 123,
                         num.threads = parallel::detectCores(),
                         importance = "impurity")

#model fitting
set.seed(123)
model <- fit_xy(
  object = model_spec,
  x = select(data_train, -attrition),
  y = select(data_train, attrition)
)

pred_test <- predict(model, new_data = data_test %>% select(-attrition))
acc <- accuracy_vec(truth = data_test$attrition, estimate = pred_test$.pred_class)
return(acc)
}

Run the Algorithm

IMPORTANT Karena beberapa model memerlukan banyak waktu untuk dilatih, Anda mungkin perlu ekstra hati-hati dalam memilih ukuran populasi dan jumlah iterasi. Jika Anda tidak memiliki masalah untuk menjalankan GA dalam jangka waktu yang lama, Anda dapat memilih ukuran populasi dan jumlah iterasi yang lebih besar. Di sini saya akan mengatur ukuran populasi menjadi hanya 100 dan jumlah maksimum iterasi adalah 100. Anda dapat menggunakan parallel = TRUE jika Anda ingin komputasi yang lebih cepat dengan komputasi paralel.

Kami akan mencoba menggunakan pemilihan turnamen alih-alih pemilihan peringkat linier default.

tic()
ga_rf <- ga(type = "binary", fitness = fit_function, nBits = 12, seed = 123, 
            popSize = 100, maxiter = 100, run = 10, parallel = T, 
            selection = gabin_tourSelection)
summary(ga_rf)
## -- Genetic Algorithm ------------------- 
## 
## GA settings: 
## Type                  =  binary 
## Population size       =  100 
## Number of generations =  100 
## Elitism               =  5 
## Crossover probability =  0.8 
## Mutation probability  =  0.1 
## 
## GA results: 
## Iterations             = 13 
## Fitness function value = 0.8576271 
## Solution = 
##      x1 x2 x3 x4 x5 x6 x7 x8 x9 x10 x11 x12
## [1,]  0  0  1  0  0  0  0  0  1   1   0   1
toc()
## 235.86 sec elapsed

Mari kita plot perkembangan GA

plot(ga_rf)

Mari kita periksa setiap variabel keputusan dan akurasi yang digunakan sebagai nilai fitness.

# Transform decision variables into data frame
ga_rf_pop <- as.data.frame(ga_rf@population)

# Convert decision variables into mtry and min_n
ga_mtry <- apply(ga_rf_pop[ , 1:5], 1, binary2decimal)
ga_min_n <- apply(ga_rf_pop[ , 6:12], 1, binary2decimal)

# Show the list of solutions in the final population
data.frame(mtry = ga_mtry,
           min_n = ga_min_n,
           accuracy = ga_rf@fitness) %>% 
  mutate(mtry = ifelse(mtry == 0, 1, mtry),
         min_n = ifelse(min_n == 0, 1, min_n),
         mtry = ifelse(mtry > 17, 17, mtry)) %>% 
  distinct() %>%
  arrange(desc(accuracy))

Compare with K-fold Cross Validation

Anda mungkin ingin membandingkan waktu komputasi GA dengan waktu yang diperlukan untuk validasi silang K-fold berulang menggunakan paket caret konvensional. Kami akan menggunakan k = 5 dan 10 pengulangan.

tic()
set.seed(123)
ctrl <- trainControl(method="repeatedcv", number=5, repeats=10)
attrition_forest <- train(attrition ~ ., data=data_train, method="rf", trControl = ctrl)
toc()
## 416.06 sec elapsed
attrition_forest
## Random Forest 
## 
## 1972 samples
##   17 predictor
##    2 classes: 'no', 'yes' 
## 
## No pre-processing
## Resampling: Cross-Validated (5 fold, repeated 10 times) 
## Summary of sample sizes: 1578, 1577, 1577, 1578, 1578, 1577, ... 
## Resampling results across tuning parameters:
## 
##   mtry  Accuracy   Kappa    
##    2    0.9513710  0.9027417
##   22    0.9646545  0.9293085
##   42    0.9578604  0.9157203
## 
## Accuracy was used to select the optimal model using the largest value.
## The final value used for the model was mtry = 22.

Mari kita periksa keakuratan model hutan acak dengan caret

pred_rf <- predict(attrition_forest, newdata = data_test)
accuracy_vec(truth = data_test$attrition, estimate = pred_rf)
## [1] 0.820339

Performa model oleh GA lebih baik daripada yang dioptimalkan dengan validasi silang K-fold sederhana, meskipun GA memerlukan lebih banyak waktu untuk menghitung. Pertukaran waktu komputasi vs kinerja ini harus dipertimbangkan.

Conclusion

Masalah optimasi adalah salah satu dari banyak perhatian dalam bisnis, yang mungkin berusaha untuk memaksimalkan keuntungan dan meminimalkan kerugian. Pembelajaran mesin menerapkan pengoptimalan pada metode model pelatihan mereka untuk mendapatkan hasil terbaik. GA merupakan algoritma optimasi yang dapat diterapkan di berbagai bidang, termasuk data science. Jika seorang ilmuwan data dapat memanfaatkan manfaat GA, ia bisa mendapatkan model yang lebih optimal dengan kinerja yang lebih baik. Kelemahan GA adalah membutuhkan waktu untuk menghitung karena termasuk dalam kelompok algoritma pencarian, di mana ia mengeksplorasi solusi yang mungkin secara efisien dibandingkan dengan pencarian acak.

What’s Next

Penelitian dalam algoritma evolusioner masih berkembang dalam beberapa tahun terakhir, beberapa membahas skema crossover dan mutasi terbaik, sementara yang lain meningkatkan metode. GA juga dapat diterapkan untuk optimasi multi-tujuan, di mana dua atau lebih tujuan yang saling bertentangan perlu dioptimalkan. Beberapa metode seperti NSGA-II atau SPEA2 merupakan metode GA populer yang dapat digunakan untuk melakukan optimasi multi-objektif. Banyak algoritma evolusioner multi-tujuan atau bahkan banyak-tujuan baru (yang memiliki lebih dari 3 tujuan) diturunkan dari dua metode ini8. Salah satu aplikasi yang paling menarik adalah di sektor manufaktur, terutama untuk masalah penjadwalan, di mana masalah optimasi kombinatorial yang kompleks perlu dipecahkan karena memiliki dampak yang besar terhadap produktivitas dan biaya9.

Reference

LS0tDQp0aXRsZTogIk9wdGltYXNpIg0Kc3VidGl0bGU6ICJ+IEFzc2lnbm1lbnQgV2VlayAxNCB+Ig0KYXV0aG9yOiAiVmFuZXNzYSBTVXBpdCINCmRhdGU6ICAiYHIgZm9ybWF0KFN5cy5EYXRlKCksICclQiAlZCwgJVknKWAiDQpvdXRwdXQ6DQogIHJtZGZvcm1hdHM6OnJlYWR0aGVkb3duOiAgICMgaHR0cHM6Ly9naXRodWIuY29tL2p1YmEvcm1kZm9ybWF0cw0KICAgIHNlbGZfY29udGFpbmVkOiB0cnVlDQogICAgdGh1bWJuYWlsczogdHJ1ZQ0KICAgIGxpZ2h0Ym94OiB0cnVlDQogICAgZ2FsbGVyeTogdHJ1ZQ0KICAgIGxpYl9kaXI6IGxpYnMNCiAgICBkZl9wcmludDogInBhZ2VkIg0KICAgIGNvZGVfZm9sZGluZzogInNob3ciDQogICAgY29kZV9kb3dubG9hZDogeWVzDQogICAgY3NzOiAic3R5bGUxLmNzcyINCi0tLQ0KIyBJbnRyb2R1Y3Rpb24gey50YWJzZXQgLnRhYnNldC1mYWRlIC50YWJzZXQtcGlsbHN9DQoNCiMjIEFib3V0DQoNCk9wdGltYXNpIHBlbnRpbmcgZGFsYW0gYmFueWFrIGJpZGFuZywgdGVybWFzdWsgZGFsYW0gaWxtdSBkYXRhLiBEYWxhbSBtYW51ZmFrdHVyLCBkaSBtYW5hIHNldGlhcCBrZXB1dHVzYW4gc2FuZ2F0IHBlbnRpbmcgdW50dWsgcHJvc2VzIGRhbiBrZXVudHVuZ2FuIG9yZ2FuaXNhc2ksIG9wdGltYXNpIHNlcmluZyBkaWd1bmFrYW4sIG11bGFpIGRhcmkganVtbGFoIHNldGlhcCBwcm9kdWsgeWFuZyBkaWhhc2lsa2FuLCBiYWdhaW1hbmEgdW5pdCBkaWphZHdhbGthbiB1bnR1ayBwcm9kdWtzaSwgbWVuZGFwYXRrYW4gcGFyYW1ldGVyIHByb3NlcyB0ZXJiYWlrIGF0YXUgb3B0aW1hbCwgZGFuIGp1Z2EgcGVydXRlYW4uIHBlbmVudHVhbiBzZXBlcnRpIG1hc2FsYWggc2FsZXNtYW4ga2VsaWxpbmcuIERhbGFtIGlsbXUgZGF0YSwga2FtaSBha3JhYiBkZW5nYW4gcGVueWV0ZWxhbiBtb2RlbCwgZGkgbWFuYSBrYW1pIG1lbnlldGVsIG1vZGVsIGthbWkgdW50dWsgbWVuaW5na2F0a2FuIGtpbmVyamEgbW9kZWwuIEFsZ29yaXRtYSBvcHRpbWFzaSBkYXBhdCBtZW1iYW50dSBraXRhIHVudHVrIG1lbmRhcGF0a2FuIHBlcmZvcm1hIG1vZGVsIHlhbmcgbGViaWggYmFpay4gQWxnb3JpdG1hIEdlbmV0aWthIChHQSkgbWVydXBha2FuIHNhbGFoIHNhdHUgYWxnb3JpdG1hIG9wdGltYXNpIHlhbmcgYmFueWFrIGRpZ3VuYWthbi4NCg0KQXJ0aWtlbCBpbmkgbWVuY29iYSBtZW5qZWxhc2thbiBtZWthbmlzbWUgZGkgYmFsaWsgc2FsYWggc2F0dSBhbGdvcml0bWEgeWFuZyBwYWxpbmcgZWZla3RpZiB1bnR1ayBtYXNhbGFoIG9wdGltYXNpOiBBbGdvcml0bWEgR2VuZXRpa2EgKEdBKS4gQWRhIGJhbnlhayBhbGdvcml0bWEgeWFuZyBraHVzdXMgZGlidWF0IHVudHVrIG9wdGltYXNpLCBzZXBlcnRpIFBhcnRpY2xlIFN3YXJtIE9wdGltaXphdGlvbiAoUFNPKSwgU2ltdWxhdGVkIEFubmVhbGluZyAoU0EpLCBkYW4gVGFidSBTZWFyY2guIE5hbXVuLCBkaSBiYW55YWsgYmlkYW5nLCBHQSBkYXBhdCBtZW5jYXBhaSB0dWp1YW4gbGViaWggYmFpayBkYXJpcGFkYSBtZXRvZGUgdGVyc2VidXQuIEFkYSBiZXJiYWdhaSB0dXJ1bmFuIGRhbiB2YXJpYXNpIGltcGxlbWVudGFzaSBHQSwgbXVuZ2tpbiB5YW5nIHBhbGluZyB0ZXJrZW5hbCBhZGFsYWggaW1wbGVtZW50YXNpIEdBIGRhbGFtIG1hc2FsYWggb3B0aW1hc2kgbXVsdGlvYmplY3RpdmUsIGRpIG1hbmEga2l0YSBpbmdpbiBtZW5nb3B0aW1hbGthbiBkdWEgdHVqdWFuIHNlY2FyYSBiZXJzYW1hYW4sIHlhbmcgZGlzZWJ1dCBNdWx0aSBPYmplY3RpdmUgRXZvbHV0aW9uYXJ5IEFsZ29yaXRobSAoTU9FQSkuIEtpdGEgYWthbiBtZWxpaGF0IGJhZ2FpbWFuYSBHQSB1bXVtIGJla2VyamEgZGFuIGRpIG1hbmEgaXR1IGRhcGF0IGRpdGVyYXBrYW4sIGJhaWsgcGFkYSBtYXNhbGFoIGJpc25pcyBtYXVwdW4gZGkgYmlkYW5nIGlsbXUgZGF0YS4NCg0KIyMgT2JqZWN0aXZlcw0KDQpUdWp1YW4gUGVtYmVsYWphcmFuOg0KDQoqIFBlbGFqYXJpIHBlbnRpbmdueWEgb3B0aW1hc2kgZGFsYW0gYmlzbmlzDQoqIFBlbGFqYXJpIHRlbnRhbmcgZnVuZ3NpIGJpYXlhL2Z1bmdzaSB0dWp1YW4gZGFuIHZhcmlhYmVsIGtlcHV0dXNhbg0KKiBQZWxhamFyaSBodWJ1bmdhbiBhbnRhcmEgcGVtYmVsYWphcmFuIG1lc2luIGRhbiBwZW5nb3B0aW1hbGFuDQoqIFBlbGFqYXJpIHByaW5zaXAgZGFuIGtvbnNlcCBBbGdvcml0bWEgR2VuZXRpa2ENCiogUGVsYWphcmkgaW1wbGVtZW50YXNpIEdBIGRhbGFtIGthc3VzIGJpc25pcyBkYW4gYmlkYW5nIGlsbXUgZGF0YQ0KDQojIyBMaWJyYXJ5IGFuZCBTZXR1cA0KDQpVbnR1ayBtZW5nZ3VuYWthbiBHQSwgQW5kYSBkYXBhdCBtZW5naW5zdGFsIHBha2V0IGBHQWAgbWVuZ2d1bmFrYW4gZnVuZ3NpIGBpbnN0YWxsLnBhY2thZ2VzKClgLg0KDQpCZXJpa3V0IGluaSBhZGFsYWggcGFrZXQtcGFrZXQgeWFuZyBha2FuIGRpZ3VuYWthbiBkaSBzZWx1cnVoIGFydGlrZWwuDQoNCmBgYHtyIG1lc3NhZ2U9RkFMU0V9DQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCmxpYnJhcnkoR0EpDQpsaWJyYXJ5KHJhbmdlcikNCmxpYnJhcnkodGlkeW1vZGVscykNCmxpYnJhcnkoY2FyZXQpDQpsaWJyYXJ5KHRpY3RvYykNCmxpYnJhcnkodHVmdGUpDQpsaWJyYXJ5KHJhbmRvbUZvcmVzdCkNCmBgYA0KDQojIE9wdGltaXphdGlvbiBQcm9ibGVtIHsudGFic2V0IC50YWJzZXQtZmFkZSAudGFic2V0LXBpbGxzfQ0KDQojIyBXaHkgT3B0aW1pemF0aW9uIGlzIEltcG9ydGFudA0KDQoNCkthdGFrYW5sYWgsIGthbWkgbWVtaWxpa2kgMiBsaW5pIHByb2R1aywgZGVuZ2FuIHByb2R1ayBBIG1lbWlsaWtpIGtldW50dW5nYW4gc2ViZXNhciBVU0QgNTAgc2VkYW5na2FuIHByb2R1ayBCIG1lbWlsaWtpIGtldW50dW5nYW4gc2ViZXNhciBVU0QgMzAuIEthbWkgaW5naW4gbWVtYWtzaW1hbGthbiBrZXVudHVuZ2FuIGthbWksIHRldGFwaSBrYW1pIG1lbWlsaWtpIHN1bWJlciBkYXlhIHlhbmcgdGVyYmF0YXMuIEthbWkgaGFueWEgbWVtaWxpa2kgODAgamFtIGtlcmphIHNlbGFtYSBzZW1pbmdndSwgZGVuZ2FuIHByb2R1ayBBIG1lbWFrYW4gd2FrdHUgc2VraXRhciAxMCBqYW0gZGFsYW0gcGVtYnVhdGFuIHNlZGFuZ2thbiBwcm9kdWsgQiBhZGFsYWggNCBqYW0gZGFsYW0gcGVtYnVhdGFuLiBLZWR1YSBwcm9kdWsgYmVyYmFnaSBiYWhhbiB5YW5nIHNhbWEgZGFuIGthbWkgaGFueWEgbWVtaWxpa2kgMTAwIG1ldGVyIGthaW4gZGVuZ2FuIG1hc2luZy1tYXNpbmcgcHJvZHVrIEEgbWVtYnV0dWhrYW4gMiBtZXRlciBrYWluIHNlZGFuZ2thbiBwcm9kdWsgQiBtZW1idXR1aGthbiA0IG1ldGVyIGthaW4uIEJhZ2FpbWFuYSBraXRhIG1lbXV0dXNrYW4gYmVyYXBhIGp1bWxhaCBwcm9kdWsgQSBkYW4gQiB0ZXJiYWlrIHlhbmcgYWthbiBkaXByb2R1a3NpPyBCZWdpdHVsYWggbWFzYWxhaCBvcHRpbWFzaSAoZGFsYW0gaGFsIGluaSwgb3B0aW1hc2kgcHJvZml0KS4NCg0KQmFnYWltYW5hIGppa2Ega2l0YSBtZW11dHVza2FuIHVudHVrIG1lbWlsaWggc2VjYXJhIGFjYWsganVtbGFoIHNldGlhcCBwcm9kdWsgeWFuZyBha2FuIGRpcHJvZHVrc2kuIEtpdGEgbXVuZ2tpbiB0aWRhayBwdW55YSBjdWt1cCB3YWt0dSB1bnR1ayBtZW55ZWxlc2Fpa2FubnlhLiBLYW1pIG11bmdraW4gdGlkYWsgbWVtaWxpa2kgY3VrdXAgYmFoYW4sIGF0YXUgc2ViYWxpa255YSBiaXNhIHRlcmphZGk6IGthbWkgbXVuZ2tpbiBtZW1pbGlraSBiYWhhbiBjYWRhbmdhbi4NCg0KS2FzdXMgbGFpbiwga2F0YWthbmxhaCBrYW1pIGluZ2luIG1lbmdpcmlta2FuIHByb2R1ayBrYW1pIGtlIGJlYmVyYXBhIGd1ZGFuZy4gQmFnYWltYW5hIGthbWkgbWVtdXR1c2thbiBndWRhbmcgbWFuYSB5YW5nIGhhcnVzIGthbWkga3VuanVuZ2kgdGVybGViaWggZGFodWx1IGF0YXUgYmFnYWltYW5hIGthbWkgbWVtdXR1c2thbiBydXRlIGFwYSB5YW5nIGhhcnVzIGthbWkgYW1iaWwgdW50dWsgbWVtaW5pbWFsa2FuIHdha3R1IHBlbmdpcmltYW4/DQoNCk9wdGltYWxpc2FzaSBwZW50aW5nIHRlcnV0YW1hIGppa2Ega2l0YSBtZW1pbGlraSBzdW1iZXIgZGF5YSB5YW5nIHRlcmJhdGFzLiBMZWJpaCBwZW50aW5nIGxhZ2ksIG9wdGltYXNpIG1lbXBlcmhhdGlrYW4ga2V1bnR1bmdhbiBkYW4ga2VydWdpYW46IG1lbWlsaWggcnV0ZSBwZW5naXJpbWFuIHlhbmcgc2FsYWggYWthbiBtZW5nYWtpYmF0a2FuIGtldGVybGFtYmF0YW4gcGVuZ2lyaW1hbiwgeWFuZyBkYXBhdCBtZW5pbWJ1bGthbiBiaWF5YSBwZW5hbHRpIGF0YXUgbWVydXNhayBwcm9kdWsga2FtaSBkaSBzZXBhbmphbmcgamFsYW4uIEl0dWxhaCBtZW5nYXBhIG9wdGltYXNpIHNhbmdhdCBwZW50aW5nIHVudHVrIGJpc25pcy4NCg0KIyMgT2JqZWN0aXZlIEZ1bmN0aW9uIGFuZCBEZWNpc2lvbiBWYXJpYWJsZXMNCg0KU2V0aWFwIG1hc2FsYWggb3B0aW1hc2kgc2VsYWx1IG1lbWlsaWtpIDIgZWxlbWVuOiBmdW5nc2kgdHVqdWFuIGRhbiB2YXJpYWJlbCBrZXB1dHVzYW4uIEZ1bmdzaSBvYmpla3RpZiBiZXJhcnRpIGJhZ2FpbWFuYSBraXRhIG1lcnVtdXNrYW4gdHVqdWFuIGtpdGEsIHNlcGVydGkgYmFnYWltYW5hIGtpdGEgbWVuZ3VrdXIga2V1bnR1bmdhbiB1bnR1ayBtZW1ha3NpbWFsa2FubnlhLCBhdGF1IGJhZ2FpbWFuYSBraXRhIG1lbmd1a3VyIHdha3R1IHBlbmdpcmltYW4gdW50dWsgbWVtaW5pbWFsa2FubnlhLiBEaSBkYWxhbSBmdW5nc2kgdHVqdWFuIHRlcmRhcGF0IHZhcmlhYmVsIGtlcHV0dXNhbiwgYXBhIHlhbmcgaGFydXMgbWVuamFkaSBrZXB1dHVzYW4ga2l0YSB5YW5nIGRhcGF0IG1lbmdoYXNpbGthbiBrZXVudHVuZ2FuIG1ha3NpbWFsPyBLYW1pIGluZ2luIG1lbWFrc2ltYWxrYW4gdHVqdWFuIGthbWkgZGVuZ2FuIG1lbWlsaWggdmFyaWFiZWwga2VwdXR1c2FuIHlhbmcgdGVwYXQuIFBhZGEgY29udG9oIHNlYmVsdW1ueWEsIHR1anVhbiB1bnR1ayBtZW1ha3NpbWFsa2FuIGxhYmEgdGVybWFzdWsga2UgZGFsYW0gZnVuZ3NpIHR1anVhbiwgc2VkYW5na2FuIGp1bWxhaCBwcm9kdWsgQSBkYW4gcHJvZHVrIEIgeWFuZyBha2FuIGRpcHJvZHVrc2kgdGVybWFzdWsgZGFsYW0gdmFyaWFiZWwga2VwdXR1c2FuLg0KDQpCZWJlcmFwYSB0dWp1YW4gbXVuZ2tpbiBtZW1pbGlraSBrZW5kYWxhLCBzZXBlcnRpIGp1bWxhaCBqYW0ga2VyamEgc2VtaW5nZ3UgaGFueWEgODAgamFtIGF0YXUganVtbGFoIGJhaGFuIHlhbmcgdGVyc2VkaWEgaGFueWEgMTAwIG1ldGVyLiBLZW5kYWxhIGl0dSBwZW50aW5nLCBrYXJlbmEgc29sdXNpIGFwYSBwdW4geWFuZyB0aWRhayBtZW1lbnVoaSBrZW5kYWxhIGFkYWxhaCBzb2x1c2kgeWFuZyB0aWRhayBsYXlhay4NCg0KIyMgTWFjaGluZSBMZWFybmluZyBhbmQgT3B0aW1pemF0aW9uDQoNCk1hY2hpbmUgbGVhcm5pbmcgZGFuIG9wdGltYXNpIHRpZGFrIGRhcGF0IGRpcGlzYWhrYW4sIGtlZHVhbnlhIHNhbGluZyBiZXJrYWl0YW4sIG1lc2tpcHVuIHR1anVhbm55YSBiZXJiZWRhLiBQZW1iZWxhamFyYW4gbWVzaW4gYmVya2FpdGFuIGRlbmdhbiBwcmVkaWtzaSBhdGF1IGtsYXNpZmlrYXNpIGJlcmRhc2Fya2FuIGJlYmVyYXBhIHByZWRpa3Rvciwgc2VkYW5na2FuIG9wdGltYXNpIGJlcmthaXRhbiBkZW5nYW4gbWVuZW11a2FuIG5pbGFpIG9iamVrdGlmIHRlcmJhaWsgYmVyZGFzYXJrYW4gcGlsaWhhbiB2YXJpYWJlbCBrZXB1dHVzYW4uIE5hbXVuLCBtZWthbmlzbWUgZGkgYmFsaWsgc2ViYWdpYW4gYmVzYXIgbW9kZWwgcGVtYmVsYWphcmFuIG1lc2luIGFkYWxhaCBwZW5nb3B0aW1hbGFuLiBTZWJhZ2FpIGNvbnRvaCwgbWV0b2RlIGdyYWRpZW50IGRlc2NlbnQgZGFyaSBtb2RlbCBwZWxhdGloYW4gTmV1cmFsIE5ldHdvcmsgYWRhbGFoIG1ldG9kZSBvcHRpbWFzaSwgeWFuZyB0dWp1YW5ueWEgYWRhbGFoIHVudHVrIG1lbmVtdWthbiB0aXRpayB0ZXJlbmRhaCBkYW4ga29udmVyZ2VuLiBDb250b2ggbGFpbiBhZGFsYWggcGVtYXNhbmdhbiByZWdyZXNpIGxpbmllciwgeWFuZyBtZW1pbmltYWxrYW4ganVtbGFoIGtlc2FsYWhhbiBrdWFkcmF0IChkZW5nYW4gZGVtaWtpYW4sIG5hbWEgbWV0b2RlIEt1YWRyYXQgVGVya2VjaWwpLg0KDQpQZW1iZWxhamFyYW4gbWVzaW4gZGFwYXQgZGl0aW5na2F0a2FuIGRlbmdhbiBtZW55ZXRlbCBoeXBlci1wYXJhbWV0ZXIgZGVuZ2FuIHBlbmdvcHRpbWFsYW4sIHNlbWVudGFyYSBwYWRhIHNhYXQgeWFuZyBzYW1hIG1ldG9kZSBwZW5nb3B0aW1hbGFuIGp1Z2EgZGFwYXQgZGl0aW5na2F0a2FuIGRlbmdhbiBtb2RlbCBwZW1iZWxhamFyYW4gbWVzaW4uIE1pc2FsbnlhLCBraXRhIGRhcGF0IHNlY2FyYSBzaWduaWZpa2FuIG1lbmd1cmFuZ2kgTVNFIG1vZGVsIFJlZ3Jlc2kgSy1OTiBTdGFuZGFyIGRhbiBUZXJ0aW1iYW5nIGRlbmdhbiBtZW5nZ3VuYWthbiBBbGdvcml0bWEgR2VuZXRpa2FbXjFdLiBEZW5nYW4gb3B0aW1hc2kgbXVsdGktdHVqdWFuLCBraXRhIGRhcGF0IG1lbXBlcm9sZWggbW9kZWwgZW5zZW1ibGUgeWFuZyBtZW1pbGlraSBrZXNlaW1iYW5nYW4ga2VzZWltYmFuZ2FuIGFudGFyYSBydW50aW1lIGRhbiBraW5lcmphIG1vZGVsW14yXS4NCg0KIyBHZW5ldGljIEFsZ29yaXRobTogQ29uY2VwdHMgey50YWJzZXQgLnRhYnNldC1mYWRlIC50YWJzZXQtcGlsbHN9DQoNCiMjIE92ZXJ2aWV3DQpQZXJ0YW1hLXRhbWEgbWFyaSBraXRhIGJhY2EgcGFyYWdyYWYgaW5kYWggaW5pIGRhcmkgKlRoZSBCbGluZCBXYXRjaG1ha2VyKiBvbGVoIFJpY2hhcmQgRGF3a2lucw0KDQo+ICJTZW11YSBwZW5hbXBpbGFuIHNlYmFsaWtueWEsIHNhdHUtc2F0dW55YSBwZW1idWF0IGphbSBkaSBhbGFtIGFkYWxhaCBrZWt1YXRhbiBidXRhIGZpc2lrYSwgbWVza2lwdW4gZGlrZXJhaGthbiBkZW5nYW4gY2FyYSB5YW5nIHNhbmdhdCBraHVzdXMuIFNlb3JhbmcgcGVtYnVhdCBqYW0gc2VqYXRpIG1lbWlsaWtpIHBhbmRhbmdhbiBqYXVoIGtlIGRlcGFuOiBpYSBtZXJhbmNhbmcgcGVnYXMgcm9kYSBnaWdpbnlhLCBkYW4gbWVyZW5jYW5ha2FuIGludGVya29uZWtzaW55YSwgZGVuZ2FuIHR1anVhbiBtYXNhIGRlcGFuIGRpIGJlbmFrbnlhLiBTZWxla3NpIGFsYW0sIHByb3NlcyBidXRhLCB0aWRhayBzYWRhciwgb3RvbWF0aXMgeWFuZyBkaXRlbXVrYW4gRGFyd2luLCBkYW4geWFuZyBzZWthcmFuZyBraXRhIGtldGFodWkgYWRhbGFoIHBlbmplbGFzYW4gYXRhcyBrZWJlcmFkYWFuIGRhbiBiZW50dWsgeWFuZyB0YW1wYWtueWEgbWVtaWxpa2kgdHVqdWFuIGRhcmkgc2VtdWEga2VoaWR1cGFuLCB0aWRhayBtZW1pbGlraSB0dWp1YW4gZGFsYW0gcGlraXJhbi4gSWEgdGlkYWsgbWVtaWxpa2kgcGlraXJhbiBkYW4gdGlkYWsgbWVtaWxpa2kgbWF0YSBwaWtpcmFuLiBJdHUgdGlkYWsgbWVyZW5jYW5ha2FuIG1hc2EgZGVwYW4uIElhIHRpZGFrIG1lbWlsaWtpIHZpc2ksIHRpZGFrIGFkYSBwYW5kYW5nYW4ga2UgZGVwYW4sIHRpZGFrIGFkYSBwZW5nbGloYXRhbiBzYW1hIHNla2FsaS4gSmlrYSBkYXBhdCBkaWthdGFrYW4gbWVtYWlua2FuIHBlcmFuIHBlbWJ1YXQgamFtIGRpIGFsYW0sIGl0dSBhZGFsYWggcGVtYnVhdCBqYW0gYnV0YS4iDQo+IGByIHR1ZnRlOjpxdW90ZV9mb290ZXIoJyBSaWNoYXJkIERhd2tpbnMnKWANCg0KDQpFdm9sdXNpIG1lbGFsdWkgc2VsZWtzaSBhbGFtIHBlcnRhbWEga2FsaSBkaWtlbXVrYWthbiBvbGVoIENoYXJsZXMgRGFyd2luIG1lbGFsdWkgYnVrdW55YSAqT24gdGhlIE9yaWdpbiBvZiBTcGVjaWVzIGJ5IE1lYW5zIG9mIE5hdHVyYWwgU2VsZWN0aW9uLCBvciB0aGUgUHJlc2VydmF0aW9uIG9mIEZhdm91cmVkIFJhY2VzIGluIHRoZSBTdHJ1Z2dsZSBmb3IgTGlmZSouIFRlcmxlcGFzIGRhcmkgcGVybnlhdGFhbiBiZXJhbmkgRGF3a2lucyBiYWh3YSBzZWxla3NpIGFsYW0gdGlkYWsgbWVtaWxpa2kgdHVqdWFuLCBtZWthbmlzbWVueWEgZGFwYXQgZGlzZXN1YWlrYW4gdW50dWsgbWVtZWNhaGthbiBtYXNhbGFoIG9wdGltYXNpLiBKaWthIGRhbGFtIGR1bmlhIGJpb2xvZ2lzIG55YXRhLCBzZWxla3NpIGFsYW0gYmVrZXJqYSBzZWNhcmEgdGlkYWsgc2VuZ2FqYSBkZW5nYW4gbWVtaWxpaCBpbmRpdmlkdSB5YW5nIHBhbGluZyBjb2NvayB5YW5nIGRhcGF0IGJlcnRhaGFuIGhpZHVwIGRhbiBiZXJlcHJvZHVrc2ksIHNlaGluZ2dhIG1lbnllYmFya2FuIGdlbm55YSwgc29sdXNpIG9wdGltYWwgYXRhdSB0ZXJiYWlrIGRhbGFtIG1hc2FsYWggb3B0aW1hc2kgZGFwYXQgZGlwZXJvbGVoIGRlbmdhbiBtZW1pbGloIHlhbmcgbWVtaWxpa2kgbmlsYWkgKip0ZXJjb2NvayoqLCB5YWl0dSBuaWxhaSBmdW5nc2kgdHVqdWFuLiBJbmkgbXVuZ2tpbiBwZXJiZWRhYW4gdXRhbWEgYW50YXJhIHNlbGVrc2kgYWxhbSB5YW5nIHNlYmVuYXJueWEgZGFuIHlhbmcgZGlhZG9wc2kgdW50dWsgQWxnb3JpdG1hIEdlbmV0aWthLg0KDQo+ICJBbGdvcml0bWEgZ2VuZXRpa2EgKEdBKSBhZGFsYWggYWxnb3JpdG1hIHBlbmNhcmlhbiBzdG9rYXN0aWsgeWFuZyB0ZXJpbnNwaXJhc2kgb2xlaCBwcmluc2lwLXByaW5zaXAgZGFzYXIgZXZvbHVzaSBiaW9sb2dpcyBkYW4gc2VsZWtzaSBhbGFtLiBHQSBtZW5zaW11bGFzaWthbiBldm9sdXNpIG9yZ2FuaXNtZSBoaWR1cCwgZGkgbWFuYSBpbmRpdmlkdSB5YW5nIHBhbGluZyBrdWF0IG1lbmRvbWluYXNpIHlhbmcgbGViaWggbGVtYWgsIGRlbmdhbiBtZW5pcnUgbWVrYW5pc21lIGV2b2x1c2kgYmlvbG9naXMsIHNlcGVydGkgc2VsZWtzaSwgcGVyc2lsYW5nYW4gZGFuIG11dGFzaS4uIiANCj4gYHIgdHVmdGU6OnF1b3RlX2Zvb3RlcignIEx1Y2EgU2NydWNjYScpYA0KDQpBbGdvcml0bWEgR2VuZXRpa2EgYWRhbGFoIGFsZ29yaXRtYSBvcHRpbWFzaSB5YW5nIG1lbmdndW5ha2FuIGtvbnNlcCBldm9sdXNpIG1lbGFsdWkgc2VsZWtzaSBhbGFtLiBFdm9sdXNpIG1lbGFsdWkgc2VsZWtzaSBhbGFtLCBzZWJhZ2FpbWFuYSBkaWtlbXVrYWthbiBvbGVoIERhcndpbiwgYWRhbGFoIG1la2FuaXNtZSBiZXJhcGEgYmFueWFrIGplbmlzIG1ha2hsdWsgaGlkdXAgeWFuZyBiZXJhZGFwdGFzaSBkZW5nYW4gbGluZ2t1bmdhbm55YSB1bnR1ayBiZXJ0YWhhbiBoaWR1cCwgbWVsYWx1aSAyIHByaW5zaXAgdXRhbWE6IHNlbGVrc2kgYWxhbSBkYW4gbXV0YXNpIGFjYWsuIEFsZ29yaXRtYSBnZW5ldGlrYSAoR0EpIG1lbWluamFtIGtvbnNlcCBkYW4gbWVuZ2d1bmFrYW5ueWEgdW50dWsgbWVuZGFwYXRrYW4gc29sdXNpIG9wdGltYWwgZGVuZ2FuIG1lbWlsaWggc29sdXNpIHRlcmJhaWsgYXRhdSBwYWxpbmcgY29jb2sgZGkgc2FtcGluZyBtdXRhc2kgeWFuZyBqYXJhbmcgZGFuIGFjYWsuDQoNCk1pc2Fsa2FuIGtpdGEgbWVtaWxpa2kgZnVuZ3NpIG5vbi1jZW1idW5nLiBKaWthIGtpdGEgbWVuZ2d1bmFrYW4gbWV0b2RlIGJlcmJhc2lzIHR1cnVuYW4gc2VwZXJ0aSBwZW51cnVuYW4gZ3JhZGllbiwgaXR1IGJlcnBvdGVuc2kgamF0dWgga2Ugb3B0aW1hIGxva2FsIGRpICR4XzEkLiBBbGdvcml0bWEgR2VuZXRpa2EgZGFwYXQgbWVuZ2F0YXNpIGxvY2FsIG9wdGltYSBzZWhpbmdnYSBkYXBhdCBtZW1iZXJpa2FuIHNvbHVzaSB5YW5nIGxlYmloIGJhaWsuIERpa29tYmluYXNpa2FuIGRlbmdhbiBwZW1iZWxhamFyYW4gbWVzaW4gc2VwZXJ0aSBqYXJpbmdhbiBzYXJhZiwgR0EgZGFwYXQgbWVtYnVhdCBzaXN0ZW0ga2VjZXJkYXNhbiBidWF0YW4geWFuZyBkYXBhdCBiZXJtYWluIGdhbWUsIGxpa2UgPGEgaHJlZiA9ICJodHRwczovL3d3dy55b3V0dWJlLmNvbS93YXRjaD92PWFlV21kb2pFSmYwIj4gRmxhcHB5IEJpcmQgPC9hPg0KDQpgYGB7ciBvdXQud2lkdGg9IjUwJSIsIGVjaG89RkFMU0UsIGV2YWwgPVRSVUV9DQprbml0cjo6aW5jbHVkZV9ncmFwaGljcygiYXNzZXQvbm9uY29udmV4LnBuZyIpDQpgYGANCg0KVGhlIGdlbmVyYWwgZmxvdyBvZiB0aGUgYWxnb3JpdGhtIGlzIHNob3duIGJlbG93Og0KDQpgYGB7ciBvdXQud2lkdGg9IjUwJSIsIGVjaG89RkFMU0UsIGV2YWwgPVRSVUV9DQprbml0cjo6aW5jbHVkZV9ncmFwaGljcygiYXNzZXQvZ2FfZmxvdy5wbmciKQ0KYGBgDQoNCjEuIEFrYW4gYWRhIHBvcHVsYXNpIGRhcmkga3JvbW9zb20gYXRhdSBzb2x1c2kgeWFuZyBkaXBpbGloIHNlY2FyYSBhY2FrLg0KMi4gTmlsYWkgZml0bmVzcyBhdGF1IG5pbGFpIGZ1bmdzaSB0dWp1YW4gZGFyaSBtYXNpbmctbWFzaW5nIHNvbHVzaSBkaWhpdHVuZy4NCjMuIERhcmkgcG9wdWxhc2kgdGVyc2VidXQgYWthbiBkaXBpbGloIGR1YSBzb2x1c2kgc2ViYWdhaSAqKnNvbHVzaSBpbmR1ayoqLCBiYWlrIGRlbmdhbiBwZW1pbGloYW4gdHVybmFtZW4gYXRhdSBtZXRvZGUgcGVtaWxpaGFuIGxhaW5ueWEuDQo0LiBTb2x1c2kgeWFuZyBkaXBpbGloIGFrYW4gZGlzaWxhbmdrYW4gdW50dWsgbWVtYnVhdCBzb2x1c2kgYmFydSwgeWFuZyBkaXNlYnV0ICoqc29sdXNpIGFuYWsqKi4NCjUuIFNvbHVzaSBhbmFrIGRhcGF0IGJlcnViYWgga2FyZW5hIG11dGFzaSBhY2FrICh5YW5nIGtlbXVuZ2tpbmFubnlhIHNhbmdhdCBrZWNpbCB1bnR1ayB0ZXJqYWRpKS4NCjYuIERpIGFraGlyIGl0ZXJhc2ksIHBvcHVsYXNpIGJhcnUgYWthbiBkaXBpbGloIGRhcmkgKipzb2x1c2kgaW5kdWsqKiBhdGF1IHBvcHVsYXNpIGF3YWwgZGFuICoqc29sdXNpIGFuYWsqKiBiZXJkYXNhcmthbiBuaWxhaSBmaXRuZXNzLg0KNy4gU2VsYW1hIGtyaXRlcmlhIGJlcmhlbnRpIHRpZGFrIHRlcnBlbnVoaSwgYmlhc2FueWEgZGFsYW0gaGFsIGp1bWxhaCBpdGVyYXNpLCBhbGdvcml0bWEgYWthbiB0ZXJ1cyBtZWxha3VrYW4gaXRlcmFzaS4NCjguIFNvbHVzaSB0ZXJiYWlrIGF0YXUgb3B0aW1hbCBhZGFsYWggc29sdXNpIGRlbmdhbiBuaWxhaSBmaXRuZXNzIHlhbmcgb3B0aW1hbC4NCg0KQWRhIGJlYmVyYXBhIGtldW50dW5nYW4gZGFyaSBBbGdvcml0bWEgR2VuZXRpa2E6DQoNCiogQWxnb3JpdG1hIEdlbmV0aWthIGRhcGF0IG1lbmdoaW5kYXJpIG9wdGltYWwgbG9rYWwNCiogRGFwYXQgbWVuYW5nYW5pIG1hc2FsYWggeWFuZyBrb21wbGVrcyBkZW5nYW4gYmFueWFrIHZhcmlhYmVsIGtlcHV0dXNhbiBkYW4gcnVhbmcgc29sdXNpIHlhbmcgYmVzYXINCiogSW5pIG1lbXBlcnRpbWJhbmdrYW4gYmFueWFrIHRpdGlrIGRpIHJ1YW5nIHBlbmNhcmlhbiBzZWNhcmEgYmVyc2FtYWFuLCBidWthbiBzYXR1IHRpdGlrLCB1bnR1ayBiZXJ1cnVzYW4gZGVuZ2FuIGJlc2FyDQpydWFuZyBwYXJhbWV0ZXIuDQoqIERhcGF0IGRpcGFyYWxlbGthbiBrYXJlbmEgR0EgdGVyZGlyaSBkYXJpIGJlYmVyYXBhIGtyb21vc29tL3NvbHVzaQ0KKiBNZW5kdWt1bmcgcGVuZ29wdGltYWxhbiBtdWx0aS10dWp1YW4NCg0KDQojIyBFbGVtZW50cw0KDQpBZGEgMyBlbGVtZW4gdXRhbWEgR0E6IFBvcHVsYXNpLCBLcm9tb3NvbSwgZGFuIEdlbi4NCg0KUG9wdWxhc2kgYWRhbGFoIHNla2Vsb21wb2sgaW5kaXZpZHUgYXRhdSBzb2x1c2ksIHRlcnV0YW1hIGRpc2VidXQga3JvbW9zb20uIFNlYnVhaCBrcm9tb3NvbSB0ZXJkaXJpIGRhcmkgdXJ1dGFuIGdlbiwgZGVuZ2FuIG1hc2luZy1tYXNpbmcgYXRhdSBiZWJlcmFwYSBnZW4gKHRlcmdhbnR1bmcgcGFkYSBwZW5na29kZWFubnlhKSBtZXdha2lsaSB2YXJpYWJlbCBrZXB1dHVzYW4gdHVuZ2dhbCB5YW5nIGFrYW4gZGlwYXNhbmcgcGFkYSBmdW5nc2kgdHVqdWFuLg0KDQpgYGB7ciBvdXQud2lkdGg9IjYwJSIsIGVjaG89RkFMU0UsIGV2YWwgPVRSVUV9DQprbml0cjo6aW5jbHVkZV9ncmFwaGljcygiYXNzZXQvZ2FfZWxlbWVudC5wbmciKQ0KYGBgDQoNCkdlbiBkYXBhdCBkaXJlcHJlc2VudGFzaWthbiBhdGF1IGRpa29kZWthbiBkZW5nYW4gYmVyYmFnYWkgY2FyYSwgdGVybWFzdWs6DQoNCiogQmluYXJ5IEVuY29kaW5nDQoNCiFbXShhc3NldC9nYTMucG5nKQ0KDQpQZW5na29kZWFuIGJpbmVyIGJlcmFydGkgYmFod2EgZ2VuIGtpdGEgZGlrb2Rla2FuIGtlIGRhbGFtIGJpbGFuZ2FuIGJpbmVyLCBpbmkgYWRhbGFoIGJlbnR1ayBwZW5na29kZWFuIEdBIHlhbmcgcGFsaW5nIGF3YWwgZGFuIHBhbGluZyB1bXVtLg0KDQoqIFJlYWwtVmFsdWVkIEVuY29kaW5nDQoNCiFbXShhc3NldC9nYTQucG5nKQ0KDQpQZW5na29kZWFuIGJlcm5pbGFpIG55YXRhIGJlcmFydGkgYmFod2EgZ2VuIGtpdGEgZGlrb2Rla2FuIGRhbGFtIGZsb2F0aW5nIHBvaW50IGF0YXUgYW5na2EgZGVzaW1hbC4gRW5jb2RpbmcgYmVybmlsYWkgbnlhdGEgZGFwYXQgZGlndW5ha2FuIHVudHVrIG1lbmdvcHRpbWFsa2FuIHBhcmFtZXRlciBwcm9zZXMsIGthcmVuYSB0ZXJkaXJpIGRhcmkgYmlsYW5nYW4gZmxvYXRpbmcgcG9pbnQgZGFuIHRpZGFrIGNvY29rIHVudHVrIG9wdGltYXNpIGJlcmJhc2lzIGludGVnZXIuDQoNCiogUGVybXV0YXRpb24gRW5jb2RpbmcNCg0KIVtdKGFzc2V0L2dhNS5wbmcpDQoNClBlbmdrb2RlYW4gcGVybXV0YXNpIGJlcmFydGkgYmFod2EgZ2VuIGtpdGEgZGlrb2Rla2FuIGtlIGRhbGFtIHVydXRhbiBub21vciwgc2V0aWFwIG5vbW9yIHNlY2FyYSB1bmlrIGRpdHVnYXNrYW4gdW50dWsgZ2VuIHNlaGluZ2dhIHRpZGFrIGFkYSAyIGdlbiB5YW5nIGFrYW4gbWVtaWxpa2kgbm9tb3IgYXRhdSBuaWxhaSB5YW5nIHNhbWEuIFBlbmdrb2RlYW4gaW5pIHNlcmluZyBkaWd1bmFrYW4gZGFsYW0gbWFzYWxhaCBwZW5qYWR3YWxhbiBhdGF1IHBlcnV0ZWFuLCBkaSBtYW5hIHNldGlhcCBwZXJtdXRhc2kgbWV3YWtpbGkgc2F0dSBsb2thc2kgYXRhdSB0aXRpayB1bmlrLg0KDQojIyBTZWxlY3Rpb24NCg0KUGVtaWxpaGFuIHBhcmVudCBkaWxha3VrYW4gdW50dWsgbWVtaWxpaCBkdWEgc29sdXNpIHlhbmcgYWthbiBkaWd1bmFrYW4gdW50dWsgY3Jvc3NvdmVyIHVudHVrIG1lbWJ1YXQgc29sdXNpIGJhcnUgeWFuZyBkaXNlYnV0ICoqc29sdXNpIGFuYWsqKi4gQWRhIGJlYmVyYXBhIG1ldG9kZSB1bnR1ayBtZW1pbGloIG9yYW5nIHR1YToNCg0KKiBQZW1pbGloYW4gUm9kYSBSb3VsZXR0ZQ0KDQpPcmFuZyB0dWEgYWthbiBkaXBpbGloIHNlY2FyYSBhY2FrIGJlcmRhc2Fya2FuIG5pbGFpIGZpdG5lc3MuIE5pbGFpIGZpdG5lc3MgeWFuZyBsZWJpaCB0aW5nZ2kgYmVyYXJ0aSBrZXNlbXBhdGFuIHlhbmcgbGViaWggdGluZ2dpIHVudHVrIGRpcGlsaWguDQohW10oYXNzZXQvZ2E5LnBuZykNCg0KKiBQZW1pbGloYW4gUGVyaW5na2F0IExpbmllcg0KDQpJbmRpdmlkdSBkaWJlcmkga2VidWdhcmFuIHN1Ympla3RpZiBiZXJkYXNhcmthbiBwZXJpbmdrYXQgZGFsYW0gcG9wdWxhc2kuIEluZGl2aWR1IGRhbGFtIHBvcHVsYXNpIGRpdXJ1dGthbiBkYXJpIHlhbmcgdGVyYmFpayBoaW5nZ2EgeWFuZyB0ZXJidXJ1ayBiZXJkYXNhcmthbiBuaWxhaSBmaXRuZXNzbnlhLiBTZXRpYXAgaW5kaXZpZHUgZGFsYW0gcG9wdWxhc2kgZGliZXJpIHBlcmluZ2thdCBudW1lcmlrIGJlcmRhc2Fya2FuIGtlYnVnYXJhbiwgZGFuIHNlbGVrc2kgZGlkYXNhcmthbiBwYWRhIHBlcmluZ2thdCBpbmkgZGFyaXBhZGEgcGVyYmVkYWFuIGRhbGFtIGtlYnVnYXJhbi4NCg0KKiBQZW1pbGloYW4gVHVybmFtZW4NCg0KU2VsZWtzaSB0dXJuYW1lbiBkaWxha3VrYW4gZGVuZ2FuIG1lbnllbGVrc2kgc2VrdW1wdWxhbiBrcm9tb3NvbSBkYXJpIHBvcHVsYXNpIGRhbiBtZW1idWF0IGtyaXRlcmlhIHNlbGVrc2kgdGVydGVudHUuIFBlbWlsaWhhbiB0dXJuYW1lbiBlZmVrdGlmIHVudHVrIEdBIHlhbmcgbWVtaWxpa2kgcG9wdWxhc2kgYmVzYXIga2FyZW5hIHRpZGFrIHBlcmx1IG1lbmd1cnV0a2FuIGluZGl2aWR1IGRpIGRhbGFtIHBvcHVsYXNpLg0KDQojIyBDcm9zc292ZXIgYW5kIE11dGF0aW9uDQoNCiMjIyBDcm9zc292ZXINCg0KQ3Jvc3NvdmVyIGFkYWxhaCBwcm9zZXMgcGVya2F3aW5hbiBhbnRhcmEgZHVhIGluZGl2aWR1IHlhbmcgZGlwaWxpaCwgcHJvc2VzIHRlcnNlYnV0IG1ld2FraWxpIGJhZ2FpbWFuYSBnZW4gZGl0cmFuc2ZlciBrZSBrZXR1cnVuYW5ueWEuIEFkYSBiZWJlcmFwYSBtZXRvZGUgY3Jvc3NvdmVyOg0KIyMjIyBCaW5hcnkgDQoNCiogQ3Jvc3NvdmVyIHRpdGlrIHR1bmdnYWwNCg0KUGlzYWhrYW4gaW5kdWsgYmVyZGFzYXJrYW4gc2F0dSB0aXRpayB5YW5nIGRpaGFzaWxrYW4gc2VjYXJhIGFjYWsuIEFuYWsgcGVydGFtYSBha2FuIG1lbWlsaWtpIGdlbiBkYXJpIG9yYW5nIHR1YSBwZXJ0YW1hIHVudHVrIHBvc2lzaSAxIHNhbXBhaSBkZW5nYW4gdGl0aWssIHNlZGFuZ2thbiBzaXNhbnlhIGRpaXNpIGRlbmdhbiBnZW4gZGFyaSBvcmFuZyB0dWEga2VkdWEuIEFuYWsga2VkdWEga2ViYWxpa2FubnlhLCBwb3Npc2kgMSBzYW1wYWkgdGl0aWsgZGlpc2kgZGVuZ2FuIGdlbiBkYXJpIGluZHVrIGtlZHVhLg0KDQohW10oYXNzZXQvZ2FiaW5fY3Jvc3MucG5nKQ0KDQoqIFNlcmFnYW0gQ3Jvc3NvdmVyDQoNClNldGlhcCBnZW4gZGlwZXJ0dWthcmthbiBhbnRhcmEgc2VwYXNhbmcga3JvbW9zb20geWFuZyBkaXBpbGloIHNlY2FyYSBhY2FrIGRlbmdhbiBwcm9iYWJpbGl0YXMgdGVydGVudHUsIHlhbmcgZGlzZWJ1dCBzZWJhZ2FpIHByb2JhYmlsaXRhcyBzd2FwcGluZy4gQmlhc2FueWEsIG5pbGFpIHByb2JhYmlsaXRhcyBzd2FwcGluZyBkaWFtYmlsIG1lbmphZGkgMCw1Lg0KDQohW10oYXNzZXQvZ2FiaW5fY3Jvc3MyLnBuZykNCg0KIyMjIyBSZWFsLVZhbHVlZA0KDQoqIENyb3Nzb3ZlciBBcml0bWF0aWthIFV0dWgNCg0KTWVuZ2FtYmlsIGp1bWxhaCB0ZXJ0aW1iYW5nIGRhcmkgZHVhIGdlbiBpbmR1ayB1bnR1ayBzZXRpYXAgcG9zaXNpLiBCb2JvdCBkaXRlcmFwa2FuIHVudHVrIHNlbXVhIHBvc2lzaS4gTWlzYWwgeCA9IG5pbGFpIHBhcmVudCBwZXJ0YW1hIGRhbiB5ID0gbmlsYWkgcGFyZW50IGtlZHVhIHVudHVrIHBvc2lzaS9nZW4gMToNCg0KJCRDaGlsZCBcIDEgPSB3ZWlnaHQgXCAqIHggKyAoMS13ZWlnaHQpIFwgKiB5JCQNCg0KJCRDaGlsZCBcIDIgPSB3ZWlnaHQgXCAqIHkgKyAoMi13ZWlnaHQpIFwgKiB4JCQNCg0KSmlrYSBiZXJhdCA9IDAsNSBkdWEga2V0dXJ1bmFuIGlkZW50aWsNCg0KIVtdKGFzc2V0L2dhcmVhbF9jcm9zcy5wbmcpDQoNCiogQ3Jvc3NvdmVyIEFyaXRtYXRpa2EgTG9rYWwNCg0KQ3Jvc3NvdmVyIGxva2FsIG1pcmlwIGRlbmdhbiBjcm9zc292ZXIgYXJpdG1hdGlrYSBrZXNlbHVydWhhbiwgdGV0YXBpIG1lbmdndW5ha2FuIGFscGhhIGFjYWsgdW50dWsgc2V0aWFwIHBvc2lzaS4NCg0KUGVybXV0YXRpb24NCg0KKiBPbmUtcG9pbnQgY3Jvc3NvdmVyDQoNCkNyb3Nzb3ZlciBzYXR1IHRpdGlrIGhhbnlhIG1lbmdndW5ha2FuIHNhdHUgdGl0aWsgYWNhayBzZWJhZ2FpIHBlbmFuZGEgZGltYW5hIHNlaGFydXNueWEgY3Jvc3NvdmVyIHRlcmphZGkuDQoNCiFbTmVhcmNob3UsIDIwMTRdKGFzc2V0L2dhNi5wbmcpDQoNCiogUG9zaXRpb24tQmFzZWQgY3Jvc3NvdmVyDQoNClNlanVtbGFoIHBvc2lzaSBkYXJpIGluZHVrIHBlcnRhbWEgZGlwaWxpaCBzZWNhcmEgYWNhayBkYW4gbWVuZ2lzaSBwb3Npc2kgeWFuZyB0ZXJzaXNhIGRhcmkgaW5kdWsga2VkdWEuIA0KDQohW05lYXJjaG91LCAyMDE0XShhc3NldC9wYl9jcm9zcy5wbmcpDQoNCiMjIyBNdXRhdGlvbg0KDQpNdXRhc2kgYmVyYXJ0aSBwZXJ1YmFoYW4gcGFkYSBzYXR1IGF0YXUgYmViZXJhcGEgZ2VuIGRpIGRhbGFtIGtyb21vc29tLiBTZXBlcnRpIGhhbG55YSBkaSBhbGFtLCBtdXRhc2kgamFyYW5nIHRlcmphZGksIHNlaGluZ2dhIGtlbXVuZ2tpbmFubnlhIGtlY2lsIHVudHVrIHRlcmphZGksIGJpYXNhbnlhIGRpdGV0YXBrYW4gcGFkYSAwLDAxLiBBZGEgYmViZXJhcGEgY29udG9oIG11dGFzaToNCg0KIyMjIyBCaW5hcnkNCg0KKiBSYW5kb20gTXV0YXRpb24NCg0KR2VuIGRpYmFsaWsgc2VjYXJhIGFjYWsgZGFyaSAxIGtlIDAgYXRhdSBzZWJhbGlrbnlhIGRlbmdhbiBwcm9iYWJpbGl0YXMgc2VyYWdhbSB1bnR1ayBzZXRpYXAgZ2VuLCBhcnRpbnlhIHNldGlhcCBnZW4gbWVtaWxpa2kgcGVsdWFuZyB5YW5nIHNhbWEgdW50dWsgYmVybXV0YXNpIGRlbmdhbiB0aW5na2F0IG11dGFzaSBhdGF1IHByb2JhYmlsaXRhcyBtdXRhc2kgeWFuZyBkaXRldGFwa2FuIG9sZWggcGVuZ2d1bmEuDQohW10oYXNzZXQvZ2FiaW5fbXV0YXRpb24ucG5nKQ0KDQojIyMjIFJlYWwgVmFsdWVkDQoNCiogTXV0YXNpIEFjYWsgU2VyYWdhbQ0KDQpHZW4gYmVybXV0YXNpIHNlY2FyYSBhY2FrIHBhZGEgcG9zaXNpIGRlbmdhbiBuaWxhaSBkYXJpIGtpc2FyYW4gdGVydGVudHUgZGVuZ2FuIGRpc3RyaWJ1c2kgc2VyYWdhbS4NCg0KKiBNdXRhc2kgQWNhayBUaWRhayBTZXJhZ2FtDQoNClBpbGloIGdlbi9wb3Npc2kgYWNhayBkYXJpIHNlYnVhaCBrcm9tb3NvbSBkYW4gYmVyaWthbiBuaWxhaSBhY2FrIHlhbmcgdGlkYWsgc2VyYWdhbSBwYWRhbnlhLg0KDQojIyMjIFBlcm11dGF0aW9uDQoNCiogTXV0YXNpIFBlcnR1a2FyYW4gQmVyZGVrYXRhbg0KDQpEdWEgZ2VuIHlhbmcgYmVyZGVrYXRhbiBkaXR1a2FyIHNlY2FyYSBhY2FrLg0KIVtOZWFyY2hvdSwgMjAxNF0oYXNzZXQvZ2ExMC5wbmcpDQoNCiogTXV0YXNpIFRlcmJhbGlrDQoNCk11dGFzaSB0ZXJiYWxpayBkaWxha3VrYW4gZGVuZ2FuIGNhcmEgbWVtYmFsaWsgYmFyaXNhbiBhbnRhcmEgZHVhIHRpdGlrIGFjYWsuDQohW05lYXJjaG91LCAyMDE0XShhc3NldC9nYTEzLnBuZykNCg0KIyMgQXBwbGljYXRpb24NCg0KQWxnb3JpdG1hIEdlbmV0aWthIGRhcGF0IGRpdGVyYXBrYW4gZGFsYW0gYmVyYmFnYWkgcGVybWFzYWxhaGFuIGJpc25pcy4gS2FyZW5hIGFsZ29yaXRtZSBhZGFsYWggdGVudGFuZyBwZW5nb3B0aW1hbGFuLCBzZWxhbWEgYWRhIGZ1bmdzaSB0dWp1YW4va2VidWdhcmFuIHVudHVrIGRpb3B0aW1hbGthbiwgR0EgZGFwYXQgZGl0ZXJhcGthbi4gQmViZXJhcGEgY29udG9oIHRlcm1hc3VrIHBlbmphZHdhbGFuIHByb2R1a3NpLCBtYXNhbGFoIGtuYXBzYWNrIChiZXJhcGEgYmFueWFrL2JlcmFwYSBiYW55YWsgYmFyYW5nIHlhbmcgZGFwYXQga2FtaSBtdWF0IGRpIHRhcyBhdGF1IHRydWsga2FtaSB1bnR1ayBtZW1ha3NpbWFsa2FuIHJ1YW5nIGF0YXUgYmViYW4gYmVyYXQpLCBtYXNhbGFoIHRyYXZlbGluZyBzYWxlc21hbiwgZGxsLiBHQSBqdWdhIGRhcGF0IGRpdGVyYXBrYW4gZGkgYmlkYW5nIGlsbXUgZGF0YSwga2h1c3VzbnlhIGtldGlrYSBrYW1pIG1lbGFrdWthbiBwZW55ZXRlbGFuIGh5cGVyLXBhcmFtZXRlciB1bnR1ayBtZW5nb3B0aW1hbGthbiBraW5lcmphIG1vZGVsLiBNYXNhbGFoIHRlcnRlbnR1IG11bmdraW4gbWVtaWxpa2kgcGVuZ2F0dXJhbiBwYXJhbWV0ZXIgdW5pa255YSBzZW5kaXJpIHlhbmcgZGFwYXQgbWVtaWxpa2kgbmlsYWkga2VidWdhcmFuIHlhbmcgbGViaWggYmFpay4gTWlzYWxueWEsIG1hc2FsYWggcGVuamFkd2FsYW4gZmxvd3Nob3AgdGVsYWggZGl0ZWxpdGkgb2xlaCBOZWFyY2hvdVteM10uIEdhbWJhcmFuIGRldGFpbCBkYW4gcGVuZXJhcGFuIEdBIGRhcGF0IGRpbGloYXQgZGkga2FyeWEgS3JhbWVyW140XQ0KDQojIyBJbGx1c3RyYXRpb24NCg0KTWFyaSBraXRhIGlsdXN0cmFzaWthbiBsYW5na2FoIGRlbWkgbGFuZ2thaCBiYWdhaW1hbmEgQWxnb3JpdG1hIEdlbmV0aWthIGJla2VyamEuIEthdGFrYW5sYWggc2F5YSBtZW1pbGlraSBzYXR1IHNldCBpdGVtIGRlbmdhbiBwb2luIGRhbiBiZXJhdCBtYXNpbmctbWFzaW5nIChrZykuIFNheWEgaW5naW4gbWVtYWtzaW1hbGthbiBwb2luLCB0ZXRhcGkgYXR1cmFuIG1lbmdhdGFrYW4gYmFod2Egc2F5YSB0aWRhayBkYXBhdCBtZW1iYXdhIGJhcmFuZyBkZW5nYW4gYmVyYXQgdG90YWwgbGViaWggZGFyaSAxNSBrZy4gSXRlbSBtYW5hIHlhbmcgaGFydXMgc2F5YSBwaWxpaCB1bnR1ayBtZW1ha3NpbWFsa2FuIHBvaW4gc2F5YT8NCg0KIVtdKGFzc2V0L3N0ZXAxLnBuZykNCg0KIyMjIERlZmluZSBFbmNvZGluZyBhbmQgRml0bmVzcyBGdW5jdGlvbg0KDQpLYXJlbmEgbWFzYWxhaG55YSBhZGFsYWggYXBha2FoIG1lbWJhd2EgaXRlbSB0ZXJ0ZW50dSBhdGF1IHRpZGFrLCBtYWthIHNvbHVzaW55YSBoYXJ1cyB0ZXJkaXJpIGRhcmkgbmlsYWkgbG9naXMgZGFyaSBzZXRpYXAgaXRlbS4gSmlrYSBuaWxhaW55YSBgMWAgYXRhdSBgVFJVRWAsIG1ha2Egc2F5YSBoYXJ1cyBtZW1iYXdhIGl0ZW0gdGVyc2VidXQsIGRhbiBqaWthIG5pbGFpbnlhIGAwYCBhdGF1IGBGQUxTRWAsIG1ha2Egc2F5YSB0aWRhayBib2xlaCBtZW1iYXdhIGl0ZW0gdGVyc2VidXQuIERlbmdhbiBsb2dpa2EgaW5pLCBwZW5na29kZWFuIHVudHVrIGFsZ29yaXRtYSBnZW5ldGlrYSBraXRhIGhhcnVzIHBlbmdrb2RlYW4gYmluZXIga2FyZW5hIGhhbnlhIHRlcmRpcmkgZGFyaSAyIG5pbGFpICgxIGF0YXUgMCkuDQoNCkZ1bmdzaSBrZWJ1Z2FyYW4ga2VtdWRpYW4sIGhhcnVzIG1lbWFrc2ltYWxrYW4gdGl0aWsgdG90YWwuDQoNCiQkTWF4aW1pemUgXCBUb3RhbFwgUG9pbnQgPSBcU2lnbWFfe2k9MX1ee2t9IFwgUF9rIFwgKiBcIGNob3Nlbl9rICQkDQoNCiogJFRvdGFsXCBQb2luJDogVG90YWwgcG9pbiB5YW5nIGRpaGFzaWxrYW4NCiogJGskOiBKdW1sYWggYmFyYW5nDQoqICRQX2skOiBUaXRpayBpdGVtIGsNCiogJGNob3Nlbl9rJDogbmlsYWkgbG9naWthIHNldGlhcCBpdGVtICgxIGF0YXUgMCkNCg0KQmF0YXNhbjoNCg0KJCRUb3RhbFwgV2VpZ2h0IDw9IDE1JCQNCg0KS2FyZW5hIGtpdGEgbWVtaWxpa2kga2VuZGFsYSwga2l0YSBwZXJsdSBtZW1hc3Vra2FuIGtlbmRhbGEga2UgZGFsYW0gZnVuZ3NpIGZpdG5lc3MuIEhhbCBpbmkgZGlsYWt1a2FuIHVudHVrIG1lbmNlZ2FoIHNvbHVzaSB5YW5nIHRpZGFrIGxheWFrICh5YW5nIG1lbGFuZ2dhciBiYXRhc2FuKSB1bnR1ayBkaXBpbGloIGF0YXUgZGlwaWxpaCBzZWJhZ2FpIG5pbGFpIG9wdGltYWwuDQoNCiQkTWF4aW1pemVcIFRvdGFsXCBQb2ludCA9IFxTaWdtYV97aT0xfV57a30gXCBQX2sgXCBjaG9zZW5fayAtIDIgXCAodG90YWxcIHdlaWdodCAtIDE1KV4yJCQNCg0KIyMjIEluaXRpYWwgUG9wdWxhdGlvbg0KDQpQZXJ0YW1hLCBHQSBha2FuIG1lbWJhbmdraXRrYW4gcG9wdWxhc2kgc29sdXNpIHNlY2FyYSBhY2FrLiBEaSBzaW5pLCBrYW1pIG1lbWlsaWtpIHBvcHVsYXNpIDQgc29sdXNpL2luZGl2aWR1LiBOaWxhaSBiaW5lciBwYWRhIHNldGlhcCBpbmRpdmlkdSBkaWJhbmdraXRrYW4gc2VjYXJhIGFjYWsuDQoNCiFbXShhc3NldC9zdGVwMi5wbmcpDQoNCiMjIyBGaXRuZXNzIEFzc2lnbm1lbnQNCg0KTm93IHdlIGNhbGN1bGF0ZSB0aGUgZml0bmVzcyB2YWx1ZSAodG90YWwgcG9pbnQpIG9mIGVhY2ggaW5kaXZpZHVhbHMgYmFzZWQgb24gdGhlIGZpdG5lc3MgZnVuY3Rpb24gYWJvdmUuIFdlIGNhbiBzZWUgdGhhdCBzb2x1dGlvbiB0aGF0IHZpb2xhdGUgdGhlIGNvbnN0cmFpbnQgKGhhdmUgdG90YWwgd2VpZ2h0ID4gMTUpIHdpbGwgaGF2ZSB3b3JzZSBmaXRuZXNzIGZ1bmN0aW9uLg0KDQohW10oYXNzZXQvc3RlcDMucG5nKQ0KDQojIyMgU2VsZWN0aW9uDQoNClNla2FyYW5nIGtpdGEgYWthbiBtZW1pbGloIGluZGl2aWR1IG1hbmEgeWFuZyBha2FuIGRpc2lsYW5na2FuIHVudHVrIG1lbmRhcGF0a2FuIHNvbHVzaSBiYXJ1LiBLYW1pIGFrYW4gbWVuZ2d1bmFrYW4gcGVtaWxpaGFuIHBlcmluZ2thdCBsaW5pZXIgdW50dWsgbWVtaWxpaCBzb2x1c2kgYGluZHVrYC4gS2FtaSBha2FuIG1lbWlsaWggMiBzb2x1c2kga2FyZW5hIGNyb3Nzb3ZlciBtZW1idXR1aGthbiAyIHNvbHVzaSBgcGFyZW50YC4NCg0KUGVtaWxpaGFuIHBlcmluZ2thdCBsaW5pZXIgYmVrZXJqYSBzZWJhZ2FpIGJlcmlrdXQ6DQoNCiogQmVyaWthbiBwZXJpbmdrYXQgdW50dWsgc2V0aWFwIGluZGl2aWR1LiBQZXJpbmdrYXQgeWFuZyBsZWJpaCBiZXNhciBkaWJlcmlrYW4gdW50dWsgZnVuZ3NpIGtlYnVnYXJhbiB5YW5nIGxlYmloIGJlc2FyLiBJbmRpdmlkdSAyIGRlbmdhbiBmdW5nc2kgZml0bmVzcyAxOSBhZGFsYWggeWFuZyB0ZXJ0aW5nZ2ksIHNlaGluZ2dhIGFrYW4gZGliZXJpa2FuIHJhbmdraW5nIDQuDQoqIEhpdHVuZyBwcm9iYWJpbGl0YXMgc2V0aWFwIGluZGl2aWR1IGJlcmRhc2Fya2FuIHBlcmluZ2thdCBtZXJla2EuIEp1bWxhaCBwZXJpbmdrYXRueWEgYWRhbGFoIDEwLCBqYWRpIHByb2JhYmlsaXRhcyBzZXRpYXAgc29sdXNpIGFkYWxhaCAkUmFua19pLzEwJC4NCiogUGlsaWggc29sdXNpIHNlY2FyYSBhY2FrIGJlcmRhc2Fya2FuIHByb2JhYmlsaXRhcy4gU29sdXNpIGRlbmdhbiBmdW5nc2kgZml0bmVzcyB5YW5nIGxlYmloIHRpbmdnaSBzZWNhcmEgYWxhbWkgYWthbiBtZW1pbGlraSBwZWx1YW5nIGxlYmloIHRpbmdnaSB1bnR1ayBkaXBpbGloLg0KIVtdKGFzc2V0L3N0ZXA0LnBuZykNCg0KS2F0YWthbmxhaCBtaXNhbG55YSwgc29sdXNpIHBlcnRhbWEgZGFuIGtlZHVhIGRpcGlsaWggc2ViYWdhaSBgcGFyZW50YA0KIVtdKGFzc2V0L3N0ZXA1LnBuZykNCg0KIyMjIENyb3Nzb3Zlcg0KDQpDcm9zc292ZXIgYmVyYXJ0aSBwZXJ0dWthcmFuIGdlbiBhbnRhcmEgZHVhIGBvcmFuZyB0dWFgIGR1YSBtZW1idWF0IHNvbHVzaSBiYXJ1IHlhbmcgZGlzZWJ1dCBzb2x1c2kgYGFuYWtgLiBLYW1pIGFrYW4gbWVuZ2d1bmFrYW4gY3Jvc3NvdmVyIHRpdGlrIHR1bmdnYWwgZGkgc2luaS4gU2F0dSB0aXRpayBkaXBpbGloIHNlY2FyYSBhY2FrLg0KDQpLYXRha2FubGFoIHRpdGlrIHlhbmcgZGlwaWxpaCBiZXJhZGEgZGkgYW50YXJhIHBvc2lzaSAyIGRhbiAzLiBVbnR1ayBhbmFrIHBlcnRhbWEgYWthbiBtZW1pbGlraSBnZW4gcGVydGFtYSBkYW4ga2VkdWEgZGFyaSBpbmR1ayBwZXJ0YW1hLCBzZWRhbmdrYW4gc2lzYW55YSBha2FuIGRpaXNpIGRhcmkgaW5kdWsga2VkdWEuIFVudHVrIGFuYWsga2VkdWEsIHNlYmFsaWtueWEsIGlhIGFrYW4gbWVtaWxpa2kgZ2VuIHBlcnRhbWEgZGFuIGtlZHVhIGRhcmkgb3JhbmcgdHVhIGtlZHVhLCBzZWRhbmdrYW4gc2lzYW55YSBha2FuIGRpaXNpIGRhcmkgb3JhbmcgdHVhIHBlcnRhbWEuDQohW10oYXNzZXQvc3RlcDYucG5nKQ0KDQojIyMgTXV0YXRpb24NCg0KTXV0YXNpIGFkYWxhaCBwZXJ1YmFoYW4gbmlsYWkgcGFkYSBnZW4uIERpIHNpbmkga2l0YSBtZW5nZ3VuYWthbiBtdXRhc2kgYWNhay4gSW5pIGFrYW4gc2VjYXJhIGFjYWsgbWVudWthciBuaWxhaSBnZW4uIEppa2EgZ2VuIGRpIHBvc2lzaSAzIGJlcm5pbGFpIDEsIG1ha2EgYWthbiBkaXViYWggbWVuamFkaSAwIGRhbiBzZWJhbGlrbnlhLiBNdXRhc2kgamFyYW5nIHRlcmphZGksIHByb2JhYmlsaXRhc255YSBkYXBhdCBkaWF0dXIgc2VjYXJhIG1hbnVhbCwgdGV0YXBpIHVtdW1ueWEgb3JhbmcgbWVuZ2d1bmFrYW4gcHJvYmFiaWxpdGFzIG11dGFzaSAwLDEuDQoNCmBgYHtyIG91dC53aWR0aD0iNTAlIiwgZWNobz1GQUxTRSwgZXZhbCA9VFJVRX0NCmtuaXRyOjppbmNsdWRlX2dyYXBoaWNzKCJhc3NldC9zdGVwNy5wbmciKQ0KYGBgDQoNClByb3NlcyBzZWxla3NpLCBjcm9zc292ZXIsIGRhbiBtdXRhc2kgYWthbiBkaXVsYW5naSBkZW5nYW4gbWVtaWxpaCBpbmR1ayB5YW5nIGJlcmJlZGEgc2FtcGFpIGp1bWxhaCBhbmFrIHNhbWEgZGVuZ2FuIHVrdXJhbiBwb3B1bGFzaSBpbmR1ay4gSmlrYSBvcmFuZyB0dWEgbWVtaWxpa2kgdWt1cmFuIHBvcHVsYXNpIDQsIG1ha2Ega2l0YSBha2FuIG1lbWlsaWtpIHBvcHVsYXNpIGFuYWstYW5hayA0Lg0KIyMjIENoZWNrIFN0b3BwaW5nIENyaXRlcmlhDQoNClNldGVsYWggbXV0YXNpIHNlbGVzYWksIGthbWkgYWthbiBtZW1lcmlrc2EgYXBha2FoIHN1ZGFoIHdha3R1bnlhIHVudHVrIG1lbmdoZW50aWthbiBhbGdvcml0bWEuIFN1ZGFoa2FoIGtpdGEgbWVuY2FwYWkgaXRlcmFzaSAxMDA/IEFwYWthaCBkYWxhbSAxMCBpdGVyYXNpIHRlcmFraGlyIG5pbGFpIGZpdG5lc3Mgb3B0aW1hbCB0aWRhayBiZXJ1YmFoPyBKaWthIHRpZGFrLCBtYWthIGl0ZXJhc2kgYWthbiBkaWxhbmp1dGthbi4gU2V0ZWxhaCBjcm9zc292ZXIgZGFuIG11dGFzaSwgc2VrYXJhbmcga2FtaSBtZW1pbGlraSBwb3B1bGFzaSBpbmR1ayA0IGRhbiBwb3B1bGFzaSBhbmFrLWFuYWsgNCwgamFkaSB0b3RhbCBrYW1pIHNla2FyYW5nIG1lbWlsaWtpIDggc29sdXNpLiBVbnR1ayBpdGVyYXNpIHNlbGFuanV0bnlhLCBraXRhIGhhbnlhIGFrYW4gbWVtaWxpaCA0IGluZGl2aWR1IChzZXN1YWkgZGVuZ2FuIHVrdXJhbiBwb3B1bGFzaSBhd2FsKSBkYXJpIGluZHVrIGRhbiBhbmFrLiBVbXVtbnlhIGluZGl2aWR1IGRlbmdhbiBuaWxhaSBmaXRuZXNzIHRpbmdnaSBha2FuIGRpcGlsaWgsIHNlaGluZ2dhIHBvcHVsYXNpIGJlcmlrdXRueWEgYWthbiBsZWJpaCBiYWlrIGRhcmkgeWFuZyB0ZXJha2hpci4NCg0KIyBHZW5ldGljIEFsZ29yaXRobSB3aXRoIFINCg0KRGkgc2luaSBraXRhIGFrYW4gbWVuZ2lsdXN0cmFzaWthbiBiYWdhaW1hbmEgR0EgZGFwYXQgYmVrZXJqYSBtZW5nZ3VuYWthbiBwYWtldCBgR0FgW141XVteNl0gYmFpayBwYWRhIG1hc2FsYWggYmlzbmlzIG1hdXB1biBwYWRhIHBlbnlldGVsYW4gaHlwZXItcGFyYW1ldGVyIG1hY2hpbmUgbGVhcm5pbmcuDQoNCiMjIEJ1c2luZXNzIEFwcGxpY2F0aW9uIHsudGFic2V0IC50YWJzZXQtZmFkZSAudGFic2V0LXBpbGxzfQ0KDQojIyMgS25hcHNhY2sgUHJvYmxlbQ0KDQpNaXNhbGthbiBraXRhIG1lbWlsaWtpIGJlYmVyYXBhIGl0ZW0gdW50dWsgZGlraXJpbSBrZSBwdXNhdCBkaXN0cmlidXNpLiBOYW11biwgdHJ1ayBrYW1pIGhhbnlhIGRhcGF0IG1lbXVhdCBoaW5nZ2EgdG90YWwgMTAgdG9uIGF0YXUgMTAuMDAwIGtnLCBqYWRpIGthbWkgaGFydXMgbWVtaWxpaCBiYXJhbmcgbWFuYSB5YW5nIGFrYW4gZGlraXJpbSBkYW4gbWVtYWtzaW1hbGthbiBiZXJhdCB5YW5nIGRpbXVhdC4gQW5kYSBkYXBhdCBsYW5nc3VuZyBtZW55ZWxlc2Fpa2FuIG1hc2FsYWggaW5pIG1lbmdndW5ha2FuICoqTWV0b2RlIEtuYXBzYWNrKiosIHRldGFwaSBBbmRhIGp1Z2EgZGFwYXQgbWVuZ2d1bmFrYW4gQWxnb3JpdG1hIEdlbmV0aWthLiBEaSBzaW5pIGtpdGEgYWthbiBtZWxpaGF0IGJhZ2FpbWFuYSBHQSBtZW5hbmdhbmkgbWFzYWxhaCB5YW5nIGRpYmF0YXNpIChiZXJhdCB0aWRhayBib2xlaCBtZWxlYmloaSAxMCB0b24gYXRhdSAxMC4wMDAga2cpLg0KYGBge3J9DQpkZl9pdGVtIDwtIGRhdGEuZnJhbWUoaXRlbSA9IGMoIlRpcmVzIiwgIkJ1bXBlciIsICJFbmdpbmUiLCAiQ2hhc2lzIiwgIlNlYXQiKSwNCiAgICAgICAgICAgICAgICAgICAgICBmcmVxID0gYyg4MCw1MCw3MCw1MCw3MCksDQogICAgICAgICAgICAgICAgICAgICAgd2VpZ2h0ID0gYyg3LCAxNywgMTU4LCAxMDAsIDMwKSkNCg0KZGZfaXRlbQ0KYGBgDQpTZXRpYXAgYmFyYW5nIG1lbWlsaWtpIGp1bWxhaCBkYW4gYmVyYXRueWEgbWFzaW5nLW1hc2luZy4NCg0KTWFyaSBraXRhIGJ1YXQgZGF0YXNldCBiYXJ1IGRlbmdhbiBzYXR1IG9ic2VydmFzaSB5YW5nIHNlc3VhaSBkZW5nYW4gaGFueWEgMSBpdGVtLg0KYGBge3J9DQpkZl9pdGVtX2xvbmcgPC0gZGZfaXRlbVtyZXAocm93bmFtZXMoZGZfaXRlbSksIGRmX2l0ZW0kZnJlcSksIGMoIml0ZW0iLCJ3ZWlnaHQiKV0NCiAgDQpoZWFkKGRmX2l0ZW1fbG9uZykNCmBgYA0KDQpNYXJpIGtpdGEgYXR1ciBmdW5nc2kgdHVqdWFuLg0KDQokJFRvdGFsXCBXZWlnaHQgPSBcU2lnbWFfe2k9MX1ee2t9IFwgV19rIFwgKiBcIG5fayAkJA0KDQoqICRUb3RhbFwgQmVyYXQkOiBCZXJhdCB0b3RhbCAoa2cpDQoqICRrJDogSnVtbGFoIGl0ZW0gdW5paw0KKiAkV19rJDogQmVyYXQgYmFyYW5nIGsNCiogJG5fayQ6IEp1bWxhaCBpdGVtIGsgeWFuZyBkaXBpbGloDQoNCkJhdGFzYW46DQoNCiQkVG90YWxcIFdlaWdodCA8PSAxMDAwMCQkDQoNCldlIHdpbGwgdHJhbnNsYXRlIHRoZSBvYmplY3RpdmUgZnVuY3Rpb24gaW50byBgUiBmdW5jdGlvbmAuDQoNCmBgYHtyfQ0Kd2VpZ2h0bGltaXQgPC0gMTAwMDANCg0KZXZhbEZ1bmMgPC0gZnVuY3Rpb24oeCkgew0KICAjIFN1YnNldCBvbmx5IHNlbGVjdGVkIGl0ZW0gYmFzZWQgb24gdGhlIHNvbHV0aW9uDQogIGRmIDwtIGRmX2l0ZW1fbG9uZ1t4ID09IDEsIF0NCiAgDQogICMgY2FsY3VsYXRlIHRoZSB0b3RhbCB3ZWlnaHQNCiAgdG90YWxfd2VpZ2h0IDwtIHN1bShkZiR3ZWlnaHQpDQogIA0KICAjIFBlbmFsdHkgaWYgdGhlIHRvdGFsIHdlaWdodCBzdXJwYXNzIHRoZSBsaW1pdA0KICB0b3RhbF93ZWlnaHQgPC0gaWZfZWxzZSh0b3RhbF93ZWlnaHQgPiB3ZWlnaHRsaW1pdCwgMCwgdG90YWxfd2VpZ2h0KQ0KICANCiAgcmV0dXJuKHRvdGFsX3dlaWdodCkNCn0NCmBgYA0KDQpNYXJpIGtpdGEgamFsYW5rYW4gYWxnb3JpdG1hbnlhLiBLYW1pIGFrYW4gbWVuZ2d1bmFrYW4gcGVuZ2tvZGVhbiBiaW5lciBrYXJlbmEgdmFyaWFiZWwga2VwdXR1c2FuIGxvZ2lzIChCRU5BUiBhdGF1IFNBTEFIKSwgZGVuZ2FuIHNldGlhcCBiaXQgbWV3YWtpbGkgbmlsYWkgbG9naXMgdHVuZ2dhbCBhcGFrYWggaXRlbSB0ZXJzZWJ1dCBkaXBpbGloLg0KDQpQYXJhbWV0ZXIgYWxnb3JpdG1hOg0KDQoqIGB0eXBlYDogSmVuaXMgcGVuZ2tvZGVhbiBnZW4sIGRpIHNpbmkga2FtaSBtZW5nZ3VuYWthbiAiYmluZXIiDQoqIGBmaXRuZXNzYDogRnVuZ3NpIGtlYnVnYXJhbiB5YW5nIGFrYW4gZGlvcHRpbWFsa2FuDQoqIGBuQml0c2A6IEp1bWxhaCBiaXQgeWFuZyBkaWhhc2lsa2FuIHVudHVrIHZhcmlhYmVsIGtlcHV0dXNhbg0KKiBgcG11dGF0aW9uYDogcHJvYmFiaWxpdGFzIG11dGFzaSwgZGVmYXVsdCAwLjENCiogYHBjcm9zc292ZXJgOiBwcm9iYWJpbGl0YXMgY3Jvc3NvdmVyLCBkZWZhdWx0IDAsOA0KKiBgc2VlZGA6IE5pbGFpIHNlZWQgYWNhayB1bnR1ayBtZW5kYXBhdGthbiBoYXNpbCB5YW5nIGRhcGF0IGRpcmVwcm9kdWtzaQ0KKiBgZWxpdGlzbWVgOiBKdW1sYWggc29sdXNpIHRlcmJhaWsgeWFuZyBha2FuIGJlcnRhaGFuIGRpIHNldGlhcCBpdGVyYXNpLCBkZWZhdWx0bnlhIGFkYWxhaCA1JSB0ZXJhdGFzIGRhcmkgcG9wdWxhc2kuDQoqIGBtYXhpdGVyYDogSnVtbGFoIGl0ZXJhc2kgbWFrc2ltYWwgdW50dWsgbWVuamFsYW5rYW4gYWxnb3JpdG1lDQoqIGBwb3BTaXplYDogSnVtbGFoIHNvbHVzaSBkYWxhbSBzdWF0dSBwb3B1bGFzaQ0KKiBgcnVuYDogSnVtbGFoIGl0ZXJhc2kgc2ViZWx1bSBhbGdvcml0bWEgYmVyaGVudGkgamlrYSB0aWRhayBhZGEgcGVyYmFpa2FuIHBhZGEgbmlsYWkgZml0bmVzcyBvcHRpbWFsDQoqIGBwYXJhbGVsYDogQXBha2FoIGtpdGEgYWthbiBtZW5nZ3VuYWthbiBrb21wdXRhc2kgcGFyYWxlbD8gRGFwYXQgYmVydXBhIG5pbGFpIGxvZ2lrYSBhdGF1IGp1bWxhaCBpbnRpIHlhbmcgZGlndW5ha2FuDQoqIGBsb3dlcmA6IE5pbGFpIHRlcmVuZGFoIGRhcmkgcGVuZ2tvZGVhbiBwZXJtdXRhc2kNCiogYHVwcGVyYDogTmlsYWkgdGVydGluZ2dpIGRhcmkgcGVuZ2tvZGVhbiBwZXJtdXRhc2kNCiogYGtlZXBCZXN0YDogTG9naXMgYXBha2FoIGthbWkgbWVueWltcGFuIHNvbHVzaSB0ZXJiYWlrIGRpIHNldGlhcCBnZW5lcmFzaSBkaSBzbG90IHRlcnBpc2FoDQoNCkFkYSBiZWJlcmFwYSBvcHNpIHlhbmcgZGFwYXQgZGlwaWxpaCB1bnR1ayBzZXRpYXAgcHJvc2VzIEdBLiBNaXNhbG55YSwgcGVtaWxpaGFuIGluZHVrIGRlZmF1bHQgR0EgdW50dWsgYmluZXIgYWRhbGFoIHBlbWlsaWhhbiBwZXJpbmdrYXQgbGluaWVyLiBNZXRvZGUgZGVmYXVsdCB0ZXJwZXJpbmNpIHlhbmcgZGlndW5ha2FuIHBhZGEgc2V0aWFwIHBlbnlhbmRpYW4gZGFwYXQgZGl0ZW11a2FuIGRpIHNpbmlbXjddLg0KDQpGdW5nc2kgYHRpYygpYCBkYW4gYHRvYygpYCBkaWd1bmFrYW4gdW50dWsgbWVuZ3VrdXIgd2FrdHUga29tcHV0YXNpLg0KDQpEaSBzaW5pIGtpdGEgYWthbiBtZW5qYWxhbmthbiBhbGdvcml0bWUgZ2VuZXRpa2EgZGVuZ2FuIHBlbmdrb2RlYW4gYmluZXIsIGZ1bmdzaSBmaXRuZXNzbnlhIGFkYWxhaCBgZXZhbEZ1bmMoKWAgeWFuZyBraXRhIGJ1YXQgc2ViZWx1bW55YS4gVWt1cmFuIHBvcHVsYXNpIChgcG9wU2l6ZWApIGFkYWxhaCAxMDAgZGVuZ2FuIGl0ZXJhc2kgbWFrc2ltdW0gKGBtYXhpdGVyYCkganVnYSAxMDAuIE1ha3NpbXVtIHJ1biAoYHJ1bmApIHRhbnBhIHBlcmJhaWthbiBhZGFsYWggMjAsIHNldGVsYWggaXR1IGFsZ29yaXRtYSBha2FuIGRpaGVudGlrYW4uIGBTZWVkYCB1bnR1ayByZXByb2R1a3RpZml0YXMgZGlhdHVyIHBhZGEgMTIzLiBLYXJlbmEga2FtaSBtZW5nZ3VuYWthbiBwZW5na29kZWFuIGJpbmVyLCBwYXJhbWV0ZXIgYG5CaXRzYCBtZW5nb250cm9sIGp1bWxhaCBiaXQgeWFuZyBkaWhhc2lsa2FuLiBLYXJlbmEgc2V0aWFwIGJpdCBha2FuIG1ld2FraWxpIGtlcHV0dXNhbiB1bnR1ayBzZXRpYXAgaXRlbSwganVtbGFoIGJpdCBzYW1hIGRlbmdhbiBqdW1sYWggYmFyaXMgZGFyaSBgZGZfaXRlbV9sb25nYC4NCg0KYGBge3J9DQp0aWMoKQ0KbGlicmFyeShHQSkNCmdhX2tuYXBzYWNrIDwtIGdhKHR5cGUgPSAiYmluYXJ5IiwgZml0bmVzcyA9IGV2YWxGdW5jLCBwb3BTaXplID0gMTAwLCANCiAgICAgICAgICAgICAgICAgIG1heGl0ZXIgPSAxMDAsIA0KICAgICAgICAgICAgICAgICAgcnVuID0gMjAsIG5CaXRzID0gbnJvdyhkZl9pdGVtX2xvbmcpLCBzZWVkID0gMTIzKQ0KdG9jKCkNCmBgYA0KDQpJdGVyYXNpIGJlcmhlbnRpIHBhZGEgaXRlcmFzaSAyMiBrYXJlbmEgc2V0ZWxhaCAyMCBpdGVyYXNpIHRpZGFrIGFkYSBwZXJiYWlrYW4gcGFkYSBgdG90YWwgd2VpZ2h0YC4gU29sdXNpIG9wdGltYWwgZGFwYXQgZGlsaWhhdCBwYWRhIGBOaWxhaSBmdW5nc2kga2VidWdhcmFuYCB5YWl0dSAxMC4wMDAgKDEwLjAwMCBrZykuDQoNCktlbHVhcmFuIGBTb2x1dGlvbmAgYWRhbGFoIHNvbHVzaSB5YW5nIG1lbmdoYXNpbGthbiBuaWxhaSBmaXRuZXNzIHlhbmcgb3B0aW1hbC4gS2l0YSBkYXBhdCBtZW1pbGloIHNhbGFoIHNhdHVueWEga2FyZW5hIGFrYW4gbWVuZ2hhc2lsa2FuIG5pbGFpIGZpdG5lc3MgeWFuZyBzYW1hLiBNYXJpIGtpdGEgcGVyaWtzYSBzYWxhaCBzYXR1bnlhLg0KDQpgYGB7cn0NCnN1bW1hcnkoZ2Ffa25hcHNhY2spDQpgYGANClNvbHVzaSAqKnRlcmJ1cnVrKiogZGFsYW0gcG9wdWxhc2kgbWVtaWxpa2kgbmlsYWkgZml0bmVzcyAwIChtZXJla2EgeWFuZyB0ZXJrZW5hIHBlbmFsdGkga2FyZW5hIG1lbGV3YXRpIGJhdGFzYW4gYmF0YXMgYm9ib3QpLCBzZWRhbmdrYW4gc29sdXNpICoqb3B0aW1hbCoqIG1lbWlsaWtpIG5pbGFpIGZpdG5lc3MgMTAwMDAuIERhcmkgc2VtdWEgMTAwIGtyb21vc29tIGF0YXUgc29sdXNpIGRpaGFzaWxrYW4sIG1lZGlhbiBuaWxhaSBmaXRuZXNzIGFkYWxhaCA5NTcyIHNlZGFuZ2thbiBtZWFubnlhIGFkYWxhaCA2NzMzLg0KDQpVbnR1ayBtZW1lcmlrc2EgcmluZ2thc2FuLCBrYW1pIG1lbmdndW5ha2FuIGZ1bmdzaSBgc3VtbWFyeSgpYCBwYWRhIG9iamVrIEdBDQoNCmBgYHtyfQ0Kc3VtbWFyeShnYV9rbmFwc2FjaykNCmBgYA0KDQpTZXBlcnRpIHlhbmcga2l0YSBsaWhhdCwgYWRhIGJlYmVyYXBhIHNvbHVzaSB1bnR1ayBtYXNhbGFoIHRlcnNlYnV0LCBzZW11YW55YSBtZW1pbGlraSBuaWxhaSBmaXRuZXNzIHlhbmcgc2FtYTogMTAwMDAuIE9sZWgga2FyZW5hIGl0dSwga2FtaSBtZW1pbGlraSBmbGVrc2liaWxpdGFzIHVudHVrIG1lbWlsaWggc29sdXNpIG1hbmEgeWFuZyBha2FuIGthbWkgcGlsaWguIFNlbXVhIHNvbHVzaSBvcHRpbWFsIHRpZGFrIG1lbWlsaWtpIG5pbGFpIGxlYmloIGJlc2FyIGRhcmkga2VuZGFsYW55YSwgeWFuZyBqdWdhIDEwLjAwMC4NCg0KTWFyaSBraXRhIHBlcmlrc2EgcGxvdCBwcm9ncmVzaSBuaWxhaSBmaXRuZXNzIG1lbmdndW5ha2FuIGBwbG90KClgLg0KDQpgYGB7cn0NCnBsb3QoZ2Ffa25hcHNhY2spDQpgYGANCg0KS2l0YSBkYXBhdCBtZWxpaGF0IGJhaHdhIHNvbHVzaSBvcHRpbWFsIChCZXN0KSBzdWRhaCB0ZXJjYXBhaSBwYWRhIGl0ZXJhc2kgMy4NCg0KTWFyaSBraXRhIHBpbGloIHNhbGFoIHNhdHUgc29sdXNpIHNlYmFnYWkga2VwdXR1c2FuIGtpdGEuDQoNCmBgYHtyfQ0KZGZfc29sIDwtIGRmX2l0ZW1fbG9uZ1tnYV9rbmFwc2Fja0Bzb2x1dGlvblsxLCBdID09IDEsIF0NCg0KZGZfc29sIDwtIGRmX3NvbCAlPiUgIA0KICBncm91cF9ieShpdGVtLCB3ZWlnaHQpICU+JSANCiAgc3VtbWFyaXNlKGZyZXEgPSBuKCkpICU+JSANCiAgbXV0YXRlKHRvdGFsX3dlaWd0aCA9IGZyZXEgKiB3ZWlnaHQpDQoNCmRmX3NvbA0KYGBgDQoNCk1hcmkga2l0YSBwZXJpa3NhIGJlcmF0IHRvdGFsbnlhDQpgYGB7cn0NCnN1bShkZl9zb2wkdG90YWxfd2VpZ3RoKQ0KYGBgDQoNCmBgYHtyfQ0KZ2Ffa25hcHNhY2tAc29sdXRpb24NCmBgYA0KDQoNCk5pbGFpIGZpdG5lc3MgeWFuZyBvcHRpbWFsIHNhbWEgZGVuZ2FuIGNvbnN0cmFpbnRueWEsIHNlaGluZ2dhIGtpdGEgZGFwYXQgbWVtdWF0IGtlbmRhcmFhbiBzZWNhcmEgcGVudWggc2VzdWFpIGRlbmdhbiBrYXBhc2l0YXMgbWFrc2ltdW1ueWEuDQoNCk1hcmkga2l0YSBwZXJpa3NhIHNvbHVzaSBrZXRpZ2ENCmBgYHtyfQ0KZGZfc29sIDwtIGRmX2l0ZW1fbG9uZ1tnYV9rbmFwc2Fja0Bzb2x1dGlvblszLCBdID09IDEsIF0NCg0KZGZfc29sIDwtIGRmX3NvbCAlPiUgIA0KICBncm91cF9ieShpdGVtLCB3ZWlnaHQpICU+JSANCiAgc3VtbWFyaXNlKGZyZXEgPSBuKCkpICU+JSANCiAgbXV0YXRlKHRvdGFsX3dlaWd0aCA9IGZyZXEgKiB3ZWlnaHQpDQoNCmRmX3NvbA0KYGBgDQoNCg0KYGBge3J9DQpzdW0oZGZfc29sJHRvdGFsX3dlaWd0aCkNCmBgYA0KDQpNZXNraXB1biBzb2x1c2kgcGVydGFtYSBkYW4gc29sdXNpIGtlZHVhIG1lbWlsaWtpIGtlcHV0dXNhbiB5YW5nIGJlcmJlZGEsIGZ1bmdzaSBmaXRuZXNzIGF0YXUgYm9ib3QgdG90YWxueWEgc2FtYSwgb2xlaCBrYXJlbmEgaXR1IGtlZHVhbnlhIG1lcnVwYWthbiBzb2x1c2kgb3B0aW1hbCBkYW4ga2VwdXR1c2FuIHVudHVrIG1lbWlsaWggbWFuYSB5YW5nIGhhcnVzIGRpZ3VuYWthbiBzZWJlbmFybnlhIGRpc2VyYWhrYW4ga2VwYWRhIHBlbmdhbWJpbCBrZXB1dHVzYW4uDQoNCiMjIyBUcmF2ZWxpbmcgU2FsZXNtYW4gUHJvYmxlbQ0KDQpUcmF2ZWxsaW5nIFNhbGVzcGVyc29uIFByb2JsZW0gKFRTUCkgYWRhbGFoIHNhbGFoIHNhdHUgbWFzYWxhaCB5YW5nIHBhbGluZyBiYW55YWsgZGliYWhhcyBkYWxhbSBvcHRpbWFzaSBrb21iaW5hdG9yaWFsLiBEYWxhbSBiZW50dWtueWEgeWFuZyBwYWxpbmcgc2VkZXJoYW5hLCBwZXJ0aW1iYW5na2FuIHNhdHUgc2V0IG4ga290YSBkZW5nYW4gamFyYWsgaW50cmEgc2ltZXRyaXMgeWFuZyBkaWtldGFodWksIFRTUCBtZWxpYmF0a2FuIHBlbmNhcmlhbiBydXRlIG9wdGltYWwgdW50dWsgbWVuZ3VuanVuZ2kgc2VtdWEga290YSBkYW4ga2VtYmFsaSBrZSB0aXRpayBhd2FsIHNlZGVtaWtpYW4gcnVwYSBzZWhpbmdnYSBqYXJhayB5YW5nIGRpdGVtcHVoIGRpbWluaW1hbGthbi4gSGltcHVuYW4gc29sdXNpIGxheWFrIGRpYmVyaWthbiBvbGVoIGp1bWxhaCB0b3RhbCBydXRlIHlhbmcgbXVuZ2tpbiwgeWFuZyBzYW1hIGRlbmdhbiAobiAxKSEvMi4NCg0KUGVydGltYmFuZ2thbiBrdW1wdWxhbiBkYXRhIGBldXJvZGlzdGAgaW5pLiBUZXJkaXJpIGRhcmkgMjEgeCAyMSBtYXRyaWtzIGphcmFrIGFudGFyIGtvdGEuIEp1bWxhaCB0b3RhbCBydXRlIHlhbmcgbXVuZ2tpbiBhZGFsYWggKDIxIC0gMSkhLzIgPSAxMjE2NDUxMDA0MDg4MzIwMDAwLiBNZW5jb2JhIHNldGlhcCBydXRlIGFrYW4gbWVtYWthbiBiYW55YWsgd2FrdHUuIFBlbmNhcmlhbiBhY2FrIHNlZGVyaGFuYSBtdW5na2luIGdhZ2FsIG1lbmVtdWthbiBnbG9iYWwgb3B0aW1hLg0KDQpgYGB7cn0NCmNpdGllcyA8LSBhcy5tYXRyaXgoZXVyb2Rpc3QpDQoNCmhlYWQoY2l0aWVzKQ0KYGBgDQoNCmthcmVuYSB0dWp1YW5ueWEgYWRhbGFoIHVudHVrIG1lbWluaW1hbGthbiBqYXJhaywgbmlsYWkgZml0bmVzcyBha2FuIGJlcm5pbGFpIG5lZ2F0aWYuIA0KDQpgYGB7cn0NCnRzcF9maXRuZXNzIDwtIGZ1bmN0aW9uKHgpIHsNCiAgeCA8LSBjKHgsIHhbMV0pDQogIA0KICAjIGNyZWF0ZSBmcm9tLXRvIG1hdHJpeCANCiAgcm91dGUgPC0gZW1iZWQoeCwgMilbLCAyOjFdDQogDQogIyBDYWxjdWxhdGUgZGlzdGFuY2UNCiBkaXN0YW5jZSA8LSAtc3VtKGNpdGllc1tyb3V0ZV0pDQogcmV0dXJuKGRpc3RhbmNlKQ0KIH0NCmBgYA0KDQpMZXQncyBydW4gdGhlIGFsZ29yaXRobS4NCg0KYGBge3J9DQp0aWMoKQ0KZ2FfY2l0aWVzIDwtIGdhKHR5cGUgPSAicGVybXV0YXRpb24iLCBmaXRuZXNzID0gdHNwX2ZpdG5lc3MsIG1vbml0b3IgPSBGLA0KICAgICAgICAgbG93ZXIgPSAxLCB1cHBlciA9IG5yb3coY2l0aWVzKSwgcG9wU2l6ZSA9IDEwMCwgbWF4aXRlciA9IDUwMDAsIA0KICAgICAgICAgcnVuID0gNTAwLCBzZWVkID0gMTIzKQ0Kc3VtbWFyeShnYV9jaXRpZXMpDQp0b2MoKQ0KYGBgDQoNCkthbWkgbWVtaWxpa2kgNSBzb2x1c2kvcnV0ZSB5YW5nIG1lbWlsaWtpIGphcmFrIG1pbmltYWwuIFViYWggaW5mb3JtYXNpIG1lbmphZGkgcnV0ZSB5YW5nIGRhcGF0IGRpYmFjYQ0KYGBge3J9DQpyb3V0ZV9vcHRpbWFsIDwtIGdhX2NpdGllc0Bzb2x1dGlvbg0KDQpjaXRpZXNfbmFtZSA8LSBuYW1lcyhjaXRpZXNbMSAsIF0pDQoNCmNpdGllc19uYW1lIDwtIGFwcGx5KHJvdXRlX29wdGltYWwsIDEsIGZ1bmN0aW9uKHgpIHtjaXRpZXNfbmFtZVt4XX0pDQoNCmFwcGx5KGNpdGllc19uYW1lLCAyLCBwYXN0ZSwgY29sbGFwc2UgPSAiID4gIikNCmBgYA0KDQpCZXR0ZXIgcmVwcmVzZW50YXRpb24gZm9yIHRoZSByb3V0ZSBjYW4gYmUgdmlzdWFsaXplZCB0aHJvdWdoIG5ldHdvcmsgb3IgZ3JhcGguDQoNCg0KIyMgQnVzaW5lc3MgQXBwbGljYXRpb24NCg0KIyMjIFByb2R1Y3Rpb24gU2NoZWR1bGluZyANCg0KTWlzYWxrYW4ga2l0YSBtZW1pbGlraSBiZXJiYWdhaSB1bml0IG1vYmlsIHlhbmcgYWthbiBkaXByb2R1a3NpIGRpIHBhYnJpayBraXRhLiBBZGEgNyBqZW5pcyB3YXJuYSBwcm9kdWsgeWFuZyBiZXJiZWRhLiBKaWthIHVuaXQgYmVyaWt1dCBtZW1pbGlraSB3YXJuYSB5YW5nIGJlcmJlZGEgZGFyaSBtb2JpbCBzZWJlbHVtbnlhLCBtYWthIGFrYW4gYWRhIGJpYXlhIGNoYW5nZW92ZXIgKGJpYXlhIHlhbmcgdGltYnVsIGthcmVuYSBwZXJ1YmFoYW4gamVuaXMgcHJvZHVrKSwgc2VwZXJ0aSBiaWF5YSBtYXRlcmlhbCB5YW5nIGRpYnV0dWhrYW4gdW50dWsgbWVtYmVyc2loa2FuIHBlcmFsYXRhbiBkYXJpIHdhcm5hIGNhdCBzZWJlbHVtbnlhLiBBZGEgYmFueWFrIGNhcmEgdW50dWsgbWVuZ29wdGltYWxrYW4gdXJ1dGFuIHdhcm5hLCB0ZXRhcGkga2FtaSBha2FuIG1lbmdndW5ha2FuIHlhbmcgcGFsaW5nIHNlZGVyaGFuYSBkaSBzaW5pLiBLYW1pIGluZ2luIG1lbWluaW1hbGthbiBqdW1sYWggc2V0dXAsIG1lbWJ1YXQganVtbGFoIHBlcmdhbnRpYW4gd2FybmEgYXRhdSBwZXJnYW50aWFuIHRlcmphZGkgbGViaWggc2VkaWtpdCwgc2VoaW5nZ2EgYmlheWEgcGVyZ2FudGlhbiBqdWdhIGRhcGF0IGRpbWluaW1hbGthbi4NCg0KUGVydGFtYSBraXRhIGltcG9yIGRhdGFueWEuIFNheWEgc3VkYWggbWVueWlhcGthbiBkYXRhIGR1bW15IHlhbmcgYmlzYSBBbmRhIGFrc2VzIGRpIHNpbmkuDQoNCmBgYHtyfQ0KZGZfY2FyIDwtIHJlYWQuY3N2KCJkYXRhX2lucHV0L2Nhcl9kYXRhLmNzdiIpDQoNCmRmX2Nhcg0KYGBgDQoNClRoZSBvYmplY3RpdmUgZnVuY3Rpb24gd2lsbCBiZToNCiQkIFMgPSAxICsgXFNpZ21hX3trPTJ9XntEVH0gXCBTX2skJA0KDQoqICRTJCA9IGp1bWxhaCBwZW5nYXR1cmFuDQoqICREX1QkID0gSnVtbGFoIG1vYmlsL3VuaXQgeWFuZyBha2FuIGRpcHJvZHVrc2kNCiogJGskID0gIFBvc2lzaSBtb2JpbCBkYWxhbSBiYXJpc2FuDQoqICRTX2skPSBBcGFrYWggcGVuZ2F0dXJhbiBkaXBlcmx1a2FuPyAxIGppa2EgbW9iaWwgYmVyaWt1dG55YSAoSysxKSB0aWRhayBtZW1pbGlraSB3YXJuYSB5YW5nIHNhbWEgZGVuZ2FuIG1vYmlsIGtlLWsgc2FhdCBpbmksIDAgamlrYSBtb2JpbCBiZXJpa3V0bnlhIGJlcndhcm5hDQoNCkthbWkgYWthbiBtZW5lcmplbWFoa2FuIGZ1bmdzaSB0dWp1YW4gbWVuamFkaSBmdW5nc2kgUiBkYXJpIGBtaW5fc2V0dXBgLiBBbGdvcml0bWEgZGFsYW0gcGFrZXQgYEdBYCBkaWphbGFua2FuIGRhbGFtIGtvbnRla3MgbWFrc2ltaXNhc2ksIGphZGkgdW50dWsgbWVueWVsZXNhaWthbiBtYXNhbGFoIG1pbmltYXNpLCBraXRhIGFrYW4gbWVtYnVhdCBuaWxhaSBmaXRuZXNzIGtpdGEgbWVuamFkaSBuZWdhdGlmLg0KDQpgYGB7cn0NCm1pbl9zZXR1cCA8LSBmdW5jdGlvbih4KXsNCiAgZGYgPC0gZGZfY2FyW3gsIF0NCiAgcHJpbnQoZGYpDQogIFMgPC0gZGYgJT4lIA0KICBtdXRhdGUoY29sb3JfbmV4dCA9IGxlYWQoY29sb3IpLA0KICAgICAgICAgc2V0dXAgPSBpZl9lbHNlKGNvbG9yID09IGNvbG9yX25leHQsIDAsIDEpKSAlPiUgDQogIGhlYWQobnJvdyhkZiktMSkgJT4lIA0KICBwdWxsKCJzZXR1cCIpICU+JSANCiAgc3VtKCkgKyAxDQogIFMgPC0gLVMNCiAgcmV0dXJuKFMpDQp9DQpgYGANCg0KU2VrYXJhbmcga2l0YSBqYWxhbmthbiBhbGdvcml0bWFueWEuIEthcmVuYSBtYXNhbGFobnlhIGFkYWxhaCBtYXNhbGFoIHBlbmphZHdhbGFuLCBrYW1pIGFrYW4gbWVuZ2tvZGVrYW4gc29sdXNpIGthbWkgZGVuZ2FuIHBlbmdrb2RlYW4gcGVybXV0YXNpLiBLYW1pIGFrYW4gbWVuZ2hlbnRpa2FuIGFsZ29yaXRtYSBqaWthIGp1bWxhaCBpdGVyYXNpIHRlbGFoIG1lbmNhcGFpIDEwMDAgaXRlcmFzaSBhdGF1IGppa2EgZGFsYW0gMTAwIGl0ZXJhc2kgdGlkYWsgYWRhIHBlbmluZ2thdGFuIG5pbGFpIGZpdG5lc3MgeWFuZyBvcHRpbWFsLg0KDQoqKlBFTlRJTkcqKg0KXG4NClBlbWlsaWhhbiB1a3VyYW4gcG9wdWxhc2kgZGFuIGp1bWxhaCBpdGVyYXNpIGFrYW4gc2FuZ2F0IG1lbXBlbmdhcnVoaSBraW5lcmphIEdBLiBKaWthIHVrdXJhbiBwb3B1bGFzaSB0ZXJsYWx1IGtlY2lsLCBha2FuIGFkYSAqa3VtcHVsYW4gZ2VuKiB5YW5nIHRlcmJhdGFzIGRhbGFtIHBvcHVsYXNpIGtpdGEgc2VoaW5nZ2Egc29sdXNpIGJhcnUgdGlkYWsgYWthbiB0ZXJsYWx1IGJlcmJlZGEgamF1aCBkYXJpIGdlbmVyYXNpIHNlYmVsdW1ueWEuIEppa2EganVtbGFoIGl0ZXJhc2kgdGVybGFsdSBzZWRpa2l0LCB0aWRhayBha2FuIGFkYSBjdWt1cCB3YWt0dSBiYWdpIGFsZ29yaXRtYSB1bnR1ayBtZW5lbXVrYW4gc29sdXNpIG9wdGltYWwgYmFydS4gUGFrZXQgYEdBYCBkYXBhdCBkaWphbGFua2FuIGRlbmdhbiBrb21wdXRhc2kgcGFyYWxlbC4NCg0KUGFyYW1ldGVyIGFsZ29yaXRtYToNCg0KKiBgdHlwZWA6IEplbmlzIHBlbmdrb2RlYW4gZ2VuLCBkaSBzaW5pIGtpdGEgbWVuZ2d1bmFrYW4gInBlcm11dGFzaSINCiogYGtlYnVnYXJhbmA6IEZ1bmdzaSBrZWJ1Z2FyYW4geWFuZyBha2FuIGRpb3B0aW1hbGthbg0KKiBgbGViaWggcmVuZGFoYDogTmlsYWkgdGVyZW5kYWggZGFyaSBwZW5na29kZWFuIHBlcm11dGFzaSwga2FtaSBha2FuIG1lbmdrb2Rla2FuIHBlcm11dGFzaSBkYXJpIDEga2UganVtbGFoIG1vYmlsIHlhbmcgdGVyc2VkaWENCiogYGF0YXNgOiBOaWxhaSB0ZXJ0aW5nZ2kgZGFyaSBwZW5na29kZWFuIHBlcm11dGFzaQ0KKiBgc2VlZGA6IE5pbGFpIHNlZWQgYWNhayB1bnR1ayBtZW5kYXBhdGthbiBoYXNpbCB5YW5nIGRhcGF0IGRpcmVwcm9kdWtzaQ0KKiBgZWxpdGlzbWAgOiBKdW1sYWggc29sdXNpIHRlcmJhaWsgeWFuZyBha2FuIGJlcnRhaGFuIGRpIHNldGlhcCBpdGVyYXNpLCBkZWZhdWx0IGFkYWxhaCA1JSB0ZXJhdGFzIGRhcmkgcG9wdWxhc2ksIGRpIHNpbmkga2FtaSBtZW5ldGFwa2FubnlhIG1lbmphZGkgNTAgc29sdXNpIHlhbmcgYWthbiBiZXJ0YWhhbg0KKiBgbWF4aXRlcmA6IEp1bWxhaCBpdGVyYXNpIG1ha3NpbWFsIHVudHVrIG1lbmphbGFua2FuIGFsZ29yaXRtYQ0KKiBgcG9wU2l6ZWA6IEp1bWxhaCBzb2x1c2kgZGFsYW0gc3VhdHUgcG9wdWxhc2kNCiogYHJ1bmA6IEp1bWxhaCBpdGVyYXNpIHNlYmVsdW0gYWxnb3JpdG1hIGJlcmhlbnRpIGppa2EgdGlkYWsgYWRhIHBlcmJhaWthbiBwYWRhIG5pbGFpIGZpdG5lc3Mgb3B0aW1hbA0KKiBgcGFyYWxlbGA6IEFwYWthaCBraXRhIGFrYW4gbWVuZ2d1bmFrYW4ga29tcHV0YXNpIHBhcmFsZWw/IERhcGF0IGJlcnVwYSBuaWxhaSBsb2dpa2EgYXRhdSBqdW1sYWggaW50aSB5YW5nIGRpZ3VuYWthbg0KDQpQYXJhbWV0ZXIgbGFpbm55YSwgc2VwZXJ0aSBwcm9iYWJpbGl0YXMgY3Jvc3NvdmVyIGRhbiBtdXRhc2kgZGlzZXJhaGthbiBrZSBwZW5nYXR1cmFuIGRlZmF1bHQuDQoNCg0KYGBge3IgaW5jbHVkZT1GQUxTRX0NCmxpYnJhcnkodGljdG9jKQ0KbGlicmFyeShHQSkNCmxpYnJhcnkocGFyYWxsZWwpDQpsaWJyYXJ5KGRvUGFyYWxsZWwpDQpsaWJyYXJ5IChkcGx5cikNCmBgYA0KDQpgYGB7cn0NCnRpYygpDQpnYW5uIDwtIGdhKHR5cGUgPSAicGVybXV0YXRpb24iLCBmaXRuZXNzID0gbWluX3NldHVwLCBsb3dlciA9IDEsIHVwcGVyID0gbnJvdyhkZl9jYXIpLCANCiAgICBzZWVkID0gMTIzLCBlbGl0aXNtID0gNTAsIG1heGl0ZXIgPSAzMDAsIHBvcFNpemUgPSAzMDAsIHJ1biA9IDMwLCBwYXJhbGxlbCA9IHBhcmFsbGVsOjpkZXRlY3RDb3JlcygpKQ0KDQpzdW1tYXJ5KGdhbm4pDQpgYGANCg0KYGBge3J9DQp0b2MoKQ0KYGBgDQoNCktpdGEgZGFwYXQgbWVtcGxvdCBwZXJrZW1iYW5nYW4gYWxnb3JpdG1hIGRlbmdhbiBtZW5nZ3VuYWthbiBmdW5nc2kgYHBsb3QoKWAgcGFkYSBvYmplayBHQS4NCg0KYGBge3J9DQpwbG90KGdhbm4pDQpgYGANCg0KU29sdXNpIHRlcmJhaWsgYXRhdSBvcHRpbWFsIGRpcGVyb2xlaCBzZXRpZGFrbnlhIHBhZGEgaXRlcmFzaSBrZS02NyBkYW4gdGlkYWsgbWVuZ2FsYW1pIHBlcmJhaWthbiBzZWphayBzYWF0IGl0dSwgc2VoaW5nZ2EgYWxnb3JpdG1hIGJlcmhlbnRpIHNldGVsYWggMzAgaXRlcmFzaSBwYWRhIGl0ZXJhc2kga2UtOTYuDQoNCk91dHB1dCBgU29sdXRpb25gIGFkYWxhaCBzb2x1c2kgeWFuZyBtZW5naGFzaWxrYW4gbmlsYWkgZml0bmVzcyB5YW5nIG9wdGltYWwuIEtpdGEgZGFwYXQgbWVtaWxpaCBzYWxhaCBzYXR1bnlhIGthcmVuYSBha2FuIG1lbmdoYXNpbGthbiBuaWxhaSBmaXRuZXNzIHlhbmcgc2FtYS4gTWFyaSBraXRhIHBlcmlrc2Egc2FsYWggc2F0dW55YS4NCg0KYGBge3J9DQptaW5fc2V0dXAoZ2FubkBzb2x1dGlvblsxLCBdKQ0KYGBgDQoNCkppa2EgaW5naW4gbWVsaWhhdCBzZW11YSBuaWxhaSBmaXRuZXNzIGRhcmkgcG9wdWxhc2kgdGVyYWtoaXIsIGd1bmFrYW4gYmlzYSBtZW5nZ3VuYWthbiBhdHJpYnV0IGBmaXRuZXNzYCBkYXJpIG9iamVrIGBHQWAuIEthbWkgYWthbiBtZXJpbmdrYXMgbmlsYWkgZml0bmVzcyB1bnR1ayBtZW5kYXBhdGthbiBkaXN0cmlidXNpbnlhLg0KDQpgYGB7cn0NCnN1bW1hcnkoZ2FubkBmaXRuZXNzKQ0KYGBgDQoNClNvbHVzaSAqKnRlcmJ1cnVrKiogZGFsYW0gcG9wdWxhc2kgbWVtaWxpa2kgbmlsYWkgZml0bmVzcyAyNSwgc2VkYW5na2FuIHNvbHVzaSAqKm9wdGltYWwqKiBtZW1pbGlraSBuaWxhaSBmaXRuZXNzIDcuIERhcmkgc2VsdXJ1aCAzMDAga3JvbW9zb20gYXRhdSBzb2x1c2kgeWFuZyBkaWhhc2lsa2FuLCBtZWRpYW4gbmlsYWkgZml0bmVzcyBhZGFsYWggMTUgc2VkYW5na2FuIG1lYW5ueWEgYWRhbGFoIDE0LDI5Lg0KDQpNYXJpIGtpdGEgbWVuZW1wYXRrYW4gdXJ1dGFuIG9wdGltYWwgdW50dWsgZGF0YSBraXRhLiBEYXRhIGZyYW1lIHlhbmcgZGloYXNpbGthbiBha2FuIG1lbmphZGkgamFkd2FsIHByb2R1a3NpIGtpdGEuIEthcmVuYSBhZGEgYmFueWFrIHNvbHVzaSwgbWFyaSBraXRhIGxpaGF0IGR1YSBzb2x1c2kgb3B0aW1hbCBwZXJ0YW1hLg0KDQpTb2x1c2kgcGVydGFtYToNCg0KYGBge3J9DQpkZl9jYXJbZ2FubkBzb2x1dGlvblsxLCBdLCBdDQpgYGANCg0KU29sdXNpIEtlZHVhOg0KDQpgYGB7cn0NCmRmX2NhcltnYW5uQHNvbHV0aW9uWzIsIF0sIF0NCmBgYA0KDQpNZXNraXB1biBzb2x1c2kgcGVydGFtYSBkYW4gc29sdXNpIGtlZHVhIG1lbWlsaWtpIHVydXRhbiB5YW5nIGJlcmJlZGEsIGZ1bmdzaSBmaXRuZXNzIGF0YXUganVtbGFoIHNldHVwIGFkYWxhaCBzYW1hLCBvbGVoIGthcmVuYSBpdHUga2VkdWFueWEgbWVydXBha2FuIHNvbHVzaSBvcHRpbWFsIGRhbiBrZXB1dHVzYW4gdW50dWsgbWVtaWxpaCBtYW5hIHlhbmcgaGFydXMgZGlndW5ha2FuIHNlYmVuYXJueWEgZGlzZXJhaGthbiBrZXBhZGEgcGVuZ2FtYmlsIGtlcHV0dXNhbi4gLCBkYWxhbSBoYWwgaW5pLCBzdGFmIGtvbnRyb2wgcHJvZHVrc2kuDQoNCiMjIyBLbmFwc2FjayBQcm9ibGVtDQoNCk1pc2Fsa2FuIGtpdGEgbWVtaWxpa2kgYmViZXJhcGEgaXRlbSB1bnR1ayBkaWtpcmltIGtlIHB1c2F0IGRpc3RyaWJ1c2kuIE5hbXVuLCB0cnVrIGthbWkgaGFueWEgZGFwYXQgbWVtdWF0IGhpbmdnYSB0b3RhbCAxMCB0b24gYXRhdSAxMC4wMDAga2csIGphZGkga2FtaSBoYXJ1cyBtZW1pbGloIGJhcmFuZyBtYW5hIHlhbmcgYWthbiBkaWtpcmltIGRhbiBtZW1ha3NpbWFsa2FuIGJlcmF0IHlhbmcgZGltdWF0LiBBbmRhIGRhcGF0IGxhbmdzdW5nIG1lbnllbGVzYWlrYW4gbWFzYWxhaCBpbmkgbWVuZ2d1bmFrYW4gTWV0b2RlIEtuYXBzYWNrLCB0ZXRhcGkgQW5kYSBqdWdhIGRhcGF0IG1lbmdndW5ha2FuIEFsZ29yaXRtYSBHZW5ldGlrYS4gRGkgc2luaSBraXRhIGFrYW4gbWVsaWhhdCBiYWdhaW1hbmEgR0EgbWVuYW5nYW5pIG1hc2FsYWggeWFuZyBkaWJhdGFzaSAoYmVyYXQgdGlkYWsgYm9sZWggbWVsZWJpaGkgMTAgdG9uIGF0YXUgMTAuMDAwIGtnKS4NCg0KYGBge3J9DQpkZl9pdGVtIDwtIGRhdGEuZnJhbWUoaXRlbSA9IGMoIlRpcmVzIiwgIkJ1bXBlciIsICJFbmdpbmUiLCAiQ2hhc2lzIiwgIlNlYXQiKSwgZnJlcSA9IGMoODAsIA0KICAgIDUwLCA3MCwgNTAsIDcwKSwgd2VpZ2h0ID0gYyg3LCAxNywgMTU4LCAxMDAsIDMwKSkNCg0KZGZfaXRlbQ0KYGBgDQoNClNldGlhcCBpdGVtIG1lbWlsaWtpIGp1bWxhaCBkYW4gYmVyYXQgbWFzaW5nLW1hc2luZy5zDQoNCk1hcmkga2l0YSBidWF0IGRhdGFzZXQgYmFydSBkZW5nYW4gc2F0dSBvYnNlcnZhc2kgeWFuZyBzZXN1YWkgZGVuZ2FuIGhhbnlhIDEgaXRlbS4NCg0KYGBge3J9DQpkZl9pdGVtX2xvbmcgPC0gZGZfaXRlbVtyZXAocm93bmFtZXMoZGZfaXRlbSksIGRmX2l0ZW0kZnJlcSksIGMoMSwgMyldDQoNCmRmX2l0ZW1fbG9uZw0KYGBgDQpUaGUgb2JqZWN0aXZlIGZ1bmN0aW9uIHdpbGwgYmU6DQokJCBUb3RhbCBXZWlnaHQ9IFxTaWdtYV97aT0xfV57a30gXCBXX2sqbl9rJCQNCg0KKiAkVG90YWwgV2VpZ2h0JCA9IFRvdGFsIGJlcmF0DQoqICRrJCA9IEp1bWxhaCBkYXRhIHVuaWsNCiogJFdfayQgPSAgYmVyYXQgZGF0YSBrDQoqICRuX2skPSBub21vciBpdGVtIGsgeWFuZyBkaXBpbGloDQoNCkJhdGFzYW46DQoNCiQkDQpUb3RhbCBXZWlnaHQgPD0gMTAwMDANCiQkDQoNCmBgYHtyfQ0Kd2VpZ2h0bGltaXQgPC0gMTAwMDANCg0KZXZhbEZ1bmMgPC0gZnVuY3Rpb24oeCkgew0KICAgIGRmIDwtIGRmX2l0ZW1fbG9uZ1t4ID09IDEsIF0NCiAgICB0b3RhbF93ZWlnaHQgPC0gc3VtKGRmJHdlaWdodCkNCiAgICB0b3RhbF93ZWlnaHQgPC0gaWZfZWxzZSh0b3RhbF93ZWlnaHQgPiB3ZWlnaHRsaW1pdCwgMCwgdG90YWxfd2VpZ2h0KQ0KICAgIHJldHVybih0b3RhbF93ZWlnaHQpDQp9DQpgYGANCg0KTWFyaSBraXRhIGphbGFua2FuIGFsZ29yaXRtYW55YS4gS2FtaSBha2FuIG1lbmdndW5ha2FuIHBlbmdrb2RlYW4gYmluZXIga2FyZW5hIHZhcmlhYmVsIGtlcHV0dXNhbiBiZXJuaWxhaSBpbnRlZ2VyLiBTZXRpYXAgYml0IG1ld2FraWxpIG5pbGFpIGxvZ2lzIHR1bmdnYWwgYXBha2FoIGl0ZW0gdGVyc2VidXQgZGlwaWxpaC4NCg0KYGBge3J9DQp0aWMoKQ0KZ2FubjIgPC0gZ2EodHlwZSA9ICJiaW5hcnkiLCBmaXRuZXNzID0gZXZhbEZ1bmMsIHBvcFNpemUgPSAxMDAsIG1heGl0ZXIgPSAxMDAsIHJ1biA9IDIwLCANCiAgICBuQml0cyA9IG5yb3coZGZfaXRlbV9sb25nKSwgc2VlZCA9IDEyMykNCg0Kc3VtbWFyeShnYW5uMikNCmBgYA0KDQpgYGB7cn0NCnRvYygpDQpwbG90KGdhbm4yKQ0KYGBgDQoNCk1hcmkgcGlsaWggc2FsYWggc2F0dSBzb2x1c2kgc2ViYWdhaSBwaWxpaGFuIG9wdGltYWwuDQpgYGB7cn0NCmRmX3NvbCA8LSBkZl9pdGVtX2xvbmdbZ2FubjJAc29sdXRpb25bMSwgXSA9PSAxLCBdDQoNCmRmX3NvbCA8LSBkZl9zb2wgJT4lIGdyb3VwX2J5KGl0ZW0sIHdlaWdodCkgJT4lIHN1bW1hcmlzZShmcmVxID0gbigpKSAlPiUgbXV0YXRlKHRvdGFsX3dlaWd0aCA9IGZyZXEgKiANCiAgICB3ZWlnaHQpDQoNCmRmX3NvbA0KYGBgDQpUb3RhbCBiZXJhdDoNCmBgYHtyfQ0Kc3VtKGRmX3NvbCR0b3RhbF93ZWlndGgpDQpgYGANCg0KTmlsYWkgZml0bmVzcyB5YW5nIG9wdGltYWwgc2FtYSBkZW5nYW4gY29uc3RyYWlubnlhLCBzZWhpbmdnYSBraXRhIGRhcGF0IG1lbXVhdCBrZW5kYXJhYW4gc2VjYXJhIHBlbnVoIHNlc3VhaSBkZW5nYW4ga2FwYXNpdGFzIG1ha3NpbXVtbnlhLg0KDQpQZW5lcmFwYW4gR0EgZGkgYmVyYmFnYWkgYmlkYW5nIG1hc2loIGJhbnlhaywgdGVydXRhbWEgZGFsYW0gcHJvc2VzIG1hbnVmYWt0dXIgZGFuIGVuZ2luZWVyaW5nLiBTZWxhbWEgYWRhIGZ1bmdzaSBmaXRuZXNzIGRhbiB2YXJpYWJlbCBrZXB1dHVzYW4sIEdBIGRhcGF0IGRpdGVyYXBrYW4gdGVyaGFkYXAgbWFzYWxhaCB0ZXJzZWJ1dC4NCg0KIyMgSHlwZXItUGFyYW1ldGVyIFR1bmluZyBvbiBNYWNoaW5lIExlYXJuaW5nIE1vZGVsDQoNClNla2FyYW5nIGthbWkgYWthbiBtZW5jb2JhIG1lbmVyYXBrYW4gYWxnb3JpdG1lIHVudHVrIG1lbmdvcHRpbWFsa2FuIG1vZGVsIHBlbWJlbGFqYXJhbiBtZXNpbiBrYW1pLiBLYW1pIGFrYW4gbWVuZ2d1bmFrYW4gZGF0YXNldCBgYXR0cml0aW9uYC4NCg0KIyMjIEltcG9ydCBEYXRhDQoNCmBgYHtyfQ0KYXR0cml0aW9uIDwtIHJlYWQuY3N2KCJkYXRhX2lucHV0L2F0dHJpdGlvbi5jc3YiKQ0KDQphdHRyaXRpb24gPC0gYXR0cml0aW9uICU+JSBtdXRhdGUoam9iX2ludm9sdmVtZW50ID0gYXMuZmFjdG9yKGpvYl9pbnZvbHZlbWVudCksIGVkdWNhdGlvbiA9IGFzLmZhY3RvcihlZHVjYXRpb24pLCANCiAgICBlbnZpcm9ubWVudF9zYXRpc2ZhY3Rpb24gPSBhcy5mYWN0b3IoZW52aXJvbm1lbnRfc2F0aXNmYWN0aW9uKSwgcGVyZm9ybWFuY2VfcmF0aW5nID0gYXMuZmFjdG9yKHBlcmZvcm1hbmNlX3JhdGluZyksIA0KICAgIHJlbGF0aW9uc2hpcF9zYXRpc2ZhY3Rpb24gPSBhcy5mYWN0b3IocmVsYXRpb25zaGlwX3NhdGlzZmFjdGlvbiksIHdvcmtfbGlmZV9iYWxhbmNlID0gYXMuZmFjdG9yKHdvcmtfbGlmZV9iYWxhbmNlKSkNCg0KYXR0cml0aW9uDQpgYGANCg0KIyMjIENyb3NzLVZhbGlkYXRpb24NCg0KS2FtaSBtZW1iYWdpIGRhdGEgbWVuamFkaSB0cmFpbmluZyBzZXQgZGFuIHRlc3RpbmcgZGF0YXNldCwgZGVuZ2FuIDgwJSBkYXRhIGFrYW4gZGlndW5ha2FuIHNlYmFnYWkgdHJhaW5pbmcgc2V0Lg0KDQpgYGB7ciBpbmNsdWRlPUZBTFNFfQ0KbGlicmFyeShkZXZ0b29scykNCiMgaW5zdGFsbF9naXRodWIoInRpZHltb2RlbHMvcnNhbXBsZS9ibG9iL21haW4vUi9pbml0aWFsX3NwbGl0LlIiKQ0KbGlicmFyeSh0aWR5dmVyc2UpDQpsaWJyYXJ5KEdBKQ0KbGlicmFyeShyYW5nZXIpDQpsaWJyYXJ5KHRpZHltb2RlbHMpDQpsaWJyYXJ5KGNhcmV0KQ0KbGlicmFyeSh0aWN0b2MpDQpgYGANCg0KYGBge3J9DQpzZXQuc2VlZCgxMjMpDQppbnRyYWluIDwtIGluaXRpYWxfc3BsaXQoYXR0cml0aW9uLCBwcm9wID0gMC44LCBzdHJhdGEgPSAiYXR0cml0aW9uIikNCg0KaW50cmFpbg0KYGBgDQoNCiMjIyBEYXRhIFByZXByb2Nlc3NpbmcNCg0KS2FtaSBha2FuIG1lbGFrdWthbiBwcmVwcm9jZXNzIGRhdGEgZGVuZ2FuIGxhbmdrYWggYmVyaWt1dDoNCg0KKiBVcHNhbXBsZSB1bnR1ayBtZW5pbmdrYXRrYW4ganVtbGFoIGtlbGFzIG1pbm9yaXRhcyBkYW4gbWVuY2VnYWgga2V0aWRha3NlaW1iYW5nYW4ga2VsYXMNCiogTWVuc2thbGFrYW4gc2VtdWEgdmFyaWFiZWwgbnVtZXJpaw0KKiBIYXB1cyB2YXJpYWJlbCBudW1lcmlrIGRlbmdhbiB2YXJpYW4gaGFtcGlyIG5vbA0KKiBHdW5ha2FuIFBDQSB1bnR1ayBtZW5ndXJhbmdpIGRpbWVuc2kgZGVuZ2FuIGFtYmFuZyA5MCUgaW5mb3JtYXNpIGRpc2ltcGFuDQoqIEhhcHVzIHZhcmlhYmVsIGBvdmVyXzE4YCBrYXJlbmEgaGFueWEgbWVtaWxpa2kgMSBsZXZlbCBmYWt0b3INCg0KYGBge3J9DQpyZWMgPC0gcmVjaXBlKGF0dHJpdGlvbiB+IC4sIGRhdGEgPSB0cmFpbmluZyhpbnRyYWluKSkgJT4lIA0KICB0aGVtaXM6OnN0ZXBfdXBzYW1wbGUoYXR0cml0aW9uKSAlPiUgDQogIHN0ZXBfc2NhbGUoYWxsX251bWVyaWMoKSkgJT4lIA0KICBzdGVwX256dihhbGxfbnVtZXJpYygpKSAlPiUgDQogIHN0ZXBfcGNhKGFsbF9udW1lcmljKCksIHRocmVzaG9sZCA9IDAuOSkgJT4lIA0KICBzdGVwX3JtKG92ZXJfMTgpICU+JSANCiAgcHJlcCgpDQoNCmRhdGFfdHJhaW4gPC0ganVpY2UocmVjKQ0KZGF0YV90ZXN0IDwtIGJha2UocmVjLCB0ZXN0aW5nKGludHJhaW4pKQ0KDQpkYXRhX3RyYWluDQpgYGANCg0KIyMjIENyZWF0ZSBGaXRuZXNzIEZ1bmN0aW9uDQoNCkthbWkgYWthbiBtZWxhdGloIG1vZGVsIG1lbmdndW5ha2FuIGBodXRhbiBhY2FrYC4gS2FtaSBha2FuIG1lbmdvcHRpbWFsa2FuIG5pbGFpIGBtdHJ5YCAoanVtbGFoIG5vZGUgZGkgc2V0aWFwIHBvaG9uKSBkYW4gbmlsYWkgYG1pbl9uYCAoanVtbGFoIG1pbmltdW0gYW5nZ290YSBkaSBzZXRpYXAgcG9ob24gdW50dWsgZGlwZWNhaCkgZGVuZ2FuIG1lbWFrc2ltYWxrYW4gYWt1cmFzaSBtb2RlbCBwYWRhIGt1bXB1bGFuIGRhdGEgcGVuZ3VqaWFuLiBLYXJlbmEganVtbGFoIGBtdHJ5YCBkYW4gYG1pbl9uYCBhZGFsYWggYmlsYW5nYW4gYnVsYXQsIGxlYmloIGJhaWsga2l0YSBtZW5nZ3VuYWthbiBlbmNvZGluZyBiaW5lciBkYXJpcGFkYSBlbmNvZGluZyByZWFsLXZhbHVlZC4NCg0KS2FtaSBha2FuIG1lbmdhdHVyIHJlbnRhbmcgYG10cnlgIGRhcmkgMSBoaW5nZ2EgMzIuIEthbWkgYWthbiBtZW1iYXRhc2kgcmVudGFuZyBgbWluX25gIGRhcmkgMSBoaW5nZ2EgMTI4LiBNYXJpIGtpdGEgcGVyaWtzYSBiZXJhcGEgYml0IHlhbmcga2l0YSBwZXJsdWthbiB1bnR1ayBtZW51dHVwaSBhbmdrYS1hbmdrYSBpdHUuDQoNCmBgYHtyfQ0KIyBtYXggdmFsdWUgb2YgbXRyeSA9IDMyDQoyXjUNCg0KIyBtYXggdmFsdWUgb2YgbWluX24gPSAxMjgNCjJeNw0KYGBgDQoNCkphZGksIGtpdGEgbWVtYnV0dWhrYW4gc2V0aWRha255YSAxMiBiaXQgYmluZXIuIEppa2EgbmlsYWkga29udmVyc2kgYml0IHVudHVrIGBtdHJ5YCBhdGF1IGBtaW5fbmAgYWRhbGFoIDAsIGthbWkgbWVuZ3ViYWhueWEgbWVuamFkaSAxLg0KDQpLYW1pIGFrYW4gbWVudWxpcyBtb2RlbCBzZWJhZ2FpIGZ1bmdzaSBrZWJ1Z2FyYW4uIEthbWkgYWthbiBtZW1ha3NpbWFsa2FuIGFrdXJhc2kgbW9kZWwga2FtaSBwYWRhIGRhdGFzZXQgcGVuZ3VqaWFuLg0KDQoNCmBgYHtyfQ0KZml0X2Z1bmN0aW9uIDwtIGZ1bmN0aW9uKHgpew0KICBhIDwtIGJpbmFyeTJkZWNpbWFsKHhbMTo1XSkNCiAgYiA8LSBiaW5hcnkyZGVjaW1hbCh4WzY6MTJdKQ0KICANCiAgaWYgKGEgPT0gMCkgew0KICAgIGEgPC0gMQ0KICB9DQogIGlmIChiID09IDApIHsNCiAgICBiIDwtIDENCiAgfQ0KICBpZiAoYSA+IDE3KSB7DQogICAgYSA8LSAxNw0KICB9DQogIA0KI2RlZmluZSBtb2RlbCBzcGVjDQptb2RlbF9zcGVjIDwtIHJhbmRfZm9yZXN0KA0KICBtb2RlID0gImNsYXNzaWZpY2F0aW9uIiwNCiAgbXRyeSA9IGEsDQogIG1pbl9uID0gYiwNCiAgdHJlZXMgPSA1MDApDQoNCiNkZWZpbmUgbW9kZWwgZW5naW5lDQptb2RlbF9zcGVjIDwtIHNldF9lbmdpbmUobW9kZWxfc3BlYywNCiAgICAgICAgICAgICAgICAgICAgICAgICBlbmdpbmUgPSAicmFuZ2VyIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICBzZWVkID0gMTIzLA0KICAgICAgICAgICAgICAgICAgICAgICAgIG51bS50aHJlYWRzID0gcGFyYWxsZWw6OmRldGVjdENvcmVzKCksDQogICAgICAgICAgICAgICAgICAgICAgICAgaW1wb3J0YW5jZSA9ICJpbXB1cml0eSIpDQoNCiNtb2RlbCBmaXR0aW5nDQpzZXQuc2VlZCgxMjMpDQptb2RlbCA8LSBmaXRfeHkoDQogIG9iamVjdCA9IG1vZGVsX3NwZWMsDQogIHggPSBzZWxlY3QoZGF0YV90cmFpbiwgLWF0dHJpdGlvbiksDQogIHkgPSBzZWxlY3QoZGF0YV90cmFpbiwgYXR0cml0aW9uKQ0KKQ0KDQpwcmVkX3Rlc3QgPC0gcHJlZGljdChtb2RlbCwgbmV3X2RhdGEgPSBkYXRhX3Rlc3QgJT4lIHNlbGVjdCgtYXR0cml0aW9uKSkNCmFjYyA8LSBhY2N1cmFjeV92ZWModHJ1dGggPSBkYXRhX3Rlc3QkYXR0cml0aW9uLCBlc3RpbWF0ZSA9IHByZWRfdGVzdCQucHJlZF9jbGFzcykNCnJldHVybihhY2MpDQp9DQpgYGANCg0KIyMjIFJ1biB0aGUgQWxnb3JpdGhtDQoNCioqSU1QT1JUQU5UKioNClxuDQpcbg0KS2FyZW5hIGJlYmVyYXBhIG1vZGVsIG1lbWVybHVrYW4gYmFueWFrIHdha3R1IHVudHVrIGRpbGF0aWgsIEFuZGEgbXVuZ2tpbiBwZXJsdSBla3N0cmEgaGF0aS1oYXRpIGRhbGFtIG1lbWlsaWggdWt1cmFuIHBvcHVsYXNpIGRhbiBqdW1sYWggaXRlcmFzaS4gSmlrYSBBbmRhIHRpZGFrIG1lbWlsaWtpIG1hc2FsYWggdW50dWsgbWVuamFsYW5rYW4gR0EgZGFsYW0gamFuZ2thIHdha3R1IHlhbmcgbGFtYSwgQW5kYSBkYXBhdCBtZW1pbGloIHVrdXJhbiBwb3B1bGFzaSBkYW4ganVtbGFoIGl0ZXJhc2kgeWFuZyBsZWJpaCBiZXNhci4gRGkgc2luaSBzYXlhIGFrYW4gbWVuZ2F0dXIgdWt1cmFuIHBvcHVsYXNpIG1lbmphZGkgaGFueWEgMTAwIGRhbiBqdW1sYWggbWFrc2ltdW0gaXRlcmFzaSBhZGFsYWggMTAwLiBBbmRhIGRhcGF0IG1lbmdndW5ha2FuIGBwYXJhbGxlbCA9IFRSVUVgIGppa2EgQW5kYSBpbmdpbiBrb21wdXRhc2kgeWFuZyBsZWJpaCBjZXBhdCBkZW5nYW4ga29tcHV0YXNpIHBhcmFsZWwuDQoNCkthbWkgYWthbiBtZW5jb2JhIG1lbmdndW5ha2FuIHBlbWlsaWhhbiB0dXJuYW1lbiBhbGloLWFsaWggcGVtaWxpaGFuIHBlcmluZ2thdCBsaW5pZXIgZGVmYXVsdC4NCg0KYGBge3J9DQp0aWMoKQ0KZ2FfcmYgPC0gZ2EodHlwZSA9ICJiaW5hcnkiLCBmaXRuZXNzID0gZml0X2Z1bmN0aW9uLCBuQml0cyA9IDEyLCBzZWVkID0gMTIzLCANCiAgICAgICAgICAgIHBvcFNpemUgPSAxMDAsIG1heGl0ZXIgPSAxMDAsIHJ1biA9IDEwLCBwYXJhbGxlbCA9IFQsIA0KICAgICAgICAgICAgc2VsZWN0aW9uID0gZ2FiaW5fdG91clNlbGVjdGlvbikNCnN1bW1hcnkoZ2FfcmYpDQp0b2MoKQ0KYGBgDQoNCk1hcmkga2l0YSBwbG90IHBlcmtlbWJhbmdhbiBHQQ0KDQpgYGB7cn0NCnBsb3QoZ2FfcmYpDQpgYGANCk1hcmkga2l0YSBwZXJpa3NhIHNldGlhcCB2YXJpYWJlbCBrZXB1dHVzYW4gZGFuIGFrdXJhc2kgeWFuZyBkaWd1bmFrYW4gc2ViYWdhaSBuaWxhaSBmaXRuZXNzLg0KDQpgYGB7ciB3YXJuaW5nPUZBTFNFLCBtZXNzYWdlPUZBTFNFfQ0KIyBUcmFuc2Zvcm0gZGVjaXNpb24gdmFyaWFibGVzIGludG8gZGF0YSBmcmFtZQ0KZ2FfcmZfcG9wIDwtIGFzLmRhdGEuZnJhbWUoZ2FfcmZAcG9wdWxhdGlvbikNCg0KIyBDb252ZXJ0IGRlY2lzaW9uIHZhcmlhYmxlcyBpbnRvIG10cnkgYW5kIG1pbl9uDQpnYV9tdHJ5IDwtIGFwcGx5KGdhX3JmX3BvcFsgLCAxOjVdLCAxLCBiaW5hcnkyZGVjaW1hbCkNCmdhX21pbl9uIDwtIGFwcGx5KGdhX3JmX3BvcFsgLCA2OjEyXSwgMSwgYmluYXJ5MmRlY2ltYWwpDQoNCiMgU2hvdyB0aGUgbGlzdCBvZiBzb2x1dGlvbnMgaW4gdGhlIGZpbmFsIHBvcHVsYXRpb24NCmRhdGEuZnJhbWUobXRyeSA9IGdhX210cnksDQogICAgICAgICAgIG1pbl9uID0gZ2FfbWluX24sDQogICAgICAgICAgIGFjY3VyYWN5ID0gZ2FfcmZAZml0bmVzcykgJT4lIA0KICBtdXRhdGUobXRyeSA9IGlmZWxzZShtdHJ5ID09IDAsIDEsIG10cnkpLA0KICAgICAgICAgbWluX24gPSBpZmVsc2UobWluX24gPT0gMCwgMSwgbWluX24pLA0KICAgICAgICAgbXRyeSA9IGlmZWxzZShtdHJ5ID4gMTcsIDE3LCBtdHJ5KSkgJT4lIA0KICBkaXN0aW5jdCgpICU+JQ0KICBhcnJhbmdlKGRlc2MoYWNjdXJhY3kpKQ0KYGBgDQoNCiMjIyBDb21wYXJlIHdpdGggSy1mb2xkIENyb3NzIFZhbGlkYXRpb24NCg0KQW5kYSBtdW5na2luIGluZ2luIG1lbWJhbmRpbmdrYW4gd2FrdHUga29tcHV0YXNpIEdBIGRlbmdhbiB3YWt0dSB5YW5nIGRpcGVybHVrYW4gdW50dWsgdmFsaWRhc2kgc2lsYW5nIEstZm9sZCBiZXJ1bGFuZyBtZW5nZ3VuYWthbiBwYWtldCBgY2FyZXRgIGtvbnZlbnNpb25hbC4gS2FtaSBha2FuIG1lbmdndW5ha2FuIGsgPSA1IGRhbiAxMCBwZW5ndWxhbmdhbi4NCg0KYGBge3J9DQp0aWMoKQ0Kc2V0LnNlZWQoMTIzKQ0KY3RybCA8LSB0cmFpbkNvbnRyb2wobWV0aG9kPSJyZXBlYXRlZGN2IiwgbnVtYmVyPTUsIHJlcGVhdHM9MTApDQphdHRyaXRpb25fZm9yZXN0IDwtIHRyYWluKGF0dHJpdGlvbiB+IC4sIGRhdGE9ZGF0YV90cmFpbiwgbWV0aG9kPSJyZiIsIHRyQ29udHJvbCA9IGN0cmwpDQp0b2MoKQ0KDQphdHRyaXRpb25fZm9yZXN0DQpgYGANCg0KTWFyaSBraXRhIHBlcmlrc2Ega2Vha3VyYXRhbiBtb2RlbCBodXRhbiBhY2FrIGRlbmdhbiBgY2FyZXRgDQoNCmBgYHtyfQ0KcHJlZF9yZiA8LSBwcmVkaWN0KGF0dHJpdGlvbl9mb3Jlc3QsIG5ld2RhdGEgPSBkYXRhX3Rlc3QpDQphY2N1cmFjeV92ZWModHJ1dGggPSBkYXRhX3Rlc3QkYXR0cml0aW9uLCBlc3RpbWF0ZSA9IHByZWRfcmYpDQpgYGANCg0KUGVyZm9ybWEgbW9kZWwgb2xlaCBHQSBsZWJpaCBiYWlrIGRhcmlwYWRhIHlhbmcgZGlvcHRpbWFsa2FuIGRlbmdhbiB2YWxpZGFzaSBzaWxhbmcgSy1mb2xkIHNlZGVyaGFuYSwgbWVza2lwdW4gR0EgbWVtZXJsdWthbiBsZWJpaCBiYW55YWsgd2FrdHUgdW50dWsgbWVuZ2hpdHVuZy4gUGVydHVrYXJhbiB3YWt0dSBrb21wdXRhc2kgdnMga2luZXJqYSBpbmkgaGFydXMgZGlwZXJ0aW1iYW5na2FuLg0KDQojIENvbmNsdXNpb24NCg0KTWFzYWxhaCBvcHRpbWFzaSBhZGFsYWggc2FsYWggc2F0dSBkYXJpIGJhbnlhayBwZXJoYXRpYW4gZGFsYW0gYmlzbmlzLCB5YW5nIG11bmdraW4gYmVydXNhaGEgdW50dWsgbWVtYWtzaW1hbGthbiBrZXVudHVuZ2FuIGRhbiBtZW1pbmltYWxrYW4ga2VydWdpYW4uIFBlbWJlbGFqYXJhbiBtZXNpbiBtZW5lcmFwa2FuIHBlbmdvcHRpbWFsYW4gcGFkYSBtZXRvZGUgbW9kZWwgcGVsYXRpaGFuIG1lcmVrYSB1bnR1ayBtZW5kYXBhdGthbiBoYXNpbCB0ZXJiYWlrLg0KR0EgbWVydXBha2FuIGFsZ29yaXRtYSBvcHRpbWFzaSB5YW5nIGRhcGF0IGRpdGVyYXBrYW4gZGkgYmVyYmFnYWkgYmlkYW5nLCB0ZXJtYXN1ayBkYXRhIHNjaWVuY2UuIEppa2Egc2VvcmFuZyBpbG11d2FuIGRhdGEgZGFwYXQgbWVtYW5mYWF0a2FuIG1hbmZhYXQgR0EsIGlhIGJpc2EgbWVuZGFwYXRrYW4gbW9kZWwgeWFuZyBsZWJpaCBvcHRpbWFsIGRlbmdhbiBraW5lcmphIHlhbmcgbGViaWggYmFpay4gS2VsZW1haGFuIEdBIGFkYWxhaCBtZW1idXR1aGthbiB3YWt0dSB1bnR1ayBtZW5naGl0dW5nIGthcmVuYSB0ZXJtYXN1ayBkYWxhbSBrZWxvbXBvayBhbGdvcml0bWEgcGVuY2FyaWFuLCBkaSBtYW5hIGlhIG1lbmdla3NwbG9yYXNpIHNvbHVzaSB5YW5nIG11bmdraW4gc2VjYXJhIGVmaXNpZW4gZGliYW5kaW5na2FuIGRlbmdhbiBwZW5jYXJpYW4gYWNhay4NCg0KIyBXaGF0J3MgTmV4dA0KDQpQZW5lbGl0aWFuIGRhbGFtIGFsZ29yaXRtYSBldm9sdXNpb25lciBtYXNpaCBiZXJrZW1iYW5nIGRhbGFtIGJlYmVyYXBhIHRhaHVuIHRlcmFraGlyLCBiZWJlcmFwYSBtZW1iYWhhcyBza2VtYSBjcm9zc292ZXIgZGFuIG11dGFzaSB0ZXJiYWlrLCBzZW1lbnRhcmEgeWFuZyBsYWluIG1lbmluZ2thdGthbiBtZXRvZGUuIEdBIGp1Z2EgZGFwYXQgZGl0ZXJhcGthbiB1bnR1ayBvcHRpbWFzaSBtdWx0aS10dWp1YW4sIGRpIG1hbmEgZHVhIGF0YXUgbGViaWggdHVqdWFuIHlhbmcgc2FsaW5nIGJlcnRlbnRhbmdhbiBwZXJsdSBkaW9wdGltYWxrYW4uIEJlYmVyYXBhIG1ldG9kZSBzZXBlcnRpIE5TR0EtSUkgYXRhdSBTUEVBMiBtZXJ1cGFrYW4gbWV0b2RlIEdBIHBvcHVsZXIgeWFuZyBkYXBhdCBkaWd1bmFrYW4gdW50dWsgbWVsYWt1a2FuIG9wdGltYXNpIG11bHRpLW9iamVrdGlmLiBCYW55YWsgYWxnb3JpdG1hIGV2b2x1c2lvbmVyIG11bHRpLXR1anVhbiBhdGF1IGJhaGthbiBiYW55YWstdHVqdWFuIGJhcnUgKHlhbmcgbWVtaWxpa2kgbGViaWggZGFyaSAzIHR1anVhbikgZGl0dXJ1bmthbiBkYXJpIGR1YSBtZXRvZGUgaW5pW144XS4gU2FsYWggc2F0dSBhcGxpa2FzaSB5YW5nIHBhbGluZyBtZW5hcmlrIGFkYWxhaCBkaSBzZWt0b3IgbWFudWZha3R1ciwgdGVydXRhbWEgdW50dWsgbWFzYWxhaCBwZW5qYWR3YWxhbiwgZGkgbWFuYSBtYXNhbGFoIG9wdGltYXNpIGtvbWJpbmF0b3JpYWwgeWFuZyBrb21wbGVrcyBwZXJsdSBkaXBlY2Foa2FuIGthcmVuYSBtZW1pbGlraSBkYW1wYWsgeWFuZyBiZXNhciB0ZXJoYWRhcCBwcm9kdWt0aXZpdGFzIGRhbiBiaWF5YVteOV0uDQoNCiMgUmVmZXJlbmNlDQoNClteMV06IFtUcmVpYmVyLCBOLiBBLiwgYW5kIE8uIEtyYW1lci4gMjAxNS4gRXZvbHV0aW9uYXJ5IGZlYXR1cmUgd2VpZ2h0aW5nIGZvciB3aW5kIHBvd2VyIHByZWRpY3Rpb24gd2l0aCBuZWFyZXN0IG5laWdoYm9yIHJlZ3Jlc3Npb24uICpJRUVFIENvbmdyZXNzIG9uIEV2b2x1dGlvbmFyeSBDb21wdXRhdGlvbiAoQ0VDKSosIDMzMi0zMzcuXShodHRwczovL2llZWV4cGxvcmUuaWVlZS5vcmcvYWJzdHJhY3QvZG9jdW1lbnQvNzI1NjkxMCkNClteMl06IFtPZWhtY2tlLCBTLiwgSGVpbmVybWFubiwgSi4sIGFuZCBLcmFtZXIsIE8uIDIwMTUuIEFuYWx5c2lzIG9mIGRpdmVyc2l0eSBtZXRob2RzIGZvciBldm9sdXRpb25hcnkNCm11bHRpLW9iamVjdGl2ZSBlbnNlbWJsZSBjbGFzc2lmaWVycy4gKkFwcGxpY2F0aW9ucyBvZiBFdm9sdXRpb25hcnkgQ29tcHV0YXRpb24gKEV2b1N0YXIpKiwgNTY34oCTNTc4Ll0oaHR0cHM6Ly93d3cucmVzZWFyY2hnYXRlLm5ldC9wcm9maWxlL1N0ZWZhbl9PZWhtY2tlL3B1YmxpY2F0aW9uLzI3NTA0MTAxOF9BbmFseXNpc19vZl9EaXZlcnNpdHlfTWV0aG9kc19mb3JfRXZvbHV0aW9uYXJ5X011bHRpLU9iamVjdGl2ZV9FbnNlbWJsZV9DbGFzc2lmaWVycy9saW5rcy81NTMxMGIzMjBjZjI3YWNiMGRlOTI1NTAvQW5hbHlzaXMtb2YtRGl2ZXJzaXR5LU1ldGhvZHMtZm9yLUV2b2x1dGlvbmFyeS1NdWx0aS1PYmplY3RpdmUtRW5zZW1ibGUtQ2xhc3NpZmllcnMucGRmKQ0KW14zXTogW05lYXJjaG91LCBBLiBDLiAyMDA0LiBUaGUgRWZmZWN0IG9mIFZhcmlvdXMgT3BlcmF0b3JzIG9uIFRoZSBHZW5ldGljIFNlYXJjaCBmb3IgTGFyZ2UgU2NoZWR1bGluZyBQcm9ibGVtcy4gKkludGVybmF0aW9uYWwgSm91cm5hbCBvZiBwcm9kdWN0aW9ucyBFY29ub21pY3MqLCA4OCwgMTkxLTIwMy5dKGh0dHBzOi8vd3d3LnNjaWVuY2VkaXJlY3QuY29tL3NjaWVuY2UvYXJ0aWNsZS9hYnMvcGlpL1MwOTI1NTI3MzAzMDAxODQxKQ0KW140XTogW0tyYW1lciwgT2xpdmVyLiAyMDE3LiAqR2VuZXRpYyBBbGdvcml0aG0gRXNzZW50aWFscyouIEdld2VyYmVzdHJhc3NlOiBTcHJpbmdlci5dKGh0dHBzOi8vd3d3LnNwcmluZ2VyLmNvbS9ncC9ib29rLzk3ODMzMTk1MjE1NTgpDQpbXjVdOiBbU2NydWNjYSwgTHVjYS4gQSBxdWljayB0b3VyIG9mIEdBXShodHRwOi8vbHVjYS1zY3IuZ2l0aHViLmlvL0dBL2FydGljbGVzL0dBLmh0bWwpDQpbXjZdOiBbU2NydWNjYSwgTHVjYS4gMjAxMy4gR0E6IEEgUGFja2FnZSBmb3IgR2VuZXRpYyBBbGdvcml0aG1zIGluIFIuICpKb3VybmFsIG9mIFN0YXRpc3RpY2FsIFNvZnR3YXJlKiwgMyg0KS5dKGh0dHBzOi8vd3d3LmpzdGF0c29mdC5vcmcvYXJ0aWNsZS92aWV3L3YwNTNpMDQvdjUzaTA0LnBkZikNClteN106IFtHQSBQYWNrYWdlOiBBIEZ1bmN0aW9uIEZvciBTZXR0aW5nIE9yIFJldHJpZXZpbmcgRGVmYXVsdHMgR2VuZXRpYyBPcGVyYXRvcnNdKGh0dHBzOi8vd3d3LnJkb2N1bWVudGF0aW9uLm9yZy9wYWNrYWdlcy9HQS92ZXJzaW9ucy8zLjIvdG9waWNzL2dhQ29udHJvbCkNClteOF06IFtDaGFuZCwgU2hlbHZpbiBhbmQgTWFya3VzIFdhZ25lci4gMjAxNS4gRXZvbHV0aW9uYXJ5IG1hbnktb2JqZWN0aXZlIG9wdGltaXphdGlvbjogQSBxdWljay1zdGFydCBndWlkZS4gKlN1cnZleXMgaW4gT3BlcmF0aW9ucyBSZXNlYXJjaCBhbmQgTWFuYWdlbWVudCBTY2llbmNlKiwgMjAoMiksIDM1LTQyLl0oaHR0cHM6Ly93d3cuc2NpZW5jZWRpcmVjdC5jb20vc2NpZW5jZS9hcnRpY2xlL3BpaS9TMTg3NjczNTQxNTAwMDE0OCkNClteOV06IFtHZW4sIE0uLCAmIExpbiwgTC4gMjAxMy4gTXVsdGlvYmplY3RpdmUgZXZvbHV0aW9uYXJ5IGFsZ29yaXRobSBmb3IgbWFudWZhY3R1cmluZyBzY2hlZHVsaW5nIHByb2JsZW1zOiBzdGF0ZS1vZi10aGUtYXJ0IHN1cnZleS4gKkpvdXJuYWwgb2YgSW50ZWxsaWdlbnQgTWFudWZhY3R1cmluZyosIDI1KDUpLCA4NDnigJM4NjYuXShodHRwczovL2x2ay5jcy5tc3Uuc3UvfnZiYWJlcm5vdi8yMDE5LW8vbTEvMjAxM19MSU5fTXVsdGlvYmplY3RpdmUlMjBldm9sdXRpb25hcnklMjBhbGdvcml0aG0lMjBmb3IlMjBtYW51ZmFjdHVyaW5nLnBkZikNCg==