library(igraph)  # used to compute the Page Rank of the n square matrix A

Final Exam


Final Problem 1. 30 Points

PAGERANK: AN APPLICATION OF PROBABILITY AND LINEAR ALGEBRA

1. Playing with PageRank

You’ll verify for yourself that PageRank works by performing calculations on a small universe of web pages.

Let’s use the 6 page universe that we had in the previous discussion For this directed graph, perform the following calculations in R.

* Form the A matrix. Then, introduce decay and form the B matrix as we did in the course notes. (5 Points)

Form the A matrix
a <- c(0  , 1/2, 1/2,   0,   0,   0,
       0  ,   0,   0,   0,   0,   0,
       1/3, 1/3,   0,   0, 1/3,   0,
       0  ,   0,   0,   0, 1/2, 1/2,
       0  ,   0,   0, 1/2,   0, 1/2,
       0  ,   0,   0,   1,   0,   0)


n <- 6   # Use 6 to correspond with the 6 URLs of our test universe

A <- matrix(data = a, nrow = n, ncol = n, byrow = TRUE)

# Print the A matrix
A
#>           [,1]      [,2] [,3] [,4]      [,5] [,6]
#> [1,] 0.0000000 0.5000000  0.5  0.0 0.0000000  0.0
#> [2,] 0.0000000 0.0000000  0.0  0.0 0.0000000  0.0
#> [3,] 0.3333333 0.3333333  0.0  0.0 0.3333333  0.0
#> [4,] 0.0000000 0.0000000  0.0  0.0 0.5000000  0.5
#> [5,] 0.0000000 0.0000000  0.0  0.5 0.0000000  0.5
#> [6,] 0.0000000 0.0000000  0.0  1.0 0.0000000  0.0


Apply the decay and form the B matrix

\(B = d \times A + \frac{(1-d)}{n}\)

d <- 0.85  # chosen decay d = 0.85 (a.k.a damping factor)

B1 <- d * A + ( 1 - d ) / n

B1
#>           [,1]      [,2]  [,3]  [,4]      [,5]  [,6]
#> [1,] 0.0250000 0.4500000 0.450 0.025 0.0250000 0.025
#> [2,] 0.0250000 0.0250000 0.025 0.025 0.0250000 0.025
#> [3,] 0.3083333 0.3083333 0.025 0.025 0.3083333 0.025
#> [4,] 0.0250000 0.0250000 0.025 0.025 0.4500000 0.450
#> [5,] 0.0250000 0.0250000 0.025 0.450 0.0250000 0.450
#> [6,] 0.0250000 0.0250000 0.025 0.875 0.0250000 0.025


* Start with a uniform rank vector r and perform power iterations on B until convergence. That is, compute the solution \(r = B^n \times r\). Attempt this for a sufficiently large \(n\) so that \(r\) actually converges. (5 Points)


Define function that will compute the solution to \(r = B^n \times r\)
pageRank <- function(B, MaxIterations = 100) {

  urls        <- dim(B)[1]
  kth_power_B <- B
  i           <- 0
  converged_i <- TRUE
  converged   <- FALSE
  
  # define initial uniform rank vector r
  r <- r_f <- matrix( rep( 1 / urls, urls ), nrow = urls, ncol = 1)
  
  #while ( (converged != TRUE) & (i < MaxIterations) ) {
  while ( (converged != converged_i) & (i < MaxIterations) ) {
    
    # perform a new iteration because r_f has not yet converged
  
    converged_i <- converged
    
    # new ith r vector
    r_i <- r                         
    
    # matrix multiplication
    kth_power_B < kth_power_B %*% B  
    
    # probability of finding a user on a page/url
    r <- kth_power_B %*% r           
    
    r_f <- r
    
    # increase iteration counter
    i <- i + 1

    # check if the new r_f vector converged to the previous ith r vector
    converged <- all.equal(r_i, r_f)

  }
  
  # after the r_f converged, normalize it
  r <- r_f / sqrt( sum( r_f ^ 2 ) )
    
  output <- list("Converged" = converged,
                 "Iterations" = i,
                 "r" = r,
                 "Stabilized_B" =  kth_power_B)
  
  return(output)
}


