BACKGROUND

Overall goal is to identify adaptively introgressed loci and determine the major environmental axis associated with adaptive introgression

Here I utilize the regression approach implemented within INTROGRESS to first identify loci exhibiting exceptional patterns of introgression. These will then be subjected to a fold enrichment approach to determine if loci associated with a given environmental variable are more likely to be candidates for adaptive introgression than those associated with other environmental variables. We will focussing primarily on environmental variables that are strongly distinguished between the two hybridizing species, or those driving the niches of the two species (if you have prior information on that).

rr library(data.table) library(introgress)

STEP 1: Prep data and run introgress (For large number of loci this needs to be run on a computing cluster)

Load data

rr Gdata<-fread(../snpData/IlluminaMix_3489/rawData/minor012.txt,sep=\t,header=T,data.table=F) Pops<-read.table(../snpData/PopInd.txt,header=T,sep=\t,stringsAsFactors = FALSE) parentals<-read.table(../snpData/summaryStats_3489/MAF_parentals.txt,header=T,sep=\t)

Due to low overall differentiation between the two species I am using, I will use a very low allele frequency difference cutoff to obtain the input loci. As states in the paper, the approach should not be biased by this. Maximising the number of loci will also help the assesment of fold enrichment.

rr alleleFreq<-cbind(diff=abs(parentals\(LP_pure-parentals\)SWWP_pure),parentals) alleleFreq<-alleleFreq[alleleFreq$diff>0, ]

Gdata<-Gdata[ ,colnames(Gdata)%in%alleleFreq$loci] Gdata<-cbind(Pops,Gdata) Gdata<-Gdata[!(Gdata$Pop==1L), ] Pops<-Pops[!(Pops$Pop==1L), ]

loci<-as.matrix(Gdata[ ,-c(1:5)]) loci[is.na(loci)]<-/NA
loci[loci==1]<-/D
loci[loci==0]<-/A
loci[loci==2]<-/D

Define groups

rr LP<-c(,,,,,,,,,,,2L,3H) SWWP<-c(2L,,2H,2L,1L,,,,,,,,,,,,1H,1L,3,1L,1H,2H) pop98<-read.table(../snpData/summaryStats_3489/bayenvOut/98PopIDs)

Gdata<-cbind.data.frame(Pops,loci)

P1_swwp<-Gdata[Gdata$Pop%in%SWWP, ] P2_lp<-Gdata[Gdata$Pop%in%LP, ] mixed<-Gdata[Gdata\(Pop%in%pop98\)V1, ]

Data prep for running INTROGRESS

rr P1_swwp<-P1_swwp[ ,-c(1:5)] P2_lp<-P2_lp[ ,-c(1:5)] mixed<-cbind(Pop=mixed\(Pop,Ind=mixed\)PopInd,mixed[ ,-c(1:5)])

P1_transpose<-t(P1_swwp) P2_transpose<-t(P2_lp) mixed_transpose<-t(mixed)

Locus<-cbind(locus=alleleFreq$loci,type=rep(,nrow(alleleFreq)))

admixCount<-prepare.data(admix.gen=mixed_transpose,loci.data=Locus,parental1=P1_transpose,parental2=P2_transpose,pop.id=TRUE,ind.id=TRUE,fixed=FALSE,sep.rows = FALSE,sep.columns = FALSE)

Estimate hybrid index and interspecfic het, this is important to determine outlier loci with respect to genome wide ancestry. (takes a long time and so save the output after it is run)

rr HIndex<-est.h(introgress.data = admixCount,loci.data = Locus,ind.touse = NULL,fixed = FALSE) head(HIndex) #write.table(cbind(HIndex,mixed[ ,1:2]),file=98Pops.txt,row.names=F,quote=F,sep=\t)

int.het<-calc.intersp.het(introgress.data=admixCount)

Now, conduct genomic cline analysis using the parametric approach since the SNPs used don’t exhibit fixed differences (Takes a long time and needs to be run on the cluster)

rr Gclines_para<-genomic.clines(introgress.data = admixCount,hi.index = HIndex,loci.data = Locus, method=,sig.test=TRUE,loci.touse=NULL,ind.touse=NULL)

Write output files

rr write.table(Gclines_para\(Summary.data, file=\Summ_para1000.txt\,quote=FALSE, sep=\\t\) write.table(Gclines_para\)Fitted.AA,file=1_para1000.txt,sep=\t,quote=F) write.table(Gclines_para\(Fitted.aa,file=\HomoP2_para1000.txt\,sep=\\t\,quote=F) write.table(Gclines_para\)Neutral.AA,file=1_CIpara1000.txt,sep=\t,quote=F) write.table(Gclines_para\(Neutral.aa,file=\HomoP2_CIpara1000.txt\,sep=\\t\,quote=F) write.table(Gclines_para\)Quantiles,file=_para1000.txt,sep=\t,quote=F)

The parametric approach is prone to false positives, specifically due to the large number of tests done, we will conduct a P.val correction and use only loci that pass p val threshold.

