The Atlas of North American English was a telephone survey of large urban centers in the United States and Canada conducted in the mid 1990s. This analysis of /ay/ allophony involves

library(lsa2017)
library(ggmap)
Loading required package: ggplot2
Google Maps API Terms of Service: http://developers.google.com/maps/terms.
Please cite ggmap if you use it: see citation('ggmap') for details.
library(tidyverse)
Loading tidyverse: tibble
Loading tidyverse: tidyr
Loading tidyverse: readr
Loading tidyverse: purrr
Loading tidyverse: dplyr
package ‘tibble’ was built under R version 3.4.1package ‘purrr’ was built under R version 3.4.2package ‘dplyr’ was built under R version 3.4.2Conflicts with tidy packages -----------------------------------------------------------------------------------------------------------------
filter(): dplyr, stats
lag():    dplyr, stats
library(stringr)
head(anae)

Geocoding

I geocoded the data using the Data Science Toolkit API.

citstate_df <- anae %>% 
                mutate(country = recode(dialect, Canada = "Canada", .default = "USA"))%>%
                group_by(city, state, country)%>%
                tally()%>%
                mutate(citstate = str_c(city, state, country, sep = ", "))
geos_dsk <- geocode(citstate_df$citstate, source = "dsk")
geocoded <- bind_cols(citstate_df, geos_dsk)
ggplot(geocoded, aes(lon, lat))+
    geom_point()

summary(geocoded)
          city         state       country          n            citstate              lon               lat       
 Springfield:  4   IL     : 15   USA   :240   Min.   : 141.0   Length:254         Min.   :-149.86   Min.   :25.89  
 Charleston :  2   OH     : 14   Canada: 14   1st Qu.: 283.5   Class :character   1st Qu.: -96.81   1st Qu.:37.85  
 Columbia   :  2   WI     : 12                Median : 411.5   Mode  :character   Median : -87.85   Median :40.79  
 Greenville :  2   IN     : 10                Mean   : 509.0                      Mean   : -89.98   Mean   :40.30  
 KansasCity :  2   MN     : 10                3rd Qu.: 636.8                      3rd Qu.: -80.86   3rd Qu.:43.01  
 Norfolk    :  2   NJ     : 10                Max.   :2429.0                      Max.   : -68.85   Max.   :61.22  
 (Other)    :240   (Other):183                                                                                     

Normalization

Normalization is easy with the split-apply-combine workflow.

anae <- anae %>%
          group_by(speakerID)%>%
          mutate(F1_n = (F1 - mean(F1))/sd(F1),
                 F2_n = (F2 - mean(F2))/sd(F2)) %>%
        left_join(geocoded)
head(anae)

Focusing on /ay/

I’m going to subset the data and recode the voicing contrast.

ays <- anae %>% 
        ungroup() %>%
        filter(vclass %in% c("ay", "ay0"),
               fol_seg %in% c("B", "D", "DH", "F", "G", "K" , "M", "N", "P", "S", "T", "V", "Z"),
               lon > -140)%>%
        mutate(voicing = recode(as.character(fol_seg),
                                B = "voiced", 
                                D = "voiced",
                                DH = "voiced",
                                `F` = "voiceless",
                                G = "voiced",
                                K = "voiceless",
                                M = "voiced",
                                N = "voiced",
                                P = "voiceless",
                                S = "voiceless",
                                `T` = "voiceless",
                                V = "voiced",
                                Z = "voiced"),
               voicing = factor(voicing),
               citstate = factor(citstate),
               word = factor(word),
               speakerID = factor(speakerID))

Gamm modelling

I fit a tensor-product smooth over latitude and longitude, including voicing as an effect. This’ll let me get difference curves, which is what’s really of interest. I also included random effects of word, speaker, and city. I’ll come back to the city effects in a moment.

library(mgcv)
Loading required package: nlme

Attaching package: ‘nlme’

The following object is masked from ‘package:dplyr’:

    collapse

This is mgcv 1.8-17. For overview type 'help("mgcv-package")'.
ays_model <- gam(F1_n ~ voicing + te(lon, lat, by = voicing, k = 10)+
                          s(speakerID, bs = 're', by = voicing)+
                          s(citstate, bs = 're', by = voicing)+
                          s(word, bs = 're'), data = ays)

It took a long time to fit, so saving for safety.

saveRDS(ays_model, "ays_model.RDS")

According to gam.check(), the parameterization of the smoothers was pretty ok.

gam.check(ays_model)


Method: GCV   Optimizer: magic
Smoothing parameter selection converged after 96 iterations.
The RMS GCV score gradient at convergence was 2.124059e-05 .
The Hessian was not positive definite.
Model rank =  2034 / 2034 

Basis dimension (k) checking results. Low p-value (k-index<1) may
indicate that k is too low, especially if edf is close to k'.

                                 k'   edf k-index p-value
te(lon,lat):voicingvoiced      99.0   8.2    1.01    0.78
te(lon,lat):voicingvoiceless   99.0  15.1    1.01    0.78
s(speakerID):voicingvoiced    426.0 186.0      NA      NA
s(speakerID):voicingvoiceless 426.0 167.3      NA      NA
s(citstate):voicingvoiced     253.0  20.4      NA      NA
s(citstate):voicingvoiceless  253.0  13.6      NA      NA
s(word)                       476.0 134.5      NA      NA

The summary is kind of useless.

summary(ays_model)

Family: gaussian 
Link function: identity 

Formula:
F1_n ~ voicing + te(lon, lat, by = voicing, k = 10) + s(speakerID, 
    bs = "re", by = voicing) + s(citstate, bs = "re", by = voicing) + 
    s(word, bs = "re")

Parametric coefficients:
                 Estimate Std. Error t value Pr(>|t|)    
(Intercept)       1.05421    0.02207  47.770  < 2e-16 ***
voicingvoiceless -0.25415    0.03361  -7.562 4.71e-14 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Approximate significance of smooth terms:
                                  edf  Ref.df      F  p-value    
te(lon,lat):voicingvoiced       8.205   9.098  5.710 5.87e-08 ***
te(lon,lat):voicingvoiceless   15.061  17.293 15.522  < 2e-16 ***
s(speakerID):voicingvoiced    185.972 421.000  1.185  < 2e-16 ***
s(speakerID):voicingvoiceless 167.318 399.000  1.185  < 2e-16 ***
s(citstate):voicingvoiced      20.421 249.000  0.150  0.01977 *  
s(citstate):voicingvoiceless   13.645 240.000  0.103  0.00593 ** 
s(word)                       134.533 474.000  1.140  < 2e-16 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

R-sq.(adj) =  0.335   Deviance explained = 40.1%
GCV = 0.24619  Scale est. = 0.22184   n = 5532

Plotting the results

library(itsadug)

First, get the predicted values across a grid of longitudes and latitudes, excluding the random effects.

bbox <- c(min(ays$lon)-5, min(ays$lat)-2, max(ays$lon)+5, max(ays$lat)+2)
ays_diff <- get_difference(ays_model, 
                           comp = list(voicing = c("voiced", "voiceless")),
                           cond = list(lon = seq(bbox[1], bbox[3], length = 100),
                                       lat = seq(bbox[2], bbox[4], length = 100)),
                           rm.ranef = T)
Summary:
    * lon : numeric predictor; with 100 values ranging from -128.119340 to -63.850405. 
    * lat : numeric predictor; with 100 values ranging from 23.893664 to 55.550140. 
    * speakerID : factor; set to the value(s): 345. (Might be canceled as random effect, check below.) 
    * citstate : factor; set to the value(s): Atlanta, GA, USA. (Might be canceled as random effect, check below.) 
    * word : factor; set to the value(s): five. (Might be canceled as random effect, check below.) 
    * NOTE : The following random effects columns are canceled: s(speakerID):voicingvoiced,s(speakerID):voicingvoiceless,s(citstate):voicingvoiced,s(citstate):voicingvoiceless,s(word)
 

A practice plot, using geom_contour()

ays_diff %>%
  ggplot(aes(lon, lat))+
    geom_contour(aes(z = difference, color = ..level..))

Next, get a basemap. I went with a black and white basemap so that it won’t compete with the colors of the plot, but that could be revisited.

bbox <- c(min(ays$lon)-5, min(ays$lat)-2, max(ays$lon)+5, max(ays$lat)+2)
na_map <- get_map(location = bbox, maptype = "toner")
ggmap(na_map)

Overlaying the contours on the map.

ggmap(na_map)+
  geom_contour(data = ays_diff, aes(z = difference, color = ..level..), size= 1)+
  scale_color_gradient2()

The one major downside of this is that the difference between regions might be actually quite a bit sharper than the gamms show. I want to include cities that are very different from their predicted geographic values. This involves:

This involves messing around with matrix multiplication, sorry.

pre_pred <- geocoded %>% 
              ungroup()%>%
              select(citstate, lon, lat)%>%
              mutate(speakerID = 1,
                     word = "five") %>%
              filter(lon > -140)
pred_df <- bind_rows(pre_pred %>% mutate(voicing = "voiced"),
                     pre_pred %>% mutate(voicing = "voiceless")) %>%
            mutate(voicing = factor(voicing))
pred_df_nocit <- pred_df
pred_matrix <- predict(ays_model, newdata = pred_df, type = "lpmatrix")
pred_matrix[,grepl("speakerID", colnames(pred_matrix))] <- 0
pred_matrix[,grepl("word", colnames(pred_matrix))] <- 0
pred_matrix_nocit <- pred_matrix
pred_matrix_nocit[,grepl("citstate", colnames(pred_matrix))] <- 0
pred_df$fit <- (pred_matrix %*% coef(ays_model))[,1]
pred_df_nocit$fit <- (pred_matrix_nocit %*% coef(ays_model))[,1]
pred_df <- pred_df %>% 
            spread(voicing, fit) %>% 
            mutate(citdiff = voiced - voiceless)%>%
            select(citstate, lon, lat, citdiff)
pred_df_nocit <- pred_df_nocit %>% 
                    spread(voicing, fit) %>% 
                    mutate(no_citdiff = voiced - voiceless)%>%
                    select(citstate, lon, lat, no_citdiff)
all_pred <- left_join(pred_df, pred_df_nocit)
Joining, by = c("citstate", "lon", "lat")

⬆️ all_pred now contains a column of the predicted difference between pre-voiced and pre-voiceless /ay/ based on its location + random effect (citdiff) and just its location (no_citdiff) and the difference between them (extradiff). I’ll plot just the cities that have the biggest absolute extradiff (they’re maximally different from their predicted values based on location alone).

big_ranef_cities <- all_pred %>% 
                        mutate(extradiff = citdiff - no_citdiff)%>%
                        filter(abs(extradiff) > 0.02)
library(ggthemes)
pal <- ptol_pal()(3)
ggmap(na_map)+
  geom_contour(data = ays_diff, aes(z = difference, color = ..level..), size= 1)+
  geom_point(data = big_ranef_cities, aes(color = citdiff, size = abs(extradiff)))+
  scale_color_gradient2(low = pal[1], mid = pal[2], high = pal[3],
                        limits = c(min(all_pred$citdiff), max(all_pred$citdiff)))

I waffled on what the right thing to do for the map was. I could color each point according to how different it was from its surrounding area, but I think looking at a map like this you’d expect each point to be colored according to its predicted value, so that’s what I did. What gets lost is, for example, the fact that Philadelphia has a larger difference than predicted for its location while NYC has a much smaller difference. The barplot below is an attempt to show this, bit it loses the geographic information.

big_ranef_cities%>%
  mutate(citstate = factor(citstate),
         citstate = reorder(citstate, extradiff, mean))%>%
  ggplot(aes(citstate, extradiff))+
    geom_bar(stat = 'identity', aes(fill = no_citdiff))+
    scale_fill_gradient2(low = pal[1], mid = pal[2], high = pal[3])+
    coord_flip()

library(scales)
ggmap(na_map)+
  geom_point(data = all_pred, aes(color = citdiff - no_citdiff, size=abs(citdiff - no_citdiff)))+
   scale_color_gradient2(low = muted("blue"), high = muted("red"), mid = "grey")

LS0tCnRpdGxlOiAiRGlzdHJpYnV0aW9uIG9mIC9heS8gYWxsb3Bob255IGluIHRoZSBBTkFFIgpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKLS0tCgpUaGUgQXRsYXMgb2YgTm9ydGggQW1lcmljYW4gRW5nbGlzaCB3YXMgYSB0ZWxlcGhvbmUgc3VydmV5IG9mIGxhcmdlIHVyYmFuIGNlbnRlcnMgaW4gdGhlIFVuaXRlZCBTdGF0ZXMgYW5kIENhbmFkYSBjb25kdWN0ZWQgaW4gdGhlIG1pZCAxOTkwcy4gVGhpcyBhbmFseXNpcyBvZiAvYXkvIGFsbG9waG9ueSBpbnZvbHZlcwoKLSBub3JtYWxpemluZyB0aGUgZGF0YQotIGdlb2NvZGluZyB0aGUgdGhlIGRhdGEKLSBmaXR0aW5nIGEgZ2FtbQotIEZpZ3VyaW5nIG91dCB0aGUgYmVzdCB3YXlzIHRvIHByZXNlbnQgdGhlIGdhbW0gcmVzdWx0cy4KCmBgYHtyfQpsaWJyYXJ5KGxzYTIwMTcpCmBgYAoKYGBge3J9CmxpYnJhcnkoZ2dtYXApCmBgYApgYGB7cn0KbGlicmFyeSh0aWR5dmVyc2UpCmxpYnJhcnkoc3RyaW5ncikKYGBgCgoKYGBge3J9CmhlYWQoYW5hZSkKYGBgCgojIyBHZW9jb2RpbmcgCgpJIGdlb2NvZGVkIHRoZSBkYXRhIHVzaW5nIHRoZSBEYXRhIFNjaWVuY2UgVG9vbGtpdCBBUEkuCmBgYHtyfQpjaXRzdGF0ZV9kZiA8LSBhbmFlICU+JSAKICAgICAgICAgICAgICAgIG11dGF0ZShjb3VudHJ5ID0gcmVjb2RlKGRpYWxlY3QsIENhbmFkYSA9ICJDYW5hZGEiLCAuZGVmYXVsdCA9ICJVU0EiKSklPiUKICAgICAgICAgICAgICAgIGdyb3VwX2J5KGNpdHksIHN0YXRlLCBjb3VudHJ5KSU+JQogICAgICAgICAgICAgICAgdGFsbHkoKSU+JQogICAgICAgICAgICAgICAgbXV0YXRlKGNpdHN0YXRlID0gc3RyX2MoY2l0eSwgc3RhdGUsIGNvdW50cnksIHNlcCA9ICIsICIpKQpgYGAKCgoKYGBge3J9Cmdlb3NfZHNrIDwtIGdlb2NvZGUoY2l0c3RhdGVfZGYkY2l0c3RhdGUsIHNvdXJjZSA9ICJkc2siKQpgYGAKCmBgYHtyfQpnZW9jb2RlZCA8LSBiaW5kX2NvbHMoY2l0c3RhdGVfZGYsIGdlb3NfZHNrKQpgYGAKCmBgYHtyfQpnZ3Bsb3QoZ2VvY29kZWQsIGFlcyhsb24sIGxhdCkpKwogICAgZ2VvbV9wb2ludCgpCmBgYApgYGB7cn0Kc3VtbWFyeShnZW9jb2RlZCkKYGBgCgoKIyMgTm9ybWFsaXphdGlvbgoKTm9ybWFsaXphdGlvbiBpcyBlYXN5IHdpdGggdGhlIHNwbGl0LWFwcGx5LWNvbWJpbmUgd29ya2Zsb3cuCgpgYGB7cn0KYW5hZSA8LSBhbmFlICU+JQogICAgICAgICAgZ3JvdXBfYnkoc3BlYWtlcklEKSU+JQogICAgICAgICAgbXV0YXRlKEYxX24gPSAoRjEgLSBtZWFuKEYxKSkvc2QoRjEpLAogICAgICAgICAgICAgICAgIEYyX24gPSAoRjIgLSBtZWFuKEYyKSkvc2QoRjIpKSAlPiUKICAgICAgICBsZWZ0X2pvaW4oZ2VvY29kZWQpCmBgYAoKYGBge3J9CmhlYWQoYW5hZSkKYGBgCgojIyBGb2N1c2luZyBvbiAvYXkvCgpJJ20gZ29pbmcgdG8gc3Vic2V0IHRoZSBkYXRhIGFuZCByZWNvZGUgdGhlIHZvaWNpbmcgY29udHJhc3QuCgpgYGB7cn0KYXlzIDwtIGFuYWUgJT4lIAogICAgICAgIHVuZ3JvdXAoKSAlPiUKICAgICAgICBmaWx0ZXIodmNsYXNzICVpbiUgYygiYXkiLCAiYXkwIiksCiAgICAgICAgICAgICAgIGZvbF9zZWcgJWluJSBjKCJCIiwgIkQiLCAiREgiLCAiRiIsICJHIiwgIksiICwgIk0iLCAiTiIsICJQIiwgIlMiLCAiVCIsICJWIiwgIloiKSwKICAgICAgICAgICAgICAgbG9uID4gLTE0MCklPiUKICAgICAgICBtdXRhdGUodm9pY2luZyA9IHJlY29kZShhcy5jaGFyYWN0ZXIoZm9sX3NlZyksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgQiA9ICJ2b2ljZWQiLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBEID0gInZvaWNlZCIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgREggPSAidm9pY2VkIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBgRmAgPSAidm9pY2VsZXNzIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBHID0gInZvaWNlZCIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgSyA9ICJ2b2ljZWxlc3MiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIE0gPSAidm9pY2VkIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBOID0gInZvaWNlZCIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgUCA9ICJ2b2ljZWxlc3MiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFMgPSAidm9pY2VsZXNzIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBgVGAgPSAidm9pY2VsZXNzIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBWID0gInZvaWNlZCIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgWiA9ICJ2b2ljZWQiKSwKICAgICAgICAgICAgICAgdm9pY2luZyA9IGZhY3Rvcih2b2ljaW5nKSwKICAgICAgICAgICAgICAgY2l0c3RhdGUgPSBmYWN0b3IoY2l0c3RhdGUpLAogICAgICAgICAgICAgICB3b3JkID0gZmFjdG9yKHdvcmQpLAogICAgICAgICAgICAgICBzcGVha2VySUQgPSBmYWN0b3Ioc3BlYWtlcklEKSkKYGBgCgoKIyMgR2FtbSBtb2RlbGxpbmcKCkkgZml0IGEgdGVuc29yLXByb2R1Y3Qgc21vb3RoIG92ZXIgbGF0aXR1ZGUgYW5kIGxvbmdpdHVkZSwgaW5jbHVkaW5nIHZvaWNpbmcgYXMgYW4gZWZmZWN0LiBUaGlzJ2xsIGxldCBtZSBnZXQgZGlmZmVyZW5jZSBjdXJ2ZXMsIHdoaWNoIGlzIHdoYXQncyByZWFsbHkgb2YgaW50ZXJlc3QuIEkgYWxzbyBpbmNsdWRlZCByYW5kb20gZWZmZWN0cyBvZiB3b3JkLCBzcGVha2VyLCBhbmQgY2l0eS4gSSdsbCBjb21lIGJhY2sgdG8gdGhlIGNpdHkgZWZmZWN0cyBpbiBhIG1vbWVudC4KCmBgYHtyfQpsaWJyYXJ5KG1nY3YpCmBgYAoKYGBge3J9CmF5c19tb2RlbCA8LSBnYW0oRjFfbiB+IHZvaWNpbmcgKyB0ZShsb24sIGxhdCwgYnkgPSB2b2ljaW5nLCBrID0gMTApKwogICAgICAgICAgICAgICAgICAgICAgICAgIHMoc3BlYWtlcklELCBicyA9ICdyZScsIGJ5ID0gdm9pY2luZykrCiAgICAgICAgICAgICAgICAgICAgICAgICAgcyhjaXRzdGF0ZSwgYnMgPSAncmUnLCBieSA9IHZvaWNpbmcpKwogICAgICAgICAgICAgICAgICAgICAgICAgIHMod29yZCwgYnMgPSAncmUnKSwgZGF0YSA9IGF5cykKYGBgCgoKSXQgdG9vayBhIGxvbmcgdGltZSB0byBmaXQsIHNvIHNhdmluZyBmb3Igc2FmZXR5LgpgYGB7cn0Kc2F2ZVJEUyhheXNfbW9kZWwsICJheXNfbW9kZWwuUkRTIikKYGBgCgpBY2NvcmRpbmcgdG8gYGdhbS5jaGVjaygpYCwgdGhlIHBhcmFtZXRlcml6YXRpb24gb2YgdGhlIHNtb290aGVycyB3YXMgcHJldHR5IG9rLgpgYGB7cn0KZ2FtLmNoZWNrKGF5c19tb2RlbCkKYGBgCgpUaGUgc3VtbWFyeSBpcyBraW5kIG9mIHVzZWxlc3MuCmBgYHtyfQpzdW1tYXJ5KGF5c19tb2RlbCkKYGBgCgojIyBQbG90dGluZyB0aGUgcmVzdWx0cwoKYGBge3J9CmxpYnJhcnkoaXRzYWR1ZykKYGBgCgpGaXJzdCwgZ2V0IHRoZSBwcmVkaWN0ZWQgdmFsdWVzIGFjcm9zcyBhIGdyaWQgb2YgbG9uZ2l0dWRlcyBhbmQgbGF0aXR1ZGVzLCBleGNsdWRpbmcgdGhlIHJhbmRvbSBlZmZlY3RzLgoKYGBge3J9CmJib3ggPC0gYyhtaW4oYXlzJGxvbiktNSwgbWluKGF5cyRsYXQpLTIsIG1heChheXMkbG9uKSs1LCBtYXgoYXlzJGxhdCkrMikKCgpheXNfZGlmZiA8LSBnZXRfZGlmZmVyZW5jZShheXNfbW9kZWwsIAogICAgICAgICAgICAgICAgICAgICAgICAgICBjb21wID0gbGlzdCh2b2ljaW5nID0gYygidm9pY2VkIiwgInZvaWNlbGVzcyIpKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgY29uZCA9IGxpc3QobG9uID0gc2VxKGJib3hbMV0sIGJib3hbM10sIGxlbmd0aCA9IDEwMCksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxhdCA9IHNlcShiYm94WzJdLCBiYm94WzRdLCBsZW5ndGggPSAxMDApKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgcm0ucmFuZWYgPSBUKQpgYGAKCkEgcHJhY3RpY2UgcGxvdCwgdXNpbmcgYGdlb21fY29udG91cigpYApgYGB7cn0KYXlzX2RpZmYgJT4lCiAgZ2dwbG90KGFlcyhsb24sIGxhdCkpKwogICAgZ2VvbV9jb250b3VyKGFlcyh6ID0gZGlmZmVyZW5jZSwgY29sb3IgPSAuLmxldmVsLi4pKQpgYGAKCk5leHQsIGdldCBhIGJhc2VtYXAuIEkgd2VudCB3aXRoIGEgYmxhY2sgYW5kIHdoaXRlIGJhc2VtYXAgc28gdGhhdCBpdCB3b24ndCBjb21wZXRlIHdpdGggdGhlIGNvbG9ycyBvZiB0aGUgcGxvdCwgYnV0IHRoYXQgY291bGQgYmUgcmV2aXNpdGVkLgoKYGBge3J9CmJib3ggPC0gYyhtaW4oYXlzJGxvbiktNSwgbWluKGF5cyRsYXQpLTIsIG1heChheXMkbG9uKSs1LCBtYXgoYXlzJGxhdCkrMikKbmFfbWFwIDwtIGdldF9tYXAobG9jYXRpb24gPSBiYm94LCBtYXB0eXBlID0gInRvbmVyIikKYGBgCgpgYGB7cn0KZ2dtYXAobmFfbWFwKQpgYGAKCk92ZXJsYXlpbmcgdGhlIGNvbnRvdXJzIG9uIHRoZSBtYXAuCgpgYGB7cn0KZ2dtYXAobmFfbWFwKSsKICBnZW9tX2NvbnRvdXIoZGF0YSA9IGF5c19kaWZmLCBhZXMoeiA9IGRpZmZlcmVuY2UsIGNvbG9yID0gLi5sZXZlbC4uKSwgc2l6ZT0gMSkrCiAgc2NhbGVfY29sb3JfZ3JhZGllbnQyKCkKYGBgCgoKVGhlIG9uZSBtYWpvciBkb3duc2lkZSBvZiB0aGlzIGlzIHRoYXQgdGhlIGRpZmZlcmVuY2UgYmV0d2VlbiByZWdpb25zIG1pZ2h0IGJlIGFjdHVhbGx5IHF1aXRlIGEgYml0IHNoYXJwZXIgdGhhbiB0aGUgZ2FtbXMgc2hvdy4gSSB3YW50IHRvIGluY2x1ZGUgY2l0aWVzIHRoYXQgYXJlIHZlcnkgZGlmZmVyZW50IGZyb20gdGhlaXIgcHJlZGljdGVkIGdlb2dyYXBoaWMgdmFsdWVzLiBUaGlzIGludm9sdmVzOgoKLSBHZXR0aW5nIHRoZSBwcmVkaWN0ZWQgdmFsdWVzIGZvciBlYWNoIGNpdHkgZ2l2ZW4ganVzdCBpdHMgZ2VvZ3JhcGhpYyBsb2NhdGlvbiAoZWxpbWluYXRpbmcgdGhlIHJhbmVmcyBvZiBjaXR5LCBzcGVha2VyIGFuZCB3b3JkKQotIEdldHRpbmcgdGhlIHByZWRpY3RlZCBmb3IgZWFjaCBjaXR5LCBpbmNsdWRpbmcgaXRzIHJhbmRvbSBlZmZlY3QgKGVsaW1pbmF0aW5nIHJhbmVmcyBvZiBzcGVha2VyIGFuZCB3b3JkKS4KClRoaXMgaW52b2x2ZXMgbWVzc2luZyBhcm91bmQgd2l0aCBtYXRyaXggbXVsdGlwbGljYXRpb24sIHNvcnJ5LgoKCi0gTWFrZSBhIHByZWRpY3Rpb24gZGF0YSBmcmFtZSBiYXNlZCBvbiB0aGUgZ2VvY29kZWQgY2l0eSBkYXRhIChhbmQgYSBjb3B5IGNhbGxlZCBub2NpdCBmb3IgbGF0ZXIpLgpgYGB7cn0KcHJlX3ByZWQgPC0gZ2VvY29kZWQgJT4lIAogICAgICAgICAgICAgIHVuZ3JvdXAoKSU+JQogICAgICAgICAgICAgIHNlbGVjdChjaXRzdGF0ZSwgbG9uLCBsYXQpJT4lCiAgICAgICAgICAgICAgbXV0YXRlKHNwZWFrZXJJRCA9IDEsCiAgICAgICAgICAgICAgICAgICAgIHdvcmQgPSAiZml2ZSIpICU+JQogICAgICAgICAgICAgIGZpbHRlcihsb24gPiAtMTQwKQpwcmVkX2RmIDwtIGJpbmRfcm93cyhwcmVfcHJlZCAlPiUgbXV0YXRlKHZvaWNpbmcgPSAidm9pY2VkIiksCiAgICAgICAgICAgICAgICAgICAgIHByZV9wcmVkICU+JSBtdXRhdGUodm9pY2luZyA9ICJ2b2ljZWxlc3MiKSkgJT4lCiAgICAgICAgICAgIG11dGF0ZSh2b2ljaW5nID0gZmFjdG9yKHZvaWNpbmcpKQoKcHJlZF9kZl9ub2NpdCA8LSBwcmVkX2RmCmBgYAoKLSBHZXQgdGhlIG1vZGVsIG1hdHJpeCB1c2luZyB0aGUgYHByZWRpY3QoLi4uLCB0eXBlID0gJ2xwbWF0cml4JylgIG1ldGhvZC4KYGBge3J9IApwcmVkX21hdHJpeCA8LSBwcmVkaWN0KGF5c19tb2RlbCwgbmV3ZGF0YSA9IHByZWRfZGYsIHR5cGUgPSAibHBtYXRyaXgiKQpgYGAKCi0gWmVybyBvdXQgdGhlIHJhbmRvbSBlZmZlY3RzIG9mIHNwZWFrZXIgYW5kIHdvcmtkCmBgYHtyfQpwcmVkX21hdHJpeFssZ3JlcGwoInNwZWFrZXJJRCIsIGNvbG5hbWVzKHByZWRfbWF0cml4KSldIDwtIDAKcHJlZF9tYXRyaXhbLGdyZXBsKCJ3b3JkIiwgY29sbmFtZXMocHJlZF9tYXRyaXgpKV0gPC0gMApgYGAKCi0gY3JlYXRlIGEgbm8gY2l0eSB2ZXJzaW9uIG9mIHRoZSBwcmVkaWN0aW9uIG1hdHJpeCwgdG8gZ2V0IHRoZSBmaXR0ZWQgdmFsdWVzIHdpdGhvdXQgdGhlIGNpdHkgcmFuZG9tIGVmZmVjdHMuCmBgYHtyfQpwcmVkX21hdHJpeF9ub2NpdCA8LSBwcmVkX21hdHJpeApwcmVkX21hdHJpeF9ub2NpdFssZ3JlcGwoImNpdHN0YXRlIiwgY29sbmFtZXMocHJlZF9tYXRyaXgpKV0gPC0gMApgYGAKCi0gTWF0cml4IG11bHRpcGxpY2F0aW9uIHRvIGdldCB0aGUgZml0dGVkIHZhbHVlcy4KYGBge3J9CnByZWRfZGYkZml0IDwtIChwcmVkX21hdHJpeCAlKiUgY29lZihheXNfbW9kZWwpKVssMV0KcHJlZF9kZl9ub2NpdCRmaXQgPC0gKHByZWRfbWF0cml4X25vY2l0ICUqJSBjb2VmKGF5c19tb2RlbCkpWywxXQpgYGAKCi0gU29tZSBmdXR6aW5nIGFyb3VuZCB0byBnZXQgdGhlc2UgdHdvIGRpZmZlcmVudCBkaWZmZXJlbmNlIHNjb3JlcyBpbnRvIG9uZSBkYXRhZnJhbWUuCmBgYHtyfQpwcmVkX2RmIDwtIHByZWRfZGYgJT4lIAogICAgICAgICAgICBzcHJlYWQodm9pY2luZywgZml0KSAlPiUgCiAgICAgICAgICAgIG11dGF0ZShjaXRkaWZmID0gdm9pY2VkIC0gdm9pY2VsZXNzKSU+JQogICAgICAgICAgICBzZWxlY3QoY2l0c3RhdGUsIGxvbiwgbGF0LCBjaXRkaWZmKQoKcHJlZF9kZl9ub2NpdCA8LSBwcmVkX2RmX25vY2l0ICU+JSAKICAgICAgICAgICAgICAgICAgICBzcHJlYWQodm9pY2luZywgZml0KSAlPiUgCiAgICAgICAgICAgICAgICAgICAgbXV0YXRlKG5vX2NpdGRpZmYgPSB2b2ljZWQgLSB2b2ljZWxlc3MpJT4lCiAgICAgICAgICAgICAgICAgICAgc2VsZWN0KGNpdHN0YXRlLCBsb24sIGxhdCwgbm9fY2l0ZGlmZikKCgphbGxfcHJlZCA8LSBsZWZ0X2pvaW4ocHJlZF9kZiwgcHJlZF9kZl9ub2NpdCkKYGBgCgpgYGB7cn0KYWxsX3ByZWQgJT4lIAogIG11dGF0ZShleHRyYWRpZmYgPSBjaXRkaWZmIC0gbm9fY2l0ZGlmZiklPiUKICBhcnJhbmdlKC1hYnMoZXh0cmFkaWZmKSkgJT4lCiAgaGVhZCgyMCkKYGBgCgrirIbvuI8gYGFsbF9wcmVkYCBub3cgY29udGFpbnMgYSBjb2x1bW4gb2YgdGhlIHByZWRpY3RlZCBkaWZmZXJlbmNlIGJldHdlZW4gcHJlLXZvaWNlZCBhbmQgcHJlLXZvaWNlbGVzcyAvYXkvIGJhc2VkIG9uIGl0cyBsb2NhdGlvbiArIHJhbmRvbSBlZmZlY3QgKGNpdGRpZmYpIGFuZCBqdXN0IGl0cyBsb2NhdGlvbiAobm9fY2l0ZGlmZikgYW5kIHRoZSBkaWZmZXJlbmNlIGJldHdlZW4gdGhlbSAoZXh0cmFkaWZmKS4gSSdsbCBwbG90IGp1c3QgdGhlIGNpdGllcyB0aGF0IGhhdmUgdGhlIGJpZ2dlc3QgYWJzb2x1dGUgZXh0cmFkaWZmICh0aGV5J3JlIG1heGltYWxseSBkaWZmZXJlbnQgZnJvbSB0aGVpciBwcmVkaWN0ZWQgdmFsdWVzIGJhc2VkIG9uIGxvY2F0aW9uIGFsb25lKS4KCgpgYGB7cn0KYmlnX3JhbmVmX2NpdGllcyA8LSBhbGxfcHJlZCAlPiUgCiAgICAgICAgICAgICAgICAgICAgICAgIG11dGF0ZShleHRyYWRpZmYgPSBjaXRkaWZmIC0gbm9fY2l0ZGlmZiklPiUKICAgICAgICAgICAgICAgICAgICAgICAgZmlsdGVyKGFicyhleHRyYWRpZmYpID4gMC4wMikKYGBgCgpgYGB7ciBmaWcud2lkdGggPSA4LCBmaWcuaGVpZ2h0ID0gNX0KbGlicmFyeShnZ3RoZW1lcykKcGFsIDwtIHB0b2xfcGFsKCkoMykKZ2dtYXAobmFfbWFwKSsKICBnZW9tX2NvbnRvdXIoZGF0YSA9IGF5c19kaWZmLCBhZXMoeiA9IGRpZmZlcmVuY2UsIGNvbG9yID0gLi5sZXZlbC4uKSwgc2l6ZT0gMSkrCiAgZ2VvbV9wb2ludChkYXRhID0gYmlnX3JhbmVmX2NpdGllcywgYWVzKGNvbG9yID0gY2l0ZGlmZiwgc2l6ZSA9IGFicyhleHRyYWRpZmYpKSkrCiAgc2NhbGVfY29sb3JfZ3JhZGllbnQyKGxvdyA9IHBhbFsxXSwgbWlkID0gcGFsWzJdLCBoaWdoID0gcGFsWzNdLAogICAgICAgICAgICAgICAgICAgICAgICBsaW1pdHMgPSBjKG1pbihhbGxfcHJlZCRjaXRkaWZmKSwgbWF4KGFsbF9wcmVkJGNpdGRpZmYpKSkKYGBgCgoKSSB3YWZmbGVkIG9uIHdoYXQgdGhlIHJpZ2h0IHRoaW5nIHRvIGRvIGZvciB0aGUgbWFwIHdhcy4gSSBjb3VsZCBjb2xvciBlYWNoIHBvaW50IGFjY29yZGluZyB0byBob3cgKmRpZmZlcmVudCogaXQgd2FzIGZyb20gaXRzIHN1cnJvdW5kaW5nIGFyZWEsIGJ1dCBJIHRoaW5rIGxvb2tpbmcgYXQgYSBtYXAgbGlrZSB0aGlzIHlvdSdkIGV4cGVjdCBlYWNoIHBvaW50IHRvIGJlIGNvbG9yZWQgYWNjb3JkaW5nIHRvIGl0cyBwcmVkaWN0ZWQgdmFsdWUsIHNvIHRoYXQncyB3aGF0IEkgZGlkLiBXaGF0IGdldHMgbG9zdCBpcywgZm9yIGV4YW1wbGUsIHRoZSBmYWN0IHRoYXQgUGhpbGFkZWxwaGlhIGhhcyBhIGxhcmdlciBkaWZmZXJlbmNlIHRoYW4gcHJlZGljdGVkIGZvciBpdHMgbG9jYXRpb24gd2hpbGUgTllDIGhhcyBhIG11Y2ggc21hbGxlciBkaWZmZXJlbmNlLiBUaGUgYmFycGxvdCBiZWxvdyBpcyBhbiBhdHRlbXB0IHRvIHNob3cgdGhpcywgYml0IGl0IGxvc2VzIHRoZSBnZW9ncmFwaGljIGluZm9ybWF0aW9uLgoKCmBgYHtyIGZpZy5oZWlnaHQ9MTAsIGZpZy53aWR0aD01fQpiaWdfcmFuZWZfY2l0aWVzJT4lCiAgbXV0YXRlKGNpdHN0YXRlID0gZmFjdG9yKGNpdHN0YXRlKSwKICAgICAgICAgY2l0c3RhdGUgPSByZW9yZGVyKGNpdHN0YXRlLCBleHRyYWRpZmYsIG1lYW4pKSU+JQogIGdncGxvdChhZXMoY2l0c3RhdGUsIGV4dHJhZGlmZikpKwogICAgZ2VvbV9iYXIoc3RhdCA9ICdpZGVudGl0eScsIGFlcyhmaWxsID0gbm9fY2l0ZGlmZikpKwogICAgc2NhbGVfZmlsbF9ncmFkaWVudDIobG93ID0gcGFsWzFdLCBtaWQgPSBwYWxbMl0sIGhpZ2ggPSBwYWxbM10pKwogICAgY29vcmRfZmxpcCgpCmBgYAoKCgpgYGB7ciBmaWcud2lkdGggPSA4LCBmaWcuaGVpZ2h0ID0gNX0KbGlicmFyeShzY2FsZXMpCmdnbWFwKG5hX21hcCkrCiAgZ2VvbV9wb2ludChkYXRhID0gYWxsX3ByZWQsIGFlcyhjb2xvciA9IGNpdGRpZmYgLSBub19jaXRkaWZmLCBzaXplPWFicyhjaXRkaWZmIC0gbm9fY2l0ZGlmZikpKSsKICAgc2NhbGVfY29sb3JfZ3JhZGllbnQyKGxvdyA9IG11dGVkKCJibHVlIiksIGhpZ2ggPSBtdXRlZCgicmVkIiksIG1pZCA9ICJncmV5IikKYGBgCgo=