Step1: Open this .RMD file in R Studio and click the “Preview” button above. A formatted version should pop up in a browser window. IF not, troubleshoot that!

manual outline

  1. Housekeeping
    1. SET WORKING DIRECTORY
    2. this R Markdown (.RMD) document was created using R Studio
      1. plain text
      2. “Reproducible Research” Movement
    3. R from https://www.r-project.org/
    4. R Studio from https://www.rstudio.com/products/rstudio/download/
    5. Environment tab (Rstudio)
    6. Files/Plots/Packages/Help/Viewer tab (Rstudio)
    7. Preview/view as HTML (R Markdown in Rstudio)
    8. Outline tab (R Markdown in Rstudio)

intro

Everything up to the intermission(?) is primarily an exercise in data wrangling (tidy+transform) and markdown formatting using R Studio. The second portion contains an overview of data modeling and a preliminary attempt to model Hop Freshening Power (synonyms: ‘dryhop creep’, ‘ABV creep’, ‘dry-hop creep’) as a component of the warm-dryhopping process.

#                       __  .__    .___      
#                     _/  |_|__| __| _/__.__.
#                     \   __\  |/ __ <   |  |
#                      |  | |  / /_/ |\___  |
#                      |__| |__\____ |/ ____|
#                                   \/\/     
#                                    \/
#                                    ||
#                                    ||
#                                    ||
#              _                     /\               
#             | |_ _ _ __ _ _ _  ___/ _|___ _ _ _ __  
#             |  _| '_/ _` | ' \(_-<  _/ _ \ '_| '  \ 
#              \__|_| \__,_|_||_/__/_| \___/_| |_|_|_|
#                        //                      \\
#                       //                        \\
#                      //                          \\
#                     //                            \\
#          _         //   _ _                        \\   _     _ 
#     __ _(_)____  _ __ _| (_)______        _ __  ___  __| |___| |
#     \ V / (_-< || / _` | | |_ / -_)------| '  \/ _ \/ _` / -_) |
#      \_/|_/__/\_,_\__,_|_|_/__\___|------|_|_|_\___/\__,_\___|_|
#
#ascii font/art from <http://www.network-science.de/ascii/>

Statement of Problem

The warm-dryhopping process can profoundly impact the body, alcohol and vicinal diketone content of the beer. Common metrics and models for this phenomenon have not been established.

Overview of Experimental Data

Through experience in brewing we know that endpoints of dryhopping include:

  • flavor impact (always)
  • impact on visual/presentation (sometimes)
  • ethanol increase accompanied by decrease in specific gravity (sometimes)
  • CO2 increase (sometimes)
  • diacetyl/VDK increase (sometimes)
  • impact on yeast quality, and so on…

Experiments were carried out where

  1. multiple replicate 250-mL samples of [nearly] end-fermented beer were collected from fermenters into 12oz amber bottles
  2. the samples were randomized into groups of three, then each group was either
    • dry-hopped at ~1.0 pounds/bbl (treatment), or
    • not dry-hopped (control)
  3. all were hand-crimped with foil, stored for various times (mostly at room temp, a few in the cold), then tested on an Anton Paar DMA4500/Alcolyzer classic

Focusing on ethanol increase as the endpoint, we know these to be relevant factors:

  • variety of hops (five different varieties)
  • presence of live yeast (true in all cases for these data)
  • temperature during dryhopping (mostly warm, with a few in the cold)
  • amount of contact time between hops and beer (up to 7 weeks)

For more details on this dataset see the manuscript.
please send comments, complaints and corrections to luke@bellsbeer.com! * Data are from the file “ujbc_a_1469081_sm5496.txt” (supplementary data from Jacob A. Kirkendall, Carter A. Mitchell & Lucas R. Chadwick (2018): The Freshening Power of Centennial Hops, Journal of the American Society of Brewing Chemists Volume 76, Issue 3, Pages 178-184 (2018) DOI: 10.1080/03610470.2018.1469081* available from https://www.tandfonline.com/doi/full/10.1080/03610470.2018.1469081?scroll=top&needAccess=true

load libraries

#.rs.restartR()  # restart R 
sessionInfo()
#data wrangling
#install.packages("dplyr")  # dplyr "data plyer"  
library(dplyr)
library(ggplot2)
library(tidyverse)
library(caret) # for dummyVars function
library(psych) # for pairs.panels function
library(memisc)  # compare models
library(drc)  # "drc" = dose response curves (contains a suite of flexible and versatile model fitting and after-fitting functions)
#library(plotly)
library(gridExtra)  # for grid.arrange in R markdown

flash forward! scroll to the bottom and run bigchunk_tidytransform

import data

rm(list = ls()) # clear workspace
mydata <-read.csv("ujbc_a_1469081_sm5496.txt",stringsAsFactors = FALSE)  ## SPECIFY filename
dput(names(mydata))
c("sample_id", "hop", "BINhop", "expt", "sample_group", "special_group", 
"brew_date", "sample_collection_date", "volume_mL", "dryhop_date", 
"dhop_day", "Hop_type", "mg_hops", "contact_days", "special_conditions", 
"temp.C", "Test_Date", "Test.time", "ABV", "ABW", "OE", "Er", 
"Ea", "SG", "RDF", "ADF", "Calories", "REF_NH", "ABV_increase", 
"vol_norm_ABV_increase", "ABV_increase_prototabs")

!manual add time zero to “days on hops” experiment!

Anton Paar data were collected at time zero for kinetics experiment (triplicate samples run immediately after sample collection, while the remainder were being dry-hopped), but were inadvertently excluded from the original dataset. Here they are: Date time sample no. ABV ABW OE (P) Er Ea SG 20/20 RDF ADF sample_id 07/27/17 3:26 PM 03_ 6.68 5.2 15.95 6.08 3.74 1.01462 63.88 76.58 JK1-20-41 07/27/17 3:30 PM 04_ 6.68 5.2 15.95 6.08 3.73 1.01461 63.89 76.6 JK1-20-50 07/27/17 3:34 PM 05_ 6.68 5.2 15.95 6.08 3.74 1.01462 63.88 76.58 JK1-20-87

## fill in MISSING DATA :(  add time zero data for time series analysis)
## nested ifelse to add time zero plato values for each expt:
mydata<- mydata %>% mutate(., initial_plato = ifelse(expt==" 2A", 3.74, ifelse(expt==" 1A", 3.69, ifelse(expt==" 1B", 3.53, ifelse(expt==" 3B", 3.54, ifelse(expt==" 4B", 4.66, NA))))))
                                             
## add complete time-zero anton paar data for time series (expt2A)
## quadruple-up each triplicate time-zero measurement (one copy for each NH/DH & rouse/still combo)
## note:  this practice is of questionable statistical rigor.  In retrospect it would have been better (and a lot easier) to just run 12 samples through Anton on day0!
timezero <- mydata %>% filter(expt==" 2A") %>% arrange(special_group) %>% slice(1:12)
#data.entry(timezero)
timezero$Test_Date <- rep(c("7/27/2017","7/27/2017","7/27/2017"),4)
timezero$Test.time <- rep(c("3:26 PM", "3:30 PM", "3:34 PM"),4)
timezero$ABV <- 6.68
timezero$ABW <- 5.2
timezero$OE <- 15.95
timezero$Er <- 6.08
timezero$Ea <- rep(c(3.74, 3.73, 3.74),4)
timezero$SG <- rep(c(1.01462, 1.01461, 1.01462),4)
timezero$RDF <- rep(c(63.88, 63.89, 63.88),4)
timezero$ADF <- rep(c(76.58, 76.60, 76.58),4)
timezero$Calories <- rep(c(215.37,215.34,215.35),4)
timezero[timezero$hop=="DH",]$contact_days <-0
timezero[,c(28:31)] <- 0
mydata <- rbind(timezero,mydata)
write.csv(mydata, "quickie.csv", row.names = FALSE)
mydata<-read.csv("quickie.csv", stringsAsFactors = FALSE)
###  this is a routine for inspecting data ###
mydatatypes<- as.data.frame(cbind(sapply(mydata, class)))  ## table of datatypes
mydatatypes$headers <- rownames(mydatatypes)    ## convert rownames to values
na_count <-as.data.frame(sapply(mydata, function(y) sum(length(which(is.na(y)))))) ## count NAs by column
mydataOverview<-as.data.frame(cbind(na_count,mydatatypes))  ## table of NA counts and datatypes
names(mydataOverview) <- c("NA_count","Data_Class", "Header")
#sum(is.na(mydata))  ## total count of NA values in entire sheet (231 in this case)
#mydataOverview %>% filter(NA_count>0)    ## breakdown of NA values

tidy & transform

note: this is a base R +dplyr data wrangling exercise! in general it’s best to use lubridate package for date/timestamps!

test_df<- mydata # (original dataframe to be used for testing/illustration)
x_brew_date.POSIXct<- as.POSIXct(mydata$brew_date, format = "%m/%d/%Y") # as a standalone vector (USELESS here), or:
test_df$brew_date.POSIXct <-as.POSIXct(mydata$brew_date, format = "%m/%d/%Y")  # as a "new column" in our dataframe
datecols<- dput(names(dplyr::select(mydata, matches("date"))))  ## headers containing string "date" => c("brew_date", "sample_collection_date", "dryhop_date", "Test_Date")
c("brew_date", "sample_collection_date", "dryhop_date", "Test_Date"
)
## FORLOOP
for (icol in datecols) {
  newcol = paste0(icol,".POSIXct")
 # print(newcol)
  mydata[, newcol] = as.POSIXct(mydata[, icol],format = "%m/%d/%Y") ###  CREATE NEW columns with POSIXct   
#  mydata[, newcol] = as.POSIXct(as.numeric(mydata[, icol])  * (60*60*24), origin="1899-12-30") ###  microsoft times
}
mydata<- mydata %>% 
  mutate(dayofaddition = as.integer(as.numeric(difftime(dryhop_date.POSIXct,brew_date.POSIXct))),
         daysonhops = as.integer(as.numeric(difftime(Test_Date.POSIXct,dryhop_date.POSIXct))/(60*60*24)),
         hops_g_100mL = (mg_hops/volume_mL)/10,
         pounds_bbl = (mg_hops/volume_mL)*117/454
         )
mydata$OX<-grepl("OX", mydata$Hop_type)       ##  create logical "OX" column
mydata$rouse<-grepl("rouse", mydata$special_group)##  create logical column
mydata$Grind<-grepl("Grind", mydata$Hop_type)     ##  create logical column
mydata$Cone<-grepl("Cone", mydata$Hop_type)       ##  create logical column
mydata$harvest2014<-grepl("14", mydata$Hop_type)  ##  create logical column
mydata$harvest2015<-grepl("15", mydata$Hop_type)  ##  create logical column
mydata$harvest2017<-grepl("17", mydata$Hop_type)  ##  create logical column
## ifelse statement for harvestyear (if neither 2014 nor 2015 nor 2017, then 2016)
mydata$harvestYear <- ifelse(
  mydata$harvest2014==TRUE, 2014,
  ifelse(mydata$harvest2015==TRUE, 2015, 
         ifelse(mydata$harvest2017==TRUE, 2017, 2016)))
dput(levels(as.factor(mydata$harvestYear)))
c("2014", "2015", "2016", "2017")
## ifelse statement for form of hops (if neither cone nor ground nor NH, then pellet) 
mydata$form_of_hops <- ifelse(
  mydata$Cone==TRUE, "cone",
  ifelse(mydata$Grind==TRUE, "ground",
         ifelse(mydata$Hop_type=="NH", "NH", "pellet")))
dput(levels(as.factor(mydata$form_of_hops)))
c("cone", "ground", "NH", "pellet")
## ifelse statement for temperature greater or less than 10 
mydata$DH_temp <- ifelse(
  mydata$temp.C<10, "cold",
  ifelse(mydata$temp.C>10, "warm", "something else"))
dput(levels(as.factor(mydata$DH_temp)))
c("cold", "warm")
mydata$variety<-mydata$Hop_type
mydata$variety<- gsub("[0-9]+","", mydata$variety) ## remove all numbers
mydata$variety<- gsub("OX","", mydata$variety)     ## remove specific text
mydata$variety<- gsub("Grind","", mydata$variety)
mydata$variety<- gsub("Cone","", mydata$variety)
mydata$variety<- gsub(" ","", mydata$variety)      ## remove spaces
dput(levels(as.factor(mydata$variety)))
c("AMAR", "CASC", "CENT", "CIT", "NH", "SIM")
mydata<- mydata %>% mutate(EXPTnew=paste0("group",expt, substr(special_group, 1,2),as.character(rouse)))
# clean it up by removing "NA" and any spaces due to canarycode bug
mydata$EXPTnew <- gsub(" ","", mydata$EXPTnew)  ## remove any spaces
mydata$EXPTnew <- gsub("NA","", mydata$EXPTnew) ## remove "NA"
dput(levels(as.factor(mydata$expt)))
c(" 1A", " 1B", " 2A", " 3A", " 3B", " 4B")
dput(levels(as.factor(mydata$EXPTnew)))
c("group1AFALSE", "group1BFALSE", "group2A01FALSE", "group2A01TRUE", 
"group2A05FALSE", "group2A05TRUE", "group2A12FALSE", "group2A12TRUE", 
"group2A19FALSE", "group2A19TRUE", "group2A25FALSE", "group2A25TRUE", 
"group2A33FALSE", "group2A33TRUE", "group2A42FALSE", "group2A42TRUE", 
"group3AFALSE", "group3BFALSE", "group4BFALSE")

select and rearrange columns

(experimental factors on the left, then measurements, followed by calculations and then all date columns on the right):

mydata <- mydata %>% 
  dplyr::select(sample_id,expt, EXPTnew, hop, BINhop, variety, OX, harvestYear, form_of_hops,special_conditions, rouse, daysonhops, dayofaddition, DH_temp, temp.C, hops_g_100mL, pounds_bbl,
         ABV, ABW, OE, Er, Ea, SG, RDF, ADF, Calories, 
         dhop_day, contact_days, REF_NH, ABV_increase, 
         brew_date.POSIXct, sample_collection_date.POSIXct, dryhop_date.POSIXct,Test_Date.POSIXct, initial_plato)
## remove ".POSIXct" suffix.  Leaving it as-is will only add to confusion if/when these data are saved and re-imported (and become 'character' format again!)
colnames(mydata) = gsub(".POSIXct", "", colnames(mydata))

compute mean NH (control) values

(NH = not dry-hopped)

## mean NH (control) values for each EXPTnew group
mean.control_NH<- mydata %>% 
  group_by(EXPTnew) %>%
  filter(hop=="NH") %>%
  summarise_at(vars(ABV, ABW, Ea, SG),funs(mean, n()))
## replace "_mean" with ".control_NH" in column names
colnames(mean.control_NH) = gsub("_mean", ".control_NH", colnames(mean.control_NH))

compute \(\Delta\) values (differences relative to NH controls) using objects created above…

  • difference of each ABV,ABW,Ea, and SG data point from corresponding mean.control_NH values (unhopped samples in same EXPTnew group)
## first join our data with mean ABV for unhopped samples in given experiment (mean.control_NH; calculated above)
FPHcalc<- left_join(mydata, mean.control_NH, by="EXPTnew")
## calculate ABW_increase by subtracting each individual ABW measurement from respective mean.control_NH:
FPHcalc$delta.ABV <- FPHcalc$ABV - FPHcalc$ABV.control_NH
FPHcalc$delta.ABW <- FPHcalc$ABW - FPHcalc$ABW.control_NH
FPHcalc$delta.plato <- FPHcalc$Ea - FPHcalc$Ea.control_NH
FPHcalc$delta.SG <- FPHcalc$SG - FPHcalc$SG.control_NH
## the control samples have served their purpose, now remove them from dataset. The following calculations are only meaningful for dry-hopped samples.  
FPHcalc<- FPHcalc %>% filter(hop=="DH")

calculate corresponding CO2 production

  • following Bamforth (describing Balling equation) “…more realistically, the ethanol yield is more like 0.46 g and carbon dioxide 0.44 g from 1 g sugar” (p. 137 in Brewing Materials and Processes: A Practical Approach to Beer Excellence, Edited by Charles Bamforth Academic Press, 2016)
FPHcalc$calcCO2_increase <- FPHcalc$delta.ABW*(0.44/0.46)
##convert calcCO2_increase (in g/100mL) to calculated CO2 volumes added
## g/L = 10* g/100mL
## The conversion factor from volumes of CO2 to CO2 by weight (g/L) is 1.96. For example: 2.5 volumes x 1.96 = 4.9 g/l.
FPHcalc$calcCO2vols_increase <- FPHcalc$calcCO2_increase*10/1.96

define “FPH” as amount produced per amount dry-hops added (all in g/100mL):

##  FPH = Fold Production due to Hops (fold-increase by mass: amount of given endpoint relative to amount of hops added)
##  
FPHcalc$FPH_EtOH = FPHcalc$delta.ABW/FPHcalc$hops_g_100mL
FPHcalc$FPH_CO2 = FPHcalc$calcCO2_increase/FPHcalc$hops_g_100mL
FPHcalc$FPH_plato = FPHcalc$delta.plato/FPHcalc$hops_g_100mL
## replacing time-zero time values with a very small number (rather than exactly zero) will prevent issues with analysis of nonlinear models
FPHcalc[FPHcalc$daysonhops==0,]$daysonhops <- 0.01
## and save the transformed data to csv:
write.csv(FPHcalc,"FPHcalc.csv", row.names = FALSE)
FPHcalc <- read.csv("FPHcalc.csv", stringsAsFactors = TRUE)
df<- FPHcalc %>% group_by(EXPTnew) %>%
  summarise_at(vars(hops_g_100mL,pounds_bbl,delta.ABV, delta.ABW, delta.plato, FPH_plato, FPH_EtOH, FPH_CO2, calcCO2vols_increase),funs(round(mean(.), 2))) %>%
  arrange(desc(FPH_EtOH)) 
df

intermission?

visualize

vector~vector*vector plots

df <-FPHcalc
p1<- ggplot(df, aes(y=FPH_EtOH,x=OE, color=contact_days)) + geom_point(size=2)
p2<- ggplot(df, aes(y=FPH_EtOH,x=ADF, color=contact_days)) + geom_point(size=2)
p3<- ggplot(df, aes(y=FPH_EtOH,x=ABW, color=contact_days)) + geom_point(size=2)
p4<- ggplot(df, aes(y=FPH_EtOH,x=Ea, color=contact_days)) + geom_point(size=2)
grid.arrange(p1, p2, p3, p4, ncol = 2)

x~y*(4 vectors) by color

df <- FPHcalc
x<- df$Ea
y<- df$ABW    #FPH_EtOH
p1<- ggplot(df, aes(x,y, color=form_of_hops)) + geom_point(size=2)
p2<- ggplot(df, aes(x,y, color=brew_date)) + geom_point(size=2)
p3<- ggplot(df, aes(x,y, color=rouse)) + geom_point(size=2)
p4<- ggplot(df, aes(x,y, color=pounds_bbl)) + geom_point(size=2)
grid.arrange(p1, p2, p3, p4, ncol = 2)

3D plots for html with library(plotly)

see https://plotly-r.com/the-plotly-cookbook.html for overview and some visualization inspiration.

# interactive 3D plots with library(plotly)
df <- FPHcalc # %>% filter(expt==" 2A")
# makes html very slow to render...comment out
#plot_ly(df, x = ~daysonhops, y = ~ABW, z = ~Ea) %>% add_markers(color = ~expt)

Amunategui “Using Correlations To Understand Your Data”

mydata<-FPHcalc %>% dplyr::select(expt,variety,form_of_hops,pounds_bbl,special_conditions,rouse,daysonhops,DH_temp,brew_date,ABW,SG,OE,ADF,RDF,calcCO2vols_increase,FPH_CO2, FPH_EtOH)  ## last one is 100% in j
## note use of "dplyr::select" because one of these packages is conflicting with dplyr commands :()
## following  Manuel Amunategui  https://www.youtube.com/watch?v=igPQ-pI8Bjo
## Using Correlations To Understand Your Data: Machine Learning With R 
##functions for flattenSquareMatrix
cor.prob <- function (X, dfr=nrow(X) -2) {
  R<- cor(X, use="pairwise.complete.obs")
  above<- row(R) < col(R)
  r2 <- R[above]^2
  Fstat<- r2 * dfr/(1-r2)
  R[above] <- 1- pf(Fstat, 1, dfr)
  R[row(R) == col(R)] <- NA
  R
}
flattenSquareMatrix <- function(m) {
  if( (class(m) != "matrix") | (nrow(m)!=ncol(m))) stop("Must be a square matrix.")
  if(!identical(rownames(m), colnames(m))) stop("Row and column names must be equal.")
  ut <- upper.tri(m)
  data.frame(i = rownames(m)[row(m)[ut]],
             j = rownames(m)[col(m)[ut]],
             cor=t(m)[ut],
             p=m[ut])
}
## library(caret) to dummify everything (turn all characters&factors into columns;  ignores numbers and integers)
dmy<- dummyVars(" ~ .",data = mydata)
mydummifieddata<- data.frame(predict(dmy, newdata = mydata))
corMat = cor(mydummifieddata)
corMasterList<- flattenSquareMatrix(cor.prob(mydummifieddata)) ## list of all correlations
## order by strength of correlation
corlist<- corMasterList %>% arrange(-abs(corMasterList$cor)) 
write.csv(corlist,paste0("FLAT correlation matrix_.csv"))
corlist <- corlist %>% dplyr::filter(j=="FPH_EtOH")   ## filter specific endpoint
head(corlist,50)

pairs.panels correlation matrix from library(psych)

## specify interesting variables:  
interestingvariables<-c("ABW", "pounds_bbl", "daysonhops", "calcCO2vols_increase") 
pairs.panels(mydummifieddata[c(interestingvariables, "FPH_EtOH")])

model

Observing the impacts of dryhopping in the presence of live yeast has led many brewing professionals to understand that FPH is a function of many of the variables above including hop variety,form_of_hops,harvestYear,OX,DH_temp,daysonhops,rouse,pounds_bbl…. Many have intuitively created a model in their heads (without necessarily thinking of it as such) and skillfully adjust process when necessary to account for this phenomenon. In linear modeling, our function will take on the form: \(FPH = intercept + \beta_{1}X_{1} + \beta_{2}X_{2} + ... + \beta_{n}X_{n}\) where \(\beta\) values are what we’re attempting to derive in this modeling exercise, and X values are (collectively) a particular set of conditions.

linear modeling overview 1 (create models)

df<-FPHcalc
lm1<-lm(df$SG~df$OE)
lm2<-lm(df$SG~df$ADF)
lm3<-lm(df$SG~df$ABW)
lm4<-lm(df$SG~df$Ea)
par(mfrow = c(2, 2), oma = c(0, 0, 0, 0))
plot(df$SG~df$OE)
abline(lm1)
plot(df$SG~df$ADF)
abline(lm2)
plot(df$SG~df$ABW)
abline(lm3)
plot(df$SG~df$Ea)
abline(lm4)

For each of the four cases above, we can easily see with our eyeballs the degree to which a linear model fits the data. Residual plots and R-squared values are commonly used to express this goodness-of-fit (that we can easily see with our eyeballs!).

linear modeling overview 2 (view residual plots)

df<-FPHcalc
lm1<-lm(df$SG~df$OE)
lm2<-lm(df$SG~df$ADF)
lm3<-lm(df$SG~df$ABW)
lm4<-lm(df$SG~df$Ea)
par(mfrow = c(2, 2), oma = c(0, 0, 0, 0))
plot(lm1$residuals)
plot(lm2$residuals)
plot(lm3$residuals)
plot(lm4$residuals)

#summary(lm4)

compare linear models using memisc package

df<-FPHcalc
lm1<-lm(df$SG~df$OE)
lm2<-lm(df$SG~df$ADF)
lm3<-lm(df$SG~df$ABW)
lm4<-lm(df$SG~df$Ea)
mtable1234 <- mtable("Model 1"=lm1,"Model 2"=lm2,"Model 3"=lm3, "Model 4"=lm4,
                    summary.stats=c("sigma","R-squared","F","p","N"),show.eqnames=T)
mtable1234b <- relabel(mtable1234,
                      "(Intercept)" = "Constant",
                      x1 = "OE = Original Extract (g/100mL)",
                      x2 = "ADF = Apparent Degree of Fermentation (%)",
                      x3 = "ABW = Ethanol (w/w)",
                      x4 = "Er = Residual Extract (g/100mL)"
                      )
mtable1234

Calls:
Model 1: lm(formula = df$SG ~ df$OE)
Model 2: lm(formula = df$SG ~ df$ADF)
Model 3: lm(formula = df$SG ~ df$ABW)
Model 4: lm(formula = df$SG ~ df$Ea)

======================================================================
                Model 1      Model 2       Model 3       Model 4      
              ----------- ------------- ------------ ---------------   
                 df$SG        df$SG         df$SG         df$SG       
----------------------------------------------------------------------
  (Intercept)    1.071***      1.063***     1.056***        1.000***  
                (0.020)       (0.000)      (0.001)         (0.000)    
  df$OE         -0.004**                                              
                (0.001)                                               
  df$ADF                      -0.001***                               
                              (0.000)                                 
  df$ABW                                   -0.008***                  
                                           (0.000)                    
  df$Ea                                                     0.004***  
                                                           (0.000)    
----------------------------------------------------------------------
  sigma          0.002         0.000        0.001           0.000     
  R-squared      0.066         0.997        0.934           1.000     
  F              8.553     48038.147     1700.301     3532408.297     
  p              0.004         0.000        0.000           0.000     
  N            123           123          123             123         
======================================================================
#show_html(mtable1234b)

linear models of FPH

df <- FPHcalc
lm1<-lm(FPH_EtOH~daysonhops, data=df)
lm2<-lm(FPH_EtOH~daysonhops*form_of_hops, data=df)
lm3<-lm(FPH_EtOH~daysonhops*rouse, data=df)
lm4<-lm(FPH_EtOH~daysonhops*form_of_hops*rouse, data=df)
mtable1234 <- mtable("Model 1"=lm1,"Model 2"=lm2,"Model 3"=lm3, "Model 4"=lm4,
                    summary.stats=c("sigma","R-squared","F","p","N"),show.eqnames=T)
mtable1234b <- relabel(mtable1234,
                      "(Intercept)" = "Constant",
                      SG = "Specific Gravity",
                      ABW = "ABW = Ethanol (w/w)",
                      Er = "Er = Residual Extract (g/100mL)"
                      )
mtable1234

Calls:
Model 1: lm(formula = FPH_EtOH ~ daysonhops, data = df)
Model 2: lm(formula = FPH_EtOH ~ daysonhops * form_of_hops, data = df)
Model 3: lm(formula = FPH_EtOH ~ daysonhops * rouse, data = df)
Model 4: lm(formula = FPH_EtOH ~ daysonhops * form_of_hops * rouse, data = df)

=============================================================================
                              Model 1     Model 2     Model 3     Model 4    
                            ----------- ----------- ----------- -----------   
                              FPH_EtOH    FPH_EtOH    FPH_EtOH    FPH_EtOH   
-----------------------------------------------------------------------------
  (Intercept)                  0.498***    0.306       0.537***    0.319     
                              (0.044)     (0.197)     (0.049)     (0.197)    
  daysonhops                   0.044***    0.044***    0.041***    0.041***  
                              (0.003)     (0.003)     (0.004)     (0.004)    
  form_of_hops: ground/cone                0.849**                 0.849**   
                                          (0.278)                 (0.277)    
  form_of_hops: pellet/cone                0.174                   0.199     
                                          (0.200)                 (0.199)    
  rouse                                               -0.227      -0.208     
                                                      (0.122)     (0.118)    
  daysonhops x rouseTRUE                               0.010       0.009     
                                                      (0.006)     (0.006)    
-----------------------------------------------------------------------------
  sigma                        0.355       0.340       0.352       0.339     
  R-squared                    0.634       0.669       0.645       0.678     
  F                          210.023      80.145      72.038      49.193     
  p                            0.000       0.000       0.000       0.000     
  N                          123         123         123         123         
=============================================================================
#show_html(mtable1234b)

The R Book (Crawley) Table 20.1: nonlinear functions useful in biology

Table 20.1. Useful non-linear functions EXPANDED:

Function Class name equation example code example applications
Asymptotic functions Michaelis–Menten \(y =\frac{ax}{1+bx}\) nls(bone~aage/(1+bage),start=list(a=8,b=0.08))) enzyme reactions
nls(rate~SSmicmen(conc,a,b)) tbd
2-parameter asymptotic exponential \(y = a(1 − e^{−bx} )\) nls(bone~a(1-exp(-cage)),start=list(a=120,c=0.064)) tbd
3-parameter asymptotic exponential \(y = a − be^{−cx}\) nls(bone~a-bexp(-cage),start=list(a=120,b=110,c=0.064)) tbd
nls(bone~SSasymp(age,a,b,c)) tbd
nls(density ~ SSlogis(log(concentration), a, b, c)) tbd
S-shaped functions 2-parameter logistic \(y = \frac{e^{a+bx}}{1 + e^{a+bx}}\) tbd
3-parameter logistic \(y = \frac{a}{1 + be^{−cx}}\) tbd
4-parameter logistic \(y = a + \frac{b-a}{1 + e^{(c−x)/d}}\) nls(weight~SSfpl(Time, a, b, c, d)) tbd
Weibull \(y = a − be^{−(cx^d)}\) nls(weight ~ SSweibull(time, Asym, Drop, lrc, pwr)) tbd
Gompertz \(y = ae^{−be^{−cx}}\) tbd
Humped curves Ricker curve \(y = axe^{−bx}\) tbd
First-order compartment \(y = k exp(−exp(a)x) − exp(−exp(b)x)\) nls(conc~SSfol(Dose, Time, a, b, c)) tbd
Bell-shaped \(y = a exp(−ABS(bx)^2)\) tbd
Biexponential \(y = ae^{bx} − ce^{−dx}\) tbd