rr Summ<-read.table(../snpData/summaryStats_3489/introgress/all1000reps/Summ_para1000.txt,header=T,sep=\t)

num<-0.05/nrow(Summ) #Bonferonni correction SummBH<-Summ[Summ$P.value<num, ]

Across all individuals we will only retain loci that have passed the Bonferonni correction. Since we have estimates for each individual, we will then classify the loci within an individual as having excess ancestry from LP using the CI estimates.

rr LPci<-fread(../snpData/summaryStats_3489/introgress/allLoci/HomoP2_CIpara1000.txt,sep=\t,data.table=F) LPout<-fread(../snpData/summaryStats_3489/introgress/allLoci/HomoP2_para1000.txt,sep=\t,data.table=F)

LP_upper<-LPci[ ,1:ncol(LPout)]

ExcessLP<-matrix(nrow = nrow(LPout),ncol=950)

for (c in 2:ncol(LPout)){ n<-c-1 for (r in 1:nrow(LPout)){ ExcessLP[r,n]<-ifelse(LPout[r,c]>LP_upper[r,c],1,0) } }

rownames(ExcessLP)<-LPout\(V1 colnames(ExcessLP)<-colnames(LPout)[2:ncol(LPout)] ExcessLP_out<-ExcessLP[rownames(ExcessLP)%in%SummBH\)locus, ]

After assigning introgressed loci as 1 and non-introgressed as 0, we can add them across all individuals to determine what proportion of times it was introgressed across the hybrid zone. We can then use a cutoff (mostly arbitary) to classify a loci as significantly introgressed. This arbitary cutoff for here is set as 0.2 (20% of individiuals) and was assessed through a series of cutoffs being subjected to downstream analysis, all of which gave similar results.

rr ExcessLP_out<-apply(ExcessLP,1,function(X) return(sum(X)/length(X))) ExcessLP_out2<-ExcessLP_out[ExcessLP_out>0.20]

STEP 2: Determining whether the introgressed loci are adaptive.

Here we will utilize the outliers identified through GEA (bayenv in my case) and intersect them with the introgressed loci. Since the number of outliers loci vary across environmental variables, we will utilize a permutation approach to assess the significance of fold enrichment.

Load GEA outliers SNPs

rr clim<-list.files(../snpData/summaryStats_3489/bayenvOut/clim/convergence/,full.names = T,pattern=) Soil<-list.files(../snpData/summaryStats_3489/bayenvOut/Soil/convergence/,full.names = T,pattern=) outliers<-c(clim,Soil)

filesBF<-vector(,length(outliers))

for (f in 1:length(filesBF)){

df<-read.table(outliers[f],header=T,sep=\t) df<-df[ ,1:5] filesBF[[f]]<-df }

