1 Introduction

The goal of this project is give you practice beginning to work with a distributed recommender system. It is sufficient for this assignment to build out your application on a single node.

2 Assignment Highlights

  1. Adapt one of your recommendation systems to work with Apache Spark and compare the performance with your previous iteration.

  2. Consider the efficiency of the system and the added complexity of using Spark. You may complete the
    assignment using PySpark (Python), SparkR (R) , sparklyr (R), or Scala.

  3. Please include in your conclusion: For your given recommender system’s data, algorithm(s), and
    (envisioned) implementation, at what point would you see moving to a distributed platform such as Spark becoming necessary?

You may work on any platform of your choosing, including Databricks Community Edition or in local mode. You are encouraged but not required to work in a small group on this project.

4 About the Data

For Project 5, I will build on Project 4 using the same MovieLens dataset, and evaluate the models accuracy using RMSE, MSE and MAE.

The MovieLens Latest Small Datasets contain 100,000 ratings and 3,600 tag applications applied to 9,000 movies by 600 users.

4.1 Loading the MovieLense Data

## 943 x 1664 rating matrix of class 'realRatingMatrix' with 99392 ratings.
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##    19.0    32.0    64.0   105.4   147.5   735.0
## [1] "data"      "normalize"
## [1] "realRatingMatrix"
## attr(,"package")
## [1] "recommenderlab"
##  [1] "Toy Story (1995)"                                    
##  [2] "GoldenEye (1995)"                                    
##  [3] "Four Rooms (1995)"                                   
##  [4] "Get Shorty (1995)"                                   
##  [5] "Copycat (1995)"                                      
##  [6] "Shanghai Triad (Yao a yao yao dao waipo qiao) (1995)"
##  [7] "Twelve Monkeys (1995)"                               
##  [8] "Babe (1995)"                                         
##  [9] "Dead Man Walking (1995)"                             
## [10] "Richard III (1995)"

4.2 Data structure

## Formal class 'realRatingMatrix' [package "recommenderlab"] with 2 slots
##   ..@ data     :Formal class 'dgCMatrix' [package "Matrix"] with 6 slots
##   .. .. ..@ i       : int [1:99392] 0 1 4 5 9 12 14 15 16 17 ...
##   .. .. ..@ p       : int [1:1665] 0 452 583 673 882 968 994 1386 1605 1904 ...
##   .. .. ..@ Dim     : int [1:2] 943 1664
##   .. .. ..@ Dimnames:List of 2
##   .. .. .. ..$ : chr [1:943] "1" "2" "3" "4" ...
##   .. .. .. ..$ : chr [1:1664] "Toy Story (1995)" "GoldenEye (1995)" "Four Rooms (1995)" "Get Shorty (1995)" ...
##   .. .. ..@ x       : num [1:99392] 5 4 4 4 4 3 1 5 4 5 ...
##   .. .. ..@ factors : list()
##   ..@ normalize: NULL

4.4 Exploring the ratings values

## [1] 5 4 0 3 1 2
## ratingvalues
##       0       1       2       3       4       5 
## 1469760    6059   11307   27002   33947   21077

4.5 Excluding the missing values

4.6 Distribution of the Ratings

# Pre-processing

Convert data to numeric

##     user                                                 item rating
## 1      1                                     Toy Story (1995)      5
## 453    1                                     GoldenEye (1995)      3
## 584    1                                    Four Rooms (1995)      4
## 674    1                                    Get Shorty (1995)      3
## 883    1                                       Copycat (1995)      3
## 969    1 Shanghai Triad (Yao a yao yao dao waipo qiao) (1995)      5

4.7 Create the sparse Matrix

## 943 x 1664 rating matrix of class 'realRatingMatrix' with 99392 ratings.

I’m selecting the Users who have rated at least 100 movies and those movies that have been watched at least 150 times

I’m selecting the Users who have rated at least 100 movies and those movies that have been watched at least 150 times

6 Algorithms to Predict Ratings