Perform power iterations on B until convergence of r is achieved or max number of iterations is achieved.
r_ranking <- pageRank(B1, 100)


After \(n = 23\) iterations, the converged \(r_f\) vector is:

r_ranking["r"]
#> $r
#>           [,1]
#> [1,] 0.2159123
#> [2,] 0.0572329
#> [3,] 0.2980792
#> [4,] 0.5358032
#> [5,] 0.5358032
#> [6,] 0.5358032


The stabilized B matrix is:

r_ranking["Stabilized_B"]
#> $Stabilized_B
#>           [,1]      [,2]  [,3]  [,4]      [,5]  [,6]
#> [1,] 0.0250000 0.4500000 0.450 0.025 0.0250000 0.025
#> [2,] 0.0250000 0.0250000 0.025 0.025 0.0250000 0.025
#> [3,] 0.3083333 0.3083333 0.025 0.025 0.3083333 0.025
#> [4,] 0.0250000 0.0250000 0.025 0.025 0.4500000 0.450
#> [5,] 0.0250000 0.0250000 0.025 0.450 0.0250000 0.450
#> [6,] 0.0250000 0.0250000 0.025 0.875 0.0250000 0.025


* Compute the eigen-decomposition of \(B\) and verify that you indeed get an eigenvalue of \(1\) as the largest eigenvalue and that its corresponding eigenvector is the same vector that you obtained in the previous power iteration method. Further, this eigenvector has all positive entries and it sums to \(1\).(10 points)


Compute the eigen-decomposition of \(B\)
eigen_decomp <- eigen(B1)

eigen_decomp
#> eigen() decomposition
#> $values
#> [1]  0.95165271 -0.42500000 -0.42500000  0.41436582 -0.34733611 -0.01868242
#> 
#> $vectors
#>            [,1]          [,2]          [,3]        [,4]         [,5]
#> [1,] -0.2159123  5.345225e-01  5.345225e-01 -0.77869913 -0.775079691
#> [2,] -0.0572329 -3.580707e-17 -3.124265e-17 -0.07537631  0.009090375
#> [3,] -0.2980792 -5.345225e-01 -5.345225e-01 -0.61034825  0.631781588
#> [4,] -0.5358032  2.672612e-01  2.672612e-01  0.07169632  0.002637034
#> [5,] -0.5358032  2.672612e-01  2.672612e-01  0.07169632  0.002637034
#> [6,] -0.5358032 -5.345225e-01 -5.345225e-01  0.07169632  0.002637034
#>             [,6]
#> [1,] -0.55060201
#> [2,]  0.61511270
#> [3,] -0.56386946
#> [4,]  0.01322899
#> [5,]  0.01322899
#> [6,]  0.01322899


Find the largest eigenvalue of the eigen-decomposition
maxEigenVal <- max(eigen_decomp$values)

maxEigenVal
#> [1] 0.9516527

The maximum value of the eigen-decomposition is not \(1\), but it is close to it.


Compare the eigenvector of the highest eigenvalue with the \(r\) vector of the power iteration method
eigenvec <- eigen_decomp$vectors[,1]

comp_vecs <- data.frame(
  "r" = r_ranking$r,
  "eigenvector" = eigenvec
)

comp_vecs
#>           r eigenvector
#> 1 0.2159123  -0.2159123
#> 2 0.0572329  -0.0572329
#> 3 0.2980792  -0.2980792
#> 4 0.5358032  -0.5358032
#> 5 0.5358032  -0.5358032
#> 6 0.5358032  -0.5358032

We notice that the values of the eigenvector are all negative but they have the same magnitude as the values of the \(r\) vector of the power iteration method.


Check if eigenvector has all positive entries and it sums to \(1\).
# sum all elements of the eigenvector
sum(eigenvec)
#> [1] -2.178634
# sum all elements of the eigenvector in normalized form
sum(1/sum(eigenvec) * eigenvec)
#> [1] 1

From the results above, we can see that all the elements of the eigenvector are negative and do not add up to \(1\). However, we can see that the normalized version of the eigenvector does add up to \(1\).


