Lastly, in this notebook I show the performance of my new QC+ metric. Again following Baseball Prospectus, in “Prospectus Feature: OPS and wOBA, Briefly Revisited” Jonathan Judge lays out the way the crew at BP goes about validating metrics. He breaks it down into three contribution measures:

In another article, “The Performance Case for DRC+,” Judge says the order of importance for those three is: reliability, predictiveness, and descriptiveness.

In order to compute the reliability performance I found each batter with more than 250 batted balls in back to back seasons (’15 & ’16, ’16 & ’17, ’17 & ’18, and ’18 & ’19). I then calculated each batter’s average QC+ in each season and found the correlation between the two. I ended up with four correlations (’15 with ’16, ’16 with ’17, ’17 with ’18, and ’18 with ’19) and found the mean of those four correlations. The result was an estimated reliability performance of 0.695. Comparatively, I found the reliability correlation of wOBAcon to be 0.628. So, it’s doing a better job than a measured metric. Not bad.

Next, for predictive and descriptive peformance I had to first calculate each team’s runs/PA for the 2015-2020 seasons. I then found each team’s average QC+ for the 2015-2019 seasons. For predictiveness, I found the correlation with year1’s QC+ and year2’s runs/PA (i.e., QC+ in 2015 with runs/PA in 2016). I had some far ranging values, from .239 all the way up to .651, so that was pretty interesting. Overall, the average correlation was .439.

For descriptiveness, I calculated the corelation with each team’s average QC+ and runs/PA for each season. It stuck out to me that the correlation started low at .593 in 2015, but, for the most part, kept increasing until 2019 where the correlation was .827. In total, the average correlation was .703.

Overall, I’m pretty happy with the performance of this metric. While it doesn’t compare to metrics such as DRC+ or Connor Kurcon’s TrueHit, that wasn’t the goal of this project. Making a metric from stratch was incredibly fun and a great learning experience. As I keep learning, I hope to make the metric stronger. If you made it this far, thanks for following along.

I’d like to thank Andrew Perpetua, Harry Pavlidis, and Jonathan Judge for guidance at various stages of this project.

Reliability Performance

group.2015 <- batted_ball_results_2015 %>%
  group_by(player_name) %>%
  summarise(BBE_2015 = n(),
            avg_qcp_2015 = mean(qcp),
            woba_con_2015 = mean(woba_value / woba_denom, na.rm=T)) %>%
  filter(BBE_2015 > 250)

group.2016 <- batted_ball_results_2016 %>%
  group_by(player_name) %>%
  summarise(BBE_2016 = n(),
            avg_qcp_2016 = mean(qcp),
            woba_con_2016 = mean(woba_value / woba_denom, na.rm=T)) %>%
  filter(BBE_2016 > 250)

group.2017 <- batted_ball_results_2017 %>%
  group_by(player_name) %>%
  summarise(BBE_2017 = n(),
            avg_qcp_2017 = mean(qcp),
            woba_con_2017 = mean(woba_value / woba_denom, na.rm=T)) %>%
  filter(BBE_2017 > 250)

group.2018 <- batted_ball_results_2018 %>%
  group_by(player_name) %>%
  summarise(BBE_2018 = n(),
            avg_qcp_2018 = mean(qcp),
            woba_con_2018 = mean(woba_value / woba_denom, na.rm=T)) %>%
  filter(BBE_2018 > 250)

group.2019 <- batted_ball_results_2019 %>%
  group_by(player_name) %>%
  summarise(BBE_2019 = n(),
            avg_qcp_2019 = mean(qcp),
            woba_con_2019 = mean(woba_value / woba_denom, na.rm=T)) %>%
  filter(BBE_2019 > 250)

# 2015-2016
joined_15_16 <- left_join(group.2015, group.2016, by = "player_name") %>%
  filter(player_name %in% group.2015$player_name & player_name %in% group.2016$player_name)

cor(joined_15_16$avg_qcp_2015, joined_15_16$avg_qcp_2016) # n > 250 = 0.7783435 ##########################################
summary(lm(avg_qcp_2016 ~ avg_qcp_2015, data = joined_15_16)) # n > 250 =  0.6058
#cor this season qcp with next wobacon
cor(joined_15_16$avg_qcp_2015, joined_15_16$woba_con_2016) # n > 250 =  0.7238685
summary(lm(woba_con_2016 ~ avg_qcp_2015, data = joined_15_16)) # n > 250, R^2 = 0.524

# 2016-2017
joined_16_17 <- left_join(group.2016, group.2017, by = "player_name") %>%
  filter(player_name %in% group.2016$player_name & player_name %in% group.2017$player_name)

cor(joined_16_17$avg_qcp_2016, joined_16_17$avg_qcp_2017) # n > 250 = 0.5735659 ##########################################
summary(lm(avg_qcp_2017 ~ avg_qcp_2016, data = joined_16_17)) # n > 250 =  0.329
#cor this season qcp with next wobacon
cor(joined_16_17$avg_qcp_2016, joined_16_17$woba_con_2017) # n > 250 =  0.455185
summary(lm(woba_con_2017 ~ avg_qcp_2016, data = joined_16_17)) # n > 250, R^2 = 0.2072

# 2017-2018
joined_17_18 <- left_join(group.2017, group.2018, by = "player_name") %>%
  filter(player_name %in% group.2017$player_name & player_name %in% group.2018$player_name)

cor(joined_17_18$avg_qcp_2017, joined_17_18$avg_qcp_2018) # n > 250 = 0.7253101 ##########################################
summary(lm(avg_qcp_2018 ~ avg_qcp_2017, data = joined_17_18)) # n > 250 =  0.5261
#cor this season qcp with next wobacon
cor(joined_17_18$avg_qcp_2017, joined_17_18$woba_con_2018) # n > 250 =  0.6887269
summary(lm(woba_con_2018 ~ avg_qcp_2017, data = joined_17_18)) # n > 250, R^2 = 0.4743

# 2018-2019
joined_18_19 <- left_join(group.2018, group.2019, by = "player_name") %>%
  filter(player_name %in% group.2018$player_name & player_name %in% group.2019$player_name)

cor(joined_18_19$avg_qcp_2018, joined_18_19$avg_qcp_2019) # n > 250 = 0.7026071 ##########################################
summary(lm(avg_qcp_2019 ~ avg_qcp_2018, data = joined_18_19)) # n > 250 =  0.4937
#cor this season qcp with next wobacon
cor(joined_18_19$avg_qcp_2018, joined_18_19$woba_con_2019) # n > 250 =  0.6328501
summary(lm(woba_con_2019 ~ avg_qcp_2018, data = joined_18_19)) # n > 250, R^2 = 0.4005

avg_rel_perf <- mean(c(cor(joined_15_16$avg_qcp_2015, joined_15_16$avg_qcp_2016), cor(joined_16_17$avg_qcp_2016, joined_16_17$avg_qcp_2017), 
                       cor(joined_17_18$avg_qcp_2017, joined_17_18$avg_qcp_2018), cor(joined_18_19$avg_qcp_2018, joined_18_19$avg_qcp_2019)))
avg_rel_perf #0.6949567


avg_rel_wobacon <- mean(c(cor(joined_15_16$woba_con_2015, joined_15_16$woba_con_2016), cor(joined_16_17$woba_con_2016, joined_16_17$woba_con_2017), 
                       cor(joined_17_18$woba_con_2017, joined_17_18$woba_con_2018), cor(joined_18_19$woba_con_2018, joined_18_19$woba_con_2019)))
avg_rel_wobacon # 0.6284415

Calculating runs/PA for each team 2015-2020.

teams_scoring_2 <- read_csv("teams_scoring_2.csv")

qcp_2015 <- batted_ball_results_2015 %>%
  group_by(batter_team) %>%
  summarise(bbe_2015 = n(),
            qcp_2015 = mean(qcp))

qcp_2016 <- batted_ball_results_2016 %>%
  group_by(batter_team) %>%
  summarise(bbe_2016 = n(),
            qcp_2016 = mean(qcp))

qcp_2017 <- batted_ball_results_2017 %>%
  group_by(batter_team) %>%
  summarise(bbe_2017 = n(),
            qcp_2017 = mean(qcp))

qcp_2018 <- batted_ball_results_2018 %>%
  group_by(batter_team) %>%
  summarise(bbe_2018 = n(),
            qcp_2018 = mean(qcp))

qcp_2019 <- batted_ball_results_2019 %>%
  group_by(batter_team) %>%
  summarise(bbe_2019 = n(),
            qcp_2019 = mean(qcp))

qcp_all <- left_join(qcp_2015, qcp_2016, by = "batter_team") %>%
  left_join(., qcp_2017, by = "batter_team") %>%
  left_join(., qcp_2018, by = "batter_team") %>%
  left_join(., qcp_2019, by = "batter_team")

qcp_and_scoring <- left_join(x = qcp_all, y = teams_scoring_2, by = c("batter_team" = "Team"))

qcp_and_scoring %<>%
  mutate(total_pa = G * PA,
         total_r = G * R,
         runs_per_pa = total_r / total_pa)

Predictive Performance

### 2015 qcp to 2016 runs/pa
pred_15_16 <- qcp_and_scoring %>%
  filter(Year == 2016) %$%
  cor(runs_per_pa, qcp_2015) # 0.6096966

### 2016 qcp to 2017 runs/pa
pred_16_17 <- qcp_and_scoring %>%
  filter(Year == 2017) %$%
  cor(runs_per_pa, qcp_2016) #0.3272711

### 2017 qcp to 2018 runs/pa
pred_17_18 <- qcp_and_scoring %>%
  filter(Year == 2018) %$%
  cor(runs_per_pa, qcp_2017) #  0.2390079

### 2018 qcp to 2019 runs/pa
pred_18_19 <- qcp_and_scoring %>%
  filter(Year == 2019) %$%
  cor(runs_per_pa, qcp_2018) #  0.6513941

### 2019 qcp to 2020 runs/pa
pred_19_20 <- qcp_and_scoring %>%
  filter(Year == 2020) %$%
  cor(runs_per_pa, qcp_2019) #  0.3568525

avg_pred_perf <- mean(c(pred_15_16, pred_16_17, pred_17_18, pred_18_19, pred_19_20))
avg_pred_perf #0.4368444

Descriptive Performance

### 2015
des_2015 <- qcp_and_scoring %>%
  filter(Year == 2015) %$%
  cor(runs_per_pa, qcp_2015) # 0.5933172

### 2016
des_2016 <- qcp_and_scoring %>%
  filter(Year == 2016) %$%
  cor(runs_per_pa, qcp_2016) #0.6646133

### 2017
des_2017 <- qcp_and_scoring %>%
  filter(Year == 2017) %$%
  cor(runs_per_pa, qcp_2017) #  0.659218

### 2018
des_2018 <- qcp_and_scoring %>%
  filter(Year == 2018) %$%
  cor(runs_per_pa, qcp_2018) #  0.7716518

### 2019
des_2019 <- qcp_and_scoring %>%
  filter(Year == 2019) %$%
  cor(runs_per_pa, qcp_2019) # 0.8270482

avg_des_perf <- mean(c(des_2015, des_2016, des_2017, des_2018, des_2019))
avg_des_perf #0.7031697
LS0tCnRpdGxlOiAiQ3JlYXRpbmcgUUMrIFBhcnQgNCAtIE1ldHJpYyBWYWxpZGF0aW9uIgpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKLS0tCgpMYXN0bHksIGluIHRoaXMgbm90ZWJvb2sgSSBzaG93IHRoZSBwZXJmb3JtYW5jZSBvZiBteSBuZXcgUUMrIG1ldHJpYy4gQWdhaW4gZm9sbG93aW5nIEJhc2ViYWxsIFByb3NwZWN0dXMsIGluICJQcm9zcGVjdHVzIEZlYXR1cmU6IE9QUyBhbmQgd09CQSwgQnJpZWZseSBSZXZpc2l0ZWQiIEpvbmF0aGFuIEp1ZGdlIGxheXMgb3V0IHRoZSB3YXkgdGhlIGNyZXcgYXQgQlAgZ29lcyBhYm91dCB2YWxpZGF0aW5nIG1ldHJpY3MuIEhlIGJyZWFrcyBpdCBkb3duIGludG8gdGhyZWUgY29udHJpYnV0aW9uIG1lYXN1cmVzOgoKKiBEZXNjcmlwdGl2ZSBQZXJmb3JtYW5jZTogdGhlIGNvcnJlbGF0aW9uIGJldHdlZW4gdGhlIG1ldHJpYyBhbmQgc2FtZS15ZWFyIHRlYW0gcnVucy9QQTsKKiBSZWxpYWJpbGl0eSBQZXJmb3JtYW5jZTogdGhlIGNvcnJlbGF0aW9uIGJldHdlZW4gdGhlIG1ldHJpYyBhbmQgaXRzZWxmIGluIHRoZSBmb2xsb3dpbmcgeWVhcjsgYW5kCiogUHJlZGljdGl2ZSBQZXJmb3JtYW5jZTogdGhlIGNvcnJlbGF0aW9uIGJldHdlZW4gdGhlIG1ldHJpYyBhbmQgdGhlIGZvbGxvd2luZyB5ZWFy4oCZcyBydW5zL1BBLgoKSW4gYW5vdGhlciBhcnRpY2xlLCAiVGhlIFBlcmZvcm1hbmNlIENhc2UgZm9yIERSQyssIiBKdWRnZSBzYXlzIHRoZSBvcmRlciBvZiBpbXBvcnRhbmNlIGZvciB0aG9zZSB0aHJlZSBpczogcmVsaWFiaWxpdHksIHByZWRpY3RpdmVuZXNzLCBhbmQgZGVzY3JpcHRpdmVuZXNzLiAKCkluIG9yZGVyIHRvIGNvbXB1dGUgdGhlIHJlbGlhYmlsaXR5IHBlcmZvcm1hbmNlIEkgZm91bmQgZWFjaCBiYXR0ZXIgd2l0aCBtb3JlIHRoYW4gMjUwIGJhdHRlZCBiYWxscyBpbiBiYWNrIHRvIGJhY2sgc2Vhc29ucyAoJzE1ICYgJzE2LCAnMTYgJiAnMTcsICcxNyAmICcxOCwgYW5kICcxOCAmICcxOSkuIEkgdGhlbiBjYWxjdWxhdGVkIGVhY2ggYmF0dGVyJ3MgYXZlcmFnZSBRQysgaW4gZWFjaCBzZWFzb24gYW5kIGZvdW5kIHRoZSBjb3JyZWxhdGlvbiBiZXR3ZWVuIHRoZSB0d28uIEkgZW5kZWQgdXAgd2l0aCBmb3VyIGNvcnJlbGF0aW9ucyAoJzE1IHdpdGggJzE2LCAnMTYgd2l0aCAnMTcsICcxNyB3aXRoICcxOCwgYW5kICcxOCB3aXRoICcxOSkgYW5kIGZvdW5kIHRoZSBtZWFuIG9mIHRob3NlIGZvdXIgY29ycmVsYXRpb25zLiBUaGUgcmVzdWx0IHdhcyBhbiBlc3RpbWF0ZWQgcmVsaWFiaWxpdHkgcGVyZm9ybWFuY2Ugb2YgMC42OTUuIENvbXBhcmF0aXZlbHksIEkgZm91bmQgdGhlIHJlbGlhYmlsaXR5IGNvcnJlbGF0aW9uIG9mIHdPQkFjb24gdG8gYmUgMC42MjguIFNvLCBpdCdzIGRvaW5nIGEgYmV0dGVyIGpvYiB0aGFuIGEgbWVhc3VyZWQgbWV0cmljLiBOb3QgYmFkLgoKTmV4dCwgZm9yIHByZWRpY3RpdmUgYW5kIGRlc2NyaXB0aXZlIHBlZm9ybWFuY2UgSSBoYWQgdG8gZmlyc3QgY2FsY3VsYXRlIGVhY2ggdGVhbSdzIHJ1bnMvUEEgZm9yIHRoZSAyMDE1LTIwMjAgc2Vhc29ucy4gSSB0aGVuIGZvdW5kIGVhY2ggdGVhbSdzIGF2ZXJhZ2UgUUMrIGZvciB0aGUgMjAxNS0yMDE5IHNlYXNvbnMuIEZvciBwcmVkaWN0aXZlbmVzcywgSSBmb3VuZCB0aGUgY29ycmVsYXRpb24gd2l0aCB5ZWFyMSdzIFFDKyBhbmQgeWVhcjIncyBydW5zL1BBIChpLmUuLCBRQysgaW4gMjAxNSB3aXRoIHJ1bnMvUEEgaW4gMjAxNikuIEkgaGFkIHNvbWUgZmFyIHJhbmdpbmcgdmFsdWVzLCBmcm9tIC4yMzkgYWxsIHRoZSB3YXkgdXAgdG8gLjY1MSwgc28gdGhhdCB3YXMgcHJldHR5IGludGVyZXN0aW5nLiBPdmVyYWxsLCB0aGUgYXZlcmFnZSBjb3JyZWxhdGlvbiB3YXMgLjQzOS4gCgpGb3IgZGVzY3JpcHRpdmVuZXNzLCBJIGNhbGN1bGF0ZWQgdGhlIGNvcmVsYXRpb24gd2l0aCBlYWNoIHRlYW0ncyBhdmVyYWdlIFFDKyBhbmQgcnVucy9QQSBmb3IgZWFjaCBzZWFzb24uIEl0IHN0dWNrIG91dCB0byBtZSB0aGF0IHRoZSBjb3JyZWxhdGlvbiBzdGFydGVkIGxvdyBhdCAuNTkzIGluIDIwMTUsIGJ1dCwgZm9yIHRoZSBtb3N0IHBhcnQsIGtlcHQgaW5jcmVhc2luZyB1bnRpbCAyMDE5IHdoZXJlIHRoZSBjb3JyZWxhdGlvbiB3YXMgLjgyNy4gSW4gdG90YWwsIHRoZSBhdmVyYWdlIGNvcnJlbGF0aW9uIHdhcyAuNzAzLgoKKiBBdmVyYWdlIHJlbGlhYmlsaXR5IHBlcmZvcm1hbmNlIGZvciBRQys6IDAuNjk1MAoqIEF2ZXJhZ2UgcHJlZGljdGl2ZSBwZWZvcm1hbmNlIGZvciBRQys6IDAuNDM2OAoqIEF2ZXJhZ2UgZGVzY3JpcHRpdmUgcGVmb3JtYW5jZSBmb3IgUUMrOiAwLjcwMzIKCk92ZXJhbGwsIEknbSBwcmV0dHkgaGFwcHkgd2l0aCB0aGUgcGVyZm9ybWFuY2Ugb2YgdGhpcyBtZXRyaWMuIFdoaWxlIGl0IGRvZXNuJ3QgY29tcGFyZSB0byBtZXRyaWNzIHN1Y2ggYXMgRFJDKyBvciBDb25ub3IgS3VyY29uJ3MgVHJ1ZUhpdCwgdGhhdCB3YXNuJ3QgdGhlIGdvYWwgb2YgdGhpcyBwcm9qZWN0LiBNYWtpbmcgYSBtZXRyaWMgZnJvbSBzdHJhdGNoIHdhcyBpbmNyZWRpYmx5IGZ1biBhbmQgYSBncmVhdCBsZWFybmluZyBleHBlcmllbmNlLiBBcyBJIGtlZXAgbGVhcm5pbmcsIEkgaG9wZSB0byBtYWtlIHRoZSBtZXRyaWMgc3Ryb25nZXIuIElmIHlvdSBtYWRlIGl0IHRoaXMgZmFyLCB0aGFua3MgZm9yIGZvbGxvd2luZyBhbG9uZy4gCgoqSSdkIGxpa2UgdG8gdGhhbmsgQW5kcmV3IFBlcnBldHVhLCBIYXJyeSBQYXZsaWRpcywgYW5kIEpvbmF0aGFuIEp1ZGdlIGZvciBndWlkYW5jZSBhdCB2YXJpb3VzIHN0YWdlcyBvZiB0aGlzIHByb2plY3QuKgoKKipSZWxpYWJpbGl0eSBQZXJmb3JtYW5jZSoqCmBgYHtyIGVjaG89VFJVRX0KZ3JvdXAuMjAxNSA8LSBiYXR0ZWRfYmFsbF9yZXN1bHRzXzIwMTUgJT4lCiAgZ3JvdXBfYnkocGxheWVyX25hbWUpICU+JQogIHN1bW1hcmlzZShCQkVfMjAxNSA9IG4oKSwKICAgICAgICAgICAgYXZnX3FjcF8yMDE1ID0gbWVhbihxY3ApLAogICAgICAgICAgICB3b2JhX2Nvbl8yMDE1ID0gbWVhbih3b2JhX3ZhbHVlIC8gd29iYV9kZW5vbSwgbmEucm09VCkpICU+JQogIGZpbHRlcihCQkVfMjAxNSA+IDI1MCkKCmdyb3VwLjIwMTYgPC0gYmF0dGVkX2JhbGxfcmVzdWx0c18yMDE2ICU+JQogIGdyb3VwX2J5KHBsYXllcl9uYW1lKSAlPiUKICBzdW1tYXJpc2UoQkJFXzIwMTYgPSBuKCksCiAgICAgICAgICAgIGF2Z19xY3BfMjAxNiA9IG1lYW4ocWNwKSwKICAgICAgICAgICAgd29iYV9jb25fMjAxNiA9IG1lYW4od29iYV92YWx1ZSAvIHdvYmFfZGVub20sIG5hLnJtPVQpKSAlPiUKICBmaWx0ZXIoQkJFXzIwMTYgPiAyNTApCgpncm91cC4yMDE3IDwtIGJhdHRlZF9iYWxsX3Jlc3VsdHNfMjAxNyAlPiUKICBncm91cF9ieShwbGF5ZXJfbmFtZSkgJT4lCiAgc3VtbWFyaXNlKEJCRV8yMDE3ID0gbigpLAogICAgICAgICAgICBhdmdfcWNwXzIwMTcgPSBtZWFuKHFjcCksCiAgICAgICAgICAgIHdvYmFfY29uXzIwMTcgPSBtZWFuKHdvYmFfdmFsdWUgLyB3b2JhX2Rlbm9tLCBuYS5ybT1UKSkgJT4lCiAgZmlsdGVyKEJCRV8yMDE3ID4gMjUwKQoKZ3JvdXAuMjAxOCA8LSBiYXR0ZWRfYmFsbF9yZXN1bHRzXzIwMTggJT4lCiAgZ3JvdXBfYnkocGxheWVyX25hbWUpICU+JQogIHN1bW1hcmlzZShCQkVfMjAxOCA9IG4oKSwKICAgICAgICAgICAgYXZnX3FjcF8yMDE4ID0gbWVhbihxY3ApLAogICAgICAgICAgICB3b2JhX2Nvbl8yMDE4ID0gbWVhbih3b2JhX3ZhbHVlIC8gd29iYV9kZW5vbSwgbmEucm09VCkpICU+JQogIGZpbHRlcihCQkVfMjAxOCA+IDI1MCkKCmdyb3VwLjIwMTkgPC0gYmF0dGVkX2JhbGxfcmVzdWx0c18yMDE5ICU+JQogIGdyb3VwX2J5KHBsYXllcl9uYW1lKSAlPiUKICBzdW1tYXJpc2UoQkJFXzIwMTkgPSBuKCksCiAgICAgICAgICAgIGF2Z19xY3BfMjAxOSA9IG1lYW4ocWNwKSwKICAgICAgICAgICAgd29iYV9jb25fMjAxOSA9IG1lYW4od29iYV92YWx1ZSAvIHdvYmFfZGVub20sIG5hLnJtPVQpKSAlPiUKICBmaWx0ZXIoQkJFXzIwMTkgPiAyNTApCgojIDIwMTUtMjAxNgpqb2luZWRfMTVfMTYgPC0gbGVmdF9qb2luKGdyb3VwLjIwMTUsIGdyb3VwLjIwMTYsIGJ5ID0gInBsYXllcl9uYW1lIikgJT4lCiAgZmlsdGVyKHBsYXllcl9uYW1lICVpbiUgZ3JvdXAuMjAxNSRwbGF5ZXJfbmFtZSAmIHBsYXllcl9uYW1lICVpbiUgZ3JvdXAuMjAxNiRwbGF5ZXJfbmFtZSkKCmNvcihqb2luZWRfMTVfMTYkYXZnX3FjcF8yMDE1LCBqb2luZWRfMTVfMTYkYXZnX3FjcF8yMDE2KSAjIG4gPiAyNTAgPSAwLjc3ODM0MzUgIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjCnN1bW1hcnkobG0oYXZnX3FjcF8yMDE2IH4gYXZnX3FjcF8yMDE1LCBkYXRhID0gam9pbmVkXzE1XzE2KSkgIyBuID4gMjUwID0gIDAuNjA1OAojY29yIHRoaXMgc2Vhc29uIHFjcCB3aXRoIG5leHQgd29iYWNvbgpjb3Ioam9pbmVkXzE1XzE2JGF2Z19xY3BfMjAxNSwgam9pbmVkXzE1XzE2JHdvYmFfY29uXzIwMTYpICMgbiA+IDI1MCA9ICAwLjcyMzg2ODUKc3VtbWFyeShsbSh3b2JhX2Nvbl8yMDE2IH4gYXZnX3FjcF8yMDE1LCBkYXRhID0gam9pbmVkXzE1XzE2KSkgIyBuID4gMjUwLCBSXjIgPSAwLjUyNAoKIyAyMDE2LTIwMTcKam9pbmVkXzE2XzE3IDwtIGxlZnRfam9pbihncm91cC4yMDE2LCBncm91cC4yMDE3LCBieSA9ICJwbGF5ZXJfbmFtZSIpICU+JQogIGZpbHRlcihwbGF5ZXJfbmFtZSAlaW4lIGdyb3VwLjIwMTYkcGxheWVyX25hbWUgJiBwbGF5ZXJfbmFtZSAlaW4lIGdyb3VwLjIwMTckcGxheWVyX25hbWUpCgpjb3Ioam9pbmVkXzE2XzE3JGF2Z19xY3BfMjAxNiwgam9pbmVkXzE2XzE3JGF2Z19xY3BfMjAxNykgIyBuID4gMjUwID0gMC41NzM1NjU5ICMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIwpzdW1tYXJ5KGxtKGF2Z19xY3BfMjAxNyB+IGF2Z19xY3BfMjAxNiwgZGF0YSA9IGpvaW5lZF8xNl8xNykpICMgbiA+IDI1MCA9ICAwLjMyOQojY29yIHRoaXMgc2Vhc29uIHFjcCB3aXRoIG5leHQgd29iYWNvbgpjb3Ioam9pbmVkXzE2XzE3JGF2Z19xY3BfMjAxNiwgam9pbmVkXzE2XzE3JHdvYmFfY29uXzIwMTcpICMgbiA+IDI1MCA9ICAwLjQ1NTE4NQpzdW1tYXJ5KGxtKHdvYmFfY29uXzIwMTcgfiBhdmdfcWNwXzIwMTYsIGRhdGEgPSBqb2luZWRfMTZfMTcpKSAjIG4gPiAyNTAsIFJeMiA9IDAuMjA3MgoKIyAyMDE3LTIwMTgKam9pbmVkXzE3XzE4IDwtIGxlZnRfam9pbihncm91cC4yMDE3LCBncm91cC4yMDE4LCBieSA9ICJwbGF5ZXJfbmFtZSIpICU+JQogIGZpbHRlcihwbGF5ZXJfbmFtZSAlaW4lIGdyb3VwLjIwMTckcGxheWVyX25hbWUgJiBwbGF5ZXJfbmFtZSAlaW4lIGdyb3VwLjIwMTgkcGxheWVyX25hbWUpCgpjb3Ioam9pbmVkXzE3XzE4JGF2Z19xY3BfMjAxNywgam9pbmVkXzE3XzE4JGF2Z19xY3BfMjAxOCkgIyBuID4gMjUwID0gMC43MjUzMTAxICMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIwpzdW1tYXJ5KGxtKGF2Z19xY3BfMjAxOCB+IGF2Z19xY3BfMjAxNywgZGF0YSA9IGpvaW5lZF8xN18xOCkpICMgbiA+IDI1MCA9ICAwLjUyNjEKI2NvciB0aGlzIHNlYXNvbiBxY3Agd2l0aCBuZXh0IHdvYmFjb24KY29yKGpvaW5lZF8xN18xOCRhdmdfcWNwXzIwMTcsIGpvaW5lZF8xN18xOCR3b2JhX2Nvbl8yMDE4KSAjIG4gPiAyNTAgPSAgMC42ODg3MjY5CnN1bW1hcnkobG0od29iYV9jb25fMjAxOCB+IGF2Z19xY3BfMjAxNywgZGF0YSA9IGpvaW5lZF8xN18xOCkpICMgbiA+IDI1MCwgUl4yID0gMC40NzQzCgojIDIwMTgtMjAxOQpqb2luZWRfMThfMTkgPC0gbGVmdF9qb2luKGdyb3VwLjIwMTgsIGdyb3VwLjIwMTksIGJ5ID0gInBsYXllcl9uYW1lIikgJT4lCiAgZmlsdGVyKHBsYXllcl9uYW1lICVpbiUgZ3JvdXAuMjAxOCRwbGF5ZXJfbmFtZSAmIHBsYXllcl9uYW1lICVpbiUgZ3JvdXAuMjAxOSRwbGF5ZXJfbmFtZSkKCmNvcihqb2luZWRfMThfMTkkYXZnX3FjcF8yMDE4LCBqb2luZWRfMThfMTkkYXZnX3FjcF8yMDE5KSAjIG4gPiAyNTAgPSAwLjcwMjYwNzEgIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjCnN1bW1hcnkobG0oYXZnX3FjcF8yMDE5IH4gYXZnX3FjcF8yMDE4LCBkYXRhID0gam9pbmVkXzE4XzE5KSkgIyBuID4gMjUwID0gIDAuNDkzNwojY29yIHRoaXMgc2Vhc29uIHFjcCB3aXRoIG5leHQgd29iYWNvbgpjb3Ioam9pbmVkXzE4XzE5JGF2Z19xY3BfMjAxOCwgam9pbmVkXzE4XzE5JHdvYmFfY29uXzIwMTkpICMgbiA+IDI1MCA9ICAwLjYzMjg1MDEKc3VtbWFyeShsbSh3b2JhX2Nvbl8yMDE5IH4gYXZnX3FjcF8yMDE4LCBkYXRhID0gam9pbmVkXzE4XzE5KSkgIyBuID4gMjUwLCBSXjIgPSAwLjQwMDUKCmF2Z19yZWxfcGVyZiA8LSBtZWFuKGMoY29yKGpvaW5lZF8xNV8xNiRhdmdfcWNwXzIwMTUsIGpvaW5lZF8xNV8xNiRhdmdfcWNwXzIwMTYpLCBjb3Ioam9pbmVkXzE2XzE3JGF2Z19xY3BfMjAxNiwgam9pbmVkXzE2XzE3JGF2Z19xY3BfMjAxNyksIAogICAgICAgICAgICAgICAgICAgICAgIGNvcihqb2luZWRfMTdfMTgkYXZnX3FjcF8yMDE3LCBqb2luZWRfMTdfMTgkYXZnX3FjcF8yMDE4KSwgY29yKGpvaW5lZF8xOF8xOSRhdmdfcWNwXzIwMTgsIGpvaW5lZF8xOF8xOSRhdmdfcWNwXzIwMTkpKSkKYXZnX3JlbF9wZXJmICMwLjY5NDk1NjcKCgphdmdfcmVsX3dvYmFjb24gPC0gbWVhbihjKGNvcihqb2luZWRfMTVfMTYkd29iYV9jb25fMjAxNSwgam9pbmVkXzE1XzE2JHdvYmFfY29uXzIwMTYpLCBjb3Ioam9pbmVkXzE2XzE3JHdvYmFfY29uXzIwMTYsIGpvaW5lZF8xNl8xNyR3b2JhX2Nvbl8yMDE3KSwgCiAgICAgICAgICAgICAgICAgICAgICAgY29yKGpvaW5lZF8xN18xOCR3b2JhX2Nvbl8yMDE3LCBqb2luZWRfMTdfMTgkd29iYV9jb25fMjAxOCksIGNvcihqb2luZWRfMThfMTkkd29iYV9jb25fMjAxOCwgam9pbmVkXzE4XzE5JHdvYmFfY29uXzIwMTkpKSkKYXZnX3JlbF93b2JhY29uICMgMC42Mjg0NDE1CmBgYAoKQ2FsY3VsYXRpbmcgcnVucy9QQSBmb3IgZWFjaCB0ZWFtIDIwMTUtMjAyMC4KYGBge3J9CnRlYW1zX3Njb3JpbmdfMiA8LSByZWFkX2NzdigidGVhbXNfc2NvcmluZ18yLmNzdiIpCgpxY3BfMjAxNSA8LSBiYXR0ZWRfYmFsbF9yZXN1bHRzXzIwMTUgJT4lCiAgZ3JvdXBfYnkoYmF0dGVyX3RlYW0pICU+JQogIHN1bW1hcmlzZShiYmVfMjAxNSA9IG4oKSwKICAgICAgICAgICAgcWNwXzIwMTUgPSBtZWFuKHFjcCkpCgpxY3BfMjAxNiA8LSBiYXR0ZWRfYmFsbF9yZXN1bHRzXzIwMTYgJT4lCiAgZ3JvdXBfYnkoYmF0dGVyX3RlYW0pICU+JQogIHN1bW1hcmlzZShiYmVfMjAxNiA9IG4oKSwKICAgICAgICAgICAgcWNwXzIwMTYgPSBtZWFuKHFjcCkpCgpxY3BfMjAxNyA8LSBiYXR0ZWRfYmFsbF9yZXN1bHRzXzIwMTcgJT4lCiAgZ3JvdXBfYnkoYmF0dGVyX3RlYW0pICU+JQogIHN1bW1hcmlzZShiYmVfMjAxNyA9IG4oKSwKICAgICAgICAgICAgcWNwXzIwMTcgPSBtZWFuKHFjcCkpCgpxY3BfMjAxOCA8LSBiYXR0ZWRfYmFsbF9yZXN1bHRzXzIwMTggJT4lCiAgZ3JvdXBfYnkoYmF0dGVyX3RlYW0pICU+JQogIHN1bW1hcmlzZShiYmVfMjAxOCA9IG4oKSwKICAgICAgICAgICAgcWNwXzIwMTggPSBtZWFuKHFjcCkpCgpxY3BfMjAxOSA8LSBiYXR0ZWRfYmFsbF9yZXN1bHRzXzIwMTkgJT4lCiAgZ3JvdXBfYnkoYmF0dGVyX3RlYW0pICU+JQogIHN1bW1hcmlzZShiYmVfMjAxOSA9IG4oKSwKICAgICAgICAgICAgcWNwXzIwMTkgPSBtZWFuKHFjcCkpCgpxY3BfYWxsIDwtIGxlZnRfam9pbihxY3BfMjAxNSwgcWNwXzIwMTYsIGJ5ID0gImJhdHRlcl90ZWFtIikgJT4lCiAgbGVmdF9qb2luKC4sIHFjcF8yMDE3LCBieSA9ICJiYXR0ZXJfdGVhbSIpICU+JQogIGxlZnRfam9pbiguLCBxY3BfMjAxOCwgYnkgPSAiYmF0dGVyX3RlYW0iKSAlPiUKICBsZWZ0X2pvaW4oLiwgcWNwXzIwMTksIGJ5ID0gImJhdHRlcl90ZWFtIikKCnFjcF9hbmRfc2NvcmluZyA8LSBsZWZ0X2pvaW4oeCA9IHFjcF9hbGwsIHkgPSB0ZWFtc19zY29yaW5nXzIsIGJ5ID0gYygiYmF0dGVyX3RlYW0iID0gIlRlYW0iKSkKCnFjcF9hbmRfc2NvcmluZyAlPD4lCiAgbXV0YXRlKHRvdGFsX3BhID0gRyAqIFBBLAogICAgICAgICB0b3RhbF9yID0gRyAqIFIsCiAgICAgICAgIHJ1bnNfcGVyX3BhID0gdG90YWxfciAvIHRvdGFsX3BhKQoKYGBgCgoqKlByZWRpY3RpdmUgUGVyZm9ybWFuY2UqKgpgYGB7cn0KIyMjIDIwMTUgcWNwIHRvIDIwMTYgcnVucy9wYQpwcmVkXzE1XzE2IDwtIHFjcF9hbmRfc2NvcmluZyAlPiUKICBmaWx0ZXIoWWVhciA9PSAyMDE2KSAlJCUKICBjb3IocnVuc19wZXJfcGEsIHFjcF8yMDE1KSAjIDAuNjA5Njk2NgoKIyMjIDIwMTYgcWNwIHRvIDIwMTcgcnVucy9wYQpwcmVkXzE2XzE3IDwtIHFjcF9hbmRfc2NvcmluZyAlPiUKICBmaWx0ZXIoWWVhciA9PSAyMDE3KSAlJCUKICBjb3IocnVuc19wZXJfcGEsIHFjcF8yMDE2KSAjMC4zMjcyNzExCgojIyMgMjAxNyBxY3AgdG8gMjAxOCBydW5zL3BhCnByZWRfMTdfMTggPC0gcWNwX2FuZF9zY29yaW5nICU+JQogIGZpbHRlcihZZWFyID09IDIwMTgpICUkJQogIGNvcihydW5zX3Blcl9wYSwgcWNwXzIwMTcpICMgIDAuMjM5MDA3OQoKIyMjIDIwMTggcWNwIHRvIDIwMTkgcnVucy9wYQpwcmVkXzE4XzE5IDwtIHFjcF9hbmRfc2NvcmluZyAlPiUKICBmaWx0ZXIoWWVhciA9PSAyMDE5KSAlJCUKICBjb3IocnVuc19wZXJfcGEsIHFjcF8yMDE4KSAjICAwLjY1MTM5NDEKCiMjIyAyMDE5IHFjcCB0byAyMDIwIHJ1bnMvcGEKcHJlZF8xOV8yMCA8LSBxY3BfYW5kX3Njb3JpbmcgJT4lCiAgZmlsdGVyKFllYXIgPT0gMjAyMCkgJSQlCiAgY29yKHJ1bnNfcGVyX3BhLCBxY3BfMjAxOSkgIyAgMC4zNTY4NTI1CgphdmdfcHJlZF9wZXJmIDwtIG1lYW4oYyhwcmVkXzE1XzE2LCBwcmVkXzE2XzE3LCBwcmVkXzE3XzE4LCBwcmVkXzE4XzE5LCBwcmVkXzE5XzIwKSkKYXZnX3ByZWRfcGVyZiAjMC40MzY4NDQ0CmBgYAoKKipEZXNjcmlwdGl2ZSBQZXJmb3JtYW5jZSoqCmBgYHtyfQojIyMgMjAxNQpkZXNfMjAxNSA8LSBxY3BfYW5kX3Njb3JpbmcgJT4lCiAgZmlsdGVyKFllYXIgPT0gMjAxNSkgJSQlCiAgY29yKHJ1bnNfcGVyX3BhLCBxY3BfMjAxNSkgIyAwLjU5MzMxNzIKCiMjIyAyMDE2CmRlc18yMDE2IDwtIHFjcF9hbmRfc2NvcmluZyAlPiUKICBmaWx0ZXIoWWVhciA9PSAyMDE2KSAlJCUKICBjb3IocnVuc19wZXJfcGEsIHFjcF8yMDE2KSAjMC42NjQ2MTMzCgojIyMgMjAxNwpkZXNfMjAxNyA8LSBxY3BfYW5kX3Njb3JpbmcgJT4lCiAgZmlsdGVyKFllYXIgPT0gMjAxNykgJSQlCiAgY29yKHJ1bnNfcGVyX3BhLCBxY3BfMjAxNykgIyAgMC42NTkyMTgKCiMjIyAyMDE4CmRlc18yMDE4IDwtIHFjcF9hbmRfc2NvcmluZyAlPiUKICBmaWx0ZXIoWWVhciA9PSAyMDE4KSAlJCUKICBjb3IocnVuc19wZXJfcGEsIHFjcF8yMDE4KSAjICAwLjc3MTY1MTgKCiMjIyAyMDE5CmRlc18yMDE5IDwtIHFjcF9hbmRfc2NvcmluZyAlPiUKICBmaWx0ZXIoWWVhciA9PSAyMDE5KSAlJCUKICBjb3IocnVuc19wZXJfcGEsIHFjcF8yMDE5KSAjIDAuODI3MDQ4MgoKYXZnX2Rlc19wZXJmIDwtIG1lYW4oYyhkZXNfMjAxNSwgZGVzXzIwMTYsIGRlc18yMDE3LCBkZXNfMjAxOCwgZGVzXzIwMTkpKQphdmdfZGVzX3BlcmYgIzAuNzAzMTY5NwpgYGA=