1. PREPARE

In Unit 2, we pick up where we left off and learn to apply some basic machine learning techniques to help us understand how a predictive model might be developed and tested as part of an early warning system to identify students at risk of failing. Specifically, we will make a very crude first attempt at developing a testing a logistic regression and a random forest model that can (we hope!) accurately predict whether a student will pass or fail an online course. Unlike Unit 1, where we were interested in identifying factors from data collected throughout the course that might help explain why students earned the grade they did, we are instead interested identifying students who may be at risk before it is too late to intervene.

1a. Load Libraries

tidymodels 📦

The tidymodels package is a “meta-package” for modeling and statistical analysis that shares the underlying design philosophy, grammar, and data structures of the tidyverse. It includes a core set of packages that are loaded on startup and contains tools for:

  • data splitting and pre-processing;
  • model selection, tuning, and evaluation;
  • feature selection and variable importance estimation;
  • as well as other functionality.

Your Turn

In addition to the {tidymodels} package, we’ll also be using the {tidyverse} packages we learned about in Unit 1, and the {ranger} package we’ll be using for our random forest model in Part 4.

Use the code chunk below to load these three packages:

library(tidymodels)
## ── Attaching packages ────────────────────────────────────── tidymodels 1.0.0 ──
## ✔ broom        1.0.1      ✔ recipes      1.0.1 
## ✔ dials        1.0.0      ✔ rsample      1.1.0 
## ✔ dplyr        1.0.10     ✔ tibble       3.1.8 
## ✔ ggplot2      3.3.6      ✔ tidyr        1.2.1 
## ✔ infer        1.0.3      ✔ tune         1.0.0 
## ✔ modeldata    1.0.1      ✔ workflows    1.1.0 
## ✔ parsnip      1.0.1      ✔ workflowsets 1.0.0 
## ✔ purrr        0.3.4      ✔ yardstick    1.1.0
## ── Conflicts ───────────────────────────────────────── tidymodels_conflicts() ──
## ✖ purrr::discard() masks scales::discard()
## ✖ dplyr::filter()  masks stats::filter()
## ✖ dplyr::lag()     masks stats::lag()
## ✖ recipes::step()  masks stats::step()
## • Dig deeper into tidy modeling with R at https://www.tmwr.org
library(tidyverse)
## ── Attaching packages
## ───────────────────────────────────────
## tidyverse 1.3.2 ──
## ✔ readr   2.1.2     ✔ forcats 0.5.2
## ✔ stringr 1.4.1     
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ readr::col_factor() masks scales::col_factor()
## ✖ purrr::discard()    masks scales::discard()
## ✖ dplyr::filter()     masks stats::filter()
## ✖ stringr::fixed()    masks recipes::fixed()
## ✖ dplyr::lag()        masks stats::lag()
## ✖ readr::spec()       masks yardstick::spec()
library(ranger)

b. Import & Inspect Data

For this case study, we will again be working again with data from the online science courses introduced in Unit 1. This data is located in the data folder and named processed-data.csv. Fortunately, our data has already been largely wrangled. This will save us quite a bit of time, which we’ll need to help wrap our heads around some supervised machine learning basics.

Run the code chunk below to read this data into your R environment as an object named processed_data and let’s take a quick look at data we may use in our predictive model:

processed_data <- read_csv("data/processed-data.csv")
## Rows: 603 Columns: 17
## ── Column specification ────────────────────────────────────────────────────────
## Delimiter: ","
## chr  (2): course_id, gender
## dbl (15): student_id, final_grade, course_interest, perceived_competence, ut...
## 
## ℹ Use `spec()` to retrieve the full column specification for this data.
## ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
processed_data
## # A tibble: 603 × 17
##    student_id course_id final…¹ gender cours…² perce…³ utili…⁴    q1    q2    q3
##         <dbl> <chr>       <dbl> <chr>    <dbl>   <dbl>   <dbl> <dbl> <dbl> <dbl>
##  1      43146 FrScA-S2…    93.5 M          5       4.5    4.33     5     4     4
##  2      44638 OcnA-S11…    81.7 F          4.2     3.5    4        4     4     3
##  3      47448 FrScA-S2…    88.5 M          5       4      3.67     5     4     4
##  4      47979 OcnA-S21…    81.9 M          5       3.5    5        5     5     3
##  5      48797 PhysA-S1…    84   F          3.8     3.5    3.5      4     3     3
##  6      51943 FrScA-S2…    NA   F          4.6     4      4       NA    NA    NA
##  7      52326 AnPhA-S2…    83.6 M          5       3.5    5        5     5     3
##  8      52446 PhysA-S1…    97.8 F          3       3      3.33     3     3     3
##  9      53447 FrScA-S1…    96.1 F          4.2     3      2.67     4     3     3
## 10      53475 FrScA-S1…    NA   M         NA      NA     NA       NA    NA    NA
## # … with 593 more rows, 7 more variables: q4 <dbl>, q5 <dbl>, q6 <dbl>,
## #   q7 <dbl>, q8 <dbl>, q9 <dbl>, q10 <dbl>, and abbreviated variable names
## #   ¹​final_grade, ²​course_interest, ³​perceived_competence, ⁴​utility_value

Your Turn

In addition to students’ gender and their pre-course survey responses assessing three aspects of student motivation, we will also incorporate gradebook data collected during the course that we will also use to help “train” a predictive model for identifying students at risk of failing.

Use the code chunk below to read in the .csv file named “grade-book.csv” located in the data folder, assign to a new object named grade_book, and use a method of your choosing to answer the questions that follow:

grade_book <- read_csv("data/grade-book.csv")
## Rows: 29711 Columns: 5
## ── Column specification ────────────────────────────────────────────────────────
## Delimiter: ","
## chr (2): course_id, gradebook_item
## dbl (3): student_id, points_possible, points_earned
## 
## ℹ Use `spec()` to retrieve the full column specification for this data.
## ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
grade_book
## # A tibble: 29,711 × 5
##    course_id     student_id gradebook_item                       point…¹ point…²
##    <chr>              <dbl> <chr>                                  <dbl>   <dbl>
##  1 AnPhA-S116-01      60186 POINTS EARNED & TOTAL COURSE POINTS        5    4.05
##  2 AnPhA-S116-01      60186 WORK ATTEMPTED                            30   24   
##  3 AnPhA-S116-01      60186 0.1: Message Your Instructor             105   71.7 
##  4 AnPhA-S116-01      60186 0.2: Intro Assignment - Discussion …     140  141.  
##  5 AnPhA-S116-01      60186 0.3: Intro Assignment - Submitting …       5    5   
##  6 AnPhA-S116-01      60186 1.1: Quiz                                  5    4   
##  7 AnPhA-S116-01      60186 1.2: Quiz                                 20   NA   
##  8 AnPhA-S116-01      60186 1.3: Create a Living Creature             50   50   
##  9 AnPhA-S116-01      60186 1.3: Create a Living Creature - Dis…      10   NA   
## 10 AnPhA-S116-01      60186 1.4: Negative Feedback Loop Flowcha…      50   44   
## # … with 29,701 more rows, and abbreviated variable names ¹​points_possible,
## #   ²​points_earned
  1. How many observations and variables are in our grade_book dataset? And roughly how many observations are there per student?

    • There are 5 variables, 29,711 total observations, and 42 observations per student.
  2. How might this data be used in our model to help predict student at risk of failing the course?

    • We can look for patterns in the students who failed the course, like if there was a particular assignment that the majority did poorly on, to see if they would work as risk indicators.
  3. What else do you notice about this data set?

    • I noticed that there is data from multiple courses, so we may need to split up the courses to get a manageable amount of data to model and to work with all the same assignments for a model.

2. WRANGLE

In general, data wrangling involves some combination of cleaning, reshaping, transforming, and merging data (Wickham & Grolemund, 2017). The importance of data wrangling is difficult to overstate, as it involves the initial steps of going from raw data to a dataset that can be explored and modeled (Krumm et al, 2018). In Part 2, we focus on the the following wrangling processes to:

  1. Create an Outcome Variable. We create a binary variable outcome we are interested in predicting, i.e. pass/fail.

  2. Convert Character Stings to Factors. We convert variables stored as character strings to factors expected by our models.

  3. Select Predictors. We narrow down our data set to specific predictors of interest.

a. Create Outcome Variable

Since we are interested in developing a predictive model that can predict whether a student is at risk of failing a course, and so we can intervene before that happens, we need an outcome variable that lets us know if they have either passed or failed.

Let’s combine the hopefully familiar mutate() function with the if_else() function also from the {dplyr} package to create a new variable called at_risk which indicates “no” for students whose final_grade is greater than or equal to 66.7% (NC State’s cutoff for a D-) and “yes” if it is not.

course_data <- processed_data %>% 
  mutate(at_risk = if_else(final_grade >= 66.7, "no", "yes")) 

course_data
## # A tibble: 603 × 18
##    student_id course_id final…¹ gender cours…² perce…³ utili…⁴    q1    q2    q3
##         <dbl> <chr>       <dbl> <chr>    <dbl>   <dbl>   <dbl> <dbl> <dbl> <dbl>
##  1      43146 FrScA-S2…    93.5 M          5       4.5    4.33     5     4     4
##  2      44638 OcnA-S11…    81.7 F          4.2     3.5    4        4     4     3
##  3      47448 FrScA-S2…    88.5 M          5       4      3.67     5     4     4
##  4      47979 OcnA-S21…    81.9 M          5       3.5    5        5     5     3
##  5      48797 PhysA-S1…    84   F          3.8     3.5    3.5      4     3     3
##  6      51943 FrScA-S2…    NA   F          4.6     4      4       NA    NA    NA
##  7      52326 AnPhA-S2…    83.6 M          5       3.5    5        5     5     3
##  8      52446 PhysA-S1…    97.8 F          3       3      3.33     3     3     3
##  9      53447 FrScA-S1…    96.1 F          4.2     3      2.67     4     3     3
## 10      53475 FrScA-S1…    NA   M         NA      NA     NA       NA    NA    NA
## # … with 593 more rows, 8 more variables: q4 <dbl>, q5 <dbl>, q6 <dbl>,
## #   q7 <dbl>, q8 <dbl>, q9 <dbl>, q10 <dbl>, at_risk <chr>, and abbreviated
## #   variable names ¹​final_grade, ²​course_interest, ³​perceived_competence,
## #   ⁴​utility_value

b. Convert to Factors

While inspecting the data, you may have noticed that one of our predictors of interest and our at_risk outcome variable are stored as character <chr> data types. For creating models, it is better to have qualitative variables like gender and at_risk encoded as factors instead of character strings. Factors store data as categorical variables, each with its own levels. Because categorical variables are used in statistical models differently than continuous variables, storing these data as factors ensures that the modeling functions will treat them correctly.

To do so, we once again use the mutate() function but instead of creating a new variable, we will use the as_factor() function to convert gender and at_risk from a character to a factor and save it as a variable of the same name, effectively replacing the old variables with new ones:

course_data <- course_data |>
  mutate(gender = as_factor(gender), 
         at_risk = as_factor(at_risk))

course_data
## # A tibble: 603 × 18
##    student_id course_id final…¹ gender cours…² perce…³ utili…⁴    q1    q2    q3
##         <dbl> <chr>       <dbl> <fct>    <dbl>   <dbl>   <dbl> <dbl> <dbl> <dbl>
##  1      43146 FrScA-S2…    93.5 M          5       4.5    4.33     5     4     4
##  2      44638 OcnA-S11…    81.7 F          4.2     3.5    4        4     4     3
##  3      47448 FrScA-S2…    88.5 M          5       4      3.67     5     4     4
##  4      47979 OcnA-S21…    81.9 M          5       3.5    5        5     5     3
##  5      48797 PhysA-S1…    84   F          3.8     3.5    3.5      4     3     3
##  6      51943 FrScA-S2…    NA   F          4.6     4      4       NA    NA    NA
##  7      52326 AnPhA-S2…    83.6 M          5       3.5    5        5     5     3
##  8      52446 PhysA-S1…    97.8 F          3       3      3.33     3     3     3
##  9      53447 FrScA-S1…    96.1 F          4.2     3      2.67     4     3     3
## 10      53475 FrScA-S1…    NA   M         NA      NA     NA       NA    NA    NA
## # … with 593 more rows, 8 more variables: q4 <dbl>, q5 <dbl>, q6 <dbl>,
## #   q7 <dbl>, q8 <dbl>, q9 <dbl>, q10 <dbl>, at_risk <fct>, and abbreviated
## #   variable names ¹​final_grade, ²​course_interest, ³​perceived_competence,
## #   ⁴​utility_value

Note: Be sure to pay attention to the fact that in addition to assigning our modified gender variable to a variable of the same name using the = operator, we also assigned our modified course_data to an object of the same name using the <- operator. That is, we effectively overwrite the old course_data that included gender stored as a character data type to a new course_data object with gender stored as a factor.

c. Select Predictors

As you’ve probably noticed, there is a lot of great information in our course_data - but we won’t, and shouldn’t, include all of it in our predictive model. Indeed, Estrellado et al. Estrellado et al. (2020) point out that for statistical reasons and as a good general practice, it’s better to select a few variables of interest because:

At a certain point, adding more variables will appear to make your analysis more accurate, but will in fact obscure the truth from you.

Your Turn

Complete the code chunk below to “select” (hint, hint) at_risk for our outcome variable, as well as the following variables we will use in our predictive model:

  • student_id
  • course_id
  • at_risk
  • gender
  • course_interest
  • perceived_competence
  • utility_value
course_data <- course_data |>
  select(student_id, course_id, at_risk, gender, course_interest,
         perceived_competence, utility_value)

course_data
## # A tibble: 603 × 7
##    student_id course_id     at_risk gender course_interest perceived_c…¹ utili…²
##         <dbl> <chr>         <fct>   <fct>            <dbl>         <dbl>   <dbl>
##  1      43146 FrScA-S216-02 no      M                  5             4.5    4.33
##  2      44638 OcnA-S116-01  no      F                  4.2           3.5    4   
##  3      47448 FrScA-S216-01 no      M                  5             4      3.67
##  4      47979 OcnA-S216-01  no      M                  5             3.5    5   
##  5      48797 PhysA-S116-01 no      F                  3.8           3.5    3.5 
##  6      51943 FrScA-S216-03 <NA>    F                  4.6           4      4   
##  7      52326 AnPhA-S216-01 no      M                  5             3.5    5   
##  8      52446 PhysA-S116-01 no      F                  3             3      3.33
##  9      53447 FrScA-S116-01 no      F                  4.2           3      2.67
## 10      53475 FrScA-S116-02 <NA>    M                 NA            NA     NA   
## # … with 593 more rows, and abbreviated variable names ¹​perceived_competence,
## #   ²​utility_value

3. Explore

As noted by Krumm et al. (2018), exploratory data analysis often involves some combination of data visualization and feature engineering. In Part 3, we will create a quick visualization to help us spot any issues with our data and engineer new features that we will use in our predictive models. Specifically, in Part 3 we will:

  1. Examine Outcomes by taking a quick look at at_risk outcome variable of interest as well as our other predictors to spot any issues that may hang up our models.

  2. Engineer Predictors by creating some new variables we think will be predictive of students at risk, such as performance on assignments on during the first half of the course.

a. Count proportions

Before we move on to feature engineering, let’s take a quick look at the proportion of students in our final data set that were identified as at-risk. To do so, run the following code chunk to take a count of the number of students labeled “yes” or “no” of being at_risk, and then create a new variable called proportion that takes the number n of each and divides by the total number:

course_data |>
  count(at_risk) |>
  mutate(proportion = n/sum(n))
## # A tibble: 3 × 3
##   at_risk     n proportion
##   <fct>   <int>      <dbl>
## 1 no        465     0.771 
## 2 yes       108     0.179 
## 3 <NA>       30     0.0498

As you can see, roughly 18% of students in our data set were identified as “yes” for being at risk. And since these are final grades we are working with, these student were not only at risk of failing, but did indeed fail the course. You may also notice we have a number of students with missing grades, which is something we will have to address prior to analysis.

Your Turn

Alternatively, we could have created a quick visualization to help us get a better sense of the outcomes we are trying to predict and to spot any potential issues we might run into during analysis.

Complete the following code chunk to create a simple bar plot illustrating the number of students who passed, failed, or have missing data:

course_data |>
  ggplot() +
  geom_bar(mapping = aes(x = at_risk))

Remove Cases

As you may have noticed from the outputs above, some students do not have a final grade and some are missing survey data as well. Since the models we will be using are not designed to deal with missing data, we will need to remove cases with missing data.

Let’s use the drop_na() function also from the {dplyr} package to remove observations with missing data and reassign to our course_data object:

course_data <- course_data |>
  drop_na()

course_data
## # A tibble: 503 × 7
##    student_id course_id     at_risk gender course_interest perceived_c…¹ utili…²
##         <dbl> <chr>         <fct>   <fct>            <dbl>         <dbl>   <dbl>
##  1      43146 FrScA-S216-02 no      M                  5             4.5    4.33
##  2      44638 OcnA-S116-01  no      F                  4.2           3.5    4   
##  3      47448 FrScA-S216-01 no      M                  5             4      3.67
##  4      47979 OcnA-S216-01  no      M                  5             3.5    5   
##  5      48797 PhysA-S116-01 no      F                  3.8           3.5    3.5 
##  6      52326 AnPhA-S216-01 no      M                  5             3.5    5   
##  7      52446 PhysA-S116-01 no      F                  3             3      3.33
##  8      53447 FrScA-S116-01 no      F                  4.2           3      2.67
##  9      54066 OcnA-S116-01  no      M                  4.4           4      5   
## 10      54282 OcnA-S116-02  no      F                  3.4           3      2.67
## # … with 493 more rows, and abbreviated variable names ¹​perceived_competence,
## #   ²​utility_value

Your Turn

Finally, you have probably noticed that we wrote a lot more code than necessary in order to illustrate different wrangling processes to get our data ready for analysis.

To reduce all the redundancy caused by demonstrating each step separately above, complete the following code below by using the new |> or old %>% pipe operators to chain the mutate(), select(), and drop_na() functions together to wrangle our data for analysis and write a brief comment following each # that explains what each line of code accomplishes.

course_data <- processed_data |>
  mutate(at_risk = if_else(final_grade >= 66.7, "no", "yes"), # adds at-risk variable
         at_risk = as_factor(at_risk), # changes at_risk data type from character to a factor
         gender = as_factor(gender)) |> # changes gender data type from character to a factor
  select(student_id, course_id, at_risk, gender, course_interest,
         perceived_competence, utility_value) |> # selects the relevant variables for the data we want to work with
  drop_na() # removes the observations with NA for any variable

course_data
## # A tibble: 503 × 7
##    student_id course_id     at_risk gender course_interest perceived_c…¹ utili…²
##         <dbl> <chr>         <fct>   <fct>            <dbl>         <dbl>   <dbl>
##  1      43146 FrScA-S216-02 no      M                  5             4.5    4.33
##  2      44638 OcnA-S116-01  no      F                  4.2           3.5    4   
##  3      47448 FrScA-S216-01 no      M                  5             4      3.67
##  4      47979 OcnA-S216-01  no      M                  5             3.5    5   
##  5      48797 PhysA-S116-01 no      F                  3.8           3.5    3.5 
##  6      52326 AnPhA-S216-01 no      M                  5             3.5    5   
##  7      52446 PhysA-S116-01 no      F                  3             3      3.33
##  8      53447 FrScA-S116-01 no      F                  4.2           3      2.67
##  9      54066 OcnA-S116-01  no      M                  4.4           4      5   
## 10      54282 OcnA-S116-02  no      F                  3.4           3      2.67
## # … with 493 more rows, and abbreviated variable names ¹​perceived_competence,
## #   ²​utility_value

Now inspect your data again and make sure it looks like expected:

view(course_data)

Hint: You should see a total of 503 observations and 7 variables including 1 outcome variable named at_risk, 1 student identifier named student_id.

b. Feature Engineering

As defined by Krumm, Means, and Bienkowski (2018) in Learning Analytics Goes to School:

Feature engineering is the process of creating new variables within a dataset, which goes above and beyond the work of recoding and rescaling variables.

The authors note that feature engineering draws on substantive knowledge from theory or practice, experience with a particular data system, and general experience in data-intensive research. Moreover, these features can be used not only in machine learning models, but also in visualizations and tables comprising descriptive statistics.

Though not often discussed, feature engineering is an important element of data-intensive research that can generate new insights and improve predictive models. Indeed, an earlier attempt using this data without feature engineering predicted students’ passing (or not passing) the course with only around 75% accuracy. One goal of this case study is to improve upon these results creating by some new variable using data that we think may improve our model.

Student Performance Features

In addition to information about student gender and motivation collected prior to the course, we will also incorporate into our model student performance data on course assignments. And since we are interested in developing a predictive model that that can be used to intervene before students actually fail the course, we will develop new features based on student performance on the first 20 assignments completed during the first half of the course. Specifically, we’ll create the following three “features” from the gradebook data we imported earlier:

  • The overall percent of points earned (across first 20 assignments)
  • The variability (in standard deviation units) in the percent earned (between assignments)
  • The number of assignments for which students earned 100% of the possible points

Group & Slice Data

The {dplyr} package has a handy slice() function that that allows us to select, remove, and duplicate rows in a dataset based the rows in which we specify.

For example, run the following code chunk to select rows 1 through 20 from our grade_book data.

slice(grade_book, 1:20)
## # A tibble: 20 × 5
##    course_id     student_id gradebook_item                       point…¹ point…²
##    <chr>              <dbl> <chr>                                  <dbl>   <dbl>
##  1 AnPhA-S116-01      60186 POINTS EARNED & TOTAL COURSE POINTS        5    4.05
##  2 AnPhA-S116-01      60186 WORK ATTEMPTED                            30   24   
##  3 AnPhA-S116-01      60186 0.1: Message Your Instructor             105   71.7 
##  4 AnPhA-S116-01      60186 0.2: Intro Assignment - Discussion …     140  141.  
##  5 AnPhA-S116-01      60186 0.3: Intro Assignment - Submitting …       5    5   
##  6 AnPhA-S116-01      60186 1.1: Quiz                                  5    4   
##  7 AnPhA-S116-01      60186 1.2: Quiz                                 20   NA   
##  8 AnPhA-S116-01      60186 1.3: Create a Living Creature             50   50   
##  9 AnPhA-S116-01      60186 1.3: Create a Living Creature - Dis…      10   NA   
## 10 AnPhA-S116-01      60186 1.4: Negative Feedback Loop Flowcha…      50   44   
## 11 AnPhA-S116-01      60186 1.5: Quiz                                  5    4.19
## 12 AnPhA-S116-01      60186 Unit 1 Assessment: Pickle Autopsy L…       5    5   
## 13 AnPhA-S116-01      60186 PROGRESS CHECK 1 @ 10-02-15               24   16   
## 14 AnPhA-S116-01      60186 2.1: Types of Membranes                   10    9.32
## 15 AnPhA-S116-01      60186 2.2 - 2.3: Quiz                           10    9   
## 16 AnPhA-S116-01      60186 2.4 - 2.5 Quiz                            10   10   
## 17 AnPhA-S116-01      60186 2.6: Quiz                                  5    5   
## 18 AnPhA-S116-01      60186 Unit 2 Assessment: Test                  443  389   
## 19 AnPhA-S116-01      60186 3.1: Models of the Integumentary Sy…      10   10   
## 20 AnPhA-S116-01      60186 3.2 - 3.3: Quiz                            5    3   
## # … with abbreviated variable names ¹​points_possible, ²​points_earned

While this helps us retrieve the first 20 assignments for the first student, it doesn’t help us with the hundreds of other students in our data set!

Fortunately, the {dplyr} package also has the extremely useful group_by() function which allows us to perform other {tidyverse} functions like slice(), mutate(), and summarize() by groups that we specify as arguments.

For example, run the following code to group our data by student_id and course_id using the group_by() function, and then we’ll use the slice() function again to select the first 20 assignment, but this time for the first 20 assignments in each course the student completed, instead of just the first 20 assignments for the first student:

grade_book |> 
  group_by(student_id, course_id) |>
  slice(1:20)
