Here, we inspect the relationship between cosine similarity and Pearson similarity

suppressPackageStartupMessages(library(magrittr))
suppressPackageStartupMessages(library(tidyverse))
cosine_sim <- function(x, y) {
  sum(x*y)/(sqrt(sum(x*x)) * sqrt(sum(y*y)))
}

Pearson correlation (interchangeably called Pearson similarity) of two vectors is the cosine similarity of the vectors after subtracting the means of each.

pearson <- function(x, y) {
  cosine_sim(x - mean(x), y - mean(y))
}

Verify that our implementation is correct

all(sapply(seq(100), 
      function(i) {
        x <- rnorm(100); 
        y <- rnorm(100); 
        pearson(x, y) - cor(x, y) < .Machine$double.eps
        }
      )
)
[1] TRUE

\[ cosine(\boldsymbol{x},\boldsymbol{y}) \doteq \frac{\boldsymbol{x}\cdot\boldsymbol{y}}{\|\boldsymbol{x}\|\|\boldsymbol{y}\|} \]

\[ \rho(\boldsymbol{x},\boldsymbol{y}) \doteq cosine(\boldsymbol{x}-\boldsymbol{\bar{x}}, \boldsymbol{y}-\boldsymbol{\bar{y}}) \]

Pearson similarity is invariant to scaling of the features (i.e. scale all features with the same value)

\[ \rho(u\boldsymbol{x},v\boldsymbol{y}) \doteq cosine(u(\boldsymbol{x}-\boldsymbol{\bar{x}}), v(\boldsymbol{y}-\boldsymbol{\bar{y}})) \doteq cosine(\boldsymbol{x}-\boldsymbol{\bar{x}}, \boldsymbol{y}-\boldsymbol{\bar{y}}) \doteq \rho(\boldsymbol{x},\boldsymbol{y}) \]

Pearson similarity is invariant to a translation in the features (i.e. translate all features with the same value)

\[ (\boldsymbol{x} - a\boldsymbol{1}) - \overline{(\boldsymbol{x}-a\boldsymbol{1})} \doteq (\boldsymbol{x} - a\boldsymbol{1}) - (\bar{\boldsymbol{x}}-a{\boldsymbol{\bar{1}}}) \doteq \boldsymbol{x} - \bar{\boldsymbol{x}} \]

\[ \rho(\boldsymbol{x}-a\boldsymbol{1},\boldsymbol{y}-b\boldsymbol{1}) \doteq cosine(\boldsymbol{x}-\boldsymbol{\bar{x}}, \boldsymbol{y}-\boldsymbol{\bar{y}}) \doteq \rho(\boldsymbol{x},\boldsymbol{y}) \]

Test it out

Two random vectors

x <- rnorm(10)
y <- rnorm(10)

Scale each separately

xa <- abs(rnorm(1)) * x
ya <- abs(rnorm(1)) * y

Pearson similarity is invariant to scale

pearson(x,y)
[1] -0.2058116
pearson(xa,ya)
[1] -0.2058116

Cosine similarity is invariant to scale

cosine_sim(x,y)
[1] -0.2799055
cosine_sim(xa,ya)
[1] -0.2799055

Now create a new vector by appending columns (all zeros)

xz <- c(x, rep(0, 2))
yz <- c(y, rep(0, 2))

This affects Pearson by not cosine (because Pearson subtracts mean)

pearson(xz,yz)
[1] -0.2201575
cosine_sim(xz,yz)
[1] -0.2799055

Now test shift and scale in higher dimensions (we haven’t tested shift yet)

set.seed(43)
n <- 2
d <- 10
m <- matrix(rnorm(d * n), n, d)
#m[, 3] <- 0

b <- matrix(rep(3, n), n, d, byrow = FALSE)
m_shift <- m +  b

s <- matrix(rep(2, n), n, d, byrow = FALSE)
m_scale <- m * s

m_scale_shift <- m * s + b
m
            [,1]       [,2]       [,3]        [,4]       [,5]       [,6]       [,7]      [,8]      [,9]      [,10]
[1,] -0.03751376 -0.4859675 -0.9040981  0.38643441 -0.6861798  1.8037598 -0.3531183 0.5663244  1.469278  0.2026365
[2,] -1.57460441  0.4651862 -0.2774328 -0.06040412 -1.9061368 -0.9668729  1.1069375 2.0643087 -1.651500 -0.7209798
b
     [,1] [,2] [,3] [,4] [,5] [,6] [,7] [,8] [,9] [,10]
[1,]    3    3    3    3    3    3    3    3    3     3
[2,]    3    3    3    3    3    3    3    3    3     3
s
     [,1] [,2] [,3] [,4] [,5] [,6] [,7] [,8] [,9] [,10]
[1,]    2    2    2    2    2    2    2    2    2     2
[2,]    2    2    2    2    2    2    2    2    2     2
m_shift
         [,1]     [,2]     [,3]     [,4]     [,5]     [,6]     [,7]     [,8]     [,9]    [,10]
[1,] 2.962486 2.514032 2.095902 3.386434 2.313820 4.803760 2.646882 3.566324 4.469278 3.202636
[2,] 1.425396 3.465186 2.722567 2.939596 1.093863 2.033127 4.106937 5.064309 1.348500 2.279020
m_scale
            [,1]       [,2]       [,3]       [,4]      [,5]      [,6]       [,7]     [,8]      [,9]      [,10]
[1,] -0.07502752 -0.9719350 -1.8081961  0.7728688 -1.372360  3.607520 -0.7062365 1.132649  2.938556  0.4052729
[2,] -3.14920883  0.9303725 -0.5548656 -0.1208082 -3.812274 -1.933746  2.2138750 4.128617 -3.303000 -1.4419597
m_scale_shift
           [,1]     [,2]     [,3]     [,4]       [,5]     [,6]     [,7]     [,8]       [,9]    [,10]
[1,]  2.9249725 2.028065 1.191804 3.772869  1.6276405 6.607520 2.293763 4.132649  5.9385556 3.405273
[2,] -0.1492088 3.930372 2.445134 2.879192 -0.8122736 1.066254 5.213875 7.128617 -0.3030003 1.558040
df <- bind_rows(
  as.data.frame(m) %>% mutate(type = "orig"),
  as.data.frame(m_shift) %>% mutate(type = "shift"),
  as.data.frame(m_scale) %>% mutate(type = "scale"),
  as.data.frame(m_scale_shift) %>% mutate(type = "scale_shift")
)

df %<>%
  mutate(type =
           factor(
             type,
             levels = c("orig", "scale", "shift", "scale_shift"),
             ordered = TRUE
           ))
df %<>%
  inner_join(
    df %>%
      group_by(type) %>%
      nest() %>%
      mutate(cosine_val = map(data, function(data) {
        m <- as.matrix(data)
        round(cosine_sim(m[1,], m[2,]), 2)
      })) %>%
      mutate(cor_val = map(data, function(data) {
        m <- as.matrix(data)
        round(cor(m[1,], m[2,]), 2)
      })) %>%
      unnest(c(cosine_val, cor_val)) %>%
      select(-data)
  )
Joining, by = "type"
df
ggplot() +
  geom_segment(data = df,
               aes(
                 x = 0,
                 y = 0,
                 xend = V1,
                 yend = V2
               ),
               arrow = arrow()) +
  coord_equal() +
  ggforce::geom_circle(data = data.frame(x0 = 0, y0 = 0, r = 1),
                       aes(x0 = x0, y0 = y0, r = r), color = "red") +
  facet_wrap( ~ type ~ cor_val ~ cosine_val, labeller = "label_both", nrow = 1) +
  ggtitle("Only first two dimensions shown")

Scatter plot of each simulated dataset, along with Pearson similarity and cosine similarity for each.

Note: This is NOT the feature space. These are d dimensions from 2 feature vectors displayed in a scatter plot.

  • Cosine similarity is scale invariant but not shift invariant
  • Pearson similarity is scale invariant and shift invariant
  • Note that the scale and shift in variance relates to scaling or shifting all features with constant, not each dimension separately. Pearson is not invariant to shifting the mean of each dimension
dfm <-
  df %>% 
  group_by(type, cor_val, cosine_val) %>% 
  mutate(repid = paste0("r", seq(n()))) %>% 
  pivot_longer(matches("^V[0-9]+")) %>% 
  pivot_wider(names_from = repid, values_from = value)

ggplot(dfm, aes(r1, r2)) + 
  geom_point() + 
  facet_wrap(~ type ~ cor_val ~ cosine_val, labeller = "label_both", nrow = 1) +
  coord_equal()

Unlike Euclidean distance, Pearson similarity 1. is shift invariant along the features (i.e. add a constant scalar to all features for both vectors) 2. is not shift invariant in the feature space (i.e. add a constant vector to the two vectors) 3. is scale invariant along the features (i.e. multiply all features by a constant scalar)

Cosine similarity is same as Pearson in this sense, except for #1 (it is not shift invariant along the features)

Now lets systemically add a constant scalar (translate) to a vector and report the cosine similarity with the untranslated vector and visualize it.

k <- 9
m <- matrix(rep(rnorm(d), k), k, d, byrow = TRUE)
m
            [,1]      [,2]       [,3]        [,4]      [,5]       [,6]      [,7]        [,8]       [,9]      [,10]
 [1,] -0.1590422 0.7342484 -0.3633032 -0.01009403 0.5827959 -0.2932883 -1.394382 -0.08508171 -0.6880953 -0.7765219
 [2,] -0.1590422 0.7342484 -0.3633032 -0.01009403 0.5827959 -0.2932883 -1.394382 -0.08508171 -0.6880953 -0.7765219
 [3,] -0.1590422 0.7342484 -0.3633032 -0.01009403 0.5827959 -0.2932883 -1.394382 -0.08508171 -0.6880953 -0.7765219
 [4,] -0.1590422 0.7342484 -0.3633032 -0.01009403 0.5827959 -0.2932883 -1.394382 -0.08508171 -0.6880953 -0.7765219
 [5,] -0.1590422 0.7342484 -0.3633032 -0.01009403 0.5827959 -0.2932883 -1.394382 -0.08508171 -0.6880953 -0.7765219
 [6,] -0.1590422 0.7342484 -0.3633032 -0.01009403 0.5827959 -0.2932883 -1.394382 -0.08508171 -0.6880953 -0.7765219
 [7,] -0.1590422 0.7342484 -0.3633032 -0.01009403 0.5827959 -0.2932883 -1.394382 -0.08508171 -0.6880953 -0.7765219
 [8,] -0.1590422 0.7342484 -0.3633032 -0.01009403 0.5827959 -0.2932883 -1.394382 -0.08508171 -0.6880953 -0.7765219
 [9,] -0.1590422 0.7342484 -0.3633032 -0.01009403 0.5827959 -0.2932883 -1.394382 -0.08508171 -0.6880953 -0.7765219
bshift <- matrix(rep(seq(k)-1, d)/(k-1)*4, k, d, byrow = FALSE)
m_shift <- m + bshift
df <- as.data.frame(m_shift)
df$shift <- bshift[,1]

df0 <- as.data.frame(m)
df0$shift <- bshift[,1]

df <- bind_rows(df, df0)

Consider \(k\) pairs of vectors.

df %>% select(V1, V2, shift) %>% arrange(shift)
df %<>%
  inner_join(
    df %>%
      group_by(shift) %>%
      nest() %>%
      mutate(cosine_val = map(data, function(data) {
        m <- as.matrix(data)
        round(cosine_sim(m[1,], m[2,]), 2)
      })) %>%
      mutate(cor_val = map(data, function(data) {
        m <- as.matrix(data)
        round(cor(m[1,], m[2,]), 2)
      })) %>%
      unnest(c(cosine_val, cor_val)) %>%
      select(-data)
  )
Joining, by = "shift"
df %>% select(V1, V2, shift, cosine_val, cor_val)
ggplot() +
  geom_segment(data = df,
               aes(
                 x = 0,
                 y = 0,
                 xend = V1,
                 yend = V2,
                 color = cosine_val
               ),
               arrow = arrow(type = "closed", angle = 20, length = unit(0.1, "inches"))) +
  coord_equal() +
  ggforce::geom_circle(data = data.frame(x0 = 0, y0 = 0, r = 1),
                       aes(x0 = x0, y0 = y0, r = r), color = "red") +
  ggtitle("Only first two dimensions shown", subtitle = "Pearson sim. = 1 for all cases") + facet_wrap(~shift)

The invariance to this shift is almost never useful, because it is shifting all dimenions by the same value. So while this invariance exists for Pearson, it doesn’t ever seem advantageous (over cosine). Cosine is better than Pearson when it is useful to ignore missing features (because cosine will remain the same but Pearson will change) Pearson is better than Cosine when it is useful to ignore the mean across features (because Pearson subtracts the mean)

We’ve seen this above but here it is again in a different form

x <- runif(100)*100
y <- runif(100)*100
xz <- c(x, rep(0, 1000))
yz <- c(y, rep(0, 1000))
mean(x)
[1] 50.99834
mean(y)
[1] 53.8328
mean(xz)
[1] 4.636213
mean(yz)
[1] 4.893891
cosine_sim(x, y)
[1] 0.7175429
pearson(x, y)
[1] -0.1655152
cosine_sim(xz, yz)
[1] 0.7175429
pearson(xz, yz)
[1] 0.6966495

Useful pages - https://www.quora.com/In-what-scenario-is-using-Pearson-correlation-better-than-Cosine-similarity - https://grouplens.org/blog/similarity-functions-for-user-user-collaborative-filtering/

LS0tCnRpdGxlOiAiUGVhcnNvbiBzaW1pbGFyaXR5IGFuZCBDb3NpbmUgc2ltaWxhcml0eSIKb3V0cHV0OiAKICBodG1sX25vdGVib29rOgogICAgdG9jOiB0cnVlCiAgICB0b2NfZmxvYXQ6IHRydWUKICAgIHRvY19kZXB0aDogMwogICAgbnVtYmVyX3NlY3Rpb25zOiB0cnVlCiAgICB0aGVtZTogbHVtZW4KLS0tCgpIZXJlLCB3ZSBpbnNwZWN0IHRoZSByZWxhdGlvbnNoaXAgYmV0d2VlbiBjb3NpbmUgc2ltaWxhcml0eSBhbmQgUGVhcnNvbiBzaW1pbGFyaXR5CgpgYGB7cn0Kc3VwcHJlc3NQYWNrYWdlU3RhcnR1cE1lc3NhZ2VzKGxpYnJhcnkobWFncml0dHIpKQpzdXBwcmVzc1BhY2thZ2VTdGFydHVwTWVzc2FnZXMobGlicmFyeSh0aWR5dmVyc2UpKQpgYGAKCgpgYGB7cn0KY29zaW5lX3NpbSA8LSBmdW5jdGlvbih4LCB5KSB7CiAgc3VtKHgqeSkvKHNxcnQoc3VtKHgqeCkpICogc3FydChzdW0oeSp5KSkpCn0KYGBgCgpQZWFyc29uIGNvcnJlbGF0aW9uIChpbnRlcmNoYW5nZWFibHkgY2FsbGVkIFBlYXJzb24gc2ltaWxhcml0eSkgb2YgdHdvIHZlY3RvcnMgaXMgdGhlIGNvc2luZSBzaW1pbGFyaXR5IG9mIHRoZSB2ZWN0b3JzIGFmdGVyIHN1YnRyYWN0aW5nIHRoZSBtZWFucyBvZiBlYWNoLgoKYGBge3J9CnBlYXJzb24gPC0gZnVuY3Rpb24oeCwgeSkgewogIGNvc2luZV9zaW0oeCAtIG1lYW4oeCksIHkgLSBtZWFuKHkpKQp9CmBgYAoKVmVyaWZ5IHRoYXQgb3VyIGltcGxlbWVudGF0aW9uIGlzIGNvcnJlY3QKCmBgYHtyfQphbGwoc2FwcGx5KHNlcSgxMDApLCAKICAgICAgZnVuY3Rpb24oaSkgewogICAgICAgIHggPC0gcm5vcm0oMTAwKTsgCiAgICAgICAgeSA8LSBybm9ybSgxMDApOyAKICAgICAgICBwZWFyc29uKHgsIHkpIC0gY29yKHgsIHkpIDwgLk1hY2hpbmUkZG91YmxlLmVwcwogICAgICAgIH0KICAgICAgKQopCmBgYAoKCiQkCmNvc2luZShcYm9sZHN5bWJvbHt4fSxcYm9sZHN5bWJvbHt5fSkgClxkb3RlcSAgClxmcmFje1xib2xkc3ltYm9se3h9XGNkb3RcYm9sZHN5bWJvbHt5fX17XHxcYm9sZHN5bWJvbHt4fVx8XHxcYm9sZHN5bWJvbHt5fVx8fQokJAoKJCQKXHJobyhcYm9sZHN5bWJvbHt4fSxcYm9sZHN5bWJvbHt5fSkgClxkb3RlcSAgCmNvc2luZShcYm9sZHN5bWJvbHt4fS1cYm9sZHN5bWJvbHtcYmFye3h9fSwgXGJvbGRzeW1ib2x7eX0tXGJvbGRzeW1ib2x7XGJhcnt5fX0pCiQkCgpQZWFyc29uIHNpbWlsYXJpdHkgaXMgIGludmFyaWFudCB0byBzY2FsaW5nIG9mIHRoZSBmZWF0dXJlcyAoaS5lLiBzY2FsZSBhbGwgZmVhdHVyZXMgd2l0aCB0aGUgc2FtZSB2YWx1ZSkKCiQkClxyaG8odVxib2xkc3ltYm9se3h9LHZcYm9sZHN5bWJvbHt5fSkgClxkb3RlcSAgCmNvc2luZSh1KFxib2xkc3ltYm9se3h9LVxib2xkc3ltYm9se1xiYXJ7eH19KSwgdihcYm9sZHN5bWJvbHt5fS1cYm9sZHN5bWJvbHtcYmFye3l9fSkpIFxkb3RlcSAKY29zaW5lKFxib2xkc3ltYm9se3h9LVxib2xkc3ltYm9se1xiYXJ7eH19LCBcYm9sZHN5bWJvbHt5fS1cYm9sZHN5bWJvbHtcYmFye3l9fSkgXGRvdGVxIApccmhvKFxib2xkc3ltYm9se3h9LFxib2xkc3ltYm9se3l9KSAKJCQKClBlYXJzb24gc2ltaWxhcml0eSBpcyAgaW52YXJpYW50IHRvIGEgdHJhbnNsYXRpb24gaW4gdGhlIGZlYXR1cmVzIChpLmUuIHRyYW5zbGF0ZSBhbGwgZmVhdHVyZXMgd2l0aCB0aGUgc2FtZSB2YWx1ZSkKCiQkCiAoXGJvbGRzeW1ib2x7eH0gLSBhXGJvbGRzeW1ib2x7MX0pIC0gXG92ZXJsaW5leyhcYm9sZHN5bWJvbHt4fS1hXGJvbGRzeW1ib2x7MX0pfSBcZG90ZXEKIChcYm9sZHN5bWJvbHt4fSAtIGFcYm9sZHN5bWJvbHsxfSkgLSAoXGJhcntcYm9sZHN5bWJvbHt4fX0tYXtcYm9sZHN5bWJvbHtcYmFyezF9fX0pIFxkb3RlcQogXGJvbGRzeW1ib2x7eH0gLSBcYmFye1xib2xkc3ltYm9se3h9fQokJAoKJCQKXHJobyhcYm9sZHN5bWJvbHt4fS1hXGJvbGRzeW1ib2x7MX0sXGJvbGRzeW1ib2x7eX0tYlxib2xkc3ltYm9sezF9KSAKXGRvdGVxICAKY29zaW5lKFxib2xkc3ltYm9se3h9LVxib2xkc3ltYm9se1xiYXJ7eH19LCBcYm9sZHN5bWJvbHt5fS1cYm9sZHN5bWJvbHtcYmFye3l9fSkKIFxkb3RlcSAKXHJobyhcYm9sZHN5bWJvbHt4fSxcYm9sZHN5bWJvbHt5fSkgCiQkCgpUZXN0IGl0IG91dAoKVHdvIHJhbmRvbSB2ZWN0b3JzCgpgYGB7cn0KeCA8LSBybm9ybSgxMCkKeSA8LSBybm9ybSgxMCkKYGBgCgpTY2FsZSBlYWNoIHNlcGFyYXRlbHkgCgpgYGB7cn0KeGEgPC0gYWJzKHJub3JtKDEpKSAqIHgKeWEgPC0gYWJzKHJub3JtKDEpKSAqIHkKYGBgCgpQZWFyc29uIHNpbWlsYXJpdHkgaXMgaW52YXJpYW50IHRvIHNjYWxlCgpgYGB7cn0KcGVhcnNvbih4LHkpCnBlYXJzb24oeGEseWEpCmBgYAoKQ29zaW5lIHNpbWlsYXJpdHkgaXMgaW52YXJpYW50IHRvIHNjYWxlCgpgYGB7cn0KY29zaW5lX3NpbSh4LHkpCmNvc2luZV9zaW0oeGEseWEpCmBgYAoKTm93IGNyZWF0ZSBhIG5ldyB2ZWN0b3IgYnkgYXBwZW5kaW5nIGNvbHVtbnMgKGFsbCB6ZXJvcykKCmBgYHtyfQp4eiA8LSBjKHgsIHJlcCgwLCAyKSkKeXogPC0gYyh5LCByZXAoMCwgMikpCmBgYAoKClRoaXMgYWZmZWN0cyBQZWFyc29uIGJ5IG5vdCBjb3NpbmUgKGJlY2F1c2UgUGVhcnNvbiBzdWJ0cmFjdHMgbWVhbikgCgpgYGB7cn0KcGVhcnNvbih4eix5eikKY29zaW5lX3NpbSh4eix5eikKYGBgCgpOb3cgdGVzdCBzaGlmdCBhbmQgc2NhbGUgaW4gaGlnaGVyIGRpbWVuc2lvbnMgKHdlIGhhdmVuJ3QgdGVzdGVkIHNoaWZ0IHlldCkKCmBgYHtyfQpzZXQuc2VlZCg0MykKbiA8LSAyCmQgPC0gMTAKbSA8LSBtYXRyaXgocm5vcm0oZCAqIG4pLCBuLCBkKQojbVssIDNdIDwtIDAKCmIgPC0gbWF0cml4KHJlcCgzLCBuKSwgbiwgZCwgYnlyb3cgPSBGQUxTRSkKbV9zaGlmdCA8LSBtICsgIGIKCnMgPC0gbWF0cml4KHJlcCgyLCBuKSwgbiwgZCwgYnlyb3cgPSBGQUxTRSkKbV9zY2FsZSA8LSBtICogcwoKbV9zY2FsZV9zaGlmdCA8LSBtICogcyArIGIKYGBgCgoKYGBge3J9Cm0KYGBgCgoKYGBge3J9CmIKYGBgCgpgYGB7cn0KcwpgYGAKCgpgYGB7cn0KbV9zaGlmdApgYGAKCgpgYGB7cn0KbV9zY2FsZQpgYGAKCmBgYHtyfQptX3NjYWxlX3NoaWZ0CmBgYAoKCmBgYHtyfQpkZiA8LSBiaW5kX3Jvd3MoCiAgYXMuZGF0YS5mcmFtZShtKSAlPiUgbXV0YXRlKHR5cGUgPSAib3JpZyIpLAogIGFzLmRhdGEuZnJhbWUobV9zaGlmdCkgJT4lIG11dGF0ZSh0eXBlID0gInNoaWZ0IiksCiAgYXMuZGF0YS5mcmFtZShtX3NjYWxlKSAlPiUgbXV0YXRlKHR5cGUgPSAic2NhbGUiKSwKICBhcy5kYXRhLmZyYW1lKG1fc2NhbGVfc2hpZnQpICU+JSBtdXRhdGUodHlwZSA9ICJzY2FsZV9zaGlmdCIpCikKCmRmICU8PiUKICBtdXRhdGUodHlwZSA9CiAgICAgICAgICAgZmFjdG9yKAogICAgICAgICAgICAgdHlwZSwKICAgICAgICAgICAgIGxldmVscyA9IGMoIm9yaWciLCAic2NhbGUiLCAic2hpZnQiLCAic2NhbGVfc2hpZnQiKSwKICAgICAgICAgICAgIG9yZGVyZWQgPSBUUlVFCiAgICAgICAgICAgKSkKYGBgCgoKYGBge3J9CmRmICU8PiUKICBpbm5lcl9qb2luKAogICAgZGYgJT4lCiAgICAgIGdyb3VwX2J5KHR5cGUpICU+JQogICAgICBuZXN0KCkgJT4lCiAgICAgIG11dGF0ZShjb3NpbmVfdmFsID0gbWFwKGRhdGEsIGZ1bmN0aW9uKGRhdGEpIHsKICAgICAgICBtIDwtIGFzLm1hdHJpeChkYXRhKQogICAgICAgIHJvdW5kKGNvc2luZV9zaW0obVsxLF0sIG1bMixdKSwgMikKICAgICAgfSkpICU+JQogICAgICBtdXRhdGUoY29yX3ZhbCA9IG1hcChkYXRhLCBmdW5jdGlvbihkYXRhKSB7CiAgICAgICAgbSA8LSBhcy5tYXRyaXgoZGF0YSkKICAgICAgICByb3VuZChjb3IobVsxLF0sIG1bMixdKSwgMikKICAgICAgfSkpICU+JQogICAgICB1bm5lc3QoYyhjb3NpbmVfdmFsLCBjb3JfdmFsKSkgJT4lCiAgICAgIHNlbGVjdCgtZGF0YSkKICApCgpkZgpgYGAKCgpgYGB7cn0KZ2dwbG90KCkgKwogIGdlb21fc2VnbWVudChkYXRhID0gZGYsCiAgICAgICAgICAgICAgIGFlcygKICAgICAgICAgICAgICAgICB4ID0gMCwKICAgICAgICAgICAgICAgICB5ID0gMCwKICAgICAgICAgICAgICAgICB4ZW5kID0gVjEsCiAgICAgICAgICAgICAgICAgeWVuZCA9IFYyCiAgICAgICAgICAgICAgICksCiAgICAgICAgICAgICAgIGFycm93ID0gYXJyb3coKSkgKwogIGNvb3JkX2VxdWFsKCkgKwogIGdnZm9yY2U6Omdlb21fY2lyY2xlKGRhdGEgPSBkYXRhLmZyYW1lKHgwID0gMCwgeTAgPSAwLCByID0gMSksCiAgICAgICAgICAgICAgICAgICAgICAgYWVzKHgwID0geDAsIHkwID0geTAsIHIgPSByKSwgY29sb3IgPSAicmVkIikgKwogIGZhY2V0X3dyYXAoIH4gdHlwZSB+IGNvcl92YWwgfiBjb3NpbmVfdmFsLCBsYWJlbGxlciA9ICJsYWJlbF9ib3RoIiwgbnJvdyA9IDEpICsKICBnZ3RpdGxlKCJPbmx5IGZpcnN0IHR3byBkaW1lbnNpb25zIHNob3duIikKYGBgCgpTY2F0dGVyIHBsb3Qgb2YgZWFjaCBzaW11bGF0ZWQgZGF0YXNldCwgYWxvbmcgd2l0aCBQZWFyc29uIHNpbWlsYXJpdHkgYW5kIGNvc2luZSBzaW1pbGFyaXR5IGZvciBlYWNoLgoKTm90ZTogVGhpcyBpcyBOT1QgdGhlIGZlYXR1cmUgc3BhY2UuIFRoZXNlIGFyZSBgZGAgZGltZW5zaW9ucyBmcm9tIDIgZmVhdHVyZSB2ZWN0b3JzIGRpc3BsYXllZCBpbiBhIHNjYXR0ZXIgcGxvdC4KCi0gQ29zaW5lIHNpbWlsYXJpdHkgaXMgc2NhbGUgaW52YXJpYW50IGJ1dCBub3Qgc2hpZnQgaW52YXJpYW50Ci0gUGVhcnNvbiBzaW1pbGFyaXR5IGlzIHNjYWxlIGludmFyaWFudCBhbmQgc2hpZnQgaW52YXJpYW50Ci0gTm90ZSB0aGF0IHRoZSBzY2FsZSBhbmQgc2hpZnQgaW4gdmFyaWFuY2UgcmVsYXRlcyB0byBzY2FsaW5nIG9yIHNoaWZ0aW5nIGFsbCBmZWF0dXJlcyB3aXRoIGNvbnN0YW50LCBub3QgZWFjaCBkaW1lbnNpb24gc2VwYXJhdGVseS4gUGVhcnNvbiBpcyBub3QgaW52YXJpYW50IHRvIHNoaWZ0aW5nIHRoZSBtZWFuIG9mIGVhY2ggZGltZW5zaW9uCgpgYGB7cn0KZGZtIDwtCiAgZGYgJT4lIAogIGdyb3VwX2J5KHR5cGUsIGNvcl92YWwsIGNvc2luZV92YWwpICU+JSAKICBtdXRhdGUocmVwaWQgPSBwYXN0ZTAoInIiLCBzZXEobigpKSkpICU+JSAKICBwaXZvdF9sb25nZXIobWF0Y2hlcygiXlZbMC05XSsiKSkgJT4lIAogIHBpdm90X3dpZGVyKG5hbWVzX2Zyb20gPSByZXBpZCwgdmFsdWVzX2Zyb20gPSB2YWx1ZSkKCmdncGxvdChkZm0sIGFlcyhyMSwgcjIpKSArIAogIGdlb21fcG9pbnQoKSArIAogIGZhY2V0X3dyYXAofiB0eXBlIH4gY29yX3ZhbCB+IGNvc2luZV92YWwsIGxhYmVsbGVyID0gImxhYmVsX2JvdGgiLCBucm93ID0gMSkgKwogIGNvb3JkX2VxdWFsKCkKCmBgYAoKCgpVbmxpa2UgRXVjbGlkZWFuIGRpc3RhbmNlLCBQZWFyc29uIHNpbWlsYXJpdHkKMS4gaXMgc2hpZnQgaW52YXJpYW50IGFsb25nIHRoZSBmZWF0dXJlcyAoaS5lLiBhZGQgYSBjb25zdGFudCBzY2FsYXIgdG8gYWxsIGZlYXR1cmVzIGZvciBib3RoIHZlY3RvcnMpCjIuIGlzIG5vdCBzaGlmdCBpbnZhcmlhbnQgaW4gdGhlIGZlYXR1cmUgc3BhY2UgKGkuZS4gYWRkIGEgY29uc3RhbnQgdmVjdG9yIHRvIHRoZSB0d28gdmVjdG9ycykKMy4gaXMgc2NhbGUgaW52YXJpYW50IGFsb25nIHRoZSBmZWF0dXJlcyAoaS5lLiBtdWx0aXBseSBhbGwgZmVhdHVyZXMgYnkgYSBjb25zdGFudCBzY2FsYXIpCgpDb3NpbmUgc2ltaWxhcml0eSBpcyBzYW1lIGFzIFBlYXJzb24gaW4gdGhpcyBzZW5zZSwgZXhjZXB0IGZvciAjMSAoaXQgaXMgbm90IHNoaWZ0IGludmFyaWFudCBhbG9uZyB0aGUgZmVhdHVyZXMpCgoKTm93IGxldHMgc3lzdGVtaWNhbGx5IGFkZCBhIGNvbnN0YW50IHNjYWxhciAodHJhbnNsYXRlKSB0byBhIHZlY3RvciBhbmQgcmVwb3J0IHRoZSBjb3NpbmUgc2ltaWxhcml0eSB3aXRoIHRoZSB1bnRyYW5zbGF0ZWQgdmVjdG9yIGFuZCB2aXN1YWxpemUgaXQuCgpgYGB7cn0KayA8LSA5Cm0gPC0gbWF0cml4KHJlcChybm9ybShkKSwgayksIGssIGQsIGJ5cm93ID0gVFJVRSkKbQpgYGAKCgpgYGB7cn0KYnNoaWZ0IDwtIG1hdHJpeChyZXAoc2VxKGspLTEsIGQpLyhrLTEpKjQsIGssIGQsIGJ5cm93ID0gRkFMU0UpCm1fc2hpZnQgPC0gbSArIGJzaGlmdApkZiA8LSBhcy5kYXRhLmZyYW1lKG1fc2hpZnQpCmRmJHNoaWZ0IDwtIGJzaGlmdFssMV0KCmRmMCA8LSBhcy5kYXRhLmZyYW1lKG0pCmRmMCRzaGlmdCA8LSBic2hpZnRbLDFdCgpkZiA8LSBiaW5kX3Jvd3MoZGYsIGRmMCkKYGBgCgoKQ29uc2lkZXIgJGskIHBhaXJzIG9mIHZlY3RvcnMuCgoKYGBge3J9CmRmICU+JSBzZWxlY3QoVjEsIFYyLCBzaGlmdCkgJT4lIGFycmFuZ2Uoc2hpZnQpCmBgYAoKCmBgYHtyfQpkZiAlPD4lCiAgaW5uZXJfam9pbigKICAgIGRmICU+JQogICAgICBncm91cF9ieShzaGlmdCkgJT4lCiAgICAgIG5lc3QoKSAlPiUKICAgICAgbXV0YXRlKGNvc2luZV92YWwgPSBtYXAoZGF0YSwgZnVuY3Rpb24oZGF0YSkgewogICAgICAgIG0gPC0gYXMubWF0cml4KGRhdGEpCiAgICAgICAgcm91bmQoY29zaW5lX3NpbShtWzEsXSwgbVsyLF0pLCAyKQogICAgICB9KSkgJT4lCiAgICAgIG11dGF0ZShjb3JfdmFsID0gbWFwKGRhdGEsIGZ1bmN0aW9uKGRhdGEpIHsKICAgICAgICBtIDwtIGFzLm1hdHJpeChkYXRhKQogICAgICAgIHJvdW5kKGNvcihtWzEsXSwgbVsyLF0pLCAyKQogICAgICB9KSkgJT4lCiAgICAgIHVubmVzdChjKGNvc2luZV92YWwsIGNvcl92YWwpKSAlPiUKICAgICAgc2VsZWN0KC1kYXRhKQogICkKCmRmICU+JSBzZWxlY3QoVjEsIFYyLCBzaGlmdCwgY29zaW5lX3ZhbCwgY29yX3ZhbCkKYGBgCgoKYGBge3J9CmdncGxvdCgpICsKICBnZW9tX3NlZ21lbnQoZGF0YSA9IGRmLAogICAgICAgICAgICAgICBhZXMoCiAgICAgICAgICAgICAgICAgeCA9IDAsCiAgICAgICAgICAgICAgICAgeSA9IDAsCiAgICAgICAgICAgICAgICAgeGVuZCA9IFYxLAogICAgICAgICAgICAgICAgIHllbmQgPSBWMiwKICAgICAgICAgICAgICAgICBjb2xvciA9IGNvc2luZV92YWwKICAgICAgICAgICAgICAgKSwKICAgICAgICAgICAgICAgYXJyb3cgPSBhcnJvdyh0eXBlID0gImNsb3NlZCIsIGFuZ2xlID0gMjAsIGxlbmd0aCA9IHVuaXQoMC4xLCAiaW5jaGVzIikpKSArCiAgY29vcmRfZXF1YWwoKSArCiAgZ2dmb3JjZTo6Z2VvbV9jaXJjbGUoZGF0YSA9IGRhdGEuZnJhbWUoeDAgPSAwLCB5MCA9IDAsIHIgPSAxKSwKICAgICAgICAgICAgICAgICAgICAgICBhZXMoeDAgPSB4MCwgeTAgPSB5MCwgciA9IHIpLCBjb2xvciA9ICJyZWQiKSArCiAgZ2d0aXRsZSgiT25seSBmaXJzdCB0d28gZGltZW5zaW9ucyBzaG93biIsIHN1YnRpdGxlID0gIlBlYXJzb24gc2ltLiA9IDEgZm9yIGFsbCBjYXNlcyIpICsgZmFjZXRfd3JhcCh+c2hpZnQpCmBgYAoKVGhlIGludmFyaWFuY2UgdG8gdGhpcyBzaGlmdCBpcyBhbG1vc3QgbmV2ZXIgdXNlZnVsLCBiZWNhdXNlIGl0IGlzIHNoaWZ0aW5nIGFsbCBkaW1lbmlvbnMgYnkgdGhlIHNhbWUgdmFsdWUuIApTbyB3aGlsZSB0aGlzIGludmFyaWFuY2UgZXhpc3RzIGZvciBQZWFyc29uLCBpdCBkb2Vzbid0IGV2ZXIgc2VlbSBhZHZhbnRhZ2VvdXMgKG92ZXIgY29zaW5lKS4KQ29zaW5lIGlzIGJldHRlciB0aGFuIFBlYXJzb24gd2hlbiBpdCBpcyB1c2VmdWwgdG8gaWdub3JlIG1pc3NpbmcgZmVhdHVyZXMgKGJlY2F1c2UgY29zaW5lIHdpbGwgcmVtYWluIHRoZSBzYW1lIGJ1dCBQZWFyc29uIHdpbGwgY2hhbmdlKQpQZWFyc29uIGlzIGJldHRlciB0aGFuIENvc2luZSB3aGVuIGl0IGlzIHVzZWZ1bCB0byBpZ25vcmUgdGhlIG1lYW4gYWNyb3NzIGZlYXR1cmVzIChiZWNhdXNlIFBlYXJzb24gc3VidHJhY3RzIHRoZSBtZWFuKQoKV2UndmUgc2VlbiB0aGlzIGFib3ZlIGJ1dCBoZXJlIGl0IGlzIGFnYWluIGluIGEgZGlmZmVyZW50IGZvcm0KCmBgYHtyfQp4IDwtIHJ1bmlmKDEwMCkqMTAwCnkgPC0gcnVuaWYoMTAwKSoxMDAKeHogPC0gYyh4LCByZXAoMCwgMTAwMCkpCnl6IDwtIGMoeSwgcmVwKDAsIDEwMDApKQpgYGAKCgpgYGB7cn0KbWVhbih4KQptZWFuKHkpCmBgYAoKYGBge3J9Cm1lYW4oeHopCm1lYW4oeXopCmBgYAoKYGBge3J9CmNvc2luZV9zaW0oeCwgeSkKcGVhcnNvbih4LCB5KQpgYGAKCgpgYGB7cn0KY29zaW5lX3NpbSh4eiwgeXopCnBlYXJzb24oeHosIHl6KQpgYGAKClVzZWZ1bCBwYWdlcwotIGh0dHBzOi8vd3d3LnF1b3JhLmNvbS9Jbi13aGF0LXNjZW5hcmlvLWlzLXVzaW5nLVBlYXJzb24tY29ycmVsYXRpb24tYmV0dGVyLXRoYW4tQ29zaW5lLXNpbWlsYXJpdHkKLSBodHRwczovL2dyb3VwbGVucy5vcmcvYmxvZy9zaW1pbGFyaXR5LWZ1bmN0aW9ucy1mb3ItdXNlci11c2VyLWNvbGxhYm9yYXRpdmUtZmlsdGVyaW5nLwo=