## ALS run fold/sample [model time/prediction time]
##   1  [0.02sec/7.05sec] 
## POPULAR run fold/sample [model time/prediction time]
##   1  [0.04sec/0.02sec] 
## UBCF run fold/sample [model time/prediction time]
##   1
## Warning: Unknown parameters: minRating
## Available parameter (with default values):
## method    =  cosine
## nn    =  25
## sample    =  FALSE
## weighted  =  TRUE
## normalize     =  center
## min_matching_items    =  0
## min_predictive_items  =  0
## verbose   =  FALSE
## [0sec/0.17sec] 
## IBCF2 run fold/sample [model time/prediction time]
##   1
## Timing stopped at: 0 0 0
## Error in .local(data, ...) : 
##   Recommender method IBCF2 not implemented for data type realRatingMatrix .
## Warning in .local(x, method, ...): 
##   Recommender 'ItemBased' has failed and has been removed from the results!

From the above information, it can be observed that Item Based failed and was removed from the modeling list.

6.1 Comparing the Recommender Systems

ALS seems to be the worst performing Model with the highest RMSE, MSE and MAE.Therefore, I will implement the worst performing Model (ALS) in Spark to see if the performance will improve.

7 The ALS Model Using Spark

7.0.1 Installing Spark

7.0.2 Check directory and version Installed

## [1] "C:\\Users\\Emahayz_Pro\\AppData\\Local/spark/spark-2.4.3-bin-hadoop2.7"
##   spark hadoop
## 1 2.4.3    2.7
##                                                                      dir
## 1 C:\\Users\\Emahayz_Pro\\AppData\\Local/spark/spark-2.4.3-bin-hadoop2.7

7.0.3 Establish a Local Connection

I have connected to Spark locally and will copy the data to spark.First, I will convert data based on sparklyr requirements as indicated below.

Split for training and testing

View the Training set

## # Source: spark<?> [?? x 4]
##    user item                                rating itemid
##   <dbl> <chr>                                <dbl>  <dbl>
## 1     1 12 Angry Men (1957)                      5      4
## 2     1 20,000 Leagues Under the Sea (1954)      3      7
## 3     1 2001: A Space Odyssey (1968)             4      8
## 4     1 Abyss, The (1989)                        3     18
## 5     1 Air Bud (1997)                           1     33
## 6     1 Akira (1988)                             4     37

View the Testing set

## # Source: spark<?> [?? x 4]
##     user item                                               rating itemid
##    <dbl> <chr>                                               <dbl>  <dbl>
##  1     1 101 Dalmatians (1996)                                   2      3
##  2     1 Ace Ventura: Pet Detective (1994)                       3     19
##  3     1 Aladdin (1992)                                          4     38
##  4     1 Alien (1979)                                            5     43
##  5     1 Austin Powers: International Man of Mystery (1997)      4    106
##  6     1 Bad Boys (1995)                                         2    117
##  7     1 Blade Runner (1982)                                     5    186
##  8     1 Cape Fear (1991)                                        3    264
##  9     1 Clerks (1994)                                           5    316
## 10     1 Copycat (1995)                                          3    344
## # ... with more rows

Spark Model

7.0.4 Making Predictions

##   user                           item rating itemid prediction
## 1  839 8 Heads in a Duffel Bag (1997)      1     12   3.660742
## 2  225               8 Seconds (1994)      4     13   3.738235
## 3  254               8 Seconds (1994)      4     13   2.838886
## 4  429               8 Seconds (1994)      2     13   3.453169
## 5  707          A Chef in Love (1996)      4     14   4.257363
## 6  794          A Chef in Love (1996)      4     14   3.984950

9 Conclusion

The purpose of this exercise was to build a recommender system and to practice working with distributed recommender system.

For the given data set, ALS provided the worst performance compared to POpular and User Based. Based on the poor performance of the ALS, I decided to build an ALS using Sparklyr to see if I can get better performance for ALS using a distributed recommender system.

The performance using Spark definitely improved as can be seen that the RMSE and MSE are far lower compared to the ALS recommender algorithm. The distributed recommender system shows a big advantage even though the implementation seems to be more complex.

9.0.1 References

Breese JS, Heckerman D, Kadie C (1998). “Empirical Analysis of Predictive Algorithms for Collaborative Filtering.” In Uncertainty in Artificial Intelligence. Proceedings of the Fourteenth Conference, pp. 43-52.

Kohavi, Ron (1995). “A study of cross-validation and bootstrap for accuracy estimation and model selection”. Proceedings of the Fourteenth International Joint Conference on Artificial Intelligence, pp. 1137-1143.

Koren, Y., Bell, R., & Volinsky, C. (2009). Matrix Factorization Techniques for Recommender Systems. Computer, 42(8), 30–37. https://doi.org/10.1109/MC.2009.263