## # A tibble: 12,060 × 5
## # Groups:   student_id, course_id [603]
##    course_id     student_id gradebook_item                       point…¹ point…²
##    <chr>              <dbl> <chr>                                  <dbl>   <dbl>
##  1 FrScA-S216-02      43146 POINTS EARNED & TOTAL COURSE POINTS        5      NA
##  2 FrScA-S216-02      43146 WORK ATTEMPTED                            10      NA
##  3 FrScA-S216-02      43146 0-1.1: Intro Assignment - Send a Me…       5       5
##  4 FrScA-S216-02      43146 0-1.2: Intro Assignment - DB #1            5       5
##  5 FrScA-S216-02      43146 0-1.3: Intro Assignment - Submittin…       5       5
##  6 FrScA-S216-02      43146 1-1.1: Lesson 1-1 Graphic Organizer       15      13
##  7 FrScA-S216-02      43146 1-2.1: Explore a Career Assignment        37      37
##  8 FrScA-S216-02      43146 1-2.2: Explore a Career DB #2            443     365
##  9 FrScA-S216-02      43146 PROGRESS CHECK 1 @ 02-18-16                5       5
## 10 FrScA-S216-02      43146 1-2.3: Lesson 1-2 Graphic Organizer        5       5
## # … with 12,050 more rows, and abbreviated variable names ¹​points_possible,
## #   ²​points_earned

You’ll notice the data have significantly different dimensions now. We’ll have to take some steps to further process our grade_book data. In doing so, we’ll engineer some features.

Your Turn

Now let’s create a new grade_book data frame using what we just learned to:

  • group our observations by student_id and course_id;
  • slice our data frame to include only the first 20 assigments for each student in each course;
  • create a new variable called percent_earned that equals the points_earned divided by points_possible; and,
  • remove rows with any missing data

Complete the following code chunk:

grade_book <- grade_book |> 
  group_by(student_id, course_id) |>
  slice(1:20) |>
  mutate(percent_earned = points_earned/points_possible) |>
  drop_na()

grade_book
## # A tibble: 10,522 × 6
## # Groups:   student_id, course_id [603]
##    course_id     student_id gradebook_item               point…¹ point…² perce…³
##    <chr>              <dbl> <chr>                          <dbl>   <dbl>   <dbl>
##  1 FrScA-S216-02      43146 0-1.1: Intro Assignment - S…       5     5     1    
##  2 FrScA-S216-02      43146 0-1.2: Intro Assignment - D…       5     5     1    
##  3 FrScA-S216-02      43146 0-1.3: Intro Assignment - S…       5     5     1    
##  4 FrScA-S216-02      43146 1-1.1: Lesson 1-1 Graphic O…      15    13     0.867
##  5 FrScA-S216-02      43146 1-2.1: Explore a Career Ass…      37    37     1    
##  6 FrScA-S216-02      43146 1-2.2: Explore a Career DB …     443   365     0.824
##  7 FrScA-S216-02      43146 PROGRESS CHECK 1 @ 02-18-16        5     5     1    
##  8 FrScA-S216-02      43146 1-2.3: Lesson 1-2 Graphic O…       5     5     1    
##  9 FrScA-S216-02      43146 2-2.1: Evidence Collection …      10     8.5   0.85 
## 10 FrScA-S216-02      43146 2-3.1: Hair Analysis Lab          30    23     0.767
## # … with 10,512 more rows, and abbreviated variable names ¹​points_possible,
## #   ²​points_earned, ³​percent_earned

Note: If you completed this correctly, you should have a data frame with 10,522 observations and 6 variables.

Create New Variables

Finally, let’s create three features from the gradebook data:

  • The overall percent of points earned
  • The variability (in standard deviation units) in the percent earned (between assignments)
  • The number of assignments for which students earned 100% of the possible points

You can probably imagine others; you’re welcome to explore adding those, too.

We’ll use the summarize() function instead of the mutate() function to do this, and then we’ll select just the variables needed to join our data and :

grade_book <- grade_book |>
  summarize(overall_percent = sum(points_earned) / sum(points_possible),
            variability = sd(percent_earned),
            n_100 = sum(percent_earned == 1)) |>
  select(student_id, 
         course_id, 
         overall_percent, 
         variability, 
         n_100)
## `summarise()` has grouped output by 'student_id'. You can override using the
## `.groups` argument.
grade_book
## # A tibble: 603 × 5
## # Groups:   student_id [580]
##    student_id course_id     overall_percent variability n_100
##         <dbl> <chr>                   <dbl>       <dbl> <int>
##  1      43146 FrScA-S216-02           0.840       0.211     9
##  2      44638 OcnA-S116-01            0.560       0.235    12
##  3      47448 FrScA-S216-01           0.420       0.297     5
##  4      47979 OcnA-S216-01            0.713       0.204     6
##  5      48797 PhysA-S116-01           0.905       0.286     9
##  6      51943 FrScA-S216-03           0.936       0.172     8
##  7      52326 AnPhA-S216-01           0.485       0.298     6
##  8      52446 PhysA-S116-01           0.843       0.137     8
##  9      53447 FrScA-S116-01           0.702       0.221     5
## 10      53475 FrScA-S116-02           0.706       0.173     4
## # … with 593 more rows

We have one last step before we can get to modeling (grade_book) - joining this data with all of the other data (course_data).

course_data <- inner_join(course_data, grade_book)
## Joining, by = c("student_id", "course_id")

Your Turn

Let’s talk a look at the joined data to make sure everything is looking as we intend it to. Inspect the data using the code chunk below and answer the following questions:

view(course_data)
  1. Why does our data frame now have 503 observations and 10 variables?
    • The grade_book data set had 603 observations and 5 variables before joining. The course_data data set had 503 observations and 7 variables. The data that was joined combined two of the variables (student_id and course_id) and all of the other variables got their own columns. Since it could only join 503 observations, the other 100 were left out.
  2. Why did we use the summarize() function above instead of the mutate() function to create our new features?
    • We used the summarize function because we are looking at the data over the first 20 assignments for each student and combining them into one number per new variable. Mutate would’ve applied to each assignment individually instead of the entire group of assignments.

4. MODEL

Recall from our readings that there are two general types of modeling approaches: unsupervised and supervised machine learning. In Part 4, we focused on supervised learning models, which are used to quantify relationships between features (e.g., motivation and performance) and a known outcome (e.g., passing or failing a course). These models can be used for statistical inference, as illustrated in Unit 1, or prediction as we’ll illustrate in this section. Specifically, in Part 4 we will learn how to:

  1. Split Data into a training and test set that will be used to develop a predictive model.

  2. Create a “Recipe” for our predictive model and learn how to deal with nominal data that we would like to use as predictors.

  3. Fit Models to our training set using logistic regression and random forest models.

  4. Check Model Accuracy on our test set to see how well our model can “predict” our outcome of interest.

a. Split Data

The authors of Data Science in Education Using R Estrellado et al. (2020) remind us that:

At its core, machine learning is the process of “showing” your statistical model only some of the data at once and training the model to predict accurately on that training dataset (this is the “learning” part of machine learning). Then, the model as developed on the training data is shown new data - data you had all along, but hid from your computer initially - and you see how well the model that you developed on the training data performs on this new testing data. Eventually, you might use the model on entirely new data.

It is therefore common when beginning a modeling project to separate the data set into two partitions:

  • The training set is used to estimate develop and compare models, feature engineering techniques, tune models, etc.

  • The test set is held in reserve until the end of the project, at which point there should only be one or two models under serious consideration. It is used as an unbiased source for measuring final model performance.

There are different ways to create these partitions of the data and there is no uniform guideline for determining how much data should be set aside for testing. The proportion of data can be driven by many factors, including the size of the original pool of samples and the total number of predictors. 

After you decide how much to set aside, the most common approach for actually partitioning your data is to use a random sample. For our purposes, we’ll use random sampling to select 25% for the test set and use the remainder for the training set, which are the defaults for the {rsample} package.

Additionally, since random sampling uses random numbers, it is important to set the random number seed. This ensures that the random numbers can be reproduced at a later time (if needed).

The function initial_split() function from the {rsample} package takes the original data and saves the information on how to make the partitions.

Run the following code to set the random number seed and make our initial data split:

set.seed(586)

course_split <- initial_split(course_data, 
                              strata = at_risk)

Note that we used the strata = argument, which conducts a stratified split. This ensures that, despite the imbalance we noticed in our at_risk variable, our training and test data sets will keep roughly the same proportions of at-risk students as in the original data. 

Your Turn

Type course_split into the code chunk below, run, and answer the question that follows?

course_split
## <Training/Testing/Total>
## <376/127/503>
  1. How many observations should we expect to see in our training and test sets respectively?

    • We should expect to see 376 observations in the training set and 127 observations in the test set.

Create a training and test set

The {rsample} package has two aptly named functions for creating a training and testing data set called training() and testing() respectively.

Run the following code to split the data into our training and test data sets:

train_data <- training(course_split)

test_data  <- testing(course_split)

Your Turn

Now take a look at the training and testing sets we just created by typing their names into the code chunk:

train_data
## # A tibble: 376 × 10
##    studen…¹ cours…² at_risk gender cours…³ perce…⁴ utili…⁵ overa…⁶ varia…⁷ n_100
##       <dbl> <chr>   <fct>   <fct>    <dbl>   <dbl>   <dbl>   <dbl>   <dbl> <int>
##  1    43146 FrScA-… no      M          5       4.5    4.33   0.840   0.211     9
##  2    44638 OcnA-S… no      F          4.2     3.5    4      0.560   0.235    12
##  3    47448 FrScA-… no      M          5       4      3.67   0.420   0.297     5
##  4    47979 OcnA-S… no      M          5       3.5    5      0.713   0.204     6
##  5    52326 AnPhA-… no      M          5       3.5    5      0.485   0.298     6
##  6    52446 PhysA-… no      F          3       3      3.33   0.843   0.137     8
##  7    54282 OcnA-S… no      F          3.4     3      2.67   0.849   0.138     7
##  8    54434 PhysA-… no      F          4       3      3      0.914   0.200     4
##  9    55078 FrScA-… no      F          4.2     3.5    4.33   0.911   0.171     9
## 10    56152 AnPhA-… no      F          4       4      2.67   0.864   0.112    10
## # … with 366 more rows, and abbreviated variable names ¹​student_id, ²​course_id,
## #   ³​course_interest, ⁴​perceived_competence, ⁵​utility_value, ⁶​overall_percent,
## #   ⁷​variability
test_data
## # A tibble: 127 × 10
##    studen…¹ cours…² at_risk gender cours…³ perce…⁴ utili…⁵ overa…⁶ varia…⁷ n_100
##       <dbl> <chr>   <fct>   <fct>    <dbl>   <dbl>   <dbl>   <dbl>   <dbl> <int>
##  1    48797 PhysA-… no      F          3.8     3.5    3.5    0.905   0.286     9
##  2    53447 FrScA-… no      F          4.2     3      2.67   0.702   0.221     5
##  3    54066 OcnA-S… no      M          4.4     4      5      0.848   0.307     6
##  4    55140 PhysA-… no      F          3.8     3      2.67   0.772   0.275     6
##  5    57981 OcnA-S… yes     F          4.2     3      3.67   0.896   0.118    10
##  6    58168 AnPhA-… no      F          5       5      4.67   0.871   0.242     4
##  7    62157 FrScA-… no      F          4.5     3.5    3.33   0.859   0.162     7
##  8    62175 OcnA-S… no      M          4       4      3.33   0.842   0.356     5
##  9    62752 PhysA-… no      F          4.2     2.5    2.33   0.834   0.120    11
## 10    64930 FrScA-… no      F          4       3      3.67   0.792   0.151     7
## # … with 117 more rows, and abbreviated variable names ¹​student_id, ²​course_id,
## #   ³​course_interest, ⁴​perceived_competence, ⁵​utility_value, ⁶​overall_percent,
## #   ⁷​variability

Next, recycle the code from above to check to see that the proportion of at-risk students in our training and test data are close to those in our overall course_data:

train_data |>
  count(at_risk) |>
  mutate(proportion = n/sum(n))
## # A tibble: 2 × 3
##   at_risk     n proportion
##   <fct>   <int>      <dbl>
## 1 no        306      0.814
## 2 yes        70      0.186
test_data |>
  count(at_risk) |>
  mutate(proportion = n/sum(n))
## # A tibble: 2 × 3
##   at_risk     n proportion
##   <fct>   <int>      <dbl>
## 1 no        103      0.811
## 2 yes        24      0.189

Now answer the following questions:

  1. Do the number of observations in each set match your expectations? Why?

    • Yes. The training data has 376 observations and the test data has 127 observations which matches exactly with the split done earlier.
  2. Do the proportion of at-risk students in each set match your expectations? Why?

    • Yes. The proportions are close to what they were in the original data set (about 20% at-risk and 80% not at-risk). The proportions in the original data set were lower, but that also included the N/A values whereas this data does not.

b. Create a Recipe

In this section, we introduce another tidymodels package, recipes, which is designed to help you prepare your data before training your model. Recipes are built as a series of preprocessing steps, such as:

  • converting qualitative predictors to indicator variables (also known as dummy variables),

  • transforming data to be on a different scale (e.g., taking the logarithm of a variable),

  • transforming whole groups of predictors together,

  • extracting key features from raw variables (e.g., getting the day of the week out of a date variable),

and so on. If you are familiar with R’s formula interface, a lot of this might sound familiar and like what a formula already does. Recipes can be used to do many of the same things, but they have a much wider range of possibilities.

Add a formula

To get started, let’s create a recipe for a simple logistic regression model. Before training the model, we can use a recipe to add a few predictors and conduct some preprocessing required by the model.

The recipe() function as we used it here has two arguments:

  • formula. Any variable on the left-hand side of the tilde (~) is considered the model outcome (at_risk in our case). On the right-hand side of the tilde are the predictors. Variables may be listed by name, or you can use the dot (.) to indicate all other variables as predictors.

  • The data. A recipe is associated with the data set used to create the model. This will typically be the training set, so data = train_data here. Naming a data set doesn’t actually change the data itself; it is only used to catalog the names of the variables and their types, like factors, integers, dates, etc.

Let’s create our very first recipe using at_risk as our outcome variable; course_interest and gender and overall_percent as predictors; and train_data as our data to train:

lr_recipe_1 <- recipe(at_risk ~ course_interest + gender + overall_percent,
                      data = train_data)

Now let’s take a quick peek at our recipe and create a quick summary of our recipe using the summary() function:

lr_recipe_1
## Recipe
## 
## Inputs:
## 
##       role #variables
##    outcome          1
##  predictor          3
summary(lr_recipe_1)
## # A tibble: 4 × 4
##   variable        type    role      source  
##   <chr>           <chr>   <chr>     <chr>   
## 1 course_interest numeric predictor original
## 2 gender          nominal predictor original
## 3 overall_percent numeric predictor original
## 4 at_risk         nominal outcome   original

You can see that our recipe has four ingredients including three predictors and 1 outcome, just as expected.

Create Dummy Variables

Because we’ll be using a simple logistic regression model, variables like gender will need to be coded as dummy variables. Dummy coding means transforming a variable with multiple categories into new variables, where each binary variable indicates the presence and absence of each category. For example, gender will be recoded to gender_f, where 1 indicates female and 0 indicates male.

Unlike the standard model formula methods in R, a recipe does not automatically create these dummy variables for you; you’ll need to tell your recipe to add this step. This is for two reasons. First, many models do not require numeric predictors, so dummy variables may not always be preferred. Second, recipes can also be used for purposes outside of modeling, where non-dummy versions of the variables may work better. For example, you may want to make a table or a plot with a variable as a single factor.

For these reasons, we need to explicitly tell recipes to create dummy variables using step_dummy(). Let’s add this to our recipe and include all_nominal_predictors() to tell our recipe to change all of our factor variables to dummy variables:

lr_recipe_1 <- 
  recipe(at_risk ~ course_interest + gender + overall_percent,
         data = train_data) |>
  step_dummy(all_nominal_predictors())

lr_recipe_1
## Recipe
## 
## Inputs:
## 
##       role #variables
##    outcome          1
##  predictor          3
## 
## Operations:
## 
## Dummy variables from all_nominal_predictors()
summary(lr_recipe_1)
## # A tibble: 4 × 4
##   variable        type    role      source  
##   <chr>           <chr>   <chr>     <chr>   
## 1 course_interest numeric predictor original
## 2 gender          nominal predictor original
## 3 overall_percent numeric predictor original
## 4 at_risk         nominal outcome   original

Create a kitchen sink recipe

Before training our model, let’s create a second recipe just for contrast that includes all of our predictors.

Run the following code to add all our predictors to our new recipe:

lr_recipe_2 <- 
  recipe(at_risk ~ course_interest + gender + overall_percent + 
           perceived_competence + utility_value + variability + n_100, 
         data = train_data) |>
  step_dummy(all_nominal_predictors())

lr_recipe_2
## Recipe
## 
## Inputs:
## 
##       role #variables
##    outcome          1
##  predictor          7
## 
## Operations:
## 
## Dummy variables from all_nominal_predictors()
summary(lr_recipe_2)
## # A tibble: 8 × 4
##   variable             type    role      source  
##   <chr>                <chr>   <chr>     <chr>   
## 1 course_interest      numeric predictor original
## 2 gender               nominal predictor original
## 3 overall_percent      numeric predictor original
## 4 perceived_competence numeric predictor original
## 5 utility_value        numeric predictor original
## 6 variability          numeric predictor original
## 7 n_100                numeric predictor original
## 8 at_risk              nominal outcome   original

c. Fit a Logistic Regression Model

With tidymodels, we start building a model by specifying the functional form of the model that we want using the [parsnip] package. Since our outcome is binary, the model type we will use is “logistic regression”. We can declare this with logistic_reg() and assign to an object we will later use in our workflow:

lr_mod <- logistic_reg()

That is pretty underwhelming since, on its own, it doesn’t really do much. However, now that the type of model has been specified, a method for fitting or training the model can be stated using the engine.

Start your engine

The engine value is often a mash-up of different packages that can be used to fit or train the model as well as the estimation method. For example, we will use “glm” a generalized linear model for binary outcomes and default for logistic regression in the {parsnip} package.

Run the following code to finish specifying our model:

lr_mod <- 
  logistic_reg() %>% 
  set_engine("glm")

Add to workflow

We will want to use our recipes created earlier across several steps as we train and test our model. To simplify this process, we can use a model workflow, which pairs a model and recipe together.

This is a straightforward approach because different recipes are often needed for different models, so when a model and recipe are bundled, it becomes easier to train and test workflows.

We’ll use the {workflows} package from tidymodels to bundle our parsnip model (lr_mod) with our first recipe (lr_recipe_1).

lr_workflow <- 
  workflow() %>% 
  add_model(lr_mod) %>% 
  add_recipe(lr_recipe_1)

Fit model to training set

Now that we have a single workflow that can be used to prepare the recipe and train the model from the resulting predictors, we can use the fit() function to fit our model to our train_data. And again, we set a random number seed to ensure that if we run this same code again, we will get the same results in terms of the data partition:

set.seed(586)

lr_fit <- 
  lr_workflow %>% 
  fit(data = train_data)

This lr_fit object has the finalized recipe and fitted model objects inside. To extract the model fit from the workflow, we will use the helper functions extract_fit_parsnip(). Here we pull the fitted model object then use the broom::tidy() function to get a tidy tibble of model coefficients:

lr_fit %>% 
  extract_fit_parsnip() %>% 
  tidy()
## # A tibble: 4 × 5
##   term            estimate std.error statistic p.value
##   <chr>              <dbl>     <dbl>     <dbl>   <dbl>
## 1 (Intercept)      -2.61       1.36     -1.92   0.0548
## 2 course_interest  -0.0513     0.227    -0.226  0.821 
## 3 overall_percent   1.56       1.16      1.35   0.177 
## 4 gender_F          0.113      0.295     0.384  0.701

Among the predictors included in our model, there are no statistically significant (i.e., p-value < 0.05) relationships between our predictors and being identified as at-risk according to our definition, which doesn’t bode too well for our model but let’s proceed with testing anyways.

Test the model

Now that we’ve fit our model to our training data, we’re FINALLY ready to test our model on the data we set aside in the beginning. Just to recap the steps that led to this moment, however, recall that we:

  1. identified the model to use (lr_mod);

  2. created a preprocessing recipe consisting or predictors and outcome (lr_recipe_1);

  3. bundled the model and recipe into a workflow (lr_workflow); and

  4. trained our workflow using a single call to fit().

The next step is to use the trained workflow (lr_fit) to predict outcomes for our unseen test data, which we will do with the function predict(). The predict() method applies the recipe to the new data, then passes them to the fitted model.

predict(lr_fit, test_data)
## # A tibble: 127 × 1
##    .pred_class
##    <fct>      
##  1 no         
##  2 no         
##  3 no         
##  4 no         
##  5 no         
##  6 no         
##  7 no         
##  8 no         
##  9 no         
## 10 no         
## # … with 117 more rows

Because our outcome variable at_risk here is a factor, the output from predict() returns the predicted class: no versus yes. Not a super useful output to be honest though.

Fortunately there is an augment() function we can use with our lr_fit model and test_data to save them together:

Let’s use this function, save as lr_predictions:

lr_predictions <- augment(lr_fit, test_data)

Your Turn

Take a quick look at lr_predictions in the code chunk below and answer the question that follows?

view(lr_predictions)

Was our model successful at predicting any students who were at-risk? How do you know?

  • No it wasn’t. The model predicted that all of the students in the test data were not at-risk even though about 20% of them were at-risk.

Hint: Scroll to the end of the data frame and take a look at our original at_risk outcome and the .pred_class variable which shows the predicted outcomes.

d. Check model accuracy

As you probably noticed, just looking at the lr_predictions object is not the easiest way to check for model accuracy. Fortunately, the {yardstick} package has an accuracy() function for looking at the overall classification accuracy, which uses the hard class predictions to measure performance.

Hard class predictions tell us whether our model predicted yes or no for each student in the .pred_class column, as well as the estimating a probability. A simple 50% probability cutoff is used to categorize a student as at risk. For example, student 47448 had a .pred_no probability of 0.8854332 and .pred_yes probability of 0.11456677 and so was classified as no for not being for at_risk.

Run the following code to select() these variables followed by the accuracy() function to see how frequently our prediction matched our observed data:

lr_predictions |>
  select(at_risk, .pred_class) |>
  accuracy(truth = at_risk, .pred_class)
## # A tibble: 1 × 3
##   .metric  .estimator .estimate
##   <chr>    <chr>          <dbl>
## 1 accuracy binary         0.811

Overall it looks like our model was correct 81% of the time, which is not too bad but we’ll see in a second is not good enough to serve it’s intended purpose of identifying at-risk students.

Another way to check model accuracy is with the conf_mat() function from {yardstick} for creating a confusion matrix. Recall from our course text Learning Analytics Goes to School that a confusion matrix is simply a 2 × 2 table that lists the number of true-negatives, false-negatives, true-positives, and false-positives.

Run the follow code to create a confusion matrix for our logistic regression predictions:

lr_predictions %>%
conf_mat(at_risk, .pred_class)
##           Truth
## Prediction  no yes
##        no  103  24
##        yes   0   0

As you can see our model accurately predicted all students as “no” for at risk 103 times, but inaccurately predicted 24 students a “no” for at risk when they were actually “yes” in our test_data. Overall our model was 81% (103/127) accurate, but it achieved this by simply labeling everyone as “no” for at risk. And since roughly 82% of our students were actually “no,” this is clearly not a great prediction model.

Your Turn

Recycle our code from above to create a workflow for lr_recipe_2 and test our “kitchen sink” model on our test data. Hint: You can accomplish this by simply copying and pasting and changing a single character from above.

# create workflow
lr_2_workflow <- 
  workflow() %>% 
  add_model(lr_mod) %>% 
  add_recipe(lr_recipe_2)

# set seed
set.seed(586)

# fit model to workflow
lr_2_fit <- 
  lr_2_workflow %>% 
  fit(data = train_data)

# extract model estimates
lr_2_fit %>% 
  extract_fit_parsnip() %>% 
  tidy()
## # A tibble: 8 × 5
##   term                 estimate std.error statistic p.value
##   <chr>                   <dbl>     <dbl>     <dbl>   <dbl>
## 1 (Intercept)           -4.51      1.85     -2.43    0.0149
## 2 course_interest       -0.528     0.323    -1.64    0.102 
## 3 overall_percent        1.49      1.30      1.15    0.251 
## 4 perceived_competence   0.224     0.283     0.792   0.428 
## 5 utility_value          0.541     0.259     2.09    0.0370
## 6 variability            0.155     2.41      0.0643  0.949 
## 7 n_100                  0.136     0.0642    2.11    0.0344
## 8 gender_F               0.0840    0.300     0.280   0.779
# get predictions 
predict(lr_2_fit, test_data)
## # A tibble: 127 × 1
##    .pred_class
##    <fct>      
##  1 no         
##  2 no         
##  3 no         
##  4 no         
##  5 no         
##  6 no         
##  7 no         
##  8 no         
##  9 no         
## 10 no         
## # … with 117 more rows
lr_2_predictions <- augment(lr_2_fit, test_data)

