There are two main goals in this project: - What variables have a noticeable impact on the actual count or percentage of EVs - What can the percentage or count of EVs be used to predict about states?

library(ggplot2)
library(tidyverse)
library(caret)
library(dplyr)
library(broom)
library(pROC)

The data that is being used in this project has a mix of variables that have varying relation to electric vehicles. These variables were collected in all 50 states each year from 2016 to 2023. However, depending on the state, small bits of data are missing in the earlier years, so most of the graphs use a version of this dataset with the missing values taken out.

evdataclean <- na.omit(evdata)
yeardata <- split(evdata, evdata$year)
statedata <- split(evdata, evdata$state)

The first area of inquiry was to see whether a high level of education has an impact on the percentage of EVs out of total cars in a state. For relevance, this graph only uses the data from 2023.

ggplot(yeardata[["2023"]], aes(Pbachelors, Pelectric)) +
  geom_point() +
  labs(x = "Percentage of People with a Bachelor's or Higher",
       y = "Percentage of EVs in State",
       title = "Percentage of Bachelors vs Percentage of EVs in States (2023)") +
  theme(plot.title = element_text(hjust = 0.5))

Because there seemed to be a moderate correlation between the two variables, we can run a linear regression model to determine if high education has any statistical significance.

set.seed(123)

trainindex1 <- createDataPartition(evdata$Pelectric, p = 0.8, list = FALSE, times = 1)
evdataTrain1 <- evdataclean[ trainindex1,]
evdataTest1 <- evdataclean[-trainindex1,]

model1 <- lm(Pelectric ~ Pbachelors, data = evdataTrain1)
summary(model1)

Call:
lm(formula = Pelectric ~ Pbachelors, data = evdataTrain1)

Residuals:
     Min       1Q   Median       3Q      Max 
-0.56474 -0.31654 -0.13190  0.06994  2.70545 

Coefficients:
             Estimate Std. Error t value Pr(>|t|)    
(Intercept) -0.797042   0.292007  -2.730  0.00737 ** 
Pbachelors   0.043613   0.008931   4.883 3.49e-06 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 0.5129 on 112 degrees of freedom
  (214 observations deleted due to missingness)
Multiple R-squared:  0.1755,    Adjusted R-squared:  0.1682 
F-statistic: 23.85 on 1 and 112 DF,  p-value: 3.494e-06

The R-squared value seems to hover around 0.17, meaning that around 17% of the variation in the EV percentage can be predicted using the percentage of people with a bachelor’s or higher.

Because the p-value is also far under 0.05, it is likely that the percentage of people with a bachelor’s or higher has some sort of relation with the EV percentage in a state.

evdataTest1 <- evdataTest1 %>%
  mutate(PredictedPelec = predict(model1, newdata = evdataTest1))

ggplot(evdataTest1, aes(PredictedPelec, Pelectric)) +
  geom_point() +
  geom_abline(slope = 1, intercept = 0, color = "red") +
  theme_minimal() +
  theme(plot.title = element_text(hjust = 0.5)) +
  labs(x = "Predicted Percentage of EVs in State",
       y = "Percentage of EVs in State",
       title = "Predicted vs Actual Percentage of EVs in States (2023)") +
  scale_y_continuous(labels = scales::label_number())

We can also turn this into a graph that compares the actual percentage of EVs in a state with the predicted percentage of EVs. In this case, the red line would be the target, where we want the predicted percentage to be exactly equal to the actual percentage.

summary_table1 <- evdataTest1 %>%
  select(PredictedPelec, Pelectric) %>%
  summary()

print(summary_table1)
 PredictedPelec     Pelectric     
 Min.   :0.1816   Min.   :0.0500  
 1st Qu.:0.3949   1st Qu.:0.2550  
 Median :0.5270   Median :0.3900  
 Mean   :0.5534   Mean   :0.5634  
 3rd Qu.:0.6625   3rd Qu.:0.6150  
 Max.   :1.0508   Max.   :2.5000  

We can also make a summary table comparing the predicted percentage and the actual percentage. Similar to what we could see on the graph, the model isn’t particularly accurate, especially with the minimum and maximum values, but the the quartiles and mean show some promising accuracy.

Another variable that would strike as potentially significant towards the EV percentage is the price of gas, especially if it has increased over time.

avg_G_value <- evdata %>%
  group_by(year) %>%
  summarize(avg_value = mean(Gprice))

ggplot(avg_G_value, aes(year, avg_value)) +
  geom_col(fill = "orange") +
  labs(x = "Year", y = "Nationwide Average Gas Price",
       title = "Nationwide Average Gas Price by Year") +
  theme(plot.title = element_text(hjust = 0.5)) +
  scale_x_continuous(breaks = seq(2018, 2023, by = 1))
Warning: Removed 2 rows containing missing values or values outside the
scale range (`geom_col()`).

Nationwide, gas prices did seem to increase from 2016 from 2023. We can run another linear regression model to see if the price of gas has actually been a factor in EV registrations.

set.seed(123)

trainindex2 <- createDataPartition(evdata$Nregistration, p = 0.8, list = FALSE, times = 1)
evdataTrain2 <- evdataclean[ trainindex,]
evdataTest2 <- evdataclean[-trainindex,]

model2 <- lm(Nregistration ~ Gprice, data = evdataTrain)
summary(model)

Call:
lm(formula = Nregistration ~ Gprice, data = evdataTrain)

Residuals:
    Min      1Q  Median      3Q     Max 
-329471  -39989    -892   26358  845726 

Coefficients:
            Estimate Std. Error t value Pr(>|t|)    
(Intercept)  -556166      79698  -6.978 2.16e-10 ***
Gprice        204405      26457   7.726 4.92e-12 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 124900 on 113 degrees of freedom
  (213 observations deleted due to missingness)
Multiple R-squared:  0.3456,    Adjusted R-squared:  0.3399 
F-statistic: 59.69 on 1 and 113 DF,  p-value: 4.918e-12

This time, the R-squared value hovers around 0.34, which is about twice the value when using the percentage of bachelor’s degrees as a factor. This suggests that the gas price is a more significant factor in EV registrations.

Once again, we can turn this into a graph where the red line would be representative of a perfect model.

evdataTest2 <- evdataTest2 %>%
  mutate(PredictedNreg = predict(model, newdata = evdataTest2))

ggplot(evdataTest2, aes(Nregistration, PredictedNreg)) +
  geom_point() +
  geom_abline(slope = 1, intercept = 0, color = "red") +
  theme_minimal() +
  theme(plot.title = element_text(hjust = 0.5)) +
  labs(x = "Annual EV Registrations", y = "Predicted Annual EV Registrations",
       title = "Predicted vs Actual Annual EV Registrations in States") +
  scale_y_continuous(labels = scales::label_number())

While there is still some variance in this model, it does seem to be much more centered around the goal than the model that uses the percentage of bachelor’s degrees.

summary_table2 <- evdataTest2 %>%
  select(Nregistration, PredictedNreg) %>%
  summary()

print(summary_table2)
 Nregistration    PredictedNreg   
 Min.   :   500   Min.   :-57827  
 1st Qu.:  5575   1st Qu.:  9320  
 Median : 18100   Median : 38755  
 Mean   : 33724   Mean   : 53869  
 3rd Qu.: 60650   3rd Qu.: 63692  
 Max.   :152100   Max.   :308672  

The summary table was also innacurate with minimum and maximum values, but showed some accuracy with quartiles.

Moving toward what EV data can be used to predict, a common example is a political party.

ggplot(evdataclean, aes(party, Pelectric, fill = party)) +
  geom_boxplot(width = 0.1) +
  geom_violin(alpha = 0.3) +
   labs(x = "State Political Party", y = "Percentage of EVs",
       title = "Percentage of EVs in States by Political Party") +
  theme(plot.title = element_text(hjust = 0.5), aspect.ratio = 0.75, legend.position = "none") +
  scale_x_discrete(expand = expansion(mult = c(0.5, 0.5))) +
  scale_fill_manual(values = c("Democratic" = "#4255ff", "Republican" = "#ff413b"))

It seems that the majority of Republican states are concentrated at less than 0.5% of EVs, while Democratic states seem to have a higher percentage and are more spread out. Looking at this, we can run a logistic regression model to determine if there is a threshold that can accurately predict a state’s political party.

set.seed(123)

trainIndex <- createDataPartition(evdataclean$party, p = 0.7, list = FALSE)
traindata <- evdataclean[trainIndex, ]
testdata <- evdataclean[-trainIndex, ]

logregmodel <- glm(party ~ Pelectric, data = traindata, family = binomial)
summary(logregmodel)

Call:
glm(formula = party ~ Pelectric, family = binomial, data = traindata)

Coefficients:
            Estimate Std. Error z value Pr(>|z|)    
(Intercept)   1.7104     0.4265    4.01 6.07e-05 ***
Pelectric    -3.4406     0.8115   -4.24 2.24e-05 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

(Dispersion parameter for binomial family taken to be 1)

    Null deviance: 145.48  on 104  degrees of freedom
Residual deviance: 111.70  on 103  degrees of freedom
AIC: 115.7

Number of Fisher Scoring iterations: 5
tidy(logregmodel, exponentiate = TRUE, conf.int = TRUE)

The p-value is far under 0.05, so it is unlikely that the percentage of electric cars in a state is unrelated to that state’s political party.

To check this, we can create an ROC Curve with the model.

testdata$predictedprob <- predict(logregmodel, newdata = testdata, type = "response")

rocurve <- roc(testdata$party, testdata$predictedprob)
Setting levels: control = Democratic, case = Republican
Setting direction: controls < cases
plot(rocurve, col = "blue", main = "ROC Curve")

We can find the point on this curve that demonstrates the most effective threshold and use that threshold in a confusion matrix.

bestcoord <- coords(rocurve, "best", ret = c("threshold", "sensitivity", "specificity", "accuracy"),
                    best.method = "closest.topleft")
print(bestcoord)

testdata$predictedparty <- ifelse(testdata$predictedprob < 0.5574024, "Democratic", "Republican")

testdata$predictedparty <- factor(testdata$predictedparty, levels = c("Democratic", "Republican"))

confusionMatrix(testdata$predictedparty, testdata$party)
Confusion Matrix and Statistics

            Reference
Prediction   Democratic Republican
  Democratic         17          5
  Republican          6         16
                                          
               Accuracy : 0.75            
                 95% CI : (0.5966, 0.8681)
    No Information Rate : 0.5227          
    P-Value [Acc > NIR] : 0.001705        
                                          
                  Kappa : 0.5             
                                          
 Mcnemar's Test P-Value : 1.000000        
                                          
            Sensitivity : 0.7391          
            Specificity : 0.7619          
         Pos Pred Value : 0.7727          
         Neg Pred Value : 0.7273          
             Prevalence : 0.5227          
         Detection Rate : 0.3864          
   Detection Prevalence : 0.5000          
      Balanced Accuracy : 0.7505          
                                          
       'Positive' Class : Democratic      
                                          
LS0tCnRpdGxlOiAiRWxlY3RyaWMgVmVoaWNsZXMgdGhyb3VnaG91dCBBbWVyaWNhOiBBIERhdGEgQW5hbHlzaXMiCm91dHB1dDogaHRtbF9ub3RlYm9vawotLS0KClRoZXJlIGFyZSB0d28gbWFpbiBnb2FscyBpbiB0aGlzIHByb2plY3Q6CiAtIFdoYXQgdmFyaWFibGVzIGhhdmUgYSBub3RpY2VhYmxlIGltcGFjdCBvbiB0aGUgYWN0dWFsIGNvdW50IG9yIHBlcmNlbnRhZ2Ugb2YgRVZzCiAtIFdoYXQgY2FuIHRoZSBwZXJjZW50YWdlIG9yIGNvdW50IG9mIEVWcyBiZSB1c2VkIHRvIHByZWRpY3QgYWJvdXQgc3RhdGVzPwoKYGBge3J9CmxpYnJhcnkoZ2dwbG90MikKbGlicmFyeSh0aWR5dmVyc2UpCmxpYnJhcnkoY2FyZXQpCmxpYnJhcnkoZHBseXIpCmxpYnJhcnkoYnJvb20pCmxpYnJhcnkocFJPQykKYGBgCgpUaGUgZGF0YSB0aGF0IGlzIGJlaW5nIHVzZWQgaW4gdGhpcyBwcm9qZWN0IGhhcyBhIG1peCBvZiB2YXJpYWJsZXMgdGhhdCBoYXZlIHZhcnlpbmcgcmVsYXRpb24gdG8gZWxlY3RyaWMgdmVoaWNsZXMuIFRoZXNlIHZhcmlhYmxlcyB3ZXJlIGNvbGxlY3RlZCBpbiBhbGwgNTAgc3RhdGVzIGVhY2ggeWVhciBmcm9tIDIwMTYgdG8gMjAyMy4gSG93ZXZlciwgZGVwZW5kaW5nIG9uIHRoZSBzdGF0ZSwgc21hbGwgYml0cyBvZiBkYXRhIGFyZSBtaXNzaW5nIGluIHRoZSBlYXJsaWVyIHllYXJzLCBzbyBtb3N0IG9mIHRoZSBncmFwaHMgdXNlIGEgdmVyc2lvbiBvZiB0aGlzIGRhdGFzZXQgd2l0aCB0aGUgbWlzc2luZyB2YWx1ZXMgdGFrZW4gb3V0LgoKYGBge3J9CmV2ZGF0YWNsZWFuIDwtIG5hLm9taXQoZXZkYXRhKQp5ZWFyZGF0YSA8LSBzcGxpdChldmRhdGEsIGV2ZGF0YSR5ZWFyKQpzdGF0ZWRhdGEgPC0gc3BsaXQoZXZkYXRhLCBldmRhdGEkc3RhdGUpCmBgYAoKVGhlIGZpcnN0IGFyZWEgb2YgaW5xdWlyeSB3YXMgdG8gc2VlIHdoZXRoZXIgYSBoaWdoIGxldmVsIG9mIGVkdWNhdGlvbiBoYXMgYW4gaW1wYWN0IG9uIHRoZSBwZXJjZW50YWdlIG9mIEVWcyBvdXQgb2YgdG90YWwgY2FycyBpbiBhIHN0YXRlLiBGb3IgcmVsZXZhbmNlLCB0aGlzIGdyYXBoIG9ubHkgdXNlcyB0aGUgZGF0YSBmcm9tIDIwMjMuCgpgYGB7cn0KZ2dwbG90KHllYXJkYXRhW1siMjAyMyJdXSwgYWVzKFBiYWNoZWxvcnMsIFBlbGVjdHJpYykpICsKICBnZW9tX3BvaW50KCkgKwogIGxhYnMoeCA9ICJQZXJjZW50YWdlIG9mIFBlb3BsZSB3aXRoIGEgQmFjaGVsb3IncyBvciBIaWdoZXIiLAogICAgICAgeSA9ICJQZXJjZW50YWdlIG9mIEVWcyBpbiBTdGF0ZSIsCiAgICAgICB0aXRsZSA9ICJQZXJjZW50YWdlIG9mIEJhY2hlbG9ycyB2cyBQZXJjZW50YWdlIG9mIEVWcyBpbiBTdGF0ZXMgKDIwMjMpIikgKwogIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLjUpKQpgYGAKCkJlY2F1c2UgdGhlcmUgc2VlbWVkIHRvIGJlIGEgbW9kZXJhdGUgY29ycmVsYXRpb24gYmV0d2VlbiB0aGUgdHdvIHZhcmlhYmxlcywgd2UgY2FuIHJ1biBhIGxpbmVhciByZWdyZXNzaW9uIG1vZGVsIHRvIGRldGVybWluZSBpZiBoaWdoIGVkdWNhdGlvbiBoYXMgYW55IHN0YXRpc3RpY2FsIHNpZ25pZmljYW5jZS4KCmBgYHtyfSAKc2V0LnNlZWQoMTIzKQoKdHJhaW5pbmRleDEgPC0gY3JlYXRlRGF0YVBhcnRpdGlvbihldmRhdGEkUGVsZWN0cmljLCBwID0gMC44LCBsaXN0ID0gRkFMU0UsIHRpbWVzID0gMSkKZXZkYXRhVHJhaW4xIDwtIGV2ZGF0YWNsZWFuWyB0cmFpbmluZGV4MSxdCmV2ZGF0YVRlc3QxIDwtIGV2ZGF0YWNsZWFuWy10cmFpbmluZGV4MSxdCgptb2RlbDEgPC0gbG0oUGVsZWN0cmljIH4gUGJhY2hlbG9ycywgZGF0YSA9IGV2ZGF0YVRyYWluMSkKc3VtbWFyeShtb2RlbDEpCmBgYApUaGUgUi1zcXVhcmVkIHZhbHVlIHNlZW1zIHRvIGhvdmVyIGFyb3VuZCAwLjE3LCBtZWFuaW5nIHRoYXQgYXJvdW5kIDE3JSBvZiB0aGUgdmFyaWF0aW9uIGluIHRoZSBFViBwZXJjZW50YWdlIGNhbiBiZSBwcmVkaWN0ZWQgdXNpbmcgdGhlIHBlcmNlbnRhZ2Ugb2YgcGVvcGxlIHdpdGggYSBiYWNoZWxvcidzIG9yIGhpZ2hlci4KCkJlY2F1c2UgdGhlIHAtdmFsdWUgaXMgYWxzbyBmYXIgdW5kZXIgMC4wNSwgaXQgaXMgbGlrZWx5IHRoYXQgdGhlIHBlcmNlbnRhZ2Ugb2YgcGVvcGxlIHdpdGggYSBiYWNoZWxvcidzIG9yIGhpZ2hlciBoYXMgc29tZSBzb3J0IG9mIHJlbGF0aW9uIHdpdGggdGhlIEVWIHBlcmNlbnRhZ2UgaW4gYSBzdGF0ZS4KCmBgYHtyfQpldmRhdGFUZXN0MSA8LSBldmRhdGFUZXN0MSAlPiUKICBtdXRhdGUoUHJlZGljdGVkUGVsZWMgPSBwcmVkaWN0KG1vZGVsMSwgbmV3ZGF0YSA9IGV2ZGF0YVRlc3QxKSkKCmdncGxvdChldmRhdGFUZXN0MSwgYWVzKFByZWRpY3RlZFBlbGVjLCBQZWxlY3RyaWMpKSArCiAgZ2VvbV9wb2ludCgpICsKICBnZW9tX2FibGluZShzbG9wZSA9IDEsIGludGVyY2VwdCA9IDAsIGNvbG9yID0gInJlZCIpICsKICB0aGVtZV9taW5pbWFsKCkgKwogIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLjUpKSArCiAgbGFicyh4ID0gIlByZWRpY3RlZCBQZXJjZW50YWdlIG9mIEVWcyBpbiBTdGF0ZSIsCiAgICAgICB5ID0gIlBlcmNlbnRhZ2Ugb2YgRVZzIGluIFN0YXRlIiwKICAgICAgIHRpdGxlID0gIlByZWRpY3RlZCB2cyBBY3R1YWwgUGVyY2VudGFnZSBvZiBFVnMgaW4gU3RhdGVzICgyMDIzKSIpICsKICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gc2NhbGVzOjpsYWJlbF9udW1iZXIoKSkKYGBgCldlIGNhbiBhbHNvIHR1cm4gdGhpcyBpbnRvIGEgZ3JhcGggdGhhdCBjb21wYXJlcyB0aGUgYWN0dWFsIHBlcmNlbnRhZ2Ugb2YgRVZzIGluIGEgc3RhdGUgd2l0aCB0aGUgcHJlZGljdGVkIHBlcmNlbnRhZ2Ugb2YgRVZzLiBJbiB0aGlzIGNhc2UsIHRoZSByZWQgbGluZSB3b3VsZCBiZSB0aGUgdGFyZ2V0LCB3aGVyZSB3ZSB3YW50IHRoZSBwcmVkaWN0ZWQgcGVyY2VudGFnZSB0byBiZSBleGFjdGx5IGVxdWFsIHRvIHRoZSBhY3R1YWwgcGVyY2VudGFnZS4KCmBgYHtyfQpzdW1tYXJ5X3RhYmxlMSA8LSBldmRhdGFUZXN0MSAlPiUKICBzZWxlY3QoUHJlZGljdGVkUGVsZWMsIFBlbGVjdHJpYykgJT4lCiAgc3VtbWFyeSgpCgpwcmludChzdW1tYXJ5X3RhYmxlMSkKYGBgCldlIGNhbiBhbHNvIG1ha2UgYSBzdW1tYXJ5IHRhYmxlIGNvbXBhcmluZyB0aGUgcHJlZGljdGVkIHBlcmNlbnRhZ2UgYW5kIHRoZSBhY3R1YWwgcGVyY2VudGFnZS4gU2ltaWxhciB0byB3aGF0IHdlIGNvdWxkIHNlZSBvbiB0aGUgZ3JhcGgsIHRoZSBtb2RlbCBpc24ndCBwYXJ0aWN1bGFybHkgYWNjdXJhdGUsIGVzcGVjaWFsbHkgd2l0aCB0aGUgbWluaW11bSBhbmQgbWF4aW11bSB2YWx1ZXMsIGJ1dCB0aGUgdGhlIHF1YXJ0aWxlcyBhbmQgbWVhbiBzaG93IHNvbWUgcHJvbWlzaW5nIGFjY3VyYWN5LgoKQW5vdGhlciB2YXJpYWJsZSB0aGF0IHdvdWxkIHN0cmlrZSBhcyBwb3RlbnRpYWxseSBzaWduaWZpY2FudCB0b3dhcmRzIHRoZSBFViBwZXJjZW50YWdlIGlzIHRoZSBwcmljZSBvZiBnYXMsIGVzcGVjaWFsbHkgaWYgaXQgaGFzIGluY3JlYXNlZCBvdmVyIHRpbWUuCgpgYGB7cn0KYXZnX0dfdmFsdWUgPC0gZXZkYXRhICU+JQogIGdyb3VwX2J5KHllYXIpICU+JQogIHN1bW1hcml6ZShhdmdfdmFsdWUgPSBtZWFuKEdwcmljZSkpCgpnZ3Bsb3QoYXZnX0dfdmFsdWUsIGFlcyh5ZWFyLCBhdmdfdmFsdWUpKSArCiAgZ2VvbV9jb2woZmlsbCA9ICJvcmFuZ2UiKSArCiAgbGFicyh4ID0gIlllYXIiLCB5ID0gIk5hdGlvbndpZGUgQXZlcmFnZSBHYXMgUHJpY2UiLAogICAgICAgdGl0bGUgPSAiTmF0aW9ud2lkZSBBdmVyYWdlIEdhcyBQcmljZSBieSBZZWFyIikgKwogIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLjUpKSArCiAgc2NhbGVfeF9jb250aW51b3VzKGJyZWFrcyA9IHNlcSgyMDE4LCAyMDIzLCBieSA9IDEpKQpgYGAKTmF0aW9ud2lkZSwgZ2FzIHByaWNlcyBkaWQgc2VlbSB0byBpbmNyZWFzZSBmcm9tIDIwMTYgZnJvbSAyMDIzLiBXZSBjYW4gcnVuIGFub3RoZXIgbGluZWFyIHJlZ3Jlc3Npb24gbW9kZWwgdG8gc2VlIGlmIHRoZSBwcmljZSBvZiBnYXMgaGFzIGFjdHVhbGx5IGJlZW4gYSBmYWN0b3IgaW4gRVYgcmVnaXN0cmF0aW9ucy4KCmBgYHtyfSAKc2V0LnNlZWQoMTIzKQoKdHJhaW5pbmRleDIgPC0gY3JlYXRlRGF0YVBhcnRpdGlvbihldmRhdGEkTnJlZ2lzdHJhdGlvbiwgcCA9IDAuOCwgbGlzdCA9IEZBTFNFLCB0aW1lcyA9IDEpCmV2ZGF0YVRyYWluMiA8LSBldmRhdGFjbGVhblsgdHJhaW5pbmRleCxdCmV2ZGF0YVRlc3QyIDwtIGV2ZGF0YWNsZWFuWy10cmFpbmluZGV4LF0KCm1vZGVsMiA8LSBsbShOcmVnaXN0cmF0aW9uIH4gR3ByaWNlLCBkYXRhID0gZXZkYXRhVHJhaW4pCnN1bW1hcnkobW9kZWwpCmBgYApUaGlzIHRpbWUsIHRoZSBSLXNxdWFyZWQgdmFsdWUgaG92ZXJzIGFyb3VuZCAwLjM0LCB3aGljaCBpcyBhYm91dCB0d2ljZSB0aGUgdmFsdWUgd2hlbiB1c2luZyB0aGUgcGVyY2VudGFnZSBvZiBiYWNoZWxvcidzIGRlZ3JlZXMgYXMgYSBmYWN0b3IuIFRoaXMgc3VnZ2VzdHMgdGhhdCB0aGUgZ2FzIHByaWNlIGlzIGEgbW9yZSBzaWduaWZpY2FudCBmYWN0b3IgaW4gRVYgcmVnaXN0cmF0aW9ucy4KCk9uY2UgYWdhaW4sIHdlIGNhbiB0dXJuIHRoaXMgaW50byBhIGdyYXBoIHdoZXJlIHRoZSByZWQgbGluZSB3b3VsZCBiZSByZXByZXNlbnRhdGl2ZSBvZiBhIHBlcmZlY3QgbW9kZWwuCgpgYGB7cn0KZXZkYXRhVGVzdDIgPC0gZXZkYXRhVGVzdDIgJT4lCiAgbXV0YXRlKFByZWRpY3RlZE5yZWcgPSBwcmVkaWN0KG1vZGVsLCBuZXdkYXRhID0gZXZkYXRhVGVzdDIpKQoKZ2dwbG90KGV2ZGF0YVRlc3QyLCBhZXMoTnJlZ2lzdHJhdGlvbiwgUHJlZGljdGVkTnJlZykpICsKICBnZW9tX3BvaW50KCkgKwogIGdlb21fYWJsaW5lKHNsb3BlID0gMSwgaW50ZXJjZXB0ID0gMCwgY29sb3IgPSAicmVkIikgKwogIHRoZW1lX21pbmltYWwoKSArCiAgdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChoanVzdCA9IDAuNSkpICsKICBsYWJzKHggPSAiQW5udWFsIEVWIFJlZ2lzdHJhdGlvbnMiLCB5ID0gIlByZWRpY3RlZCBBbm51YWwgRVYgUmVnaXN0cmF0aW9ucyIsCiAgICAgICB0aXRsZSA9ICJQcmVkaWN0ZWQgdnMgQWN0dWFsIEFubnVhbCBFViBSZWdpc3RyYXRpb25zIGluIFN0YXRlcyIpICsKICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gc2NhbGVzOjpsYWJlbF9udW1iZXIoKSkKYGBgCldoaWxlIHRoZXJlIGlzIHN0aWxsIHNvbWUgdmFyaWFuY2UgaW4gdGhpcyBtb2RlbCwgaXQgZG9lcyBzZWVtIHRvIGJlIG11Y2ggbW9yZSBjZW50ZXJlZCBhcm91bmQgdGhlIGdvYWwgdGhhbiB0aGUgbW9kZWwgdGhhdCB1c2VzIHRoZSBwZXJjZW50YWdlIG9mIGJhY2hlbG9yJ3MgZGVncmVlcy4KCmBgYHtyfQpzdW1tYXJ5X3RhYmxlMiA8LSBldmRhdGFUZXN0MiAlPiUKICBzZWxlY3QoTnJlZ2lzdHJhdGlvbiwgUHJlZGljdGVkTnJlZykgJT4lCiAgc3VtbWFyeSgpCgpwcmludChzdW1tYXJ5X3RhYmxlMikKYGBgClRoZSBzdW1tYXJ5IHRhYmxlIHdhcyBhbHNvIGlubmFjdXJhdGUgd2l0aCBtaW5pbXVtIGFuZCBtYXhpbXVtIHZhbHVlcywgYnV0IHNob3dlZCBzb21lIGFjY3VyYWN5IHdpdGggcXVhcnRpbGVzLgoKTW92aW5nIHRvd2FyZCB3aGF0IEVWIGRhdGEgY2FuIGJlIHVzZWQgdG8gcHJlZGljdCwgYSBjb21tb24gZXhhbXBsZSBpcyBhIHBvbGl0aWNhbCBwYXJ0eS4KCmBgYHtyfQpnZ3Bsb3QoZXZkYXRhY2xlYW4sIGFlcyhwYXJ0eSwgUGVsZWN0cmljLCBmaWxsID0gcGFydHkpKSArCiAgZ2VvbV9ib3hwbG90KHdpZHRoID0gMC4xKSArCiAgZ2VvbV92aW9saW4oYWxwaGEgPSAwLjMpICsKICAgbGFicyh4ID0gIlN0YXRlIFBvbGl0aWNhbCBQYXJ0eSIsIHkgPSAiUGVyY2VudGFnZSBvZiBFVnMiLAogICAgICAgdGl0bGUgPSAiUGVyY2VudGFnZSBvZiBFVnMgaW4gU3RhdGVzIGJ5IFBvbGl0aWNhbCBQYXJ0eSIpICsKICB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGhqdXN0ID0gMC41KSwgYXNwZWN0LnJhdGlvID0gMC43NSwgbGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKSArCiAgc2NhbGVfeF9kaXNjcmV0ZShleHBhbmQgPSBleHBhbnNpb24obXVsdCA9IGMoMC41LCAwLjUpKSkgKwogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IGMoIkRlbW9jcmF0aWMiID0gIiM0MjU1ZmYiLCAiUmVwdWJsaWNhbiIgPSAiI2ZmNDEzYiIpKQpgYGAKCkl0IHNlZW1zIHRoYXQgdGhlIG1ham9yaXR5IG9mIFJlcHVibGljYW4gc3RhdGVzIGFyZSBjb25jZW50cmF0ZWQgYXQgbGVzcyB0aGFuIDAuNSUgb2YgRVZzLCB3aGlsZSBEZW1vY3JhdGljIHN0YXRlcyBzZWVtIHRvIGhhdmUgYSBoaWdoZXIgcGVyY2VudGFnZSBhbmQgYXJlIG1vcmUgc3ByZWFkIG91dC4gTG9va2luZyBhdCB0aGlzLCB3ZSBjYW4gcnVuIGEgbG9naXN0aWMgcmVncmVzc2lvbiBtb2RlbCB0byBkZXRlcm1pbmUgaWYgdGhlcmUgaXMgYSB0aHJlc2hvbGQgdGhhdCBjYW4gYWNjdXJhdGVseSBwcmVkaWN0IGEgc3RhdGUncyBwb2xpdGljYWwgcGFydHkuCgpgYGB7cn0Kc2V0LnNlZWQoMTIzKQoKdHJhaW5JbmRleCA8LSBjcmVhdGVEYXRhUGFydGl0aW9uKGV2ZGF0YWNsZWFuJHBhcnR5LCBwID0gMC43LCBsaXN0ID0gRkFMU0UpCnRyYWluZGF0YSA8LSBldmRhdGFjbGVhblt0cmFpbkluZGV4LCBdCnRlc3RkYXRhIDwtIGV2ZGF0YWNsZWFuWy10cmFpbkluZGV4LCBdCgpsb2dyZWdtb2RlbCA8LSBnbG0ocGFydHkgfiBQZWxlY3RyaWMsIGRhdGEgPSB0cmFpbmRhdGEsIGZhbWlseSA9IGJpbm9taWFsKQpzdW1tYXJ5KGxvZ3JlZ21vZGVsKQpgYGAKYGBge3J9CnRpZHkobG9ncmVnbW9kZWwsIGV4cG9uZW50aWF0ZSA9IFRSVUUsIGNvbmYuaW50ID0gVFJVRSkKYGBgCgpUaGUgcC12YWx1ZSBpcyBmYXIgdW5kZXIgMC4wNSwgc28gaXQgaXMgdW5saWtlbHkgdGhhdCB0aGUgcGVyY2VudGFnZSBvZiBlbGVjdHJpYyBjYXJzIGluIGEgc3RhdGUgaXMgdW5yZWxhdGVkIHRvIHRoYXQgc3RhdGUncyBwb2xpdGljYWwgcGFydHkuCgpUbyBjaGVjayB0aGlzLCB3ZSBjYW4gY3JlYXRlIGFuIFJPQyBDdXJ2ZSB3aXRoIHRoZSBtb2RlbC4KCmBgYHtyfQp0ZXN0ZGF0YSRwcmVkaWN0ZWRwcm9iIDwtIHByZWRpY3QobG9ncmVnbW9kZWwsIG5ld2RhdGEgPSB0ZXN0ZGF0YSwgdHlwZSA9ICJyZXNwb25zZSIpCgpyb2N1cnZlIDwtIHJvYyh0ZXN0ZGF0YSRwYXJ0eSwgdGVzdGRhdGEkcHJlZGljdGVkcHJvYikKCnBsb3Qocm9jdXJ2ZSwgY29sID0gImJsdWUiLCBtYWluID0gIlJPQyBDdXJ2ZSIpCmBgYAoKV2UgY2FuIGZpbmQgdGhlIHBvaW50IG9uIHRoaXMgY3VydmUgdGhhdCBkZW1vbnN0cmF0ZXMgdGhlIG1vc3QgZWZmZWN0aXZlIHRocmVzaG9sZCBhbmQgdXNlIHRoYXQgdGhyZXNob2xkIGluIGEgY29uZnVzaW9uIG1hdHJpeC4KCmBgYHtyfQpiZXN0Y29vcmQgPC0gY29vcmRzKHJvY3VydmUsICJiZXN0IiwgcmV0ID0gYygidGhyZXNob2xkIiwgInNlbnNpdGl2aXR5IiwgInNwZWNpZmljaXR5IiwgImFjY3VyYWN5IiksCiAgICAgICAgICAgICAgICAgICAgYmVzdC5tZXRob2QgPSAiY2xvc2VzdC50b3BsZWZ0IikKcHJpbnQoYmVzdGNvb3JkKQoKdGVzdGRhdGEkcHJlZGljdGVkcGFydHkgPC0gaWZlbHNlKHRlc3RkYXRhJHByZWRpY3RlZHByb2IgPCAwLjU1NzQwMjQsICJEZW1vY3JhdGljIiwgIlJlcHVibGljYW4iKQoKdGVzdGRhdGEkcHJlZGljdGVkcGFydHkgPC0gZmFjdG9yKHRlc3RkYXRhJHByZWRpY3RlZHBhcnR5LCBsZXZlbHMgPSBjKCJEZW1vY3JhdGljIiwgIlJlcHVibGljYW4iKSkKCmNvbmZ1c2lvbk1hdHJpeCh0ZXN0ZGF0YSRwcmVkaWN0ZWRwYXJ0eSwgdGVzdGRhdGEkcGFydHkpCmBgYA==