The purpose of this analysis was to create clusters of U.S. states (including Washington, D.C.) that share common features and determine if the standard regional groupings (Midwest, New England, etc.) are truly reflective of states’ characteristics. Before feature selection, the data set contained 35 variables, including Population Density, Percent White, Percent Female, Effective Minimum Wage, 2020 Presidential Election Voter Turnout, Coronavirus Deaths per 100k Population, etc. All data was for the year 2020 only. Data was largely obtained from the 2020 Census and 2020 American Community Survey 1-Year Experimental Estimates, although supplemental sources were also used.
Latitude and Longitude were included as variables to account for certain regional similarities that cannot be quantified (accent, dialect, cuisine, etc.). Analysis results may have been different without the inclusion of Latitude and Longitude.
Before beginning the analysis, variables were assessed for multicollinearity. The table below shows variable pairings with a correlation greater than |0.75|.
# Libraries
library(readxl) #for reading the Excel file
library(tibble) #for filtering the correlation matrix
library(dplyr) #for filtering the correlation matrix
library(tidyr) #for filtering the correlation matrix
library(kohonen) #for SOM functions
library(usmap) #for state mapping
library(ggplot2) #for state mapping
library(kableExtra) #for tables
# Read Data
data <- read_excel('E:/Independent Projects/StateClustering/Master_Dataset.xlsx', sheet="Data")
# Create Correlations
corrs <- round(cor(data[,-4]),3)
# Remove the diagonal and repeat correlations
corrs[!lower.tri(corrs)]<-NA
# Look at the correlations over .75 or under -.75
high_corrs <-
data.frame(corrs) %>%
rownames_to_column() %>%
gather(key="variable", value="correlation", -rowname) %>%
filter(abs(correlation) > 0.75) %>%
arrange(desc(abs(correlation)))
# Create table of High Correlations
kable(high_corrs,
col.names=c("Variable 1", "Variable 2","Correlation"),
longtable = TRUE) %>%
kable_styling(bootstrap_options = c("striped")) %>%
row_spec(0,bold=TRUE)
| Variable 1 |
Variable 2 |
Correlation |
| Housing_Units |
Population |
0.994 |
| Bach_and_Above |
Mdn_Ind_Income |
0.873 |
| Perc_P_Islander |
Perc_Asian |
0.860 |
| Perc_Nvr_Married |
Pop_Density |
0.851 |
| Bach_and_Above |
Perc_Gen_Elect_Dem |
0.845 |
| Area_Water |
Area_Land |
0.833 |
| Perc_wo_Comp_Int |
Mdn_House_Income |
-0.799 |
| Perc_wo_Comp_Int |
Perc_Poverty |
0.798 |
| Bach_and_Above |
Mdn_House_Income |
0.778 |
| Mdn_Ind_Income |
Pop_Density |
0.777 |
| Perc_Nvr_Married |
Perc_Gen_Elect_Dem |
0.767 |
| Mdn_Ind_Income |
Mdn_House_Income |
0.754 |
| Perc_Poverty |
Mdn_House_Income |
-0.754 |
| Perc_Female |
Perc_Black |
0.752 |
Due to high correlations with other variables, Housing Units, Median Individual Income, and Percent Never Married were removed from the data set. Other slightly-less highly correlated variables were not removed. Self Organizing Maps do not require variable independence, so the removal or inclusion of correlated variables is more of an exercise in judgment (Is this variable truly different from that one? Am I measuring the same underlying data twice? Is that underlying data important enough to warrant being measured twice?), than a modelling necessity.
Next, the remaining variables were normalized, by subtracting their means and dividing by their standard deviations. This put all the variables on the same scale and removed any modelling impact from their original, highly disparate scales.
The data was then used to create a Self Organizing Map, using a 4x4 grid and 500 iterations. Training progress was plotted to determine that 500 iterations were sufficient.
# Remove Housing_Units for having a .99 correlation with Population
# Remove Mdn_Ind_Income for having a .87 correlation with Bach_and_Above, as well as a strong correlation w 4 other variables
# Remove Perc_Nvr_Married for having a .85 correlation with Pop_Density, as well as a strong correlation w 3 other variables
# Remove State, since it can't be used in the SOM
data2 <- data[,-c(4,7,29,31)]
# Normalize data by subtracting mean and dividing by s.d.
data2 <- scale(data2)
# Check the summary to verify that scaling took place
dat_summary<-summary(data2)
# SOM ---------------------------------------------------------
# Set a seed
set.seed(8)
# Create a 4x4 grid
som.grid <- somgrid(xdim = 4, ydim = 4, topo = "hexagonal")
# Create a model using our grid and 500 iterations
som.model <- som(data2,
grid = som.grid,
rlen=500,
alpha=c(0.05,0.01),
keep.data = TRUE )
# Check training progress
training_plot<-plot(som.model, type="changes") # Graph levels out, indicating that we don't need more iterations

The model’s node counts and neighbor distances were plotted, and the Within Cluster Sum of Squares were calculated for up to 15 clusters. Node counts and neighbor distances were low, but the problem largely wasn’t mitigated by using a smaller grid. Smaller grids tended to continue to have small distances between neighbors; node counts increased for one or two nodes, but most nodes remained sparse. Therefore, the 4x4 grid was maintained.
# Check node counts
plot(som.model, type="count", main="Node Counts")

# Check each cell's distance from its neighbors
plot(som.model, type="dist.neighbours", main = "SOM neighbour distances")

The plot of the Within Cluster Sum of Squares (below) elbowed at around 5 clusters, but continued to decrease steadily until around 11 clusters. 8 to 11 clusters were tested and mapped. Ultimately, 11 clusters were chosen because Hawaii, Alaska, and Washington, D.C. were consistently placed into single-state clusters by themselves, leaving less clusters for the other 48 states.
# Get the within cluster sum of squares
wss <- (nrow(getCodes(som.model))-1)*sum(apply(getCodes(som.model),2,var))
# Plot the within cluster sum of squares for up to 15 clusters
for (i in 2:15) {
wss[i] <- sum(kmeans(getCodes(som.model), centers=i)$withinss)
}
plot(wss, main = "Within Cluster Sum of Squares by # of Clusters")