# check overall accuracy 
lr_2_predictions |>
  select(at_risk, .pred_class) |>
  accuracy(truth = at_risk, .pred_class)
## # A tibble: 1 × 3
##   .metric  .estimator .estimate
##   <chr>    <chr>          <dbl>
## 1 accuracy binary         0.811
# create a confusion matrix
lr_2_predictions %>%
conf_mat(at_risk, .pred_class)
##           Truth
## Prediction  no yes
##        no  103  24
##        yes   0   0

Does this model perform any better? How do you know?

  • This model did not perform any better. The model did the same thing by always predicting not at-risk. This is most clear in the confusion matrix.

d. Fit a Random Forest Model

Random forest models are ensembles of decision trees. Each of those terms is linked because there is a dense amount of information required to understand what each of those terms really means. For now, however, them main thing to know is that:

One of the benefits of a random forest model is that it is very low maintenance; it requires very little preprocessing of the data and the default parameters tend to give reasonable results.

We’ll also be using the {ranger} pacakage which provides a fast implementation of Random Forest models and is particularly suited for high dimensional data, e.g., models with a lot of predictors and features. This package supports supervised learning approaches including classification, regression, survival and probability prediction.

Let’s create a recipe for our course_data data that includes all our predictors.

rf_recipe <- 
  recipe(at_risk ~ gender + course_interest + perceived_competence + 
           utility_value + variability + n_100 + overall_percent, 
         data = train_data)

rf_recipe
## Recipe
## 
## Inputs:
## 
##       role #variables
##    outcome          1
##  predictor          7
summary(rf_recipe)
## # A tibble: 8 × 4
##   variable             type    role      source  
##   <chr>                <chr>   <chr>     <chr>   
## 1 gender               nominal predictor original
## 2 course_interest      numeric predictor original
## 3 perceived_competence numeric predictor original
## 4 utility_value        numeric predictor original
## 5 variability          numeric predictor original
## 6 n_100                numeric predictor original
## 7 overall_percent      numeric predictor original
## 8 at_risk              nominal outcome   original

To fit a random forest model on the training set, we’ll use the {parsnip} package again along with the ranger engine. We’ll also include the set_mode() function to specify our model as “classification” rather than “regression.”

Recall from our Learning Analytics Goes to School that supervised machined learning, or predictive modeling, involves two broad approaches: classification and regression. Classification algorithms model categorical outcomes (e.g., yes or no outcomes like with our at risk data). Regression algorithms characterize continuous outcomes (e.g., test scores).

Run the following code to create our random forest model for our training data:

rf_mod <- 
  rand_forest(trees = 5000) %>% 
  set_engine("ranger", importance = "impurity") %>% 
  set_mode("classification")

Now let’s combine our model and recipe to create our new random forest workflow:

rf_workflow <- 
  workflow() %>% 
  add_model(rf_mod) %>% 
  add_recipe(rf_recipe)

And fit our model and recipe to our training data:

set.seed(586)

rf_fit <- 
  rf_workflow %>% 
  fit(data = train_data)

Gather our random forest predictions:

rf_predictions <- augment(rf_fit, test_data)

rf_predictions
## # A tibble: 127 × 13
##    studen…¹ cours…² at_risk gender cours…³ perce…⁴ utili…⁵ overa…⁶ varia…⁷ n_100
##       <dbl> <chr>   <fct>   <fct>    <dbl>   <dbl>   <dbl>   <dbl>   <dbl> <int>
##  1    48797 PhysA-… no      F          3.8     3.5    3.5    0.905   0.286     9
##  2    53447 FrScA-… no      F          4.2     3      2.67   0.702   0.221     5
##  3    54066 OcnA-S… no      M          4.4     4      5      0.848   0.307     6
##  4    55140 PhysA-… no      F          3.8     3      2.67   0.772   0.275     6
##  5    57981 OcnA-S… yes     F          4.2     3      3.67   0.896   0.118    10
##  6    58168 AnPhA-… no      F          5       5      4.67   0.871   0.242     4
##  7    62157 FrScA-… no      F          4.5     3.5    3.33   0.859   0.162     7
##  8    62175 OcnA-S… no      M          4       4      3.33   0.842   0.356     5
##  9    62752 PhysA-… no      F          4.2     2.5    2.33   0.834   0.120    11
## 10    64930 FrScA-… no      F          4       3      3.67   0.792   0.151     7
## # … with 117 more rows, 3 more variables: .pred_class <fct>, .pred_no <dbl>,
## #   .pred_yes <dbl>, and abbreviated variable names ¹​student_id, ²​course_id,
## #   ³​course_interest, ⁴​perceived_competence, ⁵​utility_value, ⁶​overall_percent,
## #   ⁷​variability

And check our model’s overall accuracy:

rf_predictions %>%
  select(at_risk, .pred_class) %>%
  accuracy(truth = at_risk, .pred_class)
## # A tibble: 1 × 3
##   .metric  .estimator .estimate
##   <chr>    <chr>          <dbl>
## 1 accuracy binary         0.803

And create a confusion matrix to see where it did and did not make accurate predictions:

rf_predictions |>
conf_mat(at_risk, .pred_class)
##           Truth
## Prediction  no yes
##        no  101  23
##        yes   2   1

Unfortunately, our random forest model wasn’t much better at predicting students who were defined as “at risk.”

Even more unfortunate, if this had been a real-world situation, only 1 of the 24 students who failed the course would have received additional support. Clearly we’d need to build a better model. In this case, more data would definitely help, but it’s likely we’re missing some important information about students that is predictive of their success.

Your Turn

Try creating your own recipe and logistic regression or random forest model and see how it performs against the three that we just created. Feel free to experiment with variables we excluded, like specific survey items. There is also a file in your data folder called sci-mo-with-text.csv that includes data about students participation in discussion forums, including the number of posts and psychometric properties about their language used. You can read more about that data in Chapter 14 of DSIEUR.

I recommend creating and using an R script to develop your model. Use the code chunk below to record your final model:

course_data_3 <- processed_data %>%
  mutate(at_risk = if_else(final_grade >= 66.7, "no", "yes")) %>%
  mutate(gender = as_factor(gender), at_risk = as_factor(at_risk)) %>%
  select(student_id, course_id, at_risk, gender, q1, q2, q3, q4, q5, q6,
         q7, q8, q9, q10) %>%
  drop_na()

course_data_3 %>%
  count(at_risk) %>%
  mutate(proportion = n/sum(n))
## # A tibble: 2 × 3
##   at_risk     n proportion
##   <fct>   <int>      <dbl>
## 1 no        361      0.813
## 2 yes        83      0.187
course_data_3 <- inner_join(course_data_3, grade_book)
## Joining, by = c("student_id", "course_id")
set.seed(123)

course_split_3 <- initial_split(course_data_3, strata = at_risk)

train_data_3 <- training(course_split_3)
test_data_3 <- testing(course_split_3)

lr_recipe_3 <- recipe(at_risk ~ course_id, gender, q1, q2, q3, q4, q5,
                      q6, q7, q8, q9, q10, data = train_data_3) %>%
  step_dummy(all_nominal_predictors())

lr_workflow_3 <-
  workflow() %>%
  add_model(lr_mod) %>%
  add_recipe(lr_recipe_3)

set.seed(123)

lr_fit_3 <-
  lr_workflow_3 %>%
  fit(data = train_data_3)

lr_fit_3 %>%
  extract_fit_parsnip() %>%
  tidy()
## # A tibble: 24 × 5
##    term                    estimate std.error statistic  p.value
##    <chr>                      <dbl>     <dbl>     <dbl>    <dbl>
##  1 (Intercept)               -2.48      0.736  -3.38    0.000735
##  2 course_id_AnPhA.S116.02    1.70      0.912   1.86    0.0630  
##  3 course_id_AnPhA.S216.01    1.93      0.859   2.24    0.0250  
##  4 course_id_AnPhA.S216.02    1.79      1.14    1.58    0.115   
##  5 course_id_AnPhA.T116.01  -15.1    1769.     -0.00852 0.993   
##  6 course_id_BioA.S116.01     2.23      0.892   2.50    0.0123  
##  7 course_id_BioA.S216.01     3.18      1.43    2.22    0.0261  
##  8 course_id_BioA.T116.01   -15.1    3956.     -0.00381 0.997   
##  9 course_id_FrScA.S116.01    0.762     0.882   0.864   0.387   
## 10 course_id_FrScA.S116.02    2.08      1.17    1.77    0.0762  
## # … with 14 more rows
lr_predictions_3 <- augment(lr_fit_3, test_data_3)

lr_predictions_3 %>%
  select(at_risk, .pred_class) %>%
  accuracy(truth = at_risk, .pred_class)
## # A tibble: 1 × 3
##   .metric  .estimator .estimate
##   <chr>    <chr>          <dbl>
## 1 accuracy binary         0.795
lr_predictions_3 %>%
  conf_mat(at_risk, .pred_class)
##           Truth
## Prediction no yes
##        no  89  21
##        yes  2   0

Now answer the following questions?

  1. How did your model do compared to others?

    • My model didn’t do any better than the previous models.
  2. What information might be useful to know about students prior to the course start that might be useful for improving our model?

    • It might be nice to know how students did in previous science classes or if they were planning on receiving support outside of class, such as tutoring.

5. COMMUNICATE

In this case study, we focused applying some basic machine learning techniques to help us understand how a predictive model used in early warning systems might actually be developed and tested. Specifically, we made a very crude first attempt at developing a model using machine learning techniques that were not terribly great at accurately predict whether a student is likely to pass or fail and online course.

Below, add a few notes in response to the following prompts:

  1. One thing I took away from this learning lab that I found especially useful:

    • I learned a lot about how to make a model, train it and test it. It was really cool to apply what we’ve been learning over the last few weeks to some machine learning. I had wondered about how to split data and train it and this taught me ways to do that with a logistic regression and a random forest model.
  2. One thing I want to learn more about:

    • I’d like to learn more about working with data sets that have a lot of variables. I looked at the sci-mo-with-text.csv data set, but it was intimidating and I didn’t know where to start even if I wanted to use it.

To “turn in” your work, you can click the “Knit” icon at the top of the file, or click the dropdown arrow next to it and select “Knit top HTML”. This will create a report in your Files pane that serves as a record of your completed assignment and that be opened in a browser or shared on the web.

Congratulations!

Estrellado, Ryan A, Emily Freer, Jesse Mostipak, Joshua M Rosenberg, and Isabella C Velásquez. 2020. Data Science in Education Using r. Routledge.
LS0tCnRpdGxlOiAnVW5pdCAyIENhc2UgU3R1ZHk6IElkZW50aWZ5aW5nIEF0LVJpc2sgU3R1ZGVudHMnCmF1dGhvcjogIkdyYWNlIFdpZWRyaWNoIgpkYXRlOiAiYHIgU3lzLkRhdGUoKWAiCm91dHB1dDoKICBodG1sX2RvY3VtZW50OgogICAgdG9jOiB5ZXMKICAgIHRvY19kZXB0aDogJzUnCiAgICB0b2NfZmxvYXQ6IHllcwogICAgY29kZV9mb2xkaW5nOiBzaG93CiAgICBjb2RlX2Rvd25sb2FkOiBUUlVFCm5hbWU6ICcnCmVkaXRvcl9vcHRpb25zOgogIG1hcmtkb3duOgogICAgd3JhcDogNzIKYmlibGlvZ3JhcGh5OiBsaXQvcmVmZXJlbmNlcy5iaWIKLS0tCgpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGVjaG8gPSBUUlVFKQpgYGAKCiMjIDEuIFBSRVBBUkUKCkluIFVuaXQgMiwgd2UgcGljayB1cCB3aGVyZSB3ZSBsZWZ0IG9mZiBhbmQgbGVhcm4gdG8gYXBwbHkgc29tZSBiYXNpYwptYWNoaW5lIGxlYXJuaW5nIHRlY2huaXF1ZXMgdG8gaGVscCB1cyB1bmRlcnN0YW5kIGhvdyBhIHByZWRpY3RpdmUgbW9kZWwKbWlnaHQgYmUgZGV2ZWxvcGVkIGFuZCB0ZXN0ZWQgYXMgcGFydCBvZiBhbiBlYXJseSB3YXJuaW5nIHN5c3RlbSB0bwppZGVudGlmeSBzdHVkZW50cyBhdCByaXNrIG9mIGZhaWxpbmcuIFNwZWNpZmljYWxseSwgd2Ugd2lsbCBtYWtlIGEgdmVyeQpjcnVkZSBmaXJzdCBhdHRlbXB0IGF0IGRldmVsb3BpbmcgYSB0ZXN0aW5nIGEgbG9naXN0aWMgcmVncmVzc2lvbiBhbmQgYQpyYW5kb20gZm9yZXN0IG1vZGVsIHRoYXQgY2FuICh3ZSBob3BlISkgYWNjdXJhdGVseSBwcmVkaWN0IHdoZXRoZXIgYQpzdHVkZW50IHdpbGwgcGFzcyBvciBmYWlsIGFuIG9ubGluZSBjb3Vyc2UuIFVubGlrZSBVbml0IDEsIHdoZXJlIHdlIHdlcmUKaW50ZXJlc3RlZCBpbiBpZGVudGlmeWluZyBmYWN0b3JzIGZyb20gZGF0YSBjb2xsZWN0ZWQgdGhyb3VnaG91dCB0aGUKY291cnNlIHRoYXQgbWlnaHQgaGVscCBleHBsYWluIHdoeSBzdHVkZW50cyBlYXJuZWQgdGhlIGdyYWRlIHRoZXkgZGlkLAp3ZSBhcmUgaW5zdGVhZCBpbnRlcmVzdGVkIGlkZW50aWZ5aW5nIHN0dWRlbnRzIHdobyBtYXkgYmUgYXQgcmlzayBiZWZvcmUKaXQgaXMgdG9vIGxhdGUgdG8gaW50ZXJ2ZW5lLgoKIyMjIDFhLiBMb2FkIExpYnJhcmllcwoKIyMjIyB0aWR5bW9kZWxzIPCfk6YKClshW10oaW1nL3RpZHltb2RlbHMuc3ZnKXt3aWR0aD0iMjAlIn1dKGh0dHBzOi8vd3d3LnRpZHltb2RlbHMub3JnKQoKVGhlIFt0aWR5bW9kZWxzXShodHRwczovL3d3dy50aWR5bW9kZWxzLm9yZykgcGFja2FnZSBpcyBhICJtZXRhLXBhY2thZ2UiCmZvciBtb2RlbGluZyBhbmQgc3RhdGlzdGljYWwgYW5hbHlzaXMgdGhhdCBzaGFyZXMgdGhlIHVuZGVybHlpbmcgZGVzaWduCnBoaWxvc29waHksIGdyYW1tYXIsIGFuZCBkYXRhIHN0cnVjdHVyZXMgb2YgdGhlClt0aWR5dmVyc2VdKGh0dHBzOi8vd3d3LnRpZHl2ZXJzZS5vcmcvKS4gSXQgaW5jbHVkZXMgYSBjb3JlIHNldCBvZgpwYWNrYWdlcyB0aGF0IGFyZSBsb2FkZWQgb24gc3RhcnR1cCBhbmQgY29udGFpbnMgdG9vbHMgZm9yOgoKLSAgIGRhdGEgc3BsaXR0aW5nIGFuZCBwcmUtcHJvY2Vzc2luZzsKLSAgIG1vZGVsIHNlbGVjdGlvbiwgdHVuaW5nLCBhbmQgZXZhbHVhdGlvbjsKLSAgIGZlYXR1cmUgc2VsZWN0aW9uIGFuZCB2YXJpYWJsZSBpbXBvcnRhbmNlIGVzdGltYXRpb247Ci0gICBhcyB3ZWxsIGFzIG90aGVyIGZ1bmN0aW9uYWxpdHkuCgojIyMjIFsqKllvdXIgVHVybioqXXtzdHlsZT0iY29sb3I6IGdyZWVuOyJ9ICoq4qS1KioKCkluIGFkZGl0aW9uIHRvIHRoZSB7dGlkeW1vZGVsc30gcGFja2FnZSwgd2UnbGwgYWxzbyBiZSB1c2luZyB0aGUKe3RpZHl2ZXJzZX0gcGFja2FnZXMgd2UgbGVhcm5lZCBhYm91dCBpbiBVbml0IDEsIGFuZCB0aGUge3Jhbmdlcn0KcGFja2FnZSB3ZSdsbCBiZSB1c2luZyBmb3Igb3VyIHJhbmRvbSBmb3Jlc3QgbW9kZWwgaW4gUGFydCA0LgoKVXNlIHRoZSBjb2RlIGNodW5rIGJlbG93IHRvIGxvYWQgdGhlc2UgdGhyZWUgcGFja2FnZXM6CgpgYGB7cn0KbGlicmFyeSh0aWR5bW9kZWxzKQpsaWJyYXJ5KHRpZHl2ZXJzZSkKbGlicmFyeShyYW5nZXIpCgpgYGAKCiMjIyBiLiBJbXBvcnQgJiBJbnNwZWN0IERhdGEKCkZvciB0aGlzIGNhc2Ugc3R1ZHksIHdlIHdpbGwgYWdhaW4gYmUgd29ya2luZyBhZ2FpbiB3aXRoIGRhdGEgZnJvbSB0aGUKb25saW5lIHNjaWVuY2UgY291cnNlcyBpbnRyb2R1Y2VkIGluIFVuaXQgMS4gVGhpcyBkYXRhIGlzIGxvY2F0ZWQgaW4gdGhlCmRhdGEgZm9sZGVyIGFuZCBuYW1lZCBgcHJvY2Vzc2VkLWRhdGEuY3N2YC4gRm9ydHVuYXRlbHksIG91ciBkYXRhIGhhcwphbHJlYWR5IGJlZW4gbGFyZ2VseSB3cmFuZ2xlZC4gVGhpcyB3aWxsIHNhdmUgdXMgcXVpdGUgYSBiaXQgb2YgdGltZSwKd2hpY2ggd2UnbGwgbmVlZCB0byBoZWxwIHdyYXAgb3VyIGhlYWRzIGFyb3VuZCBzb21lIHN1cGVydmlzZWQgbWFjaGluZQpsZWFybmluZyBiYXNpY3MuCgpSdW4gdGhlIGNvZGUgY2h1bmsgYmVsb3cgdG8gcmVhZCB0aGlzIGRhdGEgaW50byB5b3VyIFIgZW52aXJvbm1lbnQgYXMgYW4Kb2JqZWN0IG5hbWVkIGBwcm9jZXNzZWRfZGF0YWAgYW5kIGxldCdzIHRha2UgYSBxdWljayBsb29rIGF0IGRhdGEgd2UgbWF5CnVzZSBpbiBvdXIgcHJlZGljdGl2ZSBtb2RlbDoKCmBgYHtyfQpwcm9jZXNzZWRfZGF0YSA8LSByZWFkX2NzdigiZGF0YS9wcm9jZXNzZWQtZGF0YS5jc3YiKQoKcHJvY2Vzc2VkX2RhdGEKYGBgCgojIyMjIFsqKllvdXIgVHVybioqXXtzdHlsZT0iY29sb3I6IGdyZWVuOyJ9ICoq4qS1KioKCkluIGFkZGl0aW9uIHRvIHN0dWRlbnRzJyBnZW5kZXIgYW5kIHRoZWlyIHByZS1jb3Vyc2Ugc3VydmV5IHJlc3BvbnNlcwphc3Nlc3NpbmcgdGhyZWUgYXNwZWN0cyBvZiBzdHVkZW50IG1vdGl2YXRpb24sIHdlIHdpbGwgYWxzbyBpbmNvcnBvcmF0ZQpncmFkZWJvb2sgZGF0YSBjb2xsZWN0ZWQgZHVyaW5nIHRoZSBjb3Vyc2UgdGhhdCB3ZSB3aWxsIGFsc28gdXNlIHRvIGhlbHAKInRyYWluIiBhIHByZWRpY3RpdmUgbW9kZWwgZm9yIGlkZW50aWZ5aW5nIHN0dWRlbnRzIGF0IHJpc2sgb2YgZmFpbGluZy4KClVzZSB0aGUgY29kZSBjaHVuayBiZWxvdyB0byByZWFkIGluIHRoZSAuY3N2IGZpbGUgbmFtZWQgImdyYWRlLWJvb2suY3N2Igpsb2NhdGVkIGluIHRoZSBkYXRhIGZvbGRlciwgYXNzaWduIHRvIGEgbmV3IG9iamVjdCBuYW1lZCBgZ3JhZGVfYm9va2AsCmFuZCB1c2UgYSBtZXRob2Qgb2YgeW91ciBjaG9vc2luZyB0byBhbnN3ZXIgdGhlIHF1ZXN0aW9ucyB0aGF0IGZvbGxvdzoKCmBgYHtyfQoKZ3JhZGVfYm9vayA8LSByZWFkX2NzdigiZGF0YS9ncmFkZS1ib29rLmNzdiIpCgpncmFkZV9ib29rCgpgYGAKCjEuICBIb3cgbWFueSBvYnNlcnZhdGlvbnMgYW5kIHZhcmlhYmxlcyBhcmUgaW4gb3VyIGBncmFkZV9ib29rYCBkYXRhc2V0PwogICAgQW5kIHJvdWdobHkgaG93IG1hbnkgb2JzZXJ2YXRpb25zIGFyZSB0aGVyZSBwZXIgc3R1ZGVudD8KCiAgICAtICAgVGhlcmUgYXJlIDUgdmFyaWFibGVzLCAyOSw3MTEgdG90YWwgb2JzZXJ2YXRpb25zLCBhbmQgNDIKICAgICAgICBvYnNlcnZhdGlvbnMgcGVyIHN0dWRlbnQuCgoyLiAgSG93IG1pZ2h0IHRoaXMgZGF0YSBiZSB1c2VkIGluIG91ciBtb2RlbCB0byBoZWxwIHByZWRpY3Qgc3R1ZGVudCBhdAogICAgcmlzayBvZiBmYWlsaW5nIHRoZSBjb3Vyc2U/CgogICAgLSAgIFdlIGNhbiBsb29rIGZvciBwYXR0ZXJucyBpbiB0aGUgc3R1ZGVudHMgd2hvIGZhaWxlZCB0aGUgY291cnNlLAogICAgICAgIGxpa2UgaWYgdGhlcmUgd2FzIGEgcGFydGljdWxhciBhc3NpZ25tZW50IHRoYXQgdGhlIG1ham9yaXR5IGRpZAogICAgICAgIHBvb3JseSBvbiwgdG8gc2VlIGlmIHRoZXkgd291bGQgd29yayBhcyByaXNrIGluZGljYXRvcnMuCgozLiAgV2hhdCBlbHNlIGRvIHlvdSBub3RpY2UgYWJvdXQgdGhpcyBkYXRhIHNldD8KCiAgICAtICAgSSBub3RpY2VkIHRoYXQgdGhlcmUgaXMgZGF0YSBmcm9tIG11bHRpcGxlIGNvdXJzZXMsIHNvIHdlIG1heQogICAgICAgIG5lZWQgdG8gc3BsaXQgdXAgdGhlIGNvdXJzZXMgdG8gZ2V0IGEgbWFuYWdlYWJsZSBhbW91bnQgb2YgZGF0YQogICAgICAgIHRvIG1vZGVsIGFuZCB0byB3b3JrIHdpdGggYWxsIHRoZSBzYW1lIGFzc2lnbm1lbnRzIGZvciBhIG1vZGVsLgoKIyMgMi4gV1JBTkdMRQoKSW4gZ2VuZXJhbCwgZGF0YSB3cmFuZ2xpbmcgaW52b2x2ZXMgc29tZSBjb21iaW5hdGlvbiBvZiBjbGVhbmluZywKcmVzaGFwaW5nLCB0cmFuc2Zvcm1pbmcsIGFuZCBtZXJnaW5nIGRhdGEgKFdpY2toYW0gJiBHcm9sZW11bmQsIDIwMTcpLgpUaGUgaW1wb3J0YW5jZSBvZiBkYXRhIHdyYW5nbGluZyBpcyBkaWZmaWN1bHQgdG8gb3ZlcnN0YXRlLCBhcyBpdAppbnZvbHZlcyB0aGUgaW5pdGlhbCBzdGVwcyBvZiBnb2luZyBmcm9tIHJhdyBkYXRhIHRvIGEgZGF0YXNldCB0aGF0IGNhbgpiZSBleHBsb3JlZCBhbmQgbW9kZWxlZCAoS3J1bW0gZXQgYWwsIDIwMTgpLiBJbiBQYXJ0IDIsIHdlIGZvY3VzIG9uIHRoZQp0aGUgZm9sbG93aW5nIHdyYW5nbGluZyBwcm9jZXNzZXMgdG86CgphLiAgKipDcmVhdGUgYW4gT3V0Y29tZSBWYXJpYWJsZSoqLiBXZSBjcmVhdGUgYSBiaW5hcnkgdmFyaWFibGUgb3V0Y29tZQogICAgd2UgYXJlIGludGVyZXN0ZWQgaW4gcHJlZGljdGluZywgaS5lLiBwYXNzL2ZhaWwuCgpiLiAgKipDb252ZXJ0IENoYXJhY3RlciBTdGluZ3MgdG8gRmFjdG9ycy4qKiBXZSBjb252ZXJ0IHZhcmlhYmxlcyBzdG9yZWQKICAgIGFzIGNoYXJhY3RlciBzdHJpbmdzIHRvIGZhY3RvcnMgZXhwZWN0ZWQgYnkgb3VyIG1vZGVscy4KCmMuICAqKlNlbGVjdCBQcmVkaWN0b3JzLioqIFdlIG5hcnJvdyBkb3duIG91ciBkYXRhIHNldCB0byBzcGVjaWZpYwogICAgcHJlZGljdG9ycyBvZiBpbnRlcmVzdC4KCiMjIyBhLiBDcmVhdGUgT3V0Y29tZSBWYXJpYWJsZQoKU2luY2Ugd2UgYXJlIGludGVyZXN0ZWQgaW4gZGV2ZWxvcGluZyBhIHByZWRpY3RpdmUgbW9kZWwgdGhhdCBjYW4KcHJlZGljdCB3aGV0aGVyIGEgc3R1ZGVudCBpcyBhdCByaXNrIG9mIGZhaWxpbmcgYSBjb3Vyc2UsIGFuZCBzbyB3ZSBjYW4KaW50ZXJ2ZW5lIGJlZm9yZSB0aGF0IGhhcHBlbnMsIHdlIG5lZWQgYW4gb3V0Y29tZSB2YXJpYWJsZSB0aGF0IGxldHMgdXMKa25vdyBpZiB0aGV5IGhhdmUgZWl0aGVyIHBhc3NlZCBvciBmYWlsZWQuCgpMZXQncyBjb21iaW5lIHRoZSBob3BlZnVsbHkgZmFtaWxpYXIgYG11dGF0ZSgpYCBmdW5jdGlvbiB3aXRoIHRoZQpgaWZfZWxzZSgpYCBmdW5jdGlvbiBhbHNvIGZyb20gdGhlIHtkcGx5cn0gcGFja2FnZSB0byBjcmVhdGUgYSBuZXcKdmFyaWFibGUgY2FsbGVkIGBhdF9yaXNrYCB3aGljaCBpbmRpY2F0ZXMgIm5vIiBmb3Igc3R1ZGVudHMgd2hvc2UKYGZpbmFsX2dyYWRlYCBpcyBncmVhdGVyIHRoYW4gb3IgZXF1YWwgdG8gNjYuNyUgKE5DIFN0YXRlJ3MgY3V0b2ZmIGZvciBhCkQtKSBhbmQgInllcyIgaWYgaXQgaXMgbm90LgoKYGBge3J9CmNvdXJzZV9kYXRhIDwtIHByb2Nlc3NlZF9kYXRhICU+JSAKICBtdXRhdGUoYXRfcmlzayA9IGlmX2Vsc2UoZmluYWxfZ3JhZGUgPj0gNjYuNywgIm5vIiwgInllcyIpKSAKCmNvdXJzZV9kYXRhCmBgYAoKIyMjIGIuIENvbnZlcnQgdG8gRmFjdG9ycwoKV2hpbGUgaW5zcGVjdGluZyB0aGUgZGF0YSwgeW91IG1heSBoYXZlIG5vdGljZWQgdGhhdCBvbmUgb2Ygb3VyCnByZWRpY3RvcnMgb2YgaW50ZXJlc3QgYW5kIG91ciBgYXRfcmlza2Agb3V0Y29tZSB2YXJpYWJsZSBhcmUgc3RvcmVkIGFzCmNoYXJhY3RlciBcPGNoclw+IGRhdGEgdHlwZXMuIEZvciBjcmVhdGluZyBtb2RlbHMsIGl0IGlzIGJldHRlciB0byBoYXZlCnF1YWxpdGF0aXZlIHZhcmlhYmxlcyBsaWtlIGBnZW5kZXJgIGFuZCBgYXRfcmlza2AgZW5jb2RlZCBhcyBmYWN0b3JzCmluc3RlYWQgb2YgY2hhcmFjdGVyIHN0cmluZ3MuIEZhY3RvcnMgc3RvcmUgZGF0YSBhcyBjYXRlZ29yaWNhbAp2YXJpYWJsZXMsIGVhY2ggd2l0aCBpdHMgb3duIGxldmVscy4gQmVjYXVzZSBjYXRlZ29yaWNhbCB2YXJpYWJsZXMgYXJlCnVzZWQgaW4gc3RhdGlzdGljYWwgbW9kZWxzIGRpZmZlcmVudGx5IHRoYW4gY29udGludW91cyB2YXJpYWJsZXMsCnN0b3JpbmcgdGhlc2UgZGF0YSBhcyBmYWN0b3JzIGVuc3VyZXMgdGhhdCB0aGUgbW9kZWxpbmcgZnVuY3Rpb25zIHdpbGwKdHJlYXQgdGhlbSBjb3JyZWN0bHkuCgpUbyBkbyBzbywgd2Ugb25jZSBhZ2FpbiB1c2UgdGhlIGBtdXRhdGUoKWAgZnVuY3Rpb24gYnV0IGluc3RlYWQgb2YKY3JlYXRpbmcgYSBuZXcgdmFyaWFibGUsIHdlIHdpbGwgdXNlIHRoZSBgYXNfZmFjdG9yKClgIGZ1bmN0aW9uIHRvCmNvbnZlcnQgYGdlbmRlcmAgYW5kIGBhdF9yaXNrYCBmcm9tIGEgY2hhcmFjdGVyIHRvIGEgZmFjdG9yIGFuZCBzYXZlIGl0CmFzIGEgdmFyaWFibGUgb2YgdGhlIHNhbWUgbmFtZSwgZWZmZWN0aXZlbHkgcmVwbGFjaW5nIHRoZSBvbGQgdmFyaWFibGVzCndpdGggbmV3IG9uZXM6CgpgYGB7cn0KY291cnNlX2RhdGEgPC0gY291cnNlX2RhdGEgfD4KICBtdXRhdGUoZ2VuZGVyID0gYXNfZmFjdG9yKGdlbmRlciksIAogICAgICAgICBhdF9yaXNrID0gYXNfZmFjdG9yKGF0X3Jpc2spKQoKY291cnNlX2RhdGEKYGBgCgoqKk5vdGU6KiogQmUgc3VyZSB0byBwYXkgYXR0ZW50aW9uIHRvIHRoZSBmYWN0IHRoYXQgaW4gYWRkaXRpb24gdG8KYXNzaWduaW5nIG91ciBtb2RpZmllZCBgZ2VuZGVyYCB2YXJpYWJsZSB0byBhIHZhcmlhYmxlIG9mIHRoZSBzYW1lIG5hbWUKdXNpbmcgdGhlIGA9YCBvcGVyYXRvciwgd2UgYWxzbyBhc3NpZ25lZCBvdXIgbW9kaWZpZWQgYGNvdXJzZV9kYXRhYCB0bwphbiBvYmplY3Qgb2YgdGhlIHNhbWUgbmFtZSB1c2luZyB0aGUgYDwtYCBvcGVyYXRvci4gVGhhdCBpcywgd2UKZWZmZWN0aXZlbHkgb3ZlcndyaXRlIHRoZSBvbGQgYGNvdXJzZV9kYXRhYCB0aGF0IGluY2x1ZGVkIGBnZW5kZXJgCnN0b3JlZCBhcyBhIGNoYXJhY3RlciBkYXRhIHR5cGUgdG8gYSBuZXcgYGNvdXJzZV9kYXRhYCBvYmplY3Qgd2l0aApgZ2VuZGVyYCBzdG9yZWQgYXMgYSBmYWN0b3IuCgojIyMgYy4gU2VsZWN0IFByZWRpY3RvcnMKCkFzIHlvdSd2ZSBwcm9iYWJseSBub3RpY2VkLCB0aGVyZSBpcyBhIGxvdCBvZiBncmVhdCBpbmZvcm1hdGlvbiBpbiBvdXIKYGNvdXJzZV9kYXRhYCAtIGJ1dCB3ZSB3b24ndCwgYW5kIHNob3VsZG4ndCwgaW5jbHVkZSBhbGwgb2YgaXQgaW4gb3VyCnByZWRpY3RpdmUgbW9kZWwuIEluZGVlZCwgRXN0cmVsbGFkbyBldCBhbC4gQGVzdHJlbGxhZG8yMDIwZGF0YSBwb2ludApvdXQgdGhhdCBmb3Igc3RhdGlzdGljYWwgcmVhc29ucyBhbmQgYXMgYSBnb29kIGdlbmVyYWwgcHJhY3RpY2UsIGl0J3MKYmV0dGVyIHRvIHNlbGVjdCBhIGZldyB2YXJpYWJsZXMgb2YgaW50ZXJlc3QgYmVjYXVzZToKCj4gQXQgYSBjZXJ0YWluIHBvaW50LCBhZGRpbmcgbW9yZSB2YXJpYWJsZXMgd2lsbCAqYXBwZWFyKiB0byBtYWtlIHlvdXIKPiBhbmFseXNpcyBtb3JlIGFjY3VyYXRlLCBidXQgd2lsbCBpbiBmYWN0IG9ic2N1cmUgdGhlIHRydXRoIGZyb20geW91LgoKIyMjIyBbKipZb3VyIFR1cm4qKl17c3R5bGU9ImNvbG9yOiBncmVlbjsifSAqKuKktSoqCgpDb21wbGV0ZSB0aGUgY29kZSBjaHVuayBiZWxvdyB0byAic2VsZWN0IiAoaGludCwgaGludCkgYGF0X3Jpc2tgIGZvciBvdXIKb3V0Y29tZSB2YXJpYWJsZSwgYXMgd2VsbCBhcyB0aGUgZm9sbG93aW5nIHZhcmlhYmxlcyB3ZSB3aWxsIHVzZSBpbiBvdXIKcHJlZGljdGl2ZSBtb2RlbDoKCi0gICBzdHVkZW50X2lkCi0gICBjb3Vyc2VfaWQKLSAgIGF0X3Jpc2sKLSAgIGdlbmRlcgotICAgY291cnNlX2ludGVyZXN0Ci0gICBwZXJjZWl2ZWRfY29tcGV0ZW5jZQotICAgdXRpbGl0eV92YWx1ZQoKYGBge3J9CmNvdXJzZV9kYXRhIDwtIGNvdXJzZV9kYXRhIHw+CiAgc2VsZWN0KHN0dWRlbnRfaWQsIGNvdXJzZV9pZCwgYXRfcmlzaywgZ2VuZGVyLCBjb3Vyc2VfaW50ZXJlc3QsCiAgICAgICAgIHBlcmNlaXZlZF9jb21wZXRlbmNlLCB1dGlsaXR5X3ZhbHVlKQoKY291cnNlX2RhdGEKYGBgCgojIyAzLiBFeHBsb3JlCgpBcyBub3RlZCBieSBLcnVtbSBldCBhbC4gKDIwMTgpLCBleHBsb3JhdG9yeSBkYXRhIGFuYWx5c2lzIG9mdGVuCmludm9sdmVzIHNvbWUgY29tYmluYXRpb24gb2YgZGF0YSB2aXN1YWxpemF0aW9uIGFuZCBmZWF0dXJlIGVuZ2luZWVyaW5nLgpJbiBQYXJ0IDMsIHdlIHdpbGwgY3JlYXRlIGEgcXVpY2sgdmlzdWFsaXphdGlvbiB0byBoZWxwIHVzIHNwb3QgYW55Cmlzc3VlcyB3aXRoIG91ciBkYXRhIGFuZCBlbmdpbmVlciBuZXcgZmVhdHVyZXMgdGhhdCB3ZSB3aWxsIHVzZSBpbiBvdXIKcHJlZGljdGl2ZSBtb2RlbHMuIFNwZWNpZmljYWxseSwgaW4gUGFydCAzIHdlIHdpbGw6CgphLiAgKipFeGFtaW5lIE91dGNvbWVzKiogYnkgdGFraW5nIGEgcXVpY2sgbG9vayBhdCBgYXRfcmlza2Agb3V0Y29tZQogICAgdmFyaWFibGUgb2YgaW50ZXJlc3QgYXMgd2VsbCBhcyBvdXIgb3RoZXIgcHJlZGljdG9ycyB0byBzcG90IGFueQogICAgaXNzdWVzIHRoYXQgbWF5IGhhbmcgdXAgb3VyIG1vZGVscy4KCmIuICAqKkVuZ2luZWVyIFByZWRpY3RvcnMqKiBieSBjcmVhdGluZyBzb21lIG5ldyB2YXJpYWJsZXMgd2UgdGhpbmsgd2lsbAogICAgYmUgcHJlZGljdGl2ZSBvZiBzdHVkZW50cyBhdCByaXNrLCBzdWNoIGFzIHBlcmZvcm1hbmNlIG9uCiAgICBhc3NpZ25tZW50cyBvbiBkdXJpbmcgdGhlIGZpcnN0IGhhbGYgb2YgdGhlIGNvdXJzZS4KCiMjIyBhLiBDb3VudCBwcm9wb3J0aW9ucwoKQmVmb3JlIHdlIG1vdmUgb24gdG8gZmVhdHVyZSBlbmdpbmVlcmluZywgbGV0J3MgdGFrZSBhIHF1aWNrIGxvb2sgYXQgdGhlCnByb3BvcnRpb24gb2Ygc3R1ZGVudHMgaW4gb3VyIGZpbmFsIGRhdGEgc2V0IHRoYXQgd2VyZSBpZGVudGlmaWVkIGFzCmF0LXJpc2suIFRvIGRvIHNvLCBydW4gdGhlIGZvbGxvd2luZyBjb2RlIGNodW5rIHRvIHRha2UgYSBjb3VudCBvZiB0aGUKbnVtYmVyIG9mIHN0dWRlbnRzIGxhYmVsZWQgInllcyIgb3IgIm5vIiBvZiBiZWluZyBgYXRfcmlza2AsIGFuZCB0aGVuCmNyZWF0ZSBhIG5ldyB2YXJpYWJsZSBjYWxsZWQgcHJvcG9ydGlvbiB0aGF0IHRha2VzIHRoZSBudW1iZXIgYG5gIG9mCmVhY2ggYW5kIGRpdmlkZXMgYnkgdGhlIHRvdGFsIG51bWJlcjoKCmBgYHtyfQpjb3Vyc2VfZGF0YSB8PgogIGNvdW50KGF0X3Jpc2spIHw+CiAgbXV0YXRlKHByb3BvcnRpb24gPSBuL3N1bShuKSkKYGBgCgpBcyB5b3UgY2FuIHNlZSwgcm91Z2hseSAxOCUgb2Ygc3R1ZGVudHMgaW4gb3VyIGRhdGEgc2V0IHdlcmUgaWRlbnRpZmllZAphcyAieWVzIiBmb3IgYmVpbmcgYXQgcmlzay4gQW5kIHNpbmNlIHRoZXNlIGFyZSBmaW5hbCBncmFkZXMgd2UgYXJlCndvcmtpbmcgd2l0aCwgdGhlc2Ugc3R1ZGVudCB3ZXJlIG5vdCBvbmx5IGF0IHJpc2sgb2YgZmFpbGluZywgYnV0IGRpZAppbmRlZWQgZmFpbCB0aGUgY291cnNlLiBZb3UgbWF5IGFsc28gbm90aWNlIHdlIGhhdmUgYSBudW1iZXIgb2Ygc3R1ZGVudHMKd2l0aCBtaXNzaW5nIGdyYWRlcywgd2hpY2ggaXMgc29tZXRoaW5nIHdlIHdpbGwgaGF2ZSB0byBhZGRyZXNzIHByaW9yIHRvCmFuYWx5c2lzLgoKIyMjIyBbKipZb3VyIFR1cm4qKl17c3R5bGU9ImNvbG9yOiBncmVlbjsifSAqKuKktSoqCgpBbHRlcm5hdGl2ZWx5LCB3ZSBjb3VsZCBoYXZlIGNyZWF0ZWQgYSBxdWljayB2aXN1YWxpemF0aW9uIHRvIGhlbHAgdXMKZ2V0IGEgYmV0dGVyIHNlbnNlIG9mIHRoZSBvdXRjb21lcyB3ZSBhcmUgdHJ5aW5nIHRvIHByZWRpY3QgYW5kIHRvIHNwb3QKYW55IHBvdGVudGlhbCBpc3N1ZXMgd2UgbWlnaHQgcnVuIGludG8gZHVyaW5nIGFuYWx5c2lzLgoKQ29tcGxldGUgdGhlIGZvbGxvd2luZyBjb2RlIGNodW5rIHRvIGNyZWF0ZSBhIHNpbXBsZSBiYXIgcGxvdAppbGx1c3RyYXRpbmcgdGhlIG51bWJlciBvZiBzdHVkZW50cyB3aG8gcGFzc2VkLCBmYWlsZWQsIG9yIGhhdmUgbWlzc2luZwpkYXRhOgoKYGBge3J9CmNvdXJzZV9kYXRhIHw+CiAgZ2dwbG90KCkgKwogIGdlb21fYmFyKG1hcHBpbmcgPSBhZXMoeCA9IGF0X3Jpc2spKQpgYGAKCiMjIyMgUmVtb3ZlIENhc2VzCgpBcyB5b3UgbWF5IGhhdmUgbm90aWNlZCBmcm9tIHRoZSBvdXRwdXRzIGFib3ZlLCBzb21lIHN0dWRlbnRzIGRvIG5vdApoYXZlIGEgZmluYWwgZ3JhZGUgYW5kIHNvbWUgYXJlIG1pc3Npbmcgc3VydmV5IGRhdGEgYXMgd2VsbC4gU2luY2UgdGhlCm1vZGVscyB3ZSB3aWxsIGJlIHVzaW5nIGFyZSBub3QgZGVzaWduZWQgdG8gZGVhbCB3aXRoIG1pc3NpbmcgZGF0YSwgd2UKd2lsbCBuZWVkIHRvIHJlbW92ZSBjYXNlcyB3aXRoIG1pc3NpbmcgZGF0YS4KCkxldCdzIHVzZSB0aGUgYGRyb3BfbmEoKWAgZnVuY3Rpb24gYWxzbyBmcm9tIHRoZSB7ZHBseXJ9IHBhY2thZ2UgdG8KcmVtb3ZlIG9ic2VydmF0aW9ucyB3aXRoIG1pc3NpbmcgZGF0YSBhbmQgcmVhc3NpZ24gdG8gb3VyIGBjb3Vyc2VfZGF0YWAKb2JqZWN0OgoKYGBge3J9CmNvdXJzZV9kYXRhIDwtIGNvdXJzZV9kYXRhIHw+CiAgZHJvcF9uYSgpCgpjb3Vyc2VfZGF0YQpgYGAKCiMjIyMgWyoqWW91ciBUdXJuKipde3N0eWxlPSJjb2xvcjogZ3JlZW47In0gKiripLUqKgoKRmluYWxseSwgeW91IGhhdmUgcHJvYmFibHkgbm90aWNlZCB0aGF0IHdlIHdyb3RlIGEgbG90IG1vcmUgY29kZSB0aGFuCm5lY2Vzc2FyeSBpbiBvcmRlciB0byBpbGx1c3RyYXRlIGRpZmZlcmVudCB3cmFuZ2xpbmcgcHJvY2Vzc2VzIHRvIGdldApvdXIgZGF0YSByZWFkeSBmb3IgYW5hbHlzaXMuCgpUbyByZWR1Y2UgYWxsIHRoZSByZWR1bmRhbmN5IGNhdXNlZCBieSBkZW1vbnN0cmF0aW5nIGVhY2ggc3RlcApzZXBhcmF0ZWx5IGFib3ZlLCBjb21wbGV0ZSB0aGUgZm9sbG93aW5nIGNvZGUgYmVsb3cgYnkgdXNpbmcgdGhlIG5ldwpgfD5gIG9yIG9sZCBgJT4lYCBbcGlwZQpvcGVyYXRvcnNdKGh0dHBzOi8vbWVkaXVtLmNvbS9hbmFseXRpY3MtdmlkaHlhL2hvdy10by11c2UtdGhlLW5ldy1waXBlLWluLXItNC0xLWY3YmY3NDhhNDY1ZCkKdG8gY2hhaW4gdGhlIGBtdXRhdGUoKWAsIGBzZWxlY3QoKWAsIGFuZCBgZHJvcF9uYSgpYCBmdW5jdGlvbnMgdG9nZXRoZXIKdG8gd3JhbmdsZSBvdXIgZGF0YSBmb3IgYW5hbHlzaXMgYW5kIHdyaXRlIGEgYnJpZWYgY29tbWVudCBmb2xsb3dpbmcKZWFjaCBgI2AgdGhhdCBleHBsYWlucyB3aGF0IGVhY2ggbGluZSBvZiBjb2RlIGFjY29tcGxpc2hlcy4KCmBgYHtyfQpjb3Vyc2VfZGF0YSA8LSBwcm9jZXNzZWRfZGF0YSB8PgogIG11dGF0ZShhdF9yaXNrID0gaWZfZWxzZShmaW5hbF9ncmFkZSA+PSA2Ni43LCAibm8iLCAieWVzIiksICMgYWRkcyBhdC1yaXNrIHZhcmlhYmxlCiAgICAgICAgIGF0X3Jpc2sgPSBhc19mYWN0b3IoYXRfcmlzayksICMgY2hhbmdlcyBhdF9yaXNrIGRhdGEgdHlwZSBmcm9tIGNoYXJhY3RlciB0byBhIGZhY3RvcgogICAgICAgICBnZW5kZXIgPSBhc19mYWN0b3IoZ2VuZGVyKSkgfD4gIyBjaGFuZ2VzIGdlbmRlciBkYXRhIHR5cGUgZnJvbSBjaGFyYWN0ZXIgdG8gYSBmYWN0b3IKICBzZWxlY3Qoc3R1ZGVudF9pZCwgY291cnNlX2lkLCBhdF9yaXNrLCBnZW5kZXIsIGNvdXJzZV9pbnRlcmVzdCwKICAgICAgICAgcGVyY2VpdmVkX2NvbXBldGVuY2UsIHV0aWxpdHlfdmFsdWUpIHw+ICMgc2VsZWN0cyB0aGUgcmVsZXZhbnQgdmFyaWFibGVzIGZvciB0aGUgZGF0YSB3ZSB3YW50IHRvIHdvcmsgd2l0aAogIGRyb3BfbmEoKSAjIHJlbW92ZXMgdGhlIG9ic2VydmF0aW9ucyB3aXRoIE5BIGZvciBhbnkgdmFyaWFibGUKCmNvdXJzZV9kYXRhCmBgYAoKTm93IGluc3BlY3QgeW91ciBkYXRhIGFnYWluIGFuZCBtYWtlIHN1cmUgaXQgbG9va3MgbGlrZSBleHBlY3RlZDoKCmBgYHtyfQp2aWV3KGNvdXJzZV9kYXRhKQpgYGAKCioqSGludDoqKiBZb3Ugc2hvdWxkIHNlZSBhIHRvdGFsIG9mIDUwMyBvYnNlcnZhdGlvbnMgYW5kIDcgdmFyaWFibGVzCmluY2x1ZGluZyAxIG91dGNvbWUgdmFyaWFibGUgbmFtZWQgYGF0X3Jpc2tgLCAxIHN0dWRlbnQgaWRlbnRpZmllciBuYW1lZApgc3R1ZGVudF9pZGAuCgojIyMgYi4gRmVhdHVyZSBFbmdpbmVlcmluZwoKQXMgZGVmaW5lZCBieSBLcnVtbSwgTWVhbnMsIGFuZCBCaWVua293c2tpICgyMDE4KSBpbiAqTGVhcm5pbmcgQW5hbHl0aWNzCkdvZXMgdG8gU2Nob29sKjoKCj4gKipGZWF0dXJlIGVuZ2luZWVyaW5nKiogaXMgdGhlIHByb2Nlc3Mgb2YgY3JlYXRpbmcgbmV3IHZhcmlhYmxlcwo+IHdpdGhpbiBhIGRhdGFzZXQsIHdoaWNoIGdvZXMgYWJvdmUgYW5kIGJleW9uZCB0aGUgd29yayBvZiByZWNvZGluZyBhbmQKPiByZXNjYWxpbmcgdmFyaWFibGVzLgoKVGhlIGF1dGhvcnMgbm90ZSB0aGF0IGZlYXR1cmUgZW5naW5lZXJpbmcgZHJhd3Mgb24gc3Vic3RhbnRpdmUga25vd2xlZGdlCmZyb20gdGhlb3J5IG9yIHByYWN0aWNlLCBleHBlcmllbmNlIHdpdGggYSBwYXJ0aWN1bGFyIGRhdGEgc3lzdGVtLCBhbmQKZ2VuZXJhbCBleHBlcmllbmNlIGluIGRhdGEtaW50ZW5zaXZlIHJlc2VhcmNoLiBNb3Jlb3ZlciwgdGhlc2UgZmVhdHVyZXMKY2FuIGJlIHVzZWQgbm90IG9ubHkgaW4gbWFjaGluZSBsZWFybmluZyBtb2RlbHMsIGJ1dCBhbHNvIGluCnZpc3VhbGl6YXRpb25zIGFuZCB0YWJsZXMgY29tcHJpc2luZyBkZXNjcmlwdGl2ZSBzdGF0aXN0aWNzLgoKVGhvdWdoIG5vdCBvZnRlbiBkaXNjdXNzZWQsIGZlYXR1cmUgZW5naW5lZXJpbmcgaXMgYW4gaW1wb3J0YW50IGVsZW1lbnQKb2YgZGF0YS1pbnRlbnNpdmUgcmVzZWFyY2ggdGhhdCBjYW4gZ2VuZXJhdGUgbmV3IGluc2lnaHRzIGFuZCBpbXByb3ZlCnByZWRpY3RpdmUgbW9kZWxzLiBJbmRlZWQsIFthbiBlYXJsaWVyCmF0dGVtcHRdKGh0dHBzOi8vZGF0YXNjaWVuY2VpbmVkdWNhdGlvbi5jb20vYzE0Lmh0bWwpIHVzaW5nIHRoaXMgZGF0YQp3aXRob3V0IGZlYXR1cmUgZW5naW5lZXJpbmcgcHJlZGljdGVkIHN0dWRlbnRzJyBwYXNzaW5nIChvciBub3QgcGFzc2luZykKdGhlIGNvdXJzZSB3aXRoIG9ubHkgYXJvdW5kIDc1JSBhY2N1cmFjeS4gT25lIGdvYWwgb2YgdGhpcyBjYXNlIHN0dWR5IGlzCnRvIGltcHJvdmUgdXBvbiB0aGVzZSByZXN1bHRzIGNyZWF0aW5nIGJ5IHNvbWUgbmV3IHZhcmlhYmxlIHVzaW5nIGRhdGEKdGhhdCB3ZSB0aGluayBtYXkgaW1wcm92ZSBvdXIgbW9kZWwuCgojIyMjIFN0dWRlbnQgUGVyZm9ybWFuY2UgRmVhdHVyZXMKCkluIGFkZGl0aW9uIHRvIGluZm9ybWF0aW9uIGFib3V0IHN0dWRlbnQgZ2VuZGVyIGFuZCBtb3RpdmF0aW9uIGNvbGxlY3RlZApwcmlvciB0byB0aGUgY291cnNlLCB3ZSB3aWxsIGFsc28gaW5jb3Jwb3JhdGUgaW50byBvdXIgbW9kZWwgc3R1ZGVudApwZXJmb3JtYW5jZSBkYXRhIG9uIGNvdXJzZSBhc3NpZ25tZW50cy4gQW5kIHNpbmNlIHdlIGFyZSBpbnRlcmVzdGVkIGluCmRldmVsb3BpbmcgYSBwcmVkaWN0aXZlIG1vZGVsIHRoYXQgdGhhdCBjYW4gYmUgdXNlZCB0byBpbnRlcnZlbmUgYmVmb3JlCnN0dWRlbnRzIGFjdHVhbGx5IGZhaWwgdGhlIGNvdXJzZSwgd2Ugd2lsbCBkZXZlbG9wIG5ldyBmZWF0dXJlcyBiYXNlZCBvbgpzdHVkZW50IHBlcmZvcm1hbmNlIG9uIHRoZSBmaXJzdCAyMCBhc3NpZ25tZW50cyBjb21wbGV0ZWQgZHVyaW5nIHRoZQpmaXJzdCBoYWxmIG9mIHRoZSBjb3Vyc2UuIFNwZWNpZmljYWxseSwgd2UnbGwgY3JlYXRlIHRoZSBmb2xsb3dpbmcgdGhyZWUKImZlYXR1cmVzIiBmcm9tIHRoZSBncmFkZWJvb2sgZGF0YSB3ZSBpbXBvcnRlZCBlYXJsaWVyOgoKLSAgIFRoZSBvdmVyYWxsIHBlcmNlbnQgb2YgcG9pbnRzIGVhcm5lZCAoKmFjcm9zcyogZmlyc3QgMjAgYXNzaWdubWVudHMpCi0gICBUaGUgdmFyaWFiaWxpdHkgKGluIHN0YW5kYXJkIGRldmlhdGlvbiB1bml0cykgaW4gdGhlIHBlcmNlbnQgZWFybmVkCiAgICAoKmJldHdlZW4qIGFzc2lnbm1lbnRzKQotICAgVGhlIG51bWJlciBvZiBhc3NpZ25tZW50cyBmb3Igd2hpY2ggc3R1ZGVudHMgZWFybmVkIDEwMCUgb2YgdGhlCiAgICBwb3NzaWJsZSBwb2ludHMKCiMjIyMgR3JvdXAgJiBTbGljZSBEYXRhCgpUaGUge2RwbHlyfSBwYWNrYWdlIGhhcyBhIGhhbmR5IGBzbGljZSgpYCBmdW5jdGlvbiB0aGF0IHRoYXQgYWxsb3dzIHVzCnRvIHNlbGVjdCwgcmVtb3ZlLCBhbmQgZHVwbGljYXRlIHJvd3MgaW4gYSBkYXRhc2V0IGJhc2VkIHRoZSByb3dzIGluCndoaWNoIHdlIHNwZWNpZnkuCgpGb3IgZXhhbXBsZSwgcnVuIHRoZSBmb2xsb3dpbmcgY29kZSBjaHVuayB0byBzZWxlY3Qgcm93cyAxIHRocm91Z2ggMjAKZnJvbSBvdXIgYGdyYWRlX2Jvb2tgIGRhdGEuCgpgYGB7cn0Kc2xpY2UoZ3JhZGVfYm9vaywgMToyMCkKYGBgCgpXaGlsZSB0aGlzIGhlbHBzIHVzIHJldHJpZXZlIHRoZSBmaXJzdCAyMCBhc3NpZ25tZW50cyBmb3IgdGhlIGZpcnN0CnN0dWRlbnQsIGl0IGRvZXNuJ3QgaGVscCB1cyB3aXRoIHRoZSBodW5kcmVkcyBvZiBvdGhlciBzdHVkZW50cyBpbiBvdXIKZGF0YSBzZXQhCgpGb3J0dW5hdGVseSwgdGhlIHtkcGx5cn0gcGFja2FnZSBhbHNvIGhhcyB0aGUgZXh0cmVtZWx5IHVzZWZ1bApgZ3JvdXBfYnkoKWAgZnVuY3Rpb24gd2hpY2ggYWxsb3dzIHVzIHRvIHBlcmZvcm0gb3RoZXIge3RpZHl2ZXJzZX0KZnVuY3Rpb25zIGxpa2UgYHNsaWNlKClgLCBgbXV0YXRlKClgLCBhbmQgYHN1bW1hcml6ZSgpYCBieSBncm91cHMgdGhhdAp3ZSBzcGVjaWZ5IGFzIGFyZ3VtZW50cy4KCkZvciBleGFtcGxlLCBydW4gdGhlIGZvbGxvd2luZyBjb2RlIHRvICpncm91cCogb3VyIGRhdGEgYnkgYHN0dWRlbnRfaWRgCmFuZCBgY291cnNlX2lkYCB1c2luZyB0aGUgYGdyb3VwX2J5KClgIGZ1bmN0aW9uLCBhbmQgdGhlbiB3ZSdsbCB1c2UgdGhlCmBzbGljZSgpYCBmdW5jdGlvbiBhZ2FpbiB0byBzZWxlY3QgdGhlIGZpcnN0IDIwIGFzc2lnbm1lbnQsIGJ1dCB0aGlzCnRpbWUgZm9yIHRoZSBmaXJzdCAyMCBhc3NpZ25tZW50cyBpbiBlYWNoIGNvdXJzZSB0aGUgc3R1ZGVudCBjb21wbGV0ZWQsCmluc3RlYWQgb2YganVzdCB0aGUgZmlyc3QgMjAgYXNzaWdubWVudHMgZm9yIHRoZSBmaXJzdCBzdHVkZW50OgoKYGBge3J9CmdyYWRlX2Jvb2sgfD4gCiAgZ3JvdXBfYnkoc3R1ZGVudF9pZCwgY291cnNlX2lkKSB8PgogIHNsaWNlKDE6MjApCmBgYAoKWW91J2xsIG5vdGljZSB0aGUgZGF0YSBoYXZlIHNpZ25pZmljYW50bHkgZGlmZmVyZW50IGRpbWVuc2lvbnMgbm93LgpXZSdsbCBoYXZlIHRvIHRha2Ugc29tZSBzdGVwcyB0byBmdXJ0aGVyIHByb2Nlc3Mgb3VyIGBncmFkZV9ib29rYCBkYXRhLgpJbiBkb2luZyBzbywgd2UnbGwgZW5naW5lZXIgc29tZSBmZWF0dXJlcy4KCiMjIyMgW1lvdXIgVHVybl17c3R5bGU9ImNvbG9yOiBncmVlbjsifSDipLUKCk5vdyBsZXQncyBjcmVhdGUgYSBuZXcgYGdyYWRlX2Jvb2tgIGRhdGEgZnJhbWUgdXNpbmcgd2hhdCB3ZSBqdXN0CmxlYXJuZWQgdG86CgotICAgZ3JvdXAgb3VyIG9ic2VydmF0aW9ucyBieSBgc3R1ZGVudF9pZGAgYW5kIGBjb3Vyc2VfaWRgOwotICAgc2xpY2Ugb3VyIGRhdGEgZnJhbWUgdG8gaW5jbHVkZSBvbmx5IHRoZSBmaXJzdCAyMCBhc3NpZ21lbnRzIGZvcgogICAgZWFjaCBzdHVkZW50IGluIGVhY2ggY291cnNlOwotICAgY3JlYXRlIGEgbmV3IHZhcmlhYmxlIGNhbGxlZCBgcGVyY2VudF9lYXJuZWRgIHRoYXQgZXF1YWxzIHRoZQogICAgYHBvaW50c19lYXJuZWRgIGRpdmlkZWQgYnkgYHBvaW50c19wb3NzaWJsZWA7IGFuZCwKLSAgIHJlbW92ZSByb3dzIHdpdGggYW55IG1pc3NpbmcgZGF0YQoKQ29tcGxldGUgdGhlIGZvbGxvd2luZyBjb2RlIGNodW5rOgoKYGBge3J9CgpncmFkZV9ib29rIDwtIGdyYWRlX2Jvb2sgfD4gCiAgZ3JvdXBfYnkoc3R1ZGVudF9pZCwgY291cnNlX2lkKSB8PgogIHNsaWNlKDE6MjApIHw+CiAgbXV0YXRlKHBlcmNlbnRfZWFybmVkID0gcG9pbnRzX2Vhcm5lZC9wb2ludHNfcG9zc2libGUpIHw+CiAgZHJvcF9uYSgpCgpncmFkZV9ib29rCmBgYAoKKipOb3RlOioqIElmIHlvdSBjb21wbGV0ZWQgdGhpcyBjb3JyZWN0bHksIHlvdSBzaG91bGQgaGF2ZSBhIGRhdGEgZnJhbWUKd2l0aCAxMCw1MjIgb2JzZXJ2YXRpb25zIGFuZCA2IHZhcmlhYmxlcy4KCiMjIyMgQ3JlYXRlIE5ldyBWYXJpYWJsZXMKCkZpbmFsbHksIGxldCdzIGNyZWF0ZSB0aHJlZSBmZWF0dXJlcyBmcm9tIHRoZSBncmFkZWJvb2sgZGF0YToKCi0gICBUaGUgb3ZlcmFsbCBwZXJjZW50IG9mIHBvaW50cyBlYXJuZWQKLSAgIFRoZSB2YXJpYWJpbGl0eSAoaW4gc3RhbmRhcmQgZGV2aWF0aW9uIHVuaXRzKSBpbiB0aGUgcGVyY2VudCBlYXJuZWQKICAgICgqYmV0d2VlbiogYXNzaWdubWVudHMpCi0gICBUaGUgbnVtYmVyIG9mIGFzc2lnbm1lbnRzIGZvciB3aGljaCBzdHVkZW50cyBlYXJuZWQgMTAwJSBvZiB0aGUKICAgIHBvc3NpYmxlIHBvaW50cwoKWW91IGNhbiBwcm9iYWJseSBpbWFnaW5lIG90aGVyczsgeW91J3JlIHdlbGNvbWUgdG8gZXhwbG9yZSBhZGRpbmcgdGhvc2UsCnRvby4KCldlJ2xsIHVzZSB0aGUgYHN1bW1hcml6ZSgpYCBmdW5jdGlvbiBpbnN0ZWFkIG9mIHRoZSBgbXV0YXRlKClgIGZ1bmN0aW9uCnRvIGRvIHRoaXMsIGFuZCB0aGVuIHdlJ2xsIHNlbGVjdCBqdXN0IHRoZSB2YXJpYWJsZXMgbmVlZGVkIHRvIGpvaW4gb3VyCmRhdGEgYW5kIDoKCmBgYHtyfQpncmFkZV9ib29rIDwtIGdyYWRlX2Jvb2sgfD4KICBzdW1tYXJpemUob3ZlcmFsbF9wZXJjZW50ID0gc3VtKHBvaW50c19lYXJuZWQpIC8gc3VtKHBvaW50c19wb3NzaWJsZSksCiAgICAgICAgICAgIHZhcmlhYmlsaXR5ID0gc2QocGVyY2VudF9lYXJuZWQpLAogICAgICAgICAgICBuXzEwMCA9IHN1bShwZXJjZW50X2Vhcm5lZCA9PSAxKSkgfD4KICBzZWxlY3Qoc3R1ZGVudF9pZCwgCiAgICAgICAgIGNvdXJzZV9pZCwgCiAgICAgICAgIG92ZXJhbGxfcGVyY2VudCwgCiAgICAgICAgIHZhcmlhYmlsaXR5LCAKICAgICAgICAgbl8xMDApCgpncmFkZV9ib29rCmBgYAoKV2UgaGF2ZSBvbmUgbGFzdCBzdGVwIGJlZm9yZSB3ZSBjYW4gZ2V0IHRvIG1vZGVsaW5nIChgZ3JhZGVfYm9va2ApIC0Kam9pbmluZyB0aGlzIGRhdGEgd2l0aCBhbGwgb2YgdGhlIG90aGVyIGRhdGEgKGBjb3Vyc2VfZGF0YWApLgoKYGBge3J9CmNvdXJzZV9kYXRhIDwtIGlubmVyX2pvaW4oY291cnNlX2RhdGEsIGdyYWRlX2Jvb2spCmBgYAoKIyMjIyBbWW91ciBUdXJuXXtzdHlsZT0iY29sb3I6IGdyZWVuOyJ9IOKktQoKTGV0J3MgdGFsayBhIGxvb2sgYXQgdGhlIGpvaW5lZCBkYXRhIHRvIG1ha2Ugc3VyZSBldmVyeXRoaW5nIGlzIGxvb2tpbmcKYXMgd2UgaW50ZW5kIGl0IHRvLiBJbnNwZWN0IHRoZSBkYXRhIHVzaW5nIHRoZSBjb2RlIGNodW5rIGJlbG93IGFuZAphbnN3ZXIgdGhlIGZvbGxvd2luZyBxdWVzdGlvbnM6CgpgYGB7cn0Kdmlldyhjb3Vyc2VfZGF0YSkKCmBgYAoKMS4gIFdoeSBkb2VzIG91ciBkYXRhIGZyYW1lIG5vdyBoYXZlIDUwMyBvYnNlcnZhdGlvbnMgYW5kIDEwIHZhcmlhYmxlcz8KICAgIC0gICBUaGUgZ3JhZGVfYm9vayBkYXRhIHNldCBoYWQgNjAzIG9ic2VydmF0aW9ucyBhbmQgNSB2YXJpYWJsZXMKICAgICAgICBiZWZvcmUgam9pbmluZy4gVGhlIGNvdXJzZV9kYXRhIGRhdGEgc2V0IGhhZCA1MDMgb2JzZXJ2YXRpb25zCiAgICAgICAgYW5kIDcgdmFyaWFibGVzLiBUaGUgZGF0YSB0aGF0IHdhcyBqb2luZWQgY29tYmluZWQgdHdvIG9mIHRoZQogICAgICAgIHZhcmlhYmxlcyAoc3R1ZGVudF9pZCBhbmQgY291cnNlX2lkKSBhbmQgYWxsIG9mIHRoZSBvdGhlcgogICAgICAgIHZhcmlhYmxlcyBnb3QgdGhlaXIgb3duIGNvbHVtbnMuIFNpbmNlIGl0IGNvdWxkIG9ubHkgam9pbiA1MDMKICAgICAgICBvYnNlcnZhdGlvbnMsIHRoZSBvdGhlciAxMDAgd2VyZSBsZWZ0IG91dC4KMi4gIFdoeSBkaWQgd2UgdXNlIHRoZSBgc3VtbWFyaXplKClgIGZ1bmN0aW9uIGFib3ZlIGluc3RlYWQgb2YgdGhlCiAgICBgbXV0YXRlKClgIGZ1bmN0aW9uIHRvIGNyZWF0ZSBvdXIgbmV3IGZlYXR1cmVzPwogICAgLSAgIFdlIHVzZWQgdGhlIHN1bW1hcml6ZSBmdW5jdGlvbiBiZWNhdXNlIHdlIGFyZSBsb29raW5nIGF0IHRoZQogICAgICAgIGRhdGEgb3ZlciB0aGUgZmlyc3QgMjAgYXNzaWdubWVudHMgZm9yIGVhY2ggc3R1ZGVudCBhbmQKICAgICAgICBjb21iaW5pbmcgdGhlbSBpbnRvIG9uZSBudW1iZXIgcGVyIG5ldyB2YXJpYWJsZS4gTXV0YXRlIHdvdWxkJ3ZlCiAgICAgICAgYXBwbGllZCB0byBlYWNoIGFzc2lnbm1lbnQgaW5kaXZpZHVhbGx5IGluc3RlYWQgb2YgdGhlIGVudGlyZQogICAgICAgIGdyb3VwIG9mIGFzc2lnbm1lbnRzLgoKIyMgNC4gTU9ERUwKClJlY2FsbCBmcm9tIG91ciByZWFkaW5ncyB0aGF0IHRoZXJlIGFyZSB0d28gZ2VuZXJhbCB0eXBlcyBvZiBtb2RlbGluZwphcHByb2FjaGVzOiB1bnN1cGVydmlzZWQgYW5kIHN1cGVydmlzZWQgbWFjaGluZSBsZWFybmluZy4gSW4gUGFydCA0LCB3ZQpmb2N1c2VkIG9uIHN1cGVydmlzZWQgbGVhcm5pbmcgbW9kZWxzLCB3aGljaCBhcmUgdXNlZCB0byBxdWFudGlmeQpyZWxhdGlvbnNoaXBzIGJldHdlZW4gZmVhdHVyZXMgKGUuZy4sIG1vdGl2YXRpb24gYW5kIHBlcmZvcm1hbmNlKSBhbmQgYQprbm93biBvdXRjb21lIChlLmcuLCBwYXNzaW5nIG9yIGZhaWxpbmcgYSBjb3Vyc2UpLiBUaGVzZSBtb2RlbHMgY2FuIGJlCnVzZWQgZm9yIHN0YXRpc3RpY2FsIGluZmVyZW5jZSwgYXMgaWxsdXN0cmF0ZWQgaW4gVW5pdCAxLCBvciBwcmVkaWN0aW9uCmFzIHdlJ2xsIGlsbHVzdHJhdGUgaW4gdGhpcyBzZWN0aW9uLiBTcGVjaWZpY2FsbHksIGluIFBhcnQgNCB3ZSB3aWxsCmxlYXJuIGhvdyB0bzoKCmEuICAqKlNwbGl0IERhdGEqKiBpbnRvIGEgdHJhaW5pbmcgYW5kIHRlc3Qgc2V0IHRoYXQgd2lsbCBiZSB1c2VkIHRvCiAgICBkZXZlbG9wIGEgcHJlZGljdGl2ZSBtb2RlbC4KCmIuICAqKkNyZWF0ZSBhICJSZWNpcGUiKiogZm9yIG91ciBwcmVkaWN0aXZlIG1vZGVsIGFuZCBsZWFybiBob3cgdG8gZGVhbAogICAgd2l0aCBub21pbmFsIGRhdGEgdGhhdCB3ZSB3b3VsZCBsaWtlIHRvIHVzZSBhcyBwcmVkaWN0b3JzLgoKYy4gICoqRml0IE1vZGVscyoqIHRvIG91ciB0cmFpbmluZyBzZXQgdXNpbmcgbG9naXN0aWMgcmVncmVzc2lvbiBhbmQKICAgIHJhbmRvbSBmb3Jlc3QgbW9kZWxzLgoKZC4gICoqQ2hlY2sgTW9kZWwgQWNjdXJhY3kqKiBvbiBvdXIgdGVzdCBzZXQgdG8gc2VlIGhvdyB3ZWxsIG91ciBtb2RlbAogICAgY2FuICJwcmVkaWN0IiBvdXIgb3V0Y29tZSBvZiBpbnRlcmVzdC4KCiMjIyBhLiBTcGxpdCBEYXRhCgpUaGUgYXV0aG9ycyBvZiBEYXRhIFNjaWVuY2UgaW4gRWR1Y2F0aW9uIFVzaW5nIFIgQGVzdHJlbGxhZG8yMDIwZGF0YQpyZW1pbmQgdXMgdGhhdDoKCj4gQXQgaXRzIGNvcmUsIG1hY2hpbmUgbGVhcm5pbmcgaXMgdGhlIHByb2Nlc3Mgb2YgInNob3dpbmciIHlvdXIKPiBzdGF0aXN0aWNhbCBtb2RlbCBvbmx5IHNvbWUgb2YgdGhlIGRhdGEgYXQgb25jZSBhbmQgdHJhaW5pbmcgdGhlIG1vZGVsCj4gdG8gcHJlZGljdCBhY2N1cmF0ZWx5IG9uIHRoYXQgdHJhaW5pbmcgZGF0YXNldCAodGhpcyBpcyB0aGUgImxlYXJuaW5nIgo+IHBhcnQgb2YgbWFjaGluZSBsZWFybmluZykuIFRoZW4sIHRoZSBtb2RlbCBhcyBkZXZlbG9wZWQgb24gdGhlCj4gdHJhaW5pbmcgZGF0YSBpcyBzaG93biBuZXcgZGF0YSAtIGRhdGEgeW91IGhhZCBhbGwgYWxvbmcsIGJ1dCBoaWQgZnJvbQo+IHlvdXIgY29tcHV0ZXIgaW5pdGlhbGx5IC0gYW5kIHlvdSBzZWUgaG93IHdlbGwgdGhlIG1vZGVsIHRoYXQgeW91Cj4gZGV2ZWxvcGVkIG9uIHRoZSB0cmFpbmluZyBkYXRhIHBlcmZvcm1zIG9uIHRoaXMgbmV3IHRlc3RpbmcgZGF0YS4KPiBFdmVudHVhbGx5LCB5b3UgbWlnaHQgdXNlIHRoZSBtb2RlbCBvbiBlbnRpcmVseSBuZXcgZGF0YS4KCkl0IGlzIHRoZXJlZm9yZSBjb21tb24gd2hlbiBiZWdpbm5pbmcgYSBtb2RlbGluZyBwcm9qZWN0IHRvIFtzZXBhcmF0ZQp0aGUgZGF0YSBzZXRdKGh0dHBzOi8vYm9va2Rvd24ub3JnL21heC9GRVMvZGF0YS1zcGxpdHRpbmcuaHRtbCkgaW50byB0d28KcGFydGl0aW9uczoKCi0gICBUaGUgKnRyYWluaW5nIHNldCogaXMgdXNlZCB0byBlc3RpbWF0ZSBkZXZlbG9wIGFuZCBjb21wYXJlIG1vZGVscywKICAgIGZlYXR1cmUgZW5naW5lZXJpbmcgdGVjaG5pcXVlcywgdHVuZSBtb2RlbHMsIGV0Yy4KCi0gICBUaGUgKnRlc3Qgc2V0KiBpcyBoZWxkIGluIHJlc2VydmUgdW50aWwgdGhlIGVuZCBvZiB0aGUgcHJvamVjdCwgYXQKICAgIHdoaWNoIHBvaW50IHRoZXJlIHNob3VsZCBvbmx5IGJlIG9uZSBvciB0d28gbW9kZWxzIHVuZGVyIHNlcmlvdXMKICAgIGNvbnNpZGVyYXRpb24uIEl0IGlzIHVzZWQgYXMgYW4gdW5iaWFzZWQgc291cmNlIGZvciBtZWFzdXJpbmcgZmluYWwKICAgIG1vZGVsIHBlcmZvcm1hbmNlLgoKVGhlcmUgYXJlIGRpZmZlcmVudCB3YXlzIHRvIGNyZWF0ZSB0aGVzZSBwYXJ0aXRpb25zIG9mIHRoZSBkYXRhIGFuZAp0aGVyZSBpcyBubyB1bmlmb3JtIGd1aWRlbGluZSBmb3IgZGV0ZXJtaW5pbmcgaG93IG11Y2ggZGF0YSBzaG91bGQgYmUKc2V0IGFzaWRlIGZvciB0ZXN0aW5nLiBUaGUgcHJvcG9ydGlvbiBvZiBkYXRhIGNhbiBiZSBkcml2ZW4gYnkgbWFueQpmYWN0b3JzLCBpbmNsdWRpbmcgdGhlIHNpemUgb2YgdGhlIG9yaWdpbmFsIHBvb2wgb2Ygc2FtcGxlcyBhbmQgdGhlCnRvdGFsIG51bWJlciBvZiBwcmVkaWN0b3JzLsKgCgpBZnRlciB5b3UgZGVjaWRlIGhvdyBtdWNoIHRvIHNldCBhc2lkZSwgdGhlIG1vc3QgY29tbW9uIGFwcHJvYWNoIGZvcgphY3R1YWxseSBwYXJ0aXRpb25pbmcgeW91ciBkYXRhIGlzIHRvIHVzZSBhIHJhbmRvbSBzYW1wbGUuIEZvciBvdXIKcHVycG9zZXMsIHdlJ2xsIHVzZSByYW5kb20gc2FtcGxpbmcgdG8gc2VsZWN0IDI1JSBmb3IgdGhlIHRlc3Qgc2V0IGFuZAp1c2UgdGhlIHJlbWFpbmRlciBmb3IgdGhlIHRyYWluaW5nIHNldCwgd2hpY2ggYXJlIHRoZSBkZWZhdWx0cyBmb3IgdGhlCntbcnNhbXBsZV0oaHR0cHM6Ly90aWR5bW9kZWxzLmdpdGh1Yi5pby9yc2FtcGxlLyl9IHBhY2thZ2UuCgpBZGRpdGlvbmFsbHksIHNpbmNlIHJhbmRvbSBzYW1wbGluZyB1c2VzIHJhbmRvbSBudW1iZXJzLCBpdCBpcyBpbXBvcnRhbnQKdG8gc2V0IHRoZSByYW5kb20gbnVtYmVyIHNlZWQuIFRoaXMgZW5zdXJlcyB0aGF0IHRoZSByYW5kb20gbnVtYmVycyBjYW4KYmUgcmVwcm9kdWNlZCBhdCBhIGxhdGVyIHRpbWUgKGlmIG5lZWRlZCkuCgpUaGUgZnVuY3Rpb24gYGluaXRpYWxfc3BsaXQoKWAgZnVuY3Rpb24gZnJvbSB0aGUge3JzYW1wbGV9IHBhY2thZ2UgdGFrZXMKdGhlIG9yaWdpbmFsIGRhdGEgYW5kIHNhdmVzIHRoZSBpbmZvcm1hdGlvbiBvbiBob3cgdG8gbWFrZSB0aGUKcGFydGl0aW9ucy4KClJ1biB0aGUgZm9sbG93aW5nIGNvZGUgdG8gc2V0IHRoZSByYW5kb20gbnVtYmVyIHNlZWQgYW5kIG1ha2Ugb3VyCmluaXRpYWwgZGF0YSBzcGxpdDoKCmBgYHtyfQpzZXQuc2VlZCg1ODYpCgpjb3Vyc2Vfc3BsaXQgPC0gaW5pdGlhbF9zcGxpdChjb3Vyc2VfZGF0YSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHN0cmF0YSA9IGF0X3Jpc2spCmBgYAoKTm90ZSB0aGF0IHdlIHVzZWQgdGhlIGBzdHJhdGEgPWAgYXJndW1lbnQsIHdoaWNoIGNvbmR1Y3RzIGEgc3RyYXRpZmllZApzcGxpdC4gVGhpcyBlbnN1cmVzIHRoYXQsIGRlc3BpdGUgdGhlIGltYmFsYW5jZSB3ZSBub3RpY2VkIGluIG91cgpgYXRfcmlza2AgdmFyaWFibGUsIG91ciB0cmFpbmluZyBhbmQgdGVzdCBkYXRhIHNldHMgd2lsbCBrZWVwIHJvdWdobHkKdGhlIHNhbWUgcHJvcG9ydGlvbnMgb2YgYXQtcmlzayBzdHVkZW50cyBhcyBpbiB0aGUgb3JpZ2luYWwgZGF0YS7CoAoKIyMjIyBbKipZb3VyIFR1cm4qKl17c3R5bGU9ImNvbG9yOiBncmVlbjsifSAqKuKktSoqCgpUeXBlIGBjb3Vyc2Vfc3BsaXRgIGludG8gdGhlIGNvZGUgY2h1bmsgYmVsb3csIHJ1biwgYW5kIGFuc3dlciB0aGUKcXVlc3Rpb24gdGhhdCBmb2xsb3dzPwoKYGBge3J9CmNvdXJzZV9zcGxpdAoKYGBgCgoxLiAgSG93IG1hbnkgb2JzZXJ2YXRpb25zIHNob3VsZCB3ZSBleHBlY3QgdG8gc2VlIGluIG91ciB0cmFpbmluZyBhbmQKICAgIHRlc3Qgc2V0cyByZXNwZWN0aXZlbHk/CgogICAgLSAgIFdlIHNob3VsZCBleHBlY3QgdG8gc2VlIDM3NiBvYnNlcnZhdGlvbnMgaW4gdGhlIHRyYWluaW5nIHNldCBhbmQKICAgICAgICAxMjcgb2JzZXJ2YXRpb25zIGluIHRoZSB0ZXN0IHNldC4KCiMjIyMgQ3JlYXRlIGEgdHJhaW5pbmcgYW5kIHRlc3Qgc2V0CgpUaGUge3JzYW1wbGV9IHBhY2thZ2UgaGFzIHR3byBhcHRseSBuYW1lZCBmdW5jdGlvbnMgZm9yIGNyZWF0aW5nIGEKdHJhaW5pbmcgYW5kIHRlc3RpbmcgZGF0YSBzZXQgY2FsbGVkIGB0cmFpbmluZygpYCBhbmQgYHRlc3RpbmcoKWAKcmVzcGVjdGl2ZWx5LgoKUnVuIHRoZSBmb2xsb3dpbmcgY29kZSB0byBzcGxpdCB0aGUgZGF0YSBpbnRvIG91ciB0cmFpbmluZyBhbmQgdGVzdCBkYXRhCnNldHM6CgpgYGB7cn0KdHJhaW5fZGF0YSA8LSB0cmFpbmluZyhjb3Vyc2Vfc3BsaXQpCgp0ZXN0X2RhdGEgIDwtIHRlc3RpbmcoY291cnNlX3NwbGl0KQpgYGAKCiMjIyMgWyoqWW91ciBUdXJuKipde3N0eWxlPSJjb2xvcjogZ3JlZW47In0gKiripLUqKgoKTm93IHRha2UgYSBsb29rIGF0IHRoZSB0cmFpbmluZyBhbmQgdGVzdGluZyBzZXRzIHdlIGp1c3QgY3JlYXRlZCBieQp0eXBpbmcgdGhlaXIgbmFtZXMgaW50byB0aGUgY29kZSBjaHVuazoKCmBgYHtyfQp0cmFpbl9kYXRhCgp0ZXN0X2RhdGEKCmBgYAoKTmV4dCwgcmVjeWNsZSB0aGUgY29kZSBmcm9tIGFib3ZlIHRvIGNoZWNrIHRvIHNlZSB0aGF0IHRoZSBwcm9wb3J0aW9uIG9mCmF0LXJpc2sgc3R1ZGVudHMgaW4gb3VyIHRyYWluaW5nIGFuZCB0ZXN0IGRhdGEgYXJlIGNsb3NlIHRvIHRob3NlIGluIG91cgpvdmVyYWxsIGBjb3Vyc2VfZGF0YWA6CgpgYGB7cn0KdHJhaW5fZGF0YSB8PgogIGNvdW50KGF0X3Jpc2spIHw+CiAgbXV0YXRlKHByb3BvcnRpb24gPSBuL3N1bShuKSkKCnRlc3RfZGF0YSB8PgogIGNvdW50KGF0X3Jpc2spIHw+CiAgbXV0YXRlKHByb3BvcnRpb24gPSBuL3N1bShuKSkKYGBgCgpOb3cgYW5zd2VyIHRoZSBmb2xsb3dpbmcgcXVlc3Rpb25zOgoKMS4gIERvIHRoZSBudW1iZXIgb2Ygb2JzZXJ2YXRpb25zIGluIGVhY2ggc2V0IG1hdGNoIHlvdXIgZXhwZWN0YXRpb25zPwogICAgV2h5PwoKICAgIC0gICBZZXMuIFRoZSB0cmFpbmluZyBkYXRhIGhhcyAzNzYgb2JzZXJ2YXRpb25zIGFuZCB0aGUgdGVzdCBkYXRhCiAgICAgICAgaGFzIDEyNyBvYnNlcnZhdGlvbnMgd2hpY2ggbWF0Y2hlcyBleGFjdGx5IHdpdGggdGhlIHNwbGl0IGRvbmUKICAgICAgICBlYXJsaWVyLgoKMi4gIERvIHRoZSBwcm9wb3J0aW9uIG9mIGF0LXJpc2sgc3R1ZGVudHMgaW4gZWFjaCBzZXQgbWF0Y2ggeW91cgogICAgZXhwZWN0YXRpb25zPyBXaHk/CgogICAgLSAgIFllcy4gVGhlIHByb3BvcnRpb25zIGFyZSBjbG9zZSB0byB3aGF0IHRoZXkgd2VyZSBpbiB0aGUgb3JpZ2luYWwKICAgICAgICBkYXRhIHNldCAoYWJvdXQgMjAlIGF0LXJpc2sgYW5kIDgwJSBub3QgYXQtcmlzaykuIFRoZQogICAgICAgIHByb3BvcnRpb25zIGluIHRoZSBvcmlnaW5hbCBkYXRhIHNldCB3ZXJlIGxvd2VyLCBidXQgdGhhdCBhbHNvCiAgICAgICAgaW5jbHVkZWQgdGhlIE4vQSB2YWx1ZXMgd2hlcmVhcyB0aGlzIGRhdGEgZG9lcyBub3QuCgojIyMgYi4gKipDcmVhdGUgYSBSZWNpcGUqKgoKSW4gdGhpcyBzZWN0aW9uLCB3ZSBpbnRyb2R1Y2UgYW5vdGhlciB0aWR5bW9kZWxzIHBhY2thZ2UsCltyZWNpcGVzXShodHRwczovL3JlY2lwZXMudGlkeW1vZGVscy5vcmcvKSwgd2hpY2ggaXMgZGVzaWduZWQgdG8gaGVscAp5b3UgcHJlcGFyZSB5b3VyIGRhdGEgKmJlZm9yZSogdHJhaW5pbmcgeW91ciBtb2RlbC4gUmVjaXBlcyBhcmUgYnVpbHQgYXMKYSBzZXJpZXMgb2YgcHJlcHJvY2Vzc2luZyBzdGVwcywgc3VjaCBhczoKCi0gICBjb252ZXJ0aW5nIHF1YWxpdGF0aXZlIHByZWRpY3RvcnMgdG8gaW5kaWNhdG9yIHZhcmlhYmxlcyAoYWxzbyBrbm93bgogICAgYXMgZHVtbXkgdmFyaWFibGVzKSwKCi0gICB0cmFuc2Zvcm1pbmcgZGF0YSB0byBiZSBvbiBhIGRpZmZlcmVudCBzY2FsZSAoZS5nLiwgdGFraW5nIHRoZQogICAgbG9nYXJpdGhtIG9mIGEgdmFyaWFibGUpLAoKLSAgIHRyYW5zZm9ybWluZyB3aG9sZSBncm91cHMgb2YgcHJlZGljdG9ycyB0b2dldGhlciwKCi0gICBleHRyYWN0aW5nIGtleSBmZWF0dXJlcyBmcm9tIHJhdyB2YXJpYWJsZXMgKGUuZy4sIGdldHRpbmcgdGhlIGRheSBvZgogICAgdGhlIHdlZWsgb3V0IG9mIGEgZGF0ZSB2YXJpYWJsZSksCgphbmQgc28gb24uIElmIHlvdSBhcmUgZmFtaWxpYXIgd2l0aCBSJ3MgZm9ybXVsYSBpbnRlcmZhY2UsIGEgbG90IG9mIHRoaXMKbWlnaHQgc291bmQgZmFtaWxpYXIgYW5kIGxpa2Ugd2hhdCBhIGZvcm11bGEgYWxyZWFkeSBkb2VzLiBSZWNpcGVzIGNhbgpiZSB1c2VkIHRvIGRvIG1hbnkgb2YgdGhlIHNhbWUgdGhpbmdzLCBidXQgdGhleSBoYXZlIGEgbXVjaCB3aWRlciByYW5nZQpvZiBwb3NzaWJpbGl0aWVzLgoKIyMjIyBBZGQgYSBmb3JtdWxhCgpUbyBnZXQgc3RhcnRlZCwgbGV0J3MgY3JlYXRlIGEgcmVjaXBlIGZvciBhIHNpbXBsZSBsb2dpc3RpYyByZWdyZXNzaW9uCm1vZGVsLiBCZWZvcmUgdHJhaW5pbmcgdGhlIG1vZGVsLCB3ZSBjYW4gdXNlIGEgcmVjaXBlIHRvIGFkZCBhIGZldwpwcmVkaWN0b3JzIGFuZCBjb25kdWN0IHNvbWUgcHJlcHJvY2Vzc2luZyByZXF1aXJlZCBieSB0aGUgbW9kZWwuCgpUaGXCoFtgcmVjaXBlKClgwqBmdW5jdGlvbl0oaHR0cHM6Ly9yZWNpcGVzLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS9yZWNpcGUuaHRtbCnCoGFzCndlIHVzZWQgaXQgaGVyZSBoYXMgdHdvIGFyZ3VtZW50czoKCi0gICBBwqAqKmZvcm11bGEqKi4gQW55IHZhcmlhYmxlIG9uIHRoZSBsZWZ0LWhhbmQgc2lkZSBvZiB0aGUgdGlsZGUgKGB+YCkKICAgIGlzIGNvbnNpZGVyZWQgdGhlIG1vZGVsIG91dGNvbWUgKGBhdF9yaXNrYCBpbiBvdXIgY2FzZSkuIE9uIHRoZQogICAgcmlnaHQtaGFuZCBzaWRlIG9mIHRoZSB0aWxkZSBhcmUgdGhlIHByZWRpY3RvcnMuIFZhcmlhYmxlcyBtYXkgYmUKICAgIGxpc3RlZCBieSBuYW1lLCBvciB5b3UgY2FuIHVzZSB0aGUgZG90IChgLmApIHRvIGluZGljYXRlIGFsbCBvdGhlcgogICAgdmFyaWFibGVzIGFzIHByZWRpY3RvcnMuCgotICAgVGhlICoqZGF0YSoqLiBBIHJlY2lwZSBpcyBhc3NvY2lhdGVkIHdpdGggdGhlIGRhdGEgc2V0IHVzZWQgdG8KICAgIGNyZWF0ZSB0aGUgbW9kZWwuIFRoaXMgd2lsbCB0eXBpY2FsbHkgYmUgdGhlwqAqdHJhaW5pbmcqwqBzZXQsCiAgICBzb8KgYGRhdGEgPSB0cmFpbl9kYXRhYMKgaGVyZS4gTmFtaW5nIGEgZGF0YSBzZXQgZG9lc24ndCBhY3R1YWxseQogICAgY2hhbmdlIHRoZSBkYXRhIGl0c2VsZjsgaXQgaXMgb25seSB1c2VkIHRvIGNhdGFsb2cgdGhlIG5hbWVzIG9mIHRoZQogICAgdmFyaWFibGVzIGFuZCB0aGVpciB0eXBlcywgbGlrZSBmYWN0b3JzLCBpbnRlZ2VycywgZGF0ZXMsIGV0Yy4KCkxldCdzIGNyZWF0ZSBvdXIgdmVyeSBmaXJzdCByZWNpcGUgdXNpbmcgYGF0X3Jpc2tgIGFzIG91ciBvdXRjb21lCnZhcmlhYmxlOyBgY291cnNlX2ludGVyZXN0YCBhbmQgYGdlbmRlcmAgYW5kIGBvdmVyYWxsX3BlcmNlbnRgIGFzCnByZWRpY3RvcnM7IGFuZCBgdHJhaW5fZGF0YWAgYXMgb3VyIGRhdGEgdG8gdHJhaW46CgpgYGB7cn0KbHJfcmVjaXBlXzEgPC0gcmVjaXBlKGF0X3Jpc2sgfiBjb3Vyc2VfaW50ZXJlc3QgKyBnZW5kZXIgKyBvdmVyYWxsX3BlcmNlbnQsCiAgICAgICAgICAgICAgICAgICAgICBkYXRhID0gdHJhaW5fZGF0YSkKYGBgCgpOb3cgbGV0J3MgdGFrZSBhIHF1aWNrIHBlZWsgYXQgb3VyIHJlY2lwZSBhbmQgY3JlYXRlIGEgcXVpY2sgc3VtbWFyeSBvZgpvdXIgcmVjaXBlIHVzaW5nIHRoZSBgc3VtbWFyeSgpYCBmdW5jdGlvbjoKCmBgYHtyfQpscl9yZWNpcGVfMQoKc3VtbWFyeShscl9yZWNpcGVfMSkKYGBgCgpZb3UgY2FuIHNlZSB0aGF0IG91ciByZWNpcGUgaGFzIGZvdXIgaW5ncmVkaWVudHMgaW5jbHVkaW5nIHRocmVlCnByZWRpY3RvcnMgYW5kIDEgb3V0Y29tZSwganVzdCBhcyBleHBlY3RlZC4KCiMjIyMgQ3JlYXRlIER1bW15IFZhcmlhYmxlcwoKQmVjYXVzZSB3ZSdsbCBiZSB1c2luZyBhIHNpbXBsZSBsb2dpc3RpYyByZWdyZXNzaW9uIG1vZGVsLCB2YXJpYWJsZXMKbGlrZSBgZ2VuZGVyYCB3aWxsIG5lZWQgdG8gYmUgY29kZWQgYXMgW2R1bW15CnZhcmlhYmxlc10oaHR0cHM6Ly9ib29rZG93bi5vcmcvbWF4L0ZFUy9jcmVhdGluZy1kdW1teS12YXJpYWJsZXMtZm9yLXVub3JkZXJlZC1jYXRlZ29yaWVzLmh0bWwpLgpEdW1teSBjb2RpbmcgbWVhbnMgdHJhbnNmb3JtaW5nIGEgdmFyaWFibGUgd2l0aCBtdWx0aXBsZSBjYXRlZ29yaWVzIGludG8KbmV3IHZhcmlhYmxlcywgd2hlcmUgZWFjaCBiaW5hcnkgdmFyaWFibGUgaW5kaWNhdGVzIHRoZSBwcmVzZW5jZSBhbmQKYWJzZW5jZSBvZiBlYWNoIGNhdGVnb3J5LiBGb3IgZXhhbXBsZSwgZ2VuZGVyIHdpbGwgYmUgcmVjb2RlZCB0bwpnZW5kZXJfZiwgd2hlcmUgMSBpbmRpY2F0ZXMgZmVtYWxlIGFuZCAwIGluZGljYXRlcyBtYWxlLgoKVW5saWtlIHRoZSBzdGFuZGFyZCBtb2RlbCBmb3JtdWxhIG1ldGhvZHMgaW4gUiwgYSByZWNpcGUgKipkb2VzIG5vdCoqCmF1dG9tYXRpY2FsbHkgY3JlYXRlIHRoZXNlIGR1bW15IHZhcmlhYmxlcyBmb3IgeW91OyB5b3UnbGwgbmVlZCB0byB0ZWxsCnlvdXIgcmVjaXBlIHRvIGFkZCB0aGlzIHN0ZXAuIFRoaXMgaXMgZm9yIHR3byByZWFzb25zLiBGaXJzdCwgbWFueQptb2RlbHMgZG8gbm90IHJlcXVpcmXCoFtudW1lcmljCnByZWRpY3RvcnNdKGh0dHBzOi8vYm9va2Rvd24ub3JnL21heC9GRVMvY2F0ZWdvcmljYWwtdHJlZXMuaHRtbCksIHNvCmR1bW15IHZhcmlhYmxlcyBtYXkgbm90IGFsd2F5cyBiZSBwcmVmZXJyZWQuIFNlY29uZCwgcmVjaXBlcyBjYW4gYWxzbyBiZQp1c2VkIGZvciBwdXJwb3NlcyBvdXRzaWRlIG9mIG1vZGVsaW5nLCB3aGVyZSBub24tZHVtbXkgdmVyc2lvbnMgb2YgdGhlCnZhcmlhYmxlcyBtYXkgd29yayBiZXR0ZXIuIEZvciBleGFtcGxlLCB5b3UgbWF5IHdhbnQgdG8gbWFrZSBhIHRhYmxlIG9yCmEgcGxvdCB3aXRoIGEgdmFyaWFibGUgYXMgYSBzaW5nbGUgZmFjdG9yLgoKRm9yIHRoZXNlIHJlYXNvbnMsIHdlIG5lZWQgdG8gZXhwbGljaXRseSB0ZWxsIHJlY2lwZXMgdG8gY3JlYXRlIGR1bW15CnZhcmlhYmxlcyB1c2luZ8KgYHN0ZXBfZHVtbXkoKWAuIExldCdzIGFkZCB0aGlzIHRvIG91ciByZWNpcGUgYW5kIGluY2x1ZGUKYGFsbF9ub21pbmFsX3ByZWRpY3RvcnMoKWAgdG8gdGVsbCBvdXIgcmVjaXBlIHRvIGNoYW5nZSBhbGwgb2Ygb3VyCmZhY3RvciB2YXJpYWJsZXMgdG8gZHVtbXkgdmFyaWFibGVzOgoKYGBge3J9CmxyX3JlY2lwZV8xIDwtIAogIHJlY2lwZShhdF9yaXNrIH4gY291cnNlX2ludGVyZXN0ICsgZ2VuZGVyICsgb3ZlcmFsbF9wZXJjZW50LAogICAgICAgICBkYXRhID0gdHJhaW5fZGF0YSkgfD4KICBzdGVwX2R1bW15KGFsbF9ub21pbmFsX3ByZWRpY3RvcnMoKSkKCmxyX3JlY2lwZV8xCgpzdW1tYXJ5KGxyX3JlY2lwZV8xKQpgYGAKCiMjIyMgQ3JlYXRlIGEga2l0Y2hlbiBzaW5rIHJlY2lwZQoKQmVmb3JlIHRyYWluaW5nIG91ciBtb2RlbCwgbGV0J3MgY3JlYXRlIGEgc2Vjb25kIHJlY2lwZSBqdXN0IGZvcgpjb250cmFzdCB0aGF0IGluY2x1ZGVzIGFsbCBvZiBvdXIgcHJlZGljdG9ycy4KClJ1biB0aGUgZm9sbG93aW5nIGNvZGUgdG8gYWRkIGFsbCBvdXIgcHJlZGljdG9ycyB0byBvdXIgbmV3IHJlY2lwZToKCmBgYHtyfQpscl9yZWNpcGVfMiA8LSAKICByZWNpcGUoYXRfcmlzayB+IGNvdXJzZV9pbnRlcmVzdCArIGdlbmRlciArIG92ZXJhbGxfcGVyY2VudCArIAogICAgICAgICAgIHBlcmNlaXZlZF9jb21wZXRlbmNlICsgdXRpbGl0eV92YWx1ZSArIHZhcmlhYmlsaXR5ICsgbl8xMDAsIAogICAgICAgICBkYXRhID0gdHJhaW5fZGF0YSkgfD4KICBzdGVwX2R1bW15KGFsbF9ub21pbmFsX3ByZWRpY3RvcnMoKSkKCmxyX3JlY2lwZV8yCgpzdW1tYXJ5KGxyX3JlY2lwZV8yKQpgYGAKCiMjIyBjLiBGaXQgYSBMb2dpc3RpYyBSZWdyZXNzaW9uIE1vZGVsCgpXaXRoIHRpZHltb2RlbHMsIHdlIHN0YXJ0IGJ1aWxkaW5nIGEgbW9kZWwgYnkgc3BlY2lmeWluZyB0aGUgKmZ1bmN0aW9uYWwKZm9ybSogb2YgdGhlIG1vZGVsIHRoYXQgd2Ugd2FudCB1c2luZyB0aGUgW1twYXJzbmlwXQpwYWNrYWdlXShodHRwczovL3RpZHltb2RlbHMuZ2l0aHViLmlvL3BhcnNuaXAvKS4gU2luY2Ugb3VyIG91dGNvbWUgaXMKYmluYXJ5LCB0aGUgbW9kZWwgdHlwZSB3ZSB3aWxsIHVzZSBpc8KgIltsb2dpc3RpYwpyZWdyZXNzaW9uXShodHRwczovL3BhcnNuaXAudGlkeW1vZGVscy5vcmcvcmVmZXJlbmNlL2xvZ2lzdGljX3JlZy5odG1sKSIuCldlIGNhbiBkZWNsYXJlIHRoaXMgd2l0aCBgbG9naXN0aWNfcmVnKClgIGFuZCBhc3NpZ24gdG8gYW4gb2JqZWN0IHdlCndpbGwgbGF0ZXIgdXNlIGluIG91ciB3b3JrZmxvdzoKCmBgYHtyfQpscl9tb2QgPC0gbG9naXN0aWNfcmVnKCkKYGBgCgpUaGF0IGlzIHByZXR0eSB1bmRlcndoZWxtaW5nIHNpbmNlLCBvbiBpdHMgb3duLCBpdCBkb2Vzbid0IHJlYWxseSBkbwptdWNoLiBIb3dldmVyLCBub3cgdGhhdCB0aGUgdHlwZSBvZiBtb2RlbCBoYXMgYmVlbiBzcGVjaWZpZWQsIGEgbWV0aG9kCmZvcsKgKmZpdHRpbmcqwqBvciB0cmFpbmluZyB0aGUgbW9kZWwgY2FuIGJlIHN0YXRlZCB1c2luZyB0aGXCoCoqZW5naW5lKiouCgojIyMjIFN0YXJ0IHlvdXIgZW5naW5lCgpUaGUgZW5naW5lIHZhbHVlIGlzIG9mdGVuIGEgbWFzaC11cCBvZiBkaWZmZXJlbnQgcGFja2FnZXMgdGhhdCBjYW4gYmUKdXNlZCB0byBmaXQgb3IgdHJhaW4gdGhlIG1vZGVsIGFzIHdlbGwgYXMgdGhlIGVzdGltYXRpb24gbWV0aG9kLiBGb3IKZXhhbXBsZSwgd2Ugd2lsbCB1c2UgImdsbSIgYSBnZW5lcmFsaXplZCBsaW5lYXIgbW9kZWwgZm9yIGJpbmFyeQpvdXRjb21lcyBhbmQgZGVmYXVsdCBmb3IgbG9naXN0aWMgcmVncmVzc2lvbiBpbiB0aGUge3BhcnNuaXB9IHBhY2thZ2UuCgpSdW4gdGhlIGZvbGxvd2luZyBjb2RlIHRvIGZpbmlzaCBzcGVjaWZ5aW5nIG91ciBtb2RlbDoKCmBgYHtyfQpscl9tb2QgPC0gCiAgbG9naXN0aWNfcmVnKCkgJT4lIAogIHNldF9lbmdpbmUoImdsbSIpCmBgYAoKIyMjIyBBZGQgdG8gd29ya2Zsb3cKCldlIHdpbGwgd2FudCB0byB1c2Ugb3VyIHJlY2lwZXMgY3JlYXRlZCBlYXJsaWVyIGFjcm9zcyBzZXZlcmFsIHN0ZXBzIGFzCndlIHRyYWluIGFuZCB0ZXN0IG91ciBtb2RlbC4gVG8gc2ltcGxpZnkgdGhpcyBwcm9jZXNzLCB3ZSBjYW4gdXNlCmHCoCptb2RlbCB3b3JrZmxvdyosIHdoaWNoIHBhaXJzIGEgbW9kZWwgYW5kIHJlY2lwZSB0b2dldGhlci4KClRoaXMgaXMgYSBzdHJhaWdodGZvcndhcmQgYXBwcm9hY2ggYmVjYXVzZSBkaWZmZXJlbnQgcmVjaXBlcyBhcmUgb2Z0ZW4KbmVlZGVkIGZvciBkaWZmZXJlbnQgbW9kZWxzLCBzbyB3aGVuIGEgbW9kZWwgYW5kIHJlY2lwZSBhcmUgYnVuZGxlZCwgaXQKYmVjb21lcyBlYXNpZXIgdG8gdHJhaW4gYW5kIHRlc3TCoCp3b3JrZmxvd3MqLgoKV2UnbGwgdXNlIHRoZcKge1t3b3JrZmxvd3NdKGh0dHBzOi8vd29ya2Zsb3dzLnRpZHltb2RlbHMub3JnLyl9IHBhY2thZ2UKZnJvbSB0aWR5bW9kZWxzIHRvIGJ1bmRsZSBvdXIgcGFyc25pcCBtb2RlbCAoYGxyX21vZGApIHdpdGggb3VyIGZpcnN0CnJlY2lwZSAoYGxyX3JlY2lwZV8xYCkuCgpgYGB7cn0KbHJfd29ya2Zsb3cgPC0gCiAgd29ya2Zsb3coKSAlPiUgCiAgYWRkX21vZGVsKGxyX21vZCkgJT4lIAogIGFkZF9yZWNpcGUobHJfcmVjaXBlXzEpCmBgYAoKIyMjIyBGaXQgbW9kZWwgdG8gdHJhaW5pbmcgc2V0CgpOb3cgdGhhdCB3ZSBoYXZlIGEgc2luZ2xlIHdvcmtmbG93IHRoYXQgY2FuIGJlIHVzZWQgdG8gcHJlcGFyZSB0aGUKcmVjaXBlIGFuZCB0cmFpbiB0aGUgbW9kZWwgZnJvbSB0aGUgcmVzdWx0aW5nIHByZWRpY3RvcnMsIHdlIGNhbiB1c2UgdGhlCmBmaXQoKWAgZnVuY3Rpb24gdG8gZml0IG91ciBtb2RlbCB0byBvdXIgYHRyYWluX2RhdGFgLiBBbmQgYWdhaW4sIHdlIHNldAphIHJhbmRvbSBudW1iZXIgc2VlZCB0byBlbnN1cmUgdGhhdCBpZiB3ZSBydW4gdGhpcyBzYW1lIGNvZGUgYWdhaW4sIHdlCndpbGwgZ2V0IHRoZSBzYW1lIHJlc3VsdHMgaW4gdGVybXMgb2YgdGhlIGRhdGEgcGFydGl0aW9uOgoKYGBge3J9CnNldC5zZWVkKDU4NikKCmxyX2ZpdCA8LSAKICBscl93b3JrZmxvdyAlPiUgCiAgZml0KGRhdGEgPSB0cmFpbl9kYXRhKQpgYGAKClRoaXMgYGxyX2ZpdGAgb2JqZWN0IGhhcyB0aGUgZmluYWxpemVkIHJlY2lwZSBhbmQgZml0dGVkIG1vZGVsIG9iamVjdHMKaW5zaWRlLiBUbyBleHRyYWN0IHRoZSBtb2RlbCBmaXQgZnJvbSB0aGUgd29ya2Zsb3csIHdlIHdpbGwgdXNlIHRoZQpoZWxwZXIgZnVuY3Rpb25zwqBgZXh0cmFjdF9maXRfcGFyc25pcCgpYC4gSGVyZSB3ZSBwdWxsIHRoZSBmaXR0ZWQgbW9kZWwKb2JqZWN0IHRoZW4gdXNlIHRoZcKgYGJyb29tOjp0aWR5KClgwqBmdW5jdGlvbiB0byBnZXQgYSB0aWR5IHRpYmJsZSBvZgptb2RlbCBjb2VmZmljaWVudHM6CgpgYGB7cn0KbHJfZml0ICU+JSAKICBleHRyYWN0X2ZpdF9wYXJzbmlwKCkgJT4lIAogIHRpZHkoKQpgYGAKCkFtb25nIHRoZSBwcmVkaWN0b3JzIGluY2x1ZGVkIGluIG91ciBtb2RlbCwgdGhlcmUgYXJlIG5vIHN0YXRpc3RpY2FsbHkKc2lnbmlmaWNhbnQgKGkuZS4sIHAtdmFsdWUgXDwgMC4wNSkgcmVsYXRpb25zaGlwcyBiZXR3ZWVuIG91ciBwcmVkaWN0b3JzCmFuZCBiZWluZyBpZGVudGlmaWVkIGFzIGF0LXJpc2sgYWNjb3JkaW5nIHRvIG91ciBkZWZpbml0aW9uLCB3aGljaApkb2Vzbid0IGJvZGUgdG9vIHdlbGwgZm9yIG91ciBtb2RlbCBidXQgbGV0J3MgcHJvY2VlZCB3aXRoIHRlc3RpbmcKYW55d2F5cy4KCiMjIyMgVGVzdCB0aGUgbW9kZWwKCk5vdyB0aGF0IHdlJ3ZlIGZpdCBvdXIgbW9kZWwgdG8gb3VyIHRyYWluaW5nIGRhdGEsIHdlJ3JlIEZJTkFMTFkgcmVhZHkKdG8gdGVzdCBvdXIgbW9kZWwgb24gdGhlIGRhdGEgd2Ugc2V0IGFzaWRlIGluIHRoZSBiZWdpbm5pbmcuIEp1c3QgdG8KcmVjYXAgdGhlIHN0ZXBzIHRoYXQgbGVkIHRvIHRoaXMgbW9tZW50LCBob3dldmVyLCByZWNhbGwgdGhhdCB3ZToKCjEuICBpZGVudGlmaWVkIHRoZSBtb2RlbCB0byB1c2UgKGBscl9tb2RgKTsKCjIuICBjcmVhdGVkIGEgcHJlcHJvY2Vzc2luZyByZWNpcGUgY29uc2lzdGluZyBvciBwcmVkaWN0b3JzIGFuZCBvdXRjb21lCiAgICAoYGxyX3JlY2lwZV8xYCk7CgozLiAgYnVuZGxlZCB0aGUgbW9kZWwgYW5kIHJlY2lwZSBpbnRvIGEgd29ya2Zsb3cgKGBscl93b3JrZmxvd2ApOyBhbmQKCjQuICB0cmFpbmVkIG91ciB3b3JrZmxvdyB1c2luZyBhIHNpbmdsZSBjYWxsIHRvwqBgZml0KClgLgoKVGhlIG5leHQgc3RlcCBpcyB0byB1c2UgdGhlIHRyYWluZWQgd29ya2Zsb3cgKGBscl9maXRgKSB0byBwcmVkaWN0Cm91dGNvbWVzIGZvciBvdXIgdW5zZWVuIHRlc3QgZGF0YSwgd2hpY2ggd2Ugd2lsbCBkbyB3aXRoIHRoZQpmdW5jdGlvbsKgYHByZWRpY3QoKWAuIFRoZcKgYHByZWRpY3QoKWDCoG1ldGhvZCBhcHBsaWVzIHRoZSByZWNpcGUgdG8gdGhlCm5ldyBkYXRhLCB0aGVuIHBhc3NlcyB0aGVtIHRvIHRoZSBmaXR0ZWQgbW9kZWwuCgpgYGB7cn0KCnByZWRpY3QobHJfZml0LCB0ZXN0X2RhdGEpCmBgYAoKQmVjYXVzZSBvdXIgb3V0Y29tZSB2YXJpYWJsZSBgYXRfcmlza2AgaGVyZSBpcyBhIGZhY3RvciwgdGhlIG91dHB1dApmcm9twqBgcHJlZGljdCgpYMKgcmV0dXJucyB0aGUgcHJlZGljdGVkIGNsYXNzOsKgYG5vYMKgdmVyc3VzwqBgeWVzYC4gTm90IGEKc3VwZXIgdXNlZnVsIG91dHB1dCB0byBiZSBob25lc3QgdGhvdWdoLgoKRm9ydHVuYXRlbHkgdGhlcmUgaXMgYW4gYGF1Z21lbnQoKWAgZnVuY3Rpb24gd2UgY2FuIHVzZSB3aXRoIG91cgpgbHJfZml0YCBtb2RlbCBhbmQgYHRlc3RfZGF0YWAgdG8gc2F2ZSB0aGVtIHRvZ2V0aGVyOgoKTGV0J3MgdXNlIHRoaXMgZnVuY3Rpb24sIHNhdmUgYXMgYGxyX3ByZWRpY3Rpb25zYDoKCmBgYHtyfQpscl9wcmVkaWN0aW9ucyA8LSBhdWdtZW50KGxyX2ZpdCwgdGVzdF9kYXRhKQpgYGAKCiMjIyMgWyoqWW91ciBUdXJuKipde3N0eWxlPSJjb2xvcjogZ3JlZW47In0gKiripLUqKgoKVGFrZSBhIHF1aWNrIGxvb2sgYXQgYGxyX3ByZWRpY3Rpb25zYCBpbiB0aGUgY29kZSBjaHVuayBiZWxvdyBhbmQgYW5zd2VyCnRoZSBxdWVzdGlvbiB0aGF0IGZvbGxvd3M/CgpgYGB7cn0Kdmlldyhscl9wcmVkaWN0aW9ucykKCmBgYAoKV2FzIG91ciBtb2RlbCBzdWNjZXNzZnVsIGF0IHByZWRpY3RpbmcgYW55IHN0dWRlbnRzIHdobyB3ZXJlIGF0LXJpc2s/CkhvdyBkbyB5b3Uga25vdz8KCi0gICBObyBpdCB3YXNuJ3QuIFRoZSBtb2RlbCBwcmVkaWN0ZWQgdGhhdCBhbGwgb2YgdGhlIHN0dWRlbnRzIGluIHRoZQogICAgdGVzdCBkYXRhIHdlcmUgbm90IGF0LXJpc2sgZXZlbiB0aG91Z2ggYWJvdXQgMjAlIG9mIHRoZW0gd2VyZQogICAgYXQtcmlzay4KCioqSGludDoqKiBTY3JvbGwgdG8gdGhlIGVuZCBvZiB0aGUgZGF0YSBmcmFtZSBhbmQgdGFrZSBhIGxvb2sgYXQgb3VyCm9yaWdpbmFsIGBhdF9yaXNrYCBvdXRjb21lIGFuZCB0aGUgYC5wcmVkX2NsYXNzYCB2YXJpYWJsZSB3aGljaCBzaG93cwp0aGUgcHJlZGljdGVkIG91dGNvbWVzLgoKIyMjIGQuIENoZWNrIG1vZGVsIGFjY3VyYWN5CgpBcyB5b3UgcHJvYmFibHkgbm90aWNlZCwganVzdCBsb29raW5nIGF0IHRoZSBgbHJfcHJlZGljdGlvbnNgIG9iamVjdCBpcwpub3QgdGhlIGVhc2llc3Qgd2F5IHRvIGNoZWNrIGZvciBtb2RlbCBhY2N1cmFjeS4gRm9ydHVuYXRlbHksIHRoZQp7W3lhcmRzdGlja31dKGh0dHBzOi8vdGlkeW1vZGVscy5naXRodWIuaW8veWFyZHN0aWNrLykgcGFja2FnZSBoYXMgYW4KYGFjY3VyYWN5KClgIGZ1bmN0aW9uIGZvciBsb29raW5nIGF0IHRoZSBvdmVyYWxsIGNsYXNzaWZpY2F0aW9uCmFjY3VyYWN5LCB3aGljaCB1c2VzIHRoZSBoYXJkIGNsYXNzIHByZWRpY3Rpb25zIHRvIG1lYXN1cmUgcGVyZm9ybWFuY2UuCgpIYXJkIGNsYXNzIHByZWRpY3Rpb25zIHRlbGwgdXMgd2hldGhlciBvdXIgbW9kZWwKcHJlZGljdGVkwqBgeWVzYMKgb3LCoGBub2DCoGZvciBlYWNoIHN0dWRlbnQgaW4gdGhlIGAucHJlZF9jbGFzc2AgY29sdW1uLCBhcwp3ZWxsIGFzIHRoZSBlc3RpbWF0aW5nIGEgcHJvYmFiaWxpdHkuwqBBIHNpbXBsZSA1MCUgcHJvYmFiaWxpdHkgY3V0b2ZmIGlzCnVzZWQgdG8gY2F0ZWdvcml6ZSBhIHN0dWRlbnQgYXMgYXQgcmlzay4gRm9yIGV4YW1wbGUsIHN0dWRlbnQgYDQ3NDQ4YApoYWQgYSBgLnByZWRfbm9gIHByb2JhYmlsaXR5IG9mIDAuODg1NDMzMiBhbmQgYC5wcmVkX3llc2AgcHJvYmFiaWxpdHkgb2YKMC4xMTQ1NjY3NyBhbmQgc28gd2FzIGNsYXNzaWZpZWQgYXMgYG5vYCBmb3Igbm90IGJlaW5nIGZvciBgYXRfcmlza2AuCgpSdW4gdGhlIGZvbGxvd2luZyBjb2RlIHRvIGBzZWxlY3QoKWAgdGhlc2UgdmFyaWFibGVzIGZvbGxvd2VkIGJ5IHRoZQpgYWNjdXJhY3koKWAgZnVuY3Rpb24gdG8gc2VlIGhvdyBmcmVxdWVudGx5IG91ciBwcmVkaWN0aW9uIG1hdGNoZWQgb3VyCm9ic2VydmVkIGRhdGE6CgpgYGB7cn0KbHJfcHJlZGljdGlvbnMgfD4KICBzZWxlY3QoYXRfcmlzaywgLnByZWRfY2xhc3MpIHw+CiAgYWNjdXJhY3kodHJ1dGggPSBhdF9yaXNrLCAucHJlZF9jbGFzcykKYGBgCgpPdmVyYWxsIGl0IGxvb2tzIGxpa2Ugb3VyIG1vZGVsIHdhcyBjb3JyZWN0IDgxJSBvZiB0aGUgdGltZSwgd2hpY2ggaXMKbm90IHRvbyBiYWQgYnV0IHdlJ2xsIHNlZSBpbiBhIHNlY29uZCBpcyBub3QgZ29vZCBlbm91Z2ggdG8gc2VydmUgaXQncwppbnRlbmRlZCBwdXJwb3NlIG9mIGlkZW50aWZ5aW5nIGF0LXJpc2sgc3R1ZGVudHMuCgpBbm90aGVyIHdheSB0byBjaGVjayBtb2RlbCBhY2N1cmFjeSBpcyB3aXRoIHRoZSBgY29uZl9tYXQoKWAgZnVuY3Rpb24KZnJvbSB7eWFyZHN0aWNrfSBmb3IgY3JlYXRpbmcgYSBjb25mdXNpb24gbWF0cml4LiBSZWNhbGwgZnJvbSBvdXIgY291cnNlCnRleHQgTGVhcm5pbmcgQW5hbHl0aWNzIEdvZXMgdG8gU2Nob29sIHRoYXQgYSBjb25mdXNpb24gbWF0cml4IGlzIHNpbXBseQphIDIgw5cgMiB0YWJsZSB0aGF0IGxpc3RzIHRoZSBudW1iZXIgb2YgdHJ1ZS1uZWdhdGl2ZXMsIGZhbHNlLW5lZ2F0aXZlcywKdHJ1ZS1wb3NpdGl2ZXMsIGFuZCBmYWxzZS1wb3NpdGl2ZXMuCgpSdW4gdGhlIGZvbGxvdyBjb2RlIHRvIGNyZWF0ZSBhIGNvbmZ1c2lvbiBtYXRyaXggZm9yIG91ciBsb2dpc3RpYwpyZWdyZXNzaW9uIHByZWRpY3Rpb25zOgoKYGBge3J9CmxyX3ByZWRpY3Rpb25zICU+JQpjb25mX21hdChhdF9yaXNrLCAucHJlZF9jbGFzcykKYGBgCgpBcyB5b3UgY2FuIHNlZSBvdXIgbW9kZWwgYWNjdXJhdGVseSBwcmVkaWN0ZWQgYWxsIHN0dWRlbnRzIGFzICJubyIgZm9yCmF0IHJpc2sgMTAzIHRpbWVzLCBidXQgaW5hY2N1cmF0ZWx5IHByZWRpY3RlZCAyNCBzdHVkZW50cyBhICJubyIgZm9yIGF0CnJpc2sgd2hlbiB0aGV5IHdlcmUgYWN0dWFsbHkgInllcyIgaW4gb3VyIGB0ZXN0X2RhdGFgLiBPdmVyYWxsIG91ciBtb2RlbAp3YXMgODElICgxMDMvMTI3KSBhY2N1cmF0ZSwgYnV0IGl0IGFjaGlldmVkIHRoaXMgYnkgc2ltcGx5IGxhYmVsaW5nCmV2ZXJ5b25lIGFzICJubyIgZm9yIGF0IHJpc2suIEFuZCBzaW5jZSByb3VnaGx5IDgyJSBvZiBvdXIgc3R1ZGVudHMgd2VyZQphY3R1YWxseSAibm8sIiB0aGlzIGlzIGNsZWFybHkgbm90IGEgZ3JlYXQgcHJlZGljdGlvbiBtb2RlbC4KCiMjIyMgWyoqWW91ciBUdXJuKipde3N0eWxlPSJjb2xvcjogZ3JlZW47In0gKiripLUqKgoKUmVjeWNsZSBvdXIgY29kZSBmcm9tIGFib3ZlIHRvIGNyZWF0ZSBhIHdvcmtmbG93IGZvciBgbHJfcmVjaXBlXzJgIGFuZAp0ZXN0IG91ciAia2l0Y2hlbiBzaW5rIiBtb2RlbCBvbiBvdXIgdGVzdCBkYXRhLiAqKkhpbnQ6KiogWW91IGNhbgphY2NvbXBsaXNoIHRoaXMgYnkgc2ltcGx5IGNvcHlpbmcgYW5kIHBhc3RpbmcgYW5kIGNoYW5naW5nIGEgc2luZ2xlCmNoYXJhY3RlciBmcm9tIGFib3ZlLgoKYGBge3IsIHdhcm5pbmc9RkFMU0V9CgojIGNyZWF0ZSB3b3JrZmxvdwpscl8yX3dvcmtmbG93IDwtIAogIHdvcmtmbG93KCkgJT4lIAogIGFkZF9tb2RlbChscl9tb2QpICU+JSAKICBhZGRfcmVjaXBlKGxyX3JlY2lwZV8yKQoKIyBzZXQgc2VlZApzZXQuc2VlZCg1ODYpCgojIGZpdCBtb2RlbCB0byB3b3JrZmxvdwpscl8yX2ZpdCA8LSAKICBscl8yX3dvcmtmbG93ICU+JSAKICBmaXQoZGF0YSA9IHRyYWluX2RhdGEpCgojIGV4dHJhY3QgbW9kZWwgZXN0aW1hdGVzCmxyXzJfZml0ICU+JSAKICBleHRyYWN0X2ZpdF9wYXJzbmlwKCkgJT4lIAogIHRpZHkoKQoKIyBnZXQgcHJlZGljdGlvbnMgCnByZWRpY3QobHJfMl9maXQsIHRlc3RfZGF0YSkKCmxyXzJfcHJlZGljdGlvbnMgPC0gYXVnbWVudChscl8yX2ZpdCwgdGVzdF9kYXRhKQoKIyBjaGVjayBvdmVyYWxsIGFjY3VyYWN5IApscl8yX3ByZWRpY3Rpb25zIHw+CiAgc2VsZWN0KGF0X3Jpc2ssIC5wcmVkX2NsYXNzKSB8PgogIGFjY3VyYWN5KHRydXRoID0gYXRfcmlzaywgLnByZWRfY2xhc3MpCgojIGNyZWF0ZSBhIGNvbmZ1c2lvbiBtYXRyaXgKbHJfMl9wcmVkaWN0aW9ucyAlPiUKY29uZl9tYXQoYXRfcmlzaywgLnByZWRfY2xhc3MpCmBgYAoKRG9lcyB0aGlzIG1vZGVsIHBlcmZvcm0gYW55IGJldHRlcj8gSG93IGRvIHlvdSBrbm93PwoKLSAgIFRoaXMgbW9kZWwgZGlkIG5vdCBwZXJmb3JtIGFueSBiZXR0ZXIuIFRoZSBtb2RlbCBkaWQgdGhlIHNhbWUgdGhpbmcKICAgIGJ5IGFsd2F5cyBwcmVkaWN0aW5nIG5vdCBhdC1yaXNrLiBUaGlzIGlzIG1vc3QgY2xlYXIgaW4gdGhlCiAgICBjb25mdXNpb24gbWF0cml4LgoKIyMjIGQuIEZpdCBhIFJhbmRvbSBGb3Jlc3QgTW9kZWwKCltSYW5kb20gZm9yZXN0IG1vZGVsc10oaHR0cHM6Ly9lbi53aWtpcGVkaWEub3JnL3dpa2kvUmFuZG9tX2ZvcmVzdCkKYXJlwqBbZW5zZW1ibGVzXShodHRwczovL2VuLndpa2lwZWRpYS5vcmcvd2lraS9FbnNlbWJsZV9sZWFybmluZykgb2YKW2RlY2lzaW9uIHRyZWVzXShodHRwczovL2VuLndpa2lwZWRpYS5vcmcvd2lraS9EZWNpc2lvbl90cmVlKS4gRWFjaCBvZgp0aG9zZSB0ZXJtcyBpcyBsaW5rZWQgYmVjYXVzZSB0aGVyZSBpcyBhIGRlbnNlIGFtb3VudCBvZiBpbmZvcm1hdGlvbgpyZXF1aXJlZCB0byB1bmRlcnN0YW5kIHdoYXQgZWFjaCBvZiB0aG9zZSB0ZXJtcyByZWFsbHkgbWVhbnMuIEZvciBub3csCmhvd2V2ZXIsIHRoZW0gbWFpbiB0aGluZyB0byBrbm93IGlzIHRoYXQ6Cgo+IE9uZSBvZiB0aGUgYmVuZWZpdHMgb2YgYSByYW5kb20gZm9yZXN0IG1vZGVsIGlzIHRoYXQgaXQgaXMgdmVyeSBsb3cKPiBtYWludGVuYW5jZTsgaXQgcmVxdWlyZXMgdmVyeSBsaXR0bGUgcHJlcHJvY2Vzc2luZyBvZiB0aGUgZGF0YSBhbmQgdGhlCj4gZGVmYXVsdCBwYXJhbWV0ZXJzIHRlbmQgdG8gZ2l2ZSByZWFzb25hYmxlIHJlc3VsdHMuCgpXZSdsbCBhbHNvIGJlIHVzaW5nIHRoZSB7cmFuZ2VyfSBwYWNha2FnZSB3aGljaCBwcm92aWRlcyBhIGZhc3QKaW1wbGVtZW50YXRpb24gb2YgUmFuZG9tIEZvcmVzdCBtb2RlbHMgYW5kIGlzIHBhcnRpY3VsYXJseSBzdWl0ZWQgZm9yCmhpZ2ggZGltZW5zaW9uYWwgZGF0YSwgZS5nLiwgbW9kZWxzIHdpdGggYSBsb3Qgb2YgcHJlZGljdG9ycyBhbmQKZmVhdHVyZXMuIFRoaXMgcGFja2FnZSBzdXBwb3J0cyBzdXBlcnZpc2VkIGxlYXJuaW5nIGFwcHJvYWNoZXMgaW5jbHVkaW5nCmNsYXNzaWZpY2F0aW9uLCByZWdyZXNzaW9uLCBzdXJ2aXZhbCBhbmQgcHJvYmFiaWxpdHkgcHJlZGljdGlvbi4KCkxldCdzIGNyZWF0ZSBhIHJlY2lwZSBmb3Igb3VyIGBjb3Vyc2VfZGF0YWDCoGRhdGEgdGhhdCBpbmNsdWRlcyBhbGwgb3VyCnByZWRpY3RvcnMuCgpgYGB7cn0KCnJmX3JlY2lwZSA8LSAKICByZWNpcGUoYXRfcmlzayB+IGdlbmRlciArIGNvdXJzZV9pbnRlcmVzdCArIHBlcmNlaXZlZF9jb21wZXRlbmNlICsgCiAgICAgICAgICAgdXRpbGl0eV92YWx1ZSArIHZhcmlhYmlsaXR5ICsgbl8xMDAgKyBvdmVyYWxsX3BlcmNlbnQsIAogICAgICAgICBkYXRhID0gdHJhaW5fZGF0YSkKCnJmX3JlY2lwZQoKc3VtbWFyeShyZl9yZWNpcGUpCmBgYAoKVG8gZml0IGEgcmFuZG9tIGZvcmVzdCBtb2RlbCBvbiB0aGUgdHJhaW5pbmcgc2V0LCB3ZSdsbCB1c2UKdGhlwqB7W3BhcnNuaXBdKGh0dHBzOi8vdGlkeW1vZGVscy5naXRodWIuaW8vcGFyc25pcC8pfSBwYWNrYWdlIGFnYWluCmFsb25nIHdpdGggdGhlCltyYW5nZXJdKGh0dHBzOi8vY3Jhbi5yLXByb2plY3Qub3JnL3dlYi9wYWNrYWdlcy9yYW5nZXIvaW5kZXguaHRtbCkKZW5naW5lLiBXZSdsbCBhbHNvIGluY2x1ZGUgdGhlIGBzZXRfbW9kZSgpYCBmdW5jdGlvbiB0byBzcGVjaWZ5IG91cgptb2RlbCBhcyAiY2xhc3NpZmljYXRpb24iIHJhdGhlciB0aGFuICJyZWdyZXNzaW9uLiIKClJlY2FsbCBmcm9tIG91ciBMZWFybmluZyBBbmFseXRpY3MgR29lcyB0byBTY2hvb2wgdGhhdCBzdXBlcnZpc2VkCm1hY2hpbmVkIGxlYXJuaW5nLCBvciBwcmVkaWN0aXZlIG1vZGVsaW5nLCBpbnZvbHZlcyB0d28gYnJvYWQKYXBwcm9hY2hlczogY2xhc3NpZmljYXRpb24gYW5kIHJlZ3Jlc3Npb24uIENsYXNzaWZpY2F0aW9uIGFsZ29yaXRobXMKbW9kZWwgY2F0ZWdvcmljYWwgb3V0Y29tZXMgKGUuZy4sIHllcyBvciBubyBvdXRjb21lcyBsaWtlIHdpdGggb3VyIGF0CnJpc2sgZGF0YSkuIFJlZ3Jlc3Npb24gYWxnb3JpdGhtcyBjaGFyYWN0ZXJpemUgY29udGludW91cyBvdXRjb21lcwooZS5nLiwgdGVzdCBzY29yZXMpLgoKUnVuIHRoZSBmb2xsb3dpbmcgY29kZSB0byBjcmVhdGUgb3VyIHJhbmRvbSBmb3Jlc3QgbW9kZWwgZm9yIG91cgp0cmFpbmluZyBkYXRhOgoKYGBge3J9CnJmX21vZCA8LSAKICByYW5kX2ZvcmVzdCh0cmVlcyA9IDUwMDApICU+JSAKICBzZXRfZW5naW5lKCJyYW5nZXIiLCBpbXBvcnRhbmNlID0gImltcHVyaXR5IikgJT4lIAogIHNldF9tb2RlKCJjbGFzc2lmaWNhdGlvbiIpCmBgYAoKTm93IGxldCdzIGNvbWJpbmUgb3VyIG1vZGVsIGFuZCByZWNpcGUgdG8gY3JlYXRlIG91ciBuZXcgcmFuZG9tIGZvcmVzdAp3b3JrZmxvdzoKCmBgYHtyfQpyZl93b3JrZmxvdyA8LSAKICB3b3JrZmxvdygpICU+JSAKICBhZGRfbW9kZWwocmZfbW9kKSAlPiUgCiAgYWRkX3JlY2lwZShyZl9yZWNpcGUpCmBgYAoKQW5kIGZpdCBvdXIgbW9kZWwgYW5kIHJlY2lwZSB0byBvdXIgdHJhaW5pbmcgZGF0YToKCmBgYHtyfQpzZXQuc2VlZCg1ODYpCgpyZl9maXQgPC0gCiAgcmZfd29ya2Zsb3cgJT4lIAogIGZpdChkYXRhID0gdHJhaW5fZGF0YSkKYGBgCgpHYXRoZXIgb3VyIHJhbmRvbSBmb3Jlc3QgcHJlZGljdGlvbnM6CgpgYGB7cn0KCnJmX3ByZWRpY3Rpb25zIDwtIGF1Z21lbnQocmZfZml0LCB0ZXN0X2RhdGEpCgpyZl9wcmVkaWN0aW9ucwpgYGAKCkFuZCBjaGVjayBvdXIgbW9kZWwncyBvdmVyYWxsIGFjY3VyYWN5OgoKYGBge3J9CnJmX3ByZWRpY3Rpb25zICU+JQogIHNlbGVjdChhdF9yaXNrLCAucHJlZF9jbGFzcykgJT4lCiAgYWNjdXJhY3kodHJ1dGggPSBhdF9yaXNrLCAucHJlZF9jbGFzcykKYGBgCgpBbmQgY3JlYXRlIGEgY29uZnVzaW9uIG1hdHJpeCB0byBzZWUgd2hlcmUgaXQgZGlkIGFuZCBkaWQgbm90IG1ha2UKYWNjdXJhdGUgcHJlZGljdGlvbnM6CgpgYGB7cn0KcmZfcHJlZGljdGlvbnMgfD4KY29uZl9tYXQoYXRfcmlzaywgLnByZWRfY2xhc3MpCmBgYAoKVW5mb3J0dW5hdGVseSwgb3VyIHJhbmRvbSBmb3Jlc3QgbW9kZWwgd2Fzbid0IG11Y2ggYmV0dGVyIGF0IHByZWRpY3RpbmcKc3R1ZGVudHMgd2hvIHdlcmUgZGVmaW5lZCBhcyAiYXQgcmlzay4iCgpFdmVuIG1vcmUgdW5mb3J0dW5hdGUsIGlmIHRoaXMgaGFkIGJlZW4gYSByZWFsLXdvcmxkIHNpdHVhdGlvbiwgb25seSAxCm9mIHRoZSAyNCBzdHVkZW50cyB3aG8gZmFpbGVkIHRoZSBjb3Vyc2Ugd291bGQgaGF2ZSByZWNlaXZlZCBhZGRpdGlvbmFsCnN1cHBvcnQuIENsZWFybHkgd2UnZCBuZWVkIHRvIGJ1aWxkIGEgYmV0dGVyIG1vZGVsLiBJbiB0aGlzIGNhc2UsIG1vcmUKZGF0YSB3b3VsZCBkZWZpbml0ZWx5IGhlbHAsIGJ1dCBpdCdzIGxpa2VseSB3ZSdyZSBtaXNzaW5nIHNvbWUgaW1wb3J0YW50CmluZm9ybWF0aW9uIGFib3V0IHN0dWRlbnRzIHRoYXQgaXMgcHJlZGljdGl2ZSBvZiB0aGVpciBzdWNjZXNzLgoKIyMjIyBbKipZb3VyIFR1cm4qKl17c3R5bGU9ImNvbG9yOiBncmVlbjsifSAqKuKktSoqCgpUcnkgY3JlYXRpbmcgeW91ciBvd24gcmVjaXBlIGFuZCBsb2dpc3RpYyByZWdyZXNzaW9uIG9yIHJhbmRvbSBmb3Jlc3QKbW9kZWwgYW5kIHNlZSBob3cgaXQgcGVyZm9ybXMgYWdhaW5zdCB0aGUgdGhyZWUgdGhhdCB3ZSBqdXN0IGNyZWF0ZWQuCkZlZWwgZnJlZSB0byBleHBlcmltZW50IHdpdGggdmFyaWFibGVzIHdlIGV4Y2x1ZGVkLCBsaWtlIHNwZWNpZmljIHN1cnZleQppdGVtcy4gVGhlcmUgaXMgYWxzbyBhIGZpbGUgaW4geW91ciBkYXRhIGZvbGRlcgpjYWxsZWTCoGBzY2ktbW8td2l0aC10ZXh0LmNzdmDCoHRoYXQgaW5jbHVkZXMgZGF0YSBhYm91dCBzdHVkZW50cwpwYXJ0aWNpcGF0aW9uIGluIGRpc2N1c3Npb24gZm9ydW1zLCBpbmNsdWRpbmcgdGhlIG51bWJlciBvZiBwb3N0cyBhbmQKcHN5Y2hvbWV0cmljIHByb3BlcnRpZXMgYWJvdXQgdGhlaXIgbGFuZ3VhZ2UgdXNlZC4gWW91IGNhbiByZWFkIG1vcmUKYWJvdXQgdGhhdCBkYXRhIGluwqBbQ2hhcHRlciAxNCBvZgpEU0lFVVJdKGh0dHBzOi8vZGF0YXNjaWVuY2VpbmVkdWNhdGlvbi5jb20vYzE0Lmh0bWwjZGF0YS1zb3VyY2VzLTMpLgoKSSByZWNvbW1lbmQgY3JlYXRpbmcgYW5kIHVzaW5nIGFuIFIgc2NyaXB0IHRvIGRldmVsb3AgeW91ciBtb2RlbC4gVXNlCnRoZSBjb2RlIGNodW5rIGJlbG93IHRvIHJlY29yZCB5b3VyIGZpbmFsIG1vZGVsOgoKYGBge3J9CmNvdXJzZV9kYXRhXzMgPC0gcHJvY2Vzc2VkX2RhdGEgJT4lCiAgbXV0YXRlKGF0X3Jpc2sgPSBpZl9lbHNlKGZpbmFsX2dyYWRlID49IDY2LjcsICJubyIsICJ5ZXMiKSkgJT4lCiAgbXV0YXRlKGdlbmRlciA9IGFzX2ZhY3RvcihnZW5kZXIpLCBhdF9yaXNrID0gYXNfZmFjdG9yKGF0X3Jpc2spKSAlPiUKICBzZWxlY3Qoc3R1ZGVudF9pZCwgY291cnNlX2lkLCBhdF9yaXNrLCBnZW5kZXIsIHExLCBxMiwgcTMsIHE0LCBxNSwgcTYsCiAgICAgICAgIHE3LCBxOCwgcTksIHExMCkgJT4lCiAgZHJvcF9uYSgpCgpjb3Vyc2VfZGF0YV8zICU+JQogIGNvdW50KGF0X3Jpc2spICU+JQogIG11dGF0ZShwcm9wb3J0aW9uID0gbi9zdW0obikpCgpjb3Vyc2VfZGF0YV8zIDwtIGlubmVyX2pvaW4oY291cnNlX2RhdGFfMywgZ3JhZGVfYm9vaykKCnNldC5zZWVkKDEyMykKCmNvdXJzZV9zcGxpdF8zIDwtIGluaXRpYWxfc3BsaXQoY291cnNlX2RhdGFfMywgc3RyYXRhID0gYXRfcmlzaykKCnRyYWluX2RhdGFfMyA8LSB0cmFpbmluZyhjb3Vyc2Vfc3BsaXRfMykKdGVzdF9kYXRhXzMgPC0gdGVzdGluZyhjb3Vyc2Vfc3BsaXRfMykKCmxyX3JlY2lwZV8zIDwtIHJlY2lwZShhdF9yaXNrIH4gY291cnNlX2lkLCBnZW5kZXIsIHExLCBxMiwgcTMsIHE0LCBxNSwKICAgICAgICAgICAgICAgICAgICAgIHE2LCBxNywgcTgsIHE5LCBxMTAsIGRhdGEgPSB0cmFpbl9kYXRhXzMpICU+JQogIHN0ZXBfZHVtbXkoYWxsX25vbWluYWxfcHJlZGljdG9ycygpKQoKbHJfd29ya2Zsb3dfMyA8LQogIHdvcmtmbG93KCkgJT4lCiAgYWRkX21vZGVsKGxyX21vZCkgJT4lCiAgYWRkX3JlY2lwZShscl9yZWNpcGVfMykKCnNldC5zZWVkKDEyMykKCmxyX2ZpdF8zIDwtCiAgbHJfd29ya2Zsb3dfMyAlPiUKICBmaXQoZGF0YSA9IHRyYWluX2RhdGFfMykKCmxyX2ZpdF8zICU+JQogIGV4dHJhY3RfZml0X3BhcnNuaXAoKSAlPiUKICB0aWR5KCkKCmxyX3ByZWRpY3Rpb25zXzMgPC0gYXVnbWVudChscl9maXRfMywgdGVzdF9kYXRhXzMpCgpscl9wcmVkaWN0aW9uc18zICU+JQogIHNlbGVjdChhdF9yaXNrLCAucHJlZF9jbGFzcykgJT4lCiAgYWNjdXJhY3kodHJ1dGggPSBhdF9yaXNrLCAucHJlZF9jbGFzcykKCmxyX3ByZWRpY3Rpb25zXzMgJT4lCiAgY29uZl9tYXQoYXRfcmlzaywgLnByZWRfY2xhc3MpCgoKYGBgCgpOb3cgYW5zd2VyIHRoZSBmb2xsb3dpbmcgcXVlc3Rpb25zPwoKMS4gIEhvdyBkaWQgeW91ciBtb2RlbCBkbyBjb21wYXJlZCB0byBvdGhlcnM/CgogICAgLSAgIE15IG1vZGVsIGRpZG4ndCBkbyBhbnkgYmV0dGVyIHRoYW4gdGhlIHByZXZpb3VzIG1vZGVscy4KCjIuICBXaGF0IGluZm9ybWF0aW9uIG1pZ2h0IGJlIHVzZWZ1bCB0byBrbm93IGFib3V0IHN0dWRlbnRzIHByaW9yIHRvIHRoZQogICAgY291cnNlIHN0YXJ0IHRoYXQgbWlnaHQgYmUgdXNlZnVsIGZvciBpbXByb3Zpbmcgb3VyIG1vZGVsPwoKICAgIC0gICBJdCBtaWdodCBiZSBuaWNlIHRvIGtub3cgaG93IHN0dWRlbnRzIGRpZCBpbiBwcmV2aW91cyBzY2llbmNlCiAgICAgICAgY2xhc3NlcyBvciBpZiB0aGV5IHdlcmUgcGxhbm5pbmcgb24gcmVjZWl2aW5nIHN1cHBvcnQgb3V0c2lkZSBvZgogICAgICAgIGNsYXNzLCBzdWNoIGFzIHR1dG9yaW5nLgoKIyMgNS4gQ09NTVVOSUNBVEUKCkluIHRoaXMgY2FzZSBzdHVkeSwgd2UgZm9jdXNlZCBhcHBseWluZyBzb21lIGJhc2ljIG1hY2hpbmUgbGVhcm5pbmcKdGVjaG5pcXVlcyB0byBoZWxwIHVzIHVuZGVyc3RhbmQgaG93IGEgcHJlZGljdGl2ZSBtb2RlbCB1c2VkIGluIGVhcmx5Cndhcm5pbmcgc3lzdGVtcyBtaWdodCBhY3R1YWxseSBiZSBkZXZlbG9wZWQgYW5kIHRlc3RlZC4gU3BlY2lmaWNhbGx5LCB3ZQptYWRlIGEgdmVyeSBjcnVkZSBmaXJzdCBhdHRlbXB0IGF0IGRldmVsb3BpbmcgYSBtb2RlbCB1c2luZyBtYWNoaW5lCmxlYXJuaW5nIHRlY2huaXF1ZXMgdGhhdCB3ZXJlIG5vdCB0ZXJyaWJseSBncmVhdCBhdCBhY2N1cmF0ZWx5IHByZWRpY3QKd2hldGhlciBhIHN0dWRlbnQgaXMgbGlrZWx5IHRvIHBhc3Mgb3IgZmFpbCBhbmQgb25saW5lIGNvdXJzZS4KCkJlbG93LCBhZGQgYSBmZXcgbm90ZXMgaW4gcmVzcG9uc2UgdG8gdGhlIGZvbGxvd2luZyBwcm9tcHRzOgoKMS4gIE9uZSB0aGluZyBJIHRvb2sgYXdheSBmcm9tIHRoaXMgbGVhcm5pbmcgbGFiIHRoYXQgSSBmb3VuZCBlc3BlY2lhbGx5CiAgICB1c2VmdWw6CgogICAgLSAgIEkgbGVhcm5lZCBhIGxvdCBhYm91dCBob3cgdG8gbWFrZSBhIG1vZGVsLCB0cmFpbiBpdCBhbmQgdGVzdCBpdC4KICAgICAgICBJdCB3YXMgcmVhbGx5IGNvb2wgdG8gYXBwbHkgd2hhdCB3ZSd2ZSBiZWVuIGxlYXJuaW5nIG92ZXIgdGhlCiAgICAgICAgbGFzdCBmZXcgd2Vla3MgdG8gc29tZSBtYWNoaW5lIGxlYXJuaW5nLiBJIGhhZCB3b25kZXJlZCBhYm91dAogICAgICAgIGhvdyB0byBzcGxpdCBkYXRhIGFuZCB0cmFpbiBpdCBhbmQgdGhpcyB0YXVnaHQgbWUgd2F5cyB0byBkbwogICAgICAgIHRoYXQgd2l0aCBhIGxvZ2lzdGljIHJlZ3Jlc3Npb24gYW5kIGEgcmFuZG9tIGZvcmVzdCBtb2RlbC4KCjIuICBPbmUgdGhpbmcgSSB3YW50IHRvIGxlYXJuIG1vcmUgYWJvdXQ6CgogICAgLSAgIEknZCBsaWtlIHRvIGxlYXJuIG1vcmUgYWJvdXQgd29ya2luZyB3aXRoIGRhdGEgc2V0cyB0aGF0IGhhdmUgYQogICAgICAgIGxvdCBvZiB2YXJpYWJsZXMuIEkgbG9va2VkIGF0IHRoZSBzY2ktbW8td2l0aC10ZXh0LmNzdiBkYXRhIHNldCwKICAgICAgICBidXQgaXQgd2FzIGludGltaWRhdGluZyBhbmQgSSBkaWRuJ3Qga25vdyB3aGVyZSB0byBzdGFydCBldmVuIGlmCiAgICAgICAgSSB3YW50ZWQgdG8gdXNlIGl0LgoKVG8gInR1cm4gaW4iIHlvdXIgd29yaywgeW91IGNhbiBjbGljayB0aGUgIktuaXQiIGljb24gYXQgdGhlIHRvcCBvZiB0aGUKZmlsZSwgb3IgY2xpY2sgdGhlIGRyb3Bkb3duIGFycm93IG5leHQgdG8gaXQgYW5kIHNlbGVjdCAiS25pdCB0b3AgSFRNTCIuClRoaXMgd2lsbCBjcmVhdGUgYSByZXBvcnQgaW4geW91ciBGaWxlcyBwYW5lIHRoYXQgc2VydmVzIGFzIGEgcmVjb3JkIG9mCnlvdXIgY29tcGxldGVkIGFzc2lnbm1lbnQgYW5kIHRoYXQgYmUgb3BlbmVkIGluIGEgYnJvd3NlciBvciBzaGFyZWQgb24KdGhlIHdlYi4KCiMjIyBDb25ncmF0dWxhdGlvbnMhCg==