Datasets

Coronary Artery PGS

First, we need to download the coronary artery polygenic score data from https://www.pgscatalog.org/score/PGS000010/. This particular PGS is composed of 27 SNPs identified from a GWAS of 86,995 European individuals.

# download the coronary artery PGS data
URL = "https://ftp.ebi.ac.uk/pub/databases/spot/pgs/scores/PGS000010/ScoringFiles/PGS000010.txt.gz"
download.file(URL, destfile="PGS000010.txt.gz", quiet=T)
pgs = fread("PGS000010.txt.gz") %>% mutate(effect_weight=round(effect_weight, 4))
datatable(pgs, rownames=F) 

23 and Me Examples

Then, we will load in two sample genotypes from 23 and Me and merge them with the PGS data. Additionally, we need to calculate the allele counts (AC) by adding up the number of PGS effect alleles in each genotype. You can see below that the columns geno1, geno2, AC1, and AC2 have been added to the PGS data. You can also see that 23 and Me information is missing for two of the SNPs (rs3825807 and rs9411489).

# read in the 23 and Me example data
ex = read.csv("23andMeExamples.csv")

# merge the pgs and example data
pgs2 = merge(pgs, ex, by="rsID")

# count the number of effect alleles for genotype 1
geno1 = sapply(strsplit(pgs2$geno1, "/"), function(x) x[1:2])
pgs2$AC1 = (geno1[1,]==pgs2$effect_allele) + (geno1[2,]==pgs2$effect_allele)

# count the number of effect alleles for genotype 2
geno2 = sapply(strsplit(pgs2$geno2, "/"), function(x) x[1:2])
pgs2$AC2 = (geno2[1,]==pgs2$effect_allele) + (geno2[2,]==pgs2$effect_allele)
datatable(pgs2, rownames=F) 

1000 Genomes

Now, we need to load in the individual-level 1000 Genomes data. This data will be used to calculate distributions of polygenic scores that we will compare the 23 and Me examples to. A subset of the data is shown below, where the first five columns provide information related to each SNP and the next four columns beginning with HG correspond to individual genotypes. The entire dataset contains genotypes for 2,504 individuals. The ID column below corresponds to the rsID column from the PGS data above.

# read in the 1000 genomes data
data = read.table("1000G.phase3.PRS.vcf", sep="\t", header=T)
samples = colnames(data)[10:ncol(data)] # subset the sample names
datatable(data[,c(1:5, 10:13)], rownames=F)

Notice how the genotypes in the 1000 Genomes data are designated by 0’s and 1’s instead of by alleles (A, T, C, or G) as in the 23 and Me data. Here, a 0 represents the reference allele (REF) and a 1 represents the alternate allele (ALT). So, looking at the first SNP, the genotype 0|0 for the first individual (HG00096) corresponds to T/T, whereas the genotype 1|0 for the second individual (HG00097) corresponds to C/T. These genotypes can easily be converted to alternate allele counts by just adding up the 0’s and 1’s for each genotype as seen below.

The allele frequencies for the European (EUR_AF) and African (AFR_AF) populations were also added to the data. These frequencies describe how often the alternate allele is seen in the respective population. Notice how for some SNPs, the two frequencies are very similar, but for others, such as rs17465637, they can be very different. Also notice that the 1000 Genomes data only has 26 rows while the PGS data has 27. One SNP (rs9411489) is missing from the 1000 Genomes data.

# convert the genotypes into allele counts
data2 = geno(data)

# extract the European allele frequencies from the INFO column
data2$EUR_AF = sapply(strsplit(data2$INFO, ";"), function(x) x[9])
data2$EUR_AF = sapply(strsplit(data2$EUR_AF, "="), function(x) x[2])

# extract the African allele frequencies from the INFO column
data2$AFR_AF = sapply(strsplit(data2$INFO, ";"), function(x) x[8])
data2$AFR_AF = sapply(strsplit(data2$AFR_AF, "="), function(x) x[2])

data2 = data2 %>% relocate(EUR_AF:AFR_AF, .after=ALT)
datatable(data2[,c(1:7, 12:15)], rownames=F)

Data Cleaning

Merge Data

Next, the 1000 Genomes data needs to be merged with the PGS and 23 and Me data. Remember, the 1000 Genomes allele counts refer to the number of alternate alleles, so we want the effect_allele to correspond with the ALT allele. We also want the allelefrequency_effect and EUR_AF columns to be similar since the PGS data is derived from individuals with European ancestry. But, looking at the merged data below, that is not the case for some of the SNPs. However, we can switch the reference and alternate alleles for a particular SNP as long as we also update the allele counts (two minus the alternate allele count) and allele frequencies (one minus the alternate allele frequency).

You may also notice that the effect allele for one SNP (rs3825807) doesn’t match either the reference or alternate allele from 1000 Genomes. Additionally, the effect allele for another SNP (rs216172) does correspond with the alternate allele but the allelefrequency_effect and EUR_AF columns don’t match. For these two SNPs, we also need to do a strand flip. Remember that DNA is double-stranded and that A alleles on one strand pair with T alleles on the other and vice versa while C alleles on one strand pair with G alleles on the other and vice versa. We know that the 1000 Genomes and 23 and Me data refer to the positive strand, but we are unsure which strand the effect alleles from the PGS data are on. So, we will flip the strands for the two previously mentioned SNPs and assume that the effect alleles of the remaining SNPs are on the positive strand. However, this would have to be verified in real clinical or research applications.

# merge the 1000 genomes data with the pgs data
merged = left_join(pgs2, data2, by=c("rsID"="ID")) %>% 
  select(-QUAL, -FILTER, -INFO, -FORMAT) %>%
  relocate(CHROM:ALT, .after=rsID) %>%
  relocate(EUR_AF:AFR_AF, .after=allelefrequency_effect) %>%
  mutate(EUR_AF=as.numeric(EUR_AF), AFR_AF=as.numeric(AFR_AF))
datatable(merged[,1:20], rownames=F)

Looking at the updated data below, you can see that the effect_allele and ALT alleles now match and that the allelefrequency_effect and EUR_AF columns are more similar.