The clustered Self Organizing Map grid was produced for the 11 cluster model.
# Look at each variable's heat map by changing k
#k=32
#par(mfrow=c(4,8))
#for (k in 1:32){
#plot(som.model, type = "property", property = getCodes(som.model)[,k],
# main=colnames(getCodes(som.model))[k])
#}
# Use hierarchical clustering to group the vectors into 11 clusters
som.cluster <- cutree(hclust(dist(getCodes(som.model))), 11)
# Plot the clusters - this view uses 2 colors twice
#plot(som.model, type="mapping",
# bgcol = som.cluster,
# main = "Clusters")
# Plot the clusters - this view uses custom colors
plot(som.model, type="mapping",
bgcol = c("aquamarine","beige","cornflowerblue","darkseagreen","lavender","mistyrose2","cornflowerblue","slategray2","cadetblue1","mistyrose2","wheat3","lightpink","gray71","gray71","wheat3","wheat3"),
main = "Clusters")
add.cluster.boundaries(som.model, som.cluster)

The final mapping of the 11 clusters is below. Washington, D.C. is Cluster 2, although it cannot be seen on the national map.
# SOM Mapping ---------------------------------------------------------
# Create a vector of the states' assigned clusters
cluster.assignment <- som.cluster[som.model$unit.classif]
# Add assigned clusters to the dataset
data$cluster <- cluster.assignment
# Create map data
map_data <- data[,c(4,37)]
map_data$fips <- fips(map_data$State)
map_data <- map_data[,c(3,2)]
map_data$cluster <- as.factor(map_data$cluster)
# Create the map
plot_usmap(regions="states", data = map_data, values = "cluster", color = "black") +
scale_fill_discrete(name = "Cluster") +
theme(legend.position = "right")