files<-list.files(../snpData/summaryStats_3489/bayenvOut/clim/convergence/,pattern=) files<-c(files,list.files(../snpData/summaryStats_3489/bayenvOut/Soil/convergence/,pattern=)) varID<-sapply(strsplit(files,1_3chainConvg),[,2) varID<-sapply(strsplit(varID,.txt),[,1)

names(filesBF)<-varID remove<-c( ,,) outliers<-filesBF[!(names(filesBF)%in%remove)]

Some simple manipulation to reatain only the SNPs used in INTROGRESS

rr outliers2<-lapply(outliers,function(df) df[df\(loci%in%LPout\)V1, ]) #NULL SET BF_gClines<-lapply(outliers2,function(df) return(df[df$loci%in%names(ExcessLP_out2), ])) #SHARED SET BF_gClinesID<-unlist(lapply(BF_gClines,function(df) nrow(df)))

Estimating fold change for each environmental variable

rr Nr<-BF_gClinesID/unlist(lapply(outliers2,function(df) nrow(df))) Dn<-length(ExcessLP_out2)/nrow(LPout)

FC<-Nr/Dn

Function for randomization to obtain null distribution of FC

Takes 4 inputs:

df is a dataframe of bayenv outliers, where nrow = total outliers loci is a character vector of IDs of all snps used in introgress analysis Int total number of loci identified as significantly introgressed from LP in STEP 1 R number of bootstrap replicates to run

rr randFC<-function(df,loci,Int,R){

boot<-NULL for(i in 1:R){

BFrand<-sample(loci,nrow(df),replace = F)
PFrand<-sample(loci,Int,replace = F)
shared<-length(PFrand[PFrand%in%BFrand])

boot[i]<-(shared/length(BFrand))/(length(PFrand)/length(loci))

}

return(boot) }

The actual test

rr ID<-LPout$V1 Int<-length(ExcessLP_out2)

FCrandOut<-lapply(outliers2,function(df) return(randFC(df,ID,Int,10000)))

Check which variables have observed FC outside the 99th percentile of null

rr

for (i in 1:length(FCrandOut)){ Env<-names(FCrandOut)[i] emp<-FCrandOut[[Env]] if (quantile(emp,probs = c(0.999))<=FC[[Env]]){ cat(,Env,significantly enriched,\n) }

}

LS0tDQp0aXRsZTogIklOVFJPR1JFU1MgYW5kIEZvbGQgRW5yaWNobWVudCBhbmFseXNpcyINCm91dHB1dDogaHRtbF9ub3RlYm9vaw0KLS0tDQoNCiNCQUNLR1JPVU5EIw0KIypPdmVyYWxsIGdvYWwgaXMgdG8gaWRlbnRpZnkgYWRhcHRpdmVseSBpbnRyb2dyZXNzZWQgbG9jaSBhbmQgZGV0ZXJtaW5lIHRoZSBtYWpvciBlbnZpcm9ubWVudGFsIGF4aXMgYXNzb2NpYXRlZCB3aXRoIGFkYXB0aXZlIGludHJvZ3Jlc3Npb24qIw0KDQpIZXJlIEkgdXRpbGl6ZSB0aGUgcmVncmVzc2lvbiBhcHByb2FjaCBpbXBsZW1lbnRlZCB3aXRoaW4gYElOVFJPR1JFU1NgIHRvIGZpcnN0IGlkZW50aWZ5IGxvY2kgZXhoaWJpdGluZyBleGNlcHRpb25hbCBwYXR0ZXJucyBvZiBpbnRyb2dyZXNzaW9uLiBUaGVzZSB3aWxsIHRoZW4gYmUgc3ViamVjdGVkIHRvIGEgZm9sZCBlbnJpY2htZW50IGFwcHJvYWNoIHRvIGRldGVybWluZSBpZiBsb2NpIGFzc29jaWF0ZWQgd2l0aCBhIGdpdmVuIGVudmlyb25tZW50YWwgdmFyaWFibGUgYXJlIG1vcmUgbGlrZWx5IHRvIGJlIGNhbmRpZGF0ZXMgZm9yIGFkYXB0aXZlIGludHJvZ3Jlc3Npb24gdGhhbiB0aG9zZSBhc3NvY2lhdGVkIHdpdGggb3RoZXIgZW52aXJvbm1lbnRhbCB2YXJpYWJsZXMuIFdlIHdpbGwgZm9jdXNzaW5nIHByaW1hcmlseSBvbiBlbnZpcm9ubWVudGFsIHZhcmlhYmxlcyB0aGF0IGFyZSBzdHJvbmdseSBkaXN0aW5ndWlzaGVkIGJldHdlZW4gdGhlIHR3byBoeWJyaWRpemluZyBzcGVjaWVzLCBvciB0aG9zZSBkcml2aW5nIHRoZSBuaWNoZXMgb2YgdGhlIHR3byBzcGVjaWVzIChpZiB5b3UgaGF2ZSBwcmlvciBpbmZvcm1hdGlvbiBvbiB0aGF0KS4gDQoNCmBgYHtyfQ0KbGlicmFyeShkYXRhLnRhYmxlKQ0KbGlicmFyeShpbnRyb2dyZXNzKQ0KYGBgDQoNCiNTVEVQIDE6IFByZXAgZGF0YSBhbmQgcnVuIGludHJvZ3Jlc3MgKCpGb3IgbGFyZ2UgbnVtYmVyIG9mIGxvY2kgdGhpcyBuZWVkcyB0byBiZSBydW4gb24gYSBjb21wdXRpbmcgY2x1c3RlciopDQoNCkxvYWQgZGF0YQ0KYGBge3J9DQpHZGF0YTwtZnJlYWQoIi4uL3NucERhdGEvSWxsdW1pbmFNaXhfMzQ4OS9yYXdEYXRhL21pbm9yMDEyLnR4dCIsc2VwPSJcdCIsaGVhZGVyPVQsZGF0YS50YWJsZT1GKQ0KUG9wczwtcmVhZC50YWJsZSgiLi4vc25wRGF0YS9Qb3BJbmQudHh0IixoZWFkZXI9VCxzZXA9Ilx0IixzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UpDQpwYXJlbnRhbHM8LXJlYWQudGFibGUoIi4uL3NucERhdGEvc3VtbWFyeVN0YXRzXzM0ODkvTUFGX3BhcmVudGFscy50eHQiLGhlYWRlcj1ULHNlcD0iXHQiKQ0KYGBgDQoNCkR1ZSB0byBsb3cgb3ZlcmFsbCBkaWZmZXJlbnRpYXRpb24gYmV0d2VlbiB0aGUgdHdvIHNwZWNpZXMgSSBhbSB1c2luZywgSSB3aWxsIHVzZSBhIHZlcnkgbG93IGFsbGVsZSBmcmVxdWVuY3kgZGlmZmVyZW5jZSBjdXRvZmYgdG8gb2J0YWluIHRoZSBpbnB1dCBsb2NpLiBBcyBzdGF0ZXMgaW4gdGhlIHBhcGVyLCB0aGUgYXBwcm9hY2ggc2hvdWxkIG5vdCBiZSBiaWFzZWQgYnkgdGhpcy4gTWF4aW1pc2luZyB0aGUgbnVtYmVyIG9mIGxvY2kgd2lsbCBhbHNvIGhlbHAgdGhlIGFzc2VzbWVudCBvZiBmb2xkIGVucmljaG1lbnQuDQpgYGB7cn0NCmFsbGVsZUZyZXE8LWNiaW5kKGRpZmY9YWJzKHBhcmVudGFscyRMUF9wdXJlLXBhcmVudGFscyRTV1dQX3B1cmUpLHBhcmVudGFscykNCmFsbGVsZUZyZXE8LWFsbGVsZUZyZXFbYWxsZWxlRnJlcSRkaWZmPjAsIF0gDQoNCkdkYXRhPC1HZGF0YVsgLGNvbG5hbWVzKEdkYXRhKSVpbiVhbGxlbGVGcmVxJGxvY2ldDQpHZGF0YTwtY2JpbmQoUG9wcyxHZGF0YSkNCkdkYXRhPC1HZGF0YVshKEdkYXRhJFBvcD09Ik1FU0MxTCIpLCBdDQpQb3BzPC1Qb3BzWyEoUG9wcyRQb3A9PSJNRVNDMUwiKSwgXQ0KDQpsb2NpPC1hcy5tYXRyaXgoR2RhdGFbICwtYygxOjUpXSkNCmxvY2lbaXMubmEobG9jaSldPC0iTkEvTkEiDQpsb2NpW2xvY2k9PTFdPC0iQS9EIg0KbG9jaVtsb2NpPT0wXTwtIkEvQSINCmxvY2lbbG9jaT09Ml08LSJEL0QiDQpgYGANCg0KRGVmaW5lIGdyb3Vwcw0KYGBge3J9DQpMUDwtYygiQkNLIiwiQlUiLCJDSCIsIkNQIiwiSE8iLCJKRU4iLCJKViIsIkxQIiwiTUVMIiwiTVNMIiwiVkMiLCJDT0xPMkwiLCJDT0xPM0giKQ0KU1dXUDwtYygiREFWMkwiLCJBTFQiLCJIVUEySCIsIkhVQTJMIiwiU0FOUklUMUwiLCJCQUwiLCJCQVMiLCJDSEkiLCJFUEEiLCJFUk0iLCJHVUEiLCJHVUkiLCJTQU0iLCJUT00iLCJWRVIiLCJZQVEiLCJDSEkxSCIsIkNISTFMIiwiQ0hJMyIsIkRBVjFMIiwiREFWMUgiLCJEQVYySCIpDQpwb3A5ODwtcmVhZC50YWJsZSgiLi4vc25wRGF0YS9zdW1tYXJ5U3RhdHNfMzQ4OS9iYXllbnZPdXQvOThQb3BJRHMiKQ0KDQpHZGF0YTwtY2JpbmQuZGF0YS5mcmFtZShQb3BzLGxvY2kpDQoNClAxX3N3d3A8LUdkYXRhW0dkYXRhJFBvcCVpbiVTV1dQLCBdDQpQMl9scDwtR2RhdGFbR2RhdGEkUG9wJWluJUxQLCBdDQptaXhlZDwtR2RhdGFbR2RhdGEkUG9wJWluJXBvcDk4JFYxLCBdDQpgYGANCg0KRGF0YSBwcmVwIGZvciBydW5uaW5nIElOVFJPR1JFU1MNCmBgYHtyfQ0KUDFfc3d3cDwtUDFfc3d3cFsgLC1jKDE6NSldDQpQMl9scDwtUDJfbHBbICwtYygxOjUpXQ0KbWl4ZWQ8LWNiaW5kKFBvcD1taXhlZCRQb3AsSW5kPW1peGVkJFBvcEluZCxtaXhlZFsgLC1jKDE6NSldKQ0KDQpQMV90cmFuc3Bvc2U8LXQoUDFfc3d3cCkNClAyX3RyYW5zcG9zZTwtdChQMl9scCkNCm1peGVkX3RyYW5zcG9zZTwtdChtaXhlZCkNCg0KDQpMb2N1czwtY2JpbmQobG9jdXM9YWxsZWxlRnJlcSRsb2NpLHR5cGU9cmVwKCJDIixucm93KGFsbGVsZUZyZXEpKSkNCg0KYWRtaXhDb3VudDwtcHJlcGFyZS5kYXRhKGFkbWl4Lmdlbj1taXhlZF90cmFuc3Bvc2UsbG9jaS5kYXRhPUxvY3VzLHBhcmVudGFsMT1QMV90cmFuc3Bvc2UscGFyZW50YWwyPVAyX3RyYW5zcG9zZSxwb3AuaWQ9VFJVRSxpbmQuaWQ9VFJVRSxmaXhlZD1GQUxTRSxzZXAucm93cyA9IEZBTFNFLHNlcC5jb2x1bW5zID0gRkFMU0UpDQpgYGANCg0KRXN0aW1hdGUgaHlicmlkIGluZGV4IGFuZCBpbnRlcnNwZWNmaWMgaGV0LCB0aGlzIGlzIGltcG9ydGFudCB0byBkZXRlcm1pbmUgb3V0bGllciBsb2NpIHdpdGggcmVzcGVjdCB0byBnZW5vbWUgd2lkZSBhbmNlc3RyeS4gKCp0YWtlcyBhIGxvbmcgdGltZSBhbmQgc28gc2F2ZSB0aGUgb3V0cHV0IGFmdGVyIGl0IGlzIHJ1biopDQpgYGB7cn0NCkhJbmRleDwtZXN0LmgoaW50cm9ncmVzcy5kYXRhID0gYWRtaXhDb3VudCxsb2NpLmRhdGEgPSBMb2N1cyxpbmQudG91c2UgPSBOVUxMLGZpeGVkID0gRkFMU0UpDQpoZWFkKEhJbmRleCkNCiN3cml0ZS50YWJsZShjYmluZChISW5kZXgsbWl4ZWRbICwxOjJdKSxmaWxlPSJIeWJyaWRJbmRleDk4UG9wcy50eHQiLHJvdy5uYW1lcz1GLHF1b3RlPUYsc2VwPSJcdCIpDQoNCmludC5oZXQ8LWNhbGMuaW50ZXJzcC5oZXQoaW50cm9ncmVzcy5kYXRhPWFkbWl4Q291bnQpDQpgYGANCg0KTm93LCBjb25kdWN0IGdlbm9taWMgY2xpbmUgYW5hbHlzaXMgdXNpbmcgdGhlIHBhcmFtZXRyaWMgYXBwcm9hY2ggc2luY2UgdGhlIFNOUHMgdXNlZCBkb24ndCBleGhpYml0IGZpeGVkIGRpZmZlcmVuY2VzICgqVGFrZXMgYSBsb25nIHRpbWUgYW5kIG5lZWRzIHRvIGJlIHJ1biBvbiB0aGUgY2x1c3RlciopDQoNCmBgYHtyfQ0KR2NsaW5lc19wYXJhPC1nZW5vbWljLmNsaW5lcyhpbnRyb2dyZXNzLmRhdGEgPSBhZG1peENvdW50LGhpLmluZGV4ID0gSEluZGV4LGxvY2kuZGF0YSA9IExvY3VzLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1ldGhvZD0icGFyYW1ldHJpYyIsc2lnLnRlc3Q9VFJVRSxsb2NpLnRvdXNlPU5VTEwsaW5kLnRvdXNlPU5VTEwpDQpgYGANCg0KV3JpdGUgb3V0cHV0IGZpbGVzIA0KYGBge3J9DQp3cml0ZS50YWJsZShHY2xpbmVzX3BhcmEkU3VtbWFyeS5kYXRhLCBmaWxlPSJTdW1tX3BhcmExMDAwLnR4dCIscXVvdGU9RkFMU0UsIHNlcD0iXHQiKQ0Kd3JpdGUudGFibGUoR2NsaW5lc19wYXJhJEZpdHRlZC5BQSxmaWxlPSJIb21vUDFfcGFyYTEwMDAudHh0IixzZXA9Ilx0IixxdW90ZT1GKQ0Kd3JpdGUudGFibGUoR2NsaW5lc19wYXJhJEZpdHRlZC5hYSxmaWxlPSJIb21vUDJfcGFyYTEwMDAudHh0IixzZXA9Ilx0IixxdW90ZT1GKQ0Kd3JpdGUudGFibGUoR2NsaW5lc19wYXJhJE5ldXRyYWwuQUEsZmlsZT0iSG9tb1AxX0NJcGFyYTEwMDAudHh0IixzZXA9Ilx0IixxdW90ZT1GKQ0Kd3JpdGUudGFibGUoR2NsaW5lc19wYXJhJE5ldXRyYWwuYWEsZmlsZT0iSG9tb1AyX0NJcGFyYTEwMDAudHh0IixzZXA9Ilx0IixxdW90ZT1GKQ0Kd3JpdGUudGFibGUoR2NsaW5lc19wYXJhJFF1YW50aWxlcyxmaWxlPSJRdWFudGlsZV9wYXJhMTAwMC50eHQiLHNlcD0iXHQiLHF1b3RlPUYpDQpgYGANCg0KVGhlIHBhcmFtZXRyaWMgYXBwcm9hY2ggaXMgcHJvbmUgdG8gZmFsc2UgcG9zaXRpdmVzLCBzcGVjaWZpY2FsbHkgZHVlIHRvIHRoZSBsYXJnZSBudW1iZXIgb2YgdGVzdHMgZG9uZSwgd2Ugd2lsbCBjb25kdWN0IGEgUC52YWwgY29ycmVjdGlvbiBhbmQgdXNlIG9ubHkgbG9jaSB0aGF0IHBhc3MgcCB2YWwgdGhyZXNob2xkLg0KYGBge3J9DQpTdW1tPC1yZWFkLnRhYmxlKCIuLi9zbnBEYXRhL3N1bW1hcnlTdGF0c18zNDg5L2ludHJvZ3Jlc3MvYWxsMTAwMHJlcHMvU3VtbV9wYXJhMTAwMC50eHQiLGhlYWRlcj1ULHNlcD0iXHQiKQ0KDQpudW08LTAuMDUvbnJvdyhTdW1tKSAjQm9uZmVyb25uaSBjb3JyZWN0aW9uDQpTdW1tQkg8LVN1bW1bU3VtbSRQLnZhbHVlPG51bSwgXQ0KYGBgDQoNCkFjcm9zcyBhbGwgaW5kaXZpZHVhbHMgd2Ugd2lsbCBvbmx5IHJldGFpbiBsb2NpIHRoYXQgaGF2ZSBwYXNzZWQgdGhlIEJvbmZlcm9ubmkgY29ycmVjdGlvbi4gU2luY2Ugd2UgaGF2ZSBlc3RpbWF0ZXMgZm9yIGVhY2ggaW5kaXZpZHVhbCwgd2Ugd2lsbCB0aGVuIGNsYXNzaWZ5IHRoZSBsb2NpIHdpdGhpbiBhbiBpbmRpdmlkdWFsIGFzIGhhdmluZyBleGNlc3MgYW5jZXN0cnkgZnJvbSBMUCB1c2luZyB0aGUgQ0kgZXN0aW1hdGVzLg0KYGBge3J9DQpMUGNpPC1mcmVhZCgiLi4vc25wRGF0YS9zdW1tYXJ5U3RhdHNfMzQ4OS9pbnRyb2dyZXNzL2FsbExvY2kvSG9tb1AyX0NJcGFyYTEwMDAudHh0IixzZXA9Ilx0IixkYXRhLnRhYmxlPUYpDQpMUG91dDwtZnJlYWQoIi4uL3NucERhdGEvc3VtbWFyeVN0YXRzXzM0ODkvaW50cm9ncmVzcy9hbGxMb2NpL0hvbW9QMl9wYXJhMTAwMC50eHQiLHNlcD0iXHQiLGRhdGEudGFibGU9RikNCg0KTFBfdXBwZXI8LUxQY2lbICwxOm5jb2woTFBvdXQpXQ0KDQoNCkV4Y2Vzc0xQPC1tYXRyaXgobnJvdyA9IG5yb3coTFBvdXQpLG5jb2w9OTUwKQ0KDQpmb3IgKGMgaW4gMjpuY29sKExQb3V0KSl7DQogIG48LWMtMQ0KICBmb3IgKHIgaW4gMTpucm93KExQb3V0KSl7DQogICAgRXhjZXNzTFBbcixuXTwtaWZlbHNlKExQb3V0W3IsY10+TFBfdXBwZXJbcixjXSwxLDApDQogIH0NCn0NCg0Kcm93bmFtZXMoRXhjZXNzTFApPC1MUG91dCRWMQ0KY29sbmFtZXMoRXhjZXNzTFApPC1jb2xuYW1lcyhMUG91dClbMjpuY29sKExQb3V0KV0NCkV4Y2Vzc0xQX291dDwtRXhjZXNzTFBbcm93bmFtZXMoRXhjZXNzTFApJWluJVN1bW1CSCRsb2N1cywgXQ0KDQpgYGANCg0KQWZ0ZXIgYXNzaWduaW5nIGludHJvZ3Jlc3NlZCBsb2NpIGFzIDEgYW5kIG5vbi1pbnRyb2dyZXNzZWQgYXMgMCwgd2UgY2FuIGFkZCB0aGVtIGFjcm9zcyBhbGwgaW5kaXZpZHVhbHMgdG8gZGV0ZXJtaW5lIHdoYXQgcHJvcG9ydGlvbiBvZiB0aW1lcyBpdCB3YXMgaW50cm9ncmVzc2VkIGFjcm9zcyB0aGUgaHlicmlkIHpvbmUuIFdlIGNhbiB0aGVuIHVzZSBhIGN1dG9mZiAobW9zdGx5IGFyYml0YXJ5KSB0byBjbGFzc2lmeSBhIGxvY2kgYXMgc2lnbmlmaWNhbnRseSBpbnRyb2dyZXNzZWQuIFRoaXMgYXJiaXRhcnkgY3V0b2ZmIGZvciBoZXJlIGlzIHNldCBhcyAwLjIgKDIwJSBvZiBpbmRpdmlkaXVhbHMpIGFuZCB3YXMgYXNzZXNzZWQgdGhyb3VnaCBhIHNlcmllcyBvZiBjdXRvZmZzIGJlaW5nIHN1YmplY3RlZCB0byBkb3duc3RyZWFtIGFuYWx5c2lzLCBhbGwgb2Ygd2hpY2ggZ2F2ZSBzaW1pbGFyIHJlc3VsdHMuDQpgYGB7cn0NCkV4Y2Vzc0xQX291dDwtYXBwbHkoRXhjZXNzTFAsMSxmdW5jdGlvbihYKSByZXR1cm4oc3VtKFgpL2xlbmd0aChYKSkpDQpFeGNlc3NMUF9vdXQyPC1FeGNlc3NMUF9vdXRbRXhjZXNzTFBfb3V0PjAuMjBdDQpgYGANCg0KI1NURVAgMjogRGV0ZXJtaW5pbmcgd2hldGhlciB0aGUgaW50cm9ncmVzc2VkIGxvY2kgYXJlIGFkYXB0aXZlLg0KSGVyZSB3ZSB3aWxsIHV0aWxpemUgdGhlIG91dGxpZXJzIGlkZW50aWZpZWQgdGhyb3VnaCBHRUEgKGJheWVudiBpbiBteSBjYXNlKSBhbmQgaW50ZXJzZWN0IHRoZW0gd2l0aCB0aGUgaW50cm9ncmVzc2VkIGxvY2kuIFNpbmNlIHRoZSBudW1iZXIgb2Ygb3V0bGllcnMgbG9jaSB2YXJ5IGFjcm9zcyBlbnZpcm9ubWVudGFsIHZhcmlhYmxlcywgd2Ugd2lsbCB1dGlsaXplIGEgcGVybXV0YXRpb24gYXBwcm9hY2ggdG8gYXNzZXNzIHRoZSBzaWduaWZpY2FuY2Ugb2YgZm9sZCBlbnJpY2htZW50Lg0KDQpMb2FkIEdFQSBvdXRsaWVycyBTTlBzDQpgYGB7cn0NCmNsaW08LWxpc3QuZmlsZXMoIi4uL3NucERhdGEvc3VtbWFyeVN0YXRzXzM0ODkvYmF5ZW52T3V0L2NsaW0vY29udmVyZ2VuY2UvIixmdWxsLm5hbWVzID0gVCxwYXR0ZXJuPSJPdmVybGFwIikNClNvaWw8LWxpc3QuZmlsZXMoIi4uL3NucERhdGEvc3VtbWFyeVN0YXRzXzM0ODkvYmF5ZW52T3V0L1NvaWwvY29udmVyZ2VuY2UvIixmdWxsLm5hbWVzID0gVCxwYXR0ZXJuPSJPdmVybGFwIikNCm91dGxpZXJzPC1jKGNsaW0sU29pbCkNCg0KDQpmaWxlc0JGPC12ZWN0b3IoImxpc3QiLGxlbmd0aChvdXRsaWVycykpDQoNCmZvciAoZiBpbiAxOmxlbmd0aChmaWxlc0JGKSl7DQogIA0KICBkZjwtcmVhZC50YWJsZShvdXRsaWVyc1tmXSxoZWFkZXI9VCxzZXA9Ilx0IikNCiAgZGY8LWRmWyAsMTo1XQ0KICBmaWxlc0JGW1tmXV08LWRmDQp9DQoNCmZpbGVzPC1saXN0LmZpbGVzKCIuLi9zbnBEYXRhL3N1bW1hcnlTdGF0c18zNDg5L2JheWVudk91dC9jbGltL2NvbnZlcmdlbmNlLyIscGF0dGVybj0iT3ZlcmxhcCIpDQpmaWxlczwtYyhmaWxlcyxsaXN0LmZpbGVzKCIuLi9zbnBEYXRhL3N1bW1hcnlTdGF0c18zNDg5L2JheWVudk91dC9Tb2lsL2NvbnZlcmdlbmNlLyIscGF0dGVybj0iT3ZlcmxhcCIpKQ0KdmFySUQ8LXNhcHBseShzdHJzcGxpdChmaWxlcywiT3ZlcmxhcDFfM2NoYWluQ29udmciKSwiWyIsMikNCnZhcklEPC1zYXBwbHkoc3Ryc3BsaXQodmFySUQsIi50eHQiKSwiWyIsMSkNCg0KDQpuYW1lcyhmaWxlc0JGKTwtdmFySUQNCnJlbW92ZTwtYygiRWxldmF0aW9uIiAsIkxhdGl0dWRlIiwiTG9uZ2l0dWRlIikNCm91dGxpZXJzPC1maWxlc0JGWyEobmFtZXMoZmlsZXNCRiklaW4lcmVtb3ZlKV0NCmBgYA0KDQpTb21lIHNpbXBsZSBtYW5pcHVsYXRpb24gdG8gcmVhdGFpbiBvbmx5IHRoZSBTTlBzIHVzZWQgaW4gYElOVFJPR1JFU1NgDQpgYGB7cn0NCm91dGxpZXJzMjwtbGFwcGx5KG91dGxpZXJzLGZ1bmN0aW9uKGRmKSBkZltkZiRsb2NpJWluJUxQb3V0JFYxLCBdKSAjTlVMTCBTRVQNCkJGX2dDbGluZXM8LWxhcHBseShvdXRsaWVyczIsZnVuY3Rpb24oZGYpIHJldHVybihkZltkZiRsb2NpJWluJW5hbWVzKEV4Y2Vzc0xQX291dDIpLCBdKSkgI1NIQVJFRCBTRVQNCkJGX2dDbGluZXNJRDwtdW5saXN0KGxhcHBseShCRl9nQ2xpbmVzLGZ1bmN0aW9uKGRmKSBucm93KGRmKSkpDQpgYGANCg0KRXN0aW1hdGluZyBmb2xkIGNoYW5nZSBmb3IgZWFjaCBlbnZpcm9ubWVudGFsIHZhcmlhYmxlDQpgYGB7cn0NCk5yPC1CRl9nQ2xpbmVzSUQvdW5saXN0KGxhcHBseShvdXRsaWVyczIsZnVuY3Rpb24oZGYpIG5yb3coZGYpKSkNCkRuPC1sZW5ndGgoRXhjZXNzTFBfb3V0MikvbnJvdyhMUG91dCkNCg0KRkM8LU5yL0RuDQpgYGANCg0KKkZ1bmN0aW9uIGZvciByYW5kb21pemF0aW9uIHRvIG9idGFpbiBudWxsIGRpc3RyaWJ1dGlvbiBvZiBGQyoNCg0KVGFrZXMgNCBpbnB1dHM6DQoNCmBkZmAgaXMgYSBkYXRhZnJhbWUgb2YgYmF5ZW52IG91dGxpZXJzLCB3aGVyZSBucm93ID0gdG90YWwgb3V0bGllcnMNCmBsb2NpYCBpcyBhIGNoYXJhY3RlciB2ZWN0b3Igb2YgSURzIG9mIGFsbCBzbnBzIHVzZWQgaW4gaW50cm9ncmVzcyBhbmFseXNpcw0KYEludGAgdG90YWwgbnVtYmVyIG9mIGxvY2kgaWRlbnRpZmllZCBhcyBzaWduaWZpY2FudGx5IGludHJvZ3Jlc3NlZCBmcm9tIExQIGluIFNURVAgMQ0KYFJgIG51bWJlciBvZiBib290c3RyYXAgcmVwbGljYXRlcyB0byBydW4NCg0KYGBge3J9DQpyYW5kRkM8LWZ1bmN0aW9uKGRmLGxvY2ksSW50LFIpew0KDQogIGJvb3Q8LU5VTEwNCiAgZm9yKGkgaW4gMTpSKXsNCiAgICANCiAgICBCRnJhbmQ8LXNhbXBsZShsb2NpLG5yb3coZGYpLHJlcGxhY2UgPSBGKQ0KICAgIFBGcmFuZDwtc2FtcGxlKGxvY2ksSW50LHJlcGxhY2UgPSBGKQ0KICAgIHNoYXJlZDwtbGVuZ3RoKFBGcmFuZFtQRnJhbmQlaW4lQkZyYW5kXSkNCiAgICANCiAgICBib290W2ldPC0oc2hhcmVkL2xlbmd0aChCRnJhbmQpKS8obGVuZ3RoKFBGcmFuZCkvbGVuZ3RoKGxvY2kpKQ0KICB9DQoNCiAgcmV0dXJuKGJvb3QpDQp9DQoNCmBgYA0KDQpUaGUgYWN0dWFsIHRlc3QNCmBgYHtyfQ0KSUQ8LUxQb3V0JFYxDQpJbnQ8LWxlbmd0aChFeGNlc3NMUF9vdXQyKQ0KDQoNCkZDcmFuZE91dDwtbGFwcGx5KG91dGxpZXJzMixmdW5jdGlvbihkZikgcmV0dXJuKHJhbmRGQyhkZixJRCxJbnQsMTAwMDApKSkNCmBgYA0KDQpDaGVjayB3aGljaCB2YXJpYWJsZXMgaGF2ZSBvYnNlcnZlZCBGQyBvdXRzaWRlIHRoZSA5OXRoIHBlcmNlbnRpbGUgb2YgbnVsbA0KYGBge3J9DQoNCmZvciAoaSBpbiAxOmxlbmd0aChGQ3JhbmRPdXQpKXsNCiAgRW52PC1uYW1lcyhGQ3JhbmRPdXQpW2ldDQogIGVtcDwtRkNyYW5kT3V0W1tFbnZdXQ0KICBpZiAocXVhbnRpbGUoZW1wLHByb2JzID0gYygwLjk5OSkpPD1GQ1tbRW52XV0pew0KICAgIGNhdCgidmFyaWFibGUiLEVudiwiaXMgc2lnbmlmaWNhbnRseSBlbnJpY2hlZCIsIlxuIikNCiAgfQ0KICANCn0NCg0KYGBgDQoNCg==