* Use the \(graph\) package in \(R\) and its \(page.rank\) method to compute the Page Rank of the graph as given in A. Note that you don’t need to apply decay. The package starts with a connected graph and applies decay internally. Verify that you do get the same PageRank vector as the two approaches above. (10 points)


Create a directed graph from the A adjacency matrix
A_graph <- igraph::graph_from_adjacency_matrix( adjmatrix = A, mode = "directed", weighted = TRUE)


Plot the graph
plot(A_graph)


Compute the Page Rank of the graph
igraphPageRank <- igraph::page.rank(A_graph)

as.matrix(igraphPageRank$vector)
#>            [,1]
#> [1,] 0.05170475
#> [2,] 0.07367926
#> [3,] 0.05741241
#> [4,] 0.34870369
#> [5,] 0.19990381
#> [6,] 0.26859608


Verify that you do get the same PageRank vector as the two approaches above.
comp_vecs <- data.frame(
  "r" = r_ranking$r,
  "eigenvector" = eigenvec,
  "igraph_vector" = igraphPageRank$vector
)

comp_vecs
#>           r eigenvector igraph_vector
#> 1 0.2159123  -0.2159123    0.05170475
#> 2 0.0572329  -0.0572329    0.07367926
#> 3 0.2980792  -0.2980792    0.05741241
#> 4 0.5358032  -0.5358032    0.34870369
#> 5 0.5358032  -0.5358032    0.19990381
#> 6 0.5358032  -0.5358032    0.26859608

From the results above, we can see that the PageRank vector is not the same as the vectors obtained in the two previous approaches above.



LS0tDQp0aXRsZTogIkRBVEE2MDUgLSBGaW5hbCBFeGFtIC0gUHJvYmxlbSAxIg0KYXV0aG9yOiAiRXN0ZWJhbiBBcmFtYXlvIg0KZGF0ZTogIjIwMjItMDUtMjIiDQpvdXRwdXQ6IG9wZW5pbnRybzo6bGFiX3JlcG9ydA0KLS0tDQoNCmBgYHtyIGdsb2JhbC1vcHRpb25zLCBpbmNsdWRlPUZBTFNFfQ0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGVjaG89VFJVRSwgd2FybmluZz1GQUxTRSwgbWVzc2FnZT1GQUxTRSwNCiAgICAgICAgICAgICAgICAgICAgICBjb2xsYXBzZSA9IEZBTFNFLA0KICAgICAgICAgICAgICAgICAgICAgIGNvbW1lbnQgPSAiIz4iICkNCmBgYA0KDQpgYGB7cn0NCmxpYnJhcnkoaWdyYXBoKSAgIyB1c2VkIHRvIGNvbXB1dGUgdGhlIFBhZ2UgUmFuayBvZiB0aGUgbiBzcXVhcmUgbWF0cml4IEENCmBgYA0KDQoNCg0KIyBGaW5hbCBFeGFtDQoNCg0KPGJyPg0KDQojIyBGaW5hbCBQcm9ibGVtIDEuIDMwIFBvaW50cw0KDQojIyMgUEFHRVJBTks6IEFOIEFQUExJQ0FUSU9OIE9GIFBST0JBQklMSVRZIEFORCBMSU5FQVIgQUxHRUJSQQ0KDQojIyMgMS4gUGxheWluZyB3aXRoIFBhZ2VSYW5rDQoNCllvdSdsbCB2ZXJpZnkgZm9yIHlvdXJzZWxmIHRoYXQgUGFnZVJhbmsgd29ya3MgYnkgcGVyZm9ybWluZyBjYWxjdWxhdGlvbnMgb24gYSBzbWFsbCB1bml2ZXJzZSBvZiB3ZWIgcGFnZXMuDQoNCkxldCdzIHVzZSB0aGUgNiBwYWdlIHVuaXZlcnNlIHRoYXQgd2UgaGFkIGluIHRoZSBwcmV2aW91cyBkaXNjdXNzaW9uIEZvciB0aGlzIGRpcmVjdGVkIGdyYXBoLCBwZXJmb3JtIHRoZSBmb2xsb3dpbmcgY2FsY3VsYXRpb25zIGluIFIuDQoNCg0KPGltZyBzcmM9IlRveV9FeGFtcGxlXzZfVVJMLnBuZyIgc3R5bGU9IndpZHRoOjE2MHB4O2hlaWdodD04MHB4Ij4NCg0KPCEtLSAhW1RveSBFeGFtcGxlIHdpdGggNiBVUkxzXShUb3lfRXhhbXBsZV82X1VSTC5wbmcpe3dpZHRoPTUwJX17aGVpZ2h0PTUwJX0gLS0+DQoNCg0KIyMjIyAqIEZvcm0gdGhlIEEgbWF0cml4LiBUaGVuLCBpbnRyb2R1Y2UgZGVjYXkgYW5kIGZvcm0gdGhlIEIgbWF0cml4IGFzIHdlIGRpZCBpbiB0aGUgY291cnNlIG5vdGVzLiAoNSBQb2ludHMpDQoNCg0KIyMjIyMgRm9ybSB0aGUgQSBtYXRyaXgNCg0KDQpgYGB7cn0NCmEgPC0gYygwICAsIDEvMiwgMS8yLCAgIDAsICAgMCwgICAwLA0KICAgICAgIDAgICwgICAwLCAgIDAsICAgMCwgICAwLCAgIDAsDQogICAgICAgMS8zLCAxLzMsICAgMCwgICAwLCAxLzMsICAgMCwNCiAgICAgICAwICAsICAgMCwgICAwLCAgIDAsIDEvMiwgMS8yLA0KICAgICAgIDAgICwgICAwLCAgIDAsIDEvMiwgICAwLCAxLzIsDQogICAgICAgMCAgLCAgIDAsICAgMCwgICAxLCAgIDAsICAgMCkNCg0KDQpuIDwtIDYgICAjIFVzZSA2IHRvIGNvcnJlc3BvbmQgd2l0aCB0aGUgNiBVUkxzIG9mIG91ciB0ZXN0IHVuaXZlcnNlDQoNCkEgPC0gbWF0cml4KGRhdGEgPSBhLCBucm93ID0gbiwgbmNvbCA9IG4sIGJ5cm93ID0gVFJVRSkNCg0KIyBQcmludCB0aGUgQSBtYXRyaXgNCkENCg0KYGBgDQoNCg0KPCEtLSBGcm9tIHRoZSBvdXRwdXQgYWJvdmUsIHdlIG5vdGljZSB0aGF0IHRoZSAybmQgcm93IG9mIHRoZSBBIG1hdHJpeCBjb250YWlucyBhbGwgemVyb2VzLiBUaGlzIGlzIGJlY2F1c2UgdGhlIG5vZGUgMiBvZiBvdXIgVVJMIHVuaXZlcnNlIGhhcyBubyBvdXRnb2luZyBsaW5rcy4gVGhpcyBjYW4gY2F1c2UgY29udmVyZ2VuY2UgcHJvYmxlbXMgd2hlbiBwZXJmb3JtaW5nIHBvd2VyIGl0ZXJhdGlvbnMgb2YgdGhlIEEgbWF0cml4IHRvIGdlbmVyYXRlIHRoZSBCIG1hdHJpeC4gVG8gYWRkcmVzcyB0aGlzIHBvdGVudGlhbCBpc3N1ZSwgd2Ugd2lsbCBhc3NpZ24gYSB2YWx1ZSBvZiAxL24gdG8gZWFjaCBlbGVtZW50IG9mIHRoZSAybmQgcm93IHRvIGVuc3VyZSB0aGF0IHRoZSBwcm9iYWJpbGl0eSBvZiB2aXNpdGluZyBhbnkgb2YgdGhlICRuJCBVUkxzIGZyb20gbm9kZSAyIGlzIGVxdWFsbHkgcHJvYmFibGUgYW5kIGFsc28gd2lsbCBhbGxvdyB1cyB0byBlbnN1cmUgdGhhdCB0aGUgdG90YWwgcHJvYmFiaWxpdHkgb2YgdGhhdCByb3cgYWRkcyB1cCB0byAxLiAtLT4NCg0KPCEtLSBSZWRlZmluZSB0aGUgQSBtYXRyaXggLS0+DQoNCmBgYHtyIGV2YWw9RkFMU0UsIGVjaG89RkFMU0V9DQojIGEgPC0gYygwICAsIDEvMiwgMS8yLCAgIDAsICAgMCwgICAwLA0KIyAgICAgICAgMS9uLCAxL24sIDEvbiwgMS9uLCAxL24sIDEvbiwNCiMgICAgICAgIDEvMywgMS8zLCAgIDAsICAgMCwgMS8zLCAgIDAsDQojICAgICAgICAwICAsICAgMCwgICAwLCAgIDAsIDEvMiwgMS8yLA0KIyAgICAgICAgMCAgLCAgIDAsICAgMCwgMS8yLCAgIDAsIDEvMiwNCiMgICAgICAgIDAgICwgICAwLCAgIDAsICAgMSwgICAwLCAgIDApDQojIA0KIyBBIDwtIG1hdHJpeChkYXRhID0gYSwgbnJvdyA9IG4sIG5jb2wgPSBuLCBieXJvdyA9IFRSVUUpDQojIA0KIyAjIFByaW50IHRoZSBBIG1hdHJpeA0KIyBBDQpgYGANCg0KDQoNCjxicj4NCg0KIyMjIyMgQXBwbHkgdGhlIGRlY2F5IGFuZCBmb3JtIHRoZSBCIG1hdHJpeA0KDQokQiA9IGQgXHRpbWVzIEEgKyBcZnJhY3soMS1kKX17bn0kDQoNCmBgYHtyfQ0KZCA8LSAwLjg1ICAjIGNob3NlbiBkZWNheSBkID0gMC44NSAoYS5rLmEgZGFtcGluZyBmYWN0b3IpDQoNCkIxIDwtIGQgKiBBICsgKCAxIC0gZCApIC8gbg0KDQpCMQ0KYGBgDQoNCg0KDQo8YnI+DQoNCiMjIyMgKiBTdGFydCB3aXRoIGEgdW5pZm9ybSByYW5rIHZlY3RvciByIGFuZCBwZXJmb3JtIHBvd2VyIGl0ZXJhdGlvbnMgb24gQiB1bnRpbCBjb252ZXJnZW5jZS4gVGhhdCBpcywgY29tcHV0ZSB0aGUgc29sdXRpb24gJHIgPSBCXm4gXHRpbWVzIHIkLiBBdHRlbXB0IHRoaXMgZm9yIGEgc3VmZmljaWVudGx5IGxhcmdlICRuJCBzbyB0aGF0ICRyJCBhY3R1YWxseSBjb252ZXJnZXMuICg1IFBvaW50cykNCg0KPGJyPg0KDQojIyMjIyBEZWZpbmUgZnVuY3Rpb24gdGhhdCB3aWxsIGNvbXB1dGUgdGhlIHNvbHV0aW9uIHRvICRyID0gQl5uIFx0aW1lcyByJA0KDQpgYGB7cn0NCnBhZ2VSYW5rIDwtIGZ1bmN0aW9uKEIsIE1heEl0ZXJhdGlvbnMgPSAxMDApIHsNCg0KICB1cmxzICAgICAgICA8LSBkaW0oQilbMV0NCiAga3RoX3Bvd2VyX0IgPC0gQg0KICBpICAgICAgICAgICA8LSAwDQogIGNvbnZlcmdlZF9pIDwtIFRSVUUNCiAgY29udmVyZ2VkICAgPC0gRkFMU0UNCiAgDQogICMgZGVmaW5lIGluaXRpYWwgdW5pZm9ybSByYW5rIHZlY3RvciByDQogIHIgPC0gcl9mIDwtIG1hdHJpeCggcmVwKCAxIC8gdXJscywgdXJscyApLCBucm93ID0gdXJscywgbmNvbCA9IDEpDQogIA0KICAjd2hpbGUgKCAoY29udmVyZ2VkICE9IFRSVUUpICYgKGkgPCBNYXhJdGVyYXRpb25zKSApIHsNCiAgd2hpbGUgKCAoY29udmVyZ2VkICE9IGNvbnZlcmdlZF9pKSAmIChpIDwgTWF4SXRlcmF0aW9ucykgKSB7DQogICAgDQogICAgIyBwZXJmb3JtIGEgbmV3IGl0ZXJhdGlvbiBiZWNhdXNlIHJfZiBoYXMgbm90IHlldCBjb252ZXJnZWQNCiAgDQogICAgY29udmVyZ2VkX2kgPC0gY29udmVyZ2VkDQogICAgDQogICAgIyBuZXcgaXRoIHIgdmVjdG9yDQogICAgcl9pIDwtIHIgICAgICAgICAgICAgICAgICAgICAgICAgDQogICAgDQogICAgIyBtYXRyaXggbXVsdGlwbGljYXRpb24NCiAgICBrdGhfcG93ZXJfQiA8IGt0aF9wb3dlcl9CICUqJSBCICANCiAgICANCiAgICAjIHByb2JhYmlsaXR5IG9mIGZpbmRpbmcgYSB1c2VyIG9uIGEgcGFnZS91cmwNCiAgICByIDwtIGt0aF9wb3dlcl9CICUqJSByICAgICAgICAgICANCiAgICANCiAgICByX2YgPC0gcg0KICAgIA0KICAgICMgaW5jcmVhc2UgaXRlcmF0aW9uIGNvdW50ZXINCiAgICBpIDwtIGkgKyAxDQoNCiAgICAjIGNoZWNrIGlmIHRoZSBuZXcgcl9mIHZlY3RvciBjb252ZXJnZWQgdG8gdGhlIHByZXZpb3VzIGl0aCByIHZlY3Rvcg0KICAgIGNvbnZlcmdlZCA8LSBhbGwuZXF1YWwocl9pLCByX2YpDQoNCiAgfQ0KICANCiAgIyBhZnRlciB0aGUgcl9mIGNvbnZlcmdlZCwgbm9ybWFsaXplIGl0DQogIHIgPC0gcl9mIC8gc3FydCggc3VtKCByX2YgXiAyICkgKQ0KICAgIA0KICBvdXRwdXQgPC0gbGlzdCgiQ29udmVyZ2VkIiA9IGNvbnZlcmdlZCwNCiAgICAgICAgICAgICAgICAgIkl0ZXJhdGlvbnMiID0gaSwNCiAgICAgICAgICAgICAgICAgInIiID0gciwNCiAgICAgICAgICAgICAgICAgIlN0YWJpbGl6ZWRfQiIgPSAga3RoX3Bvd2VyX0IpDQogIA0KICByZXR1cm4ob3V0cHV0KQ0KfQ0KDQpgYGANCg0KPGJyPg0KDQojIyMjIyBQZXJmb3JtIHBvd2VyIGl0ZXJhdGlvbnMgb24gQiB1bnRpbCBjb252ZXJnZW5jZSBvZiByIGlzIGFjaGlldmVkIG9yIG1heCBudW1iZXIgb2YgaXRlcmF0aW9ucyBpcyBhY2hpZXZlZC4NCg0KYGBge3J9DQpyX3JhbmtpbmcgPC0gcGFnZVJhbmsoQjEsIDEwMCkNCmBgYA0KDQo8YnI+DQoNCkFmdGVyICRuID0gYHIgcl9yYW5raW5nWyJJdGVyYXRpb25zIl1gJCBpdGVyYXRpb25zLCB0aGUgY29udmVyZ2VkICRyX2YkIHZlY3RvciBpczoNCg0KYGBge3J9DQpyX3JhbmtpbmdbInIiXQ0KYGBgDQoNCjxicj4NCg0KVGhlIHN0YWJpbGl6ZWQgQiBtYXRyaXggaXM6DQoNCmBgYHtyfQ0Kcl9yYW5raW5nWyJTdGFiaWxpemVkX0IiXQ0KYGBgDQoNCg0KDQoNCjxicj4NCg0KIyMjIyAqIENvbXB1dGUgdGhlIGVpZ2VuLWRlY29tcG9zaXRpb24gb2YgJEIkIGFuZCB2ZXJpZnkgdGhhdCB5b3UgaW5kZWVkIGdldCBhbiBlaWdlbnZhbHVlIG9mICQxJCBhcyB0aGUgbGFyZ2VzdCBlaWdlbnZhbHVlIGFuZCB0aGF0IGl0cyBjb3JyZXNwb25kaW5nIGVpZ2VudmVjdG9yIGlzIHRoZSBzYW1lIHZlY3RvciB0aGF0IHlvdSBvYnRhaW5lZCBpbiB0aGUgcHJldmlvdXMgcG93ZXIgaXRlcmF0aW9uIG1ldGhvZC4gRnVydGhlciwgdGhpcyBlaWdlbnZlY3RvciBoYXMgYWxsIHBvc2l0aXZlIGVudHJpZXMgYW5kIGl0IHN1bXMgdG8gJDEkLigxMCBwb2ludHMpDQoNCjxicj4NCg0KIyMjIyMgQ29tcHV0ZSB0aGUgZWlnZW4tZGVjb21wb3NpdGlvbiBvZiAkQiQNCg0KYGBge3J9DQplaWdlbl9kZWNvbXAgPC0gZWlnZW4oQjEpDQoNCmVpZ2VuX2RlY29tcA0KYGBgDQoNCjxicj4NCg0KIyMjIyMgRmluZCB0aGUgbGFyZ2VzdCBlaWdlbnZhbHVlIG9mIHRoZSBlaWdlbi1kZWNvbXBvc2l0aW9uDQoNCmBgYHtyfQ0KbWF4RWlnZW5WYWwgPC0gbWF4KGVpZ2VuX2RlY29tcCR2YWx1ZXMpDQoNCm1heEVpZ2VuVmFsDQpgYGANCg0KVGhlIG1heGltdW0gdmFsdWUgb2YgdGhlIGVpZ2VuLWRlY29tcG9zaXRpb24gaXMgbm90ICQxJCwgYnV0IGl0IGlzIGNsb3NlIHRvIGl0Lg0KDQo8YnI+DQoNCiMjIyMjIENvbXBhcmUgdGhlIGVpZ2VudmVjdG9yIG9mIHRoZSBoaWdoZXN0IGVpZ2VudmFsdWUgd2l0aCB0aGUgJHIkIHZlY3RvciBvZiB0aGUgcG93ZXIgaXRlcmF0aW9uIG1ldGhvZA0KDQpgYGB7cn0NCg0KZWlnZW52ZWMgPC0gZWlnZW5fZGVjb21wJHZlY3RvcnNbLDFdDQoNCmNvbXBfdmVjcyA8LSBkYXRhLmZyYW1lKA0KICAiciIgPSByX3JhbmtpbmckciwNCiAgImVpZ2VudmVjdG9yIiA9IGVpZ2VudmVjDQopDQoNCmNvbXBfdmVjcw0KDQpgYGANCg0KV2Ugbm90aWNlIHRoYXQgdGhlIHZhbHVlcyBvZiB0aGUgZWlnZW52ZWN0b3IgYXJlIGFsbCBuZWdhdGl2ZSBidXQgdGhleSBoYXZlIHRoZSBzYW1lIG1hZ25pdHVkZSBhcyB0aGUgdmFsdWVzIG9mIHRoZSAkciQgdmVjdG9yIG9mIHRoZSBwb3dlciBpdGVyYXRpb24gbWV0aG9kLg0KDQo8YnI+DQoNCiMjIyMjIENoZWNrIGlmIGVpZ2VudmVjdG9yIGhhcyBhbGwgcG9zaXRpdmUgZW50cmllcyBhbmQgaXQgc3VtcyB0byAkMSQuDQoNCmBgYHtyfQ0KIyBzdW0gYWxsIGVsZW1lbnRzIG9mIHRoZSBlaWdlbnZlY3Rvcg0Kc3VtKGVpZ2VudmVjKQ0KDQojIHN1bSBhbGwgZWxlbWVudHMgb2YgdGhlIGVpZ2VudmVjdG9yIGluIG5vcm1hbGl6ZWQgZm9ybQ0Kc3VtKDEvc3VtKGVpZ2VudmVjKSAqIGVpZ2VudmVjKQ0KYGBgDQoNCkZyb20gdGhlIHJlc3VsdHMgYWJvdmUsIHdlIGNhbiBzZWUgdGhhdCAqKmFsbCB0aGUgZWxlbWVudHMgb2YgdGhlIGVpZ2VudmVjdG9yIGFyZSBuZWdhdGl2ZSoqIGFuZCBkbyBub3QgYWRkIHVwIHRvICQxJC4gSG93ZXZlciwgd2UgY2FuIHNlZSB0aGF0ICoqdGhlIG5vcm1hbGl6ZWQgdmVyc2lvbiBvZiB0aGUgZWlnZW52ZWN0b3IqKiBkb2VzIGFkZCB1cCB0byAkMSQuDQoNCg0KDQo8YnI+DQoNCiMjIyMgKiBVc2UgdGhlICRncmFwaCQgcGFja2FnZSBpbiAkUiQgYW5kIGl0cyAkcGFnZS5yYW5rJCBtZXRob2QgdG8gY29tcHV0ZSB0aGUgUGFnZSBSYW5rIG9mIHRoZSBncmFwaCBhcyBnaXZlbiBpbiBBLiBOb3RlIHRoYXQgeW91IGRvbid0IG5lZWQgdG8gYXBwbHkgZGVjYXkuIFRoZSBwYWNrYWdlIHN0YXJ0cyB3aXRoIGEgY29ubmVjdGVkIGdyYXBoIGFuZCBhcHBsaWVzIGRlY2F5IGludGVybmFsbHkuIFZlcmlmeSB0aGF0IHlvdSBkbyBnZXQgdGhlIHNhbWUgUGFnZVJhbmsgdmVjdG9yIGFzIHRoZSB0d28gYXBwcm9hY2hlcyBhYm92ZS4gKDEwIHBvaW50cykNCg0KPGJyPg0KDQojIyMjIyBDcmVhdGUgYSBkaXJlY3RlZCBncmFwaCBmcm9tIHRoZSBBIGFkamFjZW5jeSBtYXRyaXgNCg0KDQpgYGB7cn0NCkFfZ3JhcGggPC0gaWdyYXBoOjpncmFwaF9mcm9tX2FkamFjZW5jeV9tYXRyaXgoIGFkam1hdHJpeCA9IEEsIG1vZGUgPSAiZGlyZWN0ZWQiLCB3ZWlnaHRlZCA9IFRSVUUpDQpgYGANCg0KPGJyPg0KDQojIyMjIyBQbG90IHRoZSBncmFwaA0KDQpgYGB7cn0NCnBsb3QoQV9ncmFwaCkNCmBgYA0KDQo8YnI+DQoNCiMjIyMjIENvbXB1dGUgdGhlIFBhZ2UgUmFuayBvZiB0aGUgZ3JhcGgNCg0KYGBge3J9DQppZ3JhcGhQYWdlUmFuayA8LSBpZ3JhcGg6OnBhZ2UucmFuayhBX2dyYXBoKQ0KDQphcy5tYXRyaXgoaWdyYXBoUGFnZVJhbmskdmVjdG9yKQ0KYGBgDQoNCjxicj4NCg0KIyMjIyMgVmVyaWZ5IHRoYXQgeW91IGRvIGdldCB0aGUgc2FtZSBQYWdlUmFuayB2ZWN0b3IgYXMgdGhlIHR3byBhcHByb2FjaGVzIGFib3ZlLg0KDQpgYGB7cn0NCmNvbXBfdmVjcyA8LSBkYXRhLmZyYW1lKA0KICAiciIgPSByX3JhbmtpbmckciwNCiAgImVpZ2VudmVjdG9yIiA9IGVpZ2VudmVjLA0KICAiaWdyYXBoX3ZlY3RvciIgPSBpZ3JhcGhQYWdlUmFuayR2ZWN0b3INCikNCg0KY29tcF92ZWNzDQpgYGANCg0KDQpGcm9tIHRoZSByZXN1bHRzIGFib3ZlLCB3ZSBjYW4gc2VlIHRoYXQgdGhlIFBhZ2VSYW5rIHZlY3RvciBpcyBub3QgdGhlIHNhbWUgYXMgdGhlIHZlY3RvcnMgb2J0YWluZWQgaW4gdGhlIHR3byBwcmV2aW91cyBhcHByb2FjaGVzIGFib3ZlLg0KDQo8YnI+DQoNCjxicj4NCg0K