The unique features of the clusters were then assessed using box plots (not shown; see code below).
# Assess clusters ---------------------------------------------------------
# Put data in a data frame, remove variables that weren't used for analysis
data<-as.data.frame(data[,-c(7,29,31)])
# Get cluster counts
data %>%
count(cluster)
# Create list of states by cluster
state_list <-
data %>%
group_by(cluster) %>%
count(State)
# Create boxplots for each variable
for (n in c(1:3,5:33)){
boxplot(data[,n]~data$cluster,
ylab=colnames(data)[n],
main=colnames(data)[n])
}
The final clusters were:
Cluster 1 - Hawaii
Hawaii has the highest water to land area ratio, as well as the highest mixed race and Asian populations, low incarceration and poverty, and a high percentage of residents with a High School education or above.
Cluster 2 - Washington DC
D.C. has extremely high population density and percent of residents with a Bachelors degree or above, as well as a high minimum wage, high unemployment, and the largest % female population.
Cluster 3 - Connecticut, Delaware, Maryland, Massachusetts, New Jersey, Rhode Island, Virginia
These states are higher income and have a high percent of residents with a Bachelors degree or above. Their population densities are above average and they experienced above average Coronavirus deaths per 100,000 population in 2020.
Cluster 4 - Maine, New Hampshire, Vermont
These three states are distinct from the rest of New England. They are small states with small, largely white populations. They have the highest percent vacant housing of any cluster, but the lowest percent of residents who considered themselves very religious.
Cluster 5 - Arizona, California, Nevada, New Mexico, Texas
These large, South-Western states have above average areas and the highest amounts of Hispanic residents. These states have been growing in population, and this cluster has the lowest percent of residents with a High School education or above.
Cluster 6 - Illinois, Michigan, New York, Ohio, Pennsylvania
This cluster is extremely average in almost every measure, other than population change since 2010. These states have grown more slowly than average over the past 10 years.
Cluster 7 - Colorado, Oregon, Washington
These three Western states have above average Native and Pacific Islander populations and high minimum wages. They have been growing rapidly and have high voter turnout.
Cluster 8 - Florida, Georgia, North Carolina
These three Southern states have a higher than average percent black residents. They also have low minimum wages and low median household incomes. An above average amount of their residents report being very religious.
Cluster 9 - Idaho, Indiana, Iowa, Kansas, Minnesota, Missouri, Montana, Nebraska, North Dakota, South Dakota, Utah, Wisconsin, Wyoming
This cluster had the lowest minimum wages, as well as high positive COVID tests per 100,000. A large percent of their populations has a High School education or above. They are predominantly white, with above average Native populations.
Cluster 10 - Alaska
Alaska has the highest land area and a small population. It experienced high unemployment in 2020 and has the lowest percent female population of any state. It also has a large amount of vacant housing and a large Pacific Islander population.
Cluster 11 - Alabama, Arkansas, Kentucky, Louisiana, Mississippi, Oklahoma, South Carolina, Tennessee, West Virginia
Cluster 11 has the highest percent of its population incarcerated. It has low minimum wages and the lowest percent of population with a Bachelors degree and above. It is the cluster with the greatest proportion of individuals reporting themselves as very religious, as well as the greatest proportion of individuals without access to an internet-connected computer.
LS0tDQp0aXRsZTogIlUuUy4gU3RhdGUgQ2x1c3RlcmluZyINCm91dHB1dDoNCiAgaHRtbF9ub3RlYm9vazoNCiAgICAgIGNvZGVfZm9sZGluZzogaGlkZQ0KLS0tDQoNClRoZSBwdXJwb3NlIG9mIHRoaXMgYW5hbHlzaXMgd2FzIHRvIGNyZWF0ZSBjbHVzdGVycyBvZiBVLlMuIHN0YXRlcyAoaW5jbHVkaW5nIFdhc2hpbmd0b24sIEQuQy4pIHRoYXQgc2hhcmUgY29tbW9uIGZlYXR1cmVzIGFuZCBkZXRlcm1pbmUgaWYgdGhlIHN0YW5kYXJkIHJlZ2lvbmFsIGdyb3VwaW5ncyAoTWlkd2VzdCwgTmV3IEVuZ2xhbmQsIGV0Yy4pIGFyZSB0cnVseSByZWZsZWN0aXZlIG9mIHN0YXRlcycgY2hhcmFjdGVyaXN0aWNzLiBCZWZvcmUgZmVhdHVyZSBzZWxlY3Rpb24sIHRoZSBkYXRhIHNldCBjb250YWluZWQgMzUgdmFyaWFibGVzLCBpbmNsdWRpbmcgUG9wdWxhdGlvbiBEZW5zaXR5LCBQZXJjZW50IFdoaXRlLCBQZXJjZW50IEZlbWFsZSwgRWZmZWN0aXZlIE1pbmltdW0gV2FnZSwgMjAyMCBQcmVzaWRlbnRpYWwgRWxlY3Rpb24gVm90ZXIgVHVybm91dCwgQ29yb25hdmlydXMgRGVhdGhzIHBlciAxMDBrIFBvcHVsYXRpb24sIGV0Yy4gQWxsIGRhdGEgd2FzIGZvciB0aGUgeWVhciAyMDIwIG9ubHkuIERhdGEgd2FzIGxhcmdlbHkgb2J0YWluZWQgZnJvbSB0aGUgMjAyMCBDZW5zdXMgYW5kIDIwMjAgQW1lcmljYW4gQ29tbXVuaXR5IFN1cnZleSAxLVllYXIgRXhwZXJpbWVudGFsIEVzdGltYXRlcywgYWx0aG91Z2ggc3VwcGxlbWVudGFsIHNvdXJjZXMgd2VyZSBhbHNvIHVzZWQuDQoNCkxhdGl0dWRlIGFuZCBMb25naXR1ZGUgd2VyZSBpbmNsdWRlZCBhcyB2YXJpYWJsZXMgdG8gYWNjb3VudCBmb3IgY2VydGFpbiByZWdpb25hbCBzaW1pbGFyaXRpZXMgdGhhdCBjYW5ub3QgYmUgcXVhbnRpZmllZCAoYWNjZW50LCBkaWFsZWN0LCBjdWlzaW5lLCBldGMuKS4gQW5hbHlzaXMgcmVzdWx0cyBtYXkgaGF2ZSBiZWVuIGRpZmZlcmVudCB3aXRob3V0IHRoZSBpbmNsdXNpb24gb2YgTGF0aXR1ZGUgYW5kIExvbmdpdHVkZS4NCg0KQmVmb3JlIGJlZ2lubmluZyB0aGUgYW5hbHlzaXMsIHZhcmlhYmxlcyB3ZXJlIGFzc2Vzc2VkIGZvciBtdWx0aWNvbGxpbmVhcml0eS4gVGhlIHRhYmxlIGJlbG93IHNob3dzIHZhcmlhYmxlIHBhaXJpbmdzIHdpdGggYSBjb3JyZWxhdGlvbiBncmVhdGVyIHRoYW4gfDAuNzV8Lg0KDQpgYGB7ciBpbmNsdWRlPVRSVUV9DQojIExpYnJhcmllcw0KbGlicmFyeShyZWFkeGwpICAjZm9yIHJlYWRpbmcgdGhlIEV4Y2VsIGZpbGUNCmxpYnJhcnkodGliYmxlKSAgI2ZvciBmaWx0ZXJpbmcgdGhlIGNvcnJlbGF0aW9uIG1hdHJpeA0KbGlicmFyeShkcGx5cikgICAjZm9yIGZpbHRlcmluZyB0aGUgY29ycmVsYXRpb24gbWF0cml4DQpsaWJyYXJ5KHRpZHlyKSAgICNmb3IgZmlsdGVyaW5nIHRoZSBjb3JyZWxhdGlvbiBtYXRyaXgNCmxpYnJhcnkoa29ob25lbikgI2ZvciBTT00gZnVuY3Rpb25zDQpsaWJyYXJ5KHVzbWFwKSAgICNmb3Igc3RhdGUgbWFwcGluZw0KbGlicmFyeShnZ3Bsb3QyKSAjZm9yIHN0YXRlIG1hcHBpbmcNCmxpYnJhcnkoa2FibGVFeHRyYSkgI2ZvciB0YWJsZXMNCg0KDQojIFJlYWQgRGF0YQ0KZGF0YSA8LSByZWFkX2V4Y2VsKCdFOi9JbmRlcGVuZGVudCBQcm9qZWN0cy9TdGF0ZUNsdXN0ZXJpbmcvTWFzdGVyX0RhdGFzZXQueGxzeCcsIHNoZWV0PSJEYXRhIikNCg0KDQojIENyZWF0ZSBDb3JyZWxhdGlvbnMNCmNvcnJzIDwtIHJvdW5kKGNvcihkYXRhWywtNF0pLDMpDQoNCg0KIyBSZW1vdmUgdGhlIGRpYWdvbmFsIGFuZCByZXBlYXQgY29ycmVsYXRpb25zDQpjb3Jyc1shbG93ZXIudHJpKGNvcnJzKV08LU5BIA0KDQoNCiMgTG9vayBhdCB0aGUgY29ycmVsYXRpb25zIG92ZXIgLjc1IG9yIHVuZGVyIC0uNzUNCiAgaGlnaF9jb3JycyA8LQ0KICBkYXRhLmZyYW1lKGNvcnJzKSAlPiUNCiAgcm93bmFtZXNfdG9fY29sdW1uKCkgJT4lDQogIGdhdGhlcihrZXk9InZhcmlhYmxlIiwgdmFsdWU9ImNvcnJlbGF0aW9uIiwgLXJvd25hbWUpICU+JQ0KICBmaWx0ZXIoYWJzKGNvcnJlbGF0aW9uKSA+IDAuNzUpICU+JQ0KICBhcnJhbmdlKGRlc2MoYWJzKGNvcnJlbGF0aW9uKSkpDQogIA0KICANCiMgQ3JlYXRlIHRhYmxlIG9mIEhpZ2ggQ29ycmVsYXRpb25zIA0KICBrYWJsZShoaWdoX2NvcnJzLCANCiAgICAgIGNvbC5uYW1lcz1jKCJWYXJpYWJsZSAxIiwgIlZhcmlhYmxlIDIiLCJDb3JyZWxhdGlvbiIpLCANCiAgICAgIGxvbmd0YWJsZSA9IFRSVUUpICU+JQ0KICBrYWJsZV9zdHlsaW5nKGJvb3RzdHJhcF9vcHRpb25zID0gYygic3RyaXBlZCIpKSAlPiUgDQogICAgICAgIHJvd19zcGVjKDAsYm9sZD1UUlVFKSANCmBgYA0KDQoNCkR1ZSB0byBoaWdoIGNvcnJlbGF0aW9ucyB3aXRoIG90aGVyIHZhcmlhYmxlcywgSG91c2luZyBVbml0cywgTWVkaWFuIEluZGl2aWR1YWwgSW5jb21lLCBhbmQgUGVyY2VudCBOZXZlciBNYXJyaWVkIHdlcmUgcmVtb3ZlZCBmcm9tIHRoZSBkYXRhIHNldC4gT3RoZXIgc2xpZ2h0bHktbGVzcyBoaWdobHkgY29ycmVsYXRlZCB2YXJpYWJsZXMgd2VyZSBub3QgcmVtb3ZlZC4gU2VsZiBPcmdhbml6aW5nIE1hcHMgZG8gbm90IHJlcXVpcmUgdmFyaWFibGUgaW5kZXBlbmRlbmNlLCBzbyB0aGUgcmVtb3ZhbCBvciBpbmNsdXNpb24gb2YgY29ycmVsYXRlZCB2YXJpYWJsZXMgaXMgbW9yZSBvZiBhbiBleGVyY2lzZSBpbiBqdWRnbWVudCAoSXMgdGhpcyB2YXJpYWJsZSB0cnVseSBkaWZmZXJlbnQgZnJvbSB0aGF0IG9uZT8gQW0gSSBtZWFzdXJpbmcgdGhlIHNhbWUgdW5kZXJseWluZyBkYXRhIHR3aWNlPyBJcyB0aGF0IHVuZGVybHlpbmcgZGF0YSBpbXBvcnRhbnQgZW5vdWdoIHRvIHdhcnJhbnQgYmVpbmcgbWVhc3VyZWQgdHdpY2U/KSwgdGhhbiBhIG1vZGVsbGluZyBuZWNlc3NpdHkuDQoNCk5leHQsIHRoZSByZW1haW5pbmcgdmFyaWFibGVzIHdlcmUgbm9ybWFsaXplZCwgYnkgc3VidHJhY3RpbmcgdGhlaXIgbWVhbnMgYW5kIGRpdmlkaW5nIGJ5IHRoZWlyIHN0YW5kYXJkIGRldmlhdGlvbnMuIFRoaXMgcHV0IGFsbCB0aGUgdmFyaWFibGVzIG9uIHRoZSBzYW1lIHNjYWxlIGFuZCByZW1vdmVkIGFueSBtb2RlbGxpbmcgaW1wYWN0IGZyb20gdGhlaXIgb3JpZ2luYWwsIGhpZ2hseSBkaXNwYXJhdGUgc2NhbGVzLg0KDQpUaGUgZGF0YSB3YXMgdGhlbiB1c2VkIHRvIGNyZWF0ZSBhIFNlbGYgT3JnYW5pemluZyBNYXAsIHVzaW5nIGEgNHg0IGdyaWQgYW5kIDUwMCBpdGVyYXRpb25zLiBUcmFpbmluZyBwcm9ncmVzcyB3YXMgcGxvdHRlZCB0byBkZXRlcm1pbmUgdGhhdCA1MDAgaXRlcmF0aW9ucyB3ZXJlIHN1ZmZpY2llbnQuDQoNCg0KYGBge3J9DQoNCiMgUmVtb3ZlIEhvdXNpbmdfVW5pdHMgZm9yIGhhdmluZyBhIC45OSBjb3JyZWxhdGlvbiB3aXRoIFBvcHVsYXRpb24NCiMgUmVtb3ZlIE1kbl9JbmRfSW5jb21lIGZvciBoYXZpbmcgYSAuODcgY29ycmVsYXRpb24gd2l0aCBCYWNoX2FuZF9BYm92ZSwgYXMgd2VsbCBhcyBhIHN0cm9uZyBjb3JyZWxhdGlvbiB3IDQgb3RoZXIgdmFyaWFibGVzDQojIFJlbW92ZSBQZXJjX052cl9NYXJyaWVkIGZvciBoYXZpbmcgYSAuODUgY29ycmVsYXRpb24gd2l0aCBQb3BfRGVuc2l0eSwgYXMgd2VsbCBhcyBhIHN0cm9uZyBjb3JyZWxhdGlvbiB3IDMgb3RoZXIgdmFyaWFibGVzDQojIFJlbW92ZSBTdGF0ZSwgc2luY2UgaXQgY2FuJ3QgYmUgdXNlZCBpbiB0aGUgU09NDQpkYXRhMiA8LSBkYXRhWywtYyg0LDcsMjksMzEpXQ0KDQoNCiMgTm9ybWFsaXplIGRhdGEgYnkgc3VidHJhY3RpbmcgbWVhbiBhbmQgZGl2aWRpbmcgYnkgcy5kLg0KZGF0YTIgPC0gc2NhbGUoZGF0YTIpDQoNCg0KIyBDaGVjayB0aGUgc3VtbWFyeSB0byB2ZXJpZnkgdGhhdCBzY2FsaW5nIHRvb2sgcGxhY2UNCmRhdF9zdW1tYXJ5PC1zdW1tYXJ5KGRhdGEyKQ0KDQoNCg0KIyBTT00gLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQoNCg0KIyBTZXQgYSBzZWVkDQpzZXQuc2VlZCg4KQ0KDQoNCiMgQ3JlYXRlIGEgNHg0IGdyaWQNCnNvbS5ncmlkIDwtIHNvbWdyaWQoeGRpbSA9IDQsIHlkaW0gPSA0LCB0b3BvID0gImhleGFnb25hbCIpDQoNCg0KIyBDcmVhdGUgYSBtb2RlbCB1c2luZyBvdXIgZ3JpZCBhbmQgNTAwIGl0ZXJhdGlvbnMNCnNvbS5tb2RlbCA8LSBzb20oZGF0YTIsIA0KICAgICAgICAgICAgICAgICBncmlkID0gc29tLmdyaWQsIA0KICAgICAgICAgICAgICAgICBybGVuPTUwMCwgDQogICAgICAgICAgICAgICAgIGFscGhhPWMoMC4wNSwwLjAxKSwgDQogICAgICAgICAgICAgICAgIGtlZXAuZGF0YSA9IFRSVUUgKQ0KDQoNCiMgQ2hlY2sgdHJhaW5pbmcgcHJvZ3Jlc3MNCnRyYWluaW5nX3Bsb3Q8LXBsb3Qoc29tLm1vZGVsLCB0eXBlPSJjaGFuZ2VzIikgICMgR3JhcGggbGV2ZWxzIG91dCwgaW5kaWNhdGluZyB0aGF0IHdlIGRvbid0IG5lZWQgbW9yZSBpdGVyYXRpb25zDQpgYGANCg0KDQpUaGUgbW9kZWwncyBub2RlIGNvdW50cyBhbmQgbmVpZ2hib3IgZGlzdGFuY2VzIHdlcmUgcGxvdHRlZCwgYW5kIHRoZSBXaXRoaW4gQ2x1c3RlciBTdW0gb2YgU3F1YXJlcyB3ZXJlIGNhbGN1bGF0ZWQgZm9yIHVwIHRvIDE1IGNsdXN0ZXJzLiBOb2RlIGNvdW50cyBhbmQgbmVpZ2hib3IgZGlzdGFuY2VzIHdlcmUgbG93LCBidXQgdGhlIHByb2JsZW0gbGFyZ2VseSB3YXNuJ3QgbWl0aWdhdGVkIGJ5IHVzaW5nIGEgc21hbGxlciBncmlkLiBTbWFsbGVyIGdyaWRzIHRlbmRlZCB0byBjb250aW51ZSB0byBoYXZlIHNtYWxsIGRpc3RhbmNlcyBiZXR3ZWVuIG5laWdoYm9yczsgbm9kZSBjb3VudHMgaW5jcmVhc2VkIGZvciBvbmUgb3IgdHdvIG5vZGVzLCBidXQgbW9zdCBub2RlcyByZW1haW5lZCBzcGFyc2UuIFRoZXJlZm9yZSwgdGhlIDR4NCBncmlkIHdhcyBtYWludGFpbmVkLiANCg0KDQoNCmBgYHtyfQ0KIyBDaGVjayBub2RlIGNvdW50cw0KcGxvdChzb20ubW9kZWwsIHR5cGU9ImNvdW50IiwgbWFpbj0iTm9kZSBDb3VudHMiKQ0KYGBgDQoNCmBgYHtyfQ0KIyBDaGVjayBlYWNoIGNlbGwncyBkaXN0YW5jZSBmcm9tIGl0cyBuZWlnaGJvcnMNCnBsb3Qoc29tLm1vZGVsLCB0eXBlPSJkaXN0Lm5laWdoYm91cnMiLCBtYWluID0gIlNPTSBuZWlnaGJvdXIgZGlzdGFuY2VzIikNCmBgYA0KDQpUaGUgcGxvdCBvZiB0aGUgV2l0aGluIENsdXN0ZXIgU3VtIG9mIFNxdWFyZXMgKGJlbG93KSBlbGJvd2VkIGF0IGFyb3VuZCA1IGNsdXN0ZXJzLCBidXQgY29udGludWVkIHRvIGRlY3JlYXNlIHN0ZWFkaWx5IHVudGlsIGFyb3VuZCAxMSBjbHVzdGVycy4gOCB0byAxMSBjbHVzdGVycyB3ZXJlIHRlc3RlZCBhbmQgbWFwcGVkLiBVbHRpbWF0ZWx5LCAxMSBjbHVzdGVycyB3ZXJlIGNob3NlbiBiZWNhdXNlIEhhd2FpaSwgQWxhc2thLCBhbmQgV2FzaGluZ3RvbiwgRC5DLiB3ZXJlIGNvbnNpc3RlbnRseSBwbGFjZWQgaW50byBzaW5nbGUtc3RhdGUgY2x1c3RlcnMgYnkgdGhlbXNlbHZlcywgbGVhdmluZyBsZXNzIGNsdXN0ZXJzIGZvciB0aGUgb3RoZXIgNDggc3RhdGVzLg0KDQpgYGB7cn0NCiMgR2V0IHRoZSB3aXRoaW4gY2x1c3RlciBzdW0gb2Ygc3F1YXJlcyANCndzcyA8LSAobnJvdyhnZXRDb2Rlcyhzb20ubW9kZWwpKS0xKSpzdW0oYXBwbHkoZ2V0Q29kZXMoc29tLm1vZGVsKSwyLHZhcikpIA0KDQojIFBsb3QgdGhlIHdpdGhpbiBjbHVzdGVyIHN1bSBvZiBzcXVhcmVzIGZvciB1cCB0byAxNSBjbHVzdGVycw0KZm9yIChpIGluIDI6MTUpIHsNCiAgd3NzW2ldIDwtIHN1bShrbWVhbnMoZ2V0Q29kZXMoc29tLm1vZGVsKSwgY2VudGVycz1pKSR3aXRoaW5zcykNCn0NCnBsb3Qod3NzLCBtYWluID0gIldpdGhpbiBDbHVzdGVyIFN1bSBvZiBTcXVhcmVzIGJ5ICMgb2YgQ2x1c3RlcnMiKQ0KYGBgDQoNCg0KVGhlIGNsdXN0ZXJlZCBTZWxmIE9yZ2FuaXppbmcgTWFwIGdyaWQgd2FzIHByb2R1Y2VkIGZvciB0aGUgMTEgY2x1c3RlciBtb2RlbC4gDQpgYGB7cn0NCiMgTG9vayBhdCBlYWNoIHZhcmlhYmxlJ3MgaGVhdCBtYXAgYnkgY2hhbmdpbmcgaw0KI2s9MzINCiNwYXIobWZyb3c9Yyg0LDgpKQ0KI2ZvciAoayBpbiAxOjMyKXsNCiNwbG90KHNvbS5tb2RlbCwgdHlwZSA9ICJwcm9wZXJ0eSIsIHByb3BlcnR5ID0gZ2V0Q29kZXMoc29tLm1vZGVsKVssa10sIA0KIyAgICAgbWFpbj1jb2xuYW1lcyhnZXRDb2Rlcyhzb20ubW9kZWwpKVtrXSkNCiN9DQoNCiMgVXNlIGhpZXJhcmNoaWNhbCBjbHVzdGVyaW5nIHRvIGdyb3VwIHRoZSB2ZWN0b3JzIGludG8gMTEgY2x1c3RlcnMNCnNvbS5jbHVzdGVyIDwtIGN1dHJlZShoY2x1c3QoZGlzdChnZXRDb2Rlcyhzb20ubW9kZWwpKSksIDExKQ0KDQoNCiMgUGxvdCB0aGUgY2x1c3RlcnMgLSB0aGlzIHZpZXcgdXNlcyAyIGNvbG9ycyB0d2ljZQ0KI3Bsb3Qoc29tLm1vZGVsLCB0eXBlPSJtYXBwaW5nIiwgDQojICAgICBiZ2NvbCA9IHNvbS5jbHVzdGVyLA0KIyAgICAgbWFpbiA9ICJDbHVzdGVycyIpIA0KDQoNCiMgUGxvdCB0aGUgY2x1c3RlcnMgLSB0aGlzIHZpZXcgdXNlcyBjdXN0b20gY29sb3JzDQpwbG90KHNvbS5tb2RlbCwgdHlwZT0ibWFwcGluZyIsIA0KICAgICBiZ2NvbCA9IGMoImFxdWFtYXJpbmUiLCJiZWlnZSIsImNvcm5mbG93ZXJibHVlIiwiZGFya3NlYWdyZWVuIiwibGF2ZW5kZXIiLCJtaXN0eXJvc2UyIiwiY29ybmZsb3dlcmJsdWUiLCJzbGF0ZWdyYXkyIiwiY2FkZXRibHVlMSIsIm1pc3R5cm9zZTIiLCJ3aGVhdDMiLCJsaWdodHBpbmsiLCJncmF5NzEiLCJncmF5NzEiLCJ3aGVhdDMiLCJ3aGVhdDMiKSwNCiAgICAgbWFpbiA9ICJDbHVzdGVycyIpIA0KYWRkLmNsdXN0ZXIuYm91bmRhcmllcyhzb20ubW9kZWwsIHNvbS5jbHVzdGVyKQ0KYGBgDQoNCg0KVGhlIGZpbmFsIG1hcHBpbmcgb2YgdGhlIDExIGNsdXN0ZXJzIGlzIGJlbG93LiBXYXNoaW5ndG9uLCBELkMuIGlzIENsdXN0ZXIgMiwgYWx0aG91Z2ggaXQgY2Fubm90IGJlIHNlZW4gb24gdGhlIG5hdGlvbmFsIG1hcC4NCg0KYGBge3J9DQojIFNPTSBNYXBwaW5nIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KDQoNCiMgQ3JlYXRlIGEgdmVjdG9yIG9mIHRoZSBzdGF0ZXMnIGFzc2lnbmVkIGNsdXN0ZXJzDQpjbHVzdGVyLmFzc2lnbm1lbnQgPC0gc29tLmNsdXN0ZXJbc29tLm1vZGVsJHVuaXQuY2xhc3NpZl0NCg0KDQojIEFkZCBhc3NpZ25lZCBjbHVzdGVycyB0byB0aGUgZGF0YXNldA0KZGF0YSRjbHVzdGVyIDwtIGNsdXN0ZXIuYXNzaWdubWVudA0KDQoNCiMgQ3JlYXRlIG1hcCBkYXRhDQptYXBfZGF0YSA8LSBkYXRhWyxjKDQsMzcpXQ0KbWFwX2RhdGEkZmlwcyA8LSBmaXBzKG1hcF9kYXRhJFN0YXRlKQ0KbWFwX2RhdGEgPC0gbWFwX2RhdGFbLGMoMywyKV0NCm1hcF9kYXRhJGNsdXN0ZXIgPC0gYXMuZmFjdG9yKG1hcF9kYXRhJGNsdXN0ZXIpDQoNCg0KIyBDcmVhdGUgdGhlIG1hcA0KcGxvdF91c21hcChyZWdpb25zPSJzdGF0ZXMiLCBkYXRhID0gbWFwX2RhdGEsIHZhbHVlcyA9ICJjbHVzdGVyIiwgY29sb3IgPSAiYmxhY2siKSArIA0KIHNjYWxlX2ZpbGxfZGlzY3JldGUobmFtZSA9ICJDbHVzdGVyIikgKyANCiAgICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAicmlnaHQiKSANCmBgYA0KDQoNClRoZSB1bmlxdWUgZmVhdHVyZXMgb2YgdGhlIGNsdXN0ZXJzIHdlcmUgdGhlbiBhc3Nlc3NlZCB1c2luZyBib3ggcGxvdHMgKG5vdCBzaG93bjsgc2VlIGNvZGUgYmVsb3cpLg0KYGBge3J9DQojIEFzc2VzcyBjbHVzdGVycyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCg0KDQojIFB1dCBkYXRhIGluIGEgZGF0YSBmcmFtZSwgcmVtb3ZlIHZhcmlhYmxlcyB0aGF0IHdlcmVuJ3QgdXNlZCBmb3IgYW5hbHlzaXMNCmRhdGE8LWFzLmRhdGEuZnJhbWUoZGF0YVssLWMoNywyOSwzMSldKQ0KDQoNCiMgR2V0IGNsdXN0ZXIgY291bnRzDQpkYXRhICU+JSANCiAgY291bnQoY2x1c3RlcikNCg0KDQojIENyZWF0ZSBsaXN0IG9mIHN0YXRlcyBieSBjbHVzdGVyDQpzdGF0ZV9saXN0IDwtDQogICBkYXRhICU+JQ0KICAgZ3JvdXBfYnkoY2x1c3RlcikgJT4lDQogICBjb3VudChTdGF0ZSkNCg0KDQojIENyZWF0ZSBib3hwbG90cyBmb3IgZWFjaCB2YXJpYWJsZQ0KZm9yIChuIGluIGMoMTozLDU6MzMpKXsNCiAgICBib3hwbG90KGRhdGFbLG5dfmRhdGEkY2x1c3RlciwgDQogICAgICAgICAgeWxhYj1jb2xuYW1lcyhkYXRhKVtuXSwNCiAgICAgICAgICBtYWluPWNvbG5hbWVzKGRhdGEpW25dKQ0KfQ0KYGBgDQoNClRoZSBmaW5hbCBjbHVzdGVycyB3ZXJlOg0KDQoNCjxiPkNsdXN0ZXIgMSAtIEhhd2FpaTwvYj4NCg0KIEhhd2FpaSBoYXMgdGhlIGhpZ2hlc3Qgd2F0ZXIgdG8gbGFuZCBhcmVhIHJhdGlvLCBhcyB3ZWxsIGFzIHRoZSBoaWdoZXN0IG1peGVkIHJhY2UgYW5kIEFzaWFuIHBvcHVsYXRpb25zLCBsb3cgaW5jYXJjZXJhdGlvbiBhbmQgcG92ZXJ0eSwgYW5kIGEgaGlnaCBwZXJjZW50YWdlIG9mIHJlc2lkZW50cyB3aXRoIGEgSGlnaCBTY2hvb2wgZWR1Y2F0aW9uIG9yIGFib3ZlLg0KDQo8YnIvPg0KDQo8Yj5DbHVzdGVyIDIgLSBXYXNoaW5ndG9uIERDPC9iPg0KDQogRC5DLiBoYXMgZXh0cmVtZWx5IGhpZ2ggcG9wdWxhdGlvbiBkZW5zaXR5IGFuZCBwZXJjZW50IG9mIHJlc2lkZW50cyB3aXRoIGEgQmFjaGVsb3JzIGRlZ3JlZSBvciBhYm92ZSwgYXMgd2VsbCBhcyBhIGhpZ2ggbWluaW11bSB3YWdlLCBoaWdoIHVuZW1wbG95bWVudCwgYW5kIHRoZSBsYXJnZXN0ICUgZmVtYWxlIHBvcHVsYXRpb24uDQoNCjxici8+DQoNCjxiPkNsdXN0ZXIgMyAtIENvbm5lY3RpY3V0LCBEZWxhd2FyZSwgTWFyeWxhbmQsIE1hc3NhY2h1c2V0dHMsIE5ldyBKZXJzZXksIFJob2RlIElzbGFuZCwgVmlyZ2luaWE8L2I+DQoNCiBUaGVzZSBzdGF0ZXMgYXJlIGhpZ2hlciBpbmNvbWUgYW5kIGhhdmUgYSBoaWdoIHBlcmNlbnQgb2YgcmVzaWRlbnRzIHdpdGggYSBCYWNoZWxvcnMgZGVncmVlIG9yIGFib3ZlLiBUaGVpciBwb3B1bGF0aW9uIGRlbnNpdGllcyBhcmUgYWJvdmUgYXZlcmFnZSBhbmQgdGhleSBleHBlcmllbmNlZCBhYm92ZSBhdmVyYWdlIENvcm9uYXZpcnVzIGRlYXRocyBwZXIgMTAwLDAwMCBwb3B1bGF0aW9uIGluIDIwMjAuDQoNCjxici8+DQoNCjxiPkNsdXN0ZXIgNCAtIE1haW5lLCBOZXcgSGFtcHNoaXJlLCBWZXJtb250PC9iPg0KDQogVGhlc2UgdGhyZWUgc3RhdGVzIGFyZSBkaXN0aW5jdCBmcm9tIHRoZSByZXN0IG9mIE5ldyBFbmdsYW5kLiBUaGV5IGFyZSBzbWFsbCBzdGF0ZXMgd2l0aCBzbWFsbCwgbGFyZ2VseSB3aGl0ZSBwb3B1bGF0aW9ucy4gVGhleSBoYXZlIHRoZSBoaWdoZXN0IHBlcmNlbnQgdmFjYW50IGhvdXNpbmcgb2YgYW55IGNsdXN0ZXIsIGJ1dCB0aGUgbG93ZXN0IHBlcmNlbnQgb2YgcmVzaWRlbnRzIHdobyBjb25zaWRlcmVkIHRoZW1zZWx2ZXMgdmVyeSByZWxpZ2lvdXMuDQoNCjxici8+DQoNCjxiPkNsdXN0ZXIgNSAtIEFyaXpvbmEsIENhbGlmb3JuaWEsIE5ldmFkYSwgTmV3IE1leGljbywgVGV4YXM8L2I+DQoNCiBUaGVzZSBsYXJnZSwgU291dGgtV2VzdGVybiBzdGF0ZXMgaGF2ZSBhYm92ZSBhdmVyYWdlIGFyZWFzIGFuZCB0aGUgaGlnaGVzdCBhbW91bnRzIG9mIEhpc3BhbmljIHJlc2lkZW50cy4gVGhlc2Ugc3RhdGVzIGhhdmUgYmVlbiBncm93aW5nIGluIHBvcHVsYXRpb24sIGFuZCB0aGlzIGNsdXN0ZXIgaGFzIHRoZSBsb3dlc3QgcGVyY2VudCBvZiByZXNpZGVudHMgd2l0aCBhIEhpZ2ggU2Nob29sIGVkdWNhdGlvbiBvciBhYm92ZS4NCg0KPGJyLz4NCg0KPGI+Q2x1c3RlciA2IC0gSWxsaW5vaXMsIE1pY2hpZ2FuLCBOZXcgWW9yaywgT2hpbywgUGVubnN5bHZhbmlhPC9iPg0KDQogVGhpcyBjbHVzdGVyIGlzIGV4dHJlbWVseSBhdmVyYWdlIGluIGFsbW9zdCBldmVyeSBtZWFzdXJlLCBvdGhlciB0aGFuIHBvcHVsYXRpb24gY2hhbmdlIHNpbmNlIDIwMTAuIFRoZXNlIHN0YXRlcyBoYXZlIGdyb3duIG1vcmUgc2xvd2x5IHRoYW4gYXZlcmFnZSBvdmVyIHRoZSBwYXN0IDEwIHllYXJzLiANCg0KPGJyLz4NCg0KPGI+Q2x1c3RlciA3IC0gQ29sb3JhZG8sIE9yZWdvbiwgV2FzaGluZ3RvbjwvYj4NCg0KIFRoZXNlIHRocmVlIFdlc3Rlcm4gc3RhdGVzIGhhdmUgYWJvdmUgYXZlcmFnZSBOYXRpdmUgYW5kIFBhY2lmaWMgSXNsYW5kZXIgcG9wdWxhdGlvbnMgYW5kIGhpZ2ggbWluaW11bSB3YWdlcy4gVGhleSBoYXZlIGJlZW4gZ3Jvd2luZyByYXBpZGx5IGFuZCBoYXZlIGhpZ2ggdm90ZXIgdHVybm91dC4gDQoNCjxici8+DQoNCjxiPkNsdXN0ZXIgOCAtIEZsb3JpZGEsIEdlb3JnaWEsIE5vcnRoIENhcm9saW5hPC9iPg0KDQogVGhlc2UgdGhyZWUgU291dGhlcm4gc3RhdGVzIGhhdmUgYSBoaWdoZXIgdGhhbiBhdmVyYWdlIHBlcmNlbnQgYmxhY2sgcmVzaWRlbnRzLiBUaGV5IGFsc28gaGF2ZSBsb3cgbWluaW11bSB3YWdlcyBhbmQgbG93IG1lZGlhbiBob3VzZWhvbGQgaW5jb21lcy4gQW4gYWJvdmUgYXZlcmFnZSBhbW91bnQgb2YgdGhlaXIgcmVzaWRlbnRzIHJlcG9ydCBiZWluZyB2ZXJ5IHJlbGlnaW91cy4NCg0KPGJyLz4NCg0KPGI+Q2x1c3RlciA5IC0gSWRhaG8sIEluZGlhbmEsIElvd2EsIEthbnNhcywgTWlubmVzb3RhLCBNaXNzb3VyaSwgTW9udGFuYSwgTmVicmFza2EsIE5vcnRoIERha290YSwgU291dGggRGFrb3RhLCBVdGFoLCBXaXNjb25zaW4sIFd5b21pbmc8L2I+DQoNCiBUaGlzIGNsdXN0ZXIgaGFkIHRoZSBsb3dlc3QgbWluaW11bSB3YWdlcywgYXMgd2VsbCBhcyBoaWdoIHBvc2l0aXZlIENPVklEIHRlc3RzIHBlciAxMDAsMDAwLiBBIGxhcmdlIHBlcmNlbnQgb2YgdGhlaXIgcG9wdWxhdGlvbnMgaGFzIGEgSGlnaCBTY2hvb2wgZWR1Y2F0aW9uIG9yIGFib3ZlLiBUaGV5IGFyZSBwcmVkb21pbmFudGx5IHdoaXRlLCB3aXRoIGFib3ZlIGF2ZXJhZ2UgTmF0aXZlIHBvcHVsYXRpb25zLg0KDQo8YnIvPg0KDQo8Yj5DbHVzdGVyIDEwIC0gQWxhc2thPC9iPg0KDQogQWxhc2thIGhhcyB0aGUgaGlnaGVzdCBsYW5kIGFyZWEgYW5kIGEgc21hbGwgcG9wdWxhdGlvbi4gSXQgZXhwZXJpZW5jZWQgaGlnaCB1bmVtcGxveW1lbnQgaW4gMjAyMCBhbmQgaGFzIHRoZSBsb3dlc3QgcGVyY2VudCBmZW1hbGUgcG9wdWxhdGlvbiBvZiBhbnkgc3RhdGUuIEl0IGFsc28gaGFzIGEgbGFyZ2UgYW1vdW50IG9mIHZhY2FudCBob3VzaW5nIGFuZCBhIGxhcmdlIFBhY2lmaWMgSXNsYW5kZXIgcG9wdWxhdGlvbi4NCg0KPGJyLz4NCg0KPGI+Q2x1c3RlciAxMSAtIEFsYWJhbWEsIEFya2Fuc2FzLCBLZW50dWNreSwgTG91aXNpYW5hLCBNaXNzaXNzaXBwaSwgT2tsYWhvbWEsIFNvdXRoIENhcm9saW5hLCBUZW5uZXNzZWUsIFdlc3QgVmlyZ2luaWE8L2I+DQoNCiBDbHVzdGVyIDExIGhhcyB0aGUgaGlnaGVzdCBwZXJjZW50IG9mIGl0cyBwb3B1bGF0aW9uIGluY2FyY2VyYXRlZC4gSXQgaGFzIGxvdyBtaW5pbXVtIHdhZ2VzIGFuZCB0aGUgbG93ZXN0IHBlcmNlbnQgb2YgcG9wdWxhdGlvbiB3aXRoIGEgQmFjaGVsb3JzIGRlZ3JlZSBhbmQgYWJvdmUuIEl0IGlzIHRoZSBjbHVzdGVyIHdpdGggdGhlIGdyZWF0ZXN0IHByb3BvcnRpb24gb2YgaW5kaXZpZHVhbHMgcmVwb3J0aW5nIHRoZW1zZWx2ZXMgYXMgdmVyeSByZWxpZ2lvdXMsIGFzIHdlbGwgYXMgdGhlIGdyZWF0ZXN0IHByb3BvcnRpb24gb2YgaW5kaXZpZHVhbHMgd2l0aG91dCBhY2Nlc3MgdG8gYW4gaW50ZXJuZXQtY29ubmVjdGVkIGNvbXB1dGVyLg0KDQo8YnIvPg0KDQoNCg0K