Michael Hahsler (2016). recommenderlab: A Framework for Developing and Testing Recommendation Algorithms, R package. https://CRAN.R-project.org/package=recommenderlab

LS0tDQp0aXRsZTogIkRhdGEgNjEyIC0gUHJvamVjdCA1Ig0KYXV0aG9yOiBFbW1hbnVlbCBIYXlibGUtR29tZXMNCmRhdGU6ICIwNy8wNC8yMDIwIg0Kb3V0cHV0Og0KICBodG1sX2RvY3VtZW50Og0KICAgIGNvZGVfZG93bmxvYWQ6IHllcw0KICAgIGNvZGVfZm9sZGluZzogaGlkZQ0KICAgIGhpZ2hsaWdodDogcHlnbWVudHMNCiAgICBudW1iZXJfc2VjdGlvbnM6IHllcw0KICAgIHRoZW1lOiBmbGF0bHkNCiAgICB0b2M6IHllcw0KICAgIHRvY19mbG9hdDogeWVzDQogIHBkZl9kb2N1bWVudDoNCiAgICB0b2M6IHllcw0KLS0tDQoNCiMgSW50cm9kdWN0aW9uDQoNClRoZSBnb2FsIG9mIHRoaXMgcHJvamVjdCBpcyBnaXZlIHlvdSBwcmFjdGljZSBiZWdpbm5pbmcgdG8gd29yayB3aXRoIGEgZGlzdHJpYnV0ZWQgcmVjb21tZW5kZXIgc3lzdGVtLiBJdCBpcyBzdWZmaWNpZW50IGZvciB0aGlzIGFzc2lnbm1lbnQgdG8gYnVpbGQgb3V0IHlvdXIgYXBwbGljYXRpb24gb24gYSBzaW5nbGUgbm9kZS4NCg0KIyBBc3NpZ25tZW50IEhpZ2hsaWdodHMNCg0KMS4gQWRhcHQgb25lIG9mIHlvdXIgcmVjb21tZW5kYXRpb24gc3lzdGVtcyB0byB3b3JrIHdpdGggQXBhY2hlIFNwYXJrIGFuZCBjb21wYXJlIHRoZSBwZXJmb3JtYW5jZSB3aXRoIHlvdXIgICAgcHJldmlvdXMgaXRlcmF0aW9uLg0KDQoyLiBDb25zaWRlciB0aGUgZWZmaWNpZW5jeSBvZiB0aGUgc3lzdGVtIGFuZCB0aGUgYWRkZWQgY29tcGxleGl0eSBvZiB1c2luZyBTcGFyay4gWW91IG1heSBjb21wbGV0ZSB0aGUgICAgDQogICBhc3NpZ25tZW50IHVzaW5nIFB5U3BhcmsgKFB5dGhvbiksIFNwYXJrUiAoUikgLCBzcGFya2x5ciAoUiksIG9yIFNjYWxhLg0KDQozLiBQbGVhc2UgaW5jbHVkZSBpbiB5b3VyIGNvbmNsdXNpb246IEZvciB5b3VyIGdpdmVuIHJlY29tbWVuZGVyIHN5c3RlbeKAmXMgZGF0YSwgYWxnb3JpdGhtKHMpLCBhbmQgICAgDQogICAoZW52aXNpb25lZCkgaW1wbGVtZW50YXRpb24sIGF0IHdoYXQgcG9pbnQgd291bGQgeW91IHNlZSBtb3ZpbmcgdG8gYSBkaXN0cmlidXRlZCBwbGF0Zm9ybSBzdWNoIGFzIFNwYXJrICAgICBiZWNvbWluZyBuZWNlc3Nhcnk/DQoNCllvdSBtYXkgd29yayBvbiBhbnkgcGxhdGZvcm0gb2YgeW91ciBjaG9vc2luZywgaW5jbHVkaW5nIERhdGFicmlja3MgQ29tbXVuaXR5IEVkaXRpb24gb3IgaW4gbG9jYWwgbW9kZS4gWW91IGFyZSBlbmNvdXJhZ2VkIGJ1dCBub3QgcmVxdWlyZWQgdG8gd29yayBpbiBhIHNtYWxsIGdyb3VwIG9uIHRoaXMgcHJvamVjdC4NCg0KIyBMaWJyYXJpZXMgYW5kIEluc3RhbGxpbmcgU3BhcmsNCg0KYGBge3IsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCmxpYnJhcnkoZ2dwbG90MikNCmxpYnJhcnkoa2FibGVFeHRyYSkNCmxpYnJhcnkocmVjb21tZW5kZXJsYWIpDQpsaWJyYXJ5KHNwYXJrbHlyKQ0KbGlicmFyeSAoU3BhcmtSKQ0KbGlicmFyeShrbml0cikNCmBgYA0KDQojIEFib3V0IHRoZSBEYXRhDQoNCkZvciBQcm9qZWN0IDUsIEkgd2lsbCBidWlsZCBvbiBQcm9qZWN0IDQgdXNpbmcgdGhlIHNhbWUgTW92aWVMZW5zIGRhdGFzZXQsIGFuZCBldmFsdWF0ZSB0aGUgbW9kZWxzIGFjY3VyYWN5IHVzaW5nIFJNU0UsIE1TRSBhbmQgTUFFLg0KDQpUaGUgTW92aWVMZW5zIExhdGVzdCBTbWFsbCBEYXRhc2V0cyBjb250YWluIDEwMCwwMDAgcmF0aW5ncyBhbmQgMyw2MDAgdGFnIGFwcGxpY2F0aW9ucyBhcHBsaWVkIHRvIDksMDAwIG1vdmllcyBieSA2MDAgdXNlcnMuDQoNCiMjIExvYWRpbmcgdGhlIE1vdmllTGVuc2UgRGF0YQ0KDQpgYGB7cn0NCmRhdGEgKE1vdmllTGVuc2UpDQpNb3ZpZUxlbnNlDQpgYGANCg0KDQpgYGB7cn0NCnN1bW1hcnkocm93Q291bnRzKE1vdmllTGVuc2UpKQ0KDQpgYGANCg0KYGBge3J9DQpzbG90TmFtZXMoTW92aWVMZW5zZSkNCmNsYXNzKE1vdmllTGVuc2UpDQpoZWFkKGNvbG5hbWVzKE1vdmllTGVuc2UpLDEwKQ0KYGBgDQoNCiMjIERhdGEgc3RydWN0dXJlDQoNCmBgYHtyfQ0Kc3RyKE1vdmllTGVuc2UpDQpgYGANCg0KIyMgRGlzdHJpYnV0aW9uIG9mIHJhdGluZ3MNCg0KYGBge3J9DQpoaXN0KHJvd0NvdW50cyhNb3ZpZUxlbnNlKSwgDQogICAgIGJyZWFrcyA9IDIwLCANCiAgICAgbWFpbiA9ICJSYXcgRGlzdHJpYnV0aW9uIG9mIFVzZXIncyBSYXRpbmciDQogICAgICkNCmBgYA0KDQojIyBFeHBsb3JpbmcgdGhlIHJhdGluZ3MgdmFsdWVzDQoNCmBgYHtyfQ0KcmF0aW5ndmFsdWVzIDwtIGFzLnZlY3RvcihNb3ZpZUxlbnNlQGRhdGEpDQp1bmlxdWUocmF0aW5ndmFsdWVzKSAjIFRoZSByYXRpbmcgaXMgbnVtZXJpYyB3aXRoIHRoZSBsZWFzdCB2YWx1ZSBhcyAwLjUgYW5kIHRoZSBoaWdoZXN0IHZhbHVlcyBhcyA1Lg0KDQp0YWJsZXJhdGluZyA8LSB0YWJsZShyYXRpbmd2YWx1ZXMpDQp0YWJsZXJhdGluZw0KYGBgDQoNCiMjIEV4Y2x1ZGluZyB0aGUgbWlzc2luZyB2YWx1ZXMNCg0KYGBge3J9DQpyYXRpbmd2YWx1ZXMgPC0gcmF0aW5ndmFsdWVzW3JhdGluZ3ZhbHVlcyAhPTBdDQpgYGANCg0KDQojIyBEaXN0cmlidXRpb24gb2YgdGhlIFJhdGluZ3MNCg0KYGBge3J9DQpoaXN0KHJhdGluZ3ZhbHVlcywgDQogICAgIGJyZWFrcyA9IDUsIA0KICAgICBtYWluPSJEaXN0cmlidXRpb24gb2YgUmF0aW5ncyIsDQogICAgIHhsYWI9IlJhdGluZ3MiLA0KICAgICBjb2w9ImdyZXkiLA0KICAgICBmcmVxPVRSVUUNCiAgICAgKQ0KYGBgDQojIFByZS1wcm9jZXNzaW5nDQoNCioqQ29udmVydCBkYXRhIHRvIG51bWVyaWMqKg0KDQpgYGB7cn0NCm1vdmllcyA8LSBhcyhNb3ZpZUxlbnNlLCAnZGF0YS5mcmFtZScpDQpoZWFkKG1vdmllcykNCg0KYGBgDQoNCmBgYHtyfQ0KbW92aWVzIDwtIHRyYW5zZm9ybShtb3ZpZXMsIGl0ZW1pZCA9IGFzLm51bWVyaWMoZmFjdG9yKGl0ZW0pKSkNCmNvbG5hbWVzKG1vdmllcykgPC0gYygidXNlciIsIml0ZW0iLCJyYXRpbmciLCJpdGVtaWQiKQ0KDQptb3ZpZXMkdXNlciA8LSBhcy5udW1lcmljKG1vdmllcyR1c2VyKQ0KbW92aWVzJGl0ZW1pZCA8LSBhcy5udW1lcmljKG1vdmllcyRpdGVtaWQpDQoNCmBgYA0KDQojIyBDcmVhdGUgdGhlIHNwYXJzZSBNYXRyaXgNCg0KYGBge3J9DQpyYXRpbmdzTWF0IDwtIHNwYXJzZU1hdHJpeChpID0gbW92aWVzJHVzZXIsIGogPSBtb3ZpZXMkaXRlbWlkLCB4ID0gbW92aWVzJHJhdGluZywgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGltcyA9IGMobGVuZ3RoKHVuaXF1ZShtb3ZpZXMkdXNlcikpLCBsZW5ndGgodW5pcXVlKG1vdmllcyRpdGVtaWQpKSksICANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkaW1uYW1lcyA9IGxpc3QocGFzdGUoInUiLCAxOmxlbmd0aCh1bmlxdWUobW92aWVzJHVzZXIpKSwgc2VwID0gIiIpLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcGFzdGUoIm0iLCAxOmxlbmd0aCh1bmlxdWUobW92aWVzJGl0ZW1pZCkpLCBzZXAgPSAiIikpKQ0KDQpyYXRpbmdzUmVhbCA8LSBuZXcoInJlYWxSYXRpbmdNYXRyaXgiLCBkYXRhID0gcmF0aW5nc01hdCkNCnJhdGluZ3NSZWFsDQpgYGANCg0KKipJJ20gc2VsZWN0aW5nIHRoZSBVc2VycyB3aG8gaGF2ZSByYXRlZCBhdCBsZWFzdCAxMDAgbW92aWVzIGFuZCB0aG9zZSBtb3ZpZXMgdGhhdCBoYXZlIGJlZW4gd2F0Y2hlZCBhdCBsZWFzdCAxNTAgdGltZXMqKg0KDQpgYGB7cn0NCnJhdGluZ3MgPC0gTW92aWVMZW5zZVtyb3dDb3VudHMoTW92aWVMZW5zZSkgPiAxMDAsIGNvbENvdW50cyhNb3ZpZUxlbnNlKSA+IDE1MF0NCmBgYA0KDQoqKkknbSBzZWxlY3RpbmcgdGhlIFVzZXJzIHdobyBoYXZlIHJhdGVkIGF0IGxlYXN0IDEwMCBtb3ZpZXMgYW5kIHRob3NlIG1vdmllcyB0aGF0IGhhdmUgYmVlbiB3YXRjaGVkIGF0IGxlYXN0IDE1MCB0aW1lcyoqDQoNCmBgYHtyfQ0KcmF0aW5ncyA8LSBNb3ZpZUxlbnNlW3Jvd0NvdW50cyhNb3ZpZUxlbnNlKSA+IDEwMCwgY29sQ291bnRzKE1vdmllTGVuc2UpID4gMTUwXQ0KYGBgDQoNCg0KIyBTcGxpdGluZyB0aGUgRGF0YSBTZXQNCg0KVXNlIDkwJSBmb3IgdHJhaW5pbmcgdGhlIG1vZGVsDQoNCmBgYHtyfQ0KU2NoZW1lIDwtIGV2YWx1YXRpb25TY2hlbWUocmF0aW5ncywgbWV0aG9kID0gInNwbGl0IiwgayA9IDEsDQogICAgICAgICAgICAgICAgICAgICAgICB0cmFpbiA9IDAuOCwgZ2l2ZW49IDEwLCBnb29kUmF0aW5nPTQpDQpgYGANCg0KDQpgYGB7cn0NClNjaGVtZQ0KYGBgDQoNCg0KYGBge3J9DQpNb2RlbEFsZ29yaXRobXMgPC0gbGlzdCgNCiAgIkFMUyIgPSBsaXN0KG5hbWU9IkFMUyIsIHBhcmFtPWxpc3Qobm9ybWFsaXplID0gIlotc2NvcmUiKSksDQogICJQb3B1bGFyIiA9IGxpc3QobmFtZT0iUE9QVUxBUiIsIHBhcmFtPWxpc3Qobm9ybWFsaXplID0gIlotc2NvcmUiKSksDQogICJVc2VyQmFzZWQiID0gbGlzdChuYW1lPSJVQkNGIiwgcGFyYW09bGlzdChub3JtYWxpemUgPSAiWi1zY29yZSIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbWV0aG9kPSJDb3NpbmUiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5uPTUwLCBtaW5SYXRpbmc9MykpLA0KICAiSXRlbUJhc2VkIiA9IGxpc3QobmFtZT0iSUJDRjIiLCBwYXJhbT1saXN0KG5vcm1hbGl6ZSA9ICJaLXNjb3JlIg0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICkpDQogIA0KICApDQpgYGANCg0KIyBBbGdvcml0aG1zIHRvIFByZWRpY3QgUmF0aW5ncw0KDQpgYGB7cn0NCk1vZGVsUmVzdWx0cyA8LSBldmFsdWF0ZShTY2hlbWUsIE1vZGVsQWxnb3JpdGhtcywgdHlwZSA9ICJyYXRpbmdzIikNCmBgYA0KRnJvbSB0aGUgYWJvdmUgaW5mb3JtYXRpb24sIGl0IGNhbiBiZSBvYnNlcnZlZCB0aGF0IEl0ZW0gQmFzZWQgZmFpbGVkIGFuZCB3YXMgcmVtb3ZlZCBmcm9tIHRoZSBtb2RlbGluZyBsaXN0Lg0KDQojIyAqKkNvbXBhcmluZyB0aGUgUmVjb21tZW5kZXIgU3lzdGVtcyoqDQoNCmBgYHtyfQ0KcGxvdChNb2RlbFJlc3VsdHMpDQp0aXRsZShtYWluID0gIk1vZGVsIEVycm9yIENvbXBhcmlzb24iKQ0KYGBgDQpBTFMgc2VlbXMgdG8gYmUgdGhlIHdvcnN0IHBlcmZvcm1pbmcgTW9kZWwgd2l0aCB0aGUgaGlnaGVzdCBSTVNFLCBNU0UgYW5kIE1BRS5UaGVyZWZvcmUsIEkgd2lsbCBpbXBsZW1lbnQgdGhlIHdvcnN0IHBlcmZvcm1pbmcgTW9kZWwgKEFMUykgaW4gU3BhcmsgdG8gc2VlIGlmIHRoZSBwZXJmb3JtYW5jZSB3aWxsIGltcHJvdmUuDQoNCiMgVGhlIEFMUyBNb2RlbCBVc2luZyBTcGFyaw0KDQojIyMgSW5zdGFsbGluZyBTcGFyaw0KDQpgYGB7cn0NCiNzcGFya19pbnN0YWxsKCkNCmBgYA0KDQojIyMgQ2hlY2sgZGlyZWN0b3J5IGFuZCB2ZXJzaW9uIEluc3RhbGxlZA0KDQpgYGB7cn0NCnNwYXJrX2hvbWVfZGlyKCkNCnNwYXJrX2luc3RhbGxlZF92ZXJzaW9ucygpDQpgYGANCiMjIyBFc3RhYmxpc2ggYSBMb2NhbCBDb25uZWN0aW9uDQoNCmBgYHtyfQ0Kc2MgPC0gc3BhcmtfY29ubmVjdChtYXN0ZXIgPSAibG9jYWwiLCBzcGFya19ob21lPXNwYXJrX2hvbWVfZGlyKHZlcnNpb24gPSAiMi40LjMiKSkNCmBgYA0KDQpJIGhhdmUgY29ubmVjdGVkIHRvIFNwYXJrIGxvY2FsbHkgYW5kIHdpbGwgY29weSB0aGUgZGF0YSB0byBzcGFyay5GaXJzdCwgSSB3aWxsIGNvbnZlcnQgZGF0YSBiYXNlZCBvbiBzcGFya2x5ciByZXF1aXJlbWVudHMgYXMgaW5kaWNhdGVkIGJlbG93Lg0KDQpgYGB7cn0NClNwYXJrTXJ4IDwtIHNkZl9jb3B5X3RvKHNjLCBtb3ZpZXMsICJTcGFya01yeCIsIG92ZXJ3cml0ZSA9IFRSVUUpDQpgYGANCg0KU3BsaXQgZm9yIHRyYWluaW5nIGFuZCB0ZXN0aW5nDQpgYGB7cn0NCnBhcnRpdGlvbmVkIDwtIFNwYXJrTXJ4ICU+JSANCiAgc2RmX3JhbmRvbV9zcGxpdCh0cmFpbiA9IDAuOCwgdGVzdCA9IDAuMikNCmBgYA0KDQpWaWV3IHRoZSBUcmFpbmluZyBzZXQNCg0KYGBge3J9DQpoZWFkKHBhcnRpdGlvbmVkJHRyYWluKQ0KYGBgDQoNClZpZXcgdGhlIFRlc3Rpbmcgc2V0DQoNCmBgYHtyfQ0KcGFydGl0aW9uZWQkdGVzdA0KYGBgDQpTcGFyayBNb2RlbA0KDQpgYGB7cn0NClNwYXJrbW9kZWwgPC0gbWxfYWxzKHBhcnRpdGlvbmVkJHRyYWluLCByYXRpbmdfY29sID0gInJhdGluZyIsdXNlcl9jb2wgPSAidXNlciIsIGl0ZW1fY29sID0gIml0ZW1pZCIscmFuayA9IDEwKQ0KYGBgDQoNCiMjIyBNYWtpbmcgUHJlZGljdGlvbnMNCg0KYGBge3J9DQpTcGFya3ByZWQgPC0gbWxfcHJlZGljdChTcGFya21vZGVsLCBwYXJ0aXRpb25lZCR0ZXN0KSANCmBgYA0KDQoNCmBgYHtyfQ0KU3BhcmtwcmVkIDwtIGRhdGEuZnJhbWUoU3BhcmtwcmVkKQ0KaGVhZChTcGFya3ByZWQpDQpTcGFya3ByZWQgPC0gU3BhcmtwcmVkWyFpcy5uYShTcGFya3ByZWQkcHJlZGljdGlvbiksIF0NCmBgYA0KDQojIyMgTW9kZWwgRXZhbHVhdGlvbiANCg0KYGBge3J9DQpTcGFya01TRSA8LSBtZWFuKChTcGFya3ByZWQkcmF0aW5nIC0gU3BhcmtwcmVkJHByZWRpY3Rpb24pXjIpDQpTcGFya1JNU0UgPC0gc3FydChTcGFya01TRSkNClNwYXJrTUFFIDwtIG1lYW4oYWJzKFNwYXJrcHJlZCRSYXRpbmcgLSBTcGFya3ByZWQkcHJlZGljdGlvbikpDQpgYGANCg0KRGlzY29ubmVjdCBmcm9tIFNwYXJrDQoNCmBgYHtyfQ0Kc3BhcmtfZGlzY29ubmVjdChzYykNCmBgYA0KDQoNCiMgTW9kZWwgUGVyZm9ybWFuY2UNCg0KYGBge3J9DQpNb2RlbE5hbWUgPC0gKCJTcGFyayBNb2RlbCIpDQpNb2RlbF9STVNFIDwtICgiMC45MjE0IikNCk1vZGVsX01TRSA8LSAoIjAuODQ5MCIpDQpNb2RlbF9NQUUgPC0gKCJOYU4iKQ0KTW9kZWxfUGVyZm9ybWFuY2UgPC0gZGF0YS5mcmFtZShNb2RlbE5hbWUsTW9kZWxfUk1TRSxNb2RlbF9NU0UsTW9kZWxfTUFFKQ0KTW9kZWxfUGVyZm9ybWFuY2UNCmBgYA0KIyBDb25jbHVzaW9uDQoNClRoZSBwdXJwb3NlIG9mIHRoaXMgZXhlcmNpc2Ugd2FzIHRvIGJ1aWxkIGEgcmVjb21tZW5kZXIgc3lzdGVtIGFuZCB0byBwcmFjdGljZSB3b3JraW5nIHdpdGggZGlzdHJpYnV0ZWQgcmVjb21tZW5kZXIgc3lzdGVtLg0KDQpGb3IgdGhlIGdpdmVuIGRhdGEgc2V0LCBBTFMgcHJvdmlkZWQgdGhlIHdvcnN0IHBlcmZvcm1hbmNlIGNvbXBhcmVkIHRvIFBPcHVsYXIgYW5kIFVzZXIgQmFzZWQuIEJhc2VkIG9uIHRoZSBwb29yIHBlcmZvcm1hbmNlIG9mIHRoZSBBTFMsIEkgZGVjaWRlZCB0byBidWlsZCBhbiBBTFMgdXNpbmcgU3BhcmtseXIgdG8gc2VlIGlmIEkgY2FuIGdldCBiZXR0ZXIgcGVyZm9ybWFuY2UgZm9yIEFMUyB1c2luZyBhIGRpc3RyaWJ1dGVkIHJlY29tbWVuZGVyIHN5c3RlbS4gDQoNClRoZSBwZXJmb3JtYW5jZSB1c2luZyBTcGFyayBkZWZpbml0ZWx5IGltcHJvdmVkIGFzIGNhbiBiZSBzZWVuIHRoYXQgdGhlIFJNU0UgYW5kIE1TRSBhcmUgZmFyIGxvd2VyIGNvbXBhcmVkIHRvIHRoZSBBTFMgcmVjb21tZW5kZXIgYWxnb3JpdGhtLiBUaGUgZGlzdHJpYnV0ZWQgcmVjb21tZW5kZXIgc3lzdGVtIHNob3dzIGEgYmlnIGFkdmFudGFnZSBldmVuIHRob3VnaCB0aGUgaW1wbGVtZW50YXRpb24gc2VlbXMgdG8gYmUgbW9yZSBjb21wbGV4LiANCg0KDQojIyMgKipSZWZlcmVuY2VzKioNCg0KQnJlZXNlIEpTLCBIZWNrZXJtYW4gRCwgS2FkaWUgQyAoMTk5OCkuICJFbXBpcmljYWwgQW5hbHlzaXMgb2YgUHJlZGljdGl2ZSBBbGdvcml0aG1zIGZvciBDb2xsYWJvcmF0aXZlIEZpbHRlcmluZy4iIEluIFVuY2VydGFpbnR5IGluIEFydGlmaWNpYWwgSW50ZWxsaWdlbmNlLiBQcm9jZWVkaW5ncyBvZiB0aGUgRm91cnRlZW50aCBDb25mZXJlbmNlLCBwcC4gNDMtNTIuDQoNCktvaGF2aSwgUm9uICgxOTk1KS4gIkEgc3R1ZHkgb2YgY3Jvc3MtdmFsaWRhdGlvbiBhbmQgYm9vdHN0cmFwIGZvciBhY2N1cmFjeSBlc3RpbWF0aW9uIGFuZCBtb2RlbCBzZWxlY3Rpb24iLiBQcm9jZWVkaW5ncyBvZiB0aGUgRm91cnRlZW50aCBJbnRlcm5hdGlvbmFsIEpvaW50IENvbmZlcmVuY2Ugb24gQXJ0aWZpY2lhbCBJbnRlbGxpZ2VuY2UsIHBwLiAxMTM3LTExNDMuDQoNCktvcmVuLCBZLiwgQmVsbCwgUi4sICYgVm9saW5za3ksIEMuICgyMDA5KS4gTWF0cml4IEZhY3Rvcml6YXRpb24gVGVjaG5pcXVlcyBmb3IgUmVjb21tZW5kZXIgU3lzdGVtcy4gQ29tcHV0ZXIsIDQyKDgpLCAzMOKAkzM3LiBodHRwczovL2RvaS5vcmcvMTAuMTEwOS9NQy4yMDA5LjI2Mw0KDQpNaWNoYWVsIEhhaHNsZXIgKDIwMTYpLiByZWNvbW1lbmRlcmxhYjogQSBGcmFtZXdvcmsgZm9yIERldmVsb3BpbmcgYW5kIFRlc3RpbmcgUmVjb21tZW5kYXRpb24gQWxnb3JpdGhtcywgUiBwYWNrYWdlLiBodHRwczovL0NSQU4uUi1wcm9qZWN0Lm9yZy9wYWNrYWdlPXJlY29tbWVuZGVybGFiDQo=