# update the 1000 genomes data based on the effect alleles in the pgs data
merged2 = update(merged)
datatable(merged2[,1:20], rownames=F)

Subset Ancestry

Now, we can look at the metadata for the 1000 Genomes data, which shows information about each individual. We can use this information to subset the merged data by ancestry population. In this exercise, we will calculate and compare the polygenic scores for the 403 Non-Finnish European individuals (EUR Superpopulation.code, excluding Finnish Population.name) with the scores for the 661 African individuals (AFR Superpopulation.code).

# read in the 1000 genomes metadata for the samples
info = read.table("1000G.phase3.metadata.tsv", sep="\t", header=T) %>% filter(Sample.name %in% samples) %>% arrange(Sample.name)
datatable(info[,-9], rownames=F)

# subset the sample names by population
NFE.samples = info %>% filter(Superpopulation.code=="EUR", Population.code!="FIN") %>% select(Sample.name)
AFR.samples = info %>% filter(Superpopulation.code=="AFR") %>% select(Sample.name)

# subset the 1000 genomes data by population
NFE = merged2 %>% select(rsID:AC2, NFE.samples$Sample.name) %>% select(-AFR_AF)
AFR = merged2 %>% select(rsID:AC2, AFR.samples$Sample.name) %>% select(-EUR_AF)

PGS

Calculations

To calculate the polygenic scores for the 1000 Genomes populations, we will multiply the effect_weight column by the allele counts in each HG column of the NFE and AFR data. We will also calculate the scores for the 23 and Me examples by multiplying the effect_weight column by the AC1 and AC2 columns. So, we will have 403 scores for the NFE population and 661 scores for the AFR population as well as two scores for the example data.

Note, prior to calculating the scores, we will remove the two SNPs missing from the 23 and Me data (rs3825807 and rs9411489) so all of the scores will be based on the same 25 SNPs (instead of the original 27 in the PGS data).

# remove NA values
NFE2 = NFE %>% filter(geno1!="No Data")
AFR2 = AFR %>% filter(geno1!="No Data")

# calculate the pgs for each population
NFE.pgs = NFE2$effect_weight %*% data.matrix(NFE2[,16:ncol(NFE2)])
AFR.pgs = AFR2$effect_weight %*% data.matrix(AFR2[,16:ncol(AFR2)])

# calculate the pgs for the examples
ex.pgs1 = NFE2$effect_weight %*% NFE2$AC1
ex.pgs2 = NFE2$effect_weight %*% NFE2$AC2

Plots

We can then plot the distributions of the scores from the two populations as well as the two 23 and Me example scores.

# re-format the data for plotting
pgs.data = data.frame(Population=c(rep("NFE", length(NFE.pgs)), rep("AFR", length(AFR.pgs))),
                      Score=c(NFE.pgs, AFR.pgs))
ex.data = data.frame(Example=c("1", "2"), Score=c(ex.pgs1, ex.pgs2))

# plot the pgs distributions
ggplot() +
  geom_density(data=pgs.data, aes(x=Score, fill=Population), alpha=0.75) +
  geom_vline(data=ex.data, aes(xintercept=Score, linetype=Example)) +
  scale_linetype_manual(values=c("1"="dashed", "2"="dotted")) +
  theme_bw() + ylab("Density")

The plot shows that the distributions look very different for the two populations, with the average score for the NFE population 3.09 and the average score for the AFR population 2.41. The plot also shows that the score for Example 2 is about one more than the score for Example 1. Comparing the example scores to the population distributions, Example 1 would have a low score compared to the NFE population, but a pretty high score compared to the AFR population. That is why it is SO important to account for ancestry when interpreting polygenic scores. Since the PGS data is derived from individuals with European ancestry, the example scores should only be compared to a European population in practice.

Percentages

We can also calculate the exact percentiles of the example scores relative to each population.

# estimate the distributions based on the data (empirical cdfs)
NFE.cdf = ecdf(NFE.pgs)
AFR.cdf = ecdf(AFR.pgs)

# calculate the percentiles for the first example
ex1.NFE = NFE.cdf(ex.pgs1)
ex1.AFR = AFR.cdf(ex.pgs1)

# calculate the percentiles for the second example
ex2.NFE = NFE.cdf(ex.pgs2)
ex2.AFR = AFR.cdf(ex.pgs2)

Based on the distributions shown in the plot above, the score for Example 1 is in the 29.03 percentile (low risk) for the NFE population, but is in the 91.53 percentile (high risk) for the AFR population. Additionally, the score for Example 2 is in the 95.53 percentile (high risk) for the NFE population, but is in the 99.85 percentile (very high risk) for the AFR population.

Note that the individuals from the 1000 Genomes data are considered “healthy”, so the distributions and percentages might be different if we instead compared the 23 and Me examples to a population of people with coronary artery disease or a family history of coronary artery disease.

LS0tDQp0aXRsZTogIlBHUyBMZXNzb24iDQpvdXRwdXQ6IA0KICBodG1sX25vdGVib29rOg0KICAgIHRvYzogeWVzDQogICAgdG9jX2RlcHRoOiAnMycNCiAgICBjb2RlX2ZvbGRpbmc6IGhpZGUNCiAgICB0b2NfZmxvYXQ6IHllcw0KLS0tDQoNCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFfQ0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGVjaG8gPSBUUlVFKQ0KDQojaW5zdGFsbC5wYWNrYWdlcyhjKCJkYXRhLnRhYmxlIiwgIkRUIiwgIlIudXRpbHMiLCAiZHBseXIiLCAiZ2dwbG90MiIpKQ0KDQpsaWJyYXJ5KGRhdGEudGFibGUpICMgZnJlYWQNCmxpYnJhcnkoRFQpICMgZGF0YXRhYmxlDQpsaWJyYXJ5KGRwbHlyKSAjIGxlZnRfam9pbiwgc2VsZWN0LCByZWxvY2F0ZSwgbXV0YXRlDQpsaWJyYXJ5KGdncGxvdDIpICMgZ2dwbG90LCBnZW9tX2RlbnNpdHksIGdlb21fdmxpbmUNCg0Kc291cmNlKCJGdW5jdGlvbnMuUiIpDQpgYGANCg0KIyMgRGF0YXNldHMNCg0KIyMjIENvcm9uYXJ5IEFydGVyeSBQR1MNCg0KRmlyc3QsIHdlIG5lZWQgdG8gZG93bmxvYWQgdGhlIGNvcm9uYXJ5IGFydGVyeSBwb2x5Z2VuaWMgc2NvcmUgZGF0YSBmcm9tIGh0dHBzOi8vd3d3LnBnc2NhdGFsb2cub3JnL3Njb3JlL1BHUzAwMDAxMC8uIFRoaXMgcGFydGljdWxhciBQR1MgaXMgY29tcG9zZWQgb2YgMjcgU05QcyBpZGVudGlmaWVkIGZyb20gYSBHV0FTIG9mIDg2LDk5NSBFdXJvcGVhbiBpbmRpdmlkdWFscy4NCg0KYGBge3IgcGdzfQ0KIyBkb3dubG9hZCB0aGUgY29yb25hcnkgYXJ0ZXJ5IFBHUyBkYXRhDQpVUkwgPSAiaHR0cHM6Ly9mdHAuZWJpLmFjLnVrL3B1Yi9kYXRhYmFzZXMvc3BvdC9wZ3Mvc2NvcmVzL1BHUzAwMDAxMC9TY29yaW5nRmlsZXMvUEdTMDAwMDEwLnR4dC5neiINCmRvd25sb2FkLmZpbGUoVVJMLCBkZXN0ZmlsZT0iUEdTMDAwMDEwLnR4dC5neiIsIHF1aWV0PVQpDQpwZ3MgPSBmcmVhZCgiUEdTMDAwMDEwLnR4dC5neiIpICU+JSBtdXRhdGUoZWZmZWN0X3dlaWdodD1yb3VuZChlZmZlY3Rfd2VpZ2h0LCA0KSkNCmRhdGF0YWJsZShwZ3MsIHJvd25hbWVzPUYpIA0KYGBgDQoNCiMjIyAyMyBhbmQgTWUgRXhhbXBsZXMNCg0KVGhlbiwgd2Ugd2lsbCBsb2FkIGluIHR3byBzYW1wbGUgZ2Vub3R5cGVzIGZyb20gMjMgYW5kIE1lIGFuZCBtZXJnZSB0aGVtIHdpdGggdGhlIFBHUyBkYXRhLiBBZGRpdGlvbmFsbHksIHdlIG5lZWQgdG8gY2FsY3VsYXRlIHRoZSBhbGxlbGUgY291bnRzIChBQykgYnkgYWRkaW5nIHVwIHRoZSBudW1iZXIgb2YgUEdTIGVmZmVjdCBhbGxlbGVzIGluIGVhY2ggZ2Vub3R5cGUuIFlvdSBjYW4gc2VlIGJlbG93IHRoYXQgdGhlIGNvbHVtbnMgYGdlbm8xYCwgYGdlbm8yYCwgYEFDMWAsIGFuZCBgQUMyYCBoYXZlIGJlZW4gYWRkZWQgdG8gdGhlIFBHUyBkYXRhLiBZb3UgY2FuIGFsc28gc2VlIHRoYXQgMjMgYW5kIE1lIGluZm9ybWF0aW9uIGlzIG1pc3NpbmcgZm9yIHR3byBvZiB0aGUgU05QcyAocnMzODI1ODA3IGFuZCByczk0MTE0ODkpLg0KDQpgYGB7ciAyM2FuZE1lfQ0KIyByZWFkIGluIHRoZSAyMyBhbmQgTWUgZXhhbXBsZSBkYXRhDQpleCA9IHJlYWQuY3N2KCIyM2FuZE1lRXhhbXBsZXMuY3N2IikNCg0KIyBtZXJnZSB0aGUgcGdzIGFuZCBleGFtcGxlIGRhdGENCnBnczIgPSBtZXJnZShwZ3MsIGV4LCBieT0icnNJRCIpDQoNCiMgY291bnQgdGhlIG51bWJlciBvZiBlZmZlY3QgYWxsZWxlcyBmb3IgZ2Vub3R5cGUgMQ0KZ2VubzEgPSBzYXBwbHkoc3Ryc3BsaXQocGdzMiRnZW5vMSwgIi8iKSwgZnVuY3Rpb24oeCkgeFsxOjJdKQ0KcGdzMiRBQzEgPSAoZ2VubzFbMSxdPT1wZ3MyJGVmZmVjdF9hbGxlbGUpICsgKGdlbm8xWzIsXT09cGdzMiRlZmZlY3RfYWxsZWxlKQ0KDQojIGNvdW50IHRoZSBudW1iZXIgb2YgZWZmZWN0IGFsbGVsZXMgZm9yIGdlbm90eXBlIDINCmdlbm8yID0gc2FwcGx5KHN0cnNwbGl0KHBnczIkZ2VubzIsICIvIiksIGZ1bmN0aW9uKHgpIHhbMToyXSkNCnBnczIkQUMyID0gKGdlbm8yWzEsXT09cGdzMiRlZmZlY3RfYWxsZWxlKSArIChnZW5vMlsyLF09PXBnczIkZWZmZWN0X2FsbGVsZSkNCmRhdGF0YWJsZShwZ3MyLCByb3duYW1lcz1GKSANCmBgYA0KDQojIyMgMTAwMCBHZW5vbWVzDQoNCk5vdywgd2UgbmVlZCB0byBsb2FkIGluIHRoZSBpbmRpdmlkdWFsLWxldmVsIDEwMDAgR2Vub21lcyBkYXRhLiBUaGlzIGRhdGEgd2lsbCBiZSB1c2VkIHRvIGNhbGN1bGF0ZSBkaXN0cmlidXRpb25zIG9mIHBvbHlnZW5pYyBzY29yZXMgdGhhdCB3ZSB3aWxsIGNvbXBhcmUgdGhlIDIzIGFuZCBNZSBleGFtcGxlcyB0by4gQSBzdWJzZXQgb2YgdGhlIGRhdGEgaXMgc2hvd24gYmVsb3csIHdoZXJlIHRoZSBmaXJzdCBmaXZlIGNvbHVtbnMgcHJvdmlkZSBpbmZvcm1hdGlvbiByZWxhdGVkIHRvIGVhY2ggU05QIGFuZCB0aGUgbmV4dCBmb3VyIGNvbHVtbnMgYmVnaW5uaW5nIHdpdGggYEhHYCBjb3JyZXNwb25kIHRvIGluZGl2aWR1YWwgZ2Vub3R5cGVzLiBUaGUgZW50aXJlIGRhdGFzZXQgY29udGFpbnMgZ2Vub3R5cGVzIGZvciAyLDUwNCBpbmRpdmlkdWFscy4gVGhlIGBJRGAgY29sdW1uIGJlbG93IGNvcnJlc3BvbmRzIHRvIHRoZSBgcnNJRGAgY29sdW1uIGZyb20gdGhlIFBHUyBkYXRhIGFib3ZlLiANCg0KYGBge3IgMTAwMEd9DQojIHJlYWQgaW4gdGhlIDEwMDAgZ2Vub21lcyBkYXRhDQpkYXRhID0gcmVhZC50YWJsZSgiMTAwMEcucGhhc2UzLlBSUy52Y2YiLCBzZXA9Ilx0IiwgaGVhZGVyPVQpDQpzYW1wbGVzID0gY29sbmFtZXMoZGF0YSlbMTA6bmNvbChkYXRhKV0gIyBzdWJzZXQgdGhlIHNhbXBsZSBuYW1lcw0KZGF0YXRhYmxlKGRhdGFbLGMoMTo1LCAxMDoxMyldLCByb3duYW1lcz1GKQ0KYGBgDQoNCk5vdGljZSBob3cgdGhlIGdlbm90eXBlcyBpbiB0aGUgMTAwMCBHZW5vbWVzIGRhdGEgYXJlIGRlc2lnbmF0ZWQgYnkgMCdzIGFuZCAxJ3MgaW5zdGVhZCBvZiBieSBhbGxlbGVzIChBLCBULCBDLCBvciBHKSBhcyBpbiB0aGUgMjMgYW5kIE1lIGRhdGEuIEhlcmUsIGEgMCByZXByZXNlbnRzIHRoZSByZWZlcmVuY2UgYWxsZWxlIChgUkVGYCkgYW5kIGEgMSByZXByZXNlbnRzIHRoZSBhbHRlcm5hdGUgYWxsZWxlIChgQUxUYCkuIFNvLCBsb29raW5nIGF0IHRoZSBmaXJzdCBTTlAsIHRoZSBnZW5vdHlwZSAwfDAgZm9yIHRoZSBmaXJzdCBpbmRpdmlkdWFsIChIRzAwMDk2KSBjb3JyZXNwb25kcyB0byBUL1QsIHdoZXJlYXMgdGhlIGdlbm90eXBlIDF8MCBmb3IgdGhlIHNlY29uZCBpbmRpdmlkdWFsIChIRzAwMDk3KSBjb3JyZXNwb25kcyB0byBDL1QuIFRoZXNlIGdlbm90eXBlcyBjYW4gZWFzaWx5IGJlIGNvbnZlcnRlZCB0byBhbHRlcm5hdGUgYWxsZWxlIGNvdW50cyBieSBqdXN0IGFkZGluZyB1cCB0aGUgMCdzIGFuZCAxJ3MgZm9yIGVhY2ggZ2Vub3R5cGUgYXMgc2VlbiBiZWxvdy4gDQoNClRoZSBhbGxlbGUgZnJlcXVlbmNpZXMgZm9yIHRoZSBFdXJvcGVhbiAoRVVSX0FGKSBhbmQgQWZyaWNhbiAoQUZSX0FGKSBwb3B1bGF0aW9ucyB3ZXJlIGFsc28gYWRkZWQgdG8gdGhlIGRhdGEuIFRoZXNlIGZyZXF1ZW5jaWVzIGRlc2NyaWJlIGhvdyBvZnRlbiB0aGUgYWx0ZXJuYXRlIGFsbGVsZSBpcyBzZWVuIGluIHRoZSByZXNwZWN0aXZlIHBvcHVsYXRpb24uIE5vdGljZSBob3cgZm9yIHNvbWUgU05QcywgdGhlIHR3byBmcmVxdWVuY2llcyBhcmUgdmVyeSBzaW1pbGFyLCBidXQgZm9yIG90aGVycywgc3VjaCBhcyByczE3NDY1NjM3LCB0aGV5IGNhbiBiZSB2ZXJ5IGRpZmZlcmVudC4gQWxzbyBub3RpY2UgdGhhdCB0aGUgMTAwMCBHZW5vbWVzIGRhdGEgb25seSBoYXMgMjYgcm93cyB3aGlsZSB0aGUgUEdTIGRhdGEgaGFzIDI3LiBPbmUgU05QIChyczk0MTE0ODkpIGlzIG1pc3NpbmcgZnJvbSB0aGUgMTAwMCBHZW5vbWVzIGRhdGEuIA0KDQpgYGB7cn0NCiMgY29udmVydCB0aGUgZ2Vub3R5cGVzIGludG8gYWxsZWxlIGNvdW50cw0KZGF0YTIgPSBnZW5vKGRhdGEpDQoNCiMgZXh0cmFjdCB0aGUgRXVyb3BlYW4gYWxsZWxlIGZyZXF1ZW5jaWVzIGZyb20gdGhlIElORk8gY29sdW1uDQpkYXRhMiRFVVJfQUYgPSBzYXBwbHkoc3Ryc3BsaXQoZGF0YTIkSU5GTywgIjsiKSwgZnVuY3Rpb24oeCkgeFs5XSkNCmRhdGEyJEVVUl9BRiA9IHNhcHBseShzdHJzcGxpdChkYXRhMiRFVVJfQUYsICI9IiksIGZ1bmN0aW9uKHgpIHhbMl0pDQoNCiMgZXh0cmFjdCB0aGUgQWZyaWNhbiBhbGxlbGUgZnJlcXVlbmNpZXMgZnJvbSB0aGUgSU5GTyBjb2x1bW4NCmRhdGEyJEFGUl9BRiA9IHNhcHBseShzdHJzcGxpdChkYXRhMiRJTkZPLCAiOyIpLCBmdW5jdGlvbih4KSB4WzhdKQ0KZGF0YTIkQUZSX0FGID0gc2FwcGx5KHN0cnNwbGl0KGRhdGEyJEFGUl9BRiwgIj0iKSwgZnVuY3Rpb24oeCkgeFsyXSkNCg0KZGF0YTIgPSBkYXRhMiAlPiUgcmVsb2NhdGUoRVVSX0FGOkFGUl9BRiwgLmFmdGVyPUFMVCkNCmRhdGF0YWJsZShkYXRhMlssYygxOjcsIDEyOjE1KV0sIHJvd25hbWVzPUYpDQpgYGANCg0KIyMgRGF0YSBDbGVhbmluZw0KDQojIyMgTWVyZ2UgRGF0YQ0KDQpOZXh0LCB0aGUgMTAwMCBHZW5vbWVzIGRhdGEgbmVlZHMgdG8gYmUgbWVyZ2VkIHdpdGggdGhlIFBHUyBhbmQgMjMgYW5kIE1lIGRhdGEuIFJlbWVtYmVyLCB0aGUgMTAwMCBHZW5vbWVzIGFsbGVsZSBjb3VudHMgcmVmZXIgdG8gdGhlIG51bWJlciBvZiBhbHRlcm5hdGUgYWxsZWxlcywgc28gd2Ugd2FudCB0aGUgYGVmZmVjdF9hbGxlbGVgIHRvIGNvcnJlc3BvbmQgd2l0aCB0aGUgYEFMVGAgYWxsZWxlLiBXZSBhbHNvIHdhbnQgdGhlIGBhbGxlbGVmcmVxdWVuY3lfZWZmZWN0YCBhbmQgYEVVUl9BRmAgY29sdW1ucyB0byBiZSBzaW1pbGFyIHNpbmNlIHRoZSBQR1MgZGF0YSBpcyBkZXJpdmVkIGZyb20gaW5kaXZpZHVhbHMgd2l0aCBFdXJvcGVhbiBhbmNlc3RyeS4gQnV0LCBsb29raW5nIGF0IHRoZSBtZXJnZWQgZGF0YSBiZWxvdywgdGhhdCBpcyBub3QgdGhlIGNhc2UgZm9yIHNvbWUgb2YgdGhlIFNOUHMuIEhvd2V2ZXIsIHdlIGNhbiBzd2l0Y2ggdGhlIHJlZmVyZW5jZSBhbmQgYWx0ZXJuYXRlIGFsbGVsZXMgZm9yIGEgcGFydGljdWxhciBTTlAgYXMgbG9uZyBhcyB3ZSBhbHNvIHVwZGF0ZSB0aGUgYWxsZWxlIGNvdW50cyAodHdvIG1pbnVzIHRoZSBhbHRlcm5hdGUgYWxsZWxlIGNvdW50KSBhbmQgYWxsZWxlIGZyZXF1ZW5jaWVzIChvbmUgbWludXMgdGhlIGFsdGVybmF0ZSBhbGxlbGUgZnJlcXVlbmN5KS4gDQoNCllvdSBtYXkgYWxzbyBub3RpY2UgdGhhdCB0aGUgZWZmZWN0IGFsbGVsZSBmb3Igb25lIFNOUCAocnMzODI1ODA3KSBkb2Vzbid0IG1hdGNoIGVpdGhlciB0aGUgcmVmZXJlbmNlIG9yIGFsdGVybmF0ZSBhbGxlbGUgZnJvbSAxMDAwIEdlbm9tZXMuIEFkZGl0aW9uYWxseSwgdGhlIGVmZmVjdCBhbGxlbGUgZm9yIGFub3RoZXIgU05QIChyczIxNjE3MikgZG9lcyBjb3JyZXNwb25kIHdpdGggdGhlIGFsdGVybmF0ZSBhbGxlbGUgYnV0IHRoZSBgYWxsZWxlZnJlcXVlbmN5X2VmZmVjdGAgYW5kIGBFVVJfQUZgIGNvbHVtbnMgZG9uJ3QgbWF0Y2guIEZvciB0aGVzZSB0d28gU05Qcywgd2UgYWxzbyBuZWVkIHRvIGRvIGEgc3RyYW5kIGZsaXAuIFJlbWVtYmVyIHRoYXQgRE5BIGlzIGRvdWJsZS1zdHJhbmRlZCBhbmQgdGhhdCBBIGFsbGVsZXMgb24gb25lIHN0cmFuZCBwYWlyIHdpdGggVCBhbGxlbGVzIG9uIHRoZSBvdGhlciBhbmQgdmljZSB2ZXJzYSB3aGlsZSBDIGFsbGVsZXMgb24gb25lIHN0cmFuZCBwYWlyIHdpdGggRyBhbGxlbGVzIG9uIHRoZSBvdGhlciBhbmQgdmljZSB2ZXJzYS4gV2Uga25vdyB0aGF0IHRoZSAxMDAwIEdlbm9tZXMgYW5kIDIzIGFuZCBNZSBkYXRhIHJlZmVyIHRvIHRoZSBwb3NpdGl2ZSBzdHJhbmQsIGJ1dCB3ZSBhcmUgdW5zdXJlIHdoaWNoIHN0cmFuZCB0aGUgZWZmZWN0IGFsbGVsZXMgZnJvbSB0aGUgUEdTIGRhdGEgYXJlIG9uLiBTbywgd2Ugd2lsbCBmbGlwIHRoZSBzdHJhbmRzIGZvciB0aGUgdHdvIHByZXZpb3VzbHkgbWVudGlvbmVkIFNOUHMgYW5kIGFzc3VtZSB0aGF0IHRoZSBlZmZlY3QgYWxsZWxlcyBvZiB0aGUgcmVtYWluaW5nIFNOUHMgYXJlIG9uIHRoZSBwb3NpdGl2ZSBzdHJhbmQuIEhvd2V2ZXIsIHRoaXMgd291bGQgaGF2ZSB0byBiZSB2ZXJpZmllZCBpbiByZWFsIGNsaW5pY2FsIG9yIHJlc2VhcmNoIGFwcGxpY2F0aW9ucy4NCg0KYGBge3IgbWVyZ2V9DQojIG1lcmdlIHRoZSAxMDAwIGdlbm9tZXMgZGF0YSB3aXRoIHRoZSBwZ3MgZGF0YQ0KbWVyZ2VkID0gbGVmdF9qb2luKHBnczIsIGRhdGEyLCBieT1jKCJyc0lEIj0iSUQiKSkgJT4lIA0KICBzZWxlY3QoLVFVQUwsIC1GSUxURVIsIC1JTkZPLCAtRk9STUFUKSAlPiUNCiAgcmVsb2NhdGUoQ0hST006QUxULCAuYWZ0ZXI9cnNJRCkgJT4lDQogIHJlbG9jYXRlKEVVUl9BRjpBRlJfQUYsIC5hZnRlcj1hbGxlbGVmcmVxdWVuY3lfZWZmZWN0KSAlPiUNCiAgbXV0YXRlKEVVUl9BRj1hcy5udW1lcmljKEVVUl9BRiksIEFGUl9BRj1hcy5udW1lcmljKEFGUl9BRikpDQpkYXRhdGFibGUobWVyZ2VkWywxOjIwXSwgcm93bmFtZXM9RikNCmBgYA0KDQpMb29raW5nIGF0IHRoZSB1cGRhdGVkIGRhdGEgYmVsb3csIHlvdSBjYW4gc2VlIHRoYXQgdGhlIGBlZmZlY3RfYWxsZWxlYCBhbmQgYEFMVGAgYWxsZWxlcyBub3cgbWF0Y2ggYW5kIHRoYXQgdGhlIGBhbGxlbGVmcmVxdWVuY3lfZWZmZWN0YCBhbmQgYEVVUl9BRmAgY29sdW1ucyBhcmUgbW9yZSBzaW1pbGFyLg0KDQpgYGB7cn0NCiMgdXBkYXRlIHRoZSAxMDAwIGdlbm9tZXMgZGF0YSBiYXNlZCBvbiB0aGUgZWZmZWN0IGFsbGVsZXMgaW4gdGhlIHBncyBkYXRhDQptZXJnZWQyID0gdXBkYXRlKG1lcmdlZCkNCmRhdGF0YWJsZShtZXJnZWQyWywxOjIwXSwgcm93bmFtZXM9RikNCmBgYA0KDQojIyMgU3Vic2V0IEFuY2VzdHJ5DQoNCk5vdywgd2UgY2FuIGxvb2sgYXQgdGhlIG1ldGFkYXRhIGZvciB0aGUgMTAwMCBHZW5vbWVzIGRhdGEsIHdoaWNoIHNob3dzIGluZm9ybWF0aW9uIGFib3V0IGVhY2ggaW5kaXZpZHVhbC4gV2UgY2FuIHVzZSB0aGlzIGluZm9ybWF0aW9uIHRvIHN1YnNldCB0aGUgbWVyZ2VkIGRhdGEgYnkgYW5jZXN0cnkgcG9wdWxhdGlvbi4gSW4gdGhpcyBleGVyY2lzZSwgd2Ugd2lsbCBjYWxjdWxhdGUgYW5kIGNvbXBhcmUgdGhlIHBvbHlnZW5pYyBzY29yZXMgZm9yIHRoZSA0MDMgTm9uLUZpbm5pc2ggRXVyb3BlYW4gaW5kaXZpZHVhbHMgKEVVUiBgU3VwZXJwb3B1bGF0aW9uLmNvZGVgLCBleGNsdWRpbmcgRmlubmlzaCBgUG9wdWxhdGlvbi5uYW1lYCkgd2l0aCB0aGUgc2NvcmVzIGZvciB0aGUgNjYxIEFmcmljYW4gaW5kaXZpZHVhbHMgKEFGUiBgU3VwZXJwb3B1bGF0aW9uLmNvZGVgKS4NCg0KYGBge3IgcG9wc30NCiMgcmVhZCBpbiB0aGUgMTAwMCBnZW5vbWVzIG1ldGFkYXRhIGZvciB0aGUgc2FtcGxlcw0KaW5mbyA9IHJlYWQudGFibGUoIjEwMDBHLnBoYXNlMy5tZXRhZGF0YS50c3YiLCBzZXA9Ilx0IiwgaGVhZGVyPVQpICU+JSBmaWx0ZXIoU2FtcGxlLm5hbWUgJWluJSBzYW1wbGVzKSAlPiUgYXJyYW5nZShTYW1wbGUubmFtZSkNCmRhdGF0YWJsZShpbmZvWywtOV0sIHJvd25hbWVzPUYpDQoNCiMgc3Vic2V0IHRoZSBzYW1wbGUgbmFtZXMgYnkgcG9wdWxhdGlvbg0KTkZFLnNhbXBsZXMgPSBpbmZvICU+JSBmaWx0ZXIoU3VwZXJwb3B1bGF0aW9uLmNvZGU9PSJFVVIiLCBQb3B1bGF0aW9uLmNvZGUhPSJGSU4iKSAlPiUgc2VsZWN0KFNhbXBsZS5uYW1lKQ0KQUZSLnNhbXBsZXMgPSBpbmZvICU+JSBmaWx0ZXIoU3VwZXJwb3B1bGF0aW9uLmNvZGU9PSJBRlIiKSAlPiUgc2VsZWN0KFNhbXBsZS5uYW1lKQ0KDQojIHN1YnNldCB0aGUgMTAwMCBnZW5vbWVzIGRhdGEgYnkgcG9wdWxhdGlvbg0KTkZFID0gbWVyZ2VkMiAlPiUgc2VsZWN0KHJzSUQ6QUMyLCBORkUuc2FtcGxlcyRTYW1wbGUubmFtZSkgJT4lIHNlbGVjdCgtQUZSX0FGKQ0KQUZSID0gbWVyZ2VkMiAlPiUgc2VsZWN0KHJzSUQ6QUMyLCBBRlIuc2FtcGxlcyRTYW1wbGUubmFtZSkgJT4lIHNlbGVjdCgtRVVSX0FGKQ0KYGBgDQoNCiMjIFBHUw0KDQojIyMgQ2FsY3VsYXRpb25zDQoNClRvIGNhbGN1bGF0ZSB0aGUgcG9seWdlbmljIHNjb3JlcyBmb3IgdGhlIDEwMDAgR2Vub21lcyBwb3B1bGF0aW9ucywgd2Ugd2lsbCBtdWx0aXBseSB0aGUgYGVmZmVjdF93ZWlnaHRgIGNvbHVtbiBieSB0aGUgYWxsZWxlIGNvdW50cyBpbiBlYWNoIGBIR2AgY29sdW1uIG9mIHRoZSBORkUgYW5kIEFGUiBkYXRhLiBXZSB3aWxsIGFsc28gY2FsY3VsYXRlIHRoZSBzY29yZXMgZm9yIHRoZSAyMyBhbmQgTWUgZXhhbXBsZXMgYnkgbXVsdGlwbHlpbmcgdGhlIGBlZmZlY3Rfd2VpZ2h0YCBjb2x1bW4gYnkgdGhlIGBBQzFgIGFuZCBgQUMyYCBjb2x1bW5zLiBTbywgd2Ugd2lsbCBoYXZlIDQwMyBzY29yZXMgZm9yIHRoZSBORkUgcG9wdWxhdGlvbiBhbmQgNjYxIHNjb3JlcyBmb3IgdGhlIEFGUiBwb3B1bGF0aW9uIGFzIHdlbGwgYXMgdHdvIHNjb3JlcyBmb3IgdGhlIGV4YW1wbGUgZGF0YS4gDQoNCk5vdGUsIHByaW9yIHRvIGNhbGN1bGF0aW5nIHRoZSBzY29yZXMsIHdlIHdpbGwgcmVtb3ZlIHRoZSB0d28gU05QcyBtaXNzaW5nIGZyb20gdGhlIDIzIGFuZCBNZSBkYXRhIChyczM4MjU4MDcgYW5kIHJzOTQxMTQ4OSkgc28gYWxsIG9mIHRoZSBzY29yZXMgd2lsbCBiZSBiYXNlZCBvbiB0aGUgc2FtZSAyNSBTTlBzIChpbnN0ZWFkIG9mIHRoZSBvcmlnaW5hbCAyNyBpbiB0aGUgUEdTIGRhdGEpLg0KDQpgYGB7ciBjYWxjdWxhdGV9DQojIHJlbW92ZSBOQSB2YWx1ZXMNCk5GRTIgPSBORkUgJT4lIGZpbHRlcihnZW5vMSE9Ik5vIERhdGEiKQ0KQUZSMiA9IEFGUiAlPiUgZmlsdGVyKGdlbm8xIT0iTm8gRGF0YSIpDQoNCiMgY2FsY3VsYXRlIHRoZSBwZ3MgZm9yIGVhY2ggcG9wdWxhdGlvbg0KTkZFLnBncyA9IE5GRTIkZWZmZWN0X3dlaWdodCAlKiUgZGF0YS5tYXRyaXgoTkZFMlssMTY6bmNvbChORkUyKV0pDQpBRlIucGdzID0gQUZSMiRlZmZlY3Rfd2VpZ2h0ICUqJSBkYXRhLm1hdHJpeChBRlIyWywxNjpuY29sKEFGUjIpXSkNCg0KIyBjYWxjdWxhdGUgdGhlIHBncyBmb3IgdGhlIGV4YW1wbGVzDQpleC5wZ3MxID0gTkZFMiRlZmZlY3Rfd2VpZ2h0ICUqJSBORkUyJEFDMQ0KZXgucGdzMiA9IE5GRTIkZWZmZWN0X3dlaWdodCAlKiUgTkZFMiRBQzINCmBgYA0KDQojIyMgUGxvdHMNCg0KV2UgY2FuIHRoZW4gcGxvdCB0aGUgZGlzdHJpYnV0aW9ucyBvZiB0aGUgc2NvcmVzIGZyb20gdGhlIHR3byBwb3B1bGF0aW9ucyBhcyB3ZWxsIGFzIHRoZSB0d28gMjMgYW5kIE1lIGV4YW1wbGUgc2NvcmVzLg0KDQpgYGB7ciBwbG90fQ0KIyByZS1mb3JtYXQgdGhlIGRhdGEgZm9yIHBsb3R0aW5nDQpwZ3MuZGF0YSA9IGRhdGEuZnJhbWUoUG9wdWxhdGlvbj1jKHJlcCgiTkZFIiwgbGVuZ3RoKE5GRS5wZ3MpKSwgcmVwKCJBRlIiLCBsZW5ndGgoQUZSLnBncykpKSwNCiAgICAgICAgICAgICAgICAgICAgICBTY29yZT1jKE5GRS5wZ3MsIEFGUi5wZ3MpKQ0KZXguZGF0YSA9IGRhdGEuZnJhbWUoRXhhbXBsZT1jKCIxIiwgIjIiKSwgU2NvcmU9YyhleC5wZ3MxLCBleC5wZ3MyKSkNCg0KIyBwbG90IHRoZSBwZ3MgZGlzdHJpYnV0aW9ucw0KZ2dwbG90KCkgKw0KICBnZW9tX2RlbnNpdHkoZGF0YT1wZ3MuZGF0YSwgYWVzKHg9U2NvcmUsIGZpbGw9UG9wdWxhdGlvbiksIGFscGhhPTAuNzUpICsNCiAgZ2VvbV92bGluZShkYXRhPWV4LmRhdGEsIGFlcyh4aW50ZXJjZXB0PVNjb3JlLCBsaW5ldHlwZT1FeGFtcGxlKSkgKw0KICBzY2FsZV9saW5ldHlwZV9tYW51YWwodmFsdWVzPWMoIjEiPSJkYXNoZWQiLCAiMiI9ImRvdHRlZCIpKSArDQogIHRoZW1lX2J3KCkgKyB5bGFiKCJEZW5zaXR5IikNCmBgYA0KDQpUaGUgcGxvdCBzaG93cyB0aGF0IHRoZSBkaXN0cmlidXRpb25zIGxvb2sgdmVyeSBkaWZmZXJlbnQgZm9yIHRoZSB0d28gcG9wdWxhdGlvbnMsIHdpdGggdGhlIGF2ZXJhZ2Ugc2NvcmUgZm9yIHRoZSBORkUgcG9wdWxhdGlvbiBgciByb3VuZChtZWFuKE5GRS5wZ3MpLCAyKWAgYW5kIHRoZSBhdmVyYWdlIHNjb3JlIGZvciB0aGUgQUZSIHBvcHVsYXRpb24gYHIgcm91bmQobWVhbihBRlIucGdzKSwgMilgLiBUaGUgcGxvdCBhbHNvIHNob3dzIHRoYXQgdGhlIHNjb3JlIGZvciBFeGFtcGxlIDIgaXMgYWJvdXQgb25lIG1vcmUgdGhhbiB0aGUgc2NvcmUgZm9yIEV4YW1wbGUgMS4gQ29tcGFyaW5nIHRoZSBleGFtcGxlIHNjb3JlcyB0byB0aGUgcG9wdWxhdGlvbiBkaXN0cmlidXRpb25zLCBFeGFtcGxlIDEgd291bGQgaGF2ZSBhIGxvdyBzY29yZSBjb21wYXJlZCB0byB0aGUgTkZFIHBvcHVsYXRpb24sIGJ1dCBhIHByZXR0eSBoaWdoIHNjb3JlIGNvbXBhcmVkIHRvIHRoZSBBRlIgcG9wdWxhdGlvbi4gVGhhdCBpcyB3aHkgaXQgaXMgU08gaW1wb3J0YW50IHRvIGFjY291bnQgZm9yIGFuY2VzdHJ5IHdoZW4gaW50ZXJwcmV0aW5nIHBvbHlnZW5pYyBzY29yZXMuIFNpbmNlIHRoZSBQR1MgZGF0YSBpcyBkZXJpdmVkIGZyb20gaW5kaXZpZHVhbHMgd2l0aCBFdXJvcGVhbiBhbmNlc3RyeSwgdGhlIGV4YW1wbGUgc2NvcmVzIHNob3VsZCBvbmx5IGJlIGNvbXBhcmVkIHRvIGEgRXVyb3BlYW4gcG9wdWxhdGlvbiBpbiBwcmFjdGljZS4gDQoNCiMjIyBQZXJjZW50YWdlcw0KDQpXZSBjYW4gYWxzbyBjYWxjdWxhdGUgdGhlIGV4YWN0IHBlcmNlbnRpbGVzIG9mIHRoZSBleGFtcGxlIHNjb3JlcyByZWxhdGl2ZSB0byBlYWNoIHBvcHVsYXRpb24uDQoNCmBgYHtyIHBlcmNlbnRpbGVzfQ0KIyBlc3RpbWF0ZSB0aGUgZGlzdHJpYnV0aW9ucyBiYXNlZCBvbiB0aGUgZGF0YSAoZW1waXJpY2FsIGNkZnMpDQpORkUuY2RmID0gZWNkZihORkUucGdzKQ0KQUZSLmNkZiA9IGVjZGYoQUZSLnBncykNCg0KIyBjYWxjdWxhdGUgdGhlIHBlcmNlbnRpbGVzIGZvciB0aGUgZmlyc3QgZXhhbXBsZQ0KZXgxLk5GRSA9IE5GRS5jZGYoZXgucGdzMSkNCmV4MS5BRlIgPSBBRlIuY2RmKGV4LnBnczEpDQoNCiMgY2FsY3VsYXRlIHRoZSBwZXJjZW50aWxlcyBmb3IgdGhlIHNlY29uZCBleGFtcGxlDQpleDIuTkZFID0gTkZFLmNkZihleC5wZ3MyKQ0KZXgyLkFGUiA9IEFGUi5jZGYoZXgucGdzMikNCmBgYA0KDQpCYXNlZCBvbiB0aGUgZGlzdHJpYnV0aW9ucyBzaG93biBpbiB0aGUgcGxvdCBhYm92ZSwgdGhlIHNjb3JlIGZvciBFeGFtcGxlIDEgaXMgaW4gdGhlIGByIHJvdW5kKGV4MS5ORkUqMTAwLCAyKWAgcGVyY2VudGlsZSAobG93IHJpc2spIGZvciB0aGUgTkZFIHBvcHVsYXRpb24sIGJ1dCBpcyBpbiB0aGUgYHIgcm91bmQoZXgxLkFGUioxMDAsIDIpYCBwZXJjZW50aWxlIChoaWdoIHJpc2spIGZvciB0aGUgQUZSIHBvcHVsYXRpb24uIEFkZGl0aW9uYWxseSwgdGhlIHNjb3JlIGZvciBFeGFtcGxlIDIgaXMgaW4gdGhlIGByIHJvdW5kKGV4Mi5ORkUqMTAwLCAyKWAgcGVyY2VudGlsZSAoaGlnaCByaXNrKSBmb3IgdGhlIE5GRSBwb3B1bGF0aW9uLCBidXQgaXMgaW4gdGhlIGByIHJvdW5kKGV4Mi5BRlIqMTAwLCAyKWAgcGVyY2VudGlsZSAodmVyeSBoaWdoIHJpc2spIGZvciB0aGUgQUZSIHBvcHVsYXRpb24uDQoNCk5vdGUgdGhhdCB0aGUgaW5kaXZpZHVhbHMgZnJvbSB0aGUgMTAwMCBHZW5vbWVzIGRhdGEgYXJlIGNvbnNpZGVyZWQgImhlYWx0aHkiLCBzbyB0aGUgZGlzdHJpYnV0aW9ucyBhbmQgcGVyY2VudGFnZXMgbWlnaHQgYmUgZGlmZmVyZW50IGlmIHdlIGluc3RlYWQgY29tcGFyZWQgdGhlIDIzIGFuZCBNZSBleGFtcGxlcyB0byBhIHBvcHVsYXRpb24gb2YgcGVvcGxlIHdpdGggY29yb25hcnkgYXJ0ZXJ5IGRpc2Vhc2Ugb3IgYSBmYW1pbHkgaGlzdG9yeSBvZiBjb3JvbmFyeSBhcnRlcnkgZGlzZWFzZS4g