Introduction
Tasks:建立任务,包括任务描述(classification, regression, clustering, etc)和数据集
Learners:建立学习者,指定机器学习算法(GLM, SVM, xgboost, etc)和参数
Hyperparameters:对Learners直接指定超参数或超参数调优,给Learners一个可能的参数集列表
Wrapped Models:已封装模型,已经被训练可以预测的模型
Predictions:应用模型到新数据或者原始数据进行预测
Meastures:模型评估指标(RMSE, logloss, AUC, etc)
Resampling:重抽样,通过分离训练集来拟合通用的模型,通常包括holdout验证,K折交叉验证(K-fold cross-validation),留一交叉验证(LOOCV)
Preprocessing data
createDummyFeatures(obj=,target=,method=,cols=)
为每一个非数值变量创建哑变量(0,1),不包括目标变量target,也可以指定变量cols
normalizeFeatures(obj=,target=,method=,cols=, range=,on.constant=)
数字变量标准化,method: {“center”, “scale”,”standardize”,”range”(default range=c(0,1)) }
mergeSmallFactorLevels(task=,cols=,min.perc=)
组合进单一的factor level
summarizeColumns(obj=)
data.frame or Task的汇总说明
capLargeValues(obj,cols=NULL)
离群值转化 帽子法
dropFeatures
removeConstantFeatures
summarizeLevels
##Task and Learner
##1Create a task
makeClassifTask(data=,target=)
回归问题
makeRegrTask(data=,target=)
分类,目标列必须是一个factor
makeMultilabelTask(data=,target=)
多标签分类问题,每个对象可以同时属于多个类别
makeClusterTask(data=)
聚类分析
makeSurvTask(data=,target=c(“time”,”event”))
生存分析
makeCostSensTask(data=,costs=)
成本敏感的分类,分类中的标准目标是获得较高的预测准确度,即最小化分类器产生的错误数量
getTaskDesc(x)
获得总的任务描述,查看positiv
##2Making a learner
makeLearner(cl,predict.type = “response”,predict.threshold = NULL, …,par.vals) #指定机器学习算法,也能一次指定多个
参数:
cl:指定要使用的算法名,cl= . (“classif.xgboost”, “regr.randomForest” ,”cluster.kmeans”,etc)
predict.type:”response”返回原始数据,”prob”返回分类问题的概率,”se”返回回归问题的标准误
par.vals:传递超参数列表,也能直接传递(…)
predict.threshold :生成类标签的阈值
mlr有超过170个不同的是算法:mlr已经集成的算法
listLearners():展示所有的learner
listLearners(task):task允许的所有learner
listLearners(“classif”,properties=c(“prob”,“factors”)):展示所有的分类问题中,能输出概率”prob”,和因子“factors”
See also getLearnerProperties()
#Training & Testing 训练和测试
##1Setting hyperparameters 设置超参数
setHyperPars(learner=,…)传递超参数给learner
getParamSet(learner=)获得learner可能的超参数文本(such as “classif.qda”)
##2Train a model and predict 训练模型和预测
train(learner, task,subset) #训练模型,subset设置训练集(默认是全部数据)
getLearnerModel(model) #将底层的学习者模型纳入mlr
predict(model,task,subset) #预测原始数据
predict(model,newdata) #预测新数据
as.data.frame(pred) #查看预测的结果
pred2 <- setThreshold(pred, threshold) #重设分类问题的阈值,重新预测
##3Measuring performance 模型评估质量
performance(pred=,measures=) #依照一个或几个指标评估模型
getDefaultMeasure(task/learner) #查看默认指标
listMeasures() #查看完整的指标列表
listMeasures(task) #查看匹配指标
通过参数measures=list(mse,medse,mae) #指定评估指标
classif: acc auc bac ber brier[.scaled] f1 fdr fn fnr fp fpr gmean multiclass[.au1u .aunp .aunu.brier] npv ppv qsr ssr tn tnr tp tpr wkappa
regr: arsq expvar kendalltau mae mape medae medse mse msle rae rmse rmsle rrse rsq sae spearmanrho sse
cluster: db dunn G1 G2 silhouette
multilabel: multilabel[.f1 .subset01 .tpr .ppv .acc .hamloss]
costsens: mcp meancosts
surv: cindex
other: featperc timeboth timepredict timetrain
分类任务的评估详情:
calculateConfusionMatrix(pred) #获得混淆矩阵
calculateROCMeasures(pred) #ROC曲线
##4Resampling a learner 重抽样
makeResampleDesc(method=,…,stratify=)
makeResampleInstance(desc=,task=) #确保每次抽样统一,以减少噪声值
###method 重抽样的方法
“CV” cross-volidation(交叉验证),用iters指定k-folds(K折)数
“LOO” leave-one-out cross-volidation(留一交叉验证),用iters指定k-folds数
“RepCV” repeated cross-volidation(反复交叉验证),reps指定重复数,用iters指定k-folds数
“Subsample” aka Monte-Carlo cross-volidation ,用iters指定k-folds数,split指定train%
“Bootstrap” out-of-bag bootstrap(袋装法),iters
“Holdout” for train% use split
stratify 保持目标变量的抽样比例
resample(learner=,task=,resampling=,measures=)通过指定重抽样训练和测试模型
mlr有若干预先定义的重抽样:
cv2(2-koldscross-volidation),cv3, cv5, cv10, hout(holdout with split 2/3 for traing, 1/3 for testing) 也可以用重抽样函数传递给resample() crossval() repcv() holdout() subsample() bootstrapOOB() bootstrapB632() bootstrapB632plus()
#Tuning hyperparameters 超参数调优
##1设定超参数搜索空间
makeParamSet(makeParam())
makeParam() #指定超参数搜索类型和范围
makeNumericParam(id=,lower=,upper=,trafo=) #数字
makeIntegerParam(id=,lower=,upper=,trafo=) #整数
makeIntegerVectorParam(id=,len=,lower=,upper=,trafo=) #整数向量
makeDiscreteParam(id=,values=c(…)) #分类变量
Logical, LogicalVector, CharacterVector, DiscreteVector #其他类型
参数trafo可以用一个指定函数转换输出:例如lower=-2,upper=2,trafo=function(x) 10^x,输出值between 0.01 and 100
##2设定超参数搜索算法
makeTuneControl()
Grid(resolution=10L) #所有可能的网格点
Random(maxit=100) #随机抽样搜索空间
MBO(budget=) #Bayesion model-based optimization(基于贝叶斯模型的最优化)
Irace(n.instances=) #迭代比赛过程
CMAES()
Design()
GenSA()
##3超参数调优
tuneParams(learner=,task=,resampling=,measures=,par.set=,control=) #超参数调优函数
#Quickstart
#install.packages("mlr")
library(mlr)
#预处理数据
#install.packages("mlbench")
library(mlbench)
data(Soybean)
soy = createDummyFeatures(Soybean,target="Class") #转化因子变量为哑变量(0,1),对xgboost模型有用
tsk = makeClassifTask(data=soy,target="Class") # 建立任务
ho = makeResampleInstance("Holdout",tsk) # 设定训练集和测试集(default 2/3 for traing, 1/3 for testing
tsk.train = subsetTask(tsk,ho$train.inds[[1]])
tsk.test = subsetTask(tsk,ho$test.inds[[1]])
# 设定learner和评估指标
#install.packages("xgboost")
library(xgboost)
lrn = makeLearner("classif.xgboost",nrounds=10)
cv = makeResampleDesc("CV",iters=5) # 5折交叉验证
res = resample(lrn,tsk.train,cv,acc) # 准确率作为评估指标
#超参数调优,重训练模型
ps = makeParamSet(makeNumericParam("eta",0,1), # 调优超参数eta, lambda, max_depth
makeNumericParam("lambda",0,200),
makeIntegerParam("max_depth",1,20))
#install.packages("mlrMBO")
library(mlrMBO)
tc = makeTuneControlMBO(budget=100) # 用MBO算法搜索
#install.packages("DiceKriging")
#install.packages("rgenoud")
library(DiceKriging)
library(rgenoud)
tr = tuneParams(lrn,tsk.train,cv5,acc,ps,tc) # 5折交叉验证调优
lrn = setHyperPars(lrn,par.vals=tr$x)
mdl = train(lrn,tsk.train) #测试集训练模型
prd = predict(mdl,tsk.test) # 预测
calculateConfusionMatrix(prd) # 混淆矩阵评估模型
mdl = train(lrn,tsk) # 用所有的数据重新训练模型,投入实用
Configuration 配置
用getMlrOptions()查看mlr 的现有设置
用configureMlr()更改mlr的默认设置
show.info:(traning, tuning, resampling,etc)是否展示默认冗长的输出,默认TRUE
on.learner.error:怎样控制learner的错误, “stop”(default), “warn”, “quiet”
on.learner.warning:怎样控制learner的警告,”warn”(default) , “quiet”
on.par.without.desc:超参数控制,”stop”(default), “warn” ,”quiet”
on.par.out.of.bounds:超参数超出边界值,”stop”(default) ,”warn”, “quiet”
on.measure.not.applicable:评估指标对learner不适用,”stop”(default) , “warn”, “quiet”
show.learner.output:learner在训练的时候输出到控制台,默认TRUE
on.error.dump:是否为crashed learner创建一个error dump,当on.learner.error 没有被指定为”stop” 时,默认TRUE
#Parallelization并行
mlr结合parallelMap包利用多核和集群运算加快运行速度,mlr自动发现能进行并行的操作。
开始并行:parallelStart(mode=,cpus=,level=)
结束并行:parallelStop()
mode 决定并行的方式
“local” 无并行性,简单的使用mapply
“multicore” 单机器上多核,使用parallel::mclapply,windows上不适用
“socket” 多核socked mode
“mpi” 一个或多个机器上集群运算,使用parallel::makeCluster and parallel::clusterMap “BatchJobs” 批排队HPC集群,使用BatchJobs::batchMap
cpus 使用的物理内核数
level 控制并行,使用”mlr.benchmark” “mlr.resample” “mlr.selectFeatures” “mlr.tuneParams” “mlr.ensemble”
#Imputation 插补
impute(obj=,target=,cols=,dummy.cols=,dummy.type=)
缺失的数据进行插补,返回一个列表,包括插补过额数据集或task,和插补描述
reimpute(obj=,desc=)用已被impute创建的插补描述(description, desc)插补缺失值
obj= data.frame or task
target= 指定目标变量,将不会被插补
cols= 要插补的列名或逻辑列表
dummy.cols= 建立NA(T/F)列的列名
dummy.type= 设定”numeric”,用(0,1)代替(T/F)
也能用classes 和dummy.classes 代替cols
传递给cols或classes的list示例:
cols=list(V1=imputeMean()) V1是要插补的列
imputeMean()为要插补的方法
imputeConst(const=) 常数
imputeMedian() 中位数
imputeMode() 众数
imputeMin(multiplier=) 最小值
imputeMax(multiplier=) 最大值
imputeNormal(mean=,sd=) 正态插补
imputeHist(breaks=,use.mids=)
imputeLearner(learner=,features=) 模型插补
#Feature Extraction特征提取
##1Feature filtering特征筛选
filterFeatures(task=,method=,perc=,abs=,threshold=)
按特征的重要性进行排序,选择其中的top n percent(perc=), top n(abs=) or 设定阈值(threshold=),返回task,没有被筛选的特征将会被删除。
默认的筛选方法为“randomForestSRC.rfsrc”,也可以设置其他方法:
“anova.test” “carscore” “cforest.importance” “chi.squared” “gain.ratio” “information.gain” “kruskal.test” “linear.correlation” “mrmr” “oneR” “permutation.importance” “randomForest.importance” “randomForestSRC.rfsrc” “randomForestSRC.var.select” “rank.correlation” “relief” “symmetrical.uncertainty” “univariate.model.score” “variance”
##2Feature selection特征选择
selectFeatures(learner=,task=, resampling=,measures=,control=)
用一个特征选择算法(control)重抽样和建立模型,反复选择不同的特征集,直到找到最好的特征集。返回FeatSelResult对象,包括最佳选择和最佳路径。
tsk = subsetTask(tsk,features=fsr$x)应用最佳选择结果(fsr)到task(tsk)
特征选择算法(control):
makeFeatSelControlExhaustive(max.features=)尝试每一种特征组合,可选参数max.features
makeFeatSelControlRandom(maxit=,prob=,max.features=)随机抽取特征集(概率prob,default 0.5) 迭代(maxit,default 100),返回其中最好的。
makeFeatSelControlSequential(method=,maxit=,max.features=,alpha=,beta=)用以下的迭代算法搜寻,评估:
“sfs” forward search, “sffs” floating forward search,
“sbs” backward search , “sfbs” floating backward search,
“alpha” 每次增加一个特征来改善评估,取最少特征集
“beta” 每次移除一个特征来改善评估,取最少特征集
makeFeatSelControlGA(maxit=,max.features=,mu=,lambda=,crossover.rate=,mutation.rate=)随机特征向量的遗传算法,然后利用最佳性能的交叉来产生后代,代代相传,参数:
mu是父系规模
lambda 是子系规模
crossover.rate 从第一亲本选择一点的概率
mutation.rate 是翻转的概率(on or off)
Benchmarking (基准点)
benchmark(learners=,tasks=,resamplings=,measures=)
允许进行简单的比较:执行单一task的多重learner,执行多重task的单一learner,或者执行多重task的多重learner,返回一个基准结果对象。
基准结果被函数getBMR
n = getTaskSize(sonar.task)
train.set = sample(n, size = round(2/3 * n))
test.set = setdiff(seq_len(n), train.set)
lrn1 = makeLearner("classif.lda", predict.type = "prob")
mod1 = train(lrn1, sonar.task, subset = train.set)
pred1 = predict(mod1, task = sonar.task, subset = test.set)
df = generateThreshVsPerfData(pred1, measures = list(fpr, tpr, mmce))
performance(pred1, auc)
plotThreshVsPerf(df)
library(kernlab)
lrn2 = makeLearner("classif.ksvm", predict.type = "prob") # 需要载入包kernlab
mod2 = train(lrn2, sonar.task, subset = train.set)
pred2 = predict(mod2, task = sonar.task, subset = test.set)
df = generateThreshVsPerfData(list(lda = pred1, ksvm = pred2), measures = list(fpr, tpr))
plotROCCurves(df)
Learning curve 学习曲线
generateLearningCurveData(learners=,task=,resampling=,percs=,measures=) #获得不同百分比的task data上做模型评估质量数据
plotLearningCurve(obj=) #比较数据的已使用和模型质量的关系,使用LearningCurveData对象
r = generateLearningCurveData(
learners = c("classif.rpart", "classif.knn"),
task = sonar.task,
percs = seq(0.1, 1, by = 0.2),
measures = list(tp, fp, tn, fn),
resampling = makeResampleDesc(method = "CV", iters = 5),
show.info = FALSE)
plotLearningCurve(r)
Feature importance 特征重要性
generateFilterValuesData(task=,method=) #用指定的选择方法获得排序的特征重要性
plotFilterValues(obj=) #可视化特征重要性,FilterValuesData对象
fv = generateFilterValuesData(iris.task)
plotFilterValues(fv)
#Hyperparameters tuning 超参数调优
generateHyperParsEffectData(tune.result=) 获得不同超参数的影响
plotHyperParsEffect(hyperpars.effect.data=,x=,y=,z=) 可视化超参数影响,HyperParsEffectData对象
plotOptPath(op=) 可视化最优进程详情,$opt.path对象, 是 tuneResult或 featSelResult类的对象。
plotTuneMultiCritResult(res=) 展示pareto图,多重评估质量的调优结果
ps = makeParamSet(
makeNumericParam("C", lower = -5, upper = 5, trafo = function(x) 2^x)
)
ctrl = makeTuneControlGrid()
rdesc = makeResampleDesc("Holdout")
lrn = makeTuneWrapper("classif.ksvm", control = ctrl,
measures = list(acc, mmce), resampling = rdesc, par.set = ps, show.info = FALSE)
res = resample(lrn, task = pid.task, resampling = cv2, extract = getTuneResult, show.info = FALSE)
data = generateHyperParsEffectData(res)
plotHyperParsEffect(data, x = "C", y = "acc.test.mean", plot.type = "line")
Partial dependence 部分依赖
generatePartialDependenceData(obj=,input=) #获得模型(obj)的部分依赖预测,通过每一次的特征数据输入(input)
plotPartialDependence(obj=) #部分依赖图,PartialDependenceData对象
lrn.regr = makeLearner("regr.ksvm")
fit.regr = train(lrn.regr, bh.task)
#install.packages("mmpf")
library(mmpf)
pd.regr = generatePartialDependenceData(fit.regr, bh.task, "lstat", fun = median)
plotPartialDependence(pd.regr)
lrn.classif = makeLearner("classif.ksvm", predict.type = "prob")
fit.classif = train(lrn.classif, iris.task)
pd.classif = generatePartialDependenceData(fit.classif, iris.task, "Petal.Length", fun = median)
plotPartialDependence(pd.classif)
Benchmarking 基准点
plotBMRBoxplots(bmr=) #表现的分类图
plotBMRSummary(bmr=) #平均表现的散点图
plotBMRRanksAsBarChart(bmr=) #learner 排序柱形图
Other #其他图
generateCritDifferencesData(bmr=,measure=,p.value=,test=) #执行临界差检验,用bonforroni-dunn(“bd”) 或”Nemenyi”检验
plotCritDifferences(obj=) #临界点检验可视化
generateCalibrationData(obj=) #评估概率预测与真实发生率的校准
plotCalibration(obj=) #校准图
#Wrappers 封装器
封装器使具有附加功能的学习者融合。mlr把带有封装器的learner看作一个单独的learner,超参数的封装也会与基础模型参数联合调谐。带包装的模型将应用于新数据。
#Preprocessing and imputation 预处理和插补
makeDummyFeaturesWrapper(learner=)
makeImputeWrapper(learner=,classes=,cols=)
makePreprocWrapper(learner=,train=,predict=)
makePreprocWrapperCaret(learner=,…)
makeRemoveConstantFeaturesWrapper(learner=)
#Class imbance 类别不平衡
makeOverBaggingWrapper(learner=)
makeSMOTEWrapper(learner=)
makeUndersampleWrapper(learner=)
makeWeightedClassesWrapper(learner=)
#Cost-sensitive 成本分析
makeCostSensClassifWrapper(learner=)
makeCostSensRegrWrapper(learner=)
makeCostSensWeightedPairsWrapper(learner=)
#Multilabel classification 多标签分类
makeMultilabelBinaryRelevanceWrapper(learner=)
makeMultilabelClassifierChainsWrapper(learner=)
makeMultilabelDBRWrapper(learner=)
makeMultilabelNestedStackingWrapper(learner=)
makeMultilabelStackingWrapper(learner=)
#Other 其他
makeBaggingWrapper(learner=)
makeConstantClassWrapper(learner=)
makeDownsampleWrapper(learner=,dw.perc=)
makeFeatSelWrapper(learner=,resampling=,control=)
makeFilterWrapper(learner=,fw.perc=,fw.abs=,fw.threshold=)
makeMultiClassWrapper(learner=)
makeTuneWrapper(learner=,resampling=,par.set=,control=)
#Nested Resampling 嵌套重采样
mlr支持嵌套重采样进行复杂操作,例如通过包装来调优和特征选择等。为了获得良好的泛化性能估计和避免数据泄漏,建议使用外部(用于调优/特征选择)和内部(对于基本模型)重采样过程。
外部重采样能在resample, benchmark中被指定
内部重采样能在makeTuneWrapper, makeFeatSelWrapper, etc中被指定
#Ensembles 集成
makeStackedLearner(base.learners=,super.learner=,method=) #将多重learner集成
base.learners= #使用初始预测的learners
super.learner= #使用最终预测的learners
method= #组合base learners预测的方法
“average”:#所有base learners的平均
“stack.nocv” “stack.cv”:在base learners的结果上训练super learner (with or without cross-validation)
“hill.climb”:搜索最优权重平均
“compress”:用神经网络实现更快的性能
LS0tCnRpdGxlOiAibWxyIgpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKLS0tCgojSW50cm9kdWN0aW9uICAKVGFza3PvvJrlu7rnq4vku7vliqHvvIzljIXmi6zku7vliqHmj4/ov7AoY2xhc3NpZmljYXRpb24sIHJlZ3Jlc3Npb24sIGNsdXN0ZXJpbmcsIGV0YynlkozmlbDmja7pm4YgIApMZWFybmVyc++8muW7uueri+WtpuS5oOiAhe+8jOaMh+WumuacuuWZqOWtpuS5oOeul+azlShHTE0sIFNWTSwgeGdib29zdCwgZXRjKeWSjOWPguaVsCAgCkh5cGVycGFyYW1ldGVyc++8muWvuUxlYXJuZXJz55u05o6l5oyH5a6a6LaF5Y+C5pWw5oiW6LaF5Y+C5pWw6LCD5LyYLOe7mUxlYXJuZXJz5LiA5Liq5Y+v6IO955qE5Y+C5pWw6ZuG5YiX6KGoICAKV3JhcHBlZCBNb2RlbHPvvJrlt7LlsIHoo4XmqKHlnovvvIzlt7Lnu4/ooqvorq3nu4Plj6/ku6XpooTmtYvnmoTmqKHlnosgIApQcmVkaWN0aW9uc++8muW6lOeUqOaooeWei+WIsOaWsOaVsOaNruaIluiAheWOn+Wni+aVsOaNrui/m+ihjOmihOa1iyAgCk1lYXN0dXJlc++8muaooeWei+ivhOS8sOaMh+aghyhSTVNFLCBsb2dsb3NzLCBBVUMsIGV0YykgIApSZXNhbXBsaW5n77ya6YeN5oq95qC377yM6YCa6L+H5YiG56a76K6t57uD6ZuG5p2l5ouf5ZCI6YCa55So55qE5qih5Z6L77yM6YCa5bi45YyF5ousaG9sZG91dOmqjOivge+8jEvmipjkuqTlj4npqozor4EoSy1mb2xkIGNyb3NzLXZhbGlkYXRpb24p77yM55WZ5LiA5Lqk5Y+J6aqM6K+BKExPT0NWKSAgCgojUHJlcHJvY2Vzc2luZyBkYXRhICAKY3JlYXRlRHVtbXlGZWF0dXJlcyhvYmo9LHRhcmdldD0sbWV0aG9kPSxjb2xzPSkgIArkuLrmr4/kuIDkuKrpnZ7mlbDlgLzlj5jph4/liJvlu7rlk5Hlj5jph48oMCwxKe+8jOS4jeWMheaLrOebruagh+WPmOmHj3RhcmdldO+8jOS5n+WPr+S7peaMh+WumuWPmOmHj2NvbHMgIApub3JtYWxpemVGZWF0dXJlcyhvYmo9LHRhcmdldD0sbWV0aG9kPSxjb2xzPSwgcmFuZ2U9LG9uLmNvbnN0YW50PSkgIArmlbDlrZflj5jph4/moIflh4bljJbvvIxtZXRob2Q6IHvigJxjZW50ZXLigJ0sIOKAnHNjYWxl4oCdLOKAnXN0YW5kYXJkaXpl4oCdLOKAnXJhbmdl4oCdKGRlZmF1bHQgcmFuZ2U9YygwLDEpKSB9ICAKbWVyZ2VTbWFsbEZhY3RvckxldmVscyh0YXNrPSxjb2xzPSxtaW4ucGVyYz0pICAK57uE5ZCI6L+b5Y2V5LiA55qEZmFjdG9yIGxldmVsICAKc3VtbWFyaXplQ29sdW1ucyhvYmo9KSAgCmRhdGEuZnJhbWUgb3IgVGFza+eahOaxh+aAu+ivtOaYjiAgCmNhcExhcmdlVmFsdWVzKG9iaixjb2xzPU5VTEwpICAK56a7576k5YC86L2s5YyWIOW4veWtkOazlSAgCmRyb3BGZWF0dXJlcyAgCnJlbW92ZUNvbnN0YW50RmVhdHVyZXMgIApzdW1tYXJpemVMZXZlbHMgIAojI1Rhc2sgYW5kIExlYXJuZXIgIAojIzFDcmVhdGUgYSB0YXNrICAKbWFrZUNsYXNzaWZUYXNrKGRhdGE9LHRhcmdldD0pICAK5Zue5b2S6Zeu6aKYICAKbWFrZVJlZ3JUYXNrKGRhdGE9LHRhcmdldD0pICAK5YiG57G777yM55uu5qCH5YiX5b+F6aG75piv5LiA5LiqZmFjdG9yICAKbWFrZU11bHRpbGFiZWxUYXNrKGRhdGE9LHRhcmdldD0pICAK5aSa5qCH562+5YiG57G76Zeu6aKY77yM5q+P5Liq5a+56LGh5Y+v5Lul5ZCM5pe25bGe5LqO5aSa5Liq57G75YirICAKbWFrZUNsdXN0ZXJUYXNrKGRhdGE9KSAgCuiBmuexu+WIhuaekCAgCm1ha2VTdXJ2VGFzayhkYXRhPSx0YXJnZXQ9YyjigJx0aW1l4oCdLOKAnWV2ZW504oCdKSkgIArnlJ/lrZjliIbmnpAgIAptYWtlQ29zdFNlbnNUYXNrKGRhdGE9LGNvc3RzPSkgIArmiJDmnKzmlY/mhJ/nmoTliIbnsbvvvIzliIbnsbvkuK3nmoTmoIflh4bnm67moIfmmK/ojrflvpfovoPpq5jnmoTpooTmtYvlh4bnoa7luqbvvIzljbPmnIDlsI/ljJbliIbnsbvlmajkuqfnlJ/nmoTplJnor6/mlbDph48gIApnZXRUYXNrRGVzYyh4KSAgCuiOt+W+l+aAu+eahOS7u+WKoeaPj+i/sO+8jOafpeeci3Bvc2l0aXYgIAojIzJNYWtpbmcgYSBsZWFybmVyICAKbWFrZUxlYXJuZXIoY2wscHJlZGljdC50eXBlID0gInJlc3BvbnNlIixwcmVkaWN0LnRocmVzaG9sZCA9IE5VTEwsIC4uLixwYXIudmFscykgICAj5oyH5a6a5py65Zmo5a2m5Lmg566X5rOV77yM5Lmf6IO95LiA5qyh5oyH5a6a5aSa5LiqICAK5Y+C5pWw77yaICAKY2zvvJrmjIflrpropoHkvb/nlKjnmoTnrpfms5XlkI3vvIxjbD0gPHRhc2tfVHlwZT4uPFJfbWV0aG9kX25hbWU+ICjigJxjbGFzc2lmLnhnYm9vc3TigJ0sIOKAnHJlZ3IucmFuZG9tRm9yZXN04oCdICzigJ1jbHVzdGVyLmttZWFuc+KAnSxldGMpICAKcHJlZGljdC50eXBl77ya4oCdcmVzcG9uc2XigJ3ov5Tlm57ljp/lp4vmlbDmja7vvIzigJ1wcm9i4oCd6L+U5Zue5YiG57G76Zeu6aKY55qE5qaC546H77yM4oCdc2XigJ3ov5Tlm57lm57lvZLpl67popjnmoTmoIflh4bor68gIApwYXIudmFsc++8muS8oOmAkui2heWPguaVsOWIl+ihqO+8jOS5n+iDveebtOaOpeS8oOmAkijigKYpICAKcHJlZGljdC50aHJlc2hvbGQg77ya55Sf5oiQ57G75qCH562+55qE6ZiI5YC8ICAKbWxy5pyJ6LaF6L+HMTcw5Liq5LiN5ZCM55qE5piv566X5rOV77yabWxy5bey57uP6ZuG5oiQ55qE566X5rOVICAKbGlzdExlYXJuZXJzKCnvvJrlsZXnpLrmiYDmnInnmoRsZWFybmVyICAKbGlzdExlYXJuZXJzKHRhc2sp77yadGFza+WFgeiuuOeahOaJgOaciWxlYXJuZXIgIApsaXN0TGVhcm5lcnMo4oCcY2xhc3NpZuKAnSxwcm9wZXJ0aWVzPWMo4oCccHJvYuKAnSzigJxmYWN0b3Jz4oCdKSnvvJrlsZXnpLrmiYDmnInnmoTliIbnsbvpl67popjkuK3vvIzog73ovpPlh7rmpoLnjofigJ1wcm9i4oCd77yM5ZKM5Zug5a2Q4oCcZmFjdG9yc+KAnSAgClNlZSBhbHNvIGdldExlYXJuZXJQcm9wZXJ0aWVzKCkgIAojVHJhaW5pbmcgJiBUZXN0aW5nIOiuree7g+WSjOa1i+ivlSAgCiMjMVNldHRpbmcgaHlwZXJwYXJhbWV0ZXJzIOiuvue9rui2heWPguaVsCAgCnNldEh5cGVyUGFycyhsZWFybmVyPSwuLi4p5Lyg6YCS6LaF5Y+C5pWw57uZbGVhcm5lciAgCmdldFBhcmFtU2V0KGxlYXJuZXI9KeiOt+W+l2xlYXJuZXLlj6/og73nmoTotoXlj4LmlbDmlofmnKwoc3VjaCBhcyDigJxjbGFzc2lmLnFkYeKAnSkgIAojIzJUcmFpbiBhIG1vZGVsIGFuZCBwcmVkaWN0IOiuree7g+aooeWei+WSjOmihOa1iyAgCnRyYWluKGxlYXJuZXIsIHRhc2ssc3Vic2V0KSAj6K6t57uD5qih5Z6L77yMc3Vic2V06K6+572u6K6t57uD6ZuG77yI6buY6K6k5piv5YWo6YOo5pWw5o2u77yJICAKZ2V0TGVhcm5lck1vZGVsKG1vZGVsKSAj5bCG5bqV5bGC55qE5a2m5Lmg6ICF5qih5Z6L57qz5YWlbWxyICAKcHJlZGljdChtb2RlbCx0YXNrLHN1YnNldCkgI+mihOa1i+WOn+Wni+aVsOaNriAgCnByZWRpY3QobW9kZWwsbmV3ZGF0YSkgI+mihOa1i+aWsOaVsOaNriAgCmFzLmRhdGEuZnJhbWUocHJlZCkgI+afpeeci+mihOa1i+eahOe7k+aenCAgCnByZWQyIDwtIHNldFRocmVzaG9sZChwcmVkLCB0aHJlc2hvbGQpICPph43orr7liIbnsbvpl67popjnmoTpmIjlgLzvvIzph43mlrDpooTmtYsgIAojIzNNZWFzdXJpbmcgcGVyZm9ybWFuY2Ug5qih5Z6L6K+E5Lyw6LSo6YePICAKcGVyZm9ybWFuY2UocHJlZD0sbWVhc3VyZXM9KSAj5L6d54Wn5LiA5Liq5oiW5Yeg5Liq5oyH5qCH6K+E5Lyw5qih5Z6LICAKZ2V0RGVmYXVsdE1lYXN1cmUodGFzay9sZWFybmVyKSAj5p+l55yL6buY6K6k5oyH5qCHICAKbGlzdE1lYXN1cmVzKCkgI+afpeeci+WujOaVtOeahOaMh+agh+WIl+ihqCAgCmxpc3RNZWFzdXJlcyh0YXNrKSAj5p+l55yL5Yy56YWN5oyH5qCHICAK6YCa6L+H5Y+C5pWwbWVhc3VyZXM9bGlzdChtc2UsbWVkc2UsbWFlKSAj5oyH5a6a6K+E5Lyw5oyH5qCHICAKY2xhc3NpZjogYWNjIGF1YyBiYWMgYmVyIGJyaWVyWy5zY2FsZWRdIGYxIGZkciBmbiBmbnIgZnAgZnByIGdtZWFuIG11bHRpY2xhc3NbLmF1MXUgLmF1bnAgLmF1bnUuYnJpZXJdIG5wdiBwcHYgcXNyIHNzciB0biB0bnIgdHAgdHByIHdrYXBwYSAgCnJlZ3I6IGFyc3EgZXhwdmFyIGtlbmRhbGx0YXUgbWFlIG1hcGUgbWVkYWUgbWVkc2UgbXNlIG1zbGUgcmFlIHJtc2Ugcm1zbGUgcnJzZSByc3Egc2FlIHNwZWFybWFucmhvIHNzZSAgCmNsdXN0ZXI6IGRiIGR1bm4gRzEgRzIgc2lsaG91ZXR0ZSAgCm11bHRpbGFiZWw6IG11bHRpbGFiZWxbLmYxIC5zdWJzZXQwMSAudHByIC5wcHYgLmFjYyAuaGFtbG9zc10gIApjb3N0c2VuczogbWNwIG1lYW5jb3N0cyAgCnN1cnY6IGNpbmRleCAgCm90aGVyOiBmZWF0cGVyYyB0aW1lYm90aCB0aW1lcHJlZGljdCB0aW1ldHJhaW4gIArliIbnsbvku7vliqHnmoTor4TkvLDor6bmg4XvvJogIApjYWxjdWxhdGVDb25mdXNpb25NYXRyaXgocHJlZCkgI+iOt+W+l+a3t+a3huefqemYtSAgCmNhbGN1bGF0ZVJPQ01lYXN1cmVzKHByZWQpICNST0Pmm7Lnur8gIAojIzRSZXNhbXBsaW5nIGEgbGVhcm5lciDph43mir3moLcgIAptYWtlUmVzYW1wbGVEZXNjKG1ldGhvZD0sLi4uLHN0cmF0aWZ5PSkgIAptYWtlUmVzYW1wbGVJbnN0YW5jZShkZXNjPSx0YXNrPSkgI+ehruS/neavj+asoeaKveagt+e7n+S4gO+8jOS7peWHj+WwkeWZquWjsOWAvCAgCiMjI21ldGhvZAnph43mir3moLfnmoTmlrnms5UgIArigJxDVuKAnQljcm9zcy12b2xpZGF0aW9u77yI5Lqk5Y+J6aqM6K+B77yJ77yM55SoaXRlcnPmjIflrpprLWZvbGRz77yIS+aKmO+8ieaVsCAgCuKAnExPT+KAnQlsZWF2ZS1vbmUtb3V0IGNyb3NzLXZvbGlkYXRpb27vvIjnlZnkuIDkuqTlj4npqozor4HvvInvvIznlKhpdGVyc+aMh+WummstZm9sZHPmlbAgIArigJxSZXBDVuKAnQlyZXBlYXRlZCBjcm9zcy12b2xpZGF0aW9u77yI5Y+N5aSN5Lqk5Y+J6aqM6K+B77yJ77yMcmVwc+aMh+WumumHjeWkjeaVsO+8jOeUqGl0ZXJz5oyH5a6aay1mb2xkc+aVsCAgCuKAnFN1YnNhbXBsZeKAnQlha2EgTW9udGUtQ2FybG8gY3Jvc3Mtdm9saWRhdGlvbiAs55SoaXRlcnPmjIflrpprLWZvbGRz5pWwLHNwbGl05oyH5a6adHJhaW4lICAK4oCcQm9vdHN0cmFw4oCdCW91dC1vZi1iYWcgYm9vdHN0cmFw77yI6KKL6KOF5rOV77yJ77yMaXRlcnMgIArigJxIb2xkb3V04oCdCWZvciB0cmFpbiUgdXNlIHNwbGl0ICAKc3RyYXRpZnkJ5L+d5oyB55uu5qCH5Y+Y6YeP55qE5oq95qC35q+U5L6LICAKcmVzYW1wbGUobGVhcm5lcj0sdGFzaz0scmVzYW1wbGluZz0sbWVhc3VyZXM9KemAmui/h+aMh+WumumHjeaKveagt+iuree7g+WSjOa1i+ivleaooeWeiyAgCm1scuacieiLpeW5sumihOWFiOWumuS5ieeahOmHjeaKveagt++8miAgCmN2MigyLWtvbGRzY3Jvc3Mtdm9saWRhdGlvbiksY3YzLCBjdjUsIGN2MTAsIGhvdXQoaG9sZG91dCB3aXRoIHNwbGl0IDIvMyBmb3IgdHJhaW5nLCAxLzMgZm9yIHRlc3RpbmcpIOS5n+WPr+S7peeUqOmHjeaKveagt+WHveaVsOS8oOmAkue7mXJlc2FtcGxlKCkgY3Jvc3N2YWwoKSByZXBjdigpIGhvbGRvdXQoKSBzdWJzYW1wbGUoKSAgIGJvb3RzdHJhcE9PQigpIGJvb3RzdHJhcEI2MzIoKSBib290c3RyYXBCNjMycGx1cygpICAKI1R1bmluZyBoeXBlcnBhcmFtZXRlcnMg6LaF5Y+C5pWw6LCD5LyYICAKIyMx6K6+5a6a6LaF5Y+C5pWw5pCc57Si56m66Ze0ICAKbWFrZVBhcmFtU2V0KG1ha2U8dHlwZT5QYXJhbSgpKSAgCm1ha2U8dHlwZT5QYXJhbSgpCSPmjIflrprotoXlj4LmlbDmkJzntKLnsbvlnovlkozojIPlm7QgIAptYWtlTnVtZXJpY1BhcmFtKGlkPSxsb3dlcj0sdXBwZXI9LHRyYWZvPSkJI+aVsOWtlyAgCm1ha2VJbnRlZ2VyUGFyYW0oaWQ9LGxvd2VyPSx1cHBlcj0sdHJhZm89KQkj5pW05pWwICAKbWFrZUludGVnZXJWZWN0b3JQYXJhbShpZD0sbGVuPSxsb3dlcj0sdXBwZXI9LHRyYWZvPSkJI+aVtOaVsOWQkemHjyAgCm1ha2VEaXNjcmV0ZVBhcmFtKGlkPSx2YWx1ZXM9YyjigKYpKQkj5YiG57G75Y+Y6YePICAKTG9naWNhbCwgTG9naWNhbFZlY3RvciwgQ2hhcmFjdGVyVmVjdG9yLCBEaXNjcmV0ZVZlY3Rvcgkj5YW25LuW57G75Z6LICAK5Y+C5pWwdHJhZm/lj6/ku6XnlKjkuIDkuKrmjIflrprlh73mlbDovazmjaLovpPlh7rvvJrkvovlpoJsb3dlcj0tMix1cHBlcj0yLHRyYWZvPWZ1bmN0aW9uKHgpICAgMTBeeO+8jOi+k+WHuuWAvGJldHdlZW4gMC4wMSBhbmQgMTAwICAKIyMy6K6+5a6a6LaF5Y+C5pWw5pCc57Si566X5rOVICAKbWFrZVR1bmVDb250cm9sPHR5cGU+KCkgIApHcmlkKHJlc29sdXRpb249MTBMKQkj5omA5pyJ5Y+v6IO955qE572R5qC854K5ICAKUmFuZG9tKG1heGl0PTEwMCkJI+maj+acuuaKveagt+aQnOe0ouepuumXtCAgCk1CTyhidWRnZXQ9KQkjQmF5ZXNpb24gbW9kZWwtYmFzZWQgb3B0aW1pemF0aW9u77yI5Z+65LqO6LSd5Y+25pav5qih5Z6L55qE5pyA5LyY5YyW77yJICAKSXJhY2Uobi5pbnN0YW5jZXM9KQkj6L+t5Luj5q+U6LWb6L+H56iLICAKQ01BRVMoKSAgCkRlc2lnbigpICAKR2VuU0EoKSAgCiMjM+i2heWPguaVsOiwg+S8mCAgCnR1bmVQYXJhbXMobGVhcm5lcj0sdGFzaz0scmVzYW1wbGluZz0sbWVhc3VyZXM9LHBhci5zZXQ9LGNvbnRyb2w9KSAj6LaF5Y+C5pWw6LCD5LyY5Ye95pWwICAKI1F1aWNrc3RhcnQgIApgYGB7cn0KI2luc3RhbGwucGFja2FnZXMoIm1sciIpCmxpYnJhcnkobWxyKQoj6aKE5aSE55CG5pWw5o2uCiNpbnN0YWxsLnBhY2thZ2VzKCJtbGJlbmNoIikKbGlicmFyeShtbGJlbmNoKQpkYXRhKFNveWJlYW4pCnNveSA9IGNyZWF0ZUR1bW15RmVhdHVyZXMoU295YmVhbix0YXJnZXQ9IkNsYXNzIikgI+i9rOWMluWboOWtkOWPmOmHj+S4uuWTkeWPmOmHjygwLDEp77yM5a+5eGdib29zdOaooeWei+acieeUqAp0c2sgPSBtYWtlQ2xhc3NpZlRhc2soZGF0YT1zb3ksdGFyZ2V0PSJDbGFzcyIpICMg5bu656uL5Lu75YqhCmhvID0gbWFrZVJlc2FtcGxlSW5zdGFuY2UoIkhvbGRvdXQiLHRzaykgIyDorr7lrprorq3nu4Ppm4blkozmtYvor5Xpm4YoZGVmYXVsdCAyLzMgZm9yIHRyYWluZywgMS8zIGZvciB0ZXN0aW5nCnRzay50cmFpbiA9IHN1YnNldFRhc2sodHNrLGhvJHRyYWluLmluZHNbWzFdXSkKdHNrLnRlc3QgPSBzdWJzZXRUYXNrKHRzayxobyR0ZXN0LmluZHNbWzFdXSkKIyDorr7lrppsZWFybmVy5ZKM6K+E5Lyw5oyH5qCHCiNpbnN0YWxsLnBhY2thZ2VzKCJ4Z2Jvb3N0IikKbGlicmFyeSh4Z2Jvb3N0KQpscm4gPSBtYWtlTGVhcm5lcigiY2xhc3NpZi54Z2Jvb3N0Iixucm91bmRzPTEwKQpjdiA9IG1ha2VSZXNhbXBsZURlc2MoIkNWIixpdGVycz01KSAjIDXmipjkuqTlj4npqozor4EKcmVzID0gcmVzYW1wbGUobHJuLHRzay50cmFpbixjdixhY2MpICMg5YeG56Gu546H5L2c5Li66K+E5Lyw5oyH5qCHCiPotoXlj4LmlbDosIPkvJjvvIzph43orq3nu4PmqKHlnosKcHMgPSBtYWtlUGFyYW1TZXQobWFrZU51bWVyaWNQYXJhbSgiZXRhIiwwLDEpLCAjIOiwg+S8mOi2heWPguaVsGV0YSwgbGFtYmRhLCBtYXhfZGVwdGgKCQkJCQkJCQkJbWFrZU51bWVyaWNQYXJhbSgibGFtYmRhIiwwLDIwMCksCgkJCQkJCQkJCW1ha2VJbnRlZ2VyUGFyYW0oIm1heF9kZXB0aCIsMSwyMCkpCiNpbnN0YWxsLnBhY2thZ2VzKCJtbHJNQk8iKQpsaWJyYXJ5KG1sck1CTykKdGMgPSBtYWtlVHVuZUNvbnRyb2xNQk8oYnVkZ2V0PTEwMCkgIyDnlKhNQk/nrpfms5XmkJzntKIKI2luc3RhbGwucGFja2FnZXMoIkRpY2VLcmlnaW5nIikKI2luc3RhbGwucGFja2FnZXMoInJnZW5vdWQiKQpsaWJyYXJ5KERpY2VLcmlnaW5nKQpsaWJyYXJ5KHJnZW5vdWQpCnRyID0gdHVuZVBhcmFtcyhscm4sdHNrLnRyYWluLGN2NSxhY2MscHMsdGMpICMgNeaKmOS6pOWPiemqjOivgeiwg+S8mApscm4gPSBzZXRIeXBlclBhcnMobHJuLHBhci52YWxzPXRyJHgpCm1kbCA9IHRyYWluKGxybix0c2sudHJhaW4pICPmtYvor5Xpm4borq3nu4PmqKHlnosKcHJkID0gcHJlZGljdChtZGwsdHNrLnRlc3QpICMg6aKE5rWLCmNhbGN1bGF0ZUNvbmZ1c2lvbk1hdHJpeChwcmQpICMg5re35reG55+p6Zi16K+E5Lyw5qih5Z6LCm1kbCA9IHRyYWluKGxybix0c2spICMg55So5omA5pyJ55qE5pWw5o2u6YeN5paw6K6t57uD5qih5Z6L77yM5oqV5YWl5a6e55SoCmBgYAoKI0NvbmZpZ3VyYXRpb24g6YWN572uICAK55SoZ2V0TWxyT3B0aW9ucygp5p+l55yLbWxyIOeahOeOsOacieiuvue9riAgCueUqGNvbmZpZ3VyZU1scigp5pu05pS5bWxy55qE6buY6K6k6K6+572uICAKc2hvdy5pbmZv77yaKHRyYW5pbmcsIHR1bmluZywgcmVzYW1wbGluZyxldGMp5piv5ZCm5bGV56S66buY6K6k5YaX6ZW/55qE6L6T5Ye677yM6buY6K6kVFJVRSAgCm9uLmxlYXJuZXIuZXJyb3LvvJrmgI7moLfmjqfliLZsZWFybmVy55qE6ZSZ6K+v77yMIOKAnHN0b3DigJ0oZGVmYXVsdCksIOKAnHdhcm7igJ0sIOKAnHF1aWV04oCdICAKb24ubGVhcm5lci53YXJuaW5n77ya5oCO5qC35o6n5Yi2bGVhcm5lcueahOitpuWRiu+8jOKAnXdhcm7igJ0oZGVmYXVsdCkgLCDigJxxdWlldOKAnSAgCm9uLnBhci53aXRob3V0LmRlc2PvvJrotoXlj4LmlbDmjqfliLbvvIzigJ1zdG9w4oCdKGRlZmF1bHQpLCDigJx3YXJu4oCdICzigJ1xdWlldOKAnSAgCm9uLnBhci5vdXQub2YuYm91bmRz77ya6LaF5Y+C5pWw6LaF5Ye66L6555WM5YC877yM4oCdc3RvcOKAnShkZWZhdWx0KSAs4oCdd2FybuKAnSwg4oCccXVpZXTigJ0gIApvbi5tZWFzdXJlLm5vdC5hcHBsaWNhYmxl77ya6K+E5Lyw5oyH5qCH5a+5bGVhcm5lcuS4jemAgueUqO+8jOKAnXN0b3DigJ0oZGVmYXVsdCkgLCDigJx3YXJu4oCdLCDigJxxdWlldOKAnSAgCnNob3cubGVhcm5lci5vdXRwdXTvvJpsZWFybmVy5Zyo6K6t57uD55qE5pe25YCZ6L6T5Ye65Yiw5o6n5Yi25Y+w77yM6buY6K6kVFJVRSAgCm9uLmVycm9yLmR1bXDvvJrmmK/lkKbkuLpjcmFzaGVkIGxlYXJuZXLliJvlu7rkuIDkuKplcnJvciBkdW1w77yM5b2Tb24ubGVhcm5lci5lcnJvciDmsqHmnInooqvmjIflrprkuLrigJ1zdG9w4oCdIOaXtu+8jOm7mOiupFRSVUUgIAojUGFyYWxsZWxpemF0aW9u5bm26KGMICAKbWxy57uT5ZCIcGFyYWxsZWxNYXDljIXliKnnlKjlpJrmoLjlkozpm4bnvqTov5DnrpfliqDlv6vov5DooYzpgJ/luqbvvIxtbHLoh6rliqjlj5HnjrDog73ov5vooYzlubbooYznmoTmk43kvZzjgIIgIArlvIDlp4vlubbooYzvvJpwYXJhbGxlbFN0YXJ0KG1vZGU9LGNwdXM9LGxldmVsPSkgIArnu5PmnZ/lubbooYzvvJpwYXJhbGxlbFN0b3AoKSAgCm1vZGUJ5Yaz5a6a5bm26KGM55qE5pa55byPICAK4oCcbG9jYWzigJ0J5peg5bm26KGM5oCn77yM566A5Y2V55qE5L2/55SobWFwcGx5ICAK4oCcbXVsdGljb3Jl4oCdCeWNleacuuWZqOS4iuWkmuaguO+8jOS9v+eUqHBhcmFsbGVsOjptY2xhcHBsee+8jHdpbmRvd3PkuIrkuI3pgILnlKggIArigJxzb2NrZXTigJ0J5aSa5qC4c29ja2VkIG1vZGUgIArigJxtcGnigJ0J5LiA5Liq5oiW5aSa5Liq5py65Zmo5LiK6ZuG576k6L+Q566X77yM5L2/55SocGFyYWxsZWw6Om1ha2VDbHVzdGVyIGFuZCBwYXJhbGxlbDo6Y2x1c3Rlck1hcArigJxCYXRjaEpvYnPigJ0J5om55o6S6ZifSFBD6ZuG576k77yM5L2/55SoQmF0Y2hKb2JzOjpiYXRjaE1hcCAgCmNwdXMJ5L2/55So55qE54mp55CG5YaF5qC45pWwICAKbGV2ZWwJ5o6n5Yi25bm26KGM77yM5L2/55So4oCdbWxyLmJlbmNobWFya+KAnSDigJxtbHIucmVzYW1wbGXigJ0g4oCcbWxyLnNlbGVjdEZlYXR1cmVz4oCdIOKAnG1sci50dW5lUGFyYW1z4oCdIOKAnG1sci5lbnNlbWJsZeKAnSAgCiNJbXB1dGF0aW9uIOaPkuihpSAgCmltcHV0ZShvYmo9LHRhcmdldD0sY29scz0sZHVtbXkuY29scz0sZHVtbXkudHlwZT0pICAK57y65aSx55qE5pWw5o2u6L+b6KGM5o+S6KGl77yM6L+U5Zue5LiA5Liq5YiX6KGo77yM5YyF5ous5o+S6KGl6L+H6aKd5pWw5o2u6ZuG5oiWdGFza++8jOWSjOaPkuihpeaPj+i/sCAgCnJlaW1wdXRlKG9iaj0sZGVzYz0p55So5bey6KKraW1wdXRl5Yib5bu655qE5o+S6KGl5o+P6L+wKGRlc2NyaXB0aW9uLCBkZXNjKeaPkuihpee8uuWkseWAvCAgCm9iaj0gZGF0YS5mcmFtZSBvciB0YXNrICAKdGFyZ2V0PSDmjIflrprnm67moIflj5jph4/vvIzlsIbkuI3kvJrooqvmj5LooaUgIApjb2xzPSDopoHmj5LooaXnmoTliJflkI3miJbpgLvovpHliJfooaggIApkdW1teS5jb2xzPSDlu7rnq4tOQShUL0Yp5YiX55qE5YiX5ZCNICAKZHVtbXkudHlwZT0g6K6+5a6a4oCdbnVtZXJpY+KAne+8jOeUqCgwLDEp5Luj5pu/KFQvRikgIArkuZ/og73nlKhjbGFzc2VzIOWSjGR1bW15LmNsYXNzZXMg5Luj5pu/Y29scyAgCuS8oOmAkue7mWNvbHPmiJZjbGFzc2Vz55qEbGlzdOekuuS+i++8miAgCmNvbHM9bGlzdChWMT1pbXB1dGVNZWFuKCkpIFYx5piv6KaB5o+S6KGl55qE5YiXICAKaW1wdXRlTWVhbigp5Li66KaB5o+S6KGl55qE5pa55rOVICAKaW1wdXRlQ29uc3QoY29uc3Q9KQnluLjmlbAgIAppbXB1dGVNZWRpYW4oKQnkuK3kvY3mlbAgIAppbXB1dGVNb2RlKCkJ5LyX5pWwICAKaW1wdXRlTWluKG11bHRpcGxpZXI9KQnmnIDlsI/lgLwgIAppbXB1dGVNYXgobXVsdGlwbGllcj0pCeacgOWkp+WAvCAgCmltcHV0ZU5vcm1hbChtZWFuPSxzZD0pCeato+aAgeaPkuihpSAgCmltcHV0ZUhpc3QoYnJlYWtzPSx1c2UubWlkcz0pICAKaW1wdXRlTGVhcm5lcihsZWFybmVyPSxmZWF0dXJlcz0pCeaooeWei+aPkuihpSAgCiNGZWF0dXJlIEV4dHJhY3Rpb27nibnlvoHmj5Dlj5YgIAojIzFGZWF0dXJlIGZpbHRlcmluZ+eJueW+geetm+mAiSAgCmZpbHRlckZlYXR1cmVzKHRhc2s9LG1ldGhvZD0scGVyYz0sYWJzPSx0aHJlc2hvbGQ9KSAgIArmjInnibnlvoHnmoTph43opoHmgKfov5vooYzmjpLluo/vvIzpgInmi6nlhbbkuK3nmoR0b3AgbiBwZXJjZW50KHBlcmM9KSwgdG9wIG4oYWJzPSkgb3Ig6K6+5a6a6ZiI5YC8KHRocmVzaG9sZD0p77yM6L+U5ZuedGFza++8jOayoeacieiiq+etm+mAieeahOeJueW+geWwhuS8muiiq+WIoOmZpOOAgiAgCum7mOiupOeahOetm+mAieaWueazleS4uiJyYW5kb21Gb3Jlc3RTUkMucmZzcmMi77yM5Lmf5Y+v5Lul6K6+572u5YW25LuW5pa55rOV77yaICAK4oCcYW5vdmEudGVzdOKAnSDigJxjYXJzY29yZeKAnSDigJxjZm9yZXN0LmltcG9ydGFuY2XigJ0K4oCcY2hpLnNxdWFyZWTigJ0g4oCcZ2Fpbi5yYXRpb+KAnSDigJxpbmZvcm1hdGlvbi5nYWlu4oCdCuKAnGtydXNrYWwudGVzdOKAnSDigJxsaW5lYXIuY29ycmVsYXRpb27igJ0g4oCcbXJtcuKAnSDigJxvbmVS4oCdCuKAnHBlcm11dGF0aW9uLmltcG9ydGFuY2XigJ0g4oCccmFuZG9tRm9yZXN0LmltcG9ydGFuY2XigJ0K4oCccmFuZG9tRm9yZXN0U1JDLnJmc3Jj4oCdIOKAnHJhbmRvbUZvcmVzdFNSQy52YXIuc2VsZWN04oCdCuKAnHJhbmsuY29ycmVsYXRpb27igJ0g4oCccmVsaWVm4oCdCuKAnHN5bW1ldHJpY2FsLnVuY2VydGFpbnR54oCdIOKAnHVuaXZhcmlhdGUubW9kZWwuc2NvcmXigJ0K4oCcdmFyaWFuY2XigJ0gIAojIzJGZWF0dXJlIHNlbGVjdGlvbueJueW+gemAieaLqSAgCnNlbGVjdEZlYXR1cmVzKGxlYXJuZXI9LHRhc2s9LCByZXNhbXBsaW5nPSxtZWFzdXJlcz0sY29udHJvbD0pICAK55So5LiA5Liq54m55b6B6YCJ5oup566X5rOVKGNvbnRyb2wp6YeN5oq95qC35ZKM5bu656uL5qih5Z6L77yM5Y+N5aSN6YCJ5oup5LiN5ZCM55qE54m55b6B6ZuG77yM55u05Yiw5om+5Yiw5pyA5aW955qE54m55b6B6ZuG44CC6L+U5ZueRmVhdFNlbFJlc3VsdOWvueixoe+8jOWMheaLrOacgOS9s+mAieaLqeWSjOacgOS9s+i3r+W+hOOAgiAgCnRzayA9IHN1YnNldFRhc2sodHNrLGZlYXR1cmVzPWZzciR4KeW6lOeUqOacgOS9s+mAieaLqee7k+aenChmc3Ip5YiwdGFzayh0c2spICAK54m55b6B6YCJ5oup566X5rOVKGNvbnRyb2wp77yaICAKbWFrZUZlYXRTZWxDb250cm9sRXhoYXVzdGl2ZShtYXguZmVhdHVyZXM9KeWwneivleavj+S4gOenjeeJueW+gee7hOWQiO+8jOWPr+mAieWPguaVsG1heC5mZWF0dXJlcyAgCm1ha2VGZWF0U2VsQ29udHJvbFJhbmRvbShtYXhpdD0scHJvYj0sbWF4LmZlYXR1cmVzPSnpmo/mnLrmir3lj5bnibnlvoHpm4Yo5qaC546HcHJvYixkZWZhdWx0IDAuNSkgICDov63ku6MobWF4aXQsZGVmYXVsdCAxMDAp77yM6L+U5Zue5YW25Lit5pyA5aW955qE44CCICAKbWFrZUZlYXRTZWxDb250cm9sU2VxdWVudGlhbChtZXRob2Q9LG1heGl0PSxtYXguZmVhdHVyZXM9LGFscGhhPSxiZXRhPSnnlKjku6XkuIvnmoTov63ku6Pnrpfms5XmkJzlr7vvvIzor4TkvLDvvJogIArigJxzZnPigJ0gZm9yd2FyZCBzZWFyY2gsIOKAnHNmZnPigJ0gZmxvYXRpbmcgZm9yd2FyZCBzZWFyY2gsICAK4oCcc2Jz4oCdIGJhY2t3YXJkIHNlYXJjaCAsIOKAnHNmYnPigJ0gZmxvYXRpbmcgYmFja3dhcmQgc2VhcmNoLCAgCuKAnGFscGhh4oCdIOavj+asoeWinuWKoOS4gOS4queJueW+geadpeaUueWWhOivhOS8sO+8jOWPluacgOWwkeeJueW+gembhiAgCuKAnGJldGHigJ0g5q+P5qyh56e76Zmk5LiA5Liq54m55b6B5p2l5pS55ZaE6K+E5Lyw77yM5Y+W5pyA5bCR54m55b6B6ZuGICAKbWFrZUZlYXRTZWxDb250cm9sR0EobWF4aXQ9LG1heC5mZWF0dXJlcz0sbXU9LGxhbWJkYT0sY3Jvc3NvdmVyLnJhdGU9LG11dGF0aW9uLnJhdGU9Kemaj+acuueJueW+geWQkemHj+eahOmBl+S8oOeul+azle+8jOeEtuWQjuWIqeeUqOacgOS9s+aAp+iDveeahOS6pOWPieadpeS6p+eUn+WQjuS7o++8jOS7o+S7o+ebuOS8oO+8jOWPguaVsO+8miAgCm115piv54i257O76KeE5qihICAKbGFtYmRhIOaYr+WtkOezu+inhOaooSAgCmNyb3Nzb3Zlci5yYXRlIOS7juesrOS4gOS6suacrOmAieaLqeS4gOeCueeahOamgueOhyAgCm11dGF0aW9uLnJhdGUg5piv57+76L2s55qE5qaC546HKG9uIG9yIG9mZikgIApCZW5jaG1hcmtpbmcgKOWfuuWHhueCuSkgIApiZW5jaG1hcmsobGVhcm5lcnM9LHRhc2tzPSxyZXNhbXBsaW5ncz0sbWVhc3VyZXM9KSAgCuWFgeiuuOi/m+ihjOeugOWNleeahOavlOi+g++8muaJp+ihjOWNleS4gHRhc2vnmoTlpJrph41sZWFybmVy77yM5omn6KGM5aSa6YeNdGFza+eahOWNleS4gGxlYXJuZXLvvIzmiJbogIXmiafooYzlpJrph410YXNr55qE5aSa6YeNbGVhcm5lcu+8jOi/lOWbnuS4gOS4quWfuuWHhue7k+aenOWvueixoeOAgiAgCuWfuuWHhue7k+aenOiiq+WHveaVsGdldEJNUjxvYmplY3Q+5o6l5pS277yaICAKQWdnclBlcmZvcm1hbmNlICAKRmVhdFNlbFJlc3VsdHMgRmlsdGVyZWRGZWF0dXJlcyBMZWFybmVySWRzICAKTGVhbmVyU2hvcnROYW1lcyBMZWFybmVycyBNZWFzdXJlSWRzIE1lYXN1cmVzICAKTW9kZWxzIFBlcmZvcm1hbmNlcyBQcmVkaWN0aW9ucyBUYXNrRGVzY3MgVGFza0lkcyAgClR1bmVSZXN1bHRzICAKbWxy5YaF572u5LqG6K645aSa5pyJ6Laj55qEdGFza++8jOWvueWfuuWHhuWtpuS5oOW+iOacieW4ruWKqe+8miAgCmFncmkudGFzayBiYy50YXNrIGJoLnRhc2sgY29zdGlyaXMudGFzayBpcmlzLnRhc2sgIApsdW5nLnRhc2sgbXRjYXJzLnRhc2sgcGlkLnRhc2sgc29uYXIudGFzayAgCndwYmMudGFzayB5ZWFzdC50YXNrICAKI1Zpc3VhbGl6YXRpb24g5Y+v6KeG5YyWICAKIyMx5Y+v6KeG5YyW5oC76KGoICAK55Sf5oiQ5pWw5o2u5Ye95pWwCWdncGxvdDLnu5jlm77lh73mlbAJZ2d2aXPnu5jlm77lh73mlbAJ6K+05piOICAKZ2VuZXJhdGVUaHJlc2hWc1BlcmZEYXRhCXBsb3RUaHJlc1ZzUGVyZglwbG90VGhyZXNoVnNQZXJmR0dWSVMJ5oCn6IO9ICAKcGxvdFJPQ0N1cnZlcwktCVJPQ+WIhuaekCAgCmdlbmVyYXRlQ3JpdERpZmZlcmVuY2VzRGF0YQlwbG90Q3JpdERpZmZlcmVuY2VzCS0J5Z+65YeG5a6e6aqMICAKZ2VuZXJhdGVIeXBlclBhcnNFZmZlY3REYXRhCXBsb3RIeXBlclBhcnNFZmZlY3QJCeiwg+aVtO+8jOi2heWPguaVsOiwg+aVtOaViOaenCAgCmdlbmVyYXRlRmlsdGVyVmFsdWVzRGF0YQlwbG90RmlsdGVyVmFsdWVzCXBsb3RGaWx0ZXJWYWx1ZXNHR1ZJUwnlip/og73pgInmi6kgIApnZW5lcmF0ZUxlYXJuaW5nQ3VydmVEYXRhCXBsb3RMZWFybmluZ0N1cnZlCXBsb3RMZWFybmluZ0N1cnZlR0dWSVMJ5a2m5Lmg5puy57q/ICAKZ2VuZXJhdGVQYXJ0aWFsRGVwZW5kZW5jZURhdGEJcGxvdFBhcnRpYWxEZXBlbmRlbmNlCXBsb3RQYXJ0aWFsRGVwZW5kZW5jZUdHVklTCemDqOWIhuS+nei1luaDheiKggpnZW5lcmF0ZUZ1bmN0aW9uYWxBTk9WQURhdGEgIApnZW5lcmF0ZUNhbGlicmF0aW9uRGF0YQlwbG90Q2FsaWJyYXRpb24JLQnliIbnsbvlmajmoKHlh4blm74gIAojIzLlj6/op4bljJblh73mlbDor7TmmI4gIApQZXJmb3JtYW5jZQnooajnjrAgIApnZW5lcmF0ZVRocmVzaFZzUGVyZkRhdGEob2JqPSxtZWFzdXJlcz0pICAJ6I635b6X5LqM5YiG57G76Zeu6aKY5LiN5ZCM5YiH5YiG54K56K+E5Lyw6LSo6YeP77yM5L+D5L2/6YCJ5Ye65pyA5LyY6ZiI5YC8KHRocmVzaG9sZCkgIApwbG90VGhyZXNoVnNQZXJmKG9iaikJ55SoVGhyZXNoVnNQZXJmRGF0YeaVsOaNruS9nOWHuumYiOWAvOWPr+inhuWMluihqOeOsCAgCnBsb3RST0NDdXJ2ZXMob2JqKQnnlKhUaHJlc2hWc1BlcmZEYXRh5pWw5o2u5YGa5Ye6Uk9D5puy57q/77yM5LiA5a6a6KaB6K6+5a6abWVhc3VyZXM9bGlzdChmcHIsdHByKSAgClJlc2lkdWFscwnmrovlt64gIApwbG90UmVzaWR1YWxzKG9iaj0pCeS4ulByZWRpY3Rpb24g5oiWQmVuY2htYXJrUmVzdWx05a+56LGh5L2c5q6L5beuICAKYGBge3J9Cm4gPSBnZXRUYXNrU2l6ZShzb25hci50YXNrKQp0cmFpbi5zZXQgPSBzYW1wbGUobiwgc2l6ZSA9IHJvdW5kKDIvMyAqIG4pKQp0ZXN0LnNldCA9IHNldGRpZmYoc2VxX2xlbihuKSwgdHJhaW4uc2V0KQpscm4xID0gbWFrZUxlYXJuZXIoImNsYXNzaWYubGRhIiwgcHJlZGljdC50eXBlID0gInByb2IiKQptb2QxID0gdHJhaW4obHJuMSwgc29uYXIudGFzaywgc3Vic2V0ID0gdHJhaW4uc2V0KQpwcmVkMSA9IHByZWRpY3QobW9kMSwgdGFzayA9IHNvbmFyLnRhc2ssIHN1YnNldCA9IHRlc3Quc2V0KQpkZiA9IGdlbmVyYXRlVGhyZXNoVnNQZXJmRGF0YShwcmVkMSwgbWVhc3VyZXMgPSBsaXN0KGZwciwgdHByLCBtbWNlKSkKcGVyZm9ybWFuY2UocHJlZDEsIGF1YykKcGxvdFRocmVzaFZzUGVyZihkZikKbGlicmFyeShrZXJubGFiKQpscm4yID0gbWFrZUxlYXJuZXIoImNsYXNzaWYua3N2bSIsIHByZWRpY3QudHlwZSA9ICJwcm9iIikgICMg6ZyA6KaB6L295YWl5YyFa2VybmxhYgptb2QyID0gdHJhaW4obHJuMiwgc29uYXIudGFzaywgc3Vic2V0ID0gdHJhaW4uc2V0KQpwcmVkMiA9IHByZWRpY3QobW9kMiwgdGFzayA9IHNvbmFyLnRhc2ssIHN1YnNldCA9IHRlc3Quc2V0KQpkZiA9IGdlbmVyYXRlVGhyZXNoVnNQZXJmRGF0YShsaXN0KGxkYSA9IHByZWQxLCBrc3ZtID0gcHJlZDIpLCBtZWFzdXJlcyA9IGxpc3QoZnByLCB0cHIpKQpwbG90Uk9DQ3VydmVzKGRmKQpgYGAgIAoKI0xlYXJuaW5nIGN1cnZlCeWtpuS5oOabsue6vyAgCmdlbmVyYXRlTGVhcm5pbmdDdXJ2ZURhdGEobGVhcm5lcnM9LHRhc2s9LHJlc2FtcGxpbmc9LHBlcmNzPSxtZWFzdXJlcz0pCSPojrflvpfkuI3lkIznmb7liIbmr5TnmoR0YXNrIGRhdGHkuIrlgZrmqKHlnovor4TkvLDotKjph4/mlbDmja4gIApwbG90TGVhcm5pbmdDdXJ2ZShvYmo9KQkj5q+U6L6D5pWw5o2u55qE5bey5L2/55So5ZKM5qih5Z6L6LSo6YeP55qE5YWz57O777yM5L2/55SoTGVhcm5pbmdDdXJ2ZURhdGHlr7nosaEgIApgYGB7cn0KciA9IGdlbmVyYXRlTGVhcm5pbmdDdXJ2ZURhdGEoCglsZWFybmVycyA9IGMoImNsYXNzaWYucnBhcnQiLCAiY2xhc3NpZi5rbm4iKSwKCXRhc2sgPSBzb25hci50YXNrLAoJcGVyY3MgPSBzZXEoMC4xLCAxLCBieSA9IDAuMiksCgltZWFzdXJlcyA9IGxpc3QodHAsIGZwLCB0biwgZm4pLAoJcmVzYW1wbGluZyA9IG1ha2VSZXNhbXBsZURlc2MobWV0aG9kID0gIkNWIiwgaXRlcnMgPSA1KSwKCXNob3cuaW5mbyA9IEZBTFNFKQpwbG90TGVhcm5pbmdDdXJ2ZShyKQpgYGAgIAoKI0ZlYXR1cmUgaW1wb3J0YW5jZQnnibnlvoHph43opoHmgKcgIApnZW5lcmF0ZUZpbHRlclZhbHVlc0RhdGEodGFzaz0sbWV0aG9kPSkJI+eUqOaMh+WumueahOmAieaLqeaWueazleiOt+W+l+aOkuW6j+eahOeJueW+gemHjeimgeaApyAgCnBsb3RGaWx0ZXJWYWx1ZXMob2JqPSkJI+WPr+inhuWMlueJueW+gemHjeimgeaAp++8jEZpbHRlclZhbHVlc0RhdGHlr7nosaEgIApmdiA9IGdlbmVyYXRlRmlsdGVyVmFsdWVzRGF0YShpcmlzLnRhc2spICAKcGxvdEZpbHRlclZhbHVlcyhmdikgIAojSHlwZXJwYXJhbWV0ZXJzIHR1bmluZwnotoXlj4LmlbDosIPkvJggIApnZW5lcmF0ZUh5cGVyUGFyc0VmZmVjdERhdGEodHVuZS5yZXN1bHQ9KQnojrflvpfkuI3lkIzotoXlj4LmlbDnmoTlvbHlk40gIApwbG90SHlwZXJQYXJzRWZmZWN0KGh5cGVycGFycy5lZmZlY3QuZGF0YT0seD0seT0sej0pICAJ5Y+v6KeG5YyW6LaF5Y+C5pWw5b2x5ZON77yMSHlwZXJQYXJzRWZmZWN0RGF0YeWvueixoSAgCnBsb3RPcHRQYXRoKG9wPSkJ5Y+v6KeG5YyW5pyA5LyY6L+b56iL6K+m5oOF77yMPG9iaj4kb3B0LnBhdGjlr7nosaHvvIwgPG9iaj7mmK8gdHVuZVJlc3VsdOaIliBmZWF0U2VsUmVzdWx057G755qE5a+56LGh44CCICAKcGxvdFR1bmVNdWx0aUNyaXRSZXN1bHQocmVzPSkJ5bGV56S6cGFyZXRv5Zu+77yM5aSa6YeN6K+E5Lyw6LSo6YeP55qE6LCD5LyY57uT5p6cICAKYGBge3J9CnBzID0gbWFrZVBhcmFtU2V0KAoJbWFrZU51bWVyaWNQYXJhbSgiQyIsIGxvd2VyID0gLTUsIHVwcGVyID0gNSwgdHJhZm8gPSBmdW5jdGlvbih4KSAyXngpCikKY3RybCA9IG1ha2VUdW5lQ29udHJvbEdyaWQoKQpyZGVzYyA9IG1ha2VSZXNhbXBsZURlc2MoIkhvbGRvdXQiKQpscm4gPSBtYWtlVHVuZVdyYXBwZXIoImNsYXNzaWYua3N2bSIsIGNvbnRyb2wgPSBjdHJsLAoJCQkJCQkJCQkJCW1lYXN1cmVzID0gbGlzdChhY2MsIG1tY2UpLCByZXNhbXBsaW5nID0gcmRlc2MsIHBhci5zZXQgPSBwcywgc2hvdy5pbmZvID0gRkFMU0UpCnJlcyA9IHJlc2FtcGxlKGxybiwgdGFzayA9IHBpZC50YXNrLCByZXNhbXBsaW5nID0gY3YyLCBleHRyYWN0ID0gZ2V0VHVuZVJlc3VsdCwgc2hvdy5pbmZvID0gRkFMU0UpCmRhdGEgPSBnZW5lcmF0ZUh5cGVyUGFyc0VmZmVjdERhdGEocmVzKQpwbG90SHlwZXJQYXJzRWZmZWN0KGRhdGEsIHggPSAiQyIsIHkgPSAiYWNjLnRlc3QubWVhbiIsIHBsb3QudHlwZSA9ICJsaW5lIikKYGBgICAKCiNQYXJ0aWFsIGRlcGVuZGVuY2UJ6YOo5YiG5L6d6LWWICAKZ2VuZXJhdGVQYXJ0aWFsRGVwZW5kZW5jZURhdGEob2JqPSxpbnB1dD0pICAJI+iOt+W+l+aooeWeiyhvYmop55qE6YOo5YiG5L6d6LWW6aKE5rWL77yM6YCa6L+H5q+P5LiA5qyh55qE54m55b6B5pWw5o2u6L6T5YWlKGlucHV0KSAgCnBsb3RQYXJ0aWFsRGVwZW5kZW5jZShvYmo9KQkj6YOo5YiG5L6d6LWW5Zu+77yMUGFydGlhbERlcGVuZGVuY2VEYXRh5a+56LGhICAKYGBge3J9Cmxybi5yZWdyID0gbWFrZUxlYXJuZXIoInJlZ3Iua3N2bSIpCmZpdC5yZWdyID0gdHJhaW4obHJuLnJlZ3IsIGJoLnRhc2spCiNpbnN0YWxsLnBhY2thZ2VzKCJtbXBmIikKbGlicmFyeShtbXBmKQpwZC5yZWdyID0gZ2VuZXJhdGVQYXJ0aWFsRGVwZW5kZW5jZURhdGEoZml0LnJlZ3IsIGJoLnRhc2ssICJsc3RhdCIsIGZ1biA9IG1lZGlhbikKcGxvdFBhcnRpYWxEZXBlbmRlbmNlKHBkLnJlZ3IpCmxybi5jbGFzc2lmID0gbWFrZUxlYXJuZXIoImNsYXNzaWYua3N2bSIsIHByZWRpY3QudHlwZSA9ICJwcm9iIikKZml0LmNsYXNzaWYgPSB0cmFpbihscm4uY2xhc3NpZiwgaXJpcy50YXNrKQpwZC5jbGFzc2lmID0gZ2VuZXJhdGVQYXJ0aWFsRGVwZW5kZW5jZURhdGEoZml0LmNsYXNzaWYsIGlyaXMudGFzaywgIlBldGFsLkxlbmd0aCIsIGZ1biA9IG1lZGlhbikKcGxvdFBhcnRpYWxEZXBlbmRlbmNlKHBkLmNsYXNzaWYpCmBgYCAgCiAgCiNCZW5jaG1hcmtpbmcJ5Z+65YeG54K5ICAKcGxvdEJNUkJveHBsb3RzKGJtcj0pCSPooajnjrDnmoTliIbnsbvlm74gIApwbG90Qk1SU3VtbWFyeShibXI9KQkj5bmz5Z2H6KGo546w55qE5pWj54K55Zu+ICAKcGxvdEJNUlJhbmtzQXNCYXJDaGFydChibXI9KQkjbGVhcm5lciDmjpLluo/mn7HlvaLlm74gIApPdGhlcgkj5YW25LuW5Zu+ICAKZ2VuZXJhdGVDcml0RGlmZmVyZW5jZXNEYXRhKGJtcj0sbWVhc3VyZT0scC52YWx1ZT0sdGVzdD0pICAJI+aJp+ihjOS4tOeVjOW3ruajgOmqjCznlKhib25mb3Jyb25pLWR1bm4o4oCcYmTigJ0pIOaIluKAnU5lbWVueWnigJ3mo4DpqowgIApwbG90Q3JpdERpZmZlcmVuY2VzKG9iaj0pCSPkuLTnlYzngrnmo4Dpqozlj6/op4bljJYgIApnZW5lcmF0ZUNhbGlicmF0aW9uRGF0YShvYmo9KQkj6K+E5Lyw5qaC546H6aKE5rWL5LiO55yf5a6e5Y+R55Sf546H55qE5qCh5YeGICAKcGxvdENhbGlicmF0aW9uKG9iaj0pCSPmoKHlh4blm74gIAojV3JhcHBlcnMg5bCB6KOF5ZmoICAK5bCB6KOF5Zmo5L2/5YW35pyJ6ZmE5Yqg5Yqf6IO955qE5a2m5Lmg6ICF6J6N5ZCI44CCbWxy5oqK5bim5pyJ5bCB6KOF5Zmo55qEbGVhcm5lcueci+S9nOS4gOS4quWNleeLrOeahGxlYXJuZXLvvIzotoXlj4LmlbDnmoTlsIHoo4XkuZ/kvJrkuI7ln7rnoYDmqKHlnovlj4LmlbDogZTlkIjosIPosJDjgILluKbljIXoo4XnmoTmqKHlnovlsIblupTnlKjkuo7mlrDmlbDmja7jgIIgIAojUHJlcHJvY2Vzc2luZyBhbmQgaW1wdXRhdGlvbgnpooTlpITnkIblkozmj5LooaUgIAptYWtlRHVtbXlGZWF0dXJlc1dyYXBwZXIobGVhcm5lcj0pICAKbWFrZUltcHV0ZVdyYXBwZXIobGVhcm5lcj0sY2xhc3Nlcz0sY29scz0pICAKbWFrZVByZXByb2NXcmFwcGVyKGxlYXJuZXI9LHRyYWluPSxwcmVkaWN0PSkgIAptYWtlUHJlcHJvY1dyYXBwZXJDYXJldChsZWFybmVyPSzigKYpICAKbWFrZVJlbW92ZUNvbnN0YW50RmVhdHVyZXNXcmFwcGVyKGxlYXJuZXI9KSAgCiNDbGFzcyBpbWJhbmNlCeexu+WIq+S4jeW5s+ihoSAgCm1ha2VPdmVyQmFnZ2luZ1dyYXBwZXIobGVhcm5lcj0pICAKbWFrZVNNT1RFV3JhcHBlcihsZWFybmVyPSkgIAptYWtlVW5kZXJzYW1wbGVXcmFwcGVyKGxlYXJuZXI9KSAgCm1ha2VXZWlnaHRlZENsYXNzZXNXcmFwcGVyKGxlYXJuZXI9KSAgCiNDb3N0LXNlbnNpdGl2ZQnmiJDmnKzliIbmnpAgIAptYWtlQ29zdFNlbnNDbGFzc2lmV3JhcHBlcihsZWFybmVyPSkgIAptYWtlQ29zdFNlbnNSZWdyV3JhcHBlcihsZWFybmVyPSkgIAptYWtlQ29zdFNlbnNXZWlnaHRlZFBhaXJzV3JhcHBlcihsZWFybmVyPSkgIAojTXVsdGlsYWJlbCBjbGFzc2lmaWNhdGlvbgnlpJrmoIfnrb7liIbnsbsgIAptYWtlTXVsdGlsYWJlbEJpbmFyeVJlbGV2YW5jZVdyYXBwZXIobGVhcm5lcj0pICAKbWFrZU11bHRpbGFiZWxDbGFzc2lmaWVyQ2hhaW5zV3JhcHBlcihsZWFybmVyPSkgIAptYWtlTXVsdGlsYWJlbERCUldyYXBwZXIobGVhcm5lcj0pICAKbWFrZU11bHRpbGFiZWxOZXN0ZWRTdGFja2luZ1dyYXBwZXIobGVhcm5lcj0pICAKbWFrZU11bHRpbGFiZWxTdGFja2luZ1dyYXBwZXIobGVhcm5lcj0pICAKI090aGVyCeWFtuS7liAgCm1ha2VCYWdnaW5nV3JhcHBlcihsZWFybmVyPSkgIAptYWtlQ29uc3RhbnRDbGFzc1dyYXBwZXIobGVhcm5lcj0pICAKbWFrZURvd25zYW1wbGVXcmFwcGVyKGxlYXJuZXI9LGR3LnBlcmM9KSAgCm1ha2VGZWF0U2VsV3JhcHBlcihsZWFybmVyPSxyZXNhbXBsaW5nPSxjb250cm9sPSkgIAptYWtlRmlsdGVyV3JhcHBlcihsZWFybmVyPSxmdy5wZXJjPSxmdy5hYnM9LGZ3LnRocmVzaG9sZD0pICAKbWFrZU11bHRpQ2xhc3NXcmFwcGVyKGxlYXJuZXI9KSAgCm1ha2VUdW5lV3JhcHBlcihsZWFybmVyPSxyZXNhbXBsaW5nPSxwYXIuc2V0PSxjb250cm9sPSkgIAojTmVzdGVkIFJlc2FtcGxpbmcg5bWM5aWX6YeN6YeH5qC3ICAKbWxy5pSv5oyB5bWM5aWX6YeN6YeH5qC36L+b6KGM5aSN5p2C5pON5L2c77yM5L6L5aaC6YCa6L+H5YyF6KOF5p2l6LCD5LyY5ZKM54m55b6B6YCJ5oup562J44CC5Li65LqG6I635b6X6Imv5aW955qE5rOb5YyW5oCn6IO95Lyw6K6h5ZKM6YG/5YWN5pWw5o2u5rOE5ryP77yM5bu66K6u5L2/55So5aSW6YOo77yI55So5LqO6LCD5LyYL+eJueW+gemAieaLqe+8ieWSjOWGhemDqO+8iOWvueS6juWfuuacrOaooeWei++8iemHjemHh+agt+i/h+eoi+OAgiAgCuWklumDqOmHjemHh+agt+iDveWcqHJlc2FtcGxlLCBiZW5jaG1hcmvkuK3ooqvmjIflrpogIArlhoXpg6jph43ph4fmoLfog73lnKhtYWtlVHVuZVdyYXBwZXIsIG1ha2VGZWF0U2VsV3JhcHBlciwgZXRj5Lit6KKr5oyH5a6aICAKI0Vuc2VtYmxlcyDpm4bmiJAgIAptYWtlU3RhY2tlZExlYXJuZXIoYmFzZS5sZWFybmVycz0sc3VwZXIubGVhcm5lcj0sbWV0aG9kPSkgI+WwhuWkmumHjWxlYXJuZXLpm4bmiJAgIApiYXNlLmxlYXJuZXJzPSAj5L2/55So5Yid5aeL6aKE5rWL55qEbGVhcm5lcnMgIApzdXBlci5sZWFybmVyPSAj5L2/55So5pyA57uI6aKE5rWL55qEbGVhcm5lcnMgIAptZXRob2Q9ICPnu4TlkIhiYXNlIGxlYXJuZXJz6aKE5rWL55qE5pa55rOVICAK4oCcYXZlcmFnZeKAne+8miPmiYDmnIliYXNlIGxlYXJuZXJz55qE5bmz5Z2HICAK4oCcc3RhY2subm9jduKAnSDigJxzdGFjay5jduKAne+8muWcqGJhc2UgbGVhcm5lcnPnmoTnu5PmnpzkuIrorq3nu4NzdXBlciBsZWFybmVyICh3aXRoIG9yIHdpdGhvdXQgY3Jvc3MtdmFsaWRhdGlvbikgIArigJxoaWxsLmNsaW1i4oCd77ya5pCc57Si5pyA5LyY5p2D6YeN5bmz5Z2HICAK4oCcY29tcHJlc3PigJ3vvJrnlKjnpZ7nu4/nvZHnu5zlrp7njrDmm7Tlv6vnmoTmgKfog70gIAo=