base R nls function for Michaelis-Menton model

\(y =\frac{ax}{1+bx}\)

expt2 <- FPHcalc %>% dplyr::filter(expt==" 2A") %>% dplyr::select(FPH_EtOH, rouse,special_conditions, daysonhops,brew_date, pounds_bbl,form_of_hops,ABW.control_NH,dayofaddition)
myfactor<- as.factor(expt2$special_conditions)  #rouse
cols <- as.numeric(myfactor)
legend.cols <- as.numeric(as.factor(levels(myfactor)))
my.MM.model <- nls(FPH_EtOH~a*daysonhops/(1+b*daysonhops),myfactor, data=expt2,start=list(a=8, b=0.08))
summary(my.MM.model)

Formula: FPH_EtOH ~ a * daysonhops/(1 + b * daysonhops)

Parameters:
  Estimate Std. Error t value Pr(>|t|)    
a 0.186159   0.010109   18.41  < 2e-16 ***
b 0.067820   0.005728   11.84 1.45e-15 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 0.09307 on 46 degrees of freedom

Number of iterations to convergence: 6 
Achieved convergence tolerance: 1.686e-06

So using the Michaelis-Menten equation as our model, the relationship between hop-induced ethanol production (FPH_EtOH) and hop contact time (daysonhops) would be expressed as: \(FPH_EtOH = \frac{0.183572*daysonhops}{1+0.066388*daysonhops}\) with a Residual standard error of 0.09307. As detailed below, logistic models do a better job than this, and using library(drc) makes it easier to introduce a multilevel factor.

predict and plot model

x <- seq(0,50,0.1)
yv <- predict(my.MM.model, list(daysonhops=x))
{plot(expt2$daysonhops, expt2$FPH_EtOH, pch=21, col="purple", bg="green", main = "Michaelis-Menten model of ethanol production induced by dry-hopping")
  lines(x,yv,col="blue")}

Speers2003: Non-Linear Modelling of Industrial Brewing Fermentations

R. Alex Speers, Peter Rogers, Bruce Smith J. Inst. Brew. 109(3), 229–235, 2003 https://doi.org/10.1002/j.2050-0416.2003.tb00163.x Free Access from the Institute of Brewing! https://onlinelibrary.wiley.com/doi/10.1002/j.2050-0416.2003.tb00163.x

Following Speers2003, the relationship between plato and time in primary fermentations is well modeled by a four parameter logistic model:

PLATO = PINF + (P_D / (1+ (EXP(-B*(TIME-M)))))

or

\(PLATO = P_{inf}+ \frac{P_D}{1+e^{-B(t-M)}}\)

where

  • t = time into fermentation
  • P_D = change in Plato during fermentation (OE - P_{inf})
  • B ~ maximum fermentation rate
  • M = fermenation midpoint
  • P_inf = final gravity

so then for our case

\(PLATO = P_{inf}+ \frac{OE-P_{inf}}{1+e^{-F_{max}(daysonhops-M)}}\)

PLATO = PINF + (P0 / (1+ (EXP(B*(HOURS-M)))))

MacIntosh2016: An Examination of Substrate and Product Kinetics During Brewing Fermentations

Andrew J. MacIntosh, Maria Josey, R. Alex Speers J. Am. Soc. Brew. Chem. 74(4), 250-257, 2016

“is only statistically advantageous to apply the five-parameter model when many data points are available… (72 from this experiment as opposed to 10 when using Yeast-14).”

MacIntosh five-parameter logistic: \(P_{(t)} =\frac{P_i - P_e}{(1+s * e^{-B(t-M)})^{1/s}}\)

library(drc) getMeanFunctions()

drc LL.4: \(f(x) = c + \frac{d-c}{1+\exp(b(\log(x)-\tilde{e}))}\) #4param logistic drc LL.5: \(f(x) = c + \frac{d-c}{(1+\exp(b(\log(x)-e)))^f}\) #5param logistic drc G.4 \(f(x) = c + (d-c)(\exp(-\exp(b(x-e))))\) #4param Gompertz
drc W1.4 \(f(x) = c + (d-c) \exp(-\exp(b(\log(x)-\log(e))))\) #4param Weibull1 drc W2.4 \(f(x) = c + (d-c) (1 - \exp(-\exp(b(\log(x)-\log(e)))))\) #4param Weibull2 drc BC.5: \(f(x, b,c,d,e,f) = c + \frac{d-c+fx}{1+\exp(b(\log(x)-\log(e)))}\) # Brain-Cousens

explore nonlinear models of hop-induced Plato drop over time with library(drc)

df <- FPHcalc %>% filter(expt==" 2A") 
df$PLATO.hopdrop <- df$initial_plato + df$delta.plato
x <- df$daysonhops
y <- df$PLATO.hopdrop
group<- as.factor(df$special_conditions)   #rouse
cols <- as.numeric(group)
legend.cols <- as.numeric(as.factor(levels(group)))
## Fitting models using function drm from library(drc). see <http://rstats4ag.org/dose-response-curves.html> for overview and <https://www.rdocumentation.org/packages/drc/> for list of models (fct values) and other functions available in drc
## create models
#m.LL.3<-drm(y ~x,group, fct = LL.3()) #3-parameter logistic (lower limit at 0)
#m.LL.3u<-drm(y ~x, fct = LL.3u()) #3-parameter logistic (upper limit at 1)
m.LL.4<-drm(y ~x,group, fct = LL.4()) #4-parameter log-logistic  
# from ??LL.4:  f(x) = c + \frac{d-c}{1+\exp(b(\log(x)-\log(e)))} or in another parameterisation (converting the term \log(e) into a parameter) f(x) = c + \frac{d-c}{1+\exp(b(\log(x)-\tilde{e}))}  
m.L.4 <-drm(y ~x,group, fct = L.4()) #changing the fct = LL.4() to fct = L.4() allows plotting on a log10 scale
m.LL2.4<-drm(y ~x,group, fct = LL2.4()) #4-parameter log-logistic with log(e) rather than e as a parameter  
m.LL2.5<-drm(y ~x,group, fct = LL2.5()) #Generalised log-logistic
# from ??LL.2.5:   f(x) = c + \frac{d-c}{(1+\exp(b(\log(x)-e)))^f}
m.LL.5<-drm(y ~x,group, fct = LL.5()) #5-parameter logistic  
m.W1.4<-drm(y ~x,group, fct = W1.4()) #4-parameter Weibull1  
NaNs produced
m.W2.4<-drm(y ~x,group, fct = W2.4()) #4-parameter Weibull2  
m.BC.5<-drm(y ~x,group, fct = BC.5()) #5-parameter Brain-Cousens (hormesis)
m.AR.3<-drm(y ~x,group, fct = AR.3()) #3-parameter Shifted asymptotic regression
NaNs producedNaNs produced
#m.MM.2<-drm(y ~x,group, fct = MM.2()) #2-parameter Michaelis-Menten
m.MM.3<-drm(y ~x,group, fct = MM.3()) #3-parameter Michaelis-Menten
NaNs producedNaNs producedNaNs producedNaNs producedNaNs producedNaNs producedNaNs produced
##plot
#par(mfrow = c(1,2), oma = c(0, 0, 0, 0))
{plot(x , y, col=cols, main="raw data")
legend("topright", legend=levels(group), pch=16, col=legend.cols)}

#plot(m.LL.3, type='all',col=cols, main="LL.3 (lower limit at 0)")
plot(m.LL.4, type='all',col=cols, main="LL.4 four-parameter log-logistic")

plot(m.L.4, type='all',col=cols, main="L.4")

plot(m.LL.5, type='all',col=cols, main="LL.5")

plot(m.LL2.4, type='all',col=cols, main="LL2.4 four-parameter log-logistic")

plot(m.LL2.5, type='all',col=cols, main="Generalised log-logistic")

plot(m.W1.4, type='all',col=cols, main="Weibull1")

plot(m.W2.4, type='all',col=cols, main="Weibull2")

plot(m.BC.5, type='all',col=cols, main="Brain-Cousens (hormesis)")

plot(m.AR.3, type='all',col=cols, main="Shifted asymptotic regression")

#plot(m.MM.2, type='all',col=cols, main="2-parameter Michaelis-Menten")
plot(m.MM.3, type='all',col=cols, main="3-parameter Michaelis-Menten")

multilevel logistic models with library(drc)

df <- FPHcalc %>% filter(expt==" 2A") 
df$PLATO.hopdrop <- df$initial_plato + df$delta.plato
x <- df$daysonhops
y <- df$PLATO.hopdrop
myfactor<- as.factor(df$special_conditions)  #rouse
cols <- as.numeric(myfactor)
legend.cols <- as.numeric(as.factor(levels(myfactor)))
## create models
#m.LL.4<-drm(y ~x,myfactor, fct = LL.4(names = c("Slope", "Lower", "Upper", "Midpoint or ED50"))) #4-parameter log-logistic (general parameters)
#m.LL.4<-drm(y ~x,myfactor, fct = LL.4(names = c("Slope", "Lower", "Upper", "ED50"))) #4-parameter log-logistic (Dose-Response parameters)
m.LL.4<-drm(y ~x,myfactor, fct = LL.4(names = c("F_max", "P_inf", "OE", "M"))) #4-parameter log-logistic  
m.L.4 <-drm(y ~x,myfactor, fct = L.4()) #changing the fct = LL.4() to fct = L.4() allows plotting on a log10 scale
m.LL2.4<-drm(y ~x,myfactor, fct = LL2.4()) #4-parameter log-logistic with log(e) rather than e as a parameter  
m.LL2.5<-drm(y ~x,myfactor, fct = LL2.5()) #Generalised log-logistic
m.LL.5<-drm(y ~x,myfactor, fct = LL.5()) #5-parameter logistic  
#plot a few side by side
par(mfrow = c(1, 2), oma = c(0, 0, 0, 0))
plot(m.LL.4,log="",  broken = TRUE, bcontrol = list(style = "slash"), col = c(2,6,3,23,56), main = "4-parameter logistic")
plot(m.LL2.5,log="",  broken = TRUE, bcontrol = list(style = "slash"), col = c(2,6,3,23,56), main = "5-parameter logistic")

plot residuals of nonlinear models generated with library(drc)

par(mfrow = c(3, 2), oma = c(0, 0, 0, 0))
{plot(m.MM.3$predres, main = "Michaelis-Menten")  
  abline(0,0)}
{plot(m.W2.4$predres, main = "Weibull2") 
  abline(0,0)}
{plot(m.LL.4$predres, main = "4-parameter logistic") 
  abline(0,0)}
{plot(m.LL.5$predres, main = "5-parameter logistic") 
  abline(0,0)}
{plot(m.BC.5$predres, main = "Brain-Cousens (hormesis)") 
  abline(0,0)}
{plot(m.LL2.5$predres, main = "Generalised log-logistic") 
  abline(0,0)}

#pick one model to be mymodel
mymodel <- m.LL.4
mymodel

A 'drc' model.

Call:
drm(formula = y ~ x, curveid = myfactor, fct = LL.4(names = c("F_max",     "P_inf", "OE", "M")))

Coefficients:
F_max:rouse  F_max:still  P_inf:rouse  P_inf:still     OE:rouse  
      1.392        1.800        1.809        2.149        3.749  
   OE:still      M:rouse      M:still  
      3.751       12.274        7.958  
confint(mymodel)
                2.5 %    97.5 %
F_max:rouse  1.186734  1.597836
F_max:still  1.537194  2.063586
P_inf:rouse  1.653949  1.963275
P_inf:still  2.079132  2.219179
OE:rouse     3.710521  3.787753
OE:still     3.714969  3.786819
M:rouse     10.616221 13.931883
M:still      7.316189  8.600129
summary(mymodel)

Model fitted: Log-logistic (ED50 as parameter) (4 parms)

Parameter estimates:

             Estimate Std. Error t-value   p-value    
F_max:rouse  1.392285   0.101704  13.690 < 2.2e-16 ***
F_max:still  1.800390   0.130226  13.825 < 2.2e-16 ***
P_inf:rouse  1.808612   0.076525  23.634 < 2.2e-16 ***
P_inf:still  2.149156   0.034647  62.030 < 2.2e-16 ***
OE:rouse     3.749137   0.019107 196.219 < 2.2e-16 ***
OE:still     3.750894   0.017775 211.018 < 2.2e-16 ***
M:rouse     12.274052   0.820272  14.963 < 2.2e-16 ***
M:still      7.958159   0.317638  25.054 < 2.2e-16 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error:

 0.03875737 (40 degrees of freedom)

Each level (rouse and still) has a unique curve shape thereby resulting in different parameters for a best-fit log-logistic model. Now that we know this, it is practical to address each set of conditions separately. Based on the 4-parameter logistic model LL.4 we have for roused samples:

\(PLATO_{roused} = P_{inf}+ \frac{OE-P_{inf}}{1+e^{-F_{max}(daysonhops-M)}}\)

or

\(PLATO_{roused} = 1.809 + \frac{1.94}{1+e^{-1.3923(daysonhops-12.274)}}\)

and for untouched (still) samples:

\(PLATO_{still} = 2.149 + \frac{1.60}{1+e^{-1.8004(daysonhops-7.958)}}\)

Plotting and analysis options for multilevel models are limited. By filtering the data to focus on one set of conditions at a time we create ‘simple’ y~x logistic models. Many wonderful visualization tools are available for such x~y models. Here’s just a few:

single level logistic model with confidence intervals

(specify eval=false in R notebook codechunk header {r eval=FALSE} to prevent multiple messages regarding “Recycling array of length 1 in array-vector arithmetic is deprecated” from appearing in html)

df <- FPHcalc %>% filter(expt==" 2A" & rouse==TRUE) 
df$PLATO.hopdrop <- df$initial_plato + df$delta.plato
x <- as.numeric(df$daysonhops)
y <- df$PLATO.hopdrop
## create model
mymodel<-drm(y ~x, fct = LL.4()) #4-parameter logistic with library(drc)
plot(mymodel, main = "default (logarithmic) x-axis")

plot(mymodel, log="", main = "non-logarithmic x-axis")

# create prediction intervals
newdata = data.frame(y = y, x = x)
conf95 <- as.data.frame(predict(mymodel, newdata,interval = "confidence", level = 0.95))
Recycling array of length 1 in array-vector arithmetic is deprecated.
  Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
  Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
  Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
  Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
  Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
  Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
  Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
  Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
  Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
  Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
  Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
  Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
  Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
  Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
  Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
  Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
  Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
  Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
  Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
  Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
  Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
  Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
  Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
  Use c() or as.vector() instead.
conf95$x <- x
pred95 <- as.data.frame(predict(mymodel, newdata, interval = "prediction", level = 0.95))
Recycling array of length 1 in array-vector arithmetic is deprecated.
  Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
  Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
  Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
  Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
  Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
  Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
  Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
  Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
  Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
  Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
  Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
  Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
  Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
  Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
  Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
  Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
  Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
  Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
  Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
  Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
  Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
  Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
  Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
  Use c() or as.vector() instead.
pred95$x <- x
pred999 <- as.data.frame(predict(mymodel, newdata, interval = "prediction", level = 0.999))
Recycling array of length 1 in array-vector arithmetic is deprecated.
  Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
  Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
  Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
  Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
  Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
  Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
  Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
  Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
  Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
  Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
  Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
  Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
  Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
  Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
  Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
  Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
  Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
  Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
  Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
  Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
  Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
  Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
  Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
  Use c() or as.vector() instead.
pred999$x <- x
mymodel

A 'drc' model.

Call:
drm(formula = y ~ x, fct = LL.4())

Coefficients:
b:(Intercept)  c:(Intercept)  d:(Intercept)  e:(Intercept)  
        1.392          1.809          3.749         12.272  

Note: If you compare the multi-level model parameter estimates for rouse=TRUE with those for unilevel model based on rouse=TRUE subset, the results are similar, but not identical!

plot prediction confidence intervals

# Asymmetric ribbons based on prediction intervals
#newdata <- data.frame(y = c(3.5, 3, 2.9, 2.5,1.8,1.7), x = c(1,5,10,20,30,40))
#newdata <- tidyfermi %>% filter(batch = SELECTBATCH) %>% select(days,plato) 
qplot(data=pred95, x=x, y=Prediction, ymin=Lower, ymax=Prediction, geom="ribbon", fill=I("red"), alpha=I(0.2)) +
geom_ribbon(data=pred95, aes(x=x, ymin=Prediction, ymax=Upper), fill=I("blue"), alpha=I(0.2)) +
geom_line(data=pred95, aes(x=x, y=Prediction), color=I("green"), lwd=1)+
geom_point(data=newdata, aes(x=x, y=y, ymin=NULL, ymax=NULL), size=1, col="blue")+
 ylab("y")

# Visualise intervals
# based on Maurits Evers (https://stackoverflow.com/questions/49444489/nonlinear-regression-prediction-in-r?rq=1)
data.frame(x=x, y=y) %>% ggplot(aes(x, y)) +
geom_point() +
geom_line(data = pred95, aes(x = x, y = Prediction)) +
geom_ribbon(data = pred999, aes(x = x, ymin = Lower, ymax = Upper),fill=I("pink"),alpha = 0.4)+
geom_ribbon(data = pred95, aes(x = x, ymin = Lower, ymax = Upper),fill=I("green"),alpha = 0.4)+
geom_ribbon(data = conf95, aes(x = x, ymin = Lower, ymax = Upper),fill=I("purple"),alpha = 0.4);

summary

For cases where 2016 Centennial hops are added to this particular base beer (sampled into 12oz bottles) near the end of primary fermentation at room temperature and bottles remain untouched (still), the 4-parameter logistic equation with the following sets of parameters is a reasonable model of the subsequent hop-induced plato drop: \(PLATO_{still} = f(daysonhops) = 2.149 + \frac{1.60}{1+e^{-1.8004(daysonhops-7.958)}}\)

The following 4-parameter logistic equation is a reasonable model for cases identical to the above, but where bottles are roused once daily: \(PLATO_{roused} = f(daysonhops) = 1.809 + \frac{1.94}{1+e^{-1.3925(daysonhops-12.272)}}\)

The residuals for the 5-parameter logistic models are similar to those obtained for the 4-parameter equation, but in the case of unroused samples a visual inspection reveals very different curves. The 4-parameter logistic predicts a much shorter “lag time” than does the 5-parameter logistic. More data points in the days after dryhopping would shed light on the question as to which model is superior.

Only a single variable (rousing vs. still) among several hundred variables encompassing raw materials, machines, people, process, and gages (measurements) resulted in quite different estimated parameters for a 4-parameter logistic model. These results highlight the fact that all process variables known to significantly impact the endpoints we wish to control must themselves be defined, controlled and/or documented if models such as these are to be useful to characterize the process in question.

session info

sessionInfo()
R version 3.5.1 (2018-07-02)
Platform: x86_64-w64-mingw32/x64 (64-bit)
Running under: Windows >= 8 x64 (build 9200)

Matrix products: default

locale:
[1] LC_COLLATE=English_United States.1252 
[2] LC_CTYPE=English_United States.1252   
[3] LC_MONETARY=English_United States.1252
[4] LC_NUMERIC=C                          
[5] LC_TIME=English_United States.1252    

attached base packages:
[1] stats     graphics  grDevices utils     datasets  methods   base     

other attached packages:
 [1] readxl_1.2.0      data.table_1.11.8 chron_2.3-53     
 [4] lubridate_1.7.4   bindrcpp_0.2.2    gridExtra_2.3    
 [7] drc_3.0-1         memisc_0.99.14.12 MASS_7.3-50      
[10] psych_1.8.10      caret_6.0-81      lattice_0.20-35  
[13] forcats_0.3.0     stringr_1.3.1     purrr_0.2.5      
[16] readr_1.2.1       tidyr_0.8.2       tibble_1.4.2     
[19] tidyverse_1.2.1   ggplot2_3.1.0     dplyr_0.7.8      

loaded via a namespace (and not attached):
 [1] nlme_3.1-137       httr_1.3.1         rprojroot_1.3-2   
 [4] repr_0.19.1        tools_3.5.1        backports_1.1.2   
 [7] R6_2.3.0           rpart_4.1-13       lazyeval_0.2.1    
[10] colorspace_1.3-2   nnet_7.3-12        withr_2.1.2       
[13] tidyselect_0.2.5   mnormt_1.5-5       curl_3.2          
[16] compiler_3.5.1     cli_1.0.1          rvest_0.3.2       
[19] xml2_1.2.0         sandwich_2.5-0     labeling_0.3      
[22] scales_1.0.0       mvtnorm_1.0-8      digest_0.6.18     
[25] foreign_0.8-70     rmarkdown_1.10     rio_0.5.16        
[28] base64enc_0.1-3    pkgconfig_2.0.2    htmltools_0.3.6   
[31] plotrix_3.7-4      rlang_0.3.0.1      rstudioapi_0.8    
[34] bindr_0.1.1        generics_0.0.1     zoo_1.8-4         
[37] jsonlite_1.5       gtools_3.8.1       ModelMetrics_1.2.2
[40] zip_1.0.0          car_3.0-2          magrittr_1.5      
[43] Matrix_1.2-14      Rcpp_1.0.0         munsell_0.5.0     
[46] abind_1.4-5        stringi_1.2.4      multcomp_1.4-8    
[49] yaml_2.2.0         carData_3.0-2      plyr_1.8.4        
[52] recipes_0.1.4      grid_3.5.1         parallel_3.5.1    
[55] crayon_1.3.4       haven_2.0.0        splines_3.5.1     
[58] hms_0.4.2          knitr_1.20         pillar_1.3.0      
[61] reshape2_1.4.3     codetools_0.2-15   stats4_3.5.1      
[64] glue_1.3.0         evaluate_0.12      modelr_0.1.2      
[67] foreach_1.4.4      cellranger_1.1.0   gtable_0.2.0      
[70] assertthat_0.2.0   gower_0.1.2        openxlsx_4.1.0    
[73] prodlim_2018.04.18 broom_0.5.0        class_7.3-14      
[76] survival_2.42-3    timeDate_3043.102  iterators_1.0.10  
[79] lava_1.6.4         TH.data_1.0-9      ipred_0.9-8       
LS0tDQp0aXRsZTogIlIgTm90ZWJvb2s6IE1vZGVsaW5nIHRoZSBGcmVzaGVuaW5nIFBvd2VyIG9mIHRoZSBIb3AiDQpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sNCi0tLQ0KDQo+ICpTdGVwMTogIE9wZW4gdGhpcyAuUk1EIGZpbGUgaW4gUiBTdHVkaW8gYW5kIGNsaWNrIHRoZSAiUHJldmlldyIgYnV0dG9uIGFib3ZlLiAgQSBmb3JtYXR0ZWQgdmVyc2lvbiBzaG91bGQgcG9wIHVwIGluIGEgYnJvd3NlciB3aW5kb3cuICBJRiBub3QsIHRyb3VibGVzaG9vdCB0aGF0ISoNCg0KDQojIG1hbnVhbCBvdXRsaW5lDQoNCjEuIEhvdXNla2VlcGluZw0KICAgIDEuICpTRVQgV09SS0lORyBESVJFQ1RPUlkqIA0KICAgIDEuIHRoaXMgKipSIE1hcmtkb3duICguUk1EKSoqIGRvY3VtZW50IHdhcyBjcmVhdGVkIHVzaW5nIFIgU3R1ZGlvDQogICAgICAgIDEuIHBsYWluIHRleHQNCiAgICAgICAgMS4gIlJlcHJvZHVjaWJsZSBSZXNlYXJjaCIgTW92ZW1lbnQNCiAgICAxLiAqKipSKioqIGZyb20gPGh0dHBzOi8vd3d3LnItcHJvamVjdC5vcmcvPg0KICAgIDEuICoqKlIgU3R1ZGlvKioqIGZyb20gPGh0dHBzOi8vd3d3LnJzdHVkaW8uY29tL3Byb2R1Y3RzL3JzdHVkaW8vZG93bmxvYWQvPg0KICAgIDEuIEVudmlyb25tZW50IHRhYiAoUnN0dWRpbykNCiAgICAxLiBGaWxlcy9QbG90cy9QYWNrYWdlcy9IZWxwL1ZpZXdlciAgdGFiIChSc3R1ZGlvKQ0KICAgIDEuIFByZXZpZXcvdmlldyBhcyBIVE1MICAoUiBNYXJrZG93biBpbiBSc3R1ZGlvKQ0KICAgIDEuIE91dGxpbmUgdGFiIChSIE1hcmtkb3duIGluIFJzdHVkaW8pDQogICAgDQoqIEludHJvDQogICAgKyBTdGF0ZW1lbnQgb2YgUHJvYmxlbQ0KICAgICsgT3ZlcnZpZXcgb2YgRXhwZXJpbWVudGFsIERhdGENCiAgICArIE1lYXN1cmluZyB0aGUgRnJlc2hlbmluZyBQb3dlciBvZiB0aGUgSG9wIChyZWxhdGl2ZSB0byBub24tZHJ5aG9wcGVkIGNvbnRyb2xzKQ0KICAgICsgKmNhbGN1bGF0ZWQgbWV0cmljcyoNCiAgICAgICAgLSAkXERlbHRhJEFCViANCiAgICAgICAgLSAkXERlbHRhJEFCVw0KICAgICAgICAtICRcRGVsdGF7Y2FsY3VsYXRlZH0kQ08kXzIkDQogICAgICAgIC0gJFxEZWx0YSRTRyReezIwLzIwfSQNCiAgICAgICAgLSBmb2xkLWluY3JlYXNlIGV0aGFub2wgZnJvbSBob3AgYWRkaXRpb24gKGluICRcZnJhY3twb3VuZHN9e2JibH0kIGFuZCB3dCUgb3IgJFxmcmFje2dyYW1zfXsxMDAgbUx9JCkNCiAgICAgICAgLSBmb2xkLWluY3JlYXNlIENPJF8yJCBmcm9tIGhvcCBhZGRpdGlvbiAoaW4gZy9MIGFuZCBDTyRfMiQgdm9sdW1lcykNCiAgICAgICAgLSAkXERlbHRhJCAmZGVnO3BsYXRvDQoqIExvYWQgTGlicmFyaWVzDQoqIERhdGEgSW1wb3J0LCBUaWR5IGFuZCBUcmFuc2Zvcm0NCiogRGF0YSBWaXN1YWxpemF0aW9uDQogICAgKyBzY2F0dGVyIHBsb3RzDQogICAgKyBzY2F0dGVyIHBsb3RzIHdpdGggYWRkZWQgZGltZW5zaW9ucw0KKiBNb2RlbGluZyBvdmVydmlldw0KICAgICsgbGluZWFyIHJlZ3Jlc3Npb24NCiAgICArIHJlc2lkdWFscw0KKiBNb2RlbGluZyBGUEgNCg0KDQojIGludHJvDQoqIHRoaXMgaXMgbm90IGEgc3RhdHMgY291cnNlLiAgdXNlIGF0IHlvdXIgb3duIHJpc2suDQoqIFsqIkRhdGEgaXMgbm90IGluZm9ybWF0aW9uLiBJbmZvcm1hdGlvbiBpcyBub3Qga25vd2xlZGdlLiBBbmQga25vd2xlZGdlDQppcyBjZXJ0YWlubHkgbm90IHdpc2RvbS4iKl0oaHR0cDovL3d3dy5kYXRhZ292ZXJuYW5jZS5jb20vcXVvdGVzL2tub3dsZWRnZS1xdW90ZXMvICJDbGlmZm9yZCBTdG9sbCIpDQoqIFtXaWNraGFtJ3MgZm91ciBwaWxsYXJzIG9mIGRhdGEgYW5hbHlzaXM6XShodHRwczovL3I0ZHMuaGFkLmNvLm56LyAiR3JvbGVtdW5kIGFuZCBXaWNraGFtJ3MgUiBmb3IgRGF0YSBTY2llbmNlIikNCiAgICAqIHRpZHkNCiAgICAqIHRyYW5zZm9ybQ0KICAgICogdmlzdWFsaXplDQogICAgKiBtb2RlbA0KICAgIA0KRXZlcnl0aGluZyB1cCB0byB0aGUgaW50ZXJtaXNzaW9uKD8pIGlzIHByaW1hcmlseSBhbiBleGVyY2lzZSBpbiBkYXRhIHdyYW5nbGluZyAodGlkeSt0cmFuc2Zvcm0pIGFuZCBtYXJrZG93biBmb3JtYXR0aW5nIHVzaW5nIFIgU3R1ZGlvLiBUaGUgc2Vjb25kIHBvcnRpb24gY29udGFpbnMgYW4gb3ZlcnZpZXcgb2YgZGF0YSBtb2RlbGluZyBhbmQgYSAqcHJlbGltaW5hcnkqIGF0dGVtcHQgdG8gbW9kZWwgKkhvcCBGcmVzaGVuaW5nIFBvd2VyKiAoc3lub255bXM6ICdkcnlob3AgY3JlZXAnLCAnQUJWIGNyZWVwJywgJ2RyeS1ob3AgY3JlZXAnKSBhcyBhIGNvbXBvbmVudCBvZiB0aGUgd2FybS1kcnlob3BwaW5nIHByb2Nlc3MuIA0KDQpgYGB7ciBlY2hvPVRSVUV9DQojICAgICAgICAgICAgICAgICAgICAgICBfXyAgLl9fICAgIC5fX18gICAgICANCiMgICAgICAgICAgICAgICAgICAgICBfLyAgfF98X198IF9ffCBfL19fLl9fLg0KIyAgICAgICAgICAgICAgICAgICAgIFwgICBfX1wgIHwvIF9fIDwgICB8ICB8DQojICAgICAgICAgICAgICAgICAgICAgIHwgIHwgfCAgLyAvXy8gfFxfX18gIHwNCiMgICAgICAgICAgICAgICAgICAgICAgfF9ffCB8X19cX19fXyB8LyBfX19ffA0KIyAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgXC9cLyAgICAgDQojICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgXC8NCiMgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8fA0KIyAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHx8DQojICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfHwNCiMgICAgICAgICAgICAgIF8gICAgICAgICAgICAgICAgICAgICAvXCAgICAgICAgICAgICAgIA0KIyAgICAgICAgICAgICB8IHxfIF8gXyBfXyBfIF8gXyAgX19fLyBffF9fXyBfIF8gXyBfXyAgDQojICAgICAgICAgICAgIHwgIF98ICdfLyBfYCB8ICcgXChfLTwgIF8vIF8gXCAnX3wgJyAgXCANCiMgICAgICAgICAgICAgIFxfX3xffCBcX18sX3xffHxfL19fL198IFxfX18vX3wgfF98X3xffA0KIyAgICAgICAgICAgICAgICAgICAgICAgIC8vICAgICAgICAgICAgICAgICAgICAgIFxcDQojICAgICAgICAgICAgICAgICAgICAgICAvLyAgICAgICAgICAgICAgICAgICAgICAgIFxcDQojICAgICAgICAgICAgICAgICAgICAgIC8vICAgICAgICAgICAgICAgICAgICAgICAgICBcXA0KIyAgICAgICAgICAgICAgICAgICAgIC8vICAgICAgICAgICAgICAgICAgICAgICAgICAgIFxcDQojICAgICAgICAgIF8gICAgICAgICAvLyAgIF8gXyAgICAgICAgICAgICAgICAgICAgICAgIFxcICAgXyAgICAgXyANCiMgICAgIF9fIF8oXylfX19fICBfIF9fIF98IChfKV9fX19fXyAgICAgICAgXyBfXyAgX19fICBfX3wgfF9fX3wgfA0KIyAgICAgXCBWIC8gKF8tPCB8fCAvIF9gIHwgfCB8XyAvIC1fKS0tLS0tLXwgJyAgXC8gXyBcLyBfYCAvIC1fKSB8DQojICAgICAgXF8vfF8vX18vXF8sX1xfXyxffF98Xy9fX1xfX198LS0tLS0tfF98X3xfXF9fXy9cX18sX1xfX198X3wNCiMNCiNhc2NpaSBmb250L2FydCBmcm9tIDxodHRwOi8vd3d3Lm5ldHdvcmstc2NpZW5jZS5kZS9hc2NpaS8+DQpgYGANCg0KDQojIyBTdGF0ZW1lbnQgb2YgUHJvYmxlbQ0KVGhlIHdhcm0tZHJ5aG9wcGluZyBwcm9jZXNzIGNhbiBwcm9mb3VuZGx5IGltcGFjdCB0aGUgYm9keSwgYWxjb2hvbCBhbmQgdmljaW5hbCBkaWtldG9uZSBjb250ZW50IG9mIHRoZSBiZWVyLiAgQ29tbW9uIG1ldHJpY3MgYW5kIG1vZGVscyBmb3IgdGhpcyBwaGVub21lbm9uIGhhdmUgbm90IGJlZW4gZXN0YWJsaXNoZWQuDQoNCiMjIE92ZXJ2aWV3IG9mIEV4cGVyaW1lbnRhbCBEYXRhDQpUaHJvdWdoIGV4cGVyaWVuY2UgaW4gYnJld2luZyB3ZSBrbm93IHRoYXQgKiplbmRwb2ludHMqKiBvZiBkcnlob3BwaW5nIGluY2x1ZGU6DQoNCiogZmxhdm9yIGltcGFjdCAoYWx3YXlzKQ0KKiBpbXBhY3Qgb24gdmlzdWFsL3ByZXNlbnRhdGlvbiAoc29tZXRpbWVzKQ0KKiAqZXRoYW5vbCBpbmNyZWFzZSogYWNjb21wYW5pZWQgYnkgZGVjcmVhc2UgaW4gc3BlY2lmaWMgZ3Jhdml0eSAoc29tZXRpbWVzKQ0KKiBDTzIgaW5jcmVhc2UgKHNvbWV0aW1lcykNCiogZGlhY2V0eWwvVkRLIGluY3JlYXNlIChzb21ldGltZXMpDQoqIGltcGFjdCBvbiB5ZWFzdCBxdWFsaXR5LCBhbmQgc28gb24uLi4NCg0KRXhwZXJpbWVudHMgd2VyZSBjYXJyaWVkIG91dCB3aGVyZSANCg0KMS4gbXVsdGlwbGUgcmVwbGljYXRlIDI1MC1tTCBzYW1wbGVzIG9mIFtuZWFybHldIGVuZC1mZXJtZW50ZWQgYmVlciB3ZXJlIGNvbGxlY3RlZCBmcm9tIGZlcm1lbnRlcnMgaW50byAxMm96IGFtYmVyIGJvdHRsZXMNCjEuIHRoZSBzYW1wbGVzIHdlcmUgcmFuZG9taXplZCBpbnRvIGdyb3VwcyBvZiB0aHJlZSwgdGhlbiBlYWNoIGdyb3VwIHdhcyBlaXRoZXINCiAgICAqIGRyeS1ob3BwZWQgYXQgfjEuMCBwb3VuZHMvYmJsICh0cmVhdG1lbnQpLCBvcg0KICAgICogbm90IGRyeS1ob3BwZWQgKGNvbnRyb2wpDQoxLiBhbGwgd2VyZSBoYW5kLWNyaW1wZWQgd2l0aCBmb2lsLCBzdG9yZWQgZm9yIHZhcmlvdXMgdGltZXMgKG1vc3RseSBhdCByb29tIHRlbXAsIGEgZmV3IGluIHRoZSBjb2xkKSwgdGhlbiB0ZXN0ZWQgb24gYW4gQW50b24gUGFhciBETUE0NTAwL0FsY29seXplciBjbGFzc2ljDQogICAgICAgIA0KRm9jdXNpbmcgb24gKmV0aGFub2wgaW5jcmVhc2UgYXMgdGhlIGVuZHBvaW50Kiwgd2Uga25vdyB0aGVzZSB0byBiZSAqKnJlbGV2YW50IGZhY3RvcnMqKjoNCg0KKiB2YXJpZXR5IG9mIGhvcHMgKGZpdmUgZGlmZmVyZW50IHZhcmlldGllcykNCiogcHJlc2VuY2Ugb2YgbGl2ZSB5ZWFzdCAodHJ1ZSBpbiBhbGwgY2FzZXMgZm9yIHRoZXNlIGRhdGEpDQoqIHRlbXBlcmF0dXJlIGR1cmluZyBkcnlob3BwaW5nIChtb3N0bHkgd2FybSwgd2l0aCBhIGZldyBpbiB0aGUgY29sZCkNCiogYW1vdW50IG9mIGNvbnRhY3QgdGltZSBiZXR3ZWVuIGhvcHMgYW5kIGJlZXIgKHVwIHRvIDcgd2Vla3MpDQoNCipGb3IgbW9yZSBkZXRhaWxzIG9uIHRoaXMgZGF0YXNldCBzZWUgdGhlIFttYW51c2NyaXB0XShodHRwczovL3d3dy50YW5kZm9ubGluZS5jb20vZG9pL2Z1bGwvMTAuMTA4MC8wMzYxMDQ3MC4yMDE4LjE0NjkwODE/c2Nyb2xsPXRvcCZuZWVkQWNjZXNzPXRydWUgIktpcmtlbmRhbGwyMDE4IikuICANCiogcGxlYXNlIHNlbmQgY29tbWVudHMsIGNvbXBsYWludHMgYW5kIGNvcnJlY3Rpb25zIHRvIGx1a2VAYmVsbHNiZWVyLmNvbSENCiogRGF0YSBhcmUgZnJvbSB0aGUgZmlsZSAidWpiY19hXzE0NjkwODFfc201NDk2LnR4dCIgKHN1cHBsZW1lbnRhcnkgZGF0YSBmcm9tIEphY29iIEEuIEtpcmtlbmRhbGwsIENhcnRlciBBLiBNaXRjaGVsbCAmIEx1Y2FzIFIuIENoYWR3aWNrICgyMDE4KTogVGhlIEZyZXNoZW5pbmcgUG93ZXIgb2YgQ2VudGVubmlhbCBIb3BzLCAqSm91cm5hbCBvZiB0aGUgQW1lcmljYW4gU29jaWV0eSBvZiBCcmV3aW5nIENoZW1pc3RzKiBWb2x1bWUgNzYsIElzc3VlIDMsIFBhZ2VzIDE3OC0xODQgKCoqMjAxOCoqKSBET0k6IDEwLjEwODAvMDM2MTA0NzAuMjAxOC4xNDY5MDgxKiBhdmFpbGFibGUgZnJvbSA8aHR0cHM6Ly93d3cudGFuZGZvbmxpbmUuY29tL2RvaS9mdWxsLzEwLjEwODAvMDM2MTA0NzAuMjAxOC4xNDY5MDgxP3Njcm9sbD10b3AmbmVlZEFjY2Vzcz10cnVlPg0KDQoNCiMgbG9hZCBsaWJyYXJpZXMNCmBgYHtyIHBhY2thZ2UubG9hZCwgcmVzdWx0cz0naGlkZScsIGVjaG89VFJVRX0gDQojLnJzLnJlc3RhcnRSKCkgICMgcmVzdGFydCBSIA0Kc2Vzc2lvbkluZm8oKQ0KDQojZGF0YSB3cmFuZ2xpbmcNCiNpbnN0YWxsLnBhY2thZ2VzKCJkcGx5ciIpICAjIGRwbHlyICJkYXRhIHBseWVyIiAgDQpsaWJyYXJ5KGRwbHlyKQ0KbGlicmFyeShnZ3Bsb3QyKQ0KbGlicmFyeSh0aWR5dmVyc2UpDQoNCmxpYnJhcnkoY2FyZXQpICMgZm9yIGR1bW15VmFycyBmdW5jdGlvbg0KbGlicmFyeShwc3ljaCkgIyBmb3IgcGFpcnMucGFuZWxzIGZ1bmN0aW9uDQpsaWJyYXJ5KG1lbWlzYykgICMgY29tcGFyZSBtb2RlbHMNCg0KbGlicmFyeShkcmMpICAjICJkcmMiID0gZG9zZSByZXNwb25zZSBjdXJ2ZXMgKGNvbnRhaW5zIGEgc3VpdGUgb2YgZmxleGlibGUgYW5kIHZlcnNhdGlsZSBtb2RlbCBmaXR0aW5nIGFuZCBhZnRlci1maXR0aW5nIGZ1bmN0aW9ucykNCiNsaWJyYXJ5KHBsb3RseSkNCmxpYnJhcnkoZ3JpZEV4dHJhKSAgIyBmb3IgZ3JpZC5hcnJhbmdlIGluIFIgbWFya2Rvd24NCmBgYA0KDQoNCg0KPiBmbGFzaCBmb3J3YXJkISBzY3JvbGwgdG8gdGhlIGJvdHRvbSBhbmQgcnVuIGJpZ2NodW5rX3RpZHl0cmFuc2Zvcm0NCg0KDQojaW1wb3J0IGRhdGENCmBgYHtyfQ0Kcm0obGlzdCA9IGxzKCkpICMgY2xlYXIgd29ya3NwYWNlDQpteWRhdGEgPC1yZWFkLmNzdigidWpiY19hXzE0NjkwODFfc201NDk2LnR4dCIsc3RyaW5nc0FzRmFjdG9ycyA9IEZBTFNFKSAgIyMgU1BFQ0lGWSBmaWxlbmFtZQ0KDQpkcHV0KG5hbWVzKG15ZGF0YSkpDQpgYGANCg0KDQojICFtYW51YWwgYWRkIHRpbWUgemVybyB0byAiZGF5cyBvbiBob3BzIiBleHBlcmltZW50IQ0KQW50b24gUGFhciBkYXRhIHdlcmUgY29sbGVjdGVkIGF0IHRpbWUgemVybyBmb3Iga2luZXRpY3MgZXhwZXJpbWVudCAodHJpcGxpY2F0ZSBzYW1wbGVzIHJ1biBpbW1lZGlhdGVseSBhZnRlciBzYW1wbGUgY29sbGVjdGlvbiwgd2hpbGUgdGhlIHJlbWFpbmRlciB3ZXJlIGJlaW5nIGRyeS1ob3BwZWQpLCBidXQgd2VyZSBpbmFkdmVydGVudGx5IGV4Y2x1ZGVkIGZyb20gdGhlIG9yaWdpbmFsIGRhdGFzZXQuICBIZXJlIHRoZXkgYXJlOg0KRGF0ZQl0aW1lCXNhbXBsZSBuby4JQUJWCUFCVwlPRSAoUCkJRXIJRWEJU0cgMjAvMjAJUkRGCUFERiBzYW1wbGVfaWQNCjA3LzI3LzE3CTM6MjYgUE0JMDNfCTYuNjgJNS4yCTE1Ljk1CTYuMDgJMy43NAkxLjAxNDYyCTYzLjg4CTc2LjU4CUpLMS0yMC00MQ0KMDcvMjcvMTcJMzozMCBQTQkwNF8JNi42OAk1LjIJMTUuOTUJNi4wOAkzLjczCTEuMDE0NjEJNjMuODkJNzYuNglKSzEtMjAtNTANCjA3LzI3LzE3CTM6MzQgUE0JMDVfCTYuNjgJNS4yCTE1Ljk1CTYuMDgJMy43NAkxLjAxNDYyCTYzLjg4CTc2LjU4CUpLMS0yMC04Nw0KDQpgYGB7cn0NCiMjIGZpbGwgaW4gTUlTU0lORyBEQVRBIDooICBhZGQgdGltZSB6ZXJvIGRhdGEgZm9yIHRpbWUgc2VyaWVzIGFuYWx5c2lzKQ0KDQojIyBuZXN0ZWQgaWZlbHNlIHRvIGFkZCB0aW1lIHplcm8gcGxhdG8gdmFsdWVzIGZvciBlYWNoIGV4cHQ6DQpteWRhdGE8LSBteWRhdGEgJT4lIG11dGF0ZSguLCBpbml0aWFsX3BsYXRvID0gaWZlbHNlKGV4cHQ9PSIgMkEiLCAzLjc0LCBpZmVsc2UoZXhwdD09IiAxQSIsIDMuNjksIGlmZWxzZShleHB0PT0iIDFCIiwgMy41MywgaWZlbHNlKGV4cHQ9PSIgM0IiLCAzLjU0LCBpZmVsc2UoZXhwdD09IiA0QiIsIDQuNjYsIE5BKSkpKSkpDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICANCiMjIGFkZCBjb21wbGV0ZSB0aW1lLXplcm8gYW50b24gcGFhciBkYXRhIGZvciB0aW1lIHNlcmllcyAoZXhwdDJBKQ0KIyMgcXVhZHJ1cGxlLXVwIGVhY2ggdHJpcGxpY2F0ZSB0aW1lLXplcm8gbWVhc3VyZW1lbnQgKG9uZSBjb3B5IGZvciBlYWNoIE5IL0RIICYgcm91c2Uvc3RpbGwgY29tYm8pDQojIyBub3RlOiAgdGhpcyBwcmFjdGljZSBpcyBvZiBxdWVzdGlvbmFibGUgc3RhdGlzdGljYWwgcmlnb3IuICBJbiByZXRyb3NwZWN0IGl0IHdvdWxkIGhhdmUgYmVlbiBiZXR0ZXIgKGFuZCBhIGxvdCBlYXNpZXIpIHRvIGp1c3QgcnVuIDEyIHNhbXBsZXMgdGhyb3VnaCBBbnRvbiBvbiBkYXkwIQ0KdGltZXplcm8gPC0gbXlkYXRhICU+JSBmaWx0ZXIoZXhwdD09IiAyQSIpICU+JSBhcnJhbmdlKHNwZWNpYWxfZ3JvdXApICU+JSBzbGljZSgxOjEyKQ0KI2RhdGEuZW50cnkodGltZXplcm8pDQp0aW1lemVybyRUZXN0X0RhdGUgPC0gcmVwKGMoIjcvMjcvMjAxNyIsIjcvMjcvMjAxNyIsIjcvMjcvMjAxNyIpLDQpDQp0aW1lemVybyRUZXN0LnRpbWUgPC0gcmVwKGMoIjM6MjYgUE0iLCAiMzozMCBQTSIsICIzOjM0IFBNIiksNCkNCnRpbWV6ZXJvJEFCViA8LSA2LjY4DQp0aW1lemVybyRBQlcgPC0gNS4yDQp0aW1lemVybyRPRSA8LSAxNS45NQ0KdGltZXplcm8kRXIgPC0gNi4wOA0KdGltZXplcm8kRWEgPC0gcmVwKGMoMy43NCwgMy43MywgMy43NCksNCkNCnRpbWV6ZXJvJFNHIDwtIHJlcChjKDEuMDE0NjIsIDEuMDE0NjEsIDEuMDE0NjIpLDQpDQp0aW1lemVybyRSREYgPC0gcmVwKGMoNjMuODgsIDYzLjg5LCA2My44OCksNCkNCnRpbWV6ZXJvJEFERiA8LSByZXAoYyg3Ni41OCwgNzYuNjAsIDc2LjU4KSw0KQ0KdGltZXplcm8kQ2Fsb3JpZXMgPC0gcmVwKGMoMjE1LjM3LDIxNS4zNCwyMTUuMzUpLDQpDQp0aW1lemVyb1t0aW1lemVybyRob3A9PSJESCIsXSRjb250YWN0X2RheXMgPC0wDQp0aW1lemVyb1ssYygyODozMSldIDwtIDANCm15ZGF0YSA8LSByYmluZCh0aW1lemVybyxteWRhdGEpDQp3cml0ZS5jc3YobXlkYXRhLCAicXVpY2tpZS5jc3YiLCByb3cubmFtZXMgPSBGQUxTRSkNCm15ZGF0YTwtcmVhZC5jc3YoInF1aWNraWUuY3N2Iiwgc3RyaW5nc0FzRmFjdG9ycyA9IEZBTFNFKQ0KDQojIyMgIHRoaXMgaXMgYSByb3V0aW5lIGZvciBpbnNwZWN0aW5nIGRhdGEgIyMjDQpteWRhdGF0eXBlczwtIGFzLmRhdGEuZnJhbWUoY2JpbmQoc2FwcGx5KG15ZGF0YSwgY2xhc3MpKSkgICMjIHRhYmxlIG9mIGRhdGF0eXBlcw0KbXlkYXRhdHlwZXMkaGVhZGVycyA8LSByb3duYW1lcyhteWRhdGF0eXBlcykgICAgIyMgY29udmVydCByb3duYW1lcyB0byB2YWx1ZXMNCm5hX2NvdW50IDwtYXMuZGF0YS5mcmFtZShzYXBwbHkobXlkYXRhLCBmdW5jdGlvbih5KSBzdW0obGVuZ3RoKHdoaWNoKGlzLm5hKHkpKSkpKSkgIyMgY291bnQgTkFzIGJ5IGNvbHVtbg0KbXlkYXRhT3ZlcnZpZXc8LWFzLmRhdGEuZnJhbWUoY2JpbmQobmFfY291bnQsbXlkYXRhdHlwZXMpKSAgIyMgdGFibGUgb2YgTkEgY291bnRzIGFuZCBkYXRhdHlwZXMNCm5hbWVzKG15ZGF0YU92ZXJ2aWV3KSA8LSBjKCJOQV9jb3VudCIsIkRhdGFfQ2xhc3MiLCAiSGVhZGVyIikNCiNzdW0oaXMubmEobXlkYXRhKSkgICMjIHRvdGFsIGNvdW50IG9mIE5BIHZhbHVlcyBpbiBlbnRpcmUgc2hlZXQgKDIzMSBpbiB0aGlzIGNhc2UpDQojbXlkYXRhT3ZlcnZpZXcgJT4lIGZpbHRlcihOQV9jb3VudD4wKSAgICAjIyBicmVha2Rvd24gb2YgTkEgdmFsdWVzDQpgYGANCg0KDQojIHRpZHkgJiB0cmFuc2Zvcm0gDQoNCj4gbm90ZTogdGhpcyBpcyBhIGJhc2UgUiArZHBseXIgZGF0YSB3cmFuZ2xpbmcgZXhlcmNpc2UhIGluIGdlbmVyYWwgaXQncyBiZXN0IHRvIHVzZSBsdWJyaWRhdGUgcGFja2FnZSBmb3IgZGF0ZS90aW1lc3RhbXBzISANCg0KKiBjcmVhdGUgYSBkYXRlIGNvbHVtbi4gIEhlcmUgd2UgY3JlYXRlIG5ldyBkYXRlIGNvbHVtbnMgaW4gUE9TSVhjdCBmb3JtYXQsIGJhc2VkIG9uIHRoZSBwYXJ0aWN1bGFyIGRhdGUgZm9ybWF0IHVzZWQgaW4gdGhlIHNvdXJjZSBkYXRhICh1amJjX2FfMTQ2OTA4MV9zbTU0OTYudHh0OyBzZWUgP2FzLlBPU0lYY3QpLiAgRmlyc3QgYW4gZXhhbXBsZSBvZiBob3cgTk9UIHRvIGNyZWF0ZSBuZXcgdmFyaWFibGVzIGluIG91ciBkYXRhZnJhbWU6DQpgYGB7cn0NCnRlc3RfZGY8LSBteWRhdGEgIyAob3JpZ2luYWwgZGF0YWZyYW1lIHRvIGJlIHVzZWQgZm9yIHRlc3RpbmcvaWxsdXN0cmF0aW9uKQ0KDQp4X2JyZXdfZGF0ZS5QT1NJWGN0PC0gYXMuUE9TSVhjdChteWRhdGEkYnJld19kYXRlLCBmb3JtYXQgPSAiJW0vJWQvJVkiKSAjIGFzIGEgc3RhbmRhbG9uZSB2ZWN0b3IgKFVTRUxFU1MgaGVyZSksIG9yOg0KdGVzdF9kZiRicmV3X2RhdGUuUE9TSVhjdCA8LWFzLlBPU0lYY3QobXlkYXRhJGJyZXdfZGF0ZSwgZm9ybWF0ID0gIiVtLyVkLyVZIikgICMgYXMgYSAibmV3IGNvbHVtbiIgaW4gb3VyIGRhdGFmcmFtZQ0KYGBgDQoNCg0KKiBBRFZBTkNFRCBkYXRhIHdyYW5nbGluZzogDQogICAgKiBjcmVhdGUgbXVsdGlwbGUgZGF0ZSBjb2x1bW5zIHVzaW5nIEZPUkxPT1ANCihoZXJlIHdlIHRha2UgYWR2YW50YWdlIG9mIHRoZSBwYXR0ZXJuIHRoYXQgb2Ygb3VyIChjaGFyYWN0ZXIpIGRhdGUvdGltZSBjb2x1bW5zIGhhdmUgdGhlIHN0cmluZyAiZGF0ZSIgaW4gdGhlIGhlYWRlcm5hbWUuICBGaXJzdCBtYWtlIGNoYXJhY3RlciB2ZWN0b3Igb2YgYWxsIGNvbHVtbiBuYW1lcyBjb250YWluaW5nIHN0cmluZyAiZGF0ZSIsIHRoZW4gcnVuIEZPUkxPT1Agb3ZlciBlYWNoIGRhdGUgY29sdW1uKS4gIFRoaXMgZm9ybG9vcCB3aWxsIGNvbnZlcnQgZGF0ZWNvbHMgaW50byBQT1NJWGN0IGZvcm1hdCAoUi91bml2ZXJzYWwgZGF0ZXRpbWUgZm9ybWF0KS4gIFRoZXkgc2F5IGl0J3MgICoqYmVzdCB0byBhdm9pZCBmb3Jsb29wcyoqIGluIGdlbmVyYWwsIGFuZCAqKmluIHRoaXMgY2FzZSB5b3Ugd291bGQgcHJvYmFibHkgdXNlIGZ1bmN0aW9ucyBmcm9tIGx1YnJpZGF0ZSBwYWNrYWdlKiogLSBhbmQgaW4gZ2VuZXJhbCBiZSBhd2FyZSB0aGVyZSdzIHByb2JhYmx5IGEgc3BlY2lmaWMgdG9vbCBmb3IgdGhlIHRhc2sgYXQgaGFuZCkhIQ0KYGBge3J9DQpkYXRlY29sczwtIGRwdXQobmFtZXMoZHBseXI6OnNlbGVjdChteWRhdGEsIG1hdGNoZXMoImRhdGUiKSkpKSAgIyMgaGVhZGVycyBjb250YWluaW5nIHN0cmluZyAiZGF0ZSIgPT4gYygiYnJld19kYXRlIiwgInNhbXBsZV9jb2xsZWN0aW9uX2RhdGUiLCAiZHJ5aG9wX2RhdGUiLCAiVGVzdF9EYXRlIikNCiMjIEZPUkxPT1ANCmZvciAoaWNvbCBpbiBkYXRlY29scykgew0KICBuZXdjb2wgPSBwYXN0ZTAoaWNvbCwiLlBPU0lYY3QiKQ0KICMgcHJpbnQobmV3Y29sKQ0KICBteWRhdGFbLCBuZXdjb2xdID0gYXMuUE9TSVhjdChteWRhdGFbLCBpY29sXSxmb3JtYXQgPSAiJW0vJWQvJVkiKSAjIyMgIENSRUFURSBORVcgY29sdW1ucyB3aXRoIFBPU0lYY3QgICANCiMgIG15ZGF0YVssIG5ld2NvbF0gPSBhcy5QT1NJWGN0KGFzLm51bWVyaWMobXlkYXRhWywgaWNvbF0pICAqICg2MCo2MCoyNCksIG9yaWdpbj0iMTg5OS0xMi0zMCIpICMjIyAgbWljcm9zb2Z0IHRpbWVzDQp9DQpgYGANCg0KDQoqIGNyZWF0ZSBkYXlvZmFkZGl0aW9uLCBkYXlzb25ob3BzLCBob3BzX2dfMTAwbUwsIHBvdW5kc19iYmwgdmFyaWFibGVzIHdpdGggZHBseXIgIm11dGF0ZSINCmBgYHtyfQ0KbXlkYXRhPC0gbXlkYXRhICU+JSANCiAgbXV0YXRlKGRheW9mYWRkaXRpb24gPSBhcy5pbnRlZ2VyKGFzLm51bWVyaWMoZGlmZnRpbWUoZHJ5aG9wX2RhdGUuUE9TSVhjdCxicmV3X2RhdGUuUE9TSVhjdCkpKSwNCiAgICAgICAgIGRheXNvbmhvcHMgPSBhcy5pbnRlZ2VyKGFzLm51bWVyaWMoZGlmZnRpbWUoVGVzdF9EYXRlLlBPU0lYY3QsZHJ5aG9wX2RhdGUuUE9TSVhjdCkpLyg2MCo2MCoyNCkpLA0KICAgICAgICAgaG9wc19nXzEwMG1MID0gKG1nX2hvcHMvdm9sdW1lX21MKS8xMCwNCiAgICAgICAgIHBvdW5kc19iYmwgPSAobWdfaG9wcy92b2x1bWVfbUwpKjExNy80NTQNCiAgICAgICAgICkNCmBgYA0KDQoNCiogQURWQU5DRUQgZGF0YSB3cmFuZ2xpbmc6IG1hbnVhbGx5IHVucGFjayBvdmVybG9hZGVkIGNvbHVtbnMgd2l0aCBncmVwbA0KYGBge3J9DQpteWRhdGEkT1g8LWdyZXBsKCJPWCIsIG15ZGF0YSRIb3BfdHlwZSkgICAgICAgIyMgIGNyZWF0ZSBsb2dpY2FsICJPWCIgY29sdW1uDQpteWRhdGEkcm91c2U8LWdyZXBsKCJyb3VzZSIsIG15ZGF0YSRzcGVjaWFsX2dyb3VwKSMjICBjcmVhdGUgbG9naWNhbCBjb2x1bW4NCm15ZGF0YSRHcmluZDwtZ3JlcGwoIkdyaW5kIiwgbXlkYXRhJEhvcF90eXBlKSAgICAgIyMgIGNyZWF0ZSBsb2dpY2FsIGNvbHVtbg0KbXlkYXRhJENvbmU8LWdyZXBsKCJDb25lIiwgbXlkYXRhJEhvcF90eXBlKSAgICAgICAjIyAgY3JlYXRlIGxvZ2ljYWwgY29sdW1uDQpteWRhdGEkaGFydmVzdDIwMTQ8LWdyZXBsKCIxNCIsIG15ZGF0YSRIb3BfdHlwZSkgICMjICBjcmVhdGUgbG9naWNhbCBjb2x1bW4NCm15ZGF0YSRoYXJ2ZXN0MjAxNTwtZ3JlcGwoIjE1IiwgbXlkYXRhJEhvcF90eXBlKSAgIyMgIGNyZWF0ZSBsb2dpY2FsIGNvbHVtbg0KbXlkYXRhJGhhcnZlc3QyMDE3PC1ncmVwbCgiMTciLCBteWRhdGEkSG9wX3R5cGUpICAjIyAgY3JlYXRlIGxvZ2ljYWwgY29sdW1uDQpgYGANCg0KKiBBRFZBTkNFRCBkYXRhIHdyYW5nbGluZzogaWZlbHNlIHN0YXRlbWVudHMgZm9yIGhhcnZlc3R5ZWFyLCBmb3JtX29mX2hvcHMsIGFuZCBESF90ZW1wDQpgYGB7cn0NCiMjIGlmZWxzZSBzdGF0ZW1lbnQgZm9yIGhhcnZlc3R5ZWFyIChpZiBuZWl0aGVyIDIwMTQgbm9yIDIwMTUgbm9yIDIwMTcsIHRoZW4gMjAxNikNCm15ZGF0YSRoYXJ2ZXN0WWVhciA8LSBpZmVsc2UoDQogIG15ZGF0YSRoYXJ2ZXN0MjAxND09VFJVRSwgMjAxNCwNCiAgaWZlbHNlKG15ZGF0YSRoYXJ2ZXN0MjAxNT09VFJVRSwgMjAxNSwgDQogICAgICAgICBpZmVsc2UobXlkYXRhJGhhcnZlc3QyMDE3PT1UUlVFLCAyMDE3LCAyMDE2KSkpDQpkcHV0KGxldmVscyhhcy5mYWN0b3IobXlkYXRhJGhhcnZlc3RZZWFyKSkpDQojIyBpZmVsc2Ugc3RhdGVtZW50IGZvciBmb3JtIG9mIGhvcHMgKGlmIG5laXRoZXIgY29uZSBub3IgZ3JvdW5kIG5vciBOSCwgdGhlbiBwZWxsZXQpIA0KbXlkYXRhJGZvcm1fb2ZfaG9wcyA8LSBpZmVsc2UoDQogIG15ZGF0YSRDb25lPT1UUlVFLCAiY29uZSIsDQogIGlmZWxzZShteWRhdGEkR3JpbmQ9PVRSVUUsICJncm91bmQiLA0KICAgICAgICAgaWZlbHNlKG15ZGF0YSRIb3BfdHlwZT09Ik5IIiwgIk5IIiwgInBlbGxldCIpKSkNCmRwdXQobGV2ZWxzKGFzLmZhY3RvcihteWRhdGEkZm9ybV9vZl9ob3BzKSkpDQojIyBpZmVsc2Ugc3RhdGVtZW50IGZvciB0ZW1wZXJhdHVyZSBncmVhdGVyIG9yIGxlc3MgdGhhbiAxMCANCm15ZGF0YSRESF90ZW1wIDwtIGlmZWxzZSgNCiAgbXlkYXRhJHRlbXAuQzwxMCwgImNvbGQiLA0KICBpZmVsc2UobXlkYXRhJHRlbXAuQz4xMCwgIndhcm0iLCAic29tZXRoaW5nIGVsc2UiKSkNCmRwdXQobGV2ZWxzKGFzLmZhY3RvcihteWRhdGEkREhfdGVtcCkpKQ0KYGBgDQoNCiogQURWQU5DRUQgZGF0YSB3cmFuZ2xpbmc6IGNyZWF0ZSAidmFyaWV0eSIgY29sdW1uIHN0YXJ0aW5nIHdpdGggb3ZlcmxvYWRlZCAiSG9wX3R5cGUiIGNvbHVtbiwgdGhlbiBzdHJpcHBpbmcgYXdheSBhbGwgdGhlIG5vbi12YXJpZXR5IGluZm9ybWF0aW9uDQpgYGB7cn0NCm15ZGF0YSR2YXJpZXR5PC1teWRhdGEkSG9wX3R5cGUNCm15ZGF0YSR2YXJpZXR5PC0gZ3N1YigiWzAtOV0rIiwiIiwgbXlkYXRhJHZhcmlldHkpICMjIHJlbW92ZSBhbGwgbnVtYmVycw0KbXlkYXRhJHZhcmlldHk8LSBnc3ViKCJPWCIsIiIsIG15ZGF0YSR2YXJpZXR5KSAgICAgIyMgcmVtb3ZlIHNwZWNpZmljIHRleHQNCm15ZGF0YSR2YXJpZXR5PC0gZ3N1YigiR3JpbmQiLCIiLCBteWRhdGEkdmFyaWV0eSkNCm15ZGF0YSR2YXJpZXR5PC0gZ3N1YigiQ29uZSIsIiIsIG15ZGF0YSR2YXJpZXR5KQ0KbXlkYXRhJHZhcmlldHk8LSBnc3ViKCIgIiwiIiwgbXlkYXRhJHZhcmlldHkpICAgICAgIyMgcmVtb3ZlIHNwYWNlcw0KZHB1dChsZXZlbHMoYXMuZmFjdG9yKG15ZGF0YSR2YXJpZXR5KSkpDQpgYGANCg0KKiBBRFZBTkNFRCBkYXRhIHdyYW5nbGluZzogY3JlYXRlICJFWFBUbmV3IiAoZWFjaCB1bmlxdWUgZXhwZXJpbWVudGFsIGdyb3VwIGRlc2lnbmF0ZWQgaW4gYSBzaW5nbGUgdmFyaWFibGUpDQpgYGB7cn0NCm15ZGF0YTwtIG15ZGF0YSAlPiUgbXV0YXRlKEVYUFRuZXc9cGFzdGUwKCJncm91cCIsZXhwdCwgc3Vic3RyKHNwZWNpYWxfZ3JvdXAsIDEsMiksYXMuY2hhcmFjdGVyKHJvdXNlKSkpDQojIGNsZWFuIGl0IHVwIGJ5IHJlbW92aW5nICJOQSIgYW5kIGFueSBzcGFjZXMgZHVlIHRvIGNhbmFyeWNvZGUgYnVnDQpteWRhdGEkRVhQVG5ldyA8LSBnc3ViKCIgIiwiIiwgbXlkYXRhJEVYUFRuZXcpICAjIyByZW1vdmUgYW55IHNwYWNlcw0KbXlkYXRhJEVYUFRuZXcgPC0gZ3N1YigiTkEiLCIiLCBteWRhdGEkRVhQVG5ldykgIyMgcmVtb3ZlICJOQSINCmRwdXQobGV2ZWxzKGFzLmZhY3RvcihteWRhdGEkZXhwdCkpKQ0KZHB1dChsZXZlbHMoYXMuZmFjdG9yKG15ZGF0YSRFWFBUbmV3KSkpDQpgYGANCg0KDQojIyBzZWxlY3QgYW5kIHJlYXJyYW5nZSBjb2x1bW5zIA0KKGV4cGVyaW1lbnRhbCBmYWN0b3JzIG9uIHRoZSBsZWZ0LCB0aGVuIG1lYXN1cmVtZW50cywgZm9sbG93ZWQgYnkgY2FsY3VsYXRpb25zIGFuZCB0aGVuIGFsbCBkYXRlIGNvbHVtbnMgb24gdGhlIHJpZ2h0KToNCmBgYHtyfQ0KbXlkYXRhIDwtIG15ZGF0YSAlPiUgDQogIGRwbHlyOjpzZWxlY3Qoc2FtcGxlX2lkLGV4cHQsIEVYUFRuZXcsIGhvcCwgQklOaG9wLCB2YXJpZXR5LCBPWCwgaGFydmVzdFllYXIsIGZvcm1fb2ZfaG9wcyxzcGVjaWFsX2NvbmRpdGlvbnMsIHJvdXNlLCBkYXlzb25ob3BzLCBkYXlvZmFkZGl0aW9uLCBESF90ZW1wLCB0ZW1wLkMsIGhvcHNfZ18xMDBtTCwgcG91bmRzX2JibCwNCiAgICAgICAgIEFCViwgQUJXLCBPRSwgRXIsIEVhLCBTRywgUkRGLCBBREYsIENhbG9yaWVzLCANCiAgICAgICAgIGRob3BfZGF5LCBjb250YWN0X2RheXMsIFJFRl9OSCwgQUJWX2luY3JlYXNlLCANCiAgICAgICAgIGJyZXdfZGF0ZS5QT1NJWGN0LCBzYW1wbGVfY29sbGVjdGlvbl9kYXRlLlBPU0lYY3QsIGRyeWhvcF9kYXRlLlBPU0lYY3QsVGVzdF9EYXRlLlBPU0lYY3QsIGluaXRpYWxfcGxhdG8pDQojIyByZW1vdmUgIi5QT1NJWGN0IiBzdWZmaXguICBMZWF2aW5nIGl0IGFzLWlzIHdpbGwgb25seSBhZGQgdG8gY29uZnVzaW9uIGlmL3doZW4gdGhlc2UgZGF0YSBhcmUgc2F2ZWQgYW5kIHJlLWltcG9ydGVkIChhbmQgYmVjb21lICdjaGFyYWN0ZXInIGZvcm1hdCBhZ2FpbiEpDQpjb2xuYW1lcyhteWRhdGEpID0gZ3N1YigiLlBPU0lYY3QiLCAiIiwgY29sbmFtZXMobXlkYXRhKSkNCmBgYA0KDQoNCg0KIyNjb21wdXRlIG1lYW4gTkggKGNvbnRyb2wpIHZhbHVlcyANCihOSCA9IG5vdCBkcnktaG9wcGVkKSANCmBgYHtyfQ0KIyMgbWVhbiBOSCAoY29udHJvbCkgdmFsdWVzIGZvciBlYWNoIEVYUFRuZXcgZ3JvdXANCm1lYW4uY29udHJvbF9OSDwtIG15ZGF0YSAlPiUgDQogIGdyb3VwX2J5KEVYUFRuZXcpICU+JQ0KICBmaWx0ZXIoaG9wPT0iTkgiKSAlPiUNCiAgc3VtbWFyaXNlX2F0KHZhcnMoQUJWLCBBQlcsIEVhLCBTRyksZnVucyhtZWFuLCBuKCkpKQ0KIyMgcmVwbGFjZSAiX21lYW4iIHdpdGggIi5jb250cm9sX05IIiBpbiBjb2x1bW4gbmFtZXMNCmNvbG5hbWVzKG1lYW4uY29udHJvbF9OSCkgPSBnc3ViKCJfbWVhbiIsICIuY29udHJvbF9OSCIsIGNvbG5hbWVzKG1lYW4uY29udHJvbF9OSCkpDQpgYGANCg0KDQojI2NvbXB1dGUgJFxEZWx0YSQgdmFsdWVzIChkaWZmZXJlbmNlcyByZWxhdGl2ZSB0byBOSCBjb250cm9scykgdXNpbmcgb2JqZWN0cyBjcmVhdGVkIGFib3ZlLi4uDQoqIGRpZmZlcmVuY2Ugb2YgZWFjaCBBQlYsQUJXLEVhLCBhbmQgU0cgZGF0YSBwb2ludCBmcm9tIGNvcnJlc3BvbmRpbmcgbWVhbi5jb250cm9sX05IIHZhbHVlcyAodW5ob3BwZWQgc2FtcGxlcyBpbiBzYW1lIEVYUFRuZXcgZ3JvdXApDQpgYGB7cn0NCiMjIGZpcnN0IGpvaW4gb3VyIGRhdGEgd2l0aCBtZWFuIEFCViBmb3IgdW5ob3BwZWQgc2FtcGxlcyBpbiBnaXZlbiBleHBlcmltZW50IChtZWFuLmNvbnRyb2xfTkg7IGNhbGN1bGF0ZWQgYWJvdmUpDQoNCkZQSGNhbGM8LSBsZWZ0X2pvaW4obXlkYXRhLCBtZWFuLmNvbnRyb2xfTkgsIGJ5PSJFWFBUbmV3IikNCg0KIyMgY2FsY3VsYXRlIEFCV19pbmNyZWFzZSBieSBzdWJ0cmFjdGluZyBlYWNoIGluZGl2aWR1YWwgQUJXIG1lYXN1cmVtZW50IGZyb20gcmVzcGVjdGl2ZSBtZWFuLmNvbnRyb2xfTkg6DQpGUEhjYWxjJGRlbHRhLkFCViA8LSBGUEhjYWxjJEFCViAtIEZQSGNhbGMkQUJWLmNvbnRyb2xfTkgNCkZQSGNhbGMkZGVsdGEuQUJXIDwtIEZQSGNhbGMkQUJXIC0gRlBIY2FsYyRBQlcuY29udHJvbF9OSA0KRlBIY2FsYyRkZWx0YS5wbGF0byA8LSBGUEhjYWxjJEVhIC0gRlBIY2FsYyRFYS5jb250cm9sX05IDQpGUEhjYWxjJGRlbHRhLlNHIDwtIEZQSGNhbGMkU0cgLSBGUEhjYWxjJFNHLmNvbnRyb2xfTkgNCg0KIyMgdGhlIGNvbnRyb2wgc2FtcGxlcyBoYXZlIHNlcnZlZCB0aGVpciBwdXJwb3NlLCBub3cgcmVtb3ZlIHRoZW0gZnJvbSBkYXRhc2V0LiBUaGUgZm9sbG93aW5nIGNhbGN1bGF0aW9ucyBhcmUgb25seSBtZWFuaW5nZnVsIGZvciBkcnktaG9wcGVkIHNhbXBsZXMuICANCkZQSGNhbGM8LSBGUEhjYWxjICU+JSBmaWx0ZXIoaG9wPT0iREgiKQ0KYGBgDQoNCg0KIyMgKmNhbGN1bGF0ZSogY29ycmVzcG9uZGluZyBDTzIgcHJvZHVjdGlvbiANCiogZm9sbG93aW5nIEJhbWZvcnRoIChkZXNjcmliaW5nIEJhbGxpbmcgZXF1YXRpb24pICIuLi5tb3JlIHJlYWxpc3RpY2FsbHksIHRoZSBldGhhbm9sIHlpZWxkIGlzIG1vcmUgbGlrZSAwLjQ2IGcgYW5kIGNhcmJvbiBkaW94aWRlIDAuNDQgZyBmcm9tIDEgZyBzdWdhciIgIChwLiAxMzcgaW4gQnJld2luZyBNYXRlcmlhbHMgYW5kIFByb2Nlc3NlczogQSBQcmFjdGljYWwgQXBwcm9hY2ggdG8gQmVlciBFeGNlbGxlbmNlLCBFZGl0ZWQgYnkgQ2hhcmxlcyBCYW1mb3J0aCBBY2FkZW1pYyBQcmVzcywgMjAxNikNCmBgYHtyfQ0KRlBIY2FsYyRjYWxjQ08yX2luY3JlYXNlIDwtIEZQSGNhbGMkZGVsdGEuQUJXKigwLjQ0LzAuNDYpDQojI2NvbnZlcnQgY2FsY0NPMl9pbmNyZWFzZSAoaW4gZy8xMDBtTCkgdG8gY2FsY3VsYXRlZCBDTzIgdm9sdW1lcyBhZGRlZA0KIyMgZy9MID0gMTAqIGcvMTAwbUwNCiMjIFRoZSBjb252ZXJzaW9uIGZhY3RvciBmcm9tIHZvbHVtZXMgb2YgQ08yIHRvIENPMiBieSB3ZWlnaHQgKGcvTCkgaXMgMS45Ni4gRm9yIGV4YW1wbGU6IDIuNSB2b2x1bWVzIHggMS45NiA9IDQuOSBnL2wuDQpGUEhjYWxjJGNhbGNDTzJ2b2xzX2luY3JlYXNlIDwtIEZQSGNhbGMkY2FsY0NPMl9pbmNyZWFzZSoxMC8xLjk2DQpgYGANCg0KDQoNCiMjIGRlZmluZSAiRlBIIiBhcyBhbW91bnQgcHJvZHVjZWQgcGVyIGFtb3VudCBkcnktaG9wcyBhZGRlZCAoYWxsIGluIGcvMTAwbUwpOg0KYGBge3J9DQojIyAgRlBIID0gRm9sZCBQcm9kdWN0aW9uIGR1ZSB0byBIb3BzIChmb2xkLWluY3JlYXNlIGJ5IG1hc3M6IGFtb3VudCBvZiBnaXZlbiBlbmRwb2ludCByZWxhdGl2ZSB0byBhbW91bnQgb2YgaG9wcyBhZGRlZCkNCiMjICANCkZQSGNhbGMkRlBIX0V0T0ggPSBGUEhjYWxjJGRlbHRhLkFCVy9GUEhjYWxjJGhvcHNfZ18xMDBtTA0KRlBIY2FsYyRGUEhfQ08yID0gRlBIY2FsYyRjYWxjQ08yX2luY3JlYXNlL0ZQSGNhbGMkaG9wc19nXzEwMG1MDQpGUEhjYWxjJEZQSF9wbGF0byA9IEZQSGNhbGMkZGVsdGEucGxhdG8vRlBIY2FsYyRob3BzX2dfMTAwbUwNCg0KIyMgcmVwbGFjaW5nIHRpbWUtemVybyB0aW1lIHZhbHVlcyB3aXRoIGEgdmVyeSBzbWFsbCBudW1iZXIgKHJhdGhlciB0aGFuIGV4YWN0bHkgemVybykgd2lsbCBwcmV2ZW50IGlzc3VlcyB3aXRoIGFuYWx5c2lzIG9mIG5vbmxpbmVhciBtb2RlbHMNCkZQSGNhbGNbRlBIY2FsYyRkYXlzb25ob3BzPT0wLF0kZGF5c29uaG9wcyA8LSAwLjAxDQoNCg0KDQojIyBhbmQgc2F2ZSB0aGUgdHJhbnNmb3JtZWQgZGF0YSB0byBjc3Y6DQp3cml0ZS5jc3YoRlBIY2FsYywiRlBIY2FsYy5jc3YiLCByb3cubmFtZXMgPSBGQUxTRSkNCkZQSGNhbGMgPC0gcmVhZC5jc3YoIkZQSGNhbGMuY3N2Iiwgc3RyaW5nc0FzRmFjdG9ycyA9IFRSVUUpDQoNCmRmPC0gRlBIY2FsYyAlPiUgZ3JvdXBfYnkoRVhQVG5ldykgJT4lDQogIHN1bW1hcmlzZV9hdCh2YXJzKGhvcHNfZ18xMDBtTCxwb3VuZHNfYmJsLGRlbHRhLkFCViwgZGVsdGEuQUJXLCBkZWx0YS5wbGF0bywgRlBIX3BsYXRvLCBGUEhfRXRPSCwgRlBIX0NPMiwgY2FsY0NPMnZvbHNfaW5jcmVhc2UpLGZ1bnMocm91bmQobWVhbiguKSwgMikpKSAlPiUNCiAgYXJyYW5nZShkZXNjKEZQSF9FdE9IKSkgDQpkZg0KYGBgDQoNCg0KDQoNCg0KDQojIGludGVybWlzc2lvbj8NCg0KDQoNCg0KDQoNCiMgdmlzdWFsaXplDQojIyB2ZWN0b3J+dmVjdG9yKnZlY3RvciBwbG90cw0KYGBge3IgcmVncmVzc2lvbi4yfQ0KZGYgPC1GUEhjYWxjDQpwMTwtIGdncGxvdChkZiwgYWVzKHk9RlBIX0V0T0gseD1PRSwgY29sb3I9Y29udGFjdF9kYXlzKSkgKyBnZW9tX3BvaW50KHNpemU9MikNCnAyPC0gZ2dwbG90KGRmLCBhZXMoeT1GUEhfRXRPSCx4PUFERiwgY29sb3I9Y29udGFjdF9kYXlzKSkgKyBnZW9tX3BvaW50KHNpemU9MikNCnAzPC0gZ2dwbG90KGRmLCBhZXMoeT1GUEhfRXRPSCx4PUFCVywgY29sb3I9Y29udGFjdF9kYXlzKSkgKyBnZW9tX3BvaW50KHNpemU9MikNCnA0PC0gZ2dwbG90KGRmLCBhZXMoeT1GUEhfRXRPSCx4PUVhLCBjb2xvcj1jb250YWN0X2RheXMpKSArIGdlb21fcG9pbnQoc2l6ZT0yKQ0KZ3JpZC5hcnJhbmdlKHAxLCBwMiwgcDMsIHA0LCBuY29sID0gMikNCmBgYA0KDQoNCg0KIyMgeH55Kig0IHZlY3RvcnMpIGJ5IGNvbG9yDQpgYGB7ciByZWdyZXNzaW9uLjRwbG90LmNvbG9yfQ0KZGYgPC0gRlBIY2FsYw0KeDwtIGRmJEVhDQp5PC0gZGYkQUJXICAgICNGUEhfRXRPSA0KDQpwMTwtIGdncGxvdChkZiwgYWVzKHgseSwgY29sb3I9Zm9ybV9vZl9ob3BzKSkgKyBnZW9tX3BvaW50KHNpemU9MikNCnAyPC0gZ2dwbG90KGRmLCBhZXMoeCx5LCBjb2xvcj1icmV3X2RhdGUpKSArIGdlb21fcG9pbnQoc2l6ZT0yKQ0KcDM8LSBnZ3Bsb3QoZGYsIGFlcyh4LHksIGNvbG9yPXJvdXNlKSkgKyBnZW9tX3BvaW50KHNpemU9MikNCnA0PC0gZ2dwbG90KGRmLCBhZXMoeCx5LCBjb2xvcj1wb3VuZHNfYmJsKSkgKyBnZW9tX3BvaW50KHNpemU9MikNCmdyaWQuYXJyYW5nZShwMSwgcDIsIHAzLCBwNCwgbmNvbCA9IDIpDQpgYGANCg0KIyMgM0QgcGxvdHMgZm9yIGh0bWwgd2l0aCBsaWJyYXJ5KHBsb3RseSkNCnNlZSBodHRwczovL3Bsb3RseS1yLmNvbS90aGUtcGxvdGx5LWNvb2tib29rLmh0bWwgZm9yIG92ZXJ2aWV3IGFuZCBzb21lIHZpc3VhbGl6YXRpb24gaW5zcGlyYXRpb24uIA0KYGBge3J9DQojIGludGVyYWN0aXZlIDNEIHBsb3RzIHdpdGggbGlicmFyeShwbG90bHkpDQpkZiA8LSBGUEhjYWxjICMgJT4lIGZpbHRlcihleHB0PT0iIDJBIikNCg0KIyBtYWtlcyBodG1sIHZlcnkgc2xvdyB0byByZW5kZXIuLi5jb21tZW50IG91dA0KI3Bsb3RfbHkoZGYsIHggPSB+ZGF5c29uaG9wcywgeSA9IH5BQlcsIHogPSB+RWEpICU+JSBhZGRfbWFya2Vycyhjb2xvciA9IH5leHB0KQ0KYGBgDQoNCiMjIEFtdW5hdGVndWkgIlVzaW5nIENvcnJlbGF0aW9ucyBUbyBVbmRlcnN0YW5kIFlvdXIgRGF0YSINCmBgYHtyIEFtdW5hdGVndWl9DQpteWRhdGE8LUZQSGNhbGMgJT4lIGRwbHlyOjpzZWxlY3QoZXhwdCx2YXJpZXR5LGZvcm1fb2ZfaG9wcyxwb3VuZHNfYmJsLHNwZWNpYWxfY29uZGl0aW9ucyxyb3VzZSxkYXlzb25ob3BzLERIX3RlbXAsYnJld19kYXRlLEFCVyxTRyxPRSxBREYsUkRGLGNhbGNDTzJ2b2xzX2luY3JlYXNlLEZQSF9DTzIsIEZQSF9FdE9IKSAgIyMgbGFzdCBvbmUgaXMgMTAwJSBpbiBqDQoNCiMjIG5vdGUgdXNlIG9mICJkcGx5cjo6c2VsZWN0IiBiZWNhdXNlIG9uZSBvZiB0aGVzZSBwYWNrYWdlcyBpcyBjb25mbGljdGluZyB3aXRoIGRwbHlyIGNvbW1hbmRzIDooKQ0KDQojIyBmb2xsb3dpbmcgIE1hbnVlbCBBbXVuYXRlZ3VpICBodHRwczovL3d3dy55b3V0dWJlLmNvbS93YXRjaD92PWlnUFEtcEk4QmpvDQojIyBVc2luZyBDb3JyZWxhdGlvbnMgVG8gVW5kZXJzdGFuZCBZb3VyIERhdGE6IE1hY2hpbmUgTGVhcm5pbmcgV2l0aCBSIA0KIyNmdW5jdGlvbnMgZm9yIGZsYXR0ZW5TcXVhcmVNYXRyaXgNCmNvci5wcm9iIDwtIGZ1bmN0aW9uIChYLCBkZnI9bnJvdyhYKSAtMikgew0KICBSPC0gY29yKFgsIHVzZT0icGFpcndpc2UuY29tcGxldGUub2JzIikNCiAgYWJvdmU8LSByb3coUikgPCBjb2woUikNCiAgcjIgPC0gUlthYm92ZV1eMg0KICBGc3RhdDwtIHIyICogZGZyLygxLXIyKQ0KICBSW2Fib3ZlXSA8LSAxLSBwZihGc3RhdCwgMSwgZGZyKQ0KICBSW3JvdyhSKSA9PSBjb2woUildIDwtIE5BDQogIFINCn0NCmZsYXR0ZW5TcXVhcmVNYXRyaXggPC0gZnVuY3Rpb24obSkgew0KICBpZiggKGNsYXNzKG0pICE9ICJtYXRyaXgiKSB8IChucm93KG0pIT1uY29sKG0pKSkgc3RvcCgiTXVzdCBiZSBhIHNxdWFyZSBtYXRyaXguIikNCiAgaWYoIWlkZW50aWNhbChyb3duYW1lcyhtKSwgY29sbmFtZXMobSkpKSBzdG9wKCJSb3cgYW5kIGNvbHVtbiBuYW1lcyBtdXN0IGJlIGVxdWFsLiIpDQogIHV0IDwtIHVwcGVyLnRyaShtKQ0KICBkYXRhLmZyYW1lKGkgPSByb3duYW1lcyhtKVtyb3cobSlbdXRdXSwNCiAgICAgICAgICAgICBqID0gcm93bmFtZXMobSlbY29sKG0pW3V0XV0sDQogICAgICAgICAgICAgY29yPXQobSlbdXRdLA0KICAgICAgICAgICAgIHA9bVt1dF0pDQp9DQoNCiMjIGxpYnJhcnkoY2FyZXQpIHRvIGR1bW1pZnkgZXZlcnl0aGluZyAodHVybiBhbGwgY2hhcmFjdGVycyZmYWN0b3JzIGludG8gY29sdW1uczsgIGlnbm9yZXMgbnVtYmVycyBhbmQgaW50ZWdlcnMpDQpkbXk8LSBkdW1teVZhcnMoIiB+IC4iLGRhdGEgPSBteWRhdGEpDQpteWR1bW1pZmllZGRhdGE8LSBkYXRhLmZyYW1lKHByZWRpY3QoZG15LCBuZXdkYXRhID0gbXlkYXRhKSkNCmNvck1hdCA9IGNvcihteWR1bW1pZmllZGRhdGEpDQpjb3JNYXN0ZXJMaXN0PC0gZmxhdHRlblNxdWFyZU1hdHJpeChjb3IucHJvYihteWR1bW1pZmllZGRhdGEpKSAjIyBsaXN0IG9mIGFsbCBjb3JyZWxhdGlvbnMNCiMjIG9yZGVyIGJ5IHN0cmVuZ3RoIG9mIGNvcnJlbGF0aW9uDQpjb3JsaXN0PC0gY29yTWFzdGVyTGlzdCAlPiUgYXJyYW5nZSgtYWJzKGNvck1hc3Rlckxpc3QkY29yKSkgDQp3cml0ZS5jc3YoY29ybGlzdCxwYXN0ZTAoIkZMQVQgY29ycmVsYXRpb24gbWF0cml4Xy5jc3YiKSkNCmNvcmxpc3QgPC0gY29ybGlzdCAlPiUgZHBseXI6OmZpbHRlcihqPT0iRlBIX0V0T0giKSAgICMjIGZpbHRlciBzcGVjaWZpYyBlbmRwb2ludA0KaGVhZChjb3JsaXN0LDUwKQ0KYGBgDQoNCiMjIHBhaXJzLnBhbmVscyBjb3JyZWxhdGlvbiBtYXRyaXggZnJvbSBsaWJyYXJ5KHBzeWNoKQ0KYGBge3J9DQojIyBzcGVjaWZ5IGludGVyZXN0aW5nIHZhcmlhYmxlczogIA0KaW50ZXJlc3Rpbmd2YXJpYWJsZXM8LWMoIkFCVyIsICJwb3VuZHNfYmJsIiwgImRheXNvbmhvcHMiLCAiY2FsY0NPMnZvbHNfaW5jcmVhc2UiKSANCnBhaXJzLnBhbmVscyhteWR1bW1pZmllZGRhdGFbYyhpbnRlcmVzdGluZ3ZhcmlhYmxlcywgIkZQSF9FdE9IIildKQ0KYGBgDQoNCg0KI21vZGVsDQpPYnNlcnZpbmcgdGhlIGltcGFjdHMgb2YgZHJ5aG9wcGluZyBpbiB0aGUgcHJlc2VuY2Ugb2YgbGl2ZSB5ZWFzdCBoYXMgbGVkIG1hbnkgYnJld2luZyBwcm9mZXNzaW9uYWxzIHRvIHVuZGVyc3RhbmQgdGhhdCBGUEggaXMgYSBmdW5jdGlvbiBvZiBtYW55IG9mIHRoZSB2YXJpYWJsZXMgYWJvdmUgaW5jbHVkaW5nIGhvcCB2YXJpZXR5LGZvcm1fb2ZfaG9wcyxoYXJ2ZXN0WWVhcixPWCxESF90ZW1wLGRheXNvbmhvcHMscm91c2UscG91bmRzX2JibC4uLi4gTWFueSBoYXZlIGludHVpdGl2ZWx5IGNyZWF0ZWQgYSBtb2RlbCBpbiB0aGVpciBoZWFkcyAod2l0aG91dCBuZWNlc3NhcmlseSB0aGlua2luZyBvZiBpdCBhcyBzdWNoKSBhbmQgc2tpbGxmdWxseSBhZGp1c3QgcHJvY2VzcyB3aGVuIG5lY2Vzc2FyeSB0byBhY2NvdW50IGZvciB0aGlzIHBoZW5vbWVub24uICBJbiBsaW5lYXIgbW9kZWxpbmcsIG91ciBmdW5jdGlvbiB3aWxsIHRha2Ugb24gdGhlIGZvcm06DQokRlBIID0gIGludGVyY2VwdCArIFxiZXRhX3sxfVhfezF9ICsgXGJldGFfezJ9WF97Mn0gKyAuLi4gKyBcYmV0YV97bn1YX3tufSQgd2hlcmUgJFxiZXRhJCB2YWx1ZXMgYXJlIHdoYXQgd2UncmUgYXR0ZW1wdGluZyB0byBkZXJpdmUgaW4gdGhpcyBtb2RlbGluZyBleGVyY2lzZSwgYW5kIFggdmFsdWVzIGFyZSAoY29sbGVjdGl2ZWx5KSBhIHBhcnRpY3VsYXIgc2V0IG9mIGNvbmRpdGlvbnMuDQoNCg0KIyMgbGluZWFyIG1vZGVsaW5nIG92ZXJ2aWV3IDEgKGNyZWF0ZSBtb2RlbHMpDQpgYGB7cn0NCmRmPC1GUEhjYWxjDQoNCmxtMTwtbG0oZGYkU0d+ZGYkT0UpDQpsbTI8LWxtKGRmJFNHfmRmJEFERikNCmxtMzwtbG0oZGYkU0d+ZGYkQUJXKQ0KbG00PC1sbShkZiRTR35kZiRFYSkNCnBhcihtZnJvdyA9IGMoMiwgMiksIG9tYSA9IGMoMCwgMCwgMCwgMCkpDQpwbG90KGRmJFNHfmRmJE9FKQ0KYWJsaW5lKGxtMSkNCnBsb3QoZGYkU0d+ZGYkQURGKQ0KYWJsaW5lKGxtMikNCnBsb3QoZGYkU0d+ZGYkQUJXKQ0KYWJsaW5lKGxtMykNCnBsb3QoZGYkU0d+ZGYkRWEpDQphYmxpbmUobG00KQ0KYGBgDQoNCkZvciBlYWNoIG9mIHRoZSBmb3VyIGNhc2VzIGFib3ZlLCB3ZSBjYW4gZWFzaWx5IHNlZSB3aXRoIG91ciBleWViYWxscyB0aGUgZGVncmVlIHRvIHdoaWNoIGEgbGluZWFyIG1vZGVsIGZpdHMgdGhlIGRhdGEuICBSZXNpZHVhbCBwbG90cyBhbmQgUi1zcXVhcmVkIHZhbHVlcyBhcmUgY29tbW9ubHkgdXNlZCB0byBleHByZXNzIHRoaXMgZ29vZG5lc3Mtb2YtZml0ICh0aGF0IHdlIGNhbiBlYXNpbHkgc2VlIHdpdGggb3VyIGV5ZWJhbGxzISkuDQoNCiMjIGxpbmVhciBtb2RlbGluZyBvdmVydmlldyAyICh2aWV3IHJlc2lkdWFsIHBsb3RzKQ0KYGBge3J9DQpkZjwtRlBIY2FsYw0KDQpsbTE8LWxtKGRmJFNHfmRmJE9FKQ0KbG0yPC1sbShkZiRTR35kZiRBREYpDQpsbTM8LWxtKGRmJFNHfmRmJEFCVykNCmxtNDwtbG0oZGYkU0d+ZGYkRWEpDQoNCnBhcihtZnJvdyA9IGMoMiwgMiksIG9tYSA9IGMoMCwgMCwgMCwgMCkpDQpwbG90KGxtMSRyZXNpZHVhbHMpDQpwbG90KGxtMiRyZXNpZHVhbHMpDQpwbG90KGxtMyRyZXNpZHVhbHMpDQpwbG90KGxtNCRyZXNpZHVhbHMpDQojc3VtbWFyeShsbTQpDQpgYGANCg0KDQojIyBjb21wYXJlIGxpbmVhciBtb2RlbHMgdXNpbmcgbWVtaXNjIHBhY2thZ2UNCmBgYHtyfQ0KZGY8LUZQSGNhbGMNCg0KbG0xPC1sbShkZiRTR35kZiRPRSkNCmxtMjwtbG0oZGYkU0d+ZGYkQURGKQ0KbG0zPC1sbShkZiRTR35kZiRBQlcpDQpsbTQ8LWxtKGRmJFNHfmRmJEVhKQ0KDQptdGFibGUxMjM0IDwtIG10YWJsZSgiTW9kZWwgMSI9bG0xLCJNb2RlbCAyIj1sbTIsIk1vZGVsIDMiPWxtMywgIk1vZGVsIDQiPWxtNCwNCiAgICAgICAgICAgICAgICAgICAgc3VtbWFyeS5zdGF0cz1jKCJzaWdtYSIsIlItc3F1YXJlZCIsIkYiLCJwIiwiTiIpLHNob3cuZXFuYW1lcz1UKQ0KDQptdGFibGUxMjM0YiA8LSByZWxhYmVsKG10YWJsZTEyMzQsDQogICAgICAgICAgICAgICAgICAgICAgIihJbnRlcmNlcHQpIiA9ICJDb25zdGFudCIsDQogICAgICAgICAgICAgICAgICAgICAgeDEgPSAiT0UgPSBPcmlnaW5hbCBFeHRyYWN0IChnLzEwMG1MKSIsDQogICAgICAgICAgICAgICAgICAgICAgeDIgPSAiQURGID0gQXBwYXJlbnQgRGVncmVlIG9mIEZlcm1lbnRhdGlvbiAoJSkiLA0KICAgICAgICAgICAgICAgICAgICAgIHgzID0gIkFCVyA9IEV0aGFub2wgKHcvdykiLA0KICAgICAgICAgICAgICAgICAgICAgIHg0ID0gIkVyID0gUmVzaWR1YWwgRXh0cmFjdCAoZy8xMDBtTCkiDQogICAgICAgICAgICAgICAgICAgICAgKQ0KbXRhYmxlMTIzNA0KI3Nob3dfaHRtbChtdGFibGUxMjM0YikNCg0KDQpgYGANCg0KDQoNCg0KIyMgbGluZWFyIG1vZGVscyBvZiBGUEgNCmBgYHtyfQ0KZGYgPC0gRlBIY2FsYw0KbG0xPC1sbShGUEhfRXRPSH5kYXlzb25ob3BzLCBkYXRhPWRmKQ0KbG0yPC1sbShGUEhfRXRPSH5kYXlzb25ob3BzKmZvcm1fb2ZfaG9wcywgZGF0YT1kZikNCmxtMzwtbG0oRlBIX0V0T0h+ZGF5c29uaG9wcypyb3VzZSwgZGF0YT1kZikNCmxtNDwtbG0oRlBIX0V0T0h+ZGF5c29uaG9wcypmb3JtX29mX2hvcHMqcm91c2UsIGRhdGE9ZGYpDQoNCm10YWJsZTEyMzQgPC0gbXRhYmxlKCJNb2RlbCAxIj1sbTEsIk1vZGVsIDIiPWxtMiwiTW9kZWwgMyI9bG0zLCAiTW9kZWwgNCI9bG00LA0KICAgICAgICAgICAgICAgICAgICBzdW1tYXJ5LnN0YXRzPWMoInNpZ21hIiwiUi1zcXVhcmVkIiwiRiIsInAiLCJOIiksc2hvdy5lcW5hbWVzPVQpDQptdGFibGUxMjM0YiA8LSByZWxhYmVsKG10YWJsZTEyMzQsDQogICAgICAgICAgICAgICAgICAgICAgIihJbnRlcmNlcHQpIiA9ICJDb25zdGFudCIsDQogICAgICAgICAgICAgICAgICAgICAgU0cgPSAiU3BlY2lmaWMgR3Jhdml0eSIsDQogICAgICAgICAgICAgICAgICAgICAgQUJXID0gIkFCVyA9IEV0aGFub2wgKHcvdykiLA0KICAgICAgICAgICAgICAgICAgICAgIEVyID0gIkVyID0gUmVzaWR1YWwgRXh0cmFjdCAoZy8xMDBtTCkiDQogICAgICAgICAgICAgICAgICAgICAgKQ0KbXRhYmxlMTIzNA0KI3Nob3dfaHRtbChtdGFibGUxMjM0YikNCmBgYA0KDQoNCg0KIyMgVGhlIFIgQm9vayAoQ3Jhd2xleSkgVGFibGUgMjAuMTogbm9ubGluZWFyIGZ1bmN0aW9ucyB1c2VmdWwgaW4gYmlvbG9neSAgIA0KVGFibGUgMjAuMS4gW1VzZWZ1bCBub24tbGluZWFyIGZ1bmN0aW9uc10oaHR0cHM6Ly93d3cuY3MudXBjLmVkdS9+cm9iZXJ0L3RlYWNoaW5nL2VzdGFkaXN0aWNhL1RoZVJCb29rLnBkZiAiTWljaGFlbCBKLiBDcmF3bGV5LiAgVGhlIFIgYm9vay4gIHAuIDczOCIpIEVYUEFOREVEOg0KDQoNCnwgRnVuY3Rpb24gQ2xhc3MgfCBuYW1lIHwgZXF1YXRpb24gfCBleGFtcGxlIGNvZGUgfGV4YW1wbGUgYXBwbGljYXRpb25zfA0KfDotLS0tLS0tLS0tfDotLS0tLTp8Oi0tLS0tLS18Oi0tLS0tfDotLS0tLXwNCnwgKipBc3ltcHRvdGljIGZ1bmN0aW9ucyoqfE1pY2hhZWxpc+KAk01lbnRlbnwkeSA9XGZyYWN7YXh9ezErYnh9JHxubHMoYm9uZX5hKmFnZS8oMStiKmFnZSksc3RhcnQ9bGlzdChhPTgsYj0wLjA4KSkpIHwgZW56eW1lIHJlYWN0aW9ucyB8DQp8IHwgfCB8IG5scyhyYXRlflNTbWljbWVuKGNvbmMsYSxiKSkgfCB0YmR8DQp8IHwyLXBhcmFtZXRlciBhc3ltcHRvdGljIGV4cG9uZW50aWFsIHwgJHkgPSBhKDEg4oiSIGVee+KIkmJ4fSApJCB8bmxzKGJvbmV+YSooMS1leHAoLWMqYWdlKSksc3RhcnQ9bGlzdChhPTEyMCxjPTAuMDY0KSkgfCB0YmQgfA0KfCB8My1wYXJhbWV0ZXIgYXN5bXB0b3RpYyBleHBvbmVudGlhbCB8ICR5ID0gYSDiiJIgYmVee+KIkmN4fSQgfCBubHMoYm9uZX5hLWIqZXhwKC1jKmFnZSksc3RhcnQ9bGlzdChhPTEyMCxiPTExMCxjPTAuMDY0KSkgfCB0YmQgfA0KfCB8IHwgfCBubHMoYm9uZX5TU2FzeW1wKGFnZSxhLGIsYykpIHwgdGJkIHwNCnwgfCB8IHwgbmxzKGRlbnNpdHkgfiBTU2xvZ2lzKGxvZyhjb25jZW50cmF0aW9uKSwgYSwgYiwgYykpIHwgdGJkIHwNCnwqKlMtc2hhcGVkIGZ1bmN0aW9ucyoqIHwyLXBhcmFtZXRlciBsb2dpc3RpYyB8JHkgPSBcZnJhY3tlXnthK2J4fX17MSArIGVee2ErYnh9fSQgfCB8IHRiZCB8DQp8IHwgMy1wYXJhbWV0ZXIgbG9naXN0aWMgfCAkeSA9IFxmcmFje2F9ezEgKyBiZV574oiSY3h9fSQgfCB8IHRiZCB8DQp8IHwgNC1wYXJhbWV0ZXIgbG9naXN0aWMgfCAkeSA9IGEgKyBcZnJhY3tiLWF9ezEgKyBlXnsoY+KIkngpL2R9fSQgfCBubHMod2VpZ2h0flNTZnBsKFRpbWUsIGEsIGIsIGMsIGQpKSB8IHRiZCB8DQp8IHwgV2VpYnVsbCB8ICR5ID0gYSDiiJIgYmVee+KIkihjeF5kKX0kIHwgbmxzKHdlaWdodCB+IFNTd2VpYnVsbCh0aW1lLCBBc3ltLCBEcm9wLCBscmMsIHB3cikpIHwgdGJkIHwNCnwgfCBHb21wZXJ0eiB8ICR5ID0gYWVee+KIkmJlXnviiJJjeH19JCB8IHwgdGJkIHwNCnwgKipIdW1wZWQgY3VydmVzKiogfCBSaWNrZXIgY3VydmUgfCAkeSA9IGF4ZV574oiSYnh9JCB8IHwgdGJkIHwNCnwgfCBGaXJzdC1vcmRlciBjb21wYXJ0bWVudCB8ICR5ID0gayBleHAo4oiSZXhwKGEpeCkg4oiSIGV4cCjiiJJleHAoYil4KSQgfCBubHMoY29uY35TU2ZvbChEb3NlLCBUaW1lLCBhLCBiLCBjKSkgfCB0YmQgfA0KfCB8IEJlbGwtc2hhcGVkIHwgJHkgPSBhIGV4cCjiiJJBQlMoYngpXjIpJCB8IHwgdGJkIHwNCnwgfCBCaWV4cG9uZW50aWFsIHwgJHkgPSBhZV57Ynh9IOKIkiBjZV574oiSZHh9JCAgfCB8IHRiZCB8DQoNCg0KDQojIyBiYXNlIFIgbmxzIGZ1bmN0aW9uIGZvciBNaWNoYWVsaXMtTWVudG9uIG1vZGVsDQokeSA9XGZyYWN7YXh9ezErYnh9JA0KDQpgYGB7cn0NCg0KZXhwdDIgPC0gRlBIY2FsYyAlPiUgZHBseXI6OmZpbHRlcihleHB0PT0iIDJBIikgJT4lIGRwbHlyOjpzZWxlY3QoRlBIX0V0T0gsIHJvdXNlLHNwZWNpYWxfY29uZGl0aW9ucywgZGF5c29uaG9wcyxicmV3X2RhdGUsIHBvdW5kc19iYmwsZm9ybV9vZl9ob3BzLEFCVy5jb250cm9sX05ILGRheW9mYWRkaXRpb24pDQoNCm15ZmFjdG9yPC0gYXMuZmFjdG9yKGV4cHQyJHNwZWNpYWxfY29uZGl0aW9ucykgICNyb3VzZQ0KY29scyA8LSBhcy5udW1lcmljKG15ZmFjdG9yKQ0KbGVnZW5kLmNvbHMgPC0gYXMubnVtZXJpYyhhcy5mYWN0b3IobGV2ZWxzKG15ZmFjdG9yKSkpDQoNCm15Lk1NLm1vZGVsIDwtIG5scyhGUEhfRXRPSH5hKmRheXNvbmhvcHMvKDErYipkYXlzb25ob3BzKSxteWZhY3RvciwgZGF0YT1leHB0MixzdGFydD1saXN0KGE9OCwgYj0wLjA4KSkNCg0Kc3VtbWFyeShteS5NTS5tb2RlbCkNCg0KYGBgDQpTbyB1c2luZyB0aGUgTWljaGFlbGlzLU1lbnRlbiBlcXVhdGlvbiBhcyBvdXIgbW9kZWwsIHRoZSByZWxhdGlvbnNoaXAgYmV0d2VlbiBob3AtaW5kdWNlZCBldGhhbm9sIHByb2R1Y3Rpb24gKEZQSF9FdE9IKSBhbmQgaG9wIGNvbnRhY3QgdGltZSAoZGF5c29uaG9wcykgd291bGQgYmUgZXhwcmVzc2VkIGFzOg0KJEZQSF9FdE9IID0gXGZyYWN7MC4xODM1NzIqZGF5c29uaG9wc317MSswLjA2NjM4OCpkYXlzb25ob3BzfSQNCndpdGggYSBSZXNpZHVhbCBzdGFuZGFyZCBlcnJvciBvZiAwLjA5MzA3LiBBcyBkZXRhaWxlZCBiZWxvdywgbG9naXN0aWMgbW9kZWxzIGRvIGEgYmV0dGVyIGpvYiB0aGFuIHRoaXMsIGFuZCB1c2luZyBsaWJyYXJ5KGRyYykgbWFrZXMgaXQgZWFzaWVyIHRvIGludHJvZHVjZSBhIG11bHRpbGV2ZWwgZmFjdG9yLiAgDQoNCiMjIHByZWRpY3QgYW5kIHBsb3QgbW9kZWwNCmBgYHtyfQ0KeCA8LSBzZXEoMCw1MCwwLjEpDQp5diA8LSBwcmVkaWN0KG15Lk1NLm1vZGVsLCBsaXN0KGRheXNvbmhvcHM9eCkpDQp7cGxvdChleHB0MiRkYXlzb25ob3BzLCBleHB0MiRGUEhfRXRPSCwgcGNoPTIxLCBjb2w9InB1cnBsZSIsIGJnPSJncmVlbiIsIG1haW4gPSAiTWljaGFlbGlzLU1lbnRlbiBtb2RlbCBvZiBldGhhbm9sIHByb2R1Y3Rpb24gaW5kdWNlZCBieSBkcnktaG9wcGluZyIpDQogIGxpbmVzKHgseXYsY29sPSJibHVlIil9DQoNCmBgYA0KDQoNCg0KIyMgIFNwZWVyczIwMDM6IE5vbi1MaW5lYXIgTW9kZWxsaW5nIG9mIEluZHVzdHJpYWwgQnJld2luZyBGZXJtZW50YXRpb25zDQpSLiBBbGV4IFNwZWVycywgUGV0ZXIgUm9nZXJzLCBCcnVjZSBTbWl0aA0KSi4gSW5zdC4gQnJldy4gMTA5KDMpLCAyMjnigJMyMzUsIDIwMDMgDQo8aHR0cHM6Ly9kb2kub3JnLzEwLjEwMDIvai4yMDUwLTA0MTYuMjAwMy50YjAwMTYzLng+DQpGcmVlIEFjY2VzcyBmcm9tIHRoZSBJbnN0aXR1dGUgb2YgQnJld2luZyENCjxodHRwczovL29ubGluZWxpYnJhcnkud2lsZXkuY29tL2RvaS8xMC4xMDAyL2ouMjA1MC0wNDE2LjIwMDMudGIwMDE2My54Pg0KDQpGb2xsb3dpbmcgU3BlZXJzMjAwMywgdGhlIHJlbGF0aW9uc2hpcCBiZXR3ZWVuIHBsYXRvIGFuZCB0aW1lIGluIHByaW1hcnkgZmVybWVudGF0aW9ucyBpcyB3ZWxsIG1vZGVsZWQgYnkgYSBmb3VyIHBhcmFtZXRlciBsb2dpc3RpYyBtb2RlbDoNCg0KUExBVE8gPSBQSU5GICsgKFBfRCAvICgxKyAoRVhQKC1CKihUSU1FLU0pKSkpKQ0KDQpvcg0KDQokUExBVE8gPSBQX3tpbmZ9KyBcZnJhY3tQX0R9ezErZV57LUIodC1NKX19JA0KDQp3aGVyZQ0KDQoqIHQgPSB0aW1lIGludG8gZmVybWVudGF0aW9uDQoqIFBfRCA9ICBjaGFuZ2UgaW4gUGxhdG8gZHVyaW5nIGZlcm1lbnRhdGlvbiAoT0UgLSBQX3tpbmZ9KQ0KKiBCIH4gbWF4aW11bSBmZXJtZW50YXRpb24gcmF0ZQ0KKiBNID0gZmVybWVuYXRpb24gbWlkcG9pbnQNCiogUF9pbmYgPSBmaW5hbCBncmF2aXR5DQoNCg0Kc28gdGhlbiBmb3Igb3VyIGNhc2UNCg0KJFBMQVRPID0gUF97aW5mfSsgIFxmcmFje09FLVBfe2luZn19ezErZV57LUZfe21heH0oZGF5c29uaG9wcy1NKX19JA0KDQpQTEFUTyA9IFBJTkYgKyAoUDAgLyAoMSsgKEVYUChCKihIT1VSUy1NKSkpKSkNCg0KDQojIyAgTWFjSW50b3NoMjAxNjogQW4gRXhhbWluYXRpb24gb2YgU3Vic3RyYXRlIGFuZCBQcm9kdWN0IEtpbmV0aWNzIER1cmluZyBCcmV3aW5nIEZlcm1lbnRhdGlvbnMNCkFuZHJldyBKLiBNYWNJbnRvc2gsIE1hcmlhIEpvc2V5LCBSLiBBbGV4IFNwZWVycw0KW0ouIEFtLiBTb2MuIEJyZXcuIENoZW0uIDc0KDQpLCAyNTAtMjU3LCAyMDE2XShodHRwczovL3d3dy5hc2JjbmV0Lm9yZy9wdWJsaWNhdGlvbnMvam91cm5hbC92b2wvMjAxNi9QYWdlcy9BU0JDSi0yMDE2LTQ3NTMtMDEuYXNweCkNCg0KImlzIG9ubHkgc3RhdGlzdGljYWxseSBhZHZhbnRhZ2VvdXMgdG8gYXBwbHkgdGhlIGZpdmUtcGFyYW1ldGVyIG1vZGVsIHdoZW4gbWFueSBkYXRhDQpwb2ludHMgYXJlIGF2YWlsYWJsZS4uLiAoNzIgZnJvbSB0aGlzIGV4cGVyaW1lbnQgYXMgb3Bwb3NlZCB0byAxMCB3aGVuIHVzaW5nIFllYXN0LTE0KS4iDQoNCk1hY0ludG9zaCBmaXZlLXBhcmFtZXRlciBsb2dpc3RpYzogICRQX3sodCl9ID1cZnJhY3tQX2kgLSBQX2V9eygxK3MgKiBlXnstQih0LU0pfSleezEvc319JA0KDQpsaWJyYXJ5KGRyYykNCmdldE1lYW5GdW5jdGlvbnMoKQ0KDQpkcmMgTEwuNDogICRmKHgpID0gYyArIFxmcmFje2QtY317MStcZXhwKGIoXGxvZyh4KS1cdGlsZGV7ZX0pKX0kICAjNHBhcmFtIGxvZ2lzdGljDQpkcmMgTEwuNTogICRmKHgpID0gYyArIFxmcmFje2QtY317KDErXGV4cChiKFxsb2coeCktZSkpKV5mfSQgICAgICAjNXBhcmFtIGxvZ2lzdGljDQpkcmMgRy40ICRmKHgpID0gYyArIChkLWMpKFxleHAoLVxleHAoYih4LWUpKSkpJCAgICAgICAgICAgICAgICAgICAjNHBhcmFtIEdvbXBlcnR6ICANCmRyYyBXMS40ICRmKHgpID0gYyArIChkLWMpIFxleHAoLVxleHAoYihcbG9nKHgpLVxsb2coZSkpKSkkICAgICAgICM0cGFyYW0gV2VpYnVsbDENCmRyYyBXMi40ICRmKHgpID0gYyArIChkLWMpICgxIC0gXGV4cCgtXGV4cChiKFxsb2coeCktXGxvZyhlKSkpKSkkICM0cGFyYW0gV2VpYnVsbDINCmRyYyBCQy41OiAgJGYoeCwgYixjLGQsZSxmKSA9IGMgKyBcZnJhY3tkLWMrZnh9ezErXGV4cChiKFxsb2coeCktXGxvZyhlKSkpfSQgICMgQnJhaW4tQ291c2Vucw0KDQoNCg0KIyMgZXhwbG9yZSBub25saW5lYXIgbW9kZWxzIG9mIGhvcC1pbmR1Y2VkIFBsYXRvIGRyb3Agb3ZlciB0aW1lIHdpdGggbGlicmFyeShkcmMpIA0KYGBge3IgZHJjLm1vZGVsc30NCmRmIDwtIEZQSGNhbGMgJT4lIGZpbHRlcihleHB0PT0iIDJBIikgDQpkZiRQTEFUTy5ob3Bkcm9wIDwtIGRmJGluaXRpYWxfcGxhdG8gKyBkZiRkZWx0YS5wbGF0bw0KDQp4IDwtIGRmJGRheXNvbmhvcHMNCnkgPC0gZGYkUExBVE8uaG9wZHJvcA0KZ3JvdXA8LSBhcy5mYWN0b3IoZGYkc3BlY2lhbF9jb25kaXRpb25zKSAgICNyb3VzZQ0KY29scyA8LSBhcy5udW1lcmljKGdyb3VwKQ0KbGVnZW5kLmNvbHMgPC0gYXMubnVtZXJpYyhhcy5mYWN0b3IobGV2ZWxzKGdyb3VwKSkpDQoNCiMjIEZpdHRpbmcgbW9kZWxzIHVzaW5nIGZ1bmN0aW9uIGRybSBmcm9tIGxpYnJhcnkoZHJjKS4gc2VlIDxodHRwOi8vcnN0YXRzNGFnLm9yZy9kb3NlLXJlc3BvbnNlLWN1cnZlcy5odG1sPiBmb3Igb3ZlcnZpZXcgYW5kIDxodHRwczovL3d3dy5yZG9jdW1lbnRhdGlvbi5vcmcvcGFja2FnZXMvZHJjLz4gZm9yIGxpc3Qgb2YgbW9kZWxzIChmY3QgdmFsdWVzKSBhbmQgb3RoZXIgZnVuY3Rpb25zIGF2YWlsYWJsZSBpbiBkcmMNCg0KIyMgY3JlYXRlIG1vZGVscw0KI20uTEwuMzwtZHJtKHkgfngsZ3JvdXAsIGZjdCA9IExMLjMoKSkgIzMtcGFyYW1ldGVyIGxvZ2lzdGljIChsb3dlciBsaW1pdCBhdCAwKQ0KI20uTEwuM3U8LWRybSh5IH54LCBmY3QgPSBMTC4zdSgpKSAjMy1wYXJhbWV0ZXIgbG9naXN0aWMgKHVwcGVyIGxpbWl0IGF0IDEpDQptLkxMLjQ8LWRybSh5IH54LGdyb3VwLCBmY3QgPSBMTC40KCkpICM0LXBhcmFtZXRlciBsb2ctbG9naXN0aWMgIA0KIyBmcm9tID8/TEwuNDogIGYoeCkgPSBjICsgXGZyYWN7ZC1jfXsxK1xleHAoYihcbG9nKHgpLVxsb2coZSkpKX0gb3IgaW4gYW5vdGhlciBwYXJhbWV0ZXJpc2F0aW9uIChjb252ZXJ0aW5nIHRoZSB0ZXJtIFxsb2coZSkgaW50byBhIHBhcmFtZXRlcikgZih4KSA9IGMgKyBcZnJhY3tkLWN9ezErXGV4cChiKFxsb2coeCktXHRpbGRle2V9KSl9ICANCm0uTC40IDwtZHJtKHkgfngsZ3JvdXAsIGZjdCA9IEwuNCgpKSAjY2hhbmdpbmcgdGhlIGZjdCA9IExMLjQoKSB0byBmY3QgPSBMLjQoKSBhbGxvd3MgcGxvdHRpbmcgb24gYSBsb2cxMCBzY2FsZQ0KbS5MTDIuNDwtZHJtKHkgfngsZ3JvdXAsIGZjdCA9IExMMi40KCkpICM0LXBhcmFtZXRlciBsb2ctbG9naXN0aWMgd2l0aCBsb2coZSkgcmF0aGVyIHRoYW4gZSBhcyBhIHBhcmFtZXRlciAgDQptLkxMMi41PC1kcm0oeSB+eCxncm91cCwgZmN0ID0gTEwyLjUoKSkgI0dlbmVyYWxpc2VkIGxvZy1sb2dpc3RpYw0KIyBmcm9tID8/TEwuMi41OiAgIGYoeCkgPSBjICsgXGZyYWN7ZC1jfXsoMStcZXhwKGIoXGxvZyh4KS1lKSkpXmZ9DQptLkxMLjU8LWRybSh5IH54LGdyb3VwLCBmY3QgPSBMTC41KCkpICM1LXBhcmFtZXRlciBsb2dpc3RpYyAgDQptLlcxLjQ8LWRybSh5IH54LGdyb3VwLCBmY3QgPSBXMS40KCkpICM0LXBhcmFtZXRlciBXZWlidWxsMSAgDQptLlcyLjQ8LWRybSh5IH54LGdyb3VwLCBmY3QgPSBXMi40KCkpICM0LXBhcmFtZXRlciBXZWlidWxsMiAgDQptLkJDLjU8LWRybSh5IH54LGdyb3VwLCBmY3QgPSBCQy41KCkpICM1LXBhcmFtZXRlciBCcmFpbi1Db3VzZW5zIChob3JtZXNpcykNCm0uQVIuMzwtZHJtKHkgfngsZ3JvdXAsIGZjdCA9IEFSLjMoKSkgIzMtcGFyYW1ldGVyIFNoaWZ0ZWQgYXN5bXB0b3RpYyByZWdyZXNzaW9uDQojbS5NTS4yPC1kcm0oeSB+eCxncm91cCwgZmN0ID0gTU0uMigpKSAjMi1wYXJhbWV0ZXIgTWljaGFlbGlzLU1lbnRlbg0KbS5NTS4zPC1kcm0oeSB+eCxncm91cCwgZmN0ID0gTU0uMygpKSAjMy1wYXJhbWV0ZXIgTWljaGFlbGlzLU1lbnRlbg0KDQoNCiMjcGxvdA0KI3BhcihtZnJvdyA9IGMoMSwyKSwgb21hID0gYygwLCAwLCAwLCAwKSkNCg0Ke3Bsb3QoeCAsIHksIGNvbD1jb2xzLCBtYWluPSJyYXcgZGF0YSIpDQpsZWdlbmQoInRvcHJpZ2h0IiwgbGVnZW5kPWxldmVscyhncm91cCksIHBjaD0xNiwgY29sPWxlZ2VuZC5jb2xzKX0NCg0KI3Bsb3QobS5MTC4zLCB0eXBlPSdhbGwnLGNvbD1jb2xzLCBtYWluPSJMTC4zIChsb3dlciBsaW1pdCBhdCAwKSIpDQpwbG90KG0uTEwuNCwgdHlwZT0nYWxsJyxjb2w9Y29scywgbWFpbj0iTEwuNCBmb3VyLXBhcmFtZXRlciBsb2ctbG9naXN0aWMiKQ0KcGxvdChtLkwuNCwgdHlwZT0nYWxsJyxjb2w9Y29scywgbWFpbj0iTC40IikNCnBsb3QobS5MTC41LCB0eXBlPSdhbGwnLGNvbD1jb2xzLCBtYWluPSJMTC41IikNCnBsb3QobS5MTDIuNCwgdHlwZT0nYWxsJyxjb2w9Y29scywgbWFpbj0iTEwyLjQgZm91ci1wYXJhbWV0ZXIgbG9nLWxvZ2lzdGljIikNCnBsb3QobS5MTDIuNSwgdHlwZT0nYWxsJyxjb2w9Y29scywgbWFpbj0iR2VuZXJhbGlzZWQgbG9nLWxvZ2lzdGljIikNCnBsb3QobS5XMS40LCB0eXBlPSdhbGwnLGNvbD1jb2xzLCBtYWluPSJXZWlidWxsMSIpDQpwbG90KG0uVzIuNCwgdHlwZT0nYWxsJyxjb2w9Y29scywgbWFpbj0iV2VpYnVsbDIiKQ0KcGxvdChtLkJDLjUsIHR5cGU9J2FsbCcsY29sPWNvbHMsIG1haW49IkJyYWluLUNvdXNlbnMgKGhvcm1lc2lzKSIpDQpwbG90KG0uQVIuMywgdHlwZT0nYWxsJyxjb2w9Y29scywgbWFpbj0iU2hpZnRlZCBhc3ltcHRvdGljIHJlZ3Jlc3Npb24iKQ0KI3Bsb3QobS5NTS4yLCB0eXBlPSdhbGwnLGNvbD1jb2xzLCBtYWluPSIyLXBhcmFtZXRlciBNaWNoYWVsaXMtTWVudGVuIikNCnBsb3QobS5NTS4zLCB0eXBlPSdhbGwnLGNvbD1jb2xzLCBtYWluPSIzLXBhcmFtZXRlciBNaWNoYWVsaXMtTWVudGVuIikNCg0KYGBgDQoNCiMjIG11bHRpbGV2ZWwgbG9naXN0aWMgbW9kZWxzIHdpdGggbGlicmFyeShkcmMpDQpgYGB7ciBkcmMubW9kZWxzLmxldmVsc30NCmRmIDwtIEZQSGNhbGMgJT4lIGZpbHRlcihleHB0PT0iIDJBIikgDQpkZiRQTEFUTy5ob3Bkcm9wIDwtIGRmJGluaXRpYWxfcGxhdG8gKyBkZiRkZWx0YS5wbGF0bw0KeCA8LSBkZiRkYXlzb25ob3BzDQp5IDwtIGRmJFBMQVRPLmhvcGRyb3ANCm15ZmFjdG9yPC0gYXMuZmFjdG9yKGRmJHNwZWNpYWxfY29uZGl0aW9ucykgICNyb3VzZQ0KY29scyA8LSBhcy5udW1lcmljKG15ZmFjdG9yKQ0KbGVnZW5kLmNvbHMgPC0gYXMubnVtZXJpYyhhcy5mYWN0b3IobGV2ZWxzKG15ZmFjdG9yKSkpDQoNCiMjIGNyZWF0ZSBtb2RlbHMNCiNtLkxMLjQ8LWRybSh5IH54LG15ZmFjdG9yLCBmY3QgPSBMTC40KG5hbWVzID0gYygiU2xvcGUiLCAiTG93ZXIiLCAiVXBwZXIiLCAiTWlkcG9pbnQgb3IgRUQ1MCIpKSkgIzQtcGFyYW1ldGVyIGxvZy1sb2dpc3RpYyAoZ2VuZXJhbCBwYXJhbWV0ZXJzKQ0KI20uTEwuNDwtZHJtKHkgfngsbXlmYWN0b3IsIGZjdCA9IExMLjQobmFtZXMgPSBjKCJTbG9wZSIsICJMb3dlciIsICJVcHBlciIsICJFRDUwIikpKSAjNC1wYXJhbWV0ZXIgbG9nLWxvZ2lzdGljIChEb3NlLVJlc3BvbnNlIHBhcmFtZXRlcnMpDQptLkxMLjQ8LWRybSh5IH54LG15ZmFjdG9yLCBmY3QgPSBMTC40KG5hbWVzID0gYygiRl9tYXgiLCAiUF9pbmYiLCAiT0UiLCAiTSIpKSkgIzQtcGFyYW1ldGVyIGxvZy1sb2dpc3RpYyAgDQptLkwuNCA8LWRybSh5IH54LG15ZmFjdG9yLCBmY3QgPSBMLjQoKSkgI2NoYW5naW5nIHRoZSBmY3QgPSBMTC40KCkgdG8gZmN0ID0gTC40KCkgYWxsb3dzIHBsb3R0aW5nIG9uIGEgbG9nMTAgc2NhbGUNCm0uTEwyLjQ8LWRybSh5IH54LG15ZmFjdG9yLCBmY3QgPSBMTDIuNCgpKSAjNC1wYXJhbWV0ZXIgbG9nLWxvZ2lzdGljIHdpdGggbG9nKGUpIHJhdGhlciB0aGFuIGUgYXMgYSBwYXJhbWV0ZXIgIA0KbS5MTDIuNTwtZHJtKHkgfngsbXlmYWN0b3IsIGZjdCA9IExMMi41KCkpICNHZW5lcmFsaXNlZCBsb2ctbG9naXN0aWMNCm0uTEwuNTwtZHJtKHkgfngsbXlmYWN0b3IsIGZjdCA9IExMLjUoKSkgIzUtcGFyYW1ldGVyIGxvZ2lzdGljICANCg0KI3Bsb3QgYSBmZXcgc2lkZSBieSBzaWRlDQpwYXIobWZyb3cgPSBjKDEsIDIpLCBvbWEgPSBjKDAsIDAsIDAsIDApKQ0KcGxvdChtLkxMLjQsbG9nPSIiLCAgYnJva2VuID0gVFJVRSwgYmNvbnRyb2wgPSBsaXN0KHN0eWxlID0gInNsYXNoIiksIGNvbCA9IGMoMiw2LDMsMjMsNTYpLCBtYWluID0gIjQtcGFyYW1ldGVyIGxvZ2lzdGljIikNCnBsb3QobS5MTDIuNSxsb2c9IiIsICBicm9rZW4gPSBUUlVFLCBiY29udHJvbCA9IGxpc3Qoc3R5bGUgPSAic2xhc2giKSwgY29sID0gYygyLDYsMywyMyw1NiksIG1haW4gPSAiNS1wYXJhbWV0ZXIgbG9naXN0aWMiKQ0KDQpgYGANCg0KDQojIyBwbG90IHJlc2lkdWFscyBvZiBub25saW5lYXIgbW9kZWxzIGdlbmVyYXRlZCB3aXRoIGxpYnJhcnkoZHJjKQ0KYGBge3J9DQpwYXIobWZyb3cgPSBjKDMsIDIpLCBvbWEgPSBjKDAsIDAsIDAsIDApKQ0KDQp7cGxvdChtLk1NLjMkcHJlZHJlcywgbWFpbiA9ICJNaWNoYWVsaXMtTWVudGVuIikgIA0KICBhYmxpbmUoMCwwKX0NCntwbG90KG0uVzIuNCRwcmVkcmVzLCBtYWluID0gIldlaWJ1bGwyIikgDQogIGFibGluZSgwLDApfQ0Ke3Bsb3QobS5MTC40JHByZWRyZXMsIG1haW4gPSAiNC1wYXJhbWV0ZXIgbG9naXN0aWMiKSANCiAgYWJsaW5lKDAsMCl9DQp7cGxvdChtLkxMLjUkcHJlZHJlcywgbWFpbiA9ICI1LXBhcmFtZXRlciBsb2dpc3RpYyIpIA0KICBhYmxpbmUoMCwwKX0NCntwbG90KG0uQkMuNSRwcmVkcmVzLCBtYWluID0gIkJyYWluLUNvdXNlbnMgKGhvcm1lc2lzKSIpIA0KICBhYmxpbmUoMCwwKX0NCntwbG90KG0uTEwyLjUkcHJlZHJlcywgbWFpbiA9ICJHZW5lcmFsaXNlZCBsb2ctbG9naXN0aWMiKSANCiAgYWJsaW5lKDAsMCl9DQpgYGANCg0KDQpgYGB7cn0NCiNwaWNrIG9uZSBtb2RlbCB0byBiZSBteW1vZGVsDQpteW1vZGVsIDwtIG0uTEwuNA0KbXltb2RlbA0KY29uZmludChteW1vZGVsKQ0Kc3VtbWFyeShteW1vZGVsKQ0KDQpgYGANCg0KRWFjaCBsZXZlbCAocm91c2UgYW5kIHN0aWxsKSBoYXMgYSB1bmlxdWUgY3VydmUgc2hhcGUgdGhlcmVieSByZXN1bHRpbmcgaW4gZGlmZmVyZW50IHBhcmFtZXRlcnMgZm9yIGEgYmVzdC1maXQgbG9nLWxvZ2lzdGljIG1vZGVsLiAgTm93IHRoYXQgd2Uga25vdyB0aGlzLCBpdCBpcyBwcmFjdGljYWwgdG8gYWRkcmVzcyBlYWNoIHNldCBvZiBjb25kaXRpb25zIHNlcGFyYXRlbHkuIEJhc2VkIG9uIHRoZSA0LXBhcmFtZXRlciBsb2dpc3RpYyBtb2RlbCBMTC40IHdlIGhhdmUgZm9yIHJvdXNlZCBzYW1wbGVzOg0KDQokUExBVE9fe3JvdXNlZH0gPSBQX3tpbmZ9KyAgXGZyYWN7T0UtUF97aW5mfX17MStlXnstRl97bWF4fShkYXlzb25ob3BzLU0pfX0kDQoNCm9yDQoNCiRQTEFUT197cm91c2VkfSAgPSAxLjgwOSArIFxmcmFjezEuOTR9ezErZV57LTEuMzkyMyhkYXlzb25ob3BzLTEyLjI3NCl9fSQNCg0KYW5kIGZvciB1bnRvdWNoZWQgKHN0aWxsKSBzYW1wbGVzOg0KDQokUExBVE9fe3N0aWxsfSAgPSAyLjE0OSArIFxmcmFjezEuNjB9ezErZV57LTEuODAwNChkYXlzb25ob3BzLTcuOTU4KX19JA0KDQpQbG90dGluZyBhbmQgYW5hbHlzaXMgb3B0aW9ucyBmb3IgbXVsdGlsZXZlbCBtb2RlbHMgYXJlIGxpbWl0ZWQuICBCeSBmaWx0ZXJpbmcgdGhlIGRhdGEgdG8gZm9jdXMgb24gb25lIHNldCBvZiBjb25kaXRpb25zIGF0IGEgdGltZSB3ZSBjcmVhdGUgJ3NpbXBsZScgeX54IGxvZ2lzdGljIG1vZGVscy4gTWFueSB3b25kZXJmdWwgdmlzdWFsaXphdGlvbiB0b29scyBhcmUgYXZhaWxhYmxlIGZvciBzdWNoIHh+eSBtb2RlbHMuICBIZXJlJ3MganVzdCBhIGZldzoNCg0KIyMgc2luZ2xlIGxldmVsIGxvZ2lzdGljIG1vZGVsIHdpdGggY29uZmlkZW5jZSBpbnRlcnZhbHMgDQooc3BlY2lmeSBldmFsPWZhbHNlIGluIFIgIG5vdGVib29rIGNvZGVjaHVuayBoZWFkZXIge3IgZXZhbD1GQUxTRX0gdG8gcHJldmVudCBtdWx0aXBsZSBtZXNzYWdlcyByZWdhcmRpbmcgIlJlY3ljbGluZyBhcnJheSBvZiBsZW5ndGggMSBpbiBhcnJheS12ZWN0b3IgYXJpdGhtZXRpYyBpcyBkZXByZWNhdGVkIiBmcm9tIGFwcGVhcmluZyBpbiBodG1sKQ0KYGBge3J9DQpkZiA8LSBGUEhjYWxjICU+JSBmaWx0ZXIoZXhwdD09IiAyQSIgJiByb3VzZT09VFJVRSkgDQpkZiRQTEFUTy5ob3Bkcm9wIDwtIGRmJGluaXRpYWxfcGxhdG8gKyBkZiRkZWx0YS5wbGF0bw0KeCA8LSBhcy5udW1lcmljKGRmJGRheXNvbmhvcHMpDQp5IDwtIGRmJFBMQVRPLmhvcGRyb3ANCg0KIyMgY3JlYXRlIG1vZGVsDQpteW1vZGVsPC1kcm0oeSB+eCwgZmN0ID0gTEwuNCgpKSAjNC1wYXJhbWV0ZXIgbG9naXN0aWMgd2l0aCBsaWJyYXJ5KGRyYykNCg0KcGxvdChteW1vZGVsLCBtYWluID0gImRlZmF1bHQgKGxvZ2FyaXRobWljKSB4LWF4aXMiKQ0KcGxvdChteW1vZGVsLCBsb2c9IiIsIG1haW4gPSAibm9uLWxvZ2FyaXRobWljIHgtYXhpcyIpDQoNCiMgY3JlYXRlIHByZWRpY3Rpb24gaW50ZXJ2YWxzDQpuZXdkYXRhID0gZGF0YS5mcmFtZSh5ID0geSwgeCA9IHgpDQpjb25mOTUgPC0gYXMuZGF0YS5mcmFtZShwcmVkaWN0KG15bW9kZWwsIG5ld2RhdGEsaW50ZXJ2YWwgPSAiY29uZmlkZW5jZSIsIGxldmVsID0gMC45NSkpDQpjb25mOTUkeCA8LSB4DQpwcmVkOTUgPC0gYXMuZGF0YS5mcmFtZShwcmVkaWN0KG15bW9kZWwsIG5ld2RhdGEsIGludGVydmFsID0gInByZWRpY3Rpb24iLCBsZXZlbCA9IDAuOTUpKQ0KcHJlZDk1JHggPC0geA0KcHJlZDk5OSA8LSBhcy5kYXRhLmZyYW1lKHByZWRpY3QobXltb2RlbCwgbmV3ZGF0YSwgaW50ZXJ2YWwgPSAicHJlZGljdGlvbiIsIGxldmVsID0gMC45OTkpKQ0KcHJlZDk5OSR4IDwtIHgNCm15bW9kZWwNCmBgYA0KTm90ZTogIElmIHlvdSBjb21wYXJlIHRoZSBtdWx0aS1sZXZlbCBtb2RlbCBwYXJhbWV0ZXIgZXN0aW1hdGVzIGZvciByb3VzZT1UUlVFIHdpdGggdGhvc2UgZm9yIHVuaWxldmVsIG1vZGVsIGJhc2VkIG9uIHJvdXNlPVRSVUUgc3Vic2V0LCB0aGUgcmVzdWx0cyBhcmUgc2ltaWxhciwgYnV0IG5vdCBpZGVudGljYWwhDQoNCiMjIHBsb3QgcHJlZGljdGlvbiBjb25maWRlbmNlIGludGVydmFscw0KYGBge3J9DQoNCiMgQXN5bW1ldHJpYyByaWJib25zIGJhc2VkIG9uIHByZWRpY3Rpb24gaW50ZXJ2YWxzDQojbmV3ZGF0YSA8LSBkYXRhLmZyYW1lKHkgPSBjKDMuNSwgMywgMi45LCAyLjUsMS44LDEuNyksIHggPSBjKDEsNSwxMCwyMCwzMCw0MCkpDQojbmV3ZGF0YSA8LSB0aWR5ZmVybWkgJT4lIGZpbHRlcihiYXRjaCA9IFNFTEVDVEJBVENIKSAlPiUgc2VsZWN0KGRheXMscGxhdG8pIA0KcXBsb3QoZGF0YT1wcmVkOTUsIHg9eCwgeT1QcmVkaWN0aW9uLCB5bWluPUxvd2VyLCB5bWF4PVByZWRpY3Rpb24sIGdlb209InJpYmJvbiIsIGZpbGw9SSgicmVkIiksIGFscGhhPUkoMC4yKSkgKw0KZ2VvbV9yaWJib24oZGF0YT1wcmVkOTUsIGFlcyh4PXgsIHltaW49UHJlZGljdGlvbiwgeW1heD1VcHBlciksIGZpbGw9SSgiYmx1ZSIpLCBhbHBoYT1JKDAuMikpICsNCmdlb21fbGluZShkYXRhPXByZWQ5NSwgYWVzKHg9eCwgeT1QcmVkaWN0aW9uKSwgY29sb3I9SSgiZ3JlZW4iKSwgbHdkPTEpKw0KZ2VvbV9wb2ludChkYXRhPW5ld2RhdGEsIGFlcyh4PXgsIHk9eSwgeW1pbj1OVUxMLCB5bWF4PU5VTEwpLCBzaXplPTEsIGNvbD0iYmx1ZSIpKw0KIHlsYWIoInkiKQ0KDQojIFZpc3VhbGlzZSBpbnRlcnZhbHMNCiMgYmFzZWQgb24gTWF1cml0cyBFdmVycyAoaHR0cHM6Ly9zdGFja292ZXJmbG93LmNvbS9xdWVzdGlvbnMvNDk0NDQ0ODkvbm9ubGluZWFyLXJlZ3Jlc3Npb24tcHJlZGljdGlvbi1pbi1yP3JxPTEpDQpkYXRhLmZyYW1lKHg9eCwgeT15KSAlPiUgZ2dwbG90KGFlcyh4LCB5KSkgKw0KZ2VvbV9wb2ludCgpICsNCmdlb21fbGluZShkYXRhID0gcHJlZDk1LCBhZXMoeCA9IHgsIHkgPSBQcmVkaWN0aW9uKSkgKw0KZ2VvbV9yaWJib24oZGF0YSA9IHByZWQ5OTksIGFlcyh4ID0geCwgeW1pbiA9IExvd2VyLCB5bWF4ID0gVXBwZXIpLGZpbGw9SSgicGluayIpLGFscGhhID0gMC40KSsNCmdlb21fcmliYm9uKGRhdGEgPSBwcmVkOTUsIGFlcyh4ID0geCwgeW1pbiA9IExvd2VyLCB5bWF4ID0gVXBwZXIpLGZpbGw9SSgiZ3JlZW4iKSxhbHBoYSA9IDAuNCkrDQpnZW9tX3JpYmJvbihkYXRhID0gY29uZjk1LCBhZXMoeCA9IHgsIHltaW4gPSBMb3dlciwgeW1heCA9IFVwcGVyKSxmaWxsPUkoInB1cnBsZSIpLGFscGhhID0gMC40KTsNCg0KYGBgDQoNCg0KIyBzdW1tYXJ5DQoNCkZvciBjYXNlcyB3aGVyZSAyMDE2IENlbnRlbm5pYWwgaG9wcyBhcmUgYWRkZWQgdG8gdGhpcyBwYXJ0aWN1bGFyIGJhc2UgYmVlciAoc2FtcGxlZCBpbnRvIDEyb3ogYm90dGxlcykgbmVhciB0aGUgZW5kIG9mIHByaW1hcnkgZmVybWVudGF0aW9uIGF0IHJvb20gdGVtcGVyYXR1cmUgYW5kIGJvdHRsZXMgcmVtYWluIHVudG91Y2hlZCAoc3RpbGwpLCB0aGUgNC1wYXJhbWV0ZXIgbG9naXN0aWMgZXF1YXRpb24gd2l0aCB0aGUgZm9sbG93aW5nIHNldHMgb2YgcGFyYW1ldGVycyBpcyBhIHJlYXNvbmFibGUgbW9kZWwgb2YgdGhlIHN1YnNlcXVlbnQgaG9wLWluZHVjZWQgcGxhdG8gZHJvcDoNCiRQTEFUT197c3RpbGx9ID0gZihkYXlzb25ob3BzKSAgPSAyLjE0OSArIFxmcmFjezEuNjB9ezErZV57LTEuODAwNChkYXlzb25ob3BzLTcuOTU4KX19JA0KDQpUaGUgZm9sbG93aW5nIDQtcGFyYW1ldGVyIGxvZ2lzdGljIGVxdWF0aW9uIGlzIGEgcmVhc29uYWJsZSBtb2RlbCBmb3IgY2FzZXMgaWRlbnRpY2FsIHRvIHRoZSBhYm92ZSwgYnV0IHdoZXJlIGJvdHRsZXMgYXJlIHJvdXNlZCBvbmNlIGRhaWx5Og0KJFBMQVRPX3tyb3VzZWR9ID0gZihkYXlzb25ob3BzKSAgPSAxLjgwOSArIFxmcmFjezEuOTR9ezErZV57LTEuMzkyNShkYXlzb25ob3BzLTEyLjI3Mil9fSQNCg0KVGhlIHJlc2lkdWFscyBmb3IgdGhlIDUtcGFyYW1ldGVyIGxvZ2lzdGljIG1vZGVscyBhcmUgc2ltaWxhciB0byB0aG9zZSBvYnRhaW5lZCBmb3IgdGhlIDQtcGFyYW1ldGVyIGVxdWF0aW9uLCBidXQgaW4gdGhlIGNhc2Ugb2YgdW5yb3VzZWQgc2FtcGxlcyBhIHZpc3VhbCBpbnNwZWN0aW9uIHJldmVhbHMgdmVyeSBkaWZmZXJlbnQgY3VydmVzLiAgVGhlIDQtcGFyYW1ldGVyIGxvZ2lzdGljIHByZWRpY3RzIGEgbXVjaCBzaG9ydGVyICJsYWcgdGltZSIgdGhhbiBkb2VzIHRoZSA1LXBhcmFtZXRlciBsb2dpc3RpYy4gIE1vcmUgZGF0YSBwb2ludHMgaW4gdGhlIGRheXMgYWZ0ZXIgZHJ5aG9wcGluZyB3b3VsZCBzaGVkIGxpZ2h0IG9uIHRoZSBxdWVzdGlvbiBhcyB0byB3aGljaCBtb2RlbCBpcyBzdXBlcmlvci4NCg0KT25seSBhIHNpbmdsZSB2YXJpYWJsZSAocm91c2luZyB2cy4gc3RpbGwpIGFtb25nIHNldmVyYWwgaHVuZHJlZCB2YXJpYWJsZXMgZW5jb21wYXNzaW5nIHJhdyBtYXRlcmlhbHMsIG1hY2hpbmVzLCBwZW9wbGUsIHByb2Nlc3MsIGFuZCBnYWdlcyAobWVhc3VyZW1lbnRzKSByZXN1bHRlZCBpbiBxdWl0ZSBkaWZmZXJlbnQgZXN0aW1hdGVkIHBhcmFtZXRlcnMgZm9yIGEgNC1wYXJhbWV0ZXIgbG9naXN0aWMgbW9kZWwuICBUaGVzZSByZXN1bHRzIGhpZ2hsaWdodCB0aGUgZmFjdCB0aGF0IGFsbCBwcm9jZXNzIHZhcmlhYmxlcyBrbm93biB0byBzaWduaWZpY2FudGx5IGltcGFjdCB0aGUgZW5kcG9pbnRzIHdlIHdpc2ggdG8gY29udHJvbCBtdXN0IHRoZW1zZWx2ZXMgYmUgZGVmaW5lZCwgY29udHJvbGxlZCBhbmQvb3IgZG9jdW1lbnRlZCBpZiBtb2RlbHMgc3VjaCBhcyB0aGVzZSBhcmUgdG8gYmUgdXNlZnVsIHRvIGNoYXJhY3Rlcml6ZSB0aGUgcHJvY2VzcyBpbiBxdWVzdGlvbi4gIA0KDQojc2Vzc2lvbiBpbmZvDQpgYGB7cn0NCnNlc3Npb25JbmZvKCkNCmBgYA